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 或库发布的插件不需要第二份打包器配置。

    binlib

    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.tssrc/index.ts 这两个约定会补齐它们。配置总是胜出,而 bin: false / lib: false 则用于退出。

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

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

    声明文件

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

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

    可执行封套

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

    // src/cli.ts — the whole CLI entry a consumer writes
    export const main = async (argv: readonly string[]): Promise<number> => {
      // ...
      return 0;
    };

    封套会 await main(process.argv.slice(2)),把数值返回值作为进程退出码,并让逃逸的 rejection 走 Node 的顶层失败路径(堆栈打到 stderr,退出码 1)。没有 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 async 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[];
    }
    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'],
    } 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 async 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 };
    }

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

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

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

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

    宿主产物中的路由式 CLI

    包 bin 只能到达安装了 npm 包的用户,而 hook、Skill 与脚本是随宿主产物一起交付的。因此构建还会把同一张 编译后的命令图发射进每个所选 target,路径为 <target>/bin/<plugin-name>.mjs(若有任一命令需要渲染, 则再加上 bin/<plugin-name>-flight.mjs)。每个内置 target 都发布了准入它的 cli 能力。产物 bin 是一个 自包含的 ESM 模块,没有 shebang 也没有可执行位——像 scripts/*.mjs 一样以 node <plugin-root>/bin/<plugin-name>.mjs <command> 运行。帮助、argv 解析、输出模式、退出码与信号都与 包 bin 一致;唯一的区别是,在宿主未提供 AGENT_BUNDLE_PLUGIN_ROOT 时,工作区级持久状态锚定在产物根目录 (bin/ 的父目录,与生成的 MCP worker 使用同一个回退),而不是 $PWD/.agent-bundle/state,这样一起安装 的 CLI 与服务器观察到的是同一份存储。

    从其他表面引用它时使用插件根 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。包构建的 dist/bin/<plugin-name>.js 保持不变。

    把 MCP 工具投影进 CLI

    routes.mcpCommands 把生成式 MCP 服务器的工具加入同一张命令图与同一个可执行文件,即使项目完全没有 src/cli/** 路由也可以。true 选中每个符合条件的工具;对象形式接受匹配 <server>:<tool> 身份的 includeexclude 模式,其中 * 是唯一的通配符。

    每个被投影的工具以 <plugin-bin> <server> <tool> 运行,协议工具名逐字保留。它唯一的输入选项是 --input,接受一个 JSON 对象。只有当工具的静态 MCP annotations 明确设置了 readOnlyHint: true 时 它才是只读的;其余工具都被视为可变更,并在没有 --yes 时失败关闭。每个声明的模式都必须至少匹配一个 符合条件的工具,拼写错误会以 AB4822 失败,并列出可用的身份。

    发布标识

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

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

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

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

    打包与安装器

    当包输出与至少一个 Claude、Codex 或 Cursor 宿主包在同一项目内构建时,框架还会输出一个自包含、相对 包路径的安装器,位于 dist/bin/<plugin-name>.js——若该名称已被占用则为 <plugin-name>-install.js, 两者都被占用时再追加数字后缀。请在 package.jsonbin 中声明对应取值。它的语法是 install <host> [--scope <scope>] [--json],帮助中只列出已构建的宿主;它通过 import.meta.url 而 不是调用者的工作目录来定位随包发布的产物目录,因此无论当前目录在哪里,从 node_modules 中都能正常 工作。没有任何 npm 生命周期会执行安装。

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

    prepack 运行发布构建与 npm pack --dry-run --json --ignore-scripts,随后对精确的包与产物清单、 清单哈希、包 bin 目标以及发布版本一致性设卡。把它用作 npm 的 prepack 脚本;--ignore-scripts 可防止递归。

    预构建 payload

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

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

    打包器逃生舱

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

    这个逃生舱是有边界的。框架的 invariant 钩子在你的 tools.rspack 之后运行,解析后的配置断言也仍在 合并之后运行。破坏产物契约的取值——异步 chunk、输出根目录、自包含性——会以硬诊断让构建失败,而不是 悄悄覆盖契约。保留的模块 specifier 也以同样方式受到保护:把 agent-bundle/mcp-entryagent-bundle/metaagent-bundle/mcp-apps 外部化的逃生舱会让构建失败——对静态可见的 externals 在配置检查阶段失败,对函数形式的则通过构建后扫描失败。逃生舱定制的是代码如何编译,绝不是产物承诺 了什么

    有一个双引擎注意事项:产物脚本、MCP 入口、钩子包装层与包构建通过 Rslib 编译,运行在 @rslib/core 内嵌的打包器版本之下;而 MCP App 视图通过工作区固定的 @rsbuild/core 编译。因此,从单独安装的 @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

    它会把构建所组合的每个输出的合成配置,按构建实际降级的样子原样导出;由于用的就是构建所用的同一批 函数,这份导出不可能与真实编译结果发生漂移。被框架包装的入口还会附带生成的包装模块源码。

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

    下一步