teamai-cli 0.20.0-beta.6 → 0.20.0-beta.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
@@ -444,8 +444,8 @@ var init_types = __esm({
444
444
  tcodex: { skills: ".tcodex/skills", rules: ".tcodex/rules", settings: ".tcodex/hooks.json", agents: ".tcodex/agents" },
445
445
  cursor: { skills: ".cursor/skills", rules: ".cursor/rules", settings: ".cursor/hooks.json", agents: ".cursor/agents", mcp: ".cursor/mcp.json", mcpProject: ".cursor/mcp.json" },
446
446
  codebuddy: { skills: ".codebuddy/skills", rules: ".codebuddy/rules", settings: ".codebuddy/settings.json", claudemd: ".codebuddy/CODEBUDDY.md", agents: ".codebuddy/agents", mcp: ".codebuddy/mcp.json", mcpProject: ".codebuddy/mcp.json" },
447
- openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules" },
448
- hermes: { skills: ".hermes/skills" },
447
+ openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules", claudemd: ".openclaw/workspace/AGENTS.md" },
448
+ hermes: { skills: ".hermes/skills", claudemd: "AGENTS.md" },
449
449
  workbuddy: { skills: ".workbuddy/skills", rules: ".workbuddy/rules", settings: ".workbuddy/settings.json", claudemd: "AGENTS.md", mcp: ".workbuddy/mcp.json", mcpProject: ".workbuddy/mcp.json" }
450
450
  })
451
451
  });
@@ -1349,15 +1349,12 @@ function getTGitToken() {
1349
1349
  "No TGit credentials found. Set the TGIT_TOKEN environment variable (a git.woa.com Personal Access Token) or run `gf auth login`."
1350
1350
  );
1351
1351
  }
