@c4a/context-cli 0.7.20 → 0.7.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +6 -6
  2. package/README.zh-CN.md +6 -6
  3. package/cli.js +57 -73
  4. package/indexers/contracts/profile-contract.json +245 -245
  5. package/indexers/release-manifest.json +1 -1
  6. package/package.json +12 -12
  7. package/plugins/VERSION +1 -1
  8. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  9. package/plugins/claude/commands/context.md +77 -6
  10. package/plugins/codex/.codex-plugin/plugin.json +2 -2
  11. package/plugins/codex/skills/context/SKILL.md +77 -6
  12. package/plugins/cursor/.cursor-plugin/plugin.json +1 -1
  13. package/plugins/cursor/commands/c4a-context.md +77 -6
  14. package/plugins/skills/context/SKILL.md +77 -6
  15. package/providers/context/codes.yaml +1 -1
  16. package/providers/context/graphs/indexer.yaml +11 -7
  17. package/providers/context/graphs/workspace.yaml +5 -0
  18. package/providers/context/manifest.json +30 -30
  19. package/providers/context/provider.yaml +1 -1
  20. package/providers/context/resources/manuals/guides/knowledge-updates.md +5 -1
  21. package/providers/context/resources/manuals/guides/workspace-prepare.md +4 -0
  22. package/providers/context/resources/manuals/guides/workspace-restore.md +5 -1
  23. package/providers/context/resources/procedures/knowledge-review.md +11 -4
  24. package/providers/context/resources/procedures/knowledge-updates.md +5 -1
  25. package/providers/context/resources/procedures/package-output.md +4 -0
  26. package/providers/context/resources/procedures/production-requirements.md +5 -3
  27. package/providers/context/resources/procedures/source-boundary.md +15 -5
  28. package/providers/context/resources/procedures/work-start-report.md +19 -6
  29. package/providers/context/resources/procedures/workspace-prepare.md +4 -0
  30. package/providers/context/resources/procedures/workspace-restore.md +5 -1
  31. package/providers/context/skills/resolve-current-indexer-gate/SKILL.md +7 -2
  32. package/providers/context/skills/run-indexer-lifecycle/SKILL.md +3 -2
  33. package/providers/context/skills/work-production-stage/SKILL.md +5 -3
package/README.md CHANGED
@@ -20,18 +20,18 @@ an LLM itself.
20
20
  npm install -g @c4a/context-cli@latest
21
21
  context plugin install # global installation
22
22
  # Alternatively, install only in a repository:
23
- context plugin install --local /path/to/repo
23
+ context plugin install --local /path/to/repo --agent auto-detect
24
24
  ```
25
25
 
26
- `--local <path>` requires an explicit path without global configuration changes. Omit `--agent` to detect Claude Code, Cursor and Codex executables on PATH (including `cursor-agent`), plus Cursor/Codex in standard macOS Applications directories. Only detected hosts are installed under the repository's `.claude`, `.cursor`, or `.agents`. If none are detected, only `.agents/skills` is installed with a user hint. Stale configuration directories are not detection evidence; discovery does not verify runtime loading. Omitting `--local` keeps global installation.
26
+ `--local` requires explicit `--agent`: `claude`, `cursor`, `codex`, `all`, `auto-detect`, or `standalone`. The path is a repository root: explicit hosts install into `.claude`, `.cursor`, or `.agents` without detection. `all` installs all three. Only `auto-detect` checks existing `.claude`, `.cursor`, and `.agents` directories in the target repository, never PATH or installed desktop applications; it falls back to `.agents/skills` when none exist. `standalone` instead writes all skills directly to `<path>/skills`, with no commands or host subdirectory. All modes support `--dry-run` and preserve global configuration.
27
27
 
28
- Automatic mode accepts a repository root; paths ending in `.claude`, `.cursor`, or `.agents` use their parent as the root. Explicit `--agent` still uses the exact supplied host directory below; `--agent all` is not accepted with `--local`.
28
+ Migration from 0.7.20: add `--agent auto-detect` to bare `--local`; for explicit hosts pass the repository root, not its host directory. Global installation is unchanged. Host detection does not verify runtime loading.
29
29
 
30
30
  | Command | Local layout |
31
31
  | --- | --- |
32
- | `context plugin install --local /path/to/repo/.claude --agent claude` | Public entries in `commands/`, other skills in `skills/`; internal skills receive `user-invocable: false`, without duplicate entry skills |
33
- | `context plugin install --local /path/to/repo/.cursor --agent cursor` | Bundled `c4a-*` commands plus other skills; hiding Providers from the command menu is not guaranteed |
34
- | `context plugin install --local /path/to/repo/.agents --agent codex` | All entries remain skills; bundled `agents/openai.yaml` policies are preserved; no unsupported `commands/` directory |
32
+ | `context plugin install --local /path/to/repo --agent claude` | Public entries in `commands/`, other skills in `skills/`; internal skills receive `user-invocable: false`, without duplicate entry skills |
33
+ | `context plugin install --local /path/to/repo --agent cursor` | Bundled `c4a-*` commands plus other skills; hiding Providers from the command menu is not guaranteed |
34
+ | `context plugin install --local /path/to/repo --agent codex` | All entries remain skills; bundled `agents/openai.yaml` policies are preserved; no unsupported `commands/` directory |
35
35
 
36
36
  Local entries have no plugin namespace; the host must discover the destination. Use `--dry-run` to preview. Refresh only replaces unmodified managed content; unrelated files remain and conflicting/customized entries are rejected. Choose a fresh host directory when switching from a standalone installation to command entries, to avoid duplicates. `plugin status` still inspects global hosts only. Invocation metadata is not a CLI permission or security boundary.
37
37
 
package/README.zh-CN.md CHANGED
@@ -16,18 +16,18 @@
16
16
  npm install -g @c4a/context-cli@latest
17
17
  context plugin install # 全局安装
18
18
  # 或者,只安装到仓库:
19
- context plugin install --local /path/to/repo
19
+ context plugin install --local /path/to/repo --agent auto-detect
20
20
  ```
