@fitlab-ai/agent-infra 0.5.5 → 0.5.7

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 (110) hide show
  1. package/README.md +182 -1
  2. package/README.zh-CN.md +182 -1
  3. package/bin/cli.js +28 -4
  4. package/lib/defaults.json +1 -0
  5. package/lib/init.js +68 -4
  6. package/lib/prompt.js +28 -1
  7. package/lib/render.js +1 -1
  8. package/lib/sandbox/commands/create.js +7 -3
  9. package/lib/sandbox/commands/rm.js +6 -4
  10. package/lib/sandbox/commands/vm.js +43 -16
  11. package/lib/sandbox/config.js +5 -0
  12. package/lib/sandbox/engine.js +125 -16
  13. package/lib/sandbox/shell.js +47 -7
  14. package/lib/sandbox/task-resolver.js +13 -6
  15. package/lib/sandbox/tools.js +18 -14
  16. package/package.json +2 -2
  17. package/templates/.agents/QUICKSTART.en.md +17 -0
  18. package/templates/.agents/QUICKSTART.zh-CN.md +17 -0
  19. package/templates/.agents/README.en.md +121 -0
  20. package/templates/.agents/README.zh-CN.md +121 -0
  21. package/templates/.agents/rules/issue-pr-commands.en.md +5 -0
  22. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -0
  23. package/templates/.agents/rules/issue-sync.en.md +5 -0
  24. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -0
  25. package/templates/.agents/rules/label-milestone-setup.en.md +5 -0
  26. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +5 -0
  27. package/templates/.agents/rules/milestone-inference.en.md +5 -0
  28. package/templates/.agents/rules/milestone-inference.github.en.md +6 -5
  29. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +6 -5
  30. package/templates/.agents/rules/milestone-inference.zh-CN.md +5 -0
  31. package/templates/.agents/rules/pr-sync.en.md +5 -0
  32. package/templates/.agents/rules/pr-sync.zh-CN.md +5 -0
  33. package/templates/.agents/rules/release-commands.en.md +5 -0
  34. package/templates/.agents/rules/release-commands.zh-CN.md +5 -0
  35. package/templates/.agents/rules/security-alerts.en.md +5 -0
  36. package/templates/.agents/rules/security-alerts.zh-CN.md +5 -0
  37. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +47 -12
  38. package/templates/.agents/scripts/platform-adapters/platform-sync.js +6 -0
  39. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  40. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  41. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  42. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  43. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  44. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  45. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  46. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  47. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  48. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
  49. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  50. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
  51. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  52. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  53. package/templates/.agents/skills/create-issue/SKILL.en.md +2 -2
  54. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +2 -2
  55. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  56. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  57. package/templates/.agents/skills/create-release-note/SKILL.en.md +8 -1
  58. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +8 -1
  59. package/templates/.agents/skills/create-task/SKILL.en.md +2 -2
  60. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +2 -2
  61. package/templates/.agents/skills/implement-task/SKILL.en.md +3 -3
  62. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +3 -3
  63. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  64. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
  65. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  66. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
  67. package/templates/.agents/skills/import-issue/SKILL.en.md +12 -4
  68. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +12 -4
  69. package/templates/.agents/skills/import-issue/config/verify.json +2 -1
  70. package/templates/.agents/skills/init-labels/SKILL.en.md +1 -1
  71. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
  72. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +6 -0
  73. package/templates/.agents/skills/init-milestones/SKILL.en.md +1 -1
  74. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +1 -1
  75. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +6 -0
  76. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  77. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  78. package/templates/.agents/skills/post-release/SKILL.en.md +95 -0
  79. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +95 -0
  80. package/templates/.agents/skills/refine-task/SKILL.en.md +2 -2
  81. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +2 -2
  82. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  83. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  84. package/templates/.agents/skills/release/SKILL.en.md +6 -1
  85. package/templates/.agents/skills/release/SKILL.zh-CN.md +6 -1
  86. package/templates/.agents/skills/release/scripts/manage-milestones.sh +6 -0
  87. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  88. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  89. package/templates/.agents/skills/review-task/SKILL.en.md +3 -3
  90. package/templates/.agents/skills/review-task/SKILL.zh-CN.md +3 -3
  91. package/templates/.agents/skills/test/SKILL.en.md +1 -1
  92. package/templates/.agents/skills/test/SKILL.zh-CN.md +1 -1
  93. package/templates/.agents/skills/test-integration/SKILL.en.md +1 -1
  94. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +1 -1
  95. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +10 -2
  96. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -2
  97. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +598 -7
  98. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +1 -1
  99. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +1 -1
  100. package/templates/.agents/templates/task.en.md +2 -2
  101. package/templates/.agents/templates/task.zh-CN.md +2 -2
  102. package/templates/.claude/commands/post-release.en.md +8 -0
  103. package/templates/.claude/commands/post-release.zh-CN.md +8 -0
  104. package/templates/.gemini/commands/_project_/post-release.en.toml +6 -0
  105. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +6 -0
  106. package/templates/.github/workflows/metadata-sync.yml +1 -1
  107. package/templates/.github/workflows/pr-label.yml +1 -1
  108. package/templates/.github/workflows/status-label.yml +1 -1
  109. package/templates/.opencode/commands/post-release.en.md +9 -0
  110. package/templates/.opencode/commands/post-release.zh-CN.md +9 -0
