For AI agents: the complete documentation index is available at https://scriptedalchemy.github.io/agent-bundle/zh/llms.txt, the full documentation bundle is available at https://scriptedalchemy.github.io/agent-bundle/zh/llms-full.txt, and this page is available as Markdown at https://scriptedalchemy.github.io/agent-bundle/zh/guide/authoring/package-entries.md.
跳至主要内容
  • 简体中文
  • CLI 与库包入口

    agent-bundle 之于智能体插件,正如 Rslib 之于库:一份 agent-bundle.config.ts、一个 CLI、由框架拥有 的入口生命周期,以及一个被祝福的、通往打包器的逃生舱。输出宿主产物的同一份配置,也负责 npm 包构建, 因此同时作为 CLI 或库发布的插件不需要第二份打包器配置。

    bin 与 lib

    import { 
    const defineConfig: (config: AgentBundleConfig | ConfigFactory) => AgentBundleConfig | ConfigFactory
    defineConfig
    } from 'agent-bundle/config';
    export default
    function defineConfig(config: AgentBundleConfig | ConfigFactory): AgentBundleConfig | ConfigFactory
    defineConfig
    ({
    AgentBundleConfig.bin?: AgentBundleBinConfig | undefined
    bin
    : { 'my-plugin': './src/cli.ts' },
    AgentBundleConfig.lib?: AgentBundleLibConfig | undefined
    lib
    : {
    AgentBundleLibEntry.dts?: boolean | undefined

    Emit type declarations next to the library output — Rslib's bundleless dts mode, a .d.ts graph beside the bundle rather than one rolled-up file. Defaults to true.

    dts
    : true,
    AgentBundleLibEntry.entry: string
    entry
    : './src/index.ts' },
    AgentBundleConfig.output?: AgentBundleOutputConfig | undefined
    output
    : {
    AgentBundleOutputConfig.distPath?: string | undefined

    The artifact output directory of agent-bundle build, relative to the project root. Defaults to dist. The per-invocation CLI --output flag still wins, but remains subject to the same project-root containment check; absolute and external output paths are unsupported.

    distPath
    : 'artifact' },
    AgentBundleConfig.plugin: AgentBundlePluginConfig
    plugin
    : {
    AgentBundlePluginConfig.description?: string | undefined
    description
    : 'A CLI plus a plugin.',
    AgentBundlePluginConfig.name: string
    name
    : 'my-plugin' },
    AgentBundleConfig.targets?: string[] | undefined
    targets
    : ['portable', 'claude'],
    });
    配置输出说明
    bin: { '<name>': './src/cli.ts' }dist/bin/<name>.js自执行的 ESM 包,带 #!/usr/bin/env node shebang 与可执行位。
    lib: { entry: './src/index.ts', dts: true }dist/<stem>.js 以及 dist/**/*.d.ts单入口 ESM profile,node target,es2022 语法。

    配置沉默时,src/cli.ts 与 src/index.ts 这两个约定会补齐它们。配置总是胜出,而 bin: false / lib: false 则用于退出。

    由于包输出位于 dist/,宿主产物必须放在别处:命令行默认的产物根目录是 artifact/,而在带有包入口的项目上把 output.distPath 或 --output 指向 dist 就是 AB4706。dist 是强制忽略的目录:包输出 永远不会进入项目源码快照,也不会进入 Skill 与资源发现。

    输出会被暂存并原子发布,其 provenance,字节数、SHA-256,以及排序后的项目相对源码输入,会像产物文件 一样在构建结果中报告。

    声明文件

    lib.dts 默认为 true。声明生成会从项目中解析 typescript,因此请把它加为 devDependency。它把 lib 入口所在的源码目录编译为独立的 program:编译选项通过 extends 来自项目的 tsconfig.json,rootDir 固定为入口所在目录,并且只纳入该子树,测试文件永远不会让包构建失败,也不会污染它。声明文件平铺落在 dist/ 下,每个源码模块一个 .d.ts。

    lib profile 刻意做得很薄。需要多格式矩阵(UMD、多入口、逐格式 tsconfig)的包已经超出这个 profile, 它真正想要的是 Rslib。那是唯一一种仍然保留第二份打包器配置的情形,而且是主动选择的。

    可执行封套

    一个 bin 条目,或一个产物脚本,只要其模块导出了 main,或默认导出一个函数,就会获得生成的进程 封套:

    // src/cli.ts — the whole CLI entry a consumer writes
    import type { ExecutableMainContext } from 'agent-bundle';
    
    export const main = async (
      argv: readonly string[],
      { terminal }: ExecutableMainContext,
    ): Promise<number> => {
      if (terminal.stderr.color !== 'none') { /* 在 stderr 上绘制进度 */ }
      return 0;
    };

    封套会 await main(process.argv.slice(2), { terminal }),把数值返回值作为进程退出码,并让逃逸的 rejection 走 Node 的顶层失败路径(堆栈打到 stderr,退出码 1)。terminal 是该进程的 终端能力:stdout 与 stderr 各自的 TTY 属性、颜色与尺寸,以及两者是否指向同一 目标,由封套内联的一个无依赖模块在 main 运行前探测一次,普通脚本或 bin 不会为此加载任何运行时。 包 bin 的 hostSurface 为 cli,产物脚本的为 script,因此同时发布到两个表面的模块看到的是它实际 被启动的那个表面。只声明一个参数的 main 照常工作,第二个参数只是没有被读取。没有 main 导出的 自执行模块则逐字节直接打包,也不会收到探测结果。

    路由式 CLI

    src/cli/** 表面会编译成一个由框架生成的可执行文件,取代手写的分发器,也取代该项目的 src/cli.ts bin 约定。嵌套即身份:src/cli/library/audit.ts 以 <bin> library audit 运行。

    // src/cli/inspect.ts — the whole command a consumer writes
    import type { CliRouteConfig, 
    interface CliRouteProps<InputSchema extends RouteSchema>

    Props received by every routed CLI command's default function.

    Read transport-owned invocation, host, session, actor, and workspace axes with await agent() from @agent-bundle/runtime. Every identity axis is Observed; unavailable axes carry a typed reason, and parsed command input cannot override request identity.

    CliRouteProps
    } from 'agent-bundle';
    import {
    import z
    z
    } from 'zod';
    export const
    const config: {
        description: string;
        positionals: string[];
        inputJsonSchema: {
            type: "object";
            additionalProperties: false;
            properties: {
                maxFiles: {
                    type: "number";
                };
                root: {
                    type: "string";
                };
            };
            required: string[];
        };
    }
    config
    = {
    CliRouteConfig.description?: string | undefined
    description
    : 'Inspect a bounded source tree without changing it.',
    CliRouteConfig.positionals?: readonly string[] | undefined

    The inputSchema keys consumed as bare arguments, in order. All but the last must be scalar; a trailing z.array(...) key is variadic. Keys not named here become --options.

    positionals
    : ['root'],
    CliRouteConfig.inputJsonSchema?: RouteInputSchema | undefined

    Execution-free input metadata for forms and CLI flags; the original schema owns validation.

    inputJsonSchema
    : {
    RouteInputSchema.type: "object"
    type
    : 'object',
    RouteInputSchema.additionalProperties: false
    additionalProperties
    : false,
    RouteInputSchema.properties: Readonly<Record<string, RouteInputPropertySchema>>
    properties
    : {
    maxFiles: {
        type: "number";
    }
    maxFiles
    : {
    RouteInputNumberSchema.type: "number"
    type
    : 'number' },
    root: {
        type: "string";
    }
    root
    : {
    RouteInputStringSchema.type: "string"
    type
    : 'string' } },
    RouteInputSchema.required?: readonly string[] | undefined
    required
    : ['root'],
    }, } satisfies CliRouteConfig; export const
    const inputSchema: z.ZodObject<{
        maxFiles: z.ZodOptional<z.ZodNumber>;
        root: z.ZodString;
    }, z.core.$strict>
    inputSchema
    =
    import z
    z
    .
    function object<{
        maxFiles: z.ZodOptional<z.ZodNumber>;
        root: z.ZodString;
    }>(shape?: {
        maxFiles: z.ZodOptional<z.ZodNumber>;
        root: z.ZodString;
    } | undefined, params?: string | {
        error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
        message?: string | undefined | undefined;
    } | undefined): z.ZodObject<{
        maxFiles: z.ZodOptional<z.ZodNumber>;
        root: z.ZodString;
    }, z.core.$strip>
    object
    ({
    maxFiles: z.ZodOptional<z.ZodNumber>
    maxFiles
    :
    import z
    z
    .
    function number(params?: string | z.core.$ZodNumberParams): z.ZodNumber
    number
    ().
    _ZodNumber<$ZodNumberInternals<number>>.int(params?: string | z.core.$ZodCheckNumberFormatParams): z.ZodNumber

    Consider z.int() instead. This API is considered legacy; it will never be removed but a better alternative exists.

    int
    ().
    _ZodNumber<$ZodNumberInternals<number>>.min(value: number, params?: string | z.core.$ZodCheckGreaterThanParams): z.ZodNumber
    min
    (1).
    _ZodNumber<$ZodNumberInternals<number>>.max(value: number, params?: string | z.core.$ZodCheckLessThanParams): z.ZodNumber
    max
    (256).
    ZodType<any, any, $ZodNumberInternals<number>>.optional(): z.ZodOptional<z.ZodNumber>
    optional
    (),
    root: z.ZodString
    root
    :
    import z
    z
    .
    function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)
    string
    ().
    _ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodString
    min
    (1),
    }).
    ZodObject<{ maxFiles: ZodOptional<ZodNumber>; root: ZodString; }, $strip>.strict(): z.ZodObject<{
        maxFiles: z.ZodOptional<z.ZodNumber>;
        root: z.ZodString;
    }, z.core.$strict>

    Consider z.strictObject(A.shape) instead

    strict
    ();
    export const
    const resultSchema: z.ZodObject<{
        scanned: z.ZodNumber;
    }, z.core.$strict>
    resultSchema
    =
    import z
    z
    .
    function object<{
        scanned: z.ZodNumber;
    }>(shape?: {
        scanned: z.ZodNumber;
    } | undefined, params?: string | {
        error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
        message?: string | undefined | undefined;
    } | undefined): z.ZodObject<{
        scanned: z.ZodNumber;
    }, z.core.$strip>
    object
    ({
    scanned: z.ZodNumber
    scanned
    :
    import z
    z
    .
    function number(params?: string | z.core.$ZodNumberParams): z.ZodNumber
    number
    ().
    _ZodNumber<$ZodNumberInternals<number>>.int(params?: string | z.core.$ZodCheckNumberFormatParams): z.ZodNumber

    Consider z.int() instead. This API is considered legacy; it will never be removed but a better alternative exists.

    int
    () }).
    ZodObject<{ scanned: ZodNumber; }, $strip>.strict(): z.ZodObject<{
        scanned: z.ZodNumber;
    }, z.core.$strict>

    Consider z.strictObject(A.shape) instead

    strict
    ();
    export default async function
    function inspect({ input, signal }: CliRouteProps<typeof inputSchema>): Promise<{
        scanned: number;
    }>
    inspect
    (
    {
    input: {
        root: string;
        maxFiles?: number | undefined;
    }
    input
    ,
    signal: AbortSignal
    signal
    }:
    interface CliRouteProps<InputSchema extends RouteSchema>

    Props received by every routed CLI command's default function.

    Read transport-owned invocation, host, session, actor, and workspace axes with await agent() from @agent-bundle/runtime. Every identity axis is Observed; unavailable axes carry a typed reason, and parsed command input cannot override request identity.

    CliRouteProps
    <typeof
    const inputSchema: z.ZodObject<{
        maxFiles: z.ZodOptional<z.ZodNumber>;
        root: z.ZodString;
    }, z.core.$strict>
    inputSchema
    >,
    ) {
    signal: AbortSignal
    signal
    .
    AbortSignal.throwIfAborted(): void

    The throwIfAborted() method throws the signal's abort reason if the signal has been aborted; otherwise it does nothing.

    MDN Reference

    throwIfAborted
    ();
    return {
    scanned: number
    scanned
    :
    input: {
        root: string;
        maxFiles?: number | undefined;
    }
    input
    .
    maxFiles?: number | undefined
    maxFiles
    ?? 0 };
    }

    编译器把 config.inputJsonSchema 静态投影到 argv 上,生成分层帮助(每一层都有 --help,根层有 --version), 并在组合产物中输出 bin/<plugin-name>.mjs。运行时,外壳解析命令路径、 解析并强制转换 argv、用模块自身的 schema 校验、在带类型的 Agent 请求上下文中执行默认函数、向 stdout 写出一行规范 JSON,并确定性地映射退出码:

    退出码含义
    0成功;在 config.exitCode: 'result' 下则取结果的 exitCode。
    1执行失败。
    2用法或输入失败。
    130 / 143SIGINT / SIGTERM,它们同时会抵达路由的 AbortSignal。

    当 inputSchema 拒绝解析后的 argv 时,shell 会用 CLI 的措辞逐条说明问题,而不是打印原始的 schema issue JSON:按用户输入的形式指出参数(--max-files、<root>,或投影 MCP 命令的 --input.<path>)、期望值与 实际收到的值,每个问题一行,随后是该命令的准确用法行与 --help 提示,全部写到 stderr,退出码为 2:

    Invalid value for --max-files: expected number <= 55000; received 300000.
    Usage: curator doctor [options] <root>
    Run 'curator doctor --help' for usage.

    在 --json 下 stdout 保持为空,stderr 只输出一行规范化的 {"error":{"code":"CLI_INPUT_INVALID","issues":[...],"usage":"..."}};在 --ndjson 下,事件流中只包含一个 携带相同 error 对象的 type: "error" 事件。

    .tsx 命令路由把默认函数换成一个具有相同 props 的 默认 Server Component,并通过运行时分发器 针对同级的 bin/<plugin-name>-flight.mjs worker 渲染。它由此获得 脚本与资源中描述的四种输出模式。路由式 CLI 项目需要把 @agent-bundle/runtime 作为依赖,因为生成的可执行文件通过它装配请求上下文。

    渲染式命令运行在一个挂钟默认为 60 秒的渲染会话中。合理运行时间更长的命令,例如在两小时的构建上执行 <bin> await <ticket>,以 config.render: { maxElapsedMs } 声明自己的预算(以毫秒计的正整数,最多 24 小时,否则报 AB4835);编译后的命令把它带进生成的可执行文件,投影的 MCP 命令(routes.mcpCommands) 则继承其工具的预算。纯 .ts 命令没有渲染会话,会拒绝该键。参见渲染预算。

    退出方式:bin: false、routes.cli: 'conventional',或给某一段路径加 _ 前缀。

    在 MCP 与 CLI 之间共享同一份 schema

    运行时的 inputSchema 和 resultSchema 可以导入,也可以由工厂创建。编译器检查导出是否存在, 生成的可执行文件加载真实 schema。TypeScript 从这些导出推导输入和输出类型,包括默认值和转换。

    若需要不执行模块的检查、Workbench 表单或命名 CLI flag,请在工具的 defineTool 对象中声明字面量 inputJsonSchema。 它接受 additionalProperties: false 的对象、字符串/数字/布尔标量、字符串枚举、标量数组, 以及可选的 required、description 和 default 元数据。 运行时 schema 仍负责校验、默认值和转换,元数据不会替代它。

    import { defineTool } from 'agent-bundle/routes';
    
    export default defineTool({
      inputJsonSchema: {
        type: 'object',
        additionalProperties: false,
        properties: {
          laneKey: { type: 'string' },
          limit: { type: 'number' },
        },
      },
      inputSchema,
      resultSchema,
    }, handler);

    没有这些元数据的独立 CLI 路由或工具投影默认接受 --input JSON。 显式声明 input: 'json' 可在已有元数据时选择 JSON 模式。 flag 和位置参数映射需要元数据;工具缺少元数据时报告 AB4845。 元数据或独立 argv 规则无效时报告 AB4814。编译器不解释 Zod 表达式,也不跟随 schema 别名链; AB4838 和 AB4839 已退役。

    将一个工具投影为惯用命令

    同级 <tool>.cli.ts 是同一工具的 CLI 表面投影,不是新路由。它可以声明命令路径、位置参数和 flag 名称, 操作身份仍为 tool:<server>/<tool>。

    该模块位于工具旁边,绝不放在 src/cli/** 下。Status 只需重命名;解析器已经发出规范键,因此不需要 mapInput:

    // src/mcp/hauler/tools/hauler_status.cli.ts
    import type { CliProjectionConfig } from 'agent-bundle/routes';
    import type { inputSchema } from './hauler_status.js';
    
    export const config = {
      command: ['status'],
      confirm: false,
      description:
        'Show the queue, in-flight cargo work, lanes, and admission state.',
      flags: {
        laneKey: {
          description: 'Only requests in this lane key',
          name: 'lane',
        },
        limit: { description: 'Recent rows to show' },
        statuses: {
          description: 'Only these statuses (repeatable)',
          name: 'status',
        },
        tickets: {
          description: 'Only these tickets (repeatable)',
          name: 'ticket',
        },
      },
    } satisfies CliProjectionConfig<typeof inputSchema>;

    Request 需要位置参数,以及一个同步或异步的 mapInput:它会用 process.cwd() 填充规范必填的 cwd,并拆分 逗号分隔的 --after 列表:

    // src/mcp/hauler/tools/hauler_request.cli.ts
    import type { CliProjectionConfig } from 'agent-bundle/routes';
    import type { z } from 'zod';
    
    import { parseTicketList } from '../../../client/parse.js';
    import type { inputSchema } from './hauler_request.js';
    
    export const config = {
      command: ['request'],
      confirm: false,
      description:
        'Submit a background cargo request and print its ticket: ' +
        'hauler request [--after cc-N] -- cargo check -p foo',
      flags: {
        after: {
          description:
            'Tickets that must finish first (repeatable, or comma-separated)',
        },
        cwd: {
          description: 'Workspace directory (default: current directory)',
          required: false,
        },
        host: { description: 'Agent host name for attribution' },
        session: { description: 'Agent session id for attribution' },
      },
      positionals: ['argv'],
    } satisfies CliProjectionConfig<typeof inputSchema>;
    
    type CliInput = Omit<z.input<typeof inputSchema>, 'cwd' | 'after'> & {
      readonly after?: readonly string[];
      readonly cwd?: string;
    };
    
    export const mapInput = (
      input: CliInput,
    ): z.input<typeof inputSchema> => {
      const after = parseTicketList(input.after ?? []);
      return {
        ...input,
        cwd: input.cwd ?? process.cwd(),
        ...(after.length === 0 ? {} : { after }),
      };
    };

    CliProjectionConfig<Schema> 从 agent-bundle/routes 导出。flags 与 positionals 的键受限于 keyof z.input<Schema>。config 使用与路由模块相同的静态提取语法(会解包 satisfies)。 mapInput 是普通的命名导出,其存在性会被静态记录,并且只由 CLI bin 加载;MCP worker 永远不会看到 该模块。mapInput 是表面适配器,不是领域逻辑:它只把 argv 重塑或补默认值成规范输入(重命名、拆分 列表、派生工作目录)。领域校验与行为留在操作里,它的 inputSchema 精化以及它的组件。在 mapper 里重写一遍命令逻辑,正是投影要去掉的重复。由于 mapper 仍可能拆分或重命名一个值,投影导出了 mapInput 的命令,其 z.enum 取值由规范 schema 在 mapInput 运行之后判定,因此 --regions us,uk 能到达 mapper,而枚举之外的值仍以 2 退出,并以 CLI 拼写报告为 Invalid value for --<option>[<index>]: expected one of: …。没有 mapInput 的投影则像 src/cli 路由一样在解析 argv 时就拒绝未知的枚举值。

    键含义
    command命令路径段;默认为 [tool]。每一段都必须通过 safeIdentitySegment。
    description帮助文本;默认为工具的 config.description。
    positionals按顺序作为裸参数使用的规范键(规则与 src/cli 路由相同)。
    flags.<key>.nameCLI 拼写(kebab-case,不带前导短横线);默认为 kebab(key)。
    flags.<key>.aliases额外的长格式 --spellings,使用 kebab-case,不带前导短横线。
    flags.<key>.description覆盖输入元数据的描述。
    flags.<key>.defaultshell 在 mapInput 之前应用的 CLI 专用默认值(记录在 projection.defaults 中;schema 的 .default() 由 zod 在 mapInput 之后应用)。
    flags.<key>.requiredfalse 会放宽一个规范必填键;仅当导出了 mapInput 时合法。
    aliases命令别名(规则与 src/cli 路由的 config.aliases 相同)。
    confirm默认为 !(tool config.annotations.readOnlyHint === true)。
    exitCode'result' 或 'zero';默认为工具的 config.exitCode ?? 'zero'。
    input'json':命令像批量 mcpCommands 投影一样,通过 --input 以一个 JSON 对象接收工具的规范输入,而不是逐字段的 flag。用于 argv 语法无法表达的 schema。不能与 flags、positionals 或 mapInput 导出同时使用(AB4844)。

    flag 无法拼写的 schema:JSON 输入

    命名 flag 使用显式输入元数据。嵌套输入可以直接使用 JSON;没有元数据时默认采用该模式, 也可以声明 input: 'json':

    examples/audiobook-curator/src/mcp/curator/tools/convert_audiobook.cli.ts
    import type { CliProjectionConfig } from 'agent-bundle/routes';
    
    import type { inputSchema } from './convert_audiobook.js';
    
    export const config = {
      command: ['convert'],
      confirm: false,
      description: 'Plan or apply a verified conversion to one chaptered M4B.',
      input: 'json',
    } satisfies CliProjectionConfig<typeof inputSchema>;
    audiobook-curator convert --input \
      '{"conversion":{"author":"Frank Herbert","output":"./Dune.m4b","selection":"./selection.json","title":"Dune"}}' \
      --json

    该对象原样到达工具自己的 inputSchema,因此默认值、refinement 与 transform 的应用方式与 tools/call 完全一致;schema 问题以 --input.<path> 报告,非对象的 --input 会在任何工具运行之前 以退出码 2 被拒绝。命令保留投影控制的其余一切,command、aliases、description、confirm 与 exitCode,帮助、inspect 与制品清单都会记录这一模式(projection.input: "json")。 策展器的先计划后执行转换设置 confirm: false,嵌套的 conversion.apply 字段仍是显式修改门禁。

    flags 以工具的 RouteContract.input 的规范键为键。发现阶段会在派生身份之前排除 .cli.{ts,tsx}, 并将该文件与同级的 .{ts,tsx} 配对。这个后缀在 src/mcp/** 下保留;加 _ 前缀可以停用它。 孤立文件,或位于 resources/、prompts/ 或 apps/ 下的 .cli.*,会触发 AB4843。缺失或动态的 config、封闭集合之外的键、形状错误的字段、或在没有 mapInput 时对 规范必填键使用 required: false / CLI default,会触发 AB4844。契约中不存在的 flags/positionals 键、不符合 kebab-case、为保留名(help、json、ndjson、version,以及 启用确认时的 yes)或相互冲突的 name/别名、位置参数键上的 name 或 aliases(裸参数没有 --spelling)、启用确认的命令所投影的契约含有键 yes(无论如何拼写,shell 都会把 yes 当作确认 剥离),以及不是安全身份段的 command 段,会触发 AB4845。 每条消息都是 CLI projection <module> for tool:<server>/<tool>: <detail>.(位置错误的模块没有可指名的 工具:CLI projection <module>: <detail>.)。缺少工具 config.inputJsonSchema 的 flag 映射报告 AB4845。 运行时检查 mapInput 是否可调用并等待其结果。

    agent-bundle inspect --routes 会转储编译后的路由图,因此无需额外的 renderer 即可看到 cli.commands[].projection(module、mapInput、input?、defaults?、relaxed?)与 options[].{key,option,aliases}。Workbench 会在选中操作的 inspector 中展示这些事实。 编译后命令的 routeId 是工具 id;运行时的 invocation.kind 为 'cli',operationId 则为该工具 id。只要路由从生成的 CLI 可执行文件运行,无论命令由哪种投影机制产生,它观察到的都是 kind: 'cli'。生成的 MCP 服务器仍传入 kind: 'tool'。

    没有投影模块,就不会通过这条路径生成命令;批量选择加入的 routes.mcpCommands 对其他所有工具仍照常工作。任何投影字段都不会改变 MCP inputSchema、tools/list、annotations 或 _meta;ToolConfig.inputJsonSchema 提供可选 flag 元数据。

    短格式 -x 别名仍不受支持。mapInput 可以是同步或异步函数;shell 会等待它完成,然后执行 schema 验证。

    每种分发中的同一个路由式 CLI

    hook、Skill、脚本与 npm 消费者都使用插件根目录,因此构建把编译后的命令图只发射一次到产物根目录, 路径为 bin/<plugin-name>.mjs(若有任一命令需要渲染,则再加上 bin/<plugin-name>-flight.mjs),由所有选中宿主共享。每个内置 target 都发布了准入它的 cli 能力。产物 bin 是一个 带 Node shebang 和可执行位的自包含 ESM 模块。npm 根目录原样复制它,并让 package.json 的 bin 直接指向它,因此帮助、argv 解析、输出模式、退出码、信号与完整命令集合都逐字节相同。两种形式都从 bin/ 的上级根目录解析插件代码,并从该代码根目录推导出同一个框架状态根目录, ~/.agent-bundle/state/<plugin>-<digest>(或对应的 XDG_STATE_HOME 路径),或已设置的 AGENT_BUNDLE_STATE_ROOT,因此 CLI 与 MCP 服务器共享同一份存储。

    从其他表面引用它时使用插件根 token,Claude Skill Markdown 与 hook 命令中写 ${CLAUDE_PLUGIN_ROOT}/bin/<plugin-name>.mjs,Codex hook 中写 ${PLUGIN_ROOT}/…,Cursor hook 中写 ${CURSOR_PLUGIN_ROOT}/…,或者在编译后的脚本里用同级引用 new URL('../bin/<plugin-name>.mjs', import.meta.url)。inspect 把该 bin 记为每个 target 的一个 cli 组件,产物清单会记录这两个文件。不具备 cli 能力的 target 会省略该 bin 并报告 AB4765;宿主在同一路径 发射的文件(例如 claude.bin 目录中交付的 <plugin-name>.mjs)是 AB4766。npm 根目录复制清单声明的 这个可执行文件,不再编译另一份路由式 CLI。

    把 MCP 工具投影进 CLI

    routes.mcpCommands 把生成式 MCP 服务器的工具加入同一张命令图与同一个可执行文件,即使项目完全没有 src/cli/** 路由也可以。true 选中每个符合条件的工具;对象形式接受匹配 <server>:<tool> 身份的 include 与 exclude 模式,其中 * 是唯一的通配符。已经有同位置 <tool>.cli.ts 投影模块的工具会从这次 批量投影中排除:一个操作只编译成一个命令。仅匹配这类工具的 include 模式会触发 AB4822,并点名 投影模块。

    每个被批量投影的工具以 <plugin-bin> <server> <tool> 运行,协议工具名逐字保留。它唯一的输入选项是 --input,接受一个 JSON 对象。批量 routes.mcpCommands 投影与显式的 <tool>.cli.ts 都是 CLI 表面,因此只要路由从生成的 CLI 可执行文件运行,它观察到的都是 kind: 'cli';生成的 MCP 服务器仍 传入 kind: 'tool'。只有当工具的静态 MCP annotations 明确设置了 readOnlyHint: true 时 它才是只读的;其余工具都被视为可变更,并在没有 --yes 时失败关闭。每个声明的模式都必须至少匹配一个 符合条件的工具,拼写错误会以 AB4822 失败,并列出可用的身份。

    发布标识

    插件代码通过框架读取自身标识,而不是维护一个手写的版本模块:

    import meta, { name, packageName, packageVersion, version } from 'agent-bundle/meta';

    version 是解析出的插件版本,name 是宿主原生的插件 slug,绝不是 npm 包名,packageName 与 packageVersion 则是经过校验的 npm 轴,对未打包的开发期项目为 undefined。编译器会在每个被编译的 表面替换该 specifier:产物脚本、路由式 CLI、MCP 入口、钩子包装层、包构建,以及浏览器 MCP App 包。 它是保留 specifier,因此 tools 逃生舱无法把它外部化,任何输出包也不可能残留对它的未解析导入。

    在 agent-bundle 编译之外,已发布的该模块会抛出错误,而不是报告一个伪造的标识;完全没有发布版本号的 发布构建会被拒绝(AB4013)。

    打包 npm 根目录

    启用包输出后,dist/ 包含完整的已校验组合产物,以及包元数据、标准文档和包专用入口。生成的路由式 CLI 不会再次编译:已安装的 package.json 指向 agent-bundle.manifest.json 声明并从产物原样复制的 bin/<plugin-name>.mjs。因此 tarball 只有一个插件根和一个路由式 CLI,没有嵌套的 artifact/ 目录或 相对包的安装器包装器。没有任何 npm 生命周期会执行宿主安装。

    生成的根目录拥有自己的文件清单。源项目的 files 与 bin 字段会被替换;包入口路径 (exports、imports、main、module、types 与 typesVersions)会从 dist/ 重定位到 生成根目录。pack 与 publish 钩子会被移除,其他脚本保持原样,因此必须已经使用相对该根目录的路径。 只有清单拥有的产物文件、编译后的包入口以及标准 README、license 与 notice 文件会被携带;任意的 源项目额外文件不会被复制。

    npx agent-bundle prepack --root . --output artifact --json

    prepack 运行发布构建,并通过 process.execPath 启动 npm 的 npm-cli.js 执行 npm pack --dry-run --json --ignore-scripts(不是 PATH 上的裸 npm),随后对精确的包与产物清单、 清单哈希、包 bin 目标以及发布版本一致性设卡。生成的包元数据会移除 pack 与 publish 生命周期钩子, 避免源项目的 prepack 从 dist/ 递归;消费者安装脚本保持不变。请直接运行门禁,不要依赖源包的 生命周期钩子,然后发布它已经校验的 npm 根目录:

    npm publish ./dist --ignore-scripts

    预构建 payload

    有些项目确实自行掌控编译,例如逐入口逃生舱无法表达的、协调多环境的打包器拓扑,但仍希望使用框架 自有的宿主打包。顶层 payload 块声明构建将按字节、按稳定路径打包的已构建目录树,而 entry: { prebuilt: './dist/…' } 或 handler: { prebuilt: './dist/…' } 则把生成的宿主清单指向这些 树中的文件,而不编译它们。

    每个 payload 文件都保留其精确的相对路径,因为框架并没有编译这些文件,也就无法改写它们内部的引用。 完整性依然是内容寻址的:每个 payload 文件都会带着自己的 SHA-256 与 prebuilt 文件类别进入产物清单, 并参与项目 revision 的哈希。请先运行你自己的构建,缺失或为空的 payload 在 dev 下只是警告,以便 干净检出也能工作,但 agent-bundle build 会拒绝它。

    编译器不会打开 payload 文件,因此请用 definePrebuilt 和 runtimeDependencies 列出它们加载的裸包名。 npm 不会将其读作裸包名的条目,或 package.json 未为消费者安装的包名(dependencies、 optionalDependencies,或未标记为可选的 peer),会报 AB4751;package.json 缺失(静默)、 无法解析或位于项目根之外(AB4011)时跳过声明检查。已声明的包名在 AB7014 中视为已使用。

    import { defineConfig, definePrebuilt } from 'agent-bundle';
    
    export default defineConfig({
      payload: {
        tools: definePrebuilt({
          source: './built/tools',
          targets: ['claude'],
          runtimeDependencies: ['sharp'],
        }),
      },
    });

    打包器逃生舱

    tools.rsbuild(一个 Rsbuild 环境配置片段)与 tools.rspack(一个 Rspack 配置对象、mutator 函数或 数组)会最后合并进 agent-bundle 合成的每一份打包器配置:产物脚本、MCP 入口、钩子包装层、MCP App 视图与包构建。这正是消费者永远不需要第二份打包器配置文件的原因。

    这个逃生舱是有边界的。框架的 invariant 钩子在你的 tools.rspack 之后运行,解析后的配置断言也仍在 合并之后运行。破坏产物契约的取值,异步 chunk、输出根目录、自包含性,会以硬诊断让构建失败,而不是 悄悄覆盖契约。保留的模块 specifier 也以同样方式受到保护:把 agent-bundle/mcp-entry、 agent-bundle/meta 或 agent-bundle/mcp-apps 外部化的逃生舱会让构建失败。外部化整体上也受到同样的 边界约束:AB4725 会拒绝静态可见、点名某个包的 externals,相对路径、函数形式以及 mutator 添加的 externals 则由编译过程自身的证据判断(AB6005)。运行时路径引用也以同样方式得到保留:你的代码或 生成入口中的 new URL(…, import.meta.url) 或 new Worker(new URL(…)) 指向的是产物旁的一个文件,因此 不变量层会在逃生舱之后关闭打包器对 URL 与 worker 的静态资源处理,让该表达式原样进入产物。逃生舱定制的是 代码如何编译,绝不是产物承诺了什么。

    逃生舱是并列合并到框架配置旁边的:plugins 数组会拼接,而 Rsbuild 从不按名称去重插件。通过 tools.rsbuild.plugins 再次添加框架已经注册的插件,@rsbuild/plugin-react(rsbuild:react), 每个合成入口与每个 MCP App 视图都带着它,会让它运行两次,因此 agent-bundle validate 会以 AB4724 报告,并点名该插件及其包名。删除这一项即可;框架会替你注册它。

    有一个引擎身份注意事项:产物脚本、MCP 入口、钩子包装层与包构建通过 Rslib 编译,MCP App 视图通过 @rsbuild/core 编译;agent-bundle 固定了两者的版本,使项目只安装一个 @rsbuild/core 与一个 @rspack/core,但这个引擎是 agent-bundle 的依赖,而不是你的依赖。因此,从单独安装的 @rspack/core 导入的类,与实际执行配置的那个引擎的类身份可能并不相同。切勿针对导入的 @rspack/core 构造插件或执行 instanceof 检查,请改用传给 mutator 函数的 utils 参数:

    tools: {
      rspack: (config, { rspack }) => {
        // `rspack` is always the executing engine's own object.
        return config;
      },
    },

    想确切看到逃生舱产生了什么:

    npx agent-bundle inspect --bundler --root .
    npx agent-bundle inspect --bundler --root . --target claude --json

    它会导出构建所编译的每个输出降级后的 Rspack 配置,每个编译器一条记录,由构建自己的 Rslib(可执行 文件)或 Rsbuild(MCP App 视图)降级流程产出,并在构建即将开始编译的位置停下,因此这份导出不可能与 真实编译结果发生漂移。你能看到解析后的 resolve.alias 映射(框架运行时模块与你自己的别名并列)、 externals 列表、以 [object <ClassName>] 渲染的插件、以 [function <name>] 渲染的函数,以及 output.path:产物根目录显示为 <output>,包构建显示为 <项目根>/dist。被构建不变量拒绝的逃生舱值 (例如给保留的 agent-bundle/* 说明符设置别名)会让检查变为无效,并以 AB7001 指出被拒绝的原因。 被框架包装的入口还会附带生成的包装模块源码。

    本页所有内容的完整契约见 Entry conventions。

    下一步