teamai-cli 0.16.6 → 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.
Files changed (2) hide show
  1. package/dist/index.js +348 -110
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -128,11 +128,6 @@ function getStatePath(scope, projectRoot) {
128
128
  function getPushignorePath() {
129
129
  return path2.join(process.env.HOME ?? "", ".teamai", "pushignore");
130
130
  }
131
- function isWikiEnabled() {
132
- if (process.env.TEAMAI_WIKI_DISABLED === "1" || process.env.TEAMAI_WIKI_DISABLED === "true") return false;
133
- if (process.env.TEAMAI_WIKI_ENABLED === "0" || process.env.TEAMAI_WIKI_ENABLED === "false") return false;
134
- return true;
135
- }
136
131
  var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, StateSchema, TEAMAI_HOME, TEAMAI_CONFIG_PATH, TEAMAI_STATE_PATH, TEAMAI_TOKEN_PATH, TEAMAI_UPDATE_LOCK_PATH, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, SKILL_NAME_REGEX, TEAMAI_USAGE_PATH, TEAMAI_KNOWN_SKILLS_PATH, TEAMAI_PUSHIGNORE_PATH, TEAMAI_SESSIONS_DIR, UsageEventSchema, DASHBOARD_EVENTS_DIR, DASHBOARD_EVENTS_PATH, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_SCORE_CACHE_MS, CONTRIBUTE_SESSIONS_DIR, LEARNINGS_LOCAL_DIR, SEARCH_INDEX_PATH, VOTES_LOCAL_DIR, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema;
137
132
  var init_types = __esm({
138
133
  "src/types.ts"() {
@@ -812,119 +807,80 @@ var init_config = __esm({
812
807
  });
813
808
 
814
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
+ });
815
819
  import path6 from "path";
816
- function getTrackCommand(tool) {
817
- return `bash -lc "teamai track --stdin --tool ${tool} 2>/dev/null" || true`;
818
- }
819
- function getTrackSlashCommand(tool) {
820
- return `bash -lc "teamai track-slash --stdin --tool ${tool} 2>/dev/null" || true`;
821
- }
822
- function getDashboardReportCommand(tool) {
823
- return `bash -lc "teamai dashboard-report --stdin --tool ${tool} 2>/dev/null" || true`;
824
- }
825
- function getAutoRecallCommand(tool) {
826
- return `bash -lc "teamai auto-recall --stdin 2>/dev/null" || true`;
827
- }
828
- function getContributeCheckCommand(tool) {
829
- 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`;
830
823
  }
831
824
  function getClaudeHooks(tool) {
832
825
  return [
826
+ // ─── SessionStart: single dispatcher handles pull + dashboard-report ────
833
827
  {
834
828
  eventType: "SessionStart",
835
- descriptionKeyword: "Auto-pull",
829
+ descriptionKeyword: "Hook dispatch session-start",
836
830
  hook: {
837
831
  matcher: "*",
838
- hooks: [{ type: "command", command: TEAMAI_PULL_COMMAND }],
839
- 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`
840
834
  }
841
835
  },
836
+ // ─── Stop: single dispatcher handles update + contribute-check + dashboard-report ────
842
837
  {
843
838
  eventType: "Stop",
844
- descriptionKeyword: "Auto-update",
839
+ descriptionKeyword: "Hook dispatch stop",
845
840
  hook: {
846
841
  matcher: "*",
847
- // 10s timeout: npm registry call typically <5s; cap at 10s so a stalled
848
- // call cannot delay session shutdown by the default 60s.
849
- hooks: [{ type: "command", command: TEAMAI_UPDATE_COMMAND, timeout: 10 }],
850
- 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`
851
844
  }
852
845
  },
853
- // ─── Contribute check (smart threshold hint at session end) ────────
846
+ // ─── PostToolUse (*): dashboard-report ────
854
847
  {
855
- eventType: "Stop",
856
- descriptionKeyword: "Contribute check",
848
+ eventType: "PostToolUse",
849
+ descriptionKeyword: "Hook dispatch post-tool-use wildcard",
857
850
  hook: {
858
851
  matcher: "*",
859
- hooks: [{ type: "command", command: getContributeCheckCommand(tool) }],
860
- 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`
861
854
  }
862
855
  },
856
+ // ─── PostToolUse (Skill): track ────
863
857
  {
864
858
  eventType: "PostToolUse",
865
- descriptionKeyword: "Track skill",
859
+ descriptionKeyword: "Hook dispatch post-tool-use Skill",
866
860
  hook: {
867
861
  matcher: "Skill",
868
- hooks: [{ type: "command", command: getTrackCommand(tool) }],
869
- 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`
870
864
  }
871
865
  },
872
- {
873
- eventType: "UserPromptSubmit",
874
- descriptionKeyword: "Track slash",
875
- hook: {
876
- matcher: "*",
877
- hooks: [{ type: "command", command: getTrackSlashCommand(tool) }],
878
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Track slash command usage`
879
- }
880
- },
881
- // ─── Auto-recall (search knowledge base on search tools + Bash errors) ────────
882
- // Split into 4 precise matchers to avoid spawning a process for tools that
883
- // would immediately exit (auto-recall only handles Bash/Grep/WebSearch/WebFetch).
866
+ // ─── PostToolUse (Bash/Grep/WebSearch/WebFetch): auto-recall ────
884
867
  ...["Bash", "Grep", "WebSearch", "WebFetch"].map((matcher) => ({
885
868
  eventType: "PostToolUse",
886
- descriptionKeyword: `Auto-recall ${matcher}`,
869
+ descriptionKeyword: `Hook dispatch post-tool-use ${matcher}`,
887
870
  hook: {
888
871
  matcher,
889
- hooks: [{ type: "command", command: getAutoRecallCommand(tool) }],
890
- 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}`
891
874
  }
892
875
  })),
893
- // ─── Dashboard hooks (independent from tracking) ────────
894
- {
895
- eventType: "SessionStart",
896
- descriptionKeyword: "Dashboard report",
897
- hook: {
898
- matcher: "*",
899
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
900
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on session start`
901
- }
902
- },
903
- {
904
- eventType: "Stop",
905
- descriptionKeyword: "Dashboard stop",
906
- hook: {
907
- matcher: "*",
908
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
909
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on session stop`
910
- }
911
- },
912
- {
913
- eventType: "PostToolUse",
914
- descriptionKeyword: "Dashboard tool",
915
- hook: {
916
- matcher: "*",
917
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
918
- description: `${TEAMAI_HOOK_DESCRIPTION_PREFIX} Dashboard report on tool use`
919
- }
920
- },
876
+ // ─── UserPromptSubmit: track-slash + dashboard-report ────
921
877
  {
922
878
  eventType: "UserPromptSubmit",
923
- descriptionKeyword: "Dashboard prompt",
879
+ descriptionKeyword: "Hook dispatch prompt-submit",
924
880
  hook: {
925
881
  matcher: "*",
926
- hooks: [{ type: "command", command: getDashboardReportCommand(tool) }],
927
- 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`
928
884
  }
929
885
  }
930
886
  ];
@@ -932,26 +888,22 @@ function getClaudeHooks(tool) {
932
888
  function buildCursorHooks(tool) {
933
889
  return {
934
890
  sessionStart: [
935
- { command: TEAMAI_PULL_COMMAND, timeout: 30 },
936
- { command: getDashboardReportCommand(tool), timeout: 10 }
891
+ { command: getDispatchCommand("session-start", tool), timeout: 60 }
937
892
  ],
938
893
  stop: [
939
- { command: TEAMAI_UPDATE_COMMAND, timeout: 10 },
940
- { command: getDashboardReportCommand(tool), timeout: 10 },
941
- { command: getContributeCheckCommand(tool), timeout: 10 }
894
+ { command: getDispatchCommand("stop", tool), timeout: 15 }
942
895
  ],
943
896
  postToolUse: [
944
- { command: getTrackCommand(tool), timeout: 10, matcher: "Skill" },
945
- { 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" },
946
899
  ...["Bash", "Grep", "WebSearch", "WebFetch"].map((matcher) => ({
947
- command: getAutoRecallCommand(tool),
948
- timeout: 3,
900
+ command: getDispatchCommand("post-tool-use", tool, matcher),
901
+ timeout: 10,
949
902
  matcher
950
903
  }))
951
904
  ],
952
905
  beforeSubmitPrompt: [
953
- { command: getTrackSlashCommand(tool), timeout: 10 },
954
- { command: getDashboardReportCommand(tool), timeout: 10 }
906
+ { command: getDispatchCommand("prompt-submit", tool), timeout: 10 }
955
907
  ]
956
908
  };
957
909
  }
@@ -1068,6 +1020,22 @@ async function injectCursorHooks(hooksPath, tool) {
1068
1020
  }
1069
1021
  const desiredHooks = buildCursorHooks(tool);
1070
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
+ }
1071
1039
  const desiredEvents = new Set(Object.keys(desiredHooks));
1072
1040
  for (const event of Object.keys(hooksJson.hooks)) {
1073
1041
  if (desiredEvents.has(event)) continue;
@@ -1161,16 +1129,21 @@ async function injectHooksToAllTools(toolPaths, baseDir) {
1161
1129
  }
1162
1130
  }
1163
1131
  }
1164
- 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;
1165
1133
  var init_hooks = __esm({
1166
1134
  "src/hooks.ts"() {
1167
1135
  "use strict";
1168
1136
  init_fs();
1169
1137
  init_logger();
1170
1138
  init_types();
1171
- TEAMAI_PULL_COMMAND = 'bash -lc "teamai pull 2>/dev/null" || true';
1172
- TEAMAI_UPDATE_COMMAND = 'bash -lc "teamai update 2>/dev/null" || true';
1173
- 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
+ };
1174
1147
  CURSOR_TOOLS = /* @__PURE__ */ new Set(["cursor"]);
1175
1148
  TEAMAI_COMMAND_MARKERS = [
1176
1149
  "teamai pull",
@@ -2401,6 +2374,10 @@ async function init(options) {
2401
2374
  if (options.scope === "project" || options.scope === "user") {
2402
2375
  scope = options.scope;
2403
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}/`);
2404
2381
  const scopeAnswer = await askQuestion("Scope [user/project] (default: user): ", "user");
2405
2382
  if (scopeAnswer.toLowerCase() === "project") {
2406
2383
  scope = "project";
@@ -2935,7 +2912,7 @@ function getBuiltinSkillsDir() {
2935
2912
  const distDir = path12.dirname(new URL(import.meta.url).pathname);
2936
2913
  return path12.join(distDir, "..", "skills");
2937
2914
  }
2938
- async function deployBuiltinSkills(teamConfig, localConfig, options) {
2915
+ async function deployBuiltinSkills(teamConfig, localConfig) {
2939
2916
  const builtinDir = getBuiltinSkillsDir();
2940
2917
  if (!await pathExists(builtinDir)) {
2941
2918
  log.debug("No built-in skills directory found, skipping deployment");
@@ -2955,8 +2932,6 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
2955
2932
  }
2956
2933
  }
2957
2934
  if (skillNames.length === 0) return 0;
2958
- const filteredSkills = options?.skipWiki ? skillNames.filter((name) => name !== "teamai-wiki") : skillNames;
2959
- if (filteredSkills.length === 0) return 0;
2960
2935
  const baseDir = localConfig ? resolveBaseDir(localConfig) : process.env.HOME ?? "";
2961
2936
  let deployed = 0;
2962
2937
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
@@ -2966,7 +2941,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
2966
2941
  continue;
2967
2942
  }
2968
2943
  const targetSkillsDir = path12.join(baseDir, toolPath.skills);
2969
- for (const skillName of filteredSkills) {
2944
+ for (const skillName of skillNames) {
2970
2945
  const srcDir = path12.join(builtinDir, skillName);
2971
2946
  const destDir = path12.join(targetSkillsDir, skillName);
2972
2947
  try {
@@ -4709,7 +4684,7 @@ async function push(options) {
4709
4684
  log.debug(`Pre-push sync skipped: ${e.message}`);
4710
4685
  }
4711
4686
  const spin = spinner("Scanning local resources...").start();
4712
- const pushableTypes = isWikiEnabled() ? ["skills", "rules", "env", "wiki"] : ["skills", "rules", "env"];
4687
+ const pushableTypes = ["skills", "rules", "env", "wiki"];
4713
4688
  const allItems = [];
4714
4689
  for (const type of pushableTypes) {
4715
4690
  const handler = getHandler(type);
@@ -4987,7 +4962,6 @@ var init_push = __esm({
4987
4962
  init_logger();
4988
4963
  init_resources();
4989
4964
  init_skills();
4990
- init_types();
4991
4965
  init_roles();
4992
4966
  init_prompt();
4993
4967
  init_fs();
@@ -6023,8 +5997,7 @@ async function pullForScope(localConfig, options) {
6023
5997
  }
6024
5998
  const tagsConfig = await loadTagsConfig(localConfig.repo.localPath);
6025
5999
  const subscribedTags = localConfig.subscribedTags;
6026
- const wikiEnabled = isWikiEnabled();
6027
- const resourceTypes = wikiEnabled ? ["skills", "rules", "docs", "env", "wiki"] : ["skills", "rules", "docs", "env"];
6000
+ const resourceTypes = ["skills", "rules", "docs", "env", "wiki"];
6028
6001
  let totalSynced = 0;
6029
6002
  let desiredSkillNames = null;
6030
6003
  let knownRepoSkillNames = null;
@@ -6287,7 +6260,7 @@ async function pullForScope(localConfig, options) {
6287
6260
  if (!options.dryRun) {
6288
6261
  try {
6289
6262
  const { deployBuiltinSkills: deployBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
6290
- const deployed = await deployBuiltinSkills2(freshConfig, localConfig, { skipWiki: !wikiEnabled });
6263
+ const deployed = await deployBuiltinSkills2(freshConfig, localConfig);
6291
6264
  if (deployed > 0) {
6292
6265
  log.debug(`[${scopeLabel}] Deployed ${deployed} built-in skill(s)`);
6293
6266
  }
@@ -6423,7 +6396,27 @@ async function collectClaudemdFiles(repoPath, roleContext) {
6423
6396
  }
6424
6397
  return contents;
6425
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
+ }
6426
6415
  async function pull(options) {
6416
+ try {
6417
+ await autoMigrateHooksIfNeeded();
6418
+ } catch {
6419
+ }
6427
6420
  let userConfig = null;
6428
6421
  try {
6429
6422
  userConfig = await loadLocalConfigForScope("user");
@@ -6745,10 +6738,6 @@ async function status(options) {
6745
6738
  for (const [type, count] of Object.entries(counts)) {
6746
6739
  console.log(` ${type}: ${count}`);
6747
6740
  }
6748
- if (!isWikiEnabled()) {
6749
- console.log("");
6750
- log.info("\u2139 Wiki: disabled (TEAMAI_WIKI_DISABLED=1) \u2014 wiki routing handled by external plugin");
6751
- }
6752
6741
  console.log("");
6753
6742
  log.info("Local resources not yet pushed:");
6754
6743
  let anyNew = false;
@@ -6915,7 +6904,6 @@ var init_status = __esm({
6915
6904
  init_skills();
6916
6905
  init_known_agents();
6917
6906
  init_agent_skills();
6918
- init_types();
6919
6907
  }
6920
6908
  });
6921
6909
 
@@ -11081,6 +11069,252 @@ var init_auto_recall = __esm({
11081
11069
  }
11082
11070
  });
11083
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
+
11084
11318
  // src/index.ts
11085
11319
  init_logger();
11086
11320
  import { createRequire as createRequire2 } from "module";
@@ -11357,5 +11591,9 @@ program.command("auto-recall").description("Auto-recall team knowledge on tool e
11357
11591
  await autoRecall2();
11358
11592
  }
11359
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
+ });
11360
11598
  program.parse();
11361
11599
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.16.6",
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": {