package/README.md CHANGED
@@ -314,6 +314,7 @@ agent-infra ships with **a rich set of built-in AI skills**. They are organized
314
314
  |-------|-------------|------------|----------------------|
315
315
  | `release` | Execute the version release workflow. | `version` (`X.Y.Z`) | Publish a new project release. |
316
316
  | `create-release-note` | Generate release notes from PRs and commits. | `version`, `previous-version` (optional) | Prepare a changelog before shipping. |
317
+ | `post-release` | Run post-release follow-up tasks (version bump, artifact rebuild, optional demo capture). | None | Finalize the release cycle after pushing a release tag. |
317
318
 
318
319
  <a id="security-skills"></a>
319
320
 
@@ -341,6 +342,140 @@ agent-infra ships with **a rich set of built-in AI skills**. They are organized
341
342
 
342
343
  > Every skill works across supported AI TUIs. The command prefix changes, but the workflow semantics stay the same.
343
344
 
345
+ <a id="custom-skills"></a>
346
+
347
+ ## Custom Skills
348
+
349
+ Built-in skills cover the standard delivery lifecycle, but teams often need project-specific instructions such as coding standards, deployment checks, or internal review rules. agent-infra supports that through **custom skills**.
350
+
351
+ ### Create a custom skill in the project
352
+
353
+ Create a directory under `.agents/skills/<name>/` and add a `SKILL.md` file:
354
+
355
+ ```text
356
+ .agents/skills/
357
+ enforce-style/
358
+ SKILL.md
359
+ reference/
360
+ style-guide.md
361
+ ```
362
+
363
+ Minimum frontmatter:
364
+
365
+ ```yaml
366
+ ---
367
+ name: enforce-style
368
+ description: "Apply team style checks before submitting code"
369
+ args: "<task-id>" # optional
370
+ ---
371
+ ```
372
+
373
+ - `name`: user-facing skill name
374
+ - `description`: used when generating editor command metadata
375
+ - `args`: optional argument hint; agent-infra uses it when generating slash commands for supported AI TUIs
376
+
377
+ After adding the skill, run `update-agent-infra` again:
378
+
379
+ | TUI | Command |
380
+ |-----|---------|
381
+ | Claude Code | `/update-agent-infra` |
382
+ | Codex | `$update-agent-infra` |
383
+ | Gemini CLI | `/{{project}}:update-agent-infra` |
384
+ | OpenCode | `/update-agent-infra` |
385
+
386
+ That refresh detects non-built-in skill directories in `.agents/skills/` and generates matching commands for Claude Code, Gemini CLI, and OpenCode automatically.
387
+
388
+ ### Sync custom skills from shared sources
389
+
390
+ If you maintain reusable team skills outside the repository, declare them in `.agents/.airc.json`:
391
+
392
+ ```json
393
+ {
394
+ "skills": {
395
+ "sources": [
396
+ { "type": "local", "path": "~/private-skills" },
397
+ { "type": "local", "path": "~/team-skills" }
398
+ ]
399
+ }
400
+ }
401
+ ```
402
+
403
+ Expected source layout:
404
+
405
+ ```text
406
+ ~/private-skills/
407
+ enforce-style/
408
+ SKILL.md
409
+ release-check/
410
+ SKILL.md
411
+ reference/
412
+ checklist.md
413
+ ```
414
+
415
+ Behavior:
416
+
417
+ - Sources are applied in list order; later sources overwrite earlier custom sources when they define the same file
418
+ - `type: "local"` is the only supported source type today; the structure leaves room for future source types
419
+ - `~` in source paths is expanded to the current user's home directory
420
+
421
+ ### Sync behavior and conflict rules
422
+
423
+ When `update-agent-infra` runs:
424
+
425
+ - Manually created custom skills in `.agents/skills/` are protected from managed-file cleanup
426
+ - Files synced from external custom sources are copied into `.agents/skills/`
427
+ - For synced skills that still exist in a configured source, files removed from the source are also removed locally during the next sync
428
+ - Built-in skills always win over custom sources; if a source defines a skill with the same name as a built-in skill, agent-infra skips that custom source skill instead of overriding the built-in one
429
+ - If you truly need to replace a built-in skill or command, use the existing `ejected` mechanism and own that file in the project
430
+
431
+ ## Custom TUI Configuration
432
+
433
+ Use the top-level `.agents/.airc.json` `customTUIs` array when your team uses an AI TUI that is not one of the built-in command targets. This config lets agent-infra show the correct next-step commands and generate command files for project custom skills by learning from an existing command in the custom TUI directory.
434
+
435
+ | Field | Required | Meaning |
436
+ |-------|----------|---------|
437
+ | `name` | Yes | Display name shown in reports and next-step guidance, for example `Acme TUI`. |
438
+ | `dir` | Yes | Command directory relative to the project root, for example `.acme/commands`. The path must stay inside the project root. |
439
+ | `invoke` | Yes | User-facing command template used in next-step guidance. |
440
+
441
+ Supported `invoke` placeholders:
442
+
443
+ | Placeholder | Replaced with | Example |
444
+ |-------------|---------------|---------|
445
+ | `${skillName}` | The skill command name, such as `review-task` or `commit`. | `acme ${skillName}` -> `acme review-task` |
446
+ | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
447
+
448
+ Non-namespaced custom TUI:
449
+
450
+ ```json
451
+ {
452
+ "customTUIs": [
453
+ {
454
+ "name": "Acme TUI",
455
+ "dir": ".acme/commands",
456
+ "invoke": "acme ${skillName}"
457
+ }
458
+ ]
459
+ }
460
+ ```
461
+
462
+ Namespaced custom TUI:
463
+
464
+ ```json
465
+ {
466
+ "project": "agent-infra",
467
+ "customTUIs": [
468
+ {
469
+ "name": "Internal Gemini",
470
+ "dir": ".internal-gemini/commands",
471
+ "invoke": "/${projectName}:${skillName}"
472
+ }
473
+ ]
474
+ }
475
+ ```
476
+
477
+ `customTUIs` should contain one entry per custom TUI. To let `update-agent-infra` generate command files for custom skills, keep at least one existing command file in `dir` that references a built-in skill path such as `.agents/skills/analyze-task/SKILL.md`; agent-infra uses that file as the format reference.
478
+
344
479
  <a id="prebuilt-workflows"></a>
