teamai-cli 0.16.5 → 0.16.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.
package/dist/index.js CHANGED
@@ -807,119 +807,80 @@ var init_config = __esm({
807
807
  });
808
808
 
809
809
  // src/hooks.ts
810
+ var hooks_exports = {};
811
+ __export(hooks_exports, {
812
+ CLAUDE_TO_CURSOR_EVENTS: () => CLAUDE_TO_CURSOR_EVENTS,
813
+ TEAMAI_HOOK_SUBCOMMANDS: () => TEAMAI_HOOK_SUBCOMMANDS,
814
+ TEAMAI_LEGACY_HOOK_SUBCOMMANDS: () => TEAMAI_LEGACY_HOOK_SUBCOMMANDS,
815
+ injectHooks: () => injectHooks,
816
+ injectHooksToAllTools: () => injectHooksToAllTools,
817
+ removeHooks: () => removeHooks
818
+ });
810
819
  import path6 from "path";
811
- function getTrackCommand(tool) {
812
- return `bash -lc "teamai track --stdin --tool ${tool} 2>/dev/null" || true`;
813
- }
814
- function getTrackSlashCommand(tool) {
815
- return `bash -lc "teamai track-slash --stdin --tool ${tool} 2>/dev/null" || true`;
816
- }
817
- function getDashboardReportCommand(tool) {
818
- return `bash -lc "teamai dashboard-report --stdin --tool ${tool} 2>/dev/null" || true`;
819
- }
820
- function getAutoRecallCommand(tool) {
821
- return `bash -lc "teamai auto-recall --stdin 2>/dev/null" || true`;
822
- }
823
- function getContributeCheckCommand(tool) {
824
- return `bash -lc "teamai contribute-check --stdin --tool ${tool} 2>/dev/null" || true`;
820
+ function getDispatchCommand(event, tool, matcher) {
821
+ const matcherArg = matcher && matcher !== "*" ? ` --matcher ${matcher}` : "";
822
+ return `bash -lc "teamai hook-dispatch ${event} --tool ${tool}${matcherArg} 2>/dev/null" || true`;
825
823
  }
826
824
  function getClaudeHooks(tool) {
827
825
  return [
826
+ // ─── SessionStart: single dispatcher handles pull + dashboard-report ────
828
827
  {
829
828
  eventType: "SessionStart",
830
- descriptionKeyword: "Auto-pull",
829
+ descriptionKeyword: "Hook dispatch session-start",
831
830
  hook: {
832
831
  matcher: "*",
833
- hooks: [{ type: "command", command: TEAMAI_PULL_COMMAND }],
834
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Auto-pull team resources on session start`
832
+ hooks: [{ type: "command", command: getDispatchCommand("session-start", tool) }],
833
+ description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Hook dispatch session-start`
835
834
  }
836
835
  },
836
+ // ─── Stop: single dispatcher handles update + contribute-check + dashboard-report ────
837
837
  {
838
838
  eventType: "Stop",
839
- descriptionKeyword: "Auto-update",
839
+ descriptionKeyword: "Hook dispatch stop",
840
840
  hook: {
841
841
  matcher: "*",
842
- // 10s timeout: npm registry call typically <5s; cap at 10s so a stalled
843
- // call cannot delay session shutdown by the default 60s.
844
- hooks: [{ type: "command", command: TEAMAI_UPDATE_COMMAND, timeout: 10 }],
845
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Auto-update on session end`
842
+ hooks: [{ type: "command", command: getDispatchCommand("stop", tool) }],
843
+ description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Hook dispatch stop`
846
844
  }
847
845
  },
848
- // ─── Contribute check (smart threshold hint at session end) ────────
846
+ // ─── PostToolUse (*): dashboard-report ────
849
847
  {
850
- eventType: "Stop",
851
- descriptionKeyword: "Contribute check",
848
+ eventType: "PostToolUse",
849
+ descriptionKeyword: "Hook dispatch post-tool-use wildcard",
852
850
  hook: {
853
851
  matcher: "*",
854
- hooks: [{ type: "command", command: getContributeCheckCommand(tool) }],
855
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Contribute check on session end`
852
+ hooks: [{ type: "command", command: getDispatchCommand("post-tool-use", tool) }],
853
+ description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Hook dispatch post-tool-use wildcard`
856
854
  }
857
855
  },
856
+ // ─── PostToolUse (Skill): track ────
858
857
  {
859
858
  eventType: "PostToolUse",
860
- descriptionKeyword: "Track skill",
859
+ descriptionKeyword: "Hook dispatch post-tool-use Skill",
861
860
  hook: {
862
861
  matcher: "Skill",
863
- hooks: [{ type: "command", command: getTrackCommand(tool) }],
864
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Track skill usage`
862
+ hooks: [{ type: "command", command: getDispatchCommand("post-tool-use", tool, "Skill") }],
863
+ description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Hook dispatch post-tool-use Skill`
865
864
  }
866
865
  },
867
- {
868
- eventType: "UserPromptSubmit",
869
- descriptionKeyword: "Track slash",
870
- hook: {
871
- matcher: "*",
872
- hooks: [{ type: "command", command: getTrackSlashCommand(tool) }],
873
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Track slash command usage`
874
- }
875
- },
876
- // ─── Auto-recall (search knowledge base on search tools + Bash errors) ────────
877
- // Split into 4 precise matchers to avoid spawning a process for tools that
878
- // would immediately exit (auto-recall only handles Bash/Grep/WebSearch/WebFetch).
866
+ // ─── PostToolUse (Bash/Grep/WebSearch/WebFetch): auto-recall ────
879
867
  ...["Bash", "Grep", "WebSearch", "WebFetch"].map((matcher) => ({
880
868
  eventType: "PostToolUse",
881
- descriptionKeyword: `Auto-recall ${matcher}`,
869
+ descriptionKeyword: `Hook dispatch post-tool-use ${matcher}`,
882
870
  hook: {
883
871
  matcher,
884
- hooks: [{ type: "command", command: getAutoRecallCommand(tool) }],
885
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Auto-recall on ${matcher}`
872
+ hooks: [{ type: "command", command: getDispatchCommand("post-tool-use", tool, matcher) }],
873
+ description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Hook dispatch post-tool-use ${matcher}`
886
874
  }
887
875
  })),
888
- // ─── Dashboard hooks (independent from tracking) ────────
889
- {
890
- eventType: "SessionStart",
891
- descriptionKeyword: "Dashboard report",
892
- hook: {
893
- matcher: "*",
894
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
895
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on session start`
896
- }
897
- },
898
- {
899
- eventType: "Stop",
900
- descriptionKeyword: "Dashboard stop",
901
- hook: {
902
- matcher: "*",
903
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
904
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on session stop`
905
- }
906
- },
907
- {
908
- eventType: "PostToolUse",
909
- descriptionKeyword: "Dashboard tool",
910
- hook: {
911
- matcher: "*",
912
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
913
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on tool use`
914
- }
915
- },
876
+ // ─── UserPromptSubmit: track-slash + dashboard-report ────
916
877
  {
917
878
  eventType: "UserPromptSubmit",
918
- descriptionKeyword: "Dashboard prompt",
879
+ descriptionKeyword: "Hook dispatch prompt-submit",
919
880
  hook: {
920
881
  matcher: "*",
921
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
922
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on prompt submit`
882
+ hooks: [{ type: "command", command: getDispatchCommand("prompt-submit", tool) }],
883
+ description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Hook dispatch prompt-submit`
923
884
  }
924
885
  }
925
886
  ];
@@ -927,26 +888,22 @@ function getClaudeHooks(tool) {
927
888
  function buildCursorHooks(tool) {
928
889
  return {
929
890
  sessionStart: [
930
- { command: TEAMAI_PULL_COMMAND, timeout: 30 },
931
- { command: getDashboardReportCommand(tool), timeout: 10 }
891
+ { command: getDispatchCommand("session-start", tool), timeout: 60 }
932
892
  ],
933
893
  stop: [
934
- { command: TEAMAI_UPDATE_COMMAND, timeout: 10 },
935
- { command: getDashboardReportCommand(tool), timeout: 10 },
936
- { command: getContributeCheckCommand(tool), timeout: 10 }
894
+ { command: getDispatchCommand("stop", tool), timeout: 15 }
937
895
  ],
938
896
  postToolUse: [
939
- { command: getTrackCommand(tool), timeout: 10, matcher: "Skill" },
940
- { command: getDashboardReportCommand(tool), timeout: 10 },
897
+ { command: getDispatchCommand("post-tool-use", tool), timeout: 10 },
898
+ { command: getDispatchCommand("post-tool-use", tool, "Skill"), timeout: 10, matcher: "Skill" },
941
899
  ...["Bash", "Grep", "WebSearch", "WebFetch"].map((matcher) => ({
942
- command: getAutoRecallCommand(tool),
943
- timeout: 3,
900
+ command: getDispatchCommand("post-tool-use", tool, matcher),
901
+ timeout: 10,
944
902
  matcher
945
903
  }))
946
904
  ],
947
905
  beforeSubmitPrompt: [
948
- { command: getTrackSlashCommand(tool), timeout: 10 },
949
- { command: getDashboardReportCommand(tool), timeout: 10 }
906
+ { command: getDispatchCommand("prompt-submit", tool), timeout: 10 }
950
907
  ]
951
908
  };
952
909
  }
@@ -1063,6 +1020,22 @@ async function injectCursorHooks(hooksPath, tool) {
1063
1020
  }
1064
1021
  const desiredHooks = buildCursorHooks(tool);
1065
1022
  let changed = false;
1023
+ for (const event of Object.keys(hooksJson.hooks)) {
1024
+ const entries = hooksJson.hooks[event];
1025
+ const filtered = entries.filter((h) => {
1026
+ if (!isTeamaiHookCommand(h.command)) return true;
1027
+ const subcmd = extractTeamaiSubcommand(h.command);
1028
+ return subcmd === "hook-dispatch";
1029
+ });
1030
+ if (filtered.length !== entries.length) {
1031
+ changed = true;
1032
+ if (filtered.length === 0) {
1033
+ delete hooksJson.hooks[event];
1034
+ } else {
1035
+ hooksJson.hooks[event] = filtered;
1036
+ }
1037
+ }
1038
+ }
1066
1039
  const desiredEvents = new Set(Object.keys(desiredHooks));
1067
1040
  for (const event of Object.keys(hooksJson.hooks)) {
1068
1041
  if (desiredEvents.has(event)) continue;
@@ -1156,16 +1129,21 @@ async function injectHooksToAllTools(toolPaths, baseDir) {
1156
1129
  }
1157
1130
  }
1158
1131
  }
1159
- var TEAMAI_PULL_COMMAND, TEAMAI_UPDATE_COMMAND, TEAMAI_HOOK_SUBCOMMANDS, CURSOR_TOOLS, TEAMAI_COMMAND_MARKERS;
1132
+ var TEAMAI_HOOK_SUBCOMMANDS, TEAMAI_LEGACY_HOOK_SUBCOMMANDS, CLAUDE_TO_CURSOR_EVENTS, CURSOR_TOOLS, TEAMAI_COMMAND_MARKERS;
1160
1133
  var init_hooks = __esm({
1161
1134
  "src/hooks.ts"() {
1162
1135
  "use strict";
1163
1136
  init_fs();
1164
1137
  init_logger();
1165
1138
  init_types();
1166
- TEAMAI_PULL_COMMAND = 'bash -lc "teamai pull 2>/dev/null" || true';
1167
- TEAMAI_UPDATE_COMMAND = 'bash -lc "teamai update 2>/dev/null" || true';
1168
- TEAMAI_HOOK_SUBCOMMANDS = ["pull", "update", "track", "track-slash", "dashboard-report", "contribute-check", "auto-recall"];
1139
+ TEAMAI_HOOK_SUBCOMMANDS = ["hook-dispatch"];
1140
+ TEAMAI_LEGACY_HOOK_SUBCOMMANDS = ["pull", "update", "track", "track-slash", "dashboard-report", "contribute-check", "auto-recall"];
1141
+ CLAUDE_TO_CURSOR_EVENTS = {
1142
+ SessionStart: "sessionStart",
1143
+ Stop: "stop",
1144
+ PostToolUse: "postToolUse",
1145
+ UserPromptSubmit: "beforeSubmitPrompt"
1146
+ };
1169
1147
  CURSOR_TOOLS = /* @__PURE__ */ new Set(["cursor"]);
1170
1148
  TEAMAI_COMMAND_MARKERS = [
1171
1149
  "teamai pull",
@@ -2396,6 +2374,10 @@ async function init(options) {
2396
2374
  if (options.scope === "project" || options.scope === "user") {
2397
2375
  scope = options.scope;
2398
2376
  } else {
2377
+ const userPath = getTeamaiHome("user");
2378
+ const projectPath = getTeamaiHome("project", process.cwd());
2379
+ log.info(` user \u2192 ${userPath}/`);
2380
+ log.info(` project \u2192 ${projectPath}/`);
2399
2381
  const scopeAnswer = await askQuestion("Scope [user/project] (default: user): ", "user");
2400
2382
  if (scopeAnswer.toLowerCase() === "project") {
2401
2383
  scope = "project";
@@ -6414,7 +6396,27 @@ async function collectClaudemdFiles(repoPath, roleContext) {
6414
6396
  }
6415
6397
  return contents;
6416
6398
  }
6399
+ async function autoMigrateHooksIfNeeded() {
6400
+ const home = process.env.HOME ?? "";
6401
+ const primarySettings = path26.join(home, ".claude", "settings.json");
6402
+ if (!await pathExists(primarySettings)) return;
6403
+ const content = await readFileSafe(primarySettings);
6404
+ if (!content) return;
6405
+ if (content.includes("hook-dispatch")) return;
6406
+ if (!content.includes("teamai")) return;
6407
+ log.debug("Auto-migrating hooks to dispatch format...");
6408
+ const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6409
+ const { injectHooksToAllTools: injectHooksToAllTools2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
6410
+ const { localConfig, teamConfig } = await autoDetectInit2();
6411
+ const baseDir = resolveBaseDir(localConfig);
6412
+ await injectHooksToAllTools2(teamConfig.toolPaths, baseDir);
6413
+ log.debug("Hooks migrated to dispatch format");
6414
+ }
6417
6415
  async function pull(options) {
6416
+ try {
6417
+ await autoMigrateHooksIfNeeded();
6418
+ } catch {
6419
+ }
6418
6420
  let userConfig = null;
6419
6421
  try {
6420
6422
  userConfig = await loadLocalConfigForScope("user");
@@ -11067,6 +11069,252 @@ var init_auto_recall = __esm({
11067
11069
  }
11068
11070
  });
11069
11071
 
11072
+ // src/hook-dispatch.ts
11073
+ function withTimeout(promise, ms, handlerName) {
11074
+ return new Promise((resolve, reject) => {
11075
+ const timer = setTimeout(() => {
11076
+ reject(new Error(`Handler "${handlerName}" exceeded timeout of ${ms}ms`));
11077
+ }, ms);
11078
+ promise.then(
11079
+ (val) => {
11080
+ clearTimeout(timer);
11081
+ resolve(val);
11082
+ },
11083
+ (err) => {
11084
+ clearTimeout(timer);
11085
+ reject(err);
11086
+ }
11087
+ );
11088
+ });
11089
+ }
11090
+ function createDispatcher(config) {
11091
+ return {
11092
+ async dispatch(event, matcher, stdin, tool) {
11093
+ const matched = config.handlers.filter((reg) => {
11094
+ if (reg.event !== event) return false;
11095
+ return reg.matcher === "*" || reg.matcher === matcher;
11096
+ });
11097
+ const settled = await Promise.allSettled(
11098
+ matched.map((reg) => {
11099
+ const timeoutMs = reg.timeoutMs ?? DEFAULT_TIMEOUT_MS;
11100
+ return withTimeout(reg.handler.execute(stdin, tool), timeoutMs, reg.handler.name);
11101
+ })
11102
+ );
11103
+ let output = null;
11104
+ const errors = [];
11105
+ for (let i = 0; i < settled.length; i++) {
11106
+ const result = settled[i];
11107
+ const handlerName = matched[i].handler.name;
11108
+ if (result.status === "rejected") {
11109
+ errors.push({
11110
+ handlerName,
11111
+ error: result.reason instanceof Error ? result.reason : new Error(String(result.reason))
11112
+ });
11113
+ } else if (result.status === "fulfilled" && result.value != null) {
11114
+ if (output === null) {
11115
+ output = result.value;
11116
+ }
11117
+ }
11118
+ }
11119
+ return { output, errors };
11120
+ }
11121
+ };
11122
+ }
11123
+ var DEFAULT_TIMEOUT_MS;
11124
+ var init_hook_dispatch = __esm({
11125
+ "src/hook-dispatch.ts"() {
11126
+ "use strict";
11127
+ DEFAULT_TIMEOUT_MS = 6e4;
11128
+ }
11129
+ });
11130
+
11131
+ // src/hook-handlers.ts
11132
+ function buildHandlerRegistry() {
11133
+ return [
11134
+ // ─── SessionStart ─────────────────────────────────
11135
+ { event: "session-start", matcher: "*", handler: pullHandler, timeoutMs: PULL_TIMEOUT_MS },
11136
+ { event: "session-start", matcher: "*", handler: dashboardReportHandler, timeoutMs: DASHBOARD_TIMEOUT_MS },
11137
+ // ─── Stop ─────────────────────────────────────────
11138
+ { event: "stop", matcher: "*", handler: updateHandler, timeoutMs: UPDATE_TIMEOUT_MS },
11139
+ { event: "stop", matcher: "*", handler: contributeCheckHandler, timeoutMs: CONTRIBUTE_CHECK_TIMEOUT_MS },
11140
+ { event: "stop", matcher: "*", handler: dashboardReportHandler, timeoutMs: DASHBOARD_TIMEOUT_MS },
11141
+ // ─── PostToolUse ──────────────────────────────────
11142
+ { event: "post-tool-use", matcher: "*", handler: dashboardReportHandler, timeoutMs: DASHBOARD_TIMEOUT_MS },
11143
+ { event: "post-tool-use", matcher: "Skill", handler: trackHandler, timeoutMs: TRACK_TIMEOUT_MS },
11144
+ ...["Bash", "Grep", "WebSearch", "WebFetch"].map((m) => ({
11145
+ event: "post-tool-use",
11146
+ matcher: m,
11147
+ handler: autoRecallHandler,
11148
+ timeoutMs: AUTO_RECALL_TIMEOUT_MS
11149
+ })),
11150
+ // ─── UserPromptSubmit ─────────────────────────────
11151
+ { event: "prompt-submit", matcher: "*", handler: trackSlashHandler, timeoutMs: TRACK_TIMEOUT_MS },
11152
+ { event: "prompt-submit", matcher: "*", handler: dashboardReportHandler, timeoutMs: DASHBOARD_TIMEOUT_MS }
11153
+ ];
11154
+ }
11155
+ var PULL_TIMEOUT_MS, UPDATE_TIMEOUT_MS, TRACK_TIMEOUT_MS, DASHBOARD_TIMEOUT_MS, CONTRIBUTE_CHECK_TIMEOUT_MS, AUTO_RECALL_TIMEOUT_MS, pullHandler, updateHandler, dashboardReportHandler, trackHandler, trackSlashHandler, contributeCheckHandler, autoRecallHandler;
11156
+ var init_hook_handlers = __esm({
11157
+ "src/hook-handlers.ts"() {
11158
+ "use strict";
11159
+ PULL_TIMEOUT_MS = 6e4;
11160
+ UPDATE_TIMEOUT_MS = 1e4;
11161
+ TRACK_TIMEOUT_MS = 5e3;
11162
+ DASHBOARD_TIMEOUT_MS = 5e3;
11163
+ CONTRIBUTE_CHECK_TIMEOUT_MS = 1e4;
11164
+ AUTO_RECALL_TIMEOUT_MS = 1e4;
11165
+ pullHandler = {
11166
+ name: "pull",
11167
+ async execute(_stdin, _tool) {
11168
+ const { pull: pull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
11169
+ await pull2({ silent: true });
11170
+ return null;
11171
+ }
11172
+ };
11173
+ updateHandler = {
11174
+ name: "update",
11175
+ async execute(_stdin, _tool) {
11176
+ const { doUpdate: doUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
11177
+ await doUpdate2();
11178
+ return null;
11179
+ }
11180
+ };
11181
+ dashboardReportHandler = {
11182
+ name: "dashboard-report",
11183
+ async execute(stdin, tool) {
11184
+ const { parseHookEvent: parseHookEvent2, appendEvent: appendEvent2, compactEvents: compactEvents2 } = await Promise.resolve().then(() => (init_dashboard_collector(), dashboard_collector_exports));
11185
+ const raw = JSON.stringify(stdin);
11186
+ const event = await parseHookEvent2(raw, tool);
11187
+ if (event) {
11188
+ await appendEvent2(event);
11189
+ compactEvents2().catch(() => {
11190
+ });
11191
+ }
11192
+ return null;
11193
+ }
11194
+ };
11195
+ trackHandler = {
11196
+ name: "track",
11197
+ async execute(stdin, tool) {
11198
+ const { extractSkillName: extractSkillName2, isValidSkillName: isValidSkillName2, appendUsageEvent: appendUsageEvent2, updateKnownSkills: updateKnownSkills2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
11199
+ const toolName = stdin.tool_name;
11200
+ if (typeof toolName !== "string") return null;
11201
+ const toolInput = stdin.tool_input;
11202
+ if (!toolInput || typeof toolInput !== "object") return null;
11203
+ let skillName = null;
11204
+ let toolSource = tool;
11205
+ if (toolName === "Skill") {
11206
+ skillName = extractSkillName2(toolInput);
11207
+ } else if (toolName === "Read") {
11208
+ const filePath = (typeof toolInput.file_path === "string" ? toolInput.file_path : null) ?? (typeof toolInput.path === "string" ? toolInput.path : null);
11209
+ if (typeof filePath === "string" && /\/SKILL\.md$/i.test(filePath)) {
11210
+ skillName = extractSkillName2({ skill: filePath });
11211
+ toolSource = "cursor";
11212
+ }
11213
+ } else {
11214
+ return null;
11215
+ }
11216
+ if (!skillName || !isValidSkillName2(skillName)) return null;
11217
+ await appendUsageEvent2({ skill: skillName, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool: toolSource });
11218
+ await updateKnownSkills2(skillName);
11219
+ return null;
11220
+ }
11221
+ };
11222
+ trackSlashHandler = {
11223
+ name: "track-slash",
11224
+ async execute(stdin, tool) {
11225
+ const { extractSkillName: extractSkillName2, isValidSkillName: isValidSkillName2, appendUsageEvent: appendUsageEvent2, updateKnownSkills: updateKnownSkills2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
11226
+ const prompt = stdin.prompt;
11227
+ if (typeof prompt !== "string" || !prompt.startsWith("/")) return null;
11228
+ const match = prompt.match(/^\/([\w-]+)/);
11229
+ if (!match) return null;
11230
+ const skillName = match[1];
11231
+ if (!isValidSkillName2(skillName)) return null;
11232
+ await appendUsageEvent2({ skill: skillName, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool });
11233
+ await updateKnownSkills2(skillName);
11234
+ return null;
11235
+ }
11236
+ };
11237
+ contributeCheckHandler = {
11238
+ name: "contribute-check",
11239
+ async execute(stdin, _tool) {
11240
+ const { contributeCheckForSession: contributeCheckForSession2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
11241
+ const sessionId = typeof stdin.session_id === "string" ? stdin.session_id : null;
11242
+ if (!sessionId) return null;
11243
+ const { hint } = await contributeCheckForSession2(sessionId);
11244
+ if (hint) {
11245
+ return JSON.stringify({ stopReason: hint });
11246
+ }
11247
+ return null;
11248
+ }
11249
+ };
11250
+ autoRecallHandler = {
11251
+ name: "auto-recall",
11252
+ async execute(stdin, _tool) {
11253
+ const { autoRecall: autoRecall2 } = await Promise.resolve().then(() => (init_auto_recall(), auto_recall_exports));
11254
+ let capturedOutput = null;
11255
+ const originalWrite = process.stdout.write.bind(process.stdout);
11256
+ process.stdout.write = ((chunk) => {
11257
+ if (typeof chunk === "string") {
11258
+ capturedOutput = chunk;
11259
+ } else if (Buffer.isBuffer(chunk)) {
11260
+ capturedOutput = chunk.toString();
11261
+ }
11262
+ return true;
11263
+ });
11264
+ try {
11265
+ await autoRecall2();
11266
+ } finally {
11267
+ process.stdout.write = originalWrite;
11268
+ }
11269
+ return capturedOutput;
11270
+ }
11271
+ };
11272
+ }
11273
+ });
11274
+
11275
+ // src/hook-dispatch-cli.ts
11276
+ var hook_dispatch_cli_exports = {};
11277
+ __export(hook_dispatch_cli_exports, {
11278
+ hookDispatchCli: () => hookDispatchCli
11279
+ });
11280
+ async function readStdin4() {
11281
+ if (process.stdin.isTTY) return "";
11282
+ const chunks = [];
11283
+ for await (const chunk of process.stdin) {
11284
+ chunks.push(chunk);
11285
+ }
11286
+ return Buffer.concat(chunks).toString("utf-8");
11287
+ }
11288
+ async function hookDispatchCli(event, tool, matcher) {
11289
+ const raw = await readStdin4();
11290
+ let stdin = {};
11291
+ if (raw.trim()) {
11292
+ try {
11293
+ stdin = JSON.parse(raw);
11294
+ } catch {
11295
+ log.debug(`hook-dispatch: failed to parse STDIN JSON for event=${event}`);
11296
+ return;
11297
+ }
11298
+ }
11299
+ const registry = buildHandlerRegistry();
11300
+ const dispatcher = createDispatcher({ handlers: registry });
11301
+ const result = await dispatcher.dispatch(event, matcher, stdin, tool);
11302
+ for (const err of result.errors) {
11303
+ log.debug(`hook-dispatch: handler "${err.handlerName}" failed: ${err.error.message}`);
11304
+ }
11305
+ if (result.output) {
11306
+ process.stdout.write(result.output);
11307
+ }
11308
+ }
11309
+ var init_hook_dispatch_cli = __esm({
11310
+ "src/hook-dispatch-cli.ts"() {
11311
+ "use strict";
11312
+ init_hook_dispatch();
11313
+ init_hook_handlers();
11314
+ init_logger();
11315
+ }
11316
+ });
11317
+
11070
11318
  // src/index.ts
11071
11319
  init_logger();
11072
11320
  import { createRequire as createRequire2 } from "module";
@@ -11343,5 +11591,9 @@ program.command("auto-recall").description("Auto-recall team knowledge on tool e
11343
11591
  await autoRecall2();
11344
11592
  }
11345
11593
  });
11594
+ program.command("hook-dispatch <event>").description("Unified hook dispatcher \u2014 handles all teamai hooks for a given event in one process").option("--tool <name>", "Tool identifier (e.g. claude, claude-internal, cursor)").option("--matcher <matcher>", "Hook matcher for PostToolUse (e.g. Skill, Bash)").action(async (event, cmdOpts) => {
11595
+ const { hookDispatchCli: hookDispatchCli2 } = await Promise.resolve().then(() => (init_hook_dispatch_cli(), hook_dispatch_cli_exports));
11596
+ await hookDispatchCli2(event, cmdOpts.tool ?? "claude", cmdOpts.matcher ?? "*");
11597
+ });
11346
11598
  program.parse();
11347
11599
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.16.5",
3
+ "version": "0.16.7",
4
4
  "description": "TeamAI — the team harness for AI agents (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {
7
- "teamai": "./dist/index.js"
7
+ "teamai": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist/**/*.js",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "https://github.com/Tencent/teamai-cli.git"
32
+ "url": "git+https://github.com/Tencent/teamai-cli.git"
33
33
  },
34
34
  "homepage": "https://github.com/Tencent/teamai-cli#readme",
35
35
  "bugs": {
@@ -243,10 +243,10 @@ When invoked, first determine the subcommand (init/ingest/query/lint/status/expo
243
243
 
244
244
  #### Step 1 — Parse arguments
245
245
 
246
- - `WIKI_DIR`: wiki 目录路径。按以下顺序检测:
247
- 1. team repo 中的 `wiki/` 目录(如果当前项目已通过 `teamai init` 配置)→ 首选
248
- 2. `~/.claude-internal/wiki/` `~/.claude/wiki/`(本地 AI 工具 wiki 目录)
249
- 3. 当前目录的 `./wiki/`(fallback)
246
+ - `WIKI_DIR`: 根据当前项目的 teamai scope 决定:
247
+ - 读取 `<projectRoot>/.teamai/config.yaml` 中的 `scope` 字段
248
+ - **project scope** `<projectRoot>/.teamai/wiki/`
249
+ - **user scope**(或无项目配置时)→ `~/.teamai/wiki/`
250
250
  - `SOURCE_DIR`: 可选的 `dir` 参数
251
251
 
252
252
  如果 `WIKI_DIR` 已经存在且包含 `_metadata.json`,提示用户已经初始化过,询问是否要重新初始化。
@@ -1009,7 +1009,7 @@ Wiki exported to: <path>
1009
1009
  8. **_metadata.json 是真相来源** — 页面列表、文件哈希、链接图都以此为准。
1010
1010
  9. **命名一致性** — 文件名 kebab-case,标题 Title Case 或人类可读中文。
1011
1011
  10. **幂等性** — 重复 ingest 同一源目录应产生相同结果(不会重复创建页面)。
1012
- 11. **wiki 路径推断**优先使用 team repo wiki/ 目录(通过 `teamai pull` 同步到本地);其次检查 `~/.claude-internal/wiki/` `~/.claude/wiki/`;最后 fallback `./wiki/`。
1012
+ 11. **wiki 路径**读取 `<projectRoot>/.teamai/config.yaml``scope` 字段:project scope 用 `<projectRoot>/.teamai/wiki/`,user scope `~/.teamai/wiki/`。与 teamai push/pull `WikiHandler.getSharedWikiDir()` 逻辑对齐。
1013
1013
  12. **Obsidian 兼容** — 所有 `[[links]]` 使用 Obsidian 格式,方便用户在 Obsidian 中直接浏览。
1014
1014
  13. **语言** — Wiki 页面内容默认使用中文撰写,技术术语保持英文原文。
1015
1015
  14. **智能分类** — LLM 根据内容自动判断页面归属哪个分类目录,无需用户指定。