21
21
 
22
- `--local <路径>` 必须显式指定路径,不注册全局插件、不修改用户级配置。省略 `--agent` 时探测 PATH 中的 Claude Code、Cursor、Codex(Cursor 也检查 `cursor-agent`;macOS 另检查标准 Applications 目录中的 Cursor/Codex),安装检测到的宿主到仓库下的 `.claude`、`.cursor`、`.agents`。均未检测到时,仅安装 `.agents/skills` 并输出提示。不根据遗留配置目录判断已安装;探测不代表已验证宿主加载技能。
22
+ `--local` 必须显式搭配 `--agent`:`claude`、`cursor`、`codex`、`all`、`auto-detect` `standalone`。路径为仓库根;指定宿主直接写入对应 `.claude`、`.cursor`、`.agents` 子目录,不检测是否安装宿主。`all` 全装;只有 `auto-detect` 检测目标仓库已有的 `.claude`、`.cursor`、`.agents` 目录,不检测 PATH 或桌面应用;均不存在时兜底 `.agents/skills`。`standalone` 则直接写入 `<path>/skills`,不生成 commands 或宿主子目录。均支持 `--dry-run`,不改全局配置。
23
23
 
24
- 自动模式传仓库根路径;若末尾为 `.claude`、`.cursor` `.agents`,以其父目录为根,不嵌套宿主目录。显式指定单一 `--agent` 时仍以传入路径为精确目标,见下表(不接受 `--agent all`)。不传 `--local` 保持全局安装。
24
+ 0.7.20 迁移:裸 `--local` 补充 `--agent auto-detect`;指定宿主时传仓库根,不再传宿主目录。全局安装不变。自动探测不代表宿主已加载,安装后需刷新宿主。
25
25
 
26
26
  | 命令 | 局部目录与行为 |
27
27
  | --- | --- |
28
- | `context plugin install --local /path/to/repo/.claude --agent claude` | `commands/` 放公开入口;`skills/` 放其他技能,内部技能设置 `user-invocable: false`,不重复安装入口 Skill |
29
- | `context plugin install --local /path/to/repo/.cursor --agent cursor` | `commands/` 使用随包的 `c4a-*` 命令;`skills/` 放其他技能,不保证隐藏 Provider 的菜单入口 |
30
- | `context plugin install --local /path/to/repo/.agents --agent codex` | 全部入口保留为 skills,保留随包的 `agents/openai.yaml` 调用策略,不生成无效的 `commands/` |
28
+ | `context plugin install --local /path/to/repo --agent claude` | `commands/` 放公开入口;`skills/` 放其他技能,内部技能设置 `user-invocable: false`,不重复安装入口 Skill |
29
+ | `context plugin install --local /path/to/repo --agent cursor` | `commands/` 使用随包的 `c4a-*` 命令;`skills/` 放其他技能,不保证隐藏 Provider 的菜单入口 |
30
+ | `context plugin install --local /path/to/repo --agent codex` | 全部入口保留为 skills,保留随包的 `agents/openai.yaml` 调用策略,不生成无效的 `commands/` |
31
31
 
32
32
  宿主必须支持发现目标位置。局部入口没有插件命名空间。`--dry-run` 只预览;重复安装仅刷新本命令管理且未被修改的内容,保留无关文件;同名自定义内容或本地修改会报冲突。已有通用 skills 目录切换为 command 宿主时,请选择新目录,避免重复入口。`plugin status` 仍只检查全局宿主状态。调用元数据控制宿主入口展示/自动调用,不是 CLI 权限或安全隔离。
33
33
 