345
480
 
346
481
  ## Prebuilt Workflows
@@ -410,7 +545,24 @@ The generated `.agents/.airc.json` file is the central contract between the boot
410
545
  "project": "my-project",
411
546
  "org": "my-org",
412
547
  "language": "en",
413
- "templateVersion": "v0.5.5",
548
+ "templateVersion": "v0.5.7",
549
+ "templates": {
550
+ "sources": [
551
+ { "type": "local", "path": "~/private-templates" }
552
+ ]
553
+ },
554
+ "skills": {
555
+ "sources": [
556
+ { "type": "local", "path": "~/private-skills" }
557
+ ]
558
+ },
559
+ "customTUIs": [
560
+ {
561
+ "name": "Acme TUI",
562
+ "dir": ".acme/commands",
563
+ "invoke": "acme ${skillName}"
564
+ }
565
+ ],
414
566
  "files": {
415
567
  "managed": [
416
568
  ".agents/workspace/README.md",
@@ -439,8 +591,37 @@ The generated `.agents/.airc.json` file is the central contract between the boot
439
591
  | `org` | GitHub organization or owner used by generated metadata and links. |
440
592
  | `language` | Primary project language or locale used by rendered templates. |
441
593
  | `templateVersion` | Installed template version for future upgrades and drift tracking. |
594
+ | `templates` | Optional external template overlay configuration. |
595
+ | `templates.sources` | Optional ordered list of external template sources. Only `type: "local"` is supported today. |
596
+ | `skills` | Optional custom skill sync configuration. |
597
+ | `skills.sources` | Optional ordered list of external custom skill sources. Only `type: "local"` is supported today. |
598
+ | `customTUIs` | Optional top-level list of custom AI TUI adapters. |
442
599
  | `files` | Per-path update strategy configuration for managed, merged, and ejected files. |
443
600
 
601
+ ### External template and skill sources
602
+
603
+ Use external sources when your team maintains private platform templates, private rules, or shared custom skills outside this repository. You can configure them during `agent-infra init` or later by editing `.agents/.airc.json`:
604
+
605
+ ```json
606
+ {
607
+ "templates": {
608
+ "sources": [
609
+ { "type": "local", "path": "~/private-templates" },
610
+ { "type": "local", "path": "~/team-overrides/templates" }
611
+ ]
612
+ },
613
+ "skills": {
614
+ "sources": [
615
+ { "type": "local", "path": "~/private-skills" }
616
+ ]
617
+ }
618
+ }
619
+ ```
620
+
621
+ Template source precedence is built-in templates first, then external sources as supplements. External files with the same path as built-in templates are ignored and reported in `templateSources.conflicts`; between external sources, later entries override earlier entries and conflicts are also reported. Skill sources use the same local-source shape, but custom skills cannot replace built-in skills.
622
+
623
+ External template files and skill scripts can include executable JavaScript or shell commands that AI workflows may run. Only use trusted local paths.
624
+
444
625
  <a id="file-management-strategies"></a>
445
626
 
446
627
  ## File Management Strategies
package/README.zh-CN.md CHANGED
@@ -314,6 +314,7 @@ agent-infra 提供 **丰富的内置 AI skills**。它们按使用场景分组
314
314
  |-------|------|------|---------|
315
315
  | `release` | 执行版本发布流程。 | `version`(`X.Y.Z`) | 发布新版本时。 |
316
316
  | `create-release-note` | 基于 PR 和 commit 生成发布说明。 | `version`、`previous-version`(可选) | 发布前准备 changelog 时。 |
317
+ | `post-release` | 执行版本发布后的收尾工作(版本 bump、产物重建、可选动图录制)。 | 无 | 推送发布标签后完成收尾。 |
317
318
 
318
319
  <a id="security-skills"></a>
319
320
 
@@ -341,6 +342,140 @@ agent-infra 提供 **丰富的内置 AI skills**。它们按使用场景分组
341
342
 
342
343
  > 所有 skills 都可跨支持的 AI TUI 复用。变化的只是命令前缀,工作流语义保持一致。
343
344
 
345
+ <a id="custom-skills"></a>
346
+
347
+ ## 自定义 Skills
348
+
349
+ 内置 skills 覆盖了标准交付生命周期,但很多团队还需要项目特有的指令,例如编码规范、发布检查或内部审查规则。agent-infra 通过**自定义 skill**支持这些场景。
350
+
351
+ ### 在项目中创建自定义 skill
352
+
353
+ 在 `.agents/skills/<name>/` 下创建目录,并添加 `SKILL.md`:
354
+
355
+ ```text
356
+ .agents/skills/
357
+ enforce-style/
358
+ SKILL.md
359
+ reference/
360
+ style-guide.md
361
+ ```
362
+
363
+ 最小 frontmatter 示例:
364
+
365
+ ```yaml
366
+ ---
367
+ name: enforce-style
368
+ description: "在提交代码前执行团队风格检查"
369
+ args: "<task-id>" # 可选
370
+ ---
371
+ ```
372
+
373
+ - `name`:对用户可见的 skill 名称
374
+ - `description`:用于生成编辑器命令元数据
375
+ - `args`:可选参数提示;agent-infra 会在生成支持的 AI TUI 命令时使用它
376
+
377
+ 添加 skill 后,再执行一次 `update-agent-infra`:
378
+
379
+ | TUI | 命令 |
380
+ |-----|------|
381
+ | Claude Code | `/update-agent-infra` |
382
+ | Codex | `$update-agent-infra` |
383
+ | Gemini CLI | `/{{project}}:update-agent-infra` |
384
+ | OpenCode | `/update-agent-infra` |
385
+
386
+ 同步时会自动检测 `.agents/skills/` 下的非内置 skill 目录,并为 Claude Code、Gemini CLI、OpenCode 生成对应命令。
387
+
388
+ ### 从共享源同步自定义 skills
389
+
390
+ 如果团队在仓库外统一维护可复用 skill,可以在 `.agents/.airc.json` 中声明:
391
+
392
+ ```json
393
+ {
394
+ "skills": {
395
+ "sources": [
396
+ { "type": "local", "path": "~/private-skills" },
397
+ { "type": "local", "path": "~/team-skills" }
398
+ ]
399
+ }
400
+ }
401
+ ```
402
+
403
+ 源目录结构示例:
404
+
405
+ ```text
406
+ ~/private-skills/
407
+ enforce-style/
408
+ SKILL.md
409
+ release-check/
410
+ SKILL.md
411
+ reference/
412
+ checklist.md
413
+ ```
414
+
415
+ 行为说明:
416
+
417
+ - 多个 source 按数组顺序应用;后面的 source 如果定义了同名文件,会覆盖前面的自定义 source 文件
418
+ - 当前只支持 `type: "local"`;配置结构已为未来扩展其他来源类型预留
419
+ - source 路径中的 `~` 会自动展开为当前用户的 home 目录
420
+
421
+ ### 同步行为与冲突规则
422
+
423
+ 执行 `update-agent-infra` 时:
424
+
425
+ - 手动放在 `.agents/skills/` 下的自定义 skill 不会被 managed 文件清理删除
426
+ - 外部 source 中的 skill 会同步复制到 `.agents/skills/`
427
+ - 对于仍存在于配置 source 中的 skill,如果源里删掉某个文件,下次同步时本地对应残留文件也会被删除
428
+ - 内置 skill 始终优先于自定义 source;如果 source 里出现与内置 skill 同名的目录,agent-infra 会跳过该 source skill,而不是覆盖内置实现
429
+ - 如果你确实需要替换内置 skill 或命令,请使用现有的 `ejected` 机制,让项目自己接管该文件
430
+
431
+ ## 自定义 TUI 配置
432
+
433
+ 当团队使用的 AI TUI 不属于内置命令目标时,可以在 `.agents/.airc.json` 顶层配置 `customTUIs` 数组。该配置用于让 agent-infra 输出正确的下一步命令,并通过学习自定义 TUI 目录中的既有命令文件,为项目自定义 skill 生成同格式命令。
434
+
435
+ | 字段 | 必填 | 含义 |
436
+ |------|------|------|
437
+ | `name` | 是 | 报告和下一步提示中展示的工具名称,例如 `Acme TUI`。 |
438
+ | `dir` | 是 | 相对项目根目录的命令目录,例如 `.acme/commands`。路径必须位于项目根目录内。 |
439
+ | `invoke` | 是 | 面向用户展示的命令模板,用于生成下一步提示。 |
440
+
441
+ `invoke` 支持的占位符:
442
+
443
+ | 占位符 | 替换为 | 示例 |
444
+ |--------|--------|------|
445
+ | `${skillName}` | skill 命令名,例如 `review-task` 或 `commit`。 | `acme ${skillName}` -> `acme review-task` |
446
+ | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
447
+
448
+ 不带命名空间的自定义 TUI:
449
+
450
+ ```json
451
+ {
452
+ "customTUIs": [
453
+ {
454
+ "name": "Acme TUI",
455
+ "dir": ".acme/commands",
456
+ "invoke": "acme ${skillName}"
457
+ }
458
+ ]
459
+ }
460
+ ```
461
+
462
+ 带命名空间的自定义 TUI:
463
+
464
+ ```json
465
+ {
466
+ "project": "agent-infra",
467
+ "customTUIs": [
468
+ {
469
+ "name": "Internal Gemini",
470
+ "dir": ".internal-gemini/commands",
471
+ "invoke": "/${projectName}:${skillName}"
472
+ }
473
+ ]
474
+ }
475
+ ```
476
+
477
+ `customTUIs` 每个条目对应一个自定义 TUI。若希望 `update-agent-infra` 为自定义 skill 生成命令文件,请在 `dir` 中保留至少一个引用内置 skill 路径的既有命令文件,例如 `.agents/skills/analyze-task/SKILL.md`;agent-infra 会以该文件作为格式参考。
478
+
344
479
  <a id="prebuilt-workflows"></a>
345
480
 
346
481
  ## 预置工作流
@@ -410,7 +545,24 @@ import-issue #42 从 GitHub Issue 导入任务
410
545
  "project": "my-project",
411
546
  "org": "my-org",
412
547
  "language": "en",
413
- "templateVersion": "v0.5.5",
548
+ "templateVersion": "v0.5.7",
549
+ "templates": {
550
+ "sources": [
551
+ { "type": "local", "path": "~/private-templates" }
552
+ ]
553
+ },
554
+ "skills": {
555
+ "sources": [
556
+ { "type": "local", "path": "~/private-skills" }
557
+ ]
558
+ },
559
+ "customTUIs": [
560
+ {
561
+ "name": "Acme TUI",
562
+ "dir": ".acme/commands",
563
+ "invoke": "acme ${skillName}"
564
+ }
565
+ ],
414
566
  "files": {
415
567
  "managed": [
416
568
  ".agents/workspace/README.md",
@@ -439,8 +591,37 @@ import-issue #42 从 GitHub Issue 导入任务
439
591
  | `org` | 生成元数据和链接时使用的 GitHub 组织或拥有者。 |
440
592
  | `language` | 渲染模板时采用的项目主语言或区域设置。 |
441
593
  | `templateVersion` | 当前安装的模板版本,用于升级和差异追踪。 |
594
+ | `templates` | 可选的外部模板叠加配置。 |
595
+ | `templates.sources` | 可选的外部模板源列表,按顺序应用。当前仅支持 `type: "local"`。 |
596
+ | `skills` | 可选的自定义 skill 同步配置。 |
597
+ | `skills.sources` | 可选的外部自定义 skill 源列表,按顺序应用。当前仅支持 `type: "local"`。 |
598
+ | `customTUIs` | 可选的顶层自定义 AI TUI 适配配置列表。 |
442
599
  | `files` | 针对具体路径配置 `managed`、`merged`、`ejected` 三类更新策略。 |
443
600
 
601
+ ### 外部模板与 skill 源
602
+
603
+ 当团队在仓库外维护私有平台模板、私有规则或共享自定义 skill 时,可以使用外部源。你可以在 `agent-infra init` 时配置,也可以之后手动编辑 `.agents/.airc.json`:
604
+
605
+ ```json
606
+ {
607
+ "templates": {
608
+ "sources": [
609
+ { "type": "local", "path": "~/private-templates" },
610
+ { "type": "local", "path": "~/team-overrides/templates" }
611
+ ]
612
+ },
613
+ "skills": {
614
+ "sources": [
615
+ { "type": "local", "path": "~/private-skills" }
616
+ ]
617
+ }
618
+ }
619
+ ```
620
+
621
+ 模板源优先级是内置模板优先,外部源作为补充。外部源中与内置模板同路径的文件会被忽略,并记录到 `templateSources.conflicts`;多个外部源之间,后面的条目覆盖前面的条目,冲突同样会记录。Skill 源使用相同的本地源结构,但自定义 skill 不能替换内置 skill。
622
+
623
+ 外部模板文件和 skill 脚本可能包含 AI 工作流会执行的 JavaScript 或 shell 命令。只使用可信的本地路径。
624
+
444
625
  <a id="file-management-strategies"></a>
445
626
 
446
627
  ## 文件管理策略
package/bin/cli.js CHANGED
@@ -35,9 +35,27 @@ Examples:
35
35
 
36
36
  const command = process.argv[2] || '';
37
37
 
38
+ async function importCommand(importPath) {
39
+ try {
40
+ return await import(importPath);
41
+ } catch (error) {
42
+ if (error?.code === 'ERR_MODULE_NOT_FOUND') {
43
+ process.stderr.write(
44
+ 'Error: Missing npm dependency. Run npm install before using agent-infra from a development checkout.\n'
45
+ );
46
+ process.stderr.write(`${error.message}\n`);
47
+ process.exitCode = 1;
48
+ return null;
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+
38
54
  switch (command) {
39
55
  case 'init': {
40
- const { cmdInit } = await import('../lib/init.js');
56
+ const imported = await importCommand('../lib/init.js');
57
+ if (!imported) break;
58
+ const { cmdInit } = imported;
41
59
  await cmdInit().catch((e) => {
42
60
  process.stderr.write(`Error: ${e.message}\n`);
43
61
  process.exitCode = 1;
@@ -45,7 +63,9 @@ switch (command) {
45
63
  break;
46
64
  }
47
65
  case 'update': {
48
- const { cmdUpdate } = await import('../lib/update.js');
66
+ const imported = await importCommand('../lib/update.js');
67
+ if (!imported) break;
68
+ const { cmdUpdate } = imported;
49
69
  await cmdUpdate().catch((e) => {
50
70
  process.stderr.write(`Error: ${e.message}\n`);
51
71
  process.exitCode = 1;
@@ -53,7 +73,9 @@ switch (command) {
53
73
  break;
54
74
  }
55
75
  case 'merge': {
56
- const { cmdMerge } = await import('../lib/merge.js');
76
+ const imported = await importCommand('../lib/merge.js');
77
+ if (!imported) break;
78
+ const { cmdMerge } = imported;
57
79
  await cmdMerge(process.argv.slice(3)).catch((e) => {
58
80
  process.stderr.write(`Error: ${e.message}\n`);
59
81
  process.exitCode = 1;
@@ -61,7 +83,9 @@ switch (command) {
61
83
  break;
62
84
  }
63
85
  case 'sandbox': {
64
- const { runSandbox } = await import('../lib/sandbox/index.js');
86
+ const imported = await importCommand('../lib/sandbox/index.js');
87
+ if (!imported) break;
88
+ const { runSandbox } = imported;
65
89
  await runSandbox(process.argv.slice(3)).catch((e) => {
66
90
  process.stderr.write(`Error: ${e.message}\n`);
67
91
  process.exitCode = 1;
package/lib/defaults.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "type": "github"
4
4
  },
5
5
  "sandbox": {
6
+ "engine": null,
6
7
  "runtimes": [
7
8
  "node20"
8
9
  ],
package/lib/init.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
+ import { platform } from 'node:os';
4
5
  import { info, ok, err } from './log.js';
5
- import { prompt, closePrompt } from './prompt.js';
6
+ import { prompt, select, closePrompt } from './prompt.js';
6
7
  import { resolveTemplateDir } from './paths.js';
7
- import { renderFile, copySkillDir } from './render.js';
8
+ import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.js';
8
9
  import { VERSION } from './version.js';
9
10
 
10
11
  const defaults = JSON.parse(
@@ -39,10 +40,19 @@ function detectOrgName() {
39
40
 
40
41
  const VALID_NAME_RE = /^[a-zA-Z0-9_.@-]+$/;
41
42
 
43
+ function parseLocalSources(input) {
44
+ return input
45
+ .split(',')
46
+ .map((entry) => entry.trim())
47
+ .filter(Boolean)
48
+ .map((entry) => ({ type: 'local', path: entry }));
49
+ }
50
+
42
51
  async function cmdInit() {
43
52
  console.log('');
44
53
  console.log(' agent-infra init');
45
54
  console.log(' ================================');
55
+ console.log(' Optional template and skill sources can be added now or later in .agents/.airc.json.');
46
56
  console.log('');
47
57
 
48
58
  // resolve templates
@@ -100,14 +110,52 @@ async function cmdInit() {
100
110
  return;
101
111
  }
102
112
 
103
- const platformType = (await prompt('Platform type', 'github')).trim() || 'github';
104
- closePrompt();
113
+ let sandboxEngine = null;
114
+ if (platform() === 'darwin') {
115
+ sandboxEngine = await select(
116
+ 'Sandbox engine (macOS)',
117
+ ['colima', 'orbstack', 'docker-desktop'],
118
+ 'colima'
119
+ );
120
+ }
121
+
122
+ const platformChoices = [...KNOWN_PLATFORMS, 'other'];
123
+ let platformType = await select('Platform', platformChoices, 'github');
124
+
125
+ if (platformType === 'other') {
126
+ platformType = (await prompt('Custom platform type', '')).trim();
127
+ if (!platformType) {
128
+ closePrompt();
129
+ err('Custom platform type is required.');
130
+ process.exitCode = 1;
131
+ return;
132
+ }
133
+ }
134
+
105
135
  if (!/^[a-z0-9][a-z0-9-]*$/.test(platformType)) {
136
+ closePrompt();
106
137
  err(`Platform type must match /^[a-z0-9][a-z0-9-]*$/. Got: ${platformType}`);
107
138
  process.exitCode = 1;
108
139
  return;
109
140
  }
110
141
 
142
+ if (!KNOWN_PLATFORMS.has(platformType)) {
143
+ info(
144
+ `Custom platform '${platformType}' selected. Built-in templates are only complete for github;`
145
+ + ` provide matching '.${platformType}.' or generic templates before running update-agent-infra.`
146
+ );
147
+ }
148
+
149
+ const templateSources = parseLocalSources(await prompt(
150
+ 'Template sources (optional, comma-separated local paths, e.g. ~/my-templates; Enter to skip)',
151
+ ''
152
+ ));
153
+ const skillSources = parseLocalSources(await prompt(
154
+ 'Skill sources (optional, comma-separated local paths, e.g. ~/my-skills; Enter to skip)',
155
+ ''
156
+ ));
157
+ closePrompt();
158
+
111
159
  const project = projectName;
112
160
  const replacements = { project, org: orgName };
113
161
 
@@ -177,6 +225,22 @@ async function cmdInit() {
177
225
  files: structuredClone(defaults.files)
178
226
  };
179
227
 
228
+ if (sandboxEngine) {
229
+ config.sandbox.engine = sandboxEngine;
230
+ }
231
+
232
+ if (templateSources.length > 0) {
233
+ config.templates = {
234
+ sources: templateSources
235
+ };
236
+ }
237
+
238
+ if (skillSources.length > 0) {
239
+ config.skills = {
240
+ sources: skillSources
241
+ };
242
+ }
243
+
180
244
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
181
245
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
182
246
  ok(`Generated ${configPath}`);
package/lib/prompt.js CHANGED
@@ -59,6 +59,33 @@ async function prompt(question, defaultValue) {
59
59
  return line.trim() || defaultValue || '';
60
60
  }
61
61
 
62
+ async function select(question, choices, defaultValue) {
63
+ const defaultIndex = choices.indexOf(defaultValue);
64
+
65
+ process.stdout.write(` ${question}:\n`);
66
+ choices.forEach((choice, index) => {
67
+ const suffix = index === defaultIndex ? ' (default)' : '';
68
+ process.stdout.write(` ${index + 1}) ${choice}${suffix}\n`);
69
+ });
70
+
71
+ ask(defaultIndex >= 0 ? `Select [${defaultIndex + 1}]: ` : 'Select: ');
72
+
73
+ setupInterface();
74
+
75
+ const line = await nextLine();
76
+ if (line === null || line.trim() === '') {
77
+ return defaultValue || choices[0];
78
+ }
79
+
80
+ const trimmed = line.trim();
81
+ const selectedIndex = Number.parseInt(trimmed, 10);
82
+ if (String(selectedIndex) === trimmed && selectedIndex >= 1 && selectedIndex <= choices.length) {
83
+ return choices[selectedIndex - 1];
84
+ }
85
+
86
+ return trimmed;
87
+ }
88
+
62
89
  function closePrompt() {
63
90
  if (_rl) {
64
91
  _rl.close();
@@ -67,4 +94,4 @@ function closePrompt() {
67
94
  }
68
95
  }
69
96
 
70
- export { prompt, closePrompt };
97
+ export { prompt, select, closePrompt };
package/lib/render.js CHANGED
@@ -158,4 +158,4 @@ function copySkillDir(srcDir, dstDir, replacements, language, platform = 'github
158
158
  }
159
159
  }
160
160
 
161
- export { renderFile, copyFile, copySkillDir };
161
+ export { renderFile, copyFile, copySkillDir, KNOWN_PLATFORMS };
@@ -63,6 +63,10 @@ function buildSignature(preparedDockerfile, tools) {
63
63
  .slice(0, 12);
64
64
  }
65
65
 
66
+ function hostJoin(basePath, ...segments) {
67
+ return basePath.startsWith('/') ? path.posix.join(basePath, ...segments) : path.join(basePath, ...segments);
68
+ }
69
+
66
70
  function resolveToolDirs(config, tools, branch) {
67
71
  return tools.map((tool) => {
68
72
  const candidates = toolConfigDirCandidates(tool, config.project, branch);
@@ -266,7 +270,7 @@ export function prepareHostShellConfig({ home, project, branch, repoRoot }) {
266
270
  }
267
271
 
268
272
  function gpgCacheDir(home, project) {
269
- return path.join(home, '.agent-infra', 'gpg-cache', project);
273
+ return hostJoin(home, '.agent-infra', 'gpg-cache', project);
270
274
  }
271
275
 
272
276
  function normalizeSigningKey(signingKey) {
@@ -649,11 +653,11 @@ export function extractClaudeCredentialsBlob(home, execFn = execFileSync) {
649
653
  }
650
654
 
651
655
  export function claudeCredentialsDir(home, project) {
652
- return path.join(home, '.agent-infra', 'credentials', project, 'claude-code');
656
+ return hostJoin(home, '.agent-infra', 'credentials', project, 'claude-code');
653
657
  }
654
658
 
655
659
  export function claudeCredentialsPath(home, project) {
656
- return path.join(claudeCredentialsDir(home, project), '.credentials.json');
660
+ return hostJoin(claudeCredentialsDir(home, project), '.credentials.json');
657
661
  }
658
662
 
659
663
  export function writeClaudeCredentialsFile(home, project, blob) {