1352
- function tgitGitUser(scheme) {
1353
- return scheme === "private-token" ? "private" : "oauth2";
1354
- }
1355
- function tryGetTGitToken() {
1356
- try {
1357
- return getTGitToken();
1358
- } catch {
1352
+ function tgitGitCloneUrl(httpsUrl) {
1353
+ const oauthToken = gfGetOAuthToken();
1354
+ if (!oauthToken) {
1359
1355
  return null;
1360
1356
  }
1357
+ return httpsUrl.replace(/^https:\/\//, `https://oauth2:${oauthToken}@`);
1361
1358
  }
1362
1359
  function tgitAuthHeaders(token, scheme) {
1363
1360
  if (scheme === "bearer") {
@@ -1579,10 +1576,8 @@ function gitOutputSaysRepoMissing(output) {
1579
1576
  return output.includes("not found") || output.includes("does not exist") || output.includes("Repository not found");
1580
1577
  }
1581
1578
  function gfRepoClone(repo, localPath) {
1582
- const creds = tryGetTGitToken();
1583
- if (creds) {
1584
- const user = tgitGitUser(creds.scheme);
1585
- const cloneUrl = `https://${user}:${creds.token}@git.woa.com/${repo}.git`;
1579
+ const cloneUrl = tgitGitCloneUrl(`https://git.woa.com/${repo}.git`);
1580
+ if (cloneUrl) {
1586
1581
  const result2 = spawnSync("git", ["clone", cloneUrl, localPath], {
1587
1582
  encoding: "utf-8",
1588
1583
  stdio: ["pipe", "pipe", "pipe"],
@@ -1593,7 +1588,7 @@ function gfRepoClone(repo, localPath) {
1593
1588
  throw new RepoNotFoundError2(repo);
1594
1589
  }
1595
1590
  if (result2.status !== 0) {
1596
- const sanitized = allOutput2.replace(/(oauth2|private):[^@]+@/g, "$1:***@");
1591
+ const sanitized = allOutput2.replace(/oauth2:[^@]+@/g, "oauth2:***@");
1597
1592
  throw new Error(`git clone failed: ${sanitized.trim()}`);
1598
1593
  }
1599
1594
  return;
@@ -2856,9 +2851,191 @@ var init_builtin_skills = __esm({
2856
2851
  }
2857
2852
  });
2858
2853
 
2854
+ // src/openclaw-hooks.ts
2855
+ var openclaw_hooks_exports = {};
2856
+ __export(openclaw_hooks_exports, {
2857
+ OPENCLAW_HOOK_DIR: () => OPENCLAW_HOOK_DIR,
2858
+ applyOpenClawAgentHook: () => applyOpenClawAgentHook,
2859
+ injectOpenClawHooks: () => injectOpenClawHooks,
2860
+ removeOpenClawAgentHook: () => removeOpenClawAgentHook,
2861
+ removeOpenClawHooks: () => removeOpenClawHooks,
2862
+ resolveOpenClawHooksDir: () => resolveOpenClawHooksDir,
2863
+ resolveOpenclawWorkspaceDir: () => resolveOpenclawWorkspaceDir
2864
+ });
2865
+ import path10 from "path";
2866
+ function resolveOpenClawHooksDir(tool) {
2867
+ if (tool === "openclaw" && process.env.OPENCLAW_STATE_DIR) {
2868
+ return path10.join(process.env.OPENCLAW_STATE_DIR, "hooks");
2869
+ }
2870
+ const home = process.env.HOME ?? "";
2871
+ return path10.join(home, `.${tool}`, "hooks");
2872
+ }
2873
+ function buildHookMd(tool) {
2874
+ const events = Object.keys(EVENT_MAP);
2875
+ const metadata = JSON.stringify({ openclaw: { events } });
2876
+ return [
2877
+ "---",
2878
+ `name: ${TEAMAI_MARKER} status-report`,
2879
+ `metadata:`,
2880
+ ` ${metadata}`,
2881
+ `handler: ./handler.ts`,
2882
+ "---",
2883
+ "",
2884
+ `${TEAMAI_MARKER} Reports agent status to the team backend (report/sync/ack) for tool \`${tool}\`.`,
2885
+ "Managed by teamai \u2014 do not edit by hand.",
2886
+ ""
2887
+ ].join("\n");
2888
+ }
2889
+ function buildHandlerTs(tool) {
2890
+ const mapLiteral = JSON.stringify(EVENT_MAP);
2891
+ return `// ${TEAMAI_MARKER} status-report handler \u2014 generated by teamai, do not edit.
2892
+ import { spawn } from 'node:child_process';
2893
+
2894
+ const EVENT_MAP: Record<string, string> = ${mapLiteral};
2895
+ const TOOL = ${JSON.stringify(tool)};
2896
+
2897
+ export default async function handler(ctx: { event?: string } = {}): Promise<void> {
2898
+ const dispatchEvent = ctx.event ? EVENT_MAP[ctx.event] : undefined;
2899
+ if (!dispatchEvent) return;
2900
+ try {
2901
+ const child = spawn('teamai', ['hook-dispatch', dispatchEvent, '--tool', TOOL], {
2902
+ stdio: ['inherit', 'ignore', 'ignore'],
2903
+ });
2904
+ child.on('error', () => {});
2905
+ } catch {
2906
+ // never block the agent
2907
+ }
2908
+ }
2909
+ `;
2910
+ }
2911
+ async function injectOpenClawHooks(hooksDir, tool = "openclaw") {
2912
+ const effectiveHooksDir = resolveOpenClawHooksDir(tool);
2913
+ const dir = path10.join(effectiveHooksDir, OPENCLAW_HOOK_DIR);
2914
+ await ensureDir(dir);
2915
+ await writeFile(path10.join(dir, "HOOK.md"), buildHookMd(tool));
2916
+ await writeFile(path10.join(dir, "handler.ts"), buildHandlerTs(tool));
2917
+ log.success(`Injected teamai OpenClaw hook into ${dir}`);
2918
+ }
2919
+ async function removeOpenClawHooks(hooksDir) {
2920
+ const dir = path10.join(hooksDir, OPENCLAW_HOOK_DIR);
2921
+ if (await pathExists(dir)) {
2922
+ await remove(dir);
2923
+ log.success(`Removed teamai OpenClaw hook from ${dir}`);
2924
+ }
2925
+ if (process.env.OPENCLAW_STATE_DIR) {
2926
+ const altDir = path10.join(process.env.OPENCLAW_STATE_DIR, "hooks", OPENCLAW_HOOK_DIR);
2927
+ if (altDir !== dir && await pathExists(altDir)) {
2928
+ await remove(altDir);
2929
+ log.success(`Removed teamai OpenClaw hook from ${altDir}`);
2930
+ }
2931
+ }
2932
+ }
2933
+ function buildAgentHookMd(slug, openclawEvent) {
2934
+ const metadata = JSON.stringify({ openclaw: { events: [openclawEvent] } });
2935
+ return [
2936
+ "---",
2937
+ `name: ${TEAMAI_MARKER} ${slug}`,
2938
+ `metadata:`,
2939
+ ` ${metadata}`,
2940
+ `handler: ./handler.ts`,
2941
+ "---",
2942
+ "",
2943
+ `${TEAMAI_MARKER} Agent hook [${slug}] \u2014 managed by teamai, do not edit by hand.`,
2944
+ ""
2945
+ ].join("\n");
2946
+ }
2947
+ function buildAgentHandlerTs(command, timeout) {
2948
+ const timeoutMs = timeout * 1e3;
2949
+ return [
2950
+ `// ${TEAMAI_MARKER} agent hook handler \u2014 generated by teamai, do not edit.`,
2951
+ `import { spawn } from 'node:child_process';`,
2952
+ "",
2953
+ `export default async function handler(): Promise<void> {`,
2954
+ " try {",
2955
+ ` const child = spawn('sh', ['-c', ${JSON.stringify(command)}], {`,
2956
+ ` stdio: ['inherit', 'ignore', 'ignore'],`,
2957
+ ` timeout: ${timeoutMs},`,
2958
+ " });",
2959
+ " child.on('error', () => {});",
2960
+ " } catch {",
2961
+ " // never block the agent",
2962
+ " }",
2963
+ "}",
2964
+ ""
2965
+ ].join("\n");
2966
+ }
2967
+ async function applyOpenClawAgentHook(def) {
2968
+ const openclawEvent = CLAUDE_TO_OPENCLAW_EVENTS[def.event];
2969
+ if (!openclawEvent) {
2970
+ log.warn(`OpenClaw does not support event "${def.event}" \u2014 skipping hook [${def.slug}]`);
2971
+ return;
2972
+ }
2973
+ const tool = def.tool ?? "openclaw";
2974
+ const hooksDir = resolveOpenClawHooksDir(tool);
2975
+ const dir = path10.join(hooksDir, def.slug);
2976
+ await ensureDir(dir);
2977
+ await writeFile(path10.join(dir, "HOOK.md"), buildAgentHookMd(def.slug, openclawEvent));
2978
+ await writeFile(path10.join(dir, "handler.ts"), buildAgentHandlerTs(def.command, def.timeout ?? 10));
2979
+ log.success(`Installed OpenClaw agent hook [${def.slug}] in ${dir}`);
2980
+ }
2981
+ async function removeOpenClawAgentHook(opts) {
2982
+ const tool = opts.tool ?? "openclaw";
2983
+ const hooksDir = resolveOpenClawHooksDir(tool);
2984
+ const dir = path10.join(hooksDir, opts.slug);
2985
+ if (await pathExists(dir)) {
2986
+ await remove(dir);
2987
+ log.success(`Removed OpenClaw agent hook [${opts.slug}] from ${dir}`);
2988
+ }
2989
+ }
2990
+ async function resolveOpenclawWorkspaceDir(workspacePath) {
2991
+ const candidates = [];
2992
+ if (workspacePath) candidates.push(workspacePath);
2993
+ const stateDir = process.env.OPENCLAW_STATE_DIR;
2994
+ if (stateDir && path10.isAbsolute(stateDir)) {
2995
+ const cfgRaw = await readFileSafe(path10.join(stateDir, "openclaw.json"));
2996
+ if (cfgRaw) {
2997
+ try {
2998
+ const cfg = JSON.parse(cfgRaw);
2999
+ const ws = cfg?.agents?.defaults?.workspace;
3000
+ if (typeof ws === "string" && ws) candidates.push(ws);
3001
+ } catch (e) {
3002
+ log.debug(`openclaw: failed to parse openclaw.json: ${e.message}`);
3003
+ }
3004
+ }
3005
+ }
3006
+ const home = process.env.HOME;
3007
+ if (home) candidates.push(path10.join(home, ".openclaw", "workspace"));
3008
+ for (const candidate of candidates) {
3009
+ if (await pathExists(candidate)) {
3010
+ log.debug(`openclaw: resolved workspace dir to ${candidate}`);
3011
+ return candidate;
3012
+ }
3013
+ }
3014
+ log.warn(`openclaw: no workspace dir found (tried: ${candidates.join(", ") || "none"})`);
3015
+ return null;
3016
+ }
3017
+ var OPENCLAW_HOOK_DIR, TEAMAI_MARKER, EVENT_MAP, CLAUDE_TO_OPENCLAW_EVENTS;
3018
+ var init_openclaw_hooks = __esm({
3019
+ "src/openclaw-hooks.ts"() {
3020
+ "use strict";
3021
+ init_fs();
3022
+ init_logger();
3023
+ OPENCLAW_HOOK_DIR = "teamai-status-report";
3024
+ TEAMAI_MARKER = "[teamai]";
3025
+ EVENT_MAP = {
3026
+ "session:start": "session-start",
3027
+ "command:new": "prompt-submit"
3028
+ };
3029
+ CLAUDE_TO_OPENCLAW_EVENTS = {
3030
+ SessionStart: "session:start",
3031
+ UserPromptSubmit: "command:new"
3032
+ };
3033
+ }
3034
+ });
3035
+
2859
3036
  // src/builtin-hooks.ts
2860
3037
  import fs5 from "fs";
2861
- import path10 from "path";
3038
+ import path11 from "path";
2862
3039
  import { fileURLToPath as fileURLToPath2 } from "url";
2863
3040
  function hasShell() {
2864
3041
  if (_hasShellCache === void 0) {
@@ -2886,12 +3063,12 @@ function pickLatestVersion(versions) {
2886
3063
  }
2887
3064
  function resolveWorkbuddyNode() {
2888
3065
  const home = process.env.HOME ?? "";
2889
- const versionsDir = path10.join(home, WORKBUDDY_BUNDLED_NODE_DIR);
3066
+ const versionsDir = path11.join(home, WORKBUDDY_BUNDLED_NODE_DIR);
2890
3067
  try {
2891
3068
  const versions = fs5.readdirSync(versionsDir).filter((d) => !d.startsWith("."));
2892
3069
  const latest = pickLatestVersion(versions);
2893
3070
  if (!latest) return null;
2894
- const nodeBin = path10.join(versionsDir, latest, "bin", "node");
3071
+ const nodeBin = path11.join(versionsDir, latest, "bin", "node");
2895
3072
  if (fs5.existsSync(nodeBin)) return nodeBin;
2896
3073
  } catch {
2897
3074
  }
@@ -2904,10 +3081,10 @@ function resolveCodebuddyNode() {
2904
3081
  for (const entry of entries) {
2905
3082
  if (!entry.startsWith(".codebuddy-server")) continue;
2906
3083
  try {
2907
- const binDir = path10.join(home, entry, "bin");
3084
+ const binDir = path11.join(home, entry, "bin");
2908
3085
  const stableDirs = fs5.readdirSync(binDir).filter((d) => d.startsWith("stable-"));
2909
3086
  for (const stable of stableDirs) {
2910
- const nodeBin = path10.join(binDir, stable, "node");
3087
+ const nodeBin = path11.join(binDir, stable, "node");
2911
3088
  if (fs5.existsSync(nodeBin)) return nodeBin;
2912
3089
  }
2913
3090
  } catch {
@@ -2920,8 +3097,8 @@ function resolveCodebuddyNode() {
2920
3097
  function resolveTeamaiEntryScript() {
2921
3098
  try {
2922
3099
  const thisFile = fileURLToPath2(import.meta.url);
2923
- const distDir = path10.dirname(thisFile);
2924
- const candidate = path10.join(distDir, "index.js");
3100
+ const distDir = path11.dirname(thisFile);
3101
+ const candidate = path11.join(distDir, "index.js");
2925
3102
  if (fs5.existsSync(candidate)) return candidate;
2926
3103
  } catch {
2927
3104
  }
@@ -2932,8 +3109,8 @@ function ensureTeamaiWrapper() {
2932
3109
  if (!entryScript) return null;
2933
3110
  const nodeBin = resolveWorkbuddyNode() ?? resolveCodebuddyNode() ?? process.argv[0];
2934
3111
  const home = process.env.HOME ?? "";
2935
- const binDir = path10.join(home, TEAMAI_BIN_DIR);
2936
- const wrapperPath = path10.join(binDir, WRAPPER_NAME);
3112
+ const binDir = path11.join(home, TEAMAI_BIN_DIR);
3113
+ const wrapperPath = path11.join(binDir, WRAPPER_NAME);
2937
3114
  const script = [
2938
3115
  "#!/bin/sh",
2939
3116
  `# Auto-generated by teamai \u2014 do not edit.`,
@@ -3007,11 +3184,11 @@ var init_builtin_hooks = __esm({
3007
3184
  });
3008
3185
 
3009
3186
  // src/resources/hooks.ts
3010
- import path11 from "path";
3187
+ import path12 from "path";
3011
3188
  import { z as z3 } from "zod";
3012
3189
  import YAML2 from "yaml";
3013
3190
  function teamHooksYamlPath(repoPath) {
3014
- return path11.join(repoPath, "hooks", "hooks.yaml");
3191
+ return path12.join(repoPath, "hooks", "hooks.yaml");
3015
3192
  }
3016
3193
  async function parseHooksYaml(repoPath) {
3017
3194
  const content = await readFileSafe(teamHooksYamlPath(repoPath));
@@ -3134,80 +3311,6 @@ var init_hooks = __esm({
3134
3311
  }
3135
3312
  });
3136
3313
 
3137
- // src/openclaw-hooks.ts
3138
- var openclaw_hooks_exports = {};
3139
- __export(openclaw_hooks_exports, {
3140
- OPENCLAW_HOOK_DIR: () => OPENCLAW_HOOK_DIR,
3141
- injectOpenClawHooks: () => injectOpenClawHooks,
3142
- removeOpenClawHooks: () => removeOpenClawHooks
3143
- });
3144
- import path12 from "path";
3145
- function buildHookMd(tool) {
3146
- const events = Object.keys(EVENT_MAP);
3147
- return [
3148
- "---",
3149
- `name: ${TEAMAI_MARKER} status-report`,
3150
- "events:",
3151
- ...events.map((e) => ` - ${e}`),
3152
- `handler: ./handler.ts`,
3153
- "---",
3154
- "",
3155
- `${TEAMAI_MARKER} Reports agent status to the team backend (report/sync/ack) for tool \`${tool}\`.`,
3156
- "Managed by teamai \u2014 do not edit by hand.",
3157
- ""
3158
- ].join("\n");
3159
- }
3160
- function buildHandlerTs(tool) {
3161
- const mapLiteral = JSON.stringify(EVENT_MAP);
3162
- return `// ${TEAMAI_MARKER} status-report handler \u2014 generated by teamai, do not edit.
3163
- import { spawn } from 'node:child_process';
3164
-
3165
- const EVENT_MAP: Record<string, string> = ${mapLiteral};
3166
- const TOOL = ${JSON.stringify(tool)};
3167
-
3168
- export default async function handler(ctx: { event?: string } = {}): Promise<void> {
3169
- const dispatchEvent = ctx.event ? EVENT_MAP[ctx.event] : undefined;
3170
- if (!dispatchEvent) return;
3171
- try {
3172
- const child = spawn('teamai', ['hook-dispatch', dispatchEvent, '--tool', TOOL], {
3173
- stdio: ['inherit', 'ignore', 'ignore'],
3174
- });
3175
- child.on('error', () => {});
3176
- } catch {
3177
- // never block the agent
3178
- }
3179
- }
3180
- `;
3181
- }
3182
- async function injectOpenClawHooks(hooksDir, tool = "openclaw") {
3183
- const dir = path12.join(hooksDir, OPENCLAW_HOOK_DIR);
3184
- await ensureDir(dir);
3185
- await writeFile(path12.join(dir, "HOOK.md"), buildHookMd(tool));
3186
- await writeFile(path12.join(dir, "handler.ts"), buildHandlerTs(tool));
3187
- log.success(`Injected teamai OpenClaw hook into ${dir}`);
3188
- }
3189
- async function removeOpenClawHooks(hooksDir) {
3190
- const dir = path12.join(hooksDir, OPENCLAW_HOOK_DIR);
3191
- if (await pathExists(dir)) {
3192
- await remove(dir);
3193
- log.success(`Removed teamai OpenClaw hook from ${dir}`);
3194
- }
3195
- }
3196
- var OPENCLAW_HOOK_DIR, TEAMAI_MARKER, EVENT_MAP;
3197
- var init_openclaw_hooks = __esm({
3198
- "src/openclaw-hooks.ts"() {
3199
- "use strict";
3200
- init_fs();
3201
- init_logger();
3202
- OPENCLAW_HOOK_DIR = "teamai-status-report";
3203
- TEAMAI_MARKER = "[teamai]";
3204
- EVENT_MAP = {
3205
- "session:start": "session-start",
3206
- "command:new": "prompt-submit"
3207
- };
3208
- }
3209
- });
3210
-
3211
3314
  // src/hermes-home.ts
3212
3315
  var hermes_home_exports = {};
3213
3316
  __export(hermes_home_exports, {
@@ -3477,6 +3580,7 @@ var hooks_exports = {};
3477
3580
  __export(hooks_exports, {
3478
3581
  AGENT_HOOK_EVENTS: () => AGENT_HOOK_EVENTS,
3479
3582
  CLAUDE_TO_CURSOR_EVENTS: () => CLAUDE_TO_CURSOR_EVENTS,
3583
+ OPENCLAW_TOOLS: () => OPENCLAW_TOOLS,
3480
3584
  TEAMAI_HOOK_SUBCOMMANDS: () => TEAMAI_HOOK_SUBCOMMANDS,
3481
3585
  TEAMAI_LEGACY_HOOK_SUBCOMMANDS: () => TEAMAI_LEGACY_HOOK_SUBCOMMANDS,
3482
3586
  agentHookDescription: () => agentHookDescription,
@@ -3684,7 +3788,7 @@ async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCo
3684
3788
  }
3685
3789
  }
3686
3790
  function isAgentHookSupportedTool(tool) {
3687
- return !CURSOR_TOOLS.has(tool) && !OPENCLAW_TOOLS.has(tool);
3791
+ return !CURSOR_TOOLS.has(tool);
3688
3792
  }
3689
3793
  function isAgentHookEvent(event) {
3690
3794
  return AGENT_HOOK_EVENTS.has(event);
@@ -4793,6 +4897,16 @@ async function detectWorkbuddyVersion() {
4793
4897
  }
4794
4898
  return "";
4795
4899
  }
4900
+ async function detectHermesVersion() {
4901
+ const raw = await execVersion("hermes");
4902
+ const match = raw.match(/^\(?(\d+(?:\.\d+)*)\)?/);
4903
+ return match?.[1] ?? "";
4904
+ }
4905
+ async function detectOpenclawVersion() {
4906
+ const raw = await execVersion("openclaw");
4907
+ const match = raw.match(/^([\d.]+)/);
4908
+ return match?.[1] ?? "";
4909
+ }
4796
4910
  async function getAgentVersion(agentType) {
4797
4911
  if (VERSION_CACHE.has(agentType)) return VERSION_CACHE.get(agentType);
4798
4912
  const detector = DETECTORS[agentType];
@@ -4825,7 +4939,9 @@ var init_agent_version = __esm({
4825
4939
  cursor: detectCursorVersion,
4826
4940
  codebuddy: detectCodebuddyCliVersion,
4827
4941
  "codebuddy-ide": detectCodebuddyIdeVersion,
4828
- workbuddy: detectWorkbuddyVersion
4942
+ workbuddy: detectWorkbuddyVersion,
4943
+ hermes: detectHermesVersion,
4944
+ openclaw: detectOpenclawVersion
4829
4945
  };
4830
4946
  }
4831
4947
  });
@@ -4834,24 +4950,29 @@ var init_agent_version = __esm({
4834
4950
  import crypto2 from "crypto";
4835
4951
  import fs8 from "fs";
4836
4952
  import { execFileSync } from "child_process";
4953
+ import os2 from "os";
4837
4954
  function getMachineId() {
4838
4955
  if (cachedMachineId !== null) return cachedMachineId;
4839
4956
  cachedMachineId = detectMachineId();
4840
4957
  return cachedMachineId;
4841
4958
  }
4842
4959
  function detectMachineId(platform = process.platform) {
4960
+ let id = "";
4843
4961
  try {
4844
4962
  switch (platform) {
4845
4963
  case "darwin":
4846
- return readDarwinMachineId();
4964
+ id = readDarwinMachineId();
4965
+ break;
4847
4966
  case "win32":
4848
- return readWindowsMachineId();
4967
+ id = readWindowsMachineId();
4968
+ break;
4849
4969
  default:
4850
- return readLinuxMachineId();
4970
+ id = readLinuxMachineId();
4971
+ break;
4851
4972
  }
4852
4973
  } catch {
4853
- return "";
4854
4974
  }
4975
+ return id || os2.hostname() || "";
4855
4976
  }
4856
4977
  function readDarwinMachineId() {
4857
4978
  const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
@@ -4896,7 +5017,7 @@ var init_machine_id = __esm({
4896
5017
  });
4897
5018
 
4898
5019
  // src/utils/path-safety.ts
4899
- import os2 from "os";
5020
+ import os3 from "os";
4900
5021
  import path19 from "path";
4901
5022
  import fs9 from "fs";
4902
5023
  function assertSafePath(target, allowedRoots) {
@@ -4912,7 +5033,7 @@ function assertSafePath(target, allowedRoots) {
4912
5033
  );
4913
5034
  }
4914
5035
  function resolveReal(p) {
4915
- const expanded = p.startsWith("~") ? path19.join(os2.homedir(), p.slice(1)) : p;
5036
+ const expanded = p.startsWith("~") ? path19.join(os3.homedir(), p.slice(1)) : p;
4916
5037
  const abs = path19.resolve(expanded);
4917
5038
  try {
4918
5039
  return fs9.realpathSync(abs);
@@ -4921,7 +5042,7 @@ function resolveReal(p) {
4921
5042
  }
4922
5043
  }
4923
5044
  function defaultAllowedRoots() {
4924
- return [process.cwd(), os2.homedir()];
5045
+ return [process.cwd(), os3.homedir()];
4925
5046
  }
4926
5047
  function assertSafeResourceName(name) {
4927
5048
  if (name.includes("\0")) {
@@ -5300,7 +5421,7 @@ __export(local_agent_exports, {
5300
5421
  writeTokenFile: () => writeTokenFile
5301
5422
  });
5302
5423
  import fs10 from "fs";
5303
- import os3 from "os";
5424
+ import os4 from "os";
5304
5425
  import path20 from "path";
5305
5426
  import readline3 from "readline";
5306
5427
  import { execFile as execFile2 } from "child_process";
@@ -5525,7 +5646,7 @@ function createResourceLocalConfig(config, scope, repoPath, workspacePath) {
5525
5646
  const projectScope = scope === "project";
5526
5647
  return {
5527
5648
  repo: { localPath: repoPath, remote: config.endpoint },
5528
- username: os3.userInfo().username,
5649
+ username: os4.userInfo().username,
5529
5650
  scope: projectScope ? "project" : "user",
5530
5651
  projectRoot: projectScope ? workspacePath : void 0,
5531
5652
  additionalRoles: []
@@ -5835,7 +5956,7 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
5835
5956
  async function ensureWorkspaceBinding(config, workspacePath, sessionId) {
5836
5957
  if (config.workspaceBindings[workspacePath]) return;
5837
5958
  const markerKey = sessionId || `ppid-${process.ppid}`;
5838
- const hintMarker = path20.join(os3.tmpdir(), `teamai-bind-session-${markerKey}`);
5959
+ const hintMarker = path20.join(os4.tmpdir(), `teamai-bind-session-${markerKey}`);
5839
5960
  if (fs10.existsSync(hintMarker)) return;
5840
5961
  try {
5841
5962
  fs10.writeFileSync(hintMarker, "");
@@ -5883,7 +6004,7 @@ function isBindPromptEnabled() {
5883
6004
  async function emitBindingHint(config, workspacePath, sessionId) {
5884
6005
  if (config.workspaceBindings[workspacePath]) return;
5885
6006
  const markerKey = sessionId || `ppid-${process.ppid}`;
5886
- const hintMarker = path20.join(os3.tmpdir(), `teamai-bind-hint-${markerKey}`);
6007
+ const hintMarker = path20.join(os4.tmpdir(), `teamai-bind-hint-${markerKey}`);
5887
6008
  if (fs10.existsSync(hintMarker)) return;
5888
6009
  try {
5889
6010
  fs10.writeFileSync(hintMarker, "");
@@ -6044,8 +6165,8 @@ async function buildReportPayload(config, context) {
6044
6165
  agent_type: normalizeAgentType(tool),
6045
6166
  agent_version: await getAgentVersion(tool),
6046
6167
  local_agent_id: resolveLocalAgentId(context),
6047
- host_name: os3.hostname(),
6048
- os: os3.platform(),
6168
+ host_name: os4.hostname(),
6169
+ os: os4.platform(),
6049
6170
  started_at: config.createdAt,
6050
6171
  last_status: context.status ?? "running",
6051
6172
  // Instance-level skills/rules are a phase-1 legacy concept. They are
@@ -6156,7 +6277,7 @@ function assertHttpUrl(rawUrl) {
6156
6277
  return parsed;
6157
6278
  }
6158
6279
  async function downloadResource(downloadUrl) {
6159
- const tmpDir = await fs10.promises.mkdtemp(path20.join(os3.tmpdir(), "teamai-local-agent-"));
6280
+ const tmpDir = await fs10.promises.mkdtemp(path20.join(os4.tmpdir(), "teamai-local-agent-"));
6160
6281
  const filePath = path20.join(tmpDir, "resource");
6161
6282
  let current = assertHttpUrl(downloadUrl);
6162
6283
  let response;
@@ -6326,7 +6447,7 @@ async function installDownloadedResource(input) {
6326
6447
  const dest = path20.join(repoPath, "claudemd", `${input.slug}.md`);
6327
6448
  await fse3.ensureDir(path20.dirname(dest));
6328
6449
  await fse3.copyFile(mdFile, dest);
6329
- await syncClaudemd(teamConfig, localConfig, repoPath);
6450
+ await syncClaudemd(teamConfig, localConfig, repoPath, input.workspacePath);
6330
6451
  }
6331
6452
  const version2 = commandVersion(input.command, input.kind);
6332
6453
  const manifest = await loadManifest();
@@ -6364,12 +6485,28 @@ async function uninstallResource(input) {
6364
6485
  await new RulesHandler().removeItem(input.slug, teamConfig, localConfig);
6365
6486
  } else {
6366
6487
  await remove(path20.join(repoPath, "claudemd", `${input.slug}.md`));
6367
- await syncClaudemd(teamConfig, localConfig, repoPath);
6488
+ await syncClaudemd(teamConfig, localConfig, repoPath, input.workspacePath);
6368
6489
  }
6369
6490
  delete scopeManifest[manifestKind(input.kind)][input.slug];
6370
6491
  await saveManifest(manifest);
6371
6492
  }
6372
- async function syncClaudemd(teamConfig, localConfig, repoPath) {
6493
+ async function resolveHermesUserBaseDir() {
6494
+ try {
6495
+ const envWs = process.env.TEAMAI_HERMES_WORKSPACE;
6496
+ if (envWs && path20.isAbsolute(envWs)) return envWs;
6497
+ const cfg = await readJson(getConfigPath2());
6498
+ const bindings = cfg?.workspaceBindings;
6499
+ if (bindings && typeof bindings === "object") {
6500
+ const entries = Object.entries(bindings).filter(([p, v]) => path20.isAbsolute(p) && v?.ideType === "hermes").sort((a, b) => (b[1].boundAt ?? "").localeCompare(a[1].boundAt ?? ""));
6501
+ for (const [p] of entries) {
6502
+ if (await pathExists(path20.join(p, ".hermes"))) return p;
6503
+ }
6504
+ }
6505
+ } catch {
6506
+ }
6507
+ return void 0;
6508
+ }
6509
+ async function syncClaudemd(teamConfig, localConfig, repoPath, workspacePath) {
6373
6510
  const claudemdDir = path20.join(repoPath, "claudemd");
6374
6511
  const files = await pathExists(claudemdDir) ? (await fse3.readdir(claudemdDir)).filter((file) => file.endsWith(".md")).sort() : [];
6375
6512
  const contents = [];
@@ -6378,24 +6515,48 @@ async function syncClaudemd(teamConfig, localConfig, repoPath) {
6378
6515
  if (content) contents.push(content);
6379
6516
  }
6380
6517
  const block = compileClaudemdBlock(contents);
6381
- const baseDir = localConfig.scope === "project" && localConfig.projectRoot ? localConfig.projectRoot : process.env.HOME ?? "";
6518
+ let syncedAny = false;
6519
+ const defaultBaseDir = localConfig.scope === "project" && localConfig.projectRoot ? localConfig.projectRoot : process.env.HOME ?? "";
6382
6520
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
6383
6521
  if (!toolPath.claudemd) continue;
6384
- if (!await ResourceHandler.isToolInstalled(toolPath.claudemd, baseDir)) continue;
6385
- const claudeMdPath = path20.join(baseDir, toolPath.claudemd);
6522
+ let baseDir = defaultBaseDir;
6523
+ let resolvedAbsPath = null;
6524
+ if (tool === "openclaw" && localConfig.scope !== "project") {
6525
+ const openclawWs = await resolveOpenclawWorkspaceDir(workspacePath);
6526
+ if (openclawWs) {
6527
+ resolvedAbsPath = path20.join(openclawWs, path20.basename(toolPath.claudemd));
6528
+ }
6529
+ } else if (tool === "hermes" && localConfig.scope !== "project") {
6530
+ const hermesBase = workspacePath ?? await resolveHermesUserBaseDir();
6531
+ if (hermesBase) {
6532
+ baseDir = hermesBase;
6533
+ log.debug(`local-agent: hermes user-scope baseDir resolved to ${baseDir}`);
6534
+ }
6535
+ }
6536
+ const toolInstalled = resolvedAbsPath ? await pathExists(resolvedAbsPath) : toolPath.claudemd.includes("/") ? await ResourceHandler.isToolInstalled(toolPath.claudemd, baseDir) : await pathExists(path20.join(baseDir, `.${tool}`));
6537
+ if (!toolInstalled) {
6538
+ log.debug(`Skipped CLAUDE.md sync for ${tool}: target not found`);
6539
+ continue;
6540
+ }
6541
+ const claudeMdPath = resolvedAbsPath ?? path20.join(baseDir, toolPath.claudemd);
6386
6542
  try {
6387
6543
  const { injectClaudeMdSection: injectClaudeMdSection2 } = await Promise.resolve().then(() => (init_claudemd(), claudemd_exports));
6388
6544
  if (block) {
6389
6545
  await injectClaudeMdSection2(claudeMdPath, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, block);
6390
6546
  log.debug(`local-agent: synced CLAUDE.md instructions to ${tool}`);
6547
+ syncedAny = true;
6391
6548
  } else {
6392
6549
  await removeClaudeMdSection(claudeMdPath, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END);
6393
6550
  log.debug(`local-agent: removed CLAUDE.md instructions from ${tool}`);
6551
+ syncedAny = true;
6394
6552
  }
6395
6553
  } catch (e) {
6396
6554
  log.warn(`Failed to sync CLAUDE.md instructions to ${tool}: ${e.message}`);
6397
6555
  }
6398
6556
  }
6557
+ if (files.length > 0 && !syncedAny) {
6558
+ throw new Error("CLAUDE.md sync landed on no tool: every configured target was skipped");
6559
+ }
6399
6560
  }
6400
6561
  async function removeClaudeMdSection(filePath, startMarker, endMarker) {
6401
6562
  const existing = await readFileSafe(filePath);
@@ -6521,6 +6682,9 @@ async function runHookRuleCommand(config, command, context) {
6521
6682
  if (rec.tool === "hermes") {
6522
6683
  const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6523
6684
  await removeHermesAgentHook2({ slug, event: rec.event, command: rec.command });
6685
+ } else if (OPENCLAW_TOOLS.has(rec.tool)) {
6686
+ const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6687
+ await removeOpenClawAgentHook2({ slug, tool: rec.tool });
6524
6688
  } else {
6525
6689
  const settingsPath = resolveToolSettingsPath(config, rec.tool);
6526
6690
  await removeAgentHook(settingsPath, rec.tool, { slug, command: rec.command });
@@ -6546,6 +6710,9 @@ async function runHookRuleCommand(config, command, context) {
6546
6710
  if (prior.tool === "hermes") {
6547
6711
  const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6548
6712
  await removeHermesAgentHook2({ slug, event: prior.event, command: prior.command });
6713
+ } else if (OPENCLAW_TOOLS.has(prior.tool)) {
6714
+ const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6715
+ await removeOpenClawAgentHook2({ slug, tool: prior.tool });
6549
6716
  } else {
6550
6717
  const priorPath = resolveToolSettingsPath(config, prior.tool);
6551
6718
  await removeAgentHook(priorPath, prior.tool, { slug, command: prior.command });
@@ -6557,6 +6724,9 @@ async function runHookRuleCommand(config, command, context) {
6557
6724
  if (tool === "hermes") {
6558
6725
  const { applyHermesAgentHook: applyHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6559
6726
  await applyHermesAgentHook2({ slug, event, command: cmd, matcher, timeout });
6727
+ } else if (OPENCLAW_TOOLS.has(tool)) {
6728
+ const { applyOpenClawAgentHook: applyOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6729
+ await applyOpenClawAgentHook2({ slug, event, command: cmd, tool, matcher, timeout });
6560
6730
  } else {
6561
6731
  const settingsPath = resolveToolSettingsPath(config, tool);
6562
6732
  await applyAgentHook(settingsPath, tool, { slug, event, command: cmd, matcher, timeout });
@@ -6781,6 +6951,9 @@ async function removeAllAgentHooks() {
6781
6951
  if (rec.tool === "hermes") {
6782
6952
  const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6783
6953
  await removeHermesAgentHook2({ slug, event: rec.event, command: rec.command });
6954
+ } else if (OPENCLAW_TOOLS.has(rec.tool)) {
6955
+ const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6956
+ await removeOpenClawAgentHook2({ slug, tool: rec.tool });
6784
6957
  } else {
6785
6958
  const settingsPath = resolveToolSettingsPath(config, rec.tool);
6786
6959
  await removeAgentHook(settingsPath, rec.tool, { slug, command: rec.command });
@@ -6854,6 +7027,7 @@ var init_local_agent = __esm({
6854
7027
  init_machine_id();
6855
7028
  init_builtin_rules();
6856
7029
  init_builtin_hooks();
7030
+ init_openclaw_hooks();
6857
7031
  init_path_safety();
6858
7032
  init_tool_names();
6859
7033
  init_http_log();
@@ -7442,6 +7616,7 @@ var init_skills = __esm({
7442
7616
  init_fs();
7443
7617
  init_logger();
7444
7618
  init_builtin_skills();
7619
+ init_openclaw_hooks();
7445
7620
  init_roles();
7446
7621
  CONTRIBUTORS_FILE = "CONTRIBUTORS";
7447
7622
  SKILL_MD = "SKILL.md";
@@ -7627,11 +7802,21 @@ var init_skills = __esm({
7627
7802
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
7628
7803
  if (isAgentDisabled(localConfig, tool)) continue;
7629
7804
  if (!toolPath.skills) continue;
7630
- if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
7631
- log.debug(`Skipping skill sync for ${tool}: tool not installed`);
7632
- continue;
7805
+ let dest;
7806
+ if (tool === "openclaw" && localConfig.scope !== "project") {
7807
+ const wsDir = await resolveOpenclawWorkspaceDir();
7808
+ if (!wsDir) {
7809
+ log.debug(`Skipping skill sync for openclaw: workspace dir not found`);
7810
+ continue;
7811
+ }
7812
+ dest = path22.join(wsDir, "skills", item.name);
7813
+ } else {
7814
+ if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
7815
+ log.debug(`Skipping skill sync for ${tool}: tool not installed`);
7816
+ continue;
7817
+ }
7818
+ dest = path22.join(baseDir, toolPath.skills, item.name);
7633
7819
  }
7634
- const dest = path22.join(baseDir, toolPath.skills, item.name);
7635
7820
  try {
7636
7821
  await copyDir(item.sourcePath, dest);
7637
7822
  await ensureSkillFrontmatter(dest, item.name);
@@ -7666,7 +7851,14 @@ var init_skills = __esm({
7666
7851
  await this.addTombstone(name, localConfig);
7667
7852
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
7668
7853
  if (!toolPath.skills) continue;
7669
- const skillDir = path22.join(baseDir, toolPath.skills, name);
7854
+ let skillDir;
7855
+ if (tool === "openclaw" && localConfig.scope !== "project") {
7856
+ const wsDir = await resolveOpenclawWorkspaceDir();
7857
+ if (!wsDir) continue;
7858
+ skillDir = path22.join(wsDir, "skills", name);
7859
+ } else {
7860
+ skillDir = path22.join(baseDir, toolPath.skills, name);
7861
+ }
7670
7862
  if (await pathExists(skillDir)) {
7671
7863
  await remove(skillDir);
7672
7864
  removed.push(skillDir);
@@ -9743,8 +9935,8 @@ async function pushCore(localConfig, teamConfig, options) {
9743
9935
  process.exitCode = 2;
9744
9936
  return;
9745
9937
  }
9746
- const os7 = await import("os");
9747
- const skillPath = options.skill.startsWith("~") ? path32.join(os7.homedir(), options.skill.slice(1)) : path32.resolve(options.skill);
9938
+ const os8 = await import("os");
9939
+ const skillPath = options.skill.startsWith("~") ? path32.join(os8.homedir(), options.skill.slice(1)) : path32.resolve(options.skill);
9748
9940
  let matchedItem;
9749
9941
  for (const item of allItems) {
9750
9942
  if (item.type !== "skills") continue;
@@ -9756,7 +9948,7 @@ async function pushCore(localConfig, teamConfig, options) {
9756
9948
  matchedItem = item;
9757
9949
  break;
9758
9950
  }
9759
- const skillInput = options.skill.replace(/^~/, os7.homedir());
9951
+ const skillInput = options.skill.replace(/^~/, os8.homedir());
9760
9952
  if (item.sourcePath.endsWith(skillInput) || item.sourcePath.includes(path32.sep + skillInput)) {
9761
9953
  matchedItem = item;
9762
9954
  break;
@@ -11909,13 +12101,10 @@ function tokenize(text) {
11909
12101
  if (t !== word) tokens.push(t);
11910
12102
  }
11911
12103
  }
11912
- const chars = [...word];
11913
- for (const ch of chars) {
11914
- if (/[一-鿿]/.test(ch)) {
11915
- cjkRun.push(ch);
11916
- } else {
11917
- flushCjkRun();
11918
- }
12104
+ if (word.length === 1 && /[一-鿿]/.test(word)) {
12105
+ cjkRun.push(word);
12106
+ } else {
12107
+ flushCjkRun();
11919
12108
  }
11920
12109
  }
11921
12110
  flushCjkRun();
@@ -11930,6 +12119,40 @@ function tokenCount(text) {
11930
12119
  }
11931
12120
  return count;
11932
12121
  }
12122
+ function wordSegments(text) {
12123
+ if (!text) return [];
12124
+ const input = text.length > MAX_TOKENIZE_CHARS ? text.slice(0, MAX_TOKENIZE_CHARS) : text;
12125
+ const words = [];
12126
+ let run = [];
12127
+ let lastWasCjkWord = false;
12128
+ const flushRun = () => {
12129
+ if (run.length > 0 && lastWasCjkWord) {
12130
+ words[words.length - 1] += run.shift();
12131
+ lastWasCjkWord = false;
12132
+ }
12133
+ if (run.length > 0) {
12134
+ words.push(run.join(""));
12135
+ lastWasCjkWord = false;
12136
+ }
12137
+ run = [];
12138
+ };
12139
+ for (const seg of sharedSegmenter.segment(input)) {
12140
+ if (!seg.isWordLike) {
12141
+ flushRun();
12142
+ lastWasCjkWord = false;
12143
+ continue;
12144
+ }
12145
+ if (seg.segment.length === 1 && /[一-鿿]/.test(seg.segment)) {
12146
+ run.push(seg.segment);
12147
+ continue;
12148
+ }
12149
+ flushRun();
12150
+ words.push(seg.segment);
12151
+ lastWasCjkWord = /^[一-鿿]+$/.test(seg.segment);
12152
+ }
12153
+ flushRun();
12154
+ return words;
12155
+ }
11933
12156
  var MAX_TOKENIZE_CHARS, sharedSegmenter;
11934
12157
  var init_tokenizer = __esm({
11935
12158
  "src/utils/tokenizer.ts"() {
@@ -12244,7 +12467,8 @@ __export(search_index_exports, {
12244
12467
  parseLearningDoc: () => parseLearningDoc,
12245
12468
  search: () => search,
12246
12469
  titleFromFilename: () => titleFromFilename,
12247
- tokenize: () => tokenize
12470
+ tokenize: () => tokenize,
12471
+ wordSegments: () => wordSegments
12248
12472
  });
12249
12473
  import path42 from "path";
12250
12474
  import matter3 from "gray-matter";
@@ -12548,6 +12772,8 @@ function search(query, index, limit = 5) {
12548
12772
  const docFreq = df[token] ?? 0;
12549
12773
  return Math.log((N + 1) / (docFreq + 1)) + 1;
12550
12774
  };
12775
+ const lengthNorm = Math.sqrt(queryTokens.length);
12776
+ const wordTokens = wordSegments(query).map((w) => ({ word: w, tokens: tokenize(w) }));
12551
12777
  const results = [];
12552
12778
  for (const entry of index.entries) {
12553
12779
  let score = 0;
@@ -12570,6 +12796,7 @@ function search(query, index, limit = 5) {
12570
12796
  }
12571
12797
  const isCodebaseDoc = entry.type === "docs" && (entry.path ?? entry.filename ?? "").includes("team-codebase");
12572
12798
  if (score > 0 && (hasTitleOrTagMatch || isCodebaseDoc)) {
12799
+ score /= lengthNorm;
12573
12800
  score += Math.min(entry.votes * 0.5, 5);
12574
12801
  const domainMultiplier = domainWeightRow[entry.domain ?? "neutral"];
12575
12802
  const typeMultiplier = TYPE_BONUS[entry.type];
@@ -12580,14 +12807,40 @@ function search(query, index, limit = 5) {
12580
12807
  if (entry.hotness !== void 0 && entry.hotness < 1) {
12581
12808
  score *= entry.hotness;
12582
12809
  }
12583
- results.push({ entry, score });
12810
+ if (!hasTitleOrTagMatch) {
12811
+ results.push({ entry, score });
12812
+ continue;
12813
+ }
12814
+ const matchedTerms = [];
12815
+ const missingTerms = [];
12816
+ for (const { word, tokens: wt } of wordTokens) {
12817
+ const hit = wt.some((t) => entryTokens.has(`title:${t}`) || entryTokens.has(`tag:${t}`));
12818
+ (hit ? matchedTerms : missingTerms).push(word);
12819
+ }
12820
+ results.push({ entry, score, matchedTerms, missingTerms });
12584
12821
  }
12585
12822
  }
12586
12823
  results.sort((a, b) => {
12587
12824
  if (b.score !== a.score) return b.score - a.score;
12588
12825
  return (b.entry.date || "").localeCompare(a.entry.date || "");
12589
12826
  });
12590
- return results.slice(0, limit);
12827
+ const seen = /* @__PURE__ */ new Set();
12828
+ const deduped = [];
12829
+ for (const r of results) {
12830
+ const key = [
12831
+ r.entry.type,
12832
+ r.entry.title,
12833
+ r.entry.date,
12834
+ r.entry.author,
12835
+ r.entry.tokens.length,
12836
+ r.score
12837
+ ].join(" ");
12838
+ if (seen.has(key)) continue;
12839
+ seen.add(key);
12840
+ deduped.push(r);
12841
+ if (deduped.length === limit) break;
12842
+ }
12843
+ return deduped;
12591
12844
  }
12592
12845
  var MAX_BODY_CHARS, MAX_DOC_BYTES, TECHNICAL_TAGS, OPS_TAGS, SUPPORT_TAGS, TECHNICAL_PATH_PATTERNS, OPS_PATH_PATTERNS, SUPPORT_PATH_PATTERNS, DOMAIN_WEIGHT, TYPE_BONUS, CODEBASE_INDEX_FILENAME, CODEBASE_FULL_FILENAME, CODEBASE_INDEX_WEIGHT_BOOST;
12593
12846
  var init_search_index = __esm({
@@ -13349,6 +13602,7 @@ var init_skill_health = __esm({
13349
13602
  // src/digest.ts
13350
13603
  var digest_exports = {};
13351
13604
  __export(digest_exports, {
13605
+ buildSkillChangePathspec: () => buildSkillChangePathspec,
13352
13606
  formatTokenCount: () => formatTokenCount,
13353
13607
  generateDigest: () => generateDigest,
13354
13608
  summarizeConversation: () => summarizeConversation,
@@ -13399,9 +13653,17 @@ function parseGitLogOutput(output) {
13399
13653
  if (current) commits.push(current);
13400
13654
  return commits;
13401
13655
  }
13402
- async function getRecentSkillChanges(repoPath) {
13656
+ function buildSkillChangePathspec(subdir = "") {
13657
+ const prefix = subdir ? `${subdir.replace(/\/+$/, "")}/` : "";
13658
+ const pathspec = `${prefix}skills/*/SKILL.md`;
13659
+ const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13660
+ const skillRe = new RegExp(`^${escaped}skills/([^/]+)/SKILL\\.md$`);
13661
+ return { pathspec, skillRe };
13662
+ }
13663
+ async function getRecentSkillChanges(repoPath, subdir = "") {
13403
13664
  const seen = /* @__PURE__ */ new Set();
13404
13665
  const changes = [];
13666
+ const { pathspec, skillRe } = buildSkillChangePathspec(subdir);
13405
13667
  try {
13406
13668
  const git = createGit2(repoPath);
13407
13669
  const rawOutput = await git.raw([
@@ -13411,13 +13673,13 @@ async function getRecentSkillChanges(repoPath) {
13411
13673
  "--name-only",
13412
13674
  "--pretty=format:%H|%an|%s",
13413
13675
  "--",
13414
- "skills/*/SKILL.md"
13676
+ pathspec
13415
13677
  ]);
13416
13678
  if (!rawOutput.trim()) return changes;
13417
13679
  const commits = parseGitLogOutput(rawOutput);
13418
13680
  for (const commit of commits) {
13419
13681
  for (const file of commit.files) {
13420
- const match = file.match(/^skills\/([^/]+)\/SKILL\.md$/);
13682
+ const match = file.match(skillRe);
13421
13683
  if (!match) continue;
13422
13684
  const skillName = match[1];
13423
13685
  if (seen.has(skillName)) continue;
@@ -13435,14 +13697,14 @@ async function getRecentSkillChanges(repoPath) {
13435
13697
  "--name-only",
13436
13698
  "--pretty=format:%H|%an|%s",
13437
13699
  "--",
13438
- "skills/*/SKILL.md"
13700
+ pathspec
13439
13701
  ]);
13440
13702
  const newSkills = /* @__PURE__ */ new Set();
13441
13703
  if (addedOutput.trim()) {
13442
13704
  const addedCommits = parseGitLogOutput(addedOutput);
13443
13705
  for (const commit of addedCommits) {
13444
13706
  for (const file of commit.files) {
13445
- const match = file.match(/^skills\/([^/]+)\/SKILL\.md$/);
13707
+ const match = file.match(skillRe);
13446
13708
  if (match) {
13447
13709
  newSkills.add(match[1]);
13448
13710
  }
@@ -13623,7 +13885,10 @@ async function generateDigest(options) {
13623
13885
  console.log(`\u{1F4CA} Knowledge base total: ${totalLearnings} learnings`);
13624
13886
  console.log("");
13625
13887
  }
13626
- const skillChanges = await getRecentSkillChanges(repoPath);
13888
+ const skillChanges = localConfig.repo.kind === "self" ? await getRecentSkillChanges(
13889
+ localConfig.repo.businessRepoRoot ?? path44.dirname(repoPath),
13890
+ ".teamai"
13891
+ ) : await getRecentSkillChanges(repoPath);
13627
13892
  const newSkills = skillChanges.filter((c) => c.type === "new");
13628
13893
  const updatedSkills = skillChanges.filter((c) => c.type === "updated");
13629
13894
  if (newSkills.length > 0) {
@@ -15092,7 +15357,15 @@ async function pullForScope(localConfig, options, policy = {}) {
15092
15357
  const docsRepoDir = path48.join(localConfig.repo.localPath, "docs");
15093
15358
  const rulesRepoDir = path48.join(localConfig.repo.localPath, "rules");
15094
15359
  const skillsRepoDir = path48.join(localConfig.repo.localPath, "skills");
15095
- const votesDir = path48.join(localConfig.repo.localPath, "votes");
15360
+ let votesDir = path48.join(localConfig.repo.localPath, "votes");
15361
+ if (localConfig.repo.kind === "self") {
15362
+ try {
15363
+ const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
15364
+ votesDir = path48.join(await ensureReportsWorktree2(localConfig), "votes");
15365
+ } catch (e) {
15366
+ log.debug(`[self] reports worktree for votes unavailable: ${e.message}`);
15367
+ }
15368
+ }
15096
15369
  let learningsCount = 0;
15097
15370
  let effectiveLearningsDir;
15098
15371
  if (localConfig.scope === "user") {
@@ -15258,7 +15531,15 @@ async function pullForScope(localConfig, options, policy = {}) {
15258
15531
  const YAML20 = (await import("yaml")).default;
15259
15532
  const { listFiles: listFiles2, readFileSafe: readFileSafe5 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
15260
15533
  const { getRecommendations: getRecommendations2, displayRecommendations: displayRecommendations2 } = await Promise.resolve().then(() => (init_skill_recommend(), skill_recommend_exports));
15261
- const statsDir = path48.join(localConfig.repo.localPath, "stats");
15534
+ let statsDir = path48.join(localConfig.repo.localPath, "stats");
15535
+ if (localConfig.repo.kind === "self") {
15536
+ try {
15537
+ const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
15538
+ statsDir = path48.join(await ensureReportsWorktree2(localConfig), "stats");
15539
+ } catch (e) {
15540
+ log.debug(`[self] reports worktree for stats unavailable: ${e.message}`);
15541
+ }
15542
+ }
15262
15543
  const files = await listFiles2(statsDir);
15263
15544
  const teamStats = [];
15264
15545
  for (const file of files) {
@@ -17280,9 +17561,13 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
17280
17561
  res.hookFiles.push({ path: settingsPath, tool });
17281
17562
  }
17282
17563
  } else {
17283
- const hooksDir = path56.join(baseDir, `.${tool}`, "hooks");
17284
- if (await pathExists(path56.join(hooksDir, OPENCLAW_HOOK_DIR))) {
17285
- res.openclawHookDirs.push({ hooksDir, tool });
17564
+ const defaultHooksDir = path56.join(baseDir, `.${tool}`, "hooks");
17565
+ const resolvedHooksDir = resolveOpenClawHooksDir(tool);
17566
+ const dirsToCheck = /* @__PURE__ */ new Set([defaultHooksDir, resolvedHooksDir]);
17567
+ for (const hooksDir of dirsToCheck) {
17568
+ if (await pathExists(path56.join(hooksDir, OPENCLAW_HOOK_DIR))) {
17569
+ res.openclawHookDirs.push({ hooksDir, tool });
17570
+ }
17286
17571
  }
17287
17572
  }
17288
17573
  if (toolPath.claudemd) {
@@ -20784,15 +21069,31 @@ async function contributeSelf(localConfig, content, options) {
20784
21069
  await ensureDir(path69.join(wtRepo, "learnings"));
20785
21070
  await fs24.promises.writeFile(path69.join(wtRepo, relPath), content, "utf-8");
20786
21071
  try {
21072
+ const { pathExists: pathExists3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
20787
21073
  const wtLearnings = path69.join(wtRepo, "learnings");
20788
21074
  await fse11.copy(wtLearnings, LEARNINGS_LOCAL_DIR, {
20789
21075
  overwrite: true,
20790
21076
  filter: (src) => !path69.basename(src).startsWith(".")
20791
21077
  });
21078
+ const repoPath = localConfig.repo.localPath;
21079
+ const docsDir = path69.join(repoPath, "docs");
21080
+ const rulesDir = path69.join(repoPath, "rules");
21081
+ const skillsDir = path69.join(repoPath, "skills");
21082
+ let votesDir;
21083
+ try {
21084
+ const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
21085
+ const candidate = path69.join(await ensureReportsWorktree2(localConfig), "votes");
21086
+ if (await pathExists3(candidate)) votesDir = candidate;
21087
+ } catch {
21088
+ }
20792
21089
  const teamaiHome = getTeamaiHome(localConfig.scope, localConfig.projectRoot);
20793
21090
  const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
20794
21091
  await buildIndex2({
20795
- learningsDir: LEARNINGS_LOCAL_DIR,
21092
+ learningsDir: await pathExists3(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0,
21093
+ docsDir: await pathExists3(docsDir) ? docsDir : void 0,
21094
+ rulesDir: await pathExists3(rulesDir) ? rulesDir : void 0,
21095
+ skillsDir: await pathExists3(skillsDir) ? skillsDir : void 0,
21096
+ votesDir,
20796
21097
  indexPath: path69.join(teamaiHome, "search-index.json")
20797
21098
  });
20798
21099
  } catch (e) {
@@ -21568,7 +21869,7 @@ function formatResults(results) {
21568
21869
  lines.push(`--- [teamai:recall:start] --- (${results.length} result${results.length !== 1 ? "s" : ""})`);
21569
21870
  lines.push("");
21570
21871
  for (let i = 0; i < results.length; i++) {
21571
- const { entry, score, scope, learningsBase, sources } = results[i];
21872
+ const { entry, score, scope, learningsBase, sources, matchedTerms, missingTerms } = results[i];
21572
21873
  const voteStr = entry.votes > 0 ? ` \u2605${entry.votes}` : "";
21573
21874
  const scopeStr = scope ? ` [${scope}]` : "";
21574
21875
  const typeTag = entry.type ? `[${entry.type}] ` : "";
@@ -21577,6 +21878,10 @@ function formatResults(results) {
21577
21878
  if (entry.tags.length > 0) {
21578
21879
  lines.push(`Tags: ${entry.tags.join(", ")}`);
21579
21880
  }
21881
+ if (missingTerms && missingTerms.length > 0) {
21882
+ const matchedStr = matchedTerms && matchedTerms.length > 0 ? matchedTerms.join(", ") : "none";
21883
+ lines.push(`Matched: ${matchedStr} | Missing: ${missingTerms.join(", ")}`);
21884
+ }
21580
21885
  const filePath = entry.path ? entry.path : learningsBase ? `${learningsBase}/${entry.filename}` : `~/.teamai/learnings/${entry.filename}`;
21581
21886
  lines.push(`File: ${filePath}`);
21582
21887
  if (sources && sources.length > 0) {
@@ -21671,8 +21976,16 @@ async function recall(query, options) {
21671
21976
  const rounded = Math.round(score * 10) / 10;
21672
21977
  const verdict = isRelevantScore(score, isCodebaseHit, baseline) ? "RELEVANT" : "NOT_RELEVANT";
21673
21978
  let line = `${verdict} score=${rounded.toFixed(1)}`;
21979
+ const cutoff = isCodebaseHit ? CODEBASE_RELEVANCE_THRESHOLD : Math.max((baseline > 0 ? baseline : 1) * LEARNINGS_RELEVANCE_RATIO, LEARNINGS_ABSOLUTE_FLOOR);
21980
+ line += ` threshold=${(Math.round(cutoff * 10) / 10).toFixed(1)}`;
21674
21981
  if (verdict === "RELEVANT" && topResult) {
21675
21982
  line += ` title="${topResult.entry.title}"`;
21983
+ if (topResult.matchedTerms && topResult.matchedTerms.length > 0) {
21984
+ line += ` matched=${topResult.matchedTerms.join(",")}`;
21985
+ }
21986
+ if (topResult.missingTerms && topResult.missingTerms.length > 0) {
21987
+ line += ` missing=${topResult.missingTerms.join(",")}`;
21988
+ }
21676
21989
  if (topResult.sources && topResult.sources.length > 0) {
21677
21990
  const srcStr = topResult.sources.map((s) => s.desc ? `${s.path}(${s.desc})` : s.path).join(",");
21678
21991
  line += ` sources=${srcStr}`;
@@ -21964,10 +22277,10 @@ var init_recall_toggle = __esm({
21964
22277
 
21965
22278
  // src/utils/cache-index.ts
21966
22279
  import path75 from "path";
21967
- import os4 from "os";
22280
+ import os5 from "os";
21968
22281
  import fs25 from "fs-extra";
21969
22282
  function getCacheRoot() {
21970
- return process.env.TEAMAI_CACHE_DIR ?? path75.join(os4.homedir(), ".teamai", "cache", "repos");
22283
+ return process.env.TEAMAI_CACHE_DIR ?? path75.join(os5.homedir(), ".teamai", "cache", "repos");
21971
22284
  }
21972
22285
  function buildKey(provider, owner, repo) {
21973
22286
  return `${provider}/${owner}/${repo}`;
@@ -25913,6 +26226,23 @@ function parseJSON(raw) {
25913
26226
  return null;
25914
26227
  }
25915
26228
  }
26229
+ function resolveImportToModule(importerFile, importPath) {
26230
+ if (importPath.startsWith(".")) {
26231
+ const importerDir = path86.dirname(importerFile);
26232
+ const resolved = path86.normalize(path86.join(importerDir, importPath));
26233
+ const topLevel = resolved.split("/")[0];
26234
+ if (!topLevel || topLevel === ".." || topLevel === ".") return void 0;
26235
+ return topLevel;
26236
+ }
26237
+ if (importPath.includes(".") && !importPath.includes("/")) {
26238
+ return importPath.split(".")[0];
26239
+ }
26240
+ const parts = importPath.split("/");
26241
+ const first = parts[0];
26242
+ if (!first) return void 0;
26243
+ if (first.startsWith("@")) return void 0;
26244
+ return first;
26245
+ }
25916
26246
  async function enrichWithAI(ctx) {
25917
26247
  const moduleEntries = [...ctx.modules.entries()].filter(([, facts]) => facts.length >= 5);
25918
26248
  if (moduleEntries.length === 0) {
@@ -25976,9 +26306,9 @@ async function enrichWithAI(ctx) {
25976
26306
  const moduleImports = ctx.facts.filter((f) => f.kind === "relation" && f.file.startsWith(name + "/"));
25977
26307
  const targetModules = /* @__PURE__ */ new Set();
25978
26308
  for (const imp of moduleImports) {
25979
- const targetParts = imp.name.split("/");
25980
- if (targetParts[0] && targetParts[0] !== name) {
25981
- targetModules.add(targetParts[0]);
26309
+ const resolved = resolveImportToModule(imp.file, imp.name);
26310
+ if (resolved && resolved !== name) {
26311
+ targetModules.add(resolved);
25982
26312
  }
25983
26313
  }
25984
26314
  for (const target of targetModules) {
@@ -26465,7 +26795,19 @@ async function extractCodebase(opts) {
26465
26795
  let callChains;
26466
26796
  const depPathsFile = path87.join(evidenceDir, "dependency-paths.md");
26467
26797
  if (changedFiles) {
26468
- callChains = [];
26798
+ let reused = false;
26799
+ try {
26800
+ const existing = await readFile8(depPathsFile, "utf-8");
26801
+ if (existing.trim()) {
26802
+ reused = true;
26803
+ }
26804
+ } catch {
26805
+ }
26806
+ if (reused) {
26807
+ callChains = [];
26808
+ } else {
26809
+ callChains = traceCallChains(facts, files);
26810
+ }
26469
26811
  } else {
26470
26812
  callChains = traceCallChains(facts, files);
26471
26813
  }
@@ -26750,15 +27092,16 @@ async function shallowClone(url, localPath, provider, opts) {
26750
27092
  log.debug(`shallowClone: \u4F7F\u7528\u533F\u540D HTTPS \u514B\u9686 github \u4ED3\u5E93`);
26751
27093
  }
26752
27094
  } else if (provider === "tgit") {
26753
- const creds = tryGetTGitToken();
26754
- cloneUrl = url.replace(/^http:\/\//, "https://");
26755
- if (creds) {
26756
- cloneUrl = cloneUrl.replace("https://", `https://${tgitGitUser(creds.scheme)}:${creds.token}@`);
27095
+ const httpsUrl = url.replace(/^http:\/\//, "https://");
27096
+ const authed = tgitGitCloneUrl(httpsUrl);
27097
+ if (authed) {
27098
+ cloneUrl = authed;
26757
27099
  cloneMethod = "https-token";
26758
- log.debug(`shallowClone: \u4F7F\u7528 HTTPS+token \u514B\u9686 tgit \u4ED3\u5E93`);
27100
+ log.debug(`shallowClone: \u4F7F\u7528 HTTPS+OAuth token \u514B\u9686 tgit \u4ED3\u5E93`);
26759
27101
  } else {
27102
+ cloneUrl = httpsUrl;
26760
27103
  cloneMethod = "https-anonymous";
26761
- log.debug(`shallowClone: \u65E0 TGit token\uFF0C\u5C1D\u8BD5\u533F\u540D HTTPS \u514B\u9686`);
27104
+ log.debug(`shallowClone: \u65E0 TGit OAuth token\uFF0C\u5C1D\u8BD5\u533F\u540D HTTPS \u514B\u9686`);
26762
27105
  }
26763
27106
  } else {
26764
27107
  cloneUrl = url.replace(/^http:\/\//, "https://");
@@ -26826,10 +27169,10 @@ __export(repo_cache_exports, {
26826
27169
  writeLastSync: () => writeLastSync
26827
27170
  });
26828
27171
  import path88 from "path";
26829
- import os5 from "os";
27172
+ import os6 from "os";
26830
27173
  import fs31 from "fs-extra";
26831
27174
  function getCacheRoot2() {
26832
- return process.env.TEAMAI_CACHE_DIR ?? path88.join(os5.homedir(), ".teamai", "cache", "repos");
27175
+ return process.env.TEAMAI_CACHE_DIR ?? path88.join(os6.homedir(), ".teamai", "cache", "repos");
26833
27176
  }
26834
27177
  function getRepoCacheDir(provider, owner, repo) {
26835
27178
  return path88.join(getCacheRoot2(), provider, owner, repo);
@@ -28884,7 +29227,7 @@ __export(import_exports, {
28884
29227
  importCmd: () => importCmd
28885
29228
  });
28886
29229
  import path98 from "path";
28887
- import os6 from "os";
29230
+ import os7 from "os";
28888
29231
  import fs38 from "fs-extra";
28889
29232
  import { Listr, PRESET_TIMER } from "listr2";
28890
29233
  async function importCmd(opts) {
@@ -29078,7 +29421,7 @@ async function importCmd(opts) {
29078
29421
  log.success(`Local directory ${slug} import complete (dry-run)`);
29079
29422
  return;
29080
29423
  }
29081
- const tmpExtractDir = await fs38.mkdtemp(path98.join(os6.tmpdir(), "teamai-extract-"));
29424
+ const tmpExtractDir = await fs38.mkdtemp(path98.join(os7.tmpdir(), "teamai-extract-"));
29082
29425
  try {
29083
29426
  const { extractCodebase: extractCodebase2 } = await Promise.resolve().then(() => (init_codebase_extract(), codebase_extract_exports));
29084
29427
  await extractCodebase2({