package/cli.js CHANGED
@@ -79504,7 +79504,7 @@ function productionPlanMarkdown(stage) {
79504
79504
  ...!stage.report_approved && stage.planning_complete ? [
79505
79505
  `If the user requests changes, edit ${productionAgentDirectory(stage.id)}/submissions/plan.yaml using ${join30(directory, "planning.schema.json")}.`,
79506
79506
  `Resubmit: context action complete-current --revision ${stage.id} --input ${productionAgentDirectory(stage.id)}/submissions/plan.yaml --format json`,
79507
- "Then present the updated report and wait for approval; an old report confirmation does not approve changed article goals.",
79507
+ "Then present the updated report and apply context.gate.work_start_scope; an old decision does not approve changed article goals.",
79508
79508
  ""
79509
79509
  ] : [],
79510
79510
  ...stage.gaps.map((gap) => `- ${gap.scope}: ${gap.reason}`),
@@ -80697,7 +80697,7 @@ async function completeProductionSubmission(input) {
80697
80697
  if (!stage || stage.id !== input.stage)
80698
80698
  throw submissionStateError("stale-production-stage", "Task stage no longer exists. Start a new production run; do not reuse a stale submission.");
80699
80699
  if (!stage.report_approved)
80700
- throw submissionStateError("production-report-approval-required", "Present the planned report and wait for user feedback before submitting bulk writing.");
80700
+ throw submissionStateError("production-report-approval-required", "Present the planned report and apply context.gate.work_start_scope before submitting bulk writing.");
80701
80701
  if (stage.delivery)
80702
80702
  throw submissionStateError("production-delivery-paused", "Writing is paused for delivery. Finish Review and build, or run context run --resume-writing --format json; existing task inputs remain valid.");
80703
80703
  if ((await readMaintenance(input.projectRoot)).active)
@@ -83457,8 +83457,8 @@ function renderAgents(projectName, language) {
83457
83457
  "",
83458
83458
  "## 工作区契约",
83459
83459
  "",
83460
- "版本以 package.json.version 为准。正式内容变化收尾时通过 context version inspect / record 同步递增版本并生成 changelog.yaml 和 CHANGELOG.md,具体说明由 Agent 根据 diff 和会话撰写。新增模块或扩大知识覆盖升 minor,普通修补、目录和正式状态变化升 patch;major 只由人工明确指定。.tmp 进度不算变化,build 只记录 hash。仅主协调者记录版本,不自动 commit 或发布。触发来源必填;触发用户优先使用会话中明确的飞书显示名,否则由 CLI 读取 Git 用户名。",
83461
- "版本差异使用 Git 比较正文、结构、资源、导航和模板;可通过 version inspect --base 指定交付 commit/tag。构建预览不锁定版本;复用版本前核对目标平台与 Git tag,已发布版本必须递增。无需维护根目录 Context 版本、构建或发布回执。",
83460
+ "版本以 package.json.version 为准。正式内容变化收尾时通过 context version inspect / record 同步递增版本并生成 changelog.yaml 和 CHANGELOG.md,具体说明由 Agent 根据 diff 和会话撰写。新增模块或扩大知识覆盖升 minor,普通修补、目录和正式状态变化升 patch;major 只由人工明确指定。.tmp 进度不算变化,build 只记录 hash。仅主协调者记录版本;版本记录本身不提交或发布,后续交付按本次授权和已安装的分发技能执行。触发来源必填;触发用户优先使用会话中明确的飞书显示名,否则由 CLI 读取 Git 用户名。",
83461
+ "版本差异使用 Git 比较正文、结构、资源、导航和模板;可通过 version inspect --base 指定交付 commit/tag。构建预览不锁定版本,已发布内容发生变化时必须递增版本。无需维护根目录 Context 版本、构建或发布回执。",
83462
83462
  "每个版本的 Changelog 详情不得超过 1500 字:标题、变更列表、触发来源说明及用户显示名的可见字符合计计数,含标点,不计协议字段名、版本号和日期。超出时由 Agent 在提交前合并同类变化、删除重复过程描述,压缩至上限内;保留主要变化、影响和触发来源,不直接截断,也不把一轮拆成多个版本规避限制。",
83463
83463
  "",
83464
83464
  "- 通过已安装的 Context Skill 开始或恢复工作;直接使用 CLI 时,从 `context status --format json` 开始。",
@@ -83503,8 +83503,8 @@ function renderAgents(projectName, language) {
83503
83503
  "",
83504
83504
  "## Workspace Contract",
83505
83505
  "",
83506
- "package.json.version is the workspace version. At formal-content completion, use context version inspect / record to increment SemVer and generate changelog.yaml and CHANGELOG.md together. Write descriptions from the diff and conversation. Added modules/coverage increment minor; repairs, navigation and persistent status changes increment patch. Major requires an explicit user instruction. Ignore .tmp progress; build only records hashes. The coordinator alone records versions, without automatic commits or publishing. Triggers are required; use an explicitly known conversational Lark display name, otherwise let CLI use Git user.name.",
83507
- "Compare bodies, structure, assets, navigation and templates using Git; version inspect --base selects a delivery commit/tag. Preview builds do not seal versions. Check the remote target and Git tags before amending; published versions must increase. No root Context version/build/publication receipts are required.",
83506
+ "package.json.version is the workspace version. At formal-content completion, use context version inspect / record to increment SemVer and generate changelog.yaml and CHANGELOG.md together. Write descriptions from the diff and conversation. Added modules/coverage increment minor; repairs, navigation and persistent status changes increment patch. Major requires an explicit user instruction. Ignore .tmp progress; build only records hashes. The coordinator alone records versions; recording a version does not commit or publish, and later delivery follows this task's authorization and the installed distribution skill. Triggers are required; use an explicitly known conversational Lark display name, otherwise let CLI use Git user.name.",
83507
+ "Compare bodies, structure, assets, navigation and templates using Git; version inspect --base selects a delivery commit/tag. Preview builds do not seal versions. Changes to already published content must increment the version. No root Context version/build/publication receipts are required.",
83508
83508
  "Each version's changelog details must total at most 1500 visible characters, including punctuation across the title, changes, trigger descriptions and user display name; exclude protocol keys, version and date. Before submission, compress longer entries by merging related changes and removing repeated process narration. Preserve the main changes, impact and triggers; never truncate blindly or split one iteration into multiple versions to evade the limit.",
83509
83509
  "",
83510
83510
  "- Start or resume through the installed Context Skill. When using the CLI directly, begin with `context status --format json`.",
@@ -93106,7 +93106,7 @@ async function productionWorkflowRoute(input) {
93106
93106
  revision,
93107
93107
  reason_code: resolved.reasonCode,
93108
93108
  availability: resolved.availability,
93109
- summary: report ? `Present the report and wait. After approval, write {stage: ${stage.id}, decision: approved} to ${path2}.` : writing ? "Read the issued task directories. Coordinate them sequentially unless this caller supports independent Agents; only the coordinator submits shared state." : repair ? "Add revision tasks for rejected articles using the Review feedback; accepted production responsibilities remain unchanged." : investigate ? `Review pending configured scopes: ${stage.pending_scopes.filter((scope2) => !stage.gaps.some((gap2) => gap2.scope === scope2)).join(", ")}. Check their relevance to the current request and existing approved content before assigning investigation. Submit supported article tasks and remaining pending_scopes; do not infer missing articles from this list or repeat accepted work. If the requested articles are already accepted, context run --deliver --format json enters their Review while retaining unrelated pending scopes.` : resolved.node === "resolve-production-gap" ? `Source availability gaps: ${stage.gaps.map((gap2) => `${gap2.scope}: ${gap2.reason}`).join("; ")}. These failures do not establish missing knowledge or a new investigation assignment. Identify which sources the current task actually depends on; report unrelated configured-source failures separately. Preserve the configuration. To review completed articles independently, use context run --deliver --format json; this retains pending scopes and does not approve or publish content. If these sources are required, use context source recovery-plan --format json and context source restore with explicit local or clone decisions before preparation.` : "Prepare the current stage's eligible task directories.",
93109
+ summary: report ? `Present the report and apply context.gate.work_start_scope. After the applicable scope decision, write {stage: ${stage.id}, decision: approved} to ${path2}.` : writing ? "Read the issued task directories. Coordinate them sequentially unless this caller supports independent Agents; only the coordinator submits shared state." : repair ? "Add revision tasks for rejected articles using the Review feedback; accepted production responsibilities remain unchanged." : investigate ? `Review pending configured scopes: ${stage.pending_scopes.filter((scope2) => !stage.gaps.some((gap2) => gap2.scope === scope2)).join(", ")}. Check their relevance to the current request and existing approved content before assigning investigation. Submit supported article tasks and remaining pending_scopes; do not infer missing articles from this list or repeat accepted work. If the requested articles are already accepted, context run --deliver --format json enters their Review while retaining unrelated pending scopes.` : resolved.node === "resolve-production-gap" ? `Source availability gaps: ${stage.gaps.map((gap2) => `${gap2.scope}: ${gap2.reason}`).join("; ")}. These failures do not establish missing knowledge or a new investigation assignment. Identify which sources the current task actually depends on; report unrelated configured-source failures separately. Preserve the configuration. To review completed articles independently, use context run --deliver --format json; this retains pending scopes and does not approve or publish content. If these sources are required, use context source recovery-plan --format json and context source restore with explicit local or clone decisions before preparation.` : "Prepare the current stage's eligible task directories.",
93110
93110
  commands: report || prepare || writing || repair || investigate || gap ? [{
93111
93111
  command: command2,
93112
93112
  effect: "write",
@@ -98631,7 +98631,7 @@ function resolveDocsFetchPlan(requested, runner2) {
98631
98631
  }
98632
98632
  return capabilitiesPromise.then((capabilities) => {
98633
98633
  if (!capabilities.supportsDocFormat) {
98634
- throw new LarkCliError(`${LARK_BIN} docs +fetch does not support --doc-format xml; upgrade @larksuite/cli before capturing Lark sources`, 0, "");
98634
+ throw new LarkCliError(`${LARK_BIN} docs +fetch does not support --doc-format xml; a compatible @larksuite/cli is required to capture Lark sources`, 0, "");
98635
98635
  }
98636
98636
  return { apiVersion: capabilities.apiVersion, docFormat: "xml" };
98637
98637
  });
@@ -98642,7 +98642,7 @@ function docsFetchFailureMessage(stderr, apiVersion) {
98642
98642
  const mentionsV2 = /api-version|--api-version|v2|deprecated|lark-cli update/iu.test(stderr);
98643
98643
  if (apiVersion === "v1" && mentionsV2) {
98644
98644
  return `${base}
98645
- Detected docs API v2 guidance from lark-cli. Upgrade @larksuite/cli and run \`lark-cli update\`; newer docs +fetch supports \`--api-version v2\`.`;
98645
+ Detected docs API v2 guidance from lark-cli; a compatible CLI with \`--api-version v2\` is required.`;
98646
98646
  }
98647
98647
  return base;
98648
98648
  }
@@ -98707,7 +98707,7 @@ function extractDocsFetchContent(payload, requestedFormat) {
98707
98707
  const withTitle = (result) => title2 === undefined ? result : { ...result, title: title2 };
98708
98708
  if (markdown !== undefined) {
98709
98709
  if (requestedFormat === "xml") {
98710
- throw new LarkCliError(`${LARK_BIN} docs +fetch returned Markdown despite --doc-format xml; capture stopped because the response cannot provide auditable rich-block fidelity. Upgrade lark-cli and retry.`, 0, "");
98710
+ throw new LarkCliError(`${LARK_BIN} docs +fetch returned Markdown despite --doc-format xml; capture stopped because the response cannot provide auditable rich-block fidelity. Use a compatible CLI and retry.`, 0, "");
98711
98711
  }
98712
98712
  return withTitle({ body: markdown, format: "markdown", recognizedShape: true });
98713
98713
  }
@@ -99038,7 +99038,7 @@ ${body2}`,
99038
99038
  };
99039
99039
  }
99040
99040
  var LARK_BIN = "lark-cli", MAX_FETCH_PAGES = 50, MAX_STRUCTURAL_FETCH_ATTEMPTS = 2, LarkCliNotInstalledError, LarkCliError, defaultRunner = (args, options) => new Promise((resolve8, reject) => {
99041
- const child = spawn4(LARK_BIN, args, {
99041
+ const child = spawn4(process.env.CONTEXT_LARK_CLI_BIN?.trim() || LARK_BIN, args, {
99042
99042
  ...options?.cwd === undefined ? {} : { cwd: options.cwd },
99043
99043
  stdio: ["ignore", "pipe", "pipe"]
99044
99044
  });
@@ -99193,6 +99193,7 @@ function larkTarget(entry) {
99193
99193
  }
99194
99194
  function larkErrorRecovery(error, sourceName) {
99195
99195
  const message = error instanceof Error ? error.message : String(error);
99196
+ const localCliRecovery = `Install @larksuite/cli in a private directory with \`npm install --prefix "$HOME/.cache/context/lark-cli" @larksuite/cli@latest\`, then rerun \`CONTEXT_LARK_CLI_BIN="$HOME/.cache/context/lark-cli/node_modules/.bin/lark-cli" context run capture:lark:${sourceName}\`. Keep this variable on subsequent Context commands that access Lark; do not replace the global lark-cli.`;
99196
99197
  const environmentIssue = detectExternalEnvironmentIssue(message);
99197
99198
  if (environmentIssue !== undefined) {
99198
99199
  return {
@@ -99207,13 +99208,13 @@ function larkErrorRecovery(error, sourceName) {
99207
99208
  if (error instanceof LarkCliNotInstalledError || /not installed|ENOENT/iu.test(message)) {
99208
99209
  return {
99209
99210
  reasonCode: "external.dependency-missing",
99210
- next: "Install lark-cli from https://github.com/larksuite/cli, then rerun capture"
99211
+ next: localCliRecovery
99211
99212
  };
99212
99213
  }
99213
99214
  if (/does not support --doc-format|api-version|deprecated|unsupported tool version|tool version unsupported/iu.test(message)) {
99214
99215
  return {
99215
99216
  reasonCode: "external.tool-version-unsupported",
99216
- next: "Run lark-cli update, confirm docs +fetch --help lists --doc-format, then rerun capture"
99217
+ next: localCliRecovery
99217
99218
  };
99218
99219
  }
99219
99220
  if (/empty|unsupported payload shape|not JSON|parse/iu.test(message)) {
@@ -100691,7 +100692,7 @@ var init_managedDocumentRename = __esm(() => {
100691
100692
 
100692
100693
  // src/cli.ts
100693
100694
  import { existsSync as existsSync36, realpathSync as realpathSync3 } from "node:fs";
100694
- import { dirname as dirname50, join as join115 } from "node:path";
100695
+ import { dirname as dirname49, join as join115 } from "node:path";
100695
100696
  import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL3 } from "node:url";
100696
100697
 
100697
100698
  // ../../node_modules/.bun/commander@11.1.0/node_modules/commander/esm.mjs
@@ -112690,50 +112691,33 @@ ${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
112690
112691
  init_cliFeedback();
112691
112692
  init_errors3();
112692
112693
  init_exitCode();
112693
- import { access as access6, stat as stat11 } from "node:fs/promises";
112694
- import { constants as constants7 } from "node:fs";
112695
- import { homedir as homedir2 } from "node:os";
112696
- import { basename as basename13, delimiter, dirname as dirname47, join as join112, resolve as resolve41 } from "node:path";
112694
+ import { lstat as lstat15 } from "node:fs/promises";
112695
+ import { join as join112, resolve as resolve41 } from "node:path";
112697
112696
  var HOST_DIRS = { claude: ".claude", cursor: ".cursor", codex: ".agents" };
112698
- async function detectLocalAgent(agent) {
112699
- const commands = agent === "cursor" ? ["cursor", "cursor-agent"] : [agent];
112700
- for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
112701
- for (const command3 of commands) {
112702
- for (const suffix of process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""]) {
112703
- const file = join112(directory, command3 + suffix);
112704
- try {
112705
- if (!(await stat11(file)).isFile())
112706
- continue;
112707
- await access6(file, constants7.X_OK);
112708
- return true;
112709
- } catch {}
112710
- }
112711
- }
112712
- }
112713
- if (process.platform === "darwin") {
112714
- const app = { claude: "Claude.app", cursor: "Cursor.app", codex: "Codex.app" }[agent];
112715
- if (agent !== "claude") {
112716
- for (const directory of ["/Applications", join112(homedir2(), "Applications")]) {
112717
- try {
112718
- if ((await stat11(join112(directory, app))).isDirectory())
112719
- return true;
112720
- } catch {}
112721
- }
112697
+ async function detectLocalAgent(agent, repo) {
112698
+ const path3 = join112(repo, HOST_DIRS[agent]);
112699
+ try {
112700
+ const entry = await lstat15(path3);
112701
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
112702
+ throw new ContextError(ExitCode.UserError, `Local host directory is not a regular directory: ${path3}`);
112722
112703
  }
112704
+ return true;
112705
+ } catch (error) {
112706
+ if (error.code === "ENOENT")
112707
+ return false;
112708
+ throw error;
112723
112709
  }
112724
- return false;
112725
112710
  }
112726
- async function installAutoLocalSkills(root2, path3, dryRun, detect = detectLocalAgent) {
112711
+ async function installAutoLocalSkills(root2, path3, dryRun, detect = detectLocalAgent, selectedAgent) {
112727
112712
  if (!path3.trim())
112728
112713
  throw new ContextError(ExitCode.UserError, "--local requires a non-empty repository path", {
112729
112714
  category: ErrorCategory.UserInputInvalid,
112730
- next: "Run context plugin install --local /path/to/repository."
112715
+ next: "Run context plugin install --local /path/to/repository --agent auto-detect."
112731
112716
  });
112732
- const requested = resolve41(path3);
112733
- const repo = Object.values(HOST_DIRS).some((name3) => name3 === basename13(requested)) ? dirname47(requested) : requested;
112717
+ const repo = resolve41(path3);
112734
112718
  const agents = [];
112735
112719
  for (const agent of ["claude", "cursor", "codex"]) {
112736
- if (await detect(agent))
112720
+ if (selectedAgent ? selectedAgent === "all" || agent === selectedAgent : await detect(agent, repo))
112737
112721
  agents.push(agent);
112738
112722
  }
112739
112723
  const targets = agents.length ? agents.map((agent) => ({ agent, path: join112(repo, HOST_DIRS[agent]) })) : [{ path: join112(repo, ".agents") }];
@@ -112742,14 +112726,14 @@ async function installAutoLocalSkills(root2, path3, dryRun, detect = detectLocal
112742
112726
  previews.push(await installLocalSkills(root2, target.path, true, target.agent));
112743
112727
  const header = formatFeedback({
112744
112728
  symbol: agents.length ? "✓" : "⚠",
112745
- action: "detected",
112729
+ action: selectedAgent ? "selected" : "detected",
112746
112730
  subject: "local agent targets",
112747
- headline: agents.length ? agents.join(", ") : "No supported agent detected",
112731
+ headline: agents.length ? agents.join(", ") : "No supported host directory found",
112748
112732
  body: [
112749
112733
  `repository: ${repo}`,
112750
112734
  ...targets.map((target) => `${target.agent ?? "standalone"}: ${target.path}`),
112751
- ...!agents.length ? ["Installing only .agents/skills. Configure a supported host to read this directory, or rerun with an explicit --agent and its target path."] : [],
112752
- "Host discovery does not verify runtime skill loading; refresh the host after installation."
112735
+ ...!agents.length ? ["Installing only .agents/skills. Configure a supported host to read this directory, or select an explicit --agent with the same repository root."] : [],
112736
+ "Repository directory discovery does not verify runtime skill loading; refresh the host after installation."
112753
112737
  ]
112754
112738
  });
112755
112739
  if (dryRun)
@@ -112769,7 +112753,7 @@ init_cliFeedback();
112769
112753
  init_errors3();
112770
112754
  init_exitCode();
112771
112755
  import { existsSync as existsSync35 } from "node:fs";
112772
- import { dirname as dirname49, join as join114, resolve as resolve42 } from "node:path";
112756
+ import { dirname as dirname48, join as join114, resolve as resolve42 } from "node:path";
112773
112757
  import { fileURLToPath as fileURLToPath10 } from "node:url";
112774
112758
 
112775
112759
  // src/project/pluginInstallTargets.ts
@@ -112779,8 +112763,8 @@ init_exitCode();
112779
112763
  import { execFile as execFile13 } from "node:child_process";
112780
112764
  import { existsSync as existsSync34 } from "node:fs";
112781
112765
  import { cp as cp3, mkdir as mkdir37, readdir as readdir30, readFile as readFile95, rename as rename10, rm as rm25, writeFile as writeFile29 } from "node:fs/promises";
112782
- import { homedir as homedir3 } from "node:os";
112783
- import { dirname as dirname48, join as join113 } from "node:path";
112766
+ import { homedir as homedir2 } from "node:os";
112767
+ import { dirname as dirname47, join as join113 } from "node:path";
112784
112768
  import { promisify as promisify13 } from "node:util";
112785
112769
  var execFileAsync7 = promisify13(execFile13);
112786
112770
  var MARKETPLACE_NAME = "c4a";
@@ -112834,23 +112818,23 @@ async function claudePluginInstalled(pluginId) {
112834
112818
  }
112835
112819
  }
112836
112820
  function codexHome() {
112837
- return process.env.CODEX_HOME?.trim() || join113(homedir3(), ".codex");
112821
+ return process.env.CODEX_HOME?.trim() || join113(homedir2(), ".codex");
112838
112822
  }
112839
112823
  function claudePluginCacheRoot() {
112840
112824
  const explicitRoot = process.env[CLAUDE_PLUGIN_CACHE_ROOT_ENV2]?.trim();
112841
112825
  if (explicitRoot)
112842
112826
  return explicitRoot;
112843
- const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir3();
112827
+ const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir2();
112844
112828
  return join113(home, ".claude", "plugins", "cache");
112845
112829
  }
112846
112830
  function sharedSkillsRoot() {
112847
- return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join113(homedir3(), ".agents", "skills");
112831
+ return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join113(homedir2(), ".agents", "skills");
112848
112832
  }
112849
112833
  function claudeSkillsRoot() {
112850
- return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join113(homedir3(), ".claude", "skills");
112834
+ return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join113(homedir2(), ".claude", "skills");
112851
112835
  }
112852
112836
  function cursorPluginRoot() {
112853
- return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join113(homedir3(), ".cursor", "plugins", "local", PLUGIN_NAME);
112837
+ return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join113(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
112854
112838
  }
112855
112839
  function blockHeader(line) {
112856
112840
  const match = line.match(/^\s*\[([^\]]+)\]\s*$/u);
@@ -113089,7 +113073,7 @@ source = ${JSON.stringify(root2)}
113089
113073
  }
113090
113074
  async function ensureCodexPluginEnabled() {
113091
113075
  const configPath = join113(codexHome(), "config.toml");
113092
- await mkdir37(dirname48(configPath), { recursive: true });
113076
+ await mkdir37(dirname47(configPath), { recursive: true });
113093
113077
  const current2 = await readFile95(configPath, "utf8").catch(() => "");
113094
113078
  const next2 = enableCodexPluginConfig(current2);
113095
113079
  if (next2 !== current2) {
@@ -113098,7 +113082,7 @@ async function ensureCodexPluginEnabled() {
113098
113082
  }
113099
113083
  async function ensureCodexLocalMarketplace(root2) {
113100
113084
  const configPath = join113(codexHome(), "config.toml");
113101
- await mkdir37(dirname48(configPath), { recursive: true });
113085
+ await mkdir37(dirname47(configPath), { recursive: true });
113102
113086
  const current2 = await readFile95(configPath, "utf8").catch(() => "");
113103
113087
  const next2 = upsertCodexLocalMarketplaceConfig(current2, root2);
113104
113088
  if (next2 !== current2) {
@@ -113117,7 +113101,7 @@ function codexPluginCacheDir(version3) {
113117
113101
  return join113(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
113118
113102
  }
113119
113103
  async function replaceDirectoryFromSource(source2, target) {
113120
- await mkdir37(dirname48(target), { recursive: true });
113104
+ await mkdir37(dirname47(target), { recursive: true });
113121
113105
  const temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
113122
113106
  const previous3 = `${target}.previous-${process.pid}-${Date.now()}`;
113123
113107
  await rm25(temporary, { recursive: true, force: true });
@@ -113283,10 +113267,10 @@ function pluginAgentOption(value) {
113283
113267
  }
113284
113268
  function packageCandidateDirs() {
113285
113269
  const dirs = [];
113286
- let dir = dirname49(fileURLToPath10(import.meta.url));
113270
+ let dir = dirname48(fileURLToPath10(import.meta.url));
113287
113271
  for (let index2 = 0;index2 < 8; index2++) {
113288
113272
  dirs.push(dir);
113289
- const parent = dirname49(dir);
113273
+ const parent = dirname48(dir);
113290
113274
  if (parent === dir)
113291
113275
  break;
113292
113276
  dir = parent;
@@ -113455,14 +113439,14 @@ function registerPluginCommands(program2) {
113455
113439
  const agent = pluginAgentOption(options.agent);
113456
113440
  process.stdout.write(formatPluginStatusResult(await runPluginStatusCommand({ agent })));
113457
113441
  });
113458
- plugin.command("install").description("Install globally by default, or copy standalone skills with --local <path>").option("--agent <agent>", "agent target: claude | codex | cursor | all", "all").option("--local <path>", "Auto-detect hosts under a repository; with --agent, use the exact host directory").option("--dry-run", "Preview installation without writing files or configuration").action(async (options, command3) => {
113442
+ plugin.command("install").description("Install globally by default, or copy standalone skills with --local <path>").option("--agent <agent>", "agent target: claude | codex | cursor | all; local only: auto-detect | standalone", "all").option("--local <path>", "Repository root; install selected or detected hosts into their corresponding subdirectories").option("--dry-run", "Preview installation without writing files or configuration").action(async (options, command3) => {
113459
113443
  if (options.local !== undefined) {
113460
- const agent2 = command3.getOptionValueSource("agent") === "cli" ? pluginAgentOption(options.agent) : undefined;
113461
- if (agent2 === "all") {
113462
- throw new ContextError(ExitCode.UserError, "--local requires one --agent (claude, cursor or codex), or omit --agent for automatic detection.");
113444
+ if (command3.getOptionValueSource("agent") !== "cli") {
113445
+ throw new ContextError(ExitCode.UserError, "--local requires explicit --agent: claude, cursor, codex, all, auto-detect or standalone.");
113463
113446
  }
113447
+ const agent2 = options.agent === "auto-detect" || options.agent === "standalone" ? options.agent : pluginAgentOption(options.agent);
113464
113448
  const { pluginsRoot } = await runPluginPathCommand();
113465
- process.stdout.write(agent2 ? await installLocalSkills(pluginsRoot, String(options.local), options.dryRun === true, agent2) : await installAutoLocalSkills(pluginsRoot, String(options.local), options.dryRun === true));
113449
+ process.stdout.write(agent2 === "standalone" ? await installLocalSkills(pluginsRoot, String(options.local), options.dryRun === true) : await installAutoLocalSkills(pluginsRoot, String(options.local), options.dryRun === true, undefined, agent2 === "auto-detect" ? undefined : agent2));
113466
113450
  return;
113467
113451
  }
113468
113452
  const agent = pluginAgentOption(options.agent);
@@ -113550,7 +113534,7 @@ function inferErrorCategory(message) {
113550
113534
  }
113551
113535
  function readQuickstartPath() {
113552
113536
  try {
113553
- let dir = dirname50(fileURLToPath11(import.meta.url));
113537
+ let dir = dirname49(fileURLToPath11(import.meta.url));
113554
113538
  for (let i2 = 0;i2 < 8; i2++) {
113555
113539
  const candidate = join115(dir, "docs", "quickstart.md");
113556
113540
  if (existsSync36(candidate))
@@ -113558,13 +113542,13 @@ function readQuickstartPath() {
113558
113542
  const pkg = join115(dir, "package.json");
113559
113543
  if (existsSync36(pkg))
113560
113544
  return candidate;
113561
- const parent = dirname50(dir);
113545
+ const parent = dirname49(dir);
113562
113546
  if (parent === dir)
113563
113547
  break;
113564
113548
  dir = parent;
113565
113549
  }
113566
113550
  } catch {}
113567
- return join115(dirname50(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
113551
+ return join115(dirname49(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
113568
113552
  }
113569
113553
  var GREEN = "\x1B[32m";
113570
113554
  var RESET = "\x1B[0m";