teamai-cli 0.25.0-beta.1 → 0.25.0-beta.2

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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. See [standa
6
6
 
7
7
  ### ✨ Features
8
8
 
9
+ - `teamai remove` accepts `--force` to skip its confirmation prompt, spelled the same way as `teamai uninstall --force`. Without a TTY the prompt answers itself with no, so this is the only way to remove a resource from a script or a test (for [#591](https://github.com/Tencent/teamai-cli/issues/591)).
9
10
  - MCP servers in `mcp/mcp.yaml` and hooks in `hooks/hooks.yaml` accept an optional `roles:` list and ship only to members holding one of those roles; a role change removes the previous role's entries on the next pull, and `teamai mcp list` / `teamai hooks list` show the restriction (for [#563](https://github.com/Tencent/teamai-cli/issues/563)).
10
11
  - Agents can be scoped by role or project: `agents/<namespace>/` ships only to members whose `roles.yaml` / `projects.yaml` entry lists that namespace under a new optional `agents:` key, and a role change removes the previous namespaces' agents on the next pull (for [#563](https://github.com/Tencent/teamai-cli/issues/563)).
11
12
  - First-class Kiro support: skills, steering rules, JSON subagents with CLI `agentSpawn` session-start hooks, and MCP sync to `.kiro/` (for [#500](https://github.com/Tencent/teamai-cli/issues/500)).
@@ -15,8 +16,10 @@ All notable changes to this project will be documented in this file. See [standa
15
16
 
16
17
  ### 🐛 Bug Fixes
17
18
 
19
+ - `teamai tags subscribe` and `teamai tags unsubscribe` now invalidate the pull revision cache, as `teamai skill exclude` already does, so the next `teamai pull` applies the new subscriptions instead of reporting "Already synced" when the team repo has not changed.
18
20
  - `teamai pull` now deletes a tombstoned agent under all three render extensions, so the Codex `.toml` and Kiro `.json` copies of a removed agent no longer survive on other machines. The cleanup also runs when the team repo rev is unchanged, so an upgrade reaches machines that already pulled the tombstone with an older CLI. `teamai remove agents <name>` also honours `enabledAgents` and no longer deletes from excluded tools. Fixes [#576](https://github.com/Tencent/teamai-cli/issues/576).
19
21
  - `teamai remove rules <name>` and `teamai remove skills <name>` now honour `enabledAgents` and leave excluded tools untouched, matching the whitelist `teamai pull` already applies when it cleans up a tombstoned resource. Fixes [#590](https://github.com/Tencent/teamai-cli/issues/590).
22
+ - `teamai pull` and `teamai mcp inject` no longer write team MCP servers into installed tools outside `enabledAgents` or listed in `disabledAgents`, the same gate skills, rules, agents and hooks already use. Servers injected before the upgrade are left in place, and `teamai uninstall` still removes them.
20
23
  - `teamai import --cache-status` and `--cache-gc` now expose their existing JSON output through the CLI `--json` option.
21
24
  - Course-correction matching normalizes prompts and keywords to Unicode NFC, so composed and decomposed accents match. Stored prompt summaries and the 60-second correction window are unchanged. Fixes [#573](https://github.com/Tencent/teamai-cli/issues/573).
22
25
  - Course-correction detection matches keywords in space-separated scripts as whole words, so Spanish "segundo" no longer counts as `undo` (for [#564](https://github.com/Tencent/teamai-cli/issues/564)).
package/README.ja.md CHANGED
@@ -266,7 +266,7 @@ teamai recall maintenance --update-quality # draft updates for stale skill
266
266
  | `teamai remove <type> <name>` | リソースを削除し、MR を開く |
267
267
  | `teamai session save` | プライバシー除去済みのセッション要約を月次ログへ記録(`--push` は `digest` へ供給) |
268
268
  | `teamai digest` | チーム利用の週次 digest を生成 |
269
- | `teamai doctor` | 設定の問題を診断 |
269
+ | `teamai doctor` | 設定の問題を診断(`--json` で JSON 出力、CI・hook・agent 向け)|
270
270
  | `teamai uninstall` | すべての teamai リソースと hooks を削除 |
271
271
 
272
272
  ## ライセンス
package/README.ko.md CHANGED
@@ -266,7 +266,7 @@ teamai recall maintenance --update-quality # draft updates for stale skill
266
266
  | `teamai remove <type> <name>` | 리소스를 제거하고 MR을 엽니다 |
267
267
  | `teamai session save` | 개인정보를 제거한 세션 요약을 월별 로그에 기록 (`--push`는 `digest`에 공급) |
268
268
  | `teamai digest` | 주간 팀 사용 digest 생성 |
269
- | `teamai doctor` | 구성 문제 진단 |
269
+ | `teamai doctor` | 구성 문제 진단 (`--json`으로 JSON 출력, CI·hook·agent용) |
270
270
  | `teamai uninstall` | 모든 teamai 리소스와 hooks 제거 |
271
271
 
272
272
  ## 라이선스
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![npm downloads](https://img.shields.io/npm/dm/teamai-cli.svg)](https://www.npmjs.com/package/teamai-cli)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
13
 
14
- TeamAI manages your team's skills, rules, MCP, and knowledge across Claude Code, Codex, CodeBuddy, WorkBuddy, OpenCode, Cursor, and other AI agents.
14
+ TeamAI manages your team's skills, rules, MCP, and knowledge across Claude Code, Codex, GitHub Copilot CLI, CodeBuddy, WorkBuddy, OpenCode, Cursor, and other AI agents.
15
15
 
16
16
  ## Contributors
17
17
 
@@ -86,6 +86,7 @@ Once initialized, every AI session automatically pulls the latest skills / rules
86
86
  <tr><td>Claude Code</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
87
87
  <tr><td>Codex</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
88
88
  <tr><td>Cursor</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
89
+ <tr><td>GitHub Copilot CLI</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td></tr>
89
90
  <tr><td>CodeBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
90
91
  <tr><td>WorkBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
91
92
  <tr><td>OpenCode</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td></tr>
@@ -266,7 +267,7 @@ Insight into how the team actually uses its AI tools, and a starting point for t
266
267
  | `teamai remove <type> <name>` | Remove a resource and open MR |
267
268
  | `teamai session save` | Record a privacy-scrubbed session summary to a monthly log (`--push` feeds `digest`) |
268
269
  | `teamai digest` | Generate weekly team usage digest |
269
- | `teamai doctor` | Diagnose configuration issues |
270
+ | `teamai doctor` | Diagnose configuration issues (`--json` for CI, hooks and agents) |
270
271
  | `teamai uninstall` | Remove all teamai resources and hooks |
271
272
 
272
273
  ## License
package/README.th.md CHANGED
@@ -266,7 +266,7 @@ teamai recall maintenance --update-quality # draft updates for stale skill
266
266
  | `teamai remove <type> <name>` | ลบทรัพยากรแล้วเปิด MR |
267
267
  | `teamai session save` | บันทึกสรุปเซสชันที่ล้างข้อมูลส่วนตัวแล้วลงล็อกรายเดือน (`--push` จะป้อน `digest`) |
268
268
  | `teamai digest` | สร้างสรุปการใช้งานทีมรายสัปดาห์ |
269
- | `teamai doctor` | วินิจฉัยปัญหาคอนฟิก |
269
+ | `teamai doctor` | วินิจฉัยปัญหาคอนฟิก (`--json` แสดงผลเป็น JSON สำหรับ CI, hook และ agent) |
270
270
  | `teamai uninstall` | ลบทรัพยากรและ hooks ของ teamai ทั้งหมด |
271
271
 
272
272
  ## ใบอนุญาต
package/README.zh-CN.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![npm downloads](https://img.shields.io/npm/dm/teamai-cli.svg)](https://www.npmjs.com/package/teamai-cli)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
13
 
14
- TeamAI 统一管理团队的 Skills、Rules、MCP 和知识,驾驭 Claude Code、Codex、CodeBuddy、WorkBuddy、OpenCode、Cursor 等 AI Agents。
14
+ TeamAI 统一管理团队的 Skills、Rules、MCP 和知识,驾驭 Claude Code、Codex、GitHub Copilot CLI、CodeBuddy、WorkBuddy、OpenCode、Cursor 等 AI Agents。
15
15
 
16
16
  ## 贡献者
17
17
 
@@ -86,6 +86,7 @@ teamai init https://github.com/yourorg/yourrepo --scope user
86
86
  <tr><td>Claude Code</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
87
87
  <tr><td>Codex</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
88
88
  <tr><td>Cursor</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
89
+ <tr><td>GitHub Copilot CLI</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td><td align="center">—</td></tr>
89
90
  <tr><td>CodeBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
90
91
  <tr><td>WorkBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
91
92
  <tr><td>OpenCode</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td></tr>
@@ -266,7 +267,7 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
266
267
  | `teamai remove <type> <name>` | 删除资源并创建 MR |
267
268
  | `teamai session save` | 将脱敏后的 session 摘要记录到月度日志(`--push` 可喂给 `digest`) |
268
269
  | `teamai digest` | 生成团队周报 |
269
- | `teamai doctor` | 诊断配置问题 |
270
+ | `teamai doctor` | 诊断配置问题(`--json` 输出 JSON,供 CI、hook 与 agent 消费)|
270
271
  | `teamai uninstall` | 移除所有 teamai 资源和 hooks |
271
272
 
272
273
  ## 许可证
package/dist/index.js CHANGED
@@ -503,6 +503,7 @@ __export(types_exports, {
503
503
  CONTRIBUTE_SKILL_BONUS: () => CONTRIBUTE_SKILL_BONUS,
504
504
  CONTRIBUTE_SMART_THRESHOLD: () => CONTRIBUTE_SMART_THRESHOLD,
505
505
  CONTRIBUTE_TOOLERROR_TIERS: () => CONTRIBUTE_TOOLERROR_TIERS,
506
+ COPILOT_TOOL_ID: () => COPILOT_TOOL_ID,
506
507
  CORRECTION_KEYWORDS: () => CORRECTION_KEYWORDS,
507
508
  CORRECTION_WINDOW_MS: () => CORRECTION_WINDOW_MS,
508
509
  CultureCompanySchema: () => CultureCompanySchema,
@@ -556,6 +557,7 @@ __export(types_exports, {
556
557
  areTeamHooksDisabled: () => areTeamHooksDisabled,
557
558
  emptyTokenUsage: () => emptyTokenUsage,
558
559
  getConfigPath: () => getConfigPath,
560
+ getCopilotHome: () => getCopilotHome,
559
561
  getDataHome: () => getDataHome,
560
562
  getEnvBackupPath: () => getEnvBackupPath,
561
563
  getHooksSharing: () => getHooksSharing,
@@ -590,6 +592,7 @@ __export(types_exports, {
590
592
  resolveCoAuthor: () => resolveCoAuthor,
591
593
  resolveHookScope: () => resolveHookScope,
592
594
  resolveLegacyProjectHookScope: () => resolveLegacyProjectHookScope,
595
+ resolveToolBaseDir: () => resolveToolBaseDir,
593
596
  scopedToolPaths: () => scopedToolPaths,
594
597
  totalTokens: () => totalTokens,
595
598
  usesReportsBranch: () => usesReportsBranch
@@ -699,6 +702,14 @@ function resolveBaseDir(localConfig) {
699
702
  }
700
703
  return getUserHome();
701
704
  }
705
+ function getCopilotHome(env = process.env) {
706
+ const configured = env.COPILOT_HOME?.trim();
707
+ return configured ? path3.resolve(configured) : path3.join(getUserHome(), DEFAULT_COPILOT_HOME);
708
+ }
709
+ function resolveToolBaseDir(tool, localConfig) {
710
+ if (tool === COPILOT_TOOL_ID && localConfig.scope === "user") return getCopilotHome();
711
+ return resolveBaseDir(localConfig);
712
+ }
702
713
  function isAgentDisabled(localConfig, tool) {
703
714
  return localConfig.disabledAgents?.includes(tool) ?? false;
704
715
  }
@@ -719,7 +730,8 @@ function scopedToolPaths(teamConfig, localConfig) {
719
730
  ...paths,
720
731
  ...us.skills !== void 0 ? { skills: us.skills } : {},
721
732
  ...us.rules !== void 0 ? { rules: us.rules } : {},
722
- ...us.agents !== void 0 ? { agents: us.agents } : {}
733
+ ...us.agents !== void 0 ? { agents: us.agents } : {},
734
+ ...us.hooks !== void 0 ? { hooks: us.hooks } : {}
723
735
  };
724
736
  }
725
737
  return out;
@@ -795,7 +807,7 @@ function getPushignorePath() {
795
807
  function areTeamHooksDisabled() {
796
808
  return process.env.TEAMAI_HOOKS_DISABLED === "1" || process.env.TEAMAI_HOOKS_DISABLED === "true";
797
809
  }
798
- var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, UsageEventSchema, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, SEARCH_INDEX_VERSION, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME, SYNC_LOCK_FILENAME;
810
+ var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, UsageEventSchema, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, SEARCH_INDEX_VERSION, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, COPILOT_TOOL_ID, DEFAULT_COPILOT_HOME, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME, SYNC_LOCK_FILENAME;
799
811
  var init_types = __esm({
800
812
  "src/types.ts"() {
801
813
  "use strict";
@@ -804,6 +816,8 @@ var init_types = __esm({
804
816
  skills: z.string().optional(),
805
817
  rules: z.string().optional(),
806
818
  settings: z.string().optional(),
819
+ /** Standalone hooks file for tools that do not store hooks in settings. */
820
+ hooks: z.string().optional(),
807
821
  claudemd: z.string().optional(),
808
822
  /** Per-tool agents directory (Phase 1: teamai-recall subagent target).
809
823
  * Optional — tools without subagent support omit this and agents sync skips them. */
@@ -826,7 +840,8 @@ var init_types = __esm({
826
840
  userScope: z.object({
827
841
  skills: z.string().optional(),
828
842
  rules: z.string().optional(),
829
- agents: z.string().optional()
843
+ agents: z.string().optional(),
844
+ hooks: z.string().optional()
830
845
  }).optional()
831
846
  });
832
847
  ScopeEnum = z.enum(["user", "project"]);
@@ -954,6 +969,15 @@ var init_types = __esm({
954
969
  tclaude: { skills: ".tclaude/skills", rules: ".tclaude/rules", settings: ".tclaude/settings.json", claudemd: ".tclaude/CLAUDE.md", agents: ".tclaude/agents", mcp: ".tclaude/.claude.json" },
955
970
  tcodex: { skills: ".tcodex/skills", rules: ".tcodex/rules", settings: ".tcodex/hooks.json", agents: ".tcodex/agents" },
956
971
  cursor: { skills: ".cursor/skills", rules: ".cursor/rules", settings: ".cursor/hooks.json", agents: ".cursor/agents", mcp: ".cursor/mcp.json", mcpProject: ".cursor/mcp.json" },
972
+ // GitHub Copilot CLI keeps project customizations under .github and moves
973
+ // the complete user customization root when COPILOT_HOME is set. Hooks are
974
+ // a standalone file; settings.json is deliberately never managed.
975
+ copilot: {
976
+ skills: ".github/skills",
977
+ rules: ".github/instructions",
978
+ hooks: ".github/hooks/teamai.json",
979
+ userScope: { skills: "skills", rules: "instructions", hooks: "hooks/teamai.json" }
980
+ },
957
981
  // JoyCode: skills, rules (.mdc), and subagents are synced to .joycode/.
958
982
  // JoyCode currently does not provide a lifecycle hooks system or startup
959
983
  // adapter, so it intentionally has no `settings` path. Hook reconciliation
@@ -1238,6 +1262,8 @@ var init_types = __esm({
1238
1262
  company: CultureCompanySchema.optional(),
1239
1263
  team: CultureTeamSchema.optional()
1240
1264
  });
1265
+ COPILOT_TOOL_ID = "copilot";
1266
+ DEFAULT_COPILOT_HOME = ".copilot";
1241
1267
  REPORTS_BRANCH = "teamai-reports";
1242
1268
  REPORTS_WORKTREE_DIRNAME = "reports-wt";
1243
1269
  KNOWLEDGE_WORKTREE_DIRNAME = "knowledge-wt";
@@ -1587,7 +1613,7 @@ function getWrapperDispatchCommand(event, tool, matcher) {
1587
1613
  return `PATH="$HOME/${TEAMAI_BIN_DIR}:$PATH" teamai hook-dispatch ${event} --tool ${tool}${matcherArg} 2>/dev/null || true`;
1588
1614
  }
1589
1615
  function builtinHookDefs(tool) {
1590
- const withTimeout3 = tool === "cursor" || tool === "workbuddy" || tool === "codebuddy";
1616
+ const withTimeout3 = tool === "cursor" || tool === "copilot" || tool === "workbuddy" || tool === "codebuddy";
1591
1617
  const buildCommand = tool === "zcode" ? getRawDispatchCommand : WRAPPER_TOOLS.has(tool) ? getWrapperDispatchCommand : getDispatchCommand;
1592
1618
  return BUILTIN_HOOK_SPECS.map((spec) => ({
1593
1619
  source: "builtin",
@@ -1639,10 +1665,18 @@ function toolInstallRoot(toolPath) {
1639
1665
  }
1640
1666
  return segments[0] ?? toolPath;
1641
1667
  }
1668
+ async function isToolInstalledForConfig(tool, toolPath, localConfig) {
1669
+ const baseDir = resolveToolBaseDir(tool, localConfig);
1670
+ if (tool === COPILOT_TOOL_ID) {
1671
+ return localConfig.enabledAgents?.includes(COPILOT_TOOL_ID) === true || pathExists(getCopilotHome());
1672
+ }
1673
+ return ResourceHandler.isToolInstalled(toolPath, baseDir);
1674
+ }
1642
1675
  var TOMBSTONE_FILE, ResourceHandler;
1643
1676
  var init_base = __esm({
1644
1677
  "src/resources/base.ts"() {
1645
1678
  "use strict";
1679
+ init_types();
1646
1680
  init_fs();
1647
1681
  init_home();
1648
1682
  TOMBSTONE_FILE = ".removed";
@@ -2509,6 +2543,7 @@ var init_hermes_hooks = __esm({
2509
2543
  var hooks_exports = {};
2510
2544
  __export(hooks_exports, {
2511
2545
  AGENT_HOOK_EVENTS: () => AGENT_HOOK_EVENTS,
2546
+ CLAUDE_TO_COPILOT_EVENTS: () => CLAUDE_TO_COPILOT_EVENTS,
2512
2547
  CLAUDE_TO_CURSOR_EVENTS: () => CLAUDE_TO_CURSOR_EVENTS,
2513
2548
  OPENCLAW_TOOLS: () => OPENCLAW_TOOLS,
2514
2549
  TEAMAI_HOOK_SUBCOMMANDS: () => TEAMAI_HOOK_SUBCOMMANDS,
@@ -2534,6 +2569,7 @@ __export(hooks_exports, {
2534
2569
  import path14 from "path";
2535
2570
  import { realpathSync } from "fs";
2536
2571
  function detectFormat(tool) {
2572
+ if (tool === COPILOT_TOOL_ID) return "copilot";
2537
2573
  if (CODEX_TOOLS.has(tool)) return "codex";
2538
2574
  if (ZCODE_TOOLS.has(tool)) return "zcode";
2539
2575
  return CURSOR_TOOLS.has(tool) ? "cursor" : "claude";
@@ -2623,6 +2659,20 @@ function toCodexEntry(def) {
2623
2659
  if (def.matcher && def.matcher !== "*") entry.matcher = def.matcher;
2624
2660
  return entry;
2625
2661
  }
2662
+ function copilotPowershellCommand(command) {
2663
+ const match = command.match(COPILOT_BUILTIN_COMMAND_RE);
2664
+ return match ? `${match[1]} 2>$null; exit 0` : command;
2665
+ }
2666
+ function toCopilotEntry(def) {
2667
+ return {
2668
+ type: "command",
2669
+ bash: def.command,
2670
+ powershell: copilotPowershellCommand(def.command),
2671
+ command: def.command,
2672
+ ...def.matcher && def.matcher !== "*" ? { matcher: def.matcher } : {},
2673
+ ...def.timeout !== void 0 ? { timeoutSec: def.timeout } : {}
2674
+ };
2675
+ }
2626
2676
  function toZcodeEntry(def) {
2627
2677
  const ZCODE_TIMEOUT_MS = {
2628
2678
  SessionStart: 18e4,
@@ -2768,6 +2818,61 @@ async function reconcileCursorFormat(hooksPath, tool, teamDefs, opts, priorTeamC
2768
2818
  log.debug(`teamai hooks already up-to-date in ${hooksPath}`);
2769
2819
  }
2770
2820
  }
2821
+ function copilotEntryCommands(entry) {
2822
+ return [entry.bash, entry.powershell, entry.command].filter(Boolean);
2823
+ }
2824
+ async function reconcileCopilotFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
2825
+ const expanded = expandHome(hooksPath);
2826
+ const existed = await pathExists(expanded);
2827
+ if (opts.removeAll && !existed) {
2828
+ log.debug(`No teamai hooks to remove from ${hooksPath}`);
2829
+ return;
2830
+ }
2831
+ await ensureDir(path14.dirname(expanded));
2832
+ const hooksJson = await readJson(expanded) ?? {
2833
+ version: COPILOT_HOOK_SCHEMA_VERSION,
2834
+ hooks: {}
2835
+ };
2836
+ let changed = hooksJson.version !== COPILOT_HOOK_SCHEMA_VERSION;
2837
+ hooksJson.version = COPILOT_HOOK_SCHEMA_VERSION;
2838
+ if (!hooksJson.hooks) hooksJson.hooks = {};
2839
+ const isManaged = (entry) => copilotEntryCommands(entry).some(
2840
+ (command) => TEAMAI_COMMAND_MARKERS.some((marker) => command.includes(marker)) || priorTeamCommands.has(command)
2841
+ );
2842
+ const defs = opts.removeAll ? [] : desiredDefs(tool, teamDefs, opts.builtinOverride);
2843
+ const desiredByEvent = {};
2844
+ for (const def of defs) {
2845
+ const event = CLAUDE_TO_COPILOT_EVENTS[def.event];
2846
+ if (!event) continue;
2847
+ (desiredByEvent[event] ??= []).push(toCopilotEntry(def));
2848
+ }
2849
+ for (const event of Object.keys(hooksJson.hooks)) {
2850
+ const existing = hooksJson.hooks[event] ?? [];
2851
+ const untouched = existing.filter((entry) => !isManaged(entry));
2852
+ const desired = desiredByEvent[event] ?? [];
2853
+ const next = [...untouched, ...desired];
2854
+ if (next.length === 0 && !opts.removeAll) {
2855
+ if (existing.length > 0) changed = true;
2856
+ delete hooksJson.hooks[event];
2857
+ continue;
2858
+ }
2859
+ if (JSON.stringify(existing) !== JSON.stringify(next)) {
2860
+ hooksJson.hooks[event] = next;
2861
+ changed = true;
2862
+ }
2863
+ }
2864
+ for (const event of desiredEventOrder(defs, (value) => CLAUDE_TO_COPILOT_EVENTS[value])) {
2865
+ if (hooksJson.hooks[event]) continue;
2866
+ hooksJson.hooks[event] = desiredByEvent[event];
2867
+ changed = true;
2868
+ }
2869
+ if (changed || !existed) {
2870
+ await writeJson(expanded, hooksJson);
2871
+ log.success(`${opts.removeAll ? "Removed" : "Updated"} teamai hooks in ${hooksPath}`);
2872
+ } else {
2873
+ log.debug(`teamai hooks already up-to-date in ${hooksPath}`);
2874
+ }
2875
+ }
2771
2876
  async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
2772
2877
  const expanded = expandHome(hooksPath);
2773
2878
  await ensureDir(path14.dirname(expanded));
@@ -2954,6 +3059,8 @@ async function reconcileHooks(settingsPath, tool, teamDefs = [], opts = {}) {
2954
3059
  const format = detectFormat(tool);
2955
3060
  if (format === "cursor") {
2956
3061
  await reconcileCursorFormat(settingsPath, tool, scopedDefs, opts, priorTeamCommands);
3062
+ } else if (format === "copilot") {
3063
+ await reconcileCopilotFormat(settingsPath, tool, scopedDefs, opts, priorTeamCommands);
2957
3064
  } else if (format === "codex") {
2958
3065
  await reconcileCodexFormat(settingsPath, tool, scopedDefs, opts, priorTeamCommands);
2959
3066
  } else if (format === "zcode") {
@@ -3007,6 +3114,19 @@ async function getHookStatus(settingsPath, tool) {
3007
3114
  });
3008
3115
  return present2 ? "installed" : "missing";
3009
3116
  }
3117
+ if (format === "copilot") {
3118
+ const hooksJson = await readJson(expanded);
3119
+ if (hooksJson?.version !== COPILOT_HOOK_SCHEMA_VERSION || !hooksJson.hooks) return "missing";
3120
+ const present2 = defs.every((def) => {
3121
+ const event = CLAUDE_TO_COPILOT_EVENTS[def.event];
3122
+ if (!event) return true;
3123
+ const want = toCopilotEntry(def);
3124
+ return (hooksJson.hooks[event] ?? []).some(
3125
+ (entry) => entry.type === want.type && entry.bash === want.bash && entry.powershell === want.powershell && entry.command === want.command && entry.matcher === want.matcher
3126
+ );
3127
+ });
3128
+ return present2 ? "installed" : "missing";
3129
+ }
3010
3130
  if (format === "codex") {
3011
3131
  const hooksJson = await readJson(expanded);
3012
3132
  if (!hooksJson?.hooks) return "missing";
@@ -3050,6 +3170,15 @@ async function hasTeamaiHooks(settingsPath, tool, manifestPath) {
3050
3170
  (entries) => (entries ?? []).some((e) => isTeamaiHookCommand(e.command) || priorTeamCommands.has(e.command))
3051
3171
  );
3052
3172
  }
3173
+ if (format === "copilot") {
3174
+ const j = await readJson(expanded);
3175
+ if (!j?.hooks) return false;
3176
+ return Object.values(j.hooks).some(
3177
+ (entries) => (entries ?? []).some((entry) => copilotEntryCommands(entry).some(
3178
+ (command) => TEAMAI_COMMAND_MARKERS.some((marker) => command.includes(marker)) || priorTeamCommands.has(command)
3179
+ ))
3180
+ );
3181
+ }
3053
3182
  if (format === "codex") {
3054
3183
  const j = await readJson(expanded);
3055
3184
  if (!j?.hooks) return false;
@@ -3218,7 +3347,8 @@ async function reconcileTeamHooksForConfig(teamConfig, localConfig, opts = {}) {
3218
3347
  activeRoles: activeRoleIds(localConfig)
3219
3348
  });
3220
3349
  const { baseDir, manifestPath } = resolveHookScope(localConfig);
3221
- let filterAgents2 = opts.filterAgents ?? localConfig.enabledAgents;
3350
+ const explicitlySelectedAgents = opts.filterAgents ?? localConfig.enabledAgents;
3351
+ let filterAgents2 = explicitlySelectedAgents;
3222
3352
  const disabled = localConfig.disabledAgents;
3223
3353
  if (disabled && disabled.length > 0) {
3224
3354
  const universe = filterAgents2 ?? Object.keys(teamConfig.toolPaths);
@@ -3231,10 +3361,29 @@ async function reconcileTeamHooksForConfig(teamConfig, localConfig, opts = {}) {
3231
3361
  teamHookProjectRoot: localConfig.scope === "project" && !isSelfMode(localConfig) ? localConfig.projectRoot : void 0,
3232
3362
  installedBaseDir: localConfig.scope === "project" ? localConfig.projectRoot ?? baseDir : void 0
3233
3363
  });
3364
+ const copilotExcluded = disabled?.includes(COPILOT_TOOL_ID) ?? false;
3365
+ const copilotSelected = !copilotExcluded && (explicitlySelectedAgents?.includes(COPILOT_TOOL_ID) ?? false);
3366
+ const copilotEnabled = !copilotExcluded && (copilotSelected || explicitlySelectedAgents === void 0 && await pathExists(getCopilotHome()));
3367
+ const copilotPaths = scopedToolPaths(teamConfig, localConfig)[COPILOT_TOOL_ID];
3368
+ if (copilotEnabled && copilotPaths?.hooks) {
3369
+ const copilotBase = resolveToolBaseDir(COPILOT_TOOL_ID, localConfig);
3370
+ if (copilotSelected || await pathExists(getCopilotHome())) {
3371
+ await reconcileHooks(
3372
+ path14.join(copilotBase, copilotPaths.hooks),
3373
+ COPILOT_TOOL_ID,
3374
+ teamDefs,
3375
+ {
3376
+ manifestPath: getManagedHooksPath(localConfig.scope, localConfig.projectRoot),
3377
+ removeAll: opts.removeAll,
3378
+ builtinOverride: builtin
3379
+ }
3380
+ );
3381
+ }
3382
+ }
3234
3383
  await sweepLegacyProjectHooks(teamConfig.toolPaths, localConfig);
3235
3384
  return teamDefs;
3236
3385
  }
3237
- var OPENCLAW_TOOLS, TEAMAI_HOOK_SUBCOMMANDS, TEAMAI_LEGACY_HOOK_SUBCOMMANDS, CLAUDE_TO_CURSOR_EVENTS, CURSOR_TOOLS, CODEX_TOOLS, ZCODE_TOOLS, CODEX_TRUST_GATE_TOOLS, TEAMAI_COMMAND_MARKERS, AGENT_HOOK_EVENTS;
3386
+ var OPENCLAW_TOOLS, TEAMAI_HOOK_SUBCOMMANDS, TEAMAI_LEGACY_HOOK_SUBCOMMANDS, CLAUDE_TO_CURSOR_EVENTS, CLAUDE_TO_COPILOT_EVENTS, COPILOT_HOOK_SCHEMA_VERSION, CURSOR_TOOLS, CODEX_TOOLS, ZCODE_TOOLS, CODEX_TRUST_GATE_TOOLS, TEAMAI_COMMAND_MARKERS, COPILOT_BUILTIN_COMMAND_RE, AGENT_HOOK_EVENTS;
3238
3387
  var init_hooks2 = __esm({
3239
3388
  "src/hooks.ts"() {
3240
3389
  "use strict";
@@ -3255,6 +3404,14 @@ var init_hooks2 = __esm({
3255
3404
  PostToolUse: "postToolUse",
3256
3405
  UserPromptSubmit: "beforeSubmitPrompt"
3257
3406
  };
3407
+ CLAUDE_TO_COPILOT_EVENTS = {
3408
+ SessionStart: "SessionStart",
3409
+ Stop: "Stop",
3410
+ UserPromptSubmit: "UserPromptSubmit",
3411
+ PreToolUse: "PreToolUse",
3412
+ PostToolUse: "PostToolUse"
3413
+ };
3414
+ COPILOT_HOOK_SCHEMA_VERSION = 1;
3258
3415
  CURSOR_TOOLS = /* @__PURE__ */ new Set(["cursor"]);
3259
3416
  CODEX_TOOLS = /* @__PURE__ */ new Set(["codex", "codex-internal", "tcodex"]);
3260
3417
  ZCODE_TOOLS = /* @__PURE__ */ new Set(["zcode"]);
@@ -3270,6 +3427,7 @@ var init_hooks2 = __esm({
3270
3427
  "teamai mr-hint",
3271
3428
  "teamai hook-dispatch"
3272
3429
  ];
3430
+ COPILOT_BUILTIN_COMMAND_RE = /^bash -lc "(teamai hook-dispatch [^"]+) 2>\/dev\/null" \|\| true$/;
3273
3431
  AGENT_HOOK_EVENTS = /* @__PURE__ */ new Set([
3274
3432
  "SessionStart",
3275
3433
  "UserPromptSubmit",
@@ -3375,12 +3533,17 @@ var init_pending_push = __esm({
3375
3533
 
3376
3534
  // src/resources/rule-format.ts
3377
3535
  function ruleFileExtensionForTool(tool) {
3378
- return usesCursorMdcRules(tool) ? ".mdc" : ".md";
3536
+ if (usesCursorMdcRules(tool)) return ".mdc";
3537
+ return usesCopilotInstructions(tool) ? ".instructions.md" : ".md";
3379
3538
  }
3380
3539
  function usesCursorMdcRules(tool) {
3381
3540
  return CURSOR_MDC_RULE_TOOLS.has(tool);
3382
3541
  }
3542
+ function usesCopilotInstructions(tool) {
3543
+ return COPILOT_INSTRUCTIONS_RULE_TOOLS.has(tool);
3544
+ }
3383
3545
  function ruleStemFromFilename(filename) {
3546
+ if (filename.endsWith(".instructions.md")) return filename.slice(0, -".instructions.md".length);
3384
3547
  if (filename.endsWith(".mdc")) return filename.slice(0, -".mdc".length);
3385
3548
  if (filename.endsWith(".md")) return filename.slice(0, -".md".length);
3386
3549
  return null;
@@ -3388,11 +3551,12 @@ function ruleStemFromFilename(filename) {
3388
3551
  function isLegacyCursorRuleFile(tool, filename) {
3389
3552
  return usesCursorMdcRules(tool) && filename.endsWith(".md");
3390
3553
  }
3391
- var CURSOR_MDC_RULE_TOOLS;
3554
+ var CURSOR_MDC_RULE_TOOLS, COPILOT_INSTRUCTIONS_RULE_TOOLS;
3392
3555
  var init_rule_format = __esm({
3393
3556
  "src/resources/rule-format.ts"() {
3394
3557
  "use strict";
3395
3558
  CURSOR_MDC_RULE_TOOLS = /* @__PURE__ */ new Set(["cursor", "joycode"]);
3559
+ COPILOT_INSTRUCTIONS_RULE_TOOLS = /* @__PURE__ */ new Set(["copilot"]);
3396
3560
  }
3397
3561
  });
3398
3562
 
@@ -8864,6 +9028,10 @@ function codexServerNames(source) {
8864
9028
  for (const m of source.matchAll(/^\[mcp_servers\.([A-Za-z0-9_-]+)\]\s*$/gm)) names.add(m[1]);
8865
9029
  return [...names];
8866
9030
  }
9031
+ function mcpTargetExcluded(localConfig, target) {
9032
+ if (!isAgentExcluded(localConfig, target.tool)) return false;
9033
+ return !(target.projectScope && target.tool === "claude" && !isAgentExcluded(localConfig, "tclaude"));
9034
+ }
8867
9035
  async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
8868
9036
  const changes = [];
8869
9037
  let wrote = false;
@@ -8902,6 +9070,7 @@ async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
8902
9070
  if (teamDefs.length === 0 && nothingOwned) return { changes, wrote };
8903
9071
  const vars = await buildVarTable(localConfig);
8904
9072
  for (const target of targets) {
9073
+ if (!removeAll && mcpTargetExcluded(localConfig, target)) continue;
8905
9074
  const manifestKey = managedMcpManifestKey(target.tool, target.projectScope);
8906
9075
  const owned = manifest[manifestKey] ?? [];
8907
9076
  const ownedNames = new Set(owned.map((r) => r.name));
@@ -12331,9 +12500,9 @@ var init_skills = __esm({
12331
12500
  sourceSkillNames = /* @__PURE__ */ new Set();
12332
12501
  }
12333
12502
  const candidates = /* @__PURE__ */ new Map();
12334
- for (const [_tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12503
+ for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12335
12504
  if (!toolPath.skills) continue;
12336
- const skillsDir = path30.join(resolveBaseDir(localConfig), toolPath.skills);
12505
+ const skillsDir = path30.join(resolveToolBaseDir(tool, localConfig), toolPath.skills);
12337
12506
  if (!await pathExists(skillsDir)) continue;
12338
12507
  const localSkills = await scanSkillsRecursively(skillsDir);
12339
12508
  for (const [dir, localDirPath] of localSkills) {
@@ -12441,7 +12610,6 @@ var init_skills = __esm({
12441
12610
  * Pull a skill from team repo to all configured AI tool directories.
12442
12611
  */
12443
12612
  async pullItem(item, teamConfig, localConfig) {
12444
- const baseDir = resolveBaseDir(localConfig);
12445
12613
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12446
12614
  if (isAgentExcluded(localConfig, tool)) continue;
12447
12615
  if (!toolPath.skills) continue;
@@ -12460,10 +12628,11 @@ var init_skills = __esm({
12460
12628
  }
12461
12629
  dest = path30.join(getHermesHome(), "skills", item.name);
12462
12630
  } else {
12463
- if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
12631
+ if (!await isToolInstalledForConfig(tool, toolPath.skills, localConfig)) {
12464
12632
  log.debug(`Skipping skill sync for ${tool}: tool not installed`);
12465
12633
  continue;
12466
12634
  }
12635
+ const baseDir = resolveToolBaseDir(tool, localConfig);
12467
12636
  dest = await resolveSkillDestination(tool, toolPath.skills, baseDir, item.name, item.sourcePath);
12468
12637
  }
12469
12638
  try {
@@ -12480,7 +12649,6 @@ var init_skills = __esm({
12480
12649
  */
12481
12650
  async removeItem(name, teamConfig, localConfig) {
12482
12651
  const removed = [];
12483
- const baseDir = resolveBaseDir(localConfig);
12484
12652
  const scopedNamespaces = await resolveSkillNamespaces(localConfig);
12485
12653
  if (scopedNamespaces.length > 0) {
12486
12654
  for (const namespace of scopedNamespaces) {
@@ -12507,6 +12675,7 @@ var init_skills = __esm({
12507
12675
  if (!wsDir) continue;
12508
12676
  skillDir = path30.join(wsDir, "skills", name);
12509
12677
  } else {
12678
+ const baseDir = resolveToolBaseDir(tool, localConfig);
12510
12679
  const configuredDir = path30.join(baseDir, toolPath.skills, name);
12511
12680
  skillDir = await resolveSkillDestination(tool, toolPath.skills, baseDir, name);
12512
12681
  if (skillDir !== configuredDir && await pathExists(configuredDir) && await dirContentEqual(skillDir, configuredDir)) {
@@ -12535,6 +12704,55 @@ var init_skills = __esm({
12535
12704
  }
12536
12705
  });
12537
12706
 
12707
+ // src/resources/copilot-instructions.ts
12708
+ function normalizeBody2(body) {
12709
+ return body.replace(/^\s+/, "").replace(/\s+$/, "");
12710
+ }
12711
+ function rulePaths(data) {
12712
+ const value = data.paths;
12713
+ if (Array.isArray(value)) {
12714
+ return value.map((entry) => String(entry).trim()).filter(Boolean);
12715
+ }
12716
+ if (typeof value === "string") {
12717
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
12718
+ }
12719
+ return [];
12720
+ }
12721
+ function teamRuleToCopilotInstructions(rawTeamRule) {
12722
+ const { data, body } = splitFrontmatter2(rawTeamRule);
12723
+ const paths = rulePaths(data);
12724
+ return stringifyFrontmatter(
12725
+ { applyTo: paths.length > 0 ? paths.join(", ") : ALL_FILES_GLOB },
12726
+ `
12727
+ ${normalizeBody2(body)}
12728
+ `
12729
+ );
12730
+ }
12731
+ function mergeCopilotBodyIntoTeamMd(rawCopilotInstructions, existingTeamMd) {
12732
+ const body = normalizeBody2(splitFrontmatter2(rawCopilotInstructions).body);
12733
+ if (existingTeamMd === null) return `${body}
12734
+ `;
12735
+ const existing = splitFrontmatter2(existingTeamMd);
12736
+ if (normalizeBody2(existing.body) === body) return existingTeamMd;
12737
+ if (!existing.raw) return `${body}
12738
+ `;
12739
+ return `${existing.raw.endsWith("\n") ? existing.raw : `${existing.raw}
12740
+ `}
12741
+ ${body}
12742
+ `;
12743
+ }
12744
+ function copilotInstructionsBodyEqualsTeamMd(rawCopilotInstructions, rawTeamRule) {
12745
+ return normalizeBody2(splitFrontmatter2(rawCopilotInstructions).body) === normalizeBody2(splitFrontmatter2(rawTeamRule).body);
12746
+ }
12747
+ var ALL_FILES_GLOB;
12748
+ var init_copilot_instructions = __esm({
12749
+ "src/resources/copilot-instructions.ts"() {
12750
+ "use strict";
12751
+ init_frontmatter();
12752
+ ALL_FILES_GLOB = "**";
12753
+ }
12754
+ });
12755
+
12538
12756
  // src/resources/opencode-config.ts
12539
12757
  var opencode_config_exports = {};
12540
12758
  __export(opencode_config_exports, {
@@ -12607,6 +12825,7 @@ var init_rules = __esm({
12607
12825
  init_types();
12608
12826
  init_builtin_rules();
12609
12827
  init_cursor_mdc();
12828
+ init_copilot_instructions();
12610
12829
  init_rule_format();
12611
12830
  RulesHandler = class extends ResourceHandler {
12612
12831
  type = "rules";
@@ -12634,10 +12853,11 @@ var init_rules = __esm({
12634
12853
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12635
12854
  const rulesPath = toolPath.rules;
12636
12855
  if (!rulesPath) continue;
12637
- const rulesDir = path32.join(resolveBaseDir(localConfig), rulesPath);
12856
+ const rulesDir = path32.join(resolveToolBaseDir(tool, localConfig), rulesPath);
12638
12857
  if (!await pathExists(rulesDir)) continue;
12639
12858
  const ext = ruleFileExtensionForTool(tool);
12640
12859
  const isMdcTool = usesCursorMdcRules(tool);
12860
+ const isCopilotTool = usesCopilotInstructions(tool);
12641
12861
  const files = await listFilesRecursive(rulesDir);
12642
12862
  for (const file of files) {
12643
12863
  if (!file.endsWith(ext)) continue;
@@ -12648,10 +12868,12 @@ var init_rules = __esm({
12648
12868
  const teamFileName = `${name}.md`;
12649
12869
  if (teamRules.has(teamFileName)) {
12650
12870
  const teamFilePath = path32.join(teamRulesDir, teamFileName);
12871
+ const localRule = await readFileSafe(localFilePath) ?? "";
12872
+ const teamRule = await readTeamRule(teamFilePath);
12651
12873
  const equal = isMdcTool ? cursorMdcBodyEqualsTeamMd(
12652
- await readFileSafe(localFilePath) ?? "",
12653
- await readTeamRule(teamFilePath)
12654
- ) : await fileContentEqual(localFilePath, teamFilePath);
12874
+ localRule,
12875
+ teamRule
12876
+ ) : isCopilotTool ? copilotInstructionsBodyEqualsTeamMd(localRule, teamRule) : await fileContentEqual(localFilePath, teamFilePath);
12655
12877
  if (equal) continue;
12656
12878
  const mtime = await getFileMtime(localFilePath);
12657
12879
  const existing = candidates.get(name);
@@ -12659,7 +12881,7 @@ var init_rules = __esm({
12659
12881
  candidates.set(name, { sourcePath: localFilePath, mtime, status: "modified" });
12660
12882
  }
12661
12883
  } else {
12662
- if (isMdcTool) continue;
12884
+ if (isMdcTool || isCopilotTool) continue;
12663
12885
  const existing = candidates.get(name);
12664
12886
  if (!existing) {
12665
12887
  const mtime = await getFileMtime(localFilePath);
@@ -12705,6 +12927,12 @@ var init_rules = __esm({
12705
12927
  throw new Error(`Cannot read rule source ${item.sourcePath}`);
12706
12928
  }
12707
12929
  await writeFile(dest, mergeCursorBodyIntoTeamMd(raw, await readFileSafe(dest)));
12930
+ } else if (item.sourcePath.endsWith(".instructions.md")) {
12931
+ const raw = await readFileSafe(item.sourcePath);
12932
+ if (raw === null) {
12933
+ throw new Error(`Cannot read rule source ${item.sourcePath}`);
12934
+ }
12935
+ await writeFile(dest, mergeCopilotBodyIntoTeamMd(raw, await readFileSafe(dest)));
12708
12936
  } else {
12709
12937
  await copyFile(item.sourcePath, dest);
12710
12938
  }
@@ -12715,15 +12943,14 @@ var init_rules = __esm({
12715
12943
  * Pull a single rule file to all configured AI tool rules/ directories.
12716
12944
  */
12717
12945
  async pullItem(item, teamConfig, localConfig) {
12718
- const baseDir = resolveBaseDir(localConfig);
12719
12946
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12720
12947
  if (isAgentExcluded(localConfig, tool)) continue;
12721
12948
  if (!toolPath.rules) continue;
12722
- if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) {
12949
+ if (!await isToolInstalledForConfig(tool, toolPath.rules, localConfig)) {
12723
12950
  log.debug(`Skipping rule sync for ${tool}: tool not installed`);
12724
12951
  continue;
12725
12952
  }
12726
- const destDir = path32.join(baseDir, toolPath.rules);
12953
+ const destDir = path32.join(resolveToolBaseDir(tool, localConfig), toolPath.rules);
12727
12954
  await ensureDir(destDir);
12728
12955
  const dest = path32.join(destDir, `${item.name}${ruleFileExtensionForTool(tool)}`);
12729
12956
  try {
@@ -12734,6 +12961,13 @@ var init_rules = __esm({
12734
12961
  }
12735
12962
  await writeFile(dest, teamRuleToCursorMdc(raw));
12736
12963
  await remove(path32.join(destDir, `${item.name}.md`));
12964
+ } else if (usesCopilotInstructions(tool)) {
12965
+ const raw = await readFileSafe(item.sourcePath);
12966
+ if (raw === null) {
12967
+ throw new Error(`Cannot read rule source ${item.sourcePath}`);
12968
+ }
12969
+ await writeFile(dest, teamRuleToCopilotInstructions(raw));
12970
+ await remove(path32.join(destDir, `${item.name}.md`));
12737
12971
  } else {
12738
12972
  await copyFile(item.sourcePath, dest);
12739
12973
  }
@@ -12748,7 +12982,6 @@ var init_rules = __esm({
12748
12982
  */
12749
12983
  async removeItem(name, teamConfig, localConfig) {
12750
12984
  const removed = [];
12751
- const baseDir = resolveBaseDir(localConfig);
12752
12985
  const teamFile = path32.join(localConfig.repo.localPath, "rules", `${name}.md`);
12753
12986
  if (await pathExists(teamFile)) {
12754
12987
  await remove(teamFile);
@@ -12758,6 +12991,7 @@ var init_rules = __esm({
12758
12991
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12759
12992
  if (!toolPath.rules) continue;
12760
12993
  if (isAgentExcluded(localConfig, tool)) continue;
12994
+ const baseDir = resolveToolBaseDir(tool, localConfig);
12761
12995
  const extensions = /* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"]);
12762
12996
  for (const extension of extensions) {
12763
12997
  const filePath = path32.join(baseDir, toolPath.rules, `${name}${extension}`);
@@ -12796,11 +13030,11 @@ var init_rules = __esm({
12796
13030
  }
12797
13031
  const teamRuleNames = new Set(rules.map((r) => r.name));
12798
13032
  const tombstones = await this.readTombstones(localConfig);
12799
- const baseDir = resolveBaseDir(localConfig);
12800
13033
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12801
13034
  if (!toolPath.rules) continue;
12802
13035
  if (isAgentExcluded(localConfig, tool)) continue;
12803
- if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
13036
+ if (!await isToolInstalledForConfig(tool, toolPath.rules, localConfig)) continue;
13037
+ const baseDir = resolveToolBaseDir(tool, localConfig);
12804
13038
  const destDir = path32.join(baseDir, toolPath.rules);
12805
13039
  if (!await pathExists(destDir)) continue;
12806
13040
  const ext = ruleFileExtensionForTool(tool);
@@ -12808,7 +13042,7 @@ var init_rules = __esm({
12808
13042
  for (const localFile of localFiles) {
12809
13043
  const ruleName = ruleStemFromFilename(localFile);
12810
13044
  if (ruleName === null) continue;
12811
- if (tool === "joycode" && !tombstones.has(ruleName)) continue;
13045
+ if ((tool === "joycode" || usesCopilotInstructions(tool)) && !tombstones.has(ruleName)) continue;
12812
13046
  if (isLegacyCursorRuleFile(tool, localFile)) {
12813
13047
  await remove(path32.join(destDir, localFile));
12814
13048
  log.debug(`Removed legacy .md rule ${localFile} from ${tool}`);
@@ -12824,8 +13058,9 @@ var init_rules = __esm({
12824
13058
  }
12825
13059
  await this.removeEmptyDirs(destDir);
12826
13060
  }
12827
- for (const [, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
13061
+ for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
12828
13062
  if (!toolPath.claudemd) continue;
13063
+ const baseDir = resolveToolBaseDir(tool, localConfig);
12829
13064
  const claudeMdPath = path32.join(baseDir, toolPath.claudemd);
12830
13065
  try {
12831
13066
  const content = await readFileSafe(claudeMdPath);
@@ -15869,6 +16104,10 @@ async function detectHomeInstalledAgents(candidateIds = SELF_MODE_AGENT_CHOICES)
15869
16104
  const home = getUserHome();
15870
16105
  const found = [];
15871
16106
  for (const id of candidateIds) {
16107
+ if (id === COPILOT_TOOL_ID) {
16108
+ if (await pathExists(getCopilotHome())) found.push(id);
16109
+ continue;
16110
+ }
15872
16111
  const skillsPath = KNOWN_AGENTS.find((a) => a.id === id)?.skillsPath;
15873
16112
  if (!skillsPath) continue;
15874
16113
  const rootSegment = skillsPath.split("/")[0];
@@ -15903,7 +16142,6 @@ function getEffectiveAgents(teamConfig, localConfig) {
15903
16142
  return [...byId.values()];
15904
16143
  }
15905
16144
  async function detectInstalledAgents(localConfig, teamConfig) {
15906
- const baseDir = resolveBaseDir(localConfig);
15907
16145
  const agents = getEffectiveAgents(teamConfig, localConfig);
15908
16146
  const scoped = scopedToolPaths(teamConfig, localConfig);
15909
16147
  const fromTeamConfig = new Set(
@@ -15911,12 +16149,11 @@ async function detectInstalledAgents(localConfig, teamConfig) {
15911
16149
  );
15912
16150
  const results = [];
15913
16151
  for (const agent of agents) {
15914
- const rootSegment = toolInstallRoot(agent.skillsPath);
15915
- const rootPath = path44.join(baseDir, rootSegment);
15916
- const installed = rootSegment ? await pathExists(rootPath) : false;
16152
+ const baseDir = resolveToolBaseDir(agent.id, localConfig);
16153
+ const installed = await isToolInstalledForConfig(agent.id, agent.skillsPath, localConfig);
15917
16154
  results.push({
15918
16155
  ...agent,
15919
- absoluteSkillsPath: `${baseDir}/${agent.skillsPath}`,
16156
+ absoluteSkillsPath: path44.join(baseDir, agent.skillsPath),
15920
16157
  installed,
15921
16158
  fromTeamConfig: fromTeamConfig.has(agent.id)
15922
16159
  });
@@ -15931,7 +16168,7 @@ var init_known_agents = __esm({
15931
16168
  init_types();
15932
16169
  init_base();
15933
16170
  init_home();
15934
- SELF_MODE_AGENT_CHOICES = ["claude", "codex", "cursor", "joycode", "codebuddy", "workbuddy"];
16171
+ SELF_MODE_AGENT_CHOICES = ["claude", "codex", "cursor", "copilot", "joycode", "codebuddy", "workbuddy"];
15935
16172
  KNOWN_AGENTS = [
15936
16173
  // Coding agents already wired through teamConfig.toolPaths defaults
15937
16174
  { id: "claude", displayName: "Claude Code", category: "coding", skillsPath: ".claude/skills" },
@@ -25055,10 +25292,12 @@ async function removeCore(type, names, options, localConfig, teamConfig) {
25055
25292
  log.info("Dry run \u2014 no changes made");
25056
25293
  return;
25057
25294
  }
25058
- const confirmed = await askConfirmation("Are you sure? [y/N] ");
25059
- if (!confirmed) {
25060
- log.info("Cancelled");
25061
- return;
25295
+ if (!options.force) {
25296
+ const confirmed = await askConfirmation("Are you sure? [y/N] ");
25297
+ if (!confirmed) {
25298
+ log.info("Cancelled");
25299
+ return;
25300
+ }
25062
25301
  }
25063
25302
  const spin = spinner(`Removing ${found.length} ${type}...`).start();
25064
25303
  let totalRemoved = 0;
@@ -25140,16 +25379,20 @@ var init_remove = __esm({
25140
25379
  // src/doctor.ts
25141
25380
  var doctor_exports = {};
25142
25381
  __export(doctor_exports, {
25143
- doctor: () => doctor
25382
+ buildChecks: () => buildChecks,
25383
+ doctor: () => doctor,
25384
+ resolveDoctorContext: () => resolveDoctorContext
25144
25385
  });
25145
25386
  import path70 from "path";
25146
- async function buildHookChecks(toolPaths, baseDir) {
25387
+ async function buildHookChecks(toolPaths, baseDir, localConfig) {
25147
25388
  const checks = [];
25148
25389
  for (const [tool, paths] of Object.entries(toolPaths)) {
25149
- if (!paths.settings) continue;
25150
- const settingsPath = path70.join(baseDir, paths.settings);
25390
+ const hookPath = paths.hooks ? path70.join(resolveToolBaseDir(tool, localConfig), paths.hooks) : paths.settings ? path70.join(baseDir, paths.settings) : void 0;
25391
+ if (!hookPath) continue;
25392
+ const settingsPath = hookPath;
25151
25393
  const parentDir = path70.dirname(settingsPath);
25152
- if (!await pathExists(parentDir)) continue;
25394
+ const installed = tool === COPILOT_TOOL_ID ? await isToolInstalledForConfig(tool, paths.hooks ?? paths.settings ?? "", localConfig) : await pathExists(parentDir);
25395
+ if (!installed) continue;
25153
25396
  checks.push({
25154
25397
  name: `teamai hooks in ${tool} settings`,
25155
25398
  check: async () => {
@@ -25176,28 +25419,20 @@ async function hasInstalledCodexHooks(toolPaths, baseDir) {
25176
25419
  }
25177
25420
  return false;
25178
25421
  }
25179
- async function doctor(options) {
25180
- log.info("Running diagnostics...\n");
25422
+ async function resolveDoctorContext() {
25181
25423
  const projectConfig = await detectProjectConfig();
25182
25424
  const localConfig = projectConfig ?? await loadLocalConfig();
25183
- if (!localConfig) {
25184
- console.log(" Scope: not initialized\n");
25185
- console.log(" \u2716 TeamAI is not initialized");
25186
- console.log(" \u2192 Run `teamai init <repo-url>` in a project, or add `--scope user` for all projects");
25187
- console.log("");
25188
- log.warn("Initialization is required before diagnostics can run.");
25189
- return false;
25190
- }
25191
- const scope = localConfig.scope ?? "user";
25192
- const scopeLabel = `${scope}${scope === "project" && localConfig.projectRoot ? ` (${localConfig.projectRoot})` : ""}`;
25193
- console.log(` Scope: ${scopeLabel}
25194
- `);
25425
+ if (!localConfig) return null;
25195
25426
  const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
25196
25427
  const toolPaths = teamConfig ? Object.fromEntries(
25197
25428
  Object.entries(scopedToolPaths(teamConfig, localConfig)).filter(([tool]) => !isAgentExcluded(localConfig, tool))
25198
25429
  ) : {};
25199
- const providerName = teamConfig?.provider;
25200
25430
  const baseDir = resolveHookScope(localConfig).baseDir;
25431
+ return { localConfig, teamConfig, toolPaths, baseDir };
25432
+ }
25433
+ async function buildChecks(ctx) {
25434
+ const { localConfig, teamConfig, toolPaths, baseDir } = ctx;
25435
+ const providerName = teamConfig?.provider;
25201
25436
  const checks = [];
25202
25437
  if (providerName === "tgit") {
25203
25438
  const { isGfInstalled: isGfInstalled2, gfIsAuthenticated: gfIsAuthenticated2 } = await Promise.resolve().then(() => (init_tgit(), tgit_exports));
@@ -25256,7 +25491,7 @@ async function doctor(options) {
25256
25491
  },
25257
25492
  fix: "Check teamai.yaml in team repo for syntax errors"
25258
25493
  },
25259
- ...await buildHookChecks(toolPaths, baseDir),
25494
+ ...await buildHookChecks(toolPaths, baseDir, localConfig),
25260
25495
  {
25261
25496
  name: "Env variables injected in shell profile",
25262
25497
  check: async () => {
@@ -25278,26 +25513,80 @@ async function doctor(options) {
25278
25513
  fix: "Run `teamai pull` to inject env variables into shell profile"
25279
25514
  }
25280
25515
  );
25281
- let allPassed = true;
25516
+ return checks;
25517
+ }
25518
+ async function runChecks(checks, onResult) {
25519
+ const results = [];
25282
25520
  for (const { name, check, fix } of checks) {
25283
25521
  const ok = await check();
25284
- if (ok) {
25285
- console.log(` \u2714 ${name}`);
25522
+ const result = ok ? { name, ok } : { name, ok, fix };
25523
+ results.push(result);
25524
+ onResult?.(result);
25525
+ }
25526
+ return results;
25527
+ }
25528
+ function emitReport(report) {
25529
+ console.log(JSON.stringify(report, null, 2));
25530
+ }
25531
+ function renderResult({ name, ok, fix }) {
25532
+ if (ok) {
25533
+ console.log(` \u2714 ${name}`);
25534
+ return;
25535
+ }
25536
+ console.log(` \u2716 ${name}`);
25537
+ if (fix) console.log(` \u2192 ${fix}`);
25538
+ }
25539
+ async function doctor(options) {
25540
+ const jsonMode = options.json === true;
25541
+ if (jsonMode) setStderrOnly(true);
25542
+ log.info("Running diagnostics...\n");
25543
+ const ctx = await resolveDoctorContext();
25544
+ if (!ctx) {
25545
+ const notInitialized = {
25546
+ name: "TeamAI is not initialized",
25547
+ ok: false,
25548
+ fix: "Run `teamai init <repo-url>` in a project, or add `--scope user` for all projects"
25549
+ };
25550
+ if (jsonMode) {
25551
+ emitReport({ ok: false, scope: null, checks: [notInitialized] });
25286
25552
  } else {
25287
- console.log(` \u2716 ${name}`);
25288
- if (fix) console.log(` \u2192 ${fix}`);
25289
- allPassed = false;
25553
+ console.log(" Scope: not initialized\n");
25554
+ renderResult(notInitialized);
25555
+ console.log("");
25290
25556
  }
25557
+ log.warn("Initialization is required before diagnostics can run.");
25558
+ return false;
25291
25559
  }
25560
+ const { localConfig, toolPaths, baseDir } = ctx;
25561
+ const scope = localConfig.scope ?? "user";
25562
+ if (!jsonMode) {
25563
+ const scopeLabel = `${scope}${scope === "project" && localConfig.projectRoot ? ` (${localConfig.projectRoot})` : ""}`;
25564
+ console.log(` Scope: ${scopeLabel}
25565
+ `);
25566
+ }
25567
+ const results = await runChecks(await buildChecks(ctx), jsonMode ? void 0 : renderResult);
25568
+ let allPassed = results.every((r) => r.ok);
25292
25569
  const { pkgDoctorReport: pkgDoctorReport2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
25293
25570
  const packageReport = await pkgDoctorReport2(localConfig, process.cwd());
25571
+ if (packageReport && !packageReport.allPassed) allPassed = false;
25572
+ const codexNote = await hasInstalledCodexHooks(toolPaths, baseDir) ? codexTrustReminder() : null;
25573
+ if (jsonMode) {
25574
+ emitReport({
25575
+ ok: allPassed,
25576
+ scope,
25577
+ checks: results,
25578
+ // pkgDoctorReport renders its own lines; they are human text, not checks.
25579
+ ...packageReport ? { packages: { ok: packageReport.allPassed, lines: packageReport.lines } } : {},
25580
+ ...codexNote ? { notes: [codexNote] } : {}
25581
+ });
25582
+ return allPassed;
25583
+ }
25294
25584
  if (packageReport) {
25295
25585
  for (const line of packageReport.lines) console.log(line);
25296
- if (!packageReport.allPassed) allPassed = false;
25297
25586
  }
25298
- if (await hasInstalledCodexHooks(toolPaths, baseDir)) {
25587
+ if (codexNote) {
25299
25588
  console.log("");
25300
- log.info(codexTrustReminder());
25589
+ log.info(codexNote);
25301
25590
  }
25302
25591
  console.log("");
25303
25592
  if (allPassed) {
@@ -25314,6 +25603,7 @@ var init_doctor = __esm({
25314
25603
  init_fs();
25315
25604
  init_logger();
25316
25605
  init_types();
25606
+ init_base();
25317
25607
  init_hooks2();
25318
25608
  init_home();
25319
25609
  }
@@ -25897,6 +26187,12 @@ async function saveTagsScopeConfig(localConfig) {
25897
26187
  } else {
25898
26188
  await saveLocalConfig(localConfig);
25899
26189
  }
26190
+ try {
26191
+ const state = await loadStateForScope(localConfig);
26192
+ state.lastPullRev = null;
26193
+ await saveStateForScope(state, localConfig);
26194
+ } catch {
26195
+ }
25900
26196
  }
25901
26197
  async function tagsList(options) {
25902
26198
  const localConfig = await resolveTagsScope();
@@ -26135,7 +26431,7 @@ function opencodePluginTargets(baseDir, scope) {
26135
26431
  }
26136
26432
  return targets;
26137
26433
  }
26138
- async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, teamRuleNames, teamAgentNames, hookTargets, scope) {
26434
+ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, teamRuleNames, teamAgentNames, hookTargets, standaloneHookManifestPath, scope) {
26139
26435
  const res = {
26140
26436
  hookFiles: [],
26141
26437
  openclawHookDirs: [],
@@ -26145,7 +26441,16 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
26145
26441
  ruleFiles: [],
26146
26442
  agentFiles: []
26147
26443
  };
26148
- if (tool === "opencode") {
26444
+ if (toolPath.hooks) {
26445
+ const hooksPath = path74.join(baseDir, toolPath.hooks);
26446
+ if (await pathExists(hooksPath) && (await hasTeamaiHooks(hooksPath, tool, standaloneHookManifestPath) || isEmptyHooksResidue(await readJson(hooksPath)))) {
26447
+ res.hookFiles.push({
26448
+ path: hooksPath,
26449
+ tool,
26450
+ manifestPath: standaloneHookManifestPath
26451
+ });
26452
+ }
26453
+ } else if (tool === "opencode") {
26149
26454
  const { resolveOpencodePluginDir: resolveOpencodePluginDir2, OPENCODE_HOOK_FILE: OPENCODE_HOOK_FILE2 } = await Promise.resolve().then(() => (init_opencode_hooks(), opencode_hooks_exports));
26150
26455
  for (const target of opencodePluginTargets(baseDir, scope)) {
26151
26456
  const pluginDir = resolveOpencodePluginDir2(target.baseDir, target.scope);
@@ -26232,6 +26537,10 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
26232
26537
  async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
26233
26538
  const baseDir = resolveBaseDir(localConfig);
26234
26539
  const teamaiHome = getDataHome(localConfig);
26540
+ const standaloneHookManifestPath = getManagedHooksPath(
26541
+ localConfig.scope,
26542
+ localConfig.projectRoot
26543
+ );
26235
26544
  const repoPath = localConfig.repo.localPath;
26236
26545
  const teamSkillNames = await collectTeamSkillNames(repoPath);
26237
26546
  for (const name of BUILTIN_SKILL_NAMES) teamSkillNames.add(name);
@@ -26268,11 +26577,12 @@ async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
26268
26577
  await discoverToolResources(
26269
26578
  tool,
26270
26579
  toolPath,
26271
- baseDir,
26580
+ resolveToolBaseDir(tool, localConfig),
26272
26581
  teamSkillNames,
26273
26582
  teamRuleNames,
26274
26583
  teamAgentNames,
26275
26584
  hookTargets,
26585
+ standaloneHookManifestPath,
26276
26586
  localConfig.scope
26277
26587
  )
26278
26588
  );
@@ -26890,16 +27200,16 @@ async function hooksList(_options) {
26890
27200
  const { localConfig, teamConfig } = await autoDetectInit();
26891
27201
  const { baseDir } = resolveHookScope(localConfig);
26892
27202
  const rows = [];
26893
- for (const [tool, paths] of Object.entries(teamConfig.toolPaths)) {
26894
- if (!paths.settings) {
27203
+ for (const [tool, paths] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
27204
+ const hookPath = paths.hooks ? path76.join(resolveToolBaseDir(tool, localConfig), paths.hooks) : paths.settings ? path76.join(baseDir, paths.settings) : void 0;
27205
+ if (!hookPath) {
26895
27206
  rows.push({ tool, status: "not configured", settingsPath: "no settings configured" });
26896
27207
  continue;
26897
27208
  }
26898
- const settingsPath = path76.join(baseDir, paths.settings);
26899
27209
  rows.push({
26900
27210
  tool,
26901
- status: await getHookStatus(settingsPath, tool),
26902
- settingsPath: formatDisplayPath(settingsPath)
27211
+ status: await getHookStatus(hookPath, tool),
27212
+ settingsPath: formatDisplayPath(hookPath)
26903
27213
  });
26904
27214
  }
26905
27215
  console.log(formatHooksList(rows));
@@ -26928,6 +27238,18 @@ async function hooksRemove(_options) {
26928
27238
  const { localConfig, teamConfig } = await autoDetectInit();
26929
27239
  const { baseDir, manifestPath } = resolveHookScope(localConfig);
26930
27240
  await reconcileHooksToAllTools(teamConfig.toolPaths, baseDir, [], manifestPath, { removeAll: true });
27241
+ const copilotPaths = scopedToolPaths(teamConfig, localConfig)[COPILOT_TOOL_ID];
27242
+ if (copilotPaths?.hooks) {
27243
+ await reconcileHooks(
27244
+ path76.join(resolveToolBaseDir(COPILOT_TOOL_ID, localConfig), copilotPaths.hooks),
27245
+ COPILOT_TOOL_ID,
27246
+ [],
27247
+ {
27248
+ manifestPath: getManagedHooksPath(localConfig.scope, localConfig.projectRoot),
27249
+ removeAll: true
27250
+ }
27251
+ );
27252
+ }
26931
27253
  await sweepLegacyProjectHooks(teamConfig.toolPaths, localConfig);
26932
27254
  log.success("Hooks removed from all AI tool settings");
26933
27255
  }
@@ -43317,16 +43639,16 @@ membersCmd.command("list").description("List team members").action(async () => {
43317
43639
  const { listMembers: listMembers2 } = await Promise.resolve().then(() => (init_members(), members_exports));
43318
43640
  await listMembers2(globalOpts);
43319
43641
  });
43320
- program.command("remove <type> <names...>").description("Remove resource(s) from team repo and all local AI tools (type: skills|rules|agents|mcp)").action(async (type, names) => {
43642
+ program.command("remove <type> <names...>").description("Remove resource(s) from team repo and all local AI tools (type: skills|rules|agents|mcp)").option("--force", "Skip confirmation prompt").action(async (type, names, cmdOpts) => {
43321
43643
  const globalOpts = program.opts();
43322
43644
  const { remove: remove3 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
43323
- await remove3(type, names, globalOpts);
43645
+ await remove3(type, names, { ...globalOpts, ...cmdOpts });
43324
43646
  });
43325
43647
  registerPackagesCommand(program);
43326
- program.command("doctor").description("Diagnose configuration issues").action(async () => {
43648
+ program.command("doctor").description("Diagnose configuration issues").option("--json", "Output the report as JSON (suitable for CI)").action(async (cmdOpts) => {
43327
43649
  const globalOpts = program.opts();
43328
43650
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
43329
- const allPassed = await doctor2(globalOpts);
43651
+ const allPassed = await doctor2({ ...globalOpts, ...cmdOpts });
43330
43652
  if (!allPassed) process.exitCode = 1;
43331
43653
  });
43332
43654
  var rolesCmd = program.command("roles").description("Manage team roles and resource namespaces").action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.25.0-beta.1",
3
+ "version": "0.25.0-beta.2",
4
4
  "description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {