chat-recall 0.5.8 → 0.5.9

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/cli.js CHANGED
@@ -7456,7 +7456,7 @@ async function archiveRawSession(store, sessionId) {
7456
7456
  }
7457
7457
  function detectForkPredecessor(sessionPath) {
7458
7458
  const { readFileSync: readFileSync49, readdirSync: readdirSync35 } = requireFs();
7459
- const { dirname: dirname26, join: join61, basename: basename29 } = requirePath();
7459
+ const { dirname: dirname27, join: join61, basename: basename29 } = requirePath();
7460
7460
  let text;
7461
7461
  try {
7462
7462
  text = readFileSync49(sessionPath, "utf-8");
@@ -7481,7 +7481,7 @@ function detectForkPredecessor(sessionPath) {
7481
7481
  return null;
7482
7482
  if (text.includes(`"uuid":"${headParent}"`))
7483
7483
  return null;
7484
- const dir = dirname26(sessionPath);
7484
+ const dir = dirname27(sessionPath);
7485
7485
  const self = basename29(sessionPath);
7486
7486
  let entries;
7487
7487
  try {
@@ -14608,10 +14608,10 @@ var init_types = __esm({
14608
14608
 
14609
14609
  // ../engine/dist/transcript/opencode.js
14610
14610
  async function parseOpenCodeSubagents(parentSessionId) {
14611
- const { existsSync: existsSync56 } = await import("fs");
14611
+ const { existsSync: existsSync57 } = await import("fs");
14612
14612
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14613
14613
  const dbPath = opencodeBackend2.dbPath();
14614
- if (!existsSync56(dbPath))
14614
+ if (!existsSync57(dbPath))
14615
14615
  return [];
14616
14616
  const realParent = parentSessionId.replace("opencode_", "");
14617
14617
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -14652,10 +14652,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
14652
14652
  }
14653
14653
  }
14654
14654
  async function parseOpenCodeTranscript(sessionId) {
14655
- const { existsSync: existsSync56 } = await import("fs");
14655
+ const { existsSync: existsSync57 } = await import("fs");
14656
14656
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14657
14657
  const dbPath = opencodeBackend2.dbPath();
14658
- if (!existsSync56(dbPath))
14658
+ if (!existsSync57(dbPath))
14659
14659
  return [];
14660
14660
  const realId = sessionId.replace("opencode_", "");
14661
14661
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -18052,10 +18052,55 @@ var init_diary_source = __esm({
18052
18052
  }
18053
18053
  });
18054
18054
 
18055
+ // ../engine/dist/core/redact-inline.js
18056
+ function redactInlineSecrets(value) {
18057
+ let redacted = false;
18058
+ let out = value;
18059
+ out = out.replace(/(\w+:\/\/[^:/@\s]+:)([^@\s]{1,512})(@)/g, (_m, head, _pw, tail) => {
18060
+ redacted = true;
18061
+ return `${head}__SECRET_NOT_SYNCED__${tail}`;
18062
+ });
18063
+ const tokenShapes = [
18064
+ /\bsk-[A-Za-z0-9_-]{16,}\b/g,
18065
+ // OpenAI-style
18066
+ /\bgh[pousr]_[A-Za-z0-9]{16,}\b/g,
18067
+ // GitHub
18068
+ /\bAIza[0-9A-Za-z_-]{20,}\b/g,
18069
+ // Google
18070
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g
18071
+ // Slack
18072
+ ];
18073
+ for (const re of tokenShapes) {
18074
+ out = out.replace(re, () => {
18075
+ redacted = true;
18076
+ return "__SECRET_NOT_SYNCED__";
18077
+ });
18078
+ }
18079
+ return { text: out, redacted };
18080
+ }
18081
+ var init_redact_inline = __esm({
18082
+ "../engine/dist/core/redact-inline.js"() {
18083
+ "use strict";
18084
+ }
18085
+ });
18086
+
18055
18087
  // ../engine/dist/parsers/skills-source.js
18056
18088
  import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
18057
18089
  import { homedir as homedir12 } from "os";
18058
18090
  import { join as join34 } from "path";
18091
+ function rebuildBody(content) {
18092
+ const { text, redacted } = redactInlineSecrets(content);
18093
+ const out = {};
18094
+ if (text.length > MAX_BODY_CHARS) {
18095
+ out.body = text.slice(0, MAX_BODY_CHARS);
18096
+ out.bodyTruncated = true;
18097
+ } else {
18098
+ out.body = text;
18099
+ }
18100
+ if (redacted)
18101
+ out.bodySecretsRedacted = true;
18102
+ return out;
18103
+ }
18059
18104
  function resolveRoots() {
18060
18105
  const home = homedir12();
18061
18106
  const all = [];
@@ -18099,7 +18144,7 @@ function parseFrontmatter3(text) {
18099
18144
  }
18100
18145
  return out;
18101
18146
  }
18102
- var MAX_CHUNK_CHARS12, SkillsSource;
18147
+ var MAX_CHUNK_CHARS12, MAX_BODY_CHARS, SkillsSource;
18103
18148
  var init_skills_source = __esm({
18104
18149
  "../engine/dist/parsers/skills-source.js"() {
18105
18150
  "use strict";
@@ -18108,7 +18153,9 @@ var init_skills_source = __esm({
18108
18153
  init_codex2();
18109
18154
  init_tool_paths();
18110
18155
  init_settings();
18156
+ init_redact_inline();
18111
18157
  MAX_CHUNK_CHARS12 = 2e3;
18158
+ MAX_BODY_CHARS = 512 * 1024;
18112
18159
  SkillsSource = class {
18113
18160
  sourceType = "skill";
18114
18161
  async *discover() {
@@ -18186,7 +18233,10 @@ var init_skills_source = __esm({
18186
18233
  skillName,
18187
18234
  description: fm.description || "",
18188
18235
  skillDir,
18189
- subdirs
18236
+ subdirs,
18237
+ // The whole file, so another machine can rebuild this skill. The
18238
+ // search chunk below stays capped at 2000 chars; this does not.
18239
+ ...rebuildBody(content)
18190
18240
  };
18191
18241
  if (root.readonly || pluginName !== void 0)
18192
18242
  extra.readonly = true;
@@ -18259,31 +18309,6 @@ function commandPreview(cfg) {
18259
18309
  }
18260
18310
  return "";
18261
18311
  }
18262
- function redactInlineSecrets(value) {
18263
- let redacted = false;
18264
- let out = value;
18265
- out = out.replace(/(\w+:\/\/[^:/@\s]+:)([^@\s]{1,512})(@)/g, (_m, head, _pw, tail) => {
18266
- redacted = true;
18267
- return `${head}__SECRET_NOT_SYNCED__${tail}`;
18268
- });
18269
- const tokenShapes = [
18270
- /\bsk-[A-Za-z0-9_-]{16,}\b/g,
18271
- // OpenAI-style
18272
- /\bgh[pousr]_[A-Za-z0-9]{16,}\b/g,
18273
- // GitHub
18274
- /\bAIza[0-9A-Za-z_-]{20,}\b/g,
18275
- // Google
18276
- /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g
18277
- // Slack
18278
- ];
18279
- for (const re of tokenShapes) {
18280
- out = out.replace(re, () => {
18281
- redacted = true;
18282
- return "__SECRET_NOT_SYNCED__";
18283
- });
18284
- }
18285
- return { text: out, redacted };
18286
- }
18287
18312
  function reconstructionSpec(cfg) {
18288
18313
  const out = {
18289
18314
  type: cfg.type || (cfg.url ? "remote" : "local"),
@@ -18320,6 +18345,7 @@ var init_mcps_source = __esm({
18320
18345
  init_codex2();
18321
18346
  init_settings();
18322
18347
  init_tool_paths();
18348
+ init_redact_inline();
18323
18349
  McpsSource = class {
18324
18350
  sourceType = "mcp";
18325
18351
  async *discover() {
@@ -18598,6 +18624,19 @@ var init_toolkit_format = __esm({
18598
18624
  // ../engine/dist/parsers/slash-commands-source.js
18599
18625
  import { existsSync as existsSync37, readdirSync as readdirSync28, readFileSync as readFileSync27, statSync as statSync33 } from "fs";
18600
18626
  import { join as join36, basename as basename20 } from "path";
18627
+ function rebuildBody2(content) {
18628
+ const { text, redacted } = redactInlineSecrets(content);
18629
+ const out = {};
18630
+ if (text.length > MAX_BODY_CHARS2) {
18631
+ out.body = text.slice(0, MAX_BODY_CHARS2);
18632
+ out.bodyTruncated = true;
18633
+ } else {
18634
+ out.body = text;
18635
+ }
18636
+ if (redacted)
18637
+ out.bodySecretsRedacted = true;
18638
+ return out;
18639
+ }
18601
18640
  function parseCommand(content, fallbackName, format) {
18602
18641
  if (format === "toml") {
18603
18642
  const t = parseScalarToml(content);
@@ -18614,7 +18653,7 @@ function parseCommand(content, fallbackName, format) {
18614
18653
  body: body.trim()
18615
18654
  };
18616
18655
  }
18617
- var MAX_CHUNK_CHARS13, SlashCommandsSource;
18656
+ var MAX_CHUNK_CHARS13, MAX_BODY_CHARS2, SlashCommandsSource;
18618
18657
  var init_slash_commands_source = __esm({
18619
18658
  "../engine/dist/parsers/slash-commands-source.js"() {
18620
18659
  "use strict";
@@ -18626,7 +18665,9 @@ var init_slash_commands_source = __esm({
18626
18665
  init_tool_paths();
18627
18666
  init_toolkit_format();
18628
18667
  init_project_dir_name();
18668
+ init_redact_inline();
18629
18669
  MAX_CHUNK_CHARS13 = 2e3;
18670
+ MAX_BODY_CHARS2 = 512 * 1024;
18630
18671
  SlashCommandsSource = class {
18631
18672
  sourceType = "command";
18632
18673
  roots() {
@@ -18690,7 +18731,10 @@ var init_slash_commands_source = __esm({
18690
18731
  commandName: parsed.name,
18691
18732
  description: parsed.description,
18692
18733
  scope: root.scope,
18693
- format: root.format
18734
+ format: root.format,
18735
+ // The whole file, so another machine can rebuild it. The search
18736
+ // chunk stays capped at 2000 chars; this does not.
18737
+ ...rebuildBody2(content)
18694
18738
  }
18695
18739
  };
18696
18740
  } catch {
@@ -18726,6 +18770,19 @@ var init_slash_commands_source = __esm({
18726
18770
  // ../engine/dist/parsers/subagents-source.js
18727
18771
  import { existsSync as existsSync38, readdirSync as readdirSync29, readFileSync as readFileSync28, statSync as statSync34 } from "fs";
18728
18772
  import { join as join37, basename as basename21 } from "path";
18773
+ function rebuildBody3(content) {
18774
+ const { text, redacted } = redactInlineSecrets(content);
18775
+ const out = {};
18776
+ if (text.length > MAX_BODY_CHARS3) {
18777
+ out.body = text.slice(0, MAX_BODY_CHARS3);
18778
+ out.bodyTruncated = true;
18779
+ } else {
18780
+ out.body = text;
18781
+ }
18782
+ if (redacted)
18783
+ out.bodySecretsRedacted = true;
18784
+ return out;
18785
+ }
18729
18786
  function parseAgent(content, fallbackName, format) {
18730
18787
  if (format === "toml") {
18731
18788
  const t = parseScalarToml(content);
@@ -18744,7 +18801,7 @@ function parseAgent(content, fallbackName, format) {
18744
18801
  body: body.trim()
18745
18802
  };
18746
18803
  }
18747
- var MAX_CHUNK_CHARS14, SubagentsSource;
18804
+ var MAX_CHUNK_CHARS14, MAX_BODY_CHARS3, SubagentsSource;
18748
18805
  var init_subagents_source = __esm({
18749
18806
  "../engine/dist/parsers/subagents-source.js"() {
18750
18807
  "use strict";
@@ -18756,7 +18813,9 @@ var init_subagents_source = __esm({
18756
18813
  init_tool_paths();
18757
18814
  init_toolkit_format();
18758
18815
  init_project_dir_name();
18816
+ init_redact_inline();
18759
18817
  MAX_CHUNK_CHARS14 = 2e3;
18818
+ MAX_BODY_CHARS3 = 512 * 1024;
18760
18819
  SubagentsSource = class {
18761
18820
  sourceType = "agent";
18762
18821
  roots() {
@@ -18821,7 +18880,10 @@ var init_subagents_source = __esm({
18821
18880
  description: parsed.description,
18822
18881
  tools: parsed.tools,
18823
18882
  scope: root.scope,
18824
- format: root.format
18883
+ format: root.format,
18884
+ // The whole file, so another machine can rebuild it. The search
18885
+ // chunk stays capped at 2000 chars; this does not.
18886
+ ...rebuildBody3(content)
18825
18887
  }
18826
18888
  };
18827
18889
  } catch {
@@ -19210,6 +19272,9 @@ var init_all_sources = __esm({
19210
19272
  });
19211
19273
 
19212
19274
  // ../engine/dist/core/extractor-version.js
19275
+ function extractorVersionForItem(id, sourceType) {
19276
+ return extractorVersionForTool(toolOfId(id)) + (ITEM_SOURCE_BUMP[sourceType] ?? 0);
19277
+ }
19213
19278
  function toolOfId(id) {
19214
19279
  if (id.startsWith("agy_"))
19215
19280
  return "agy";
@@ -19229,11 +19294,17 @@ function extractorVersionForTool(tool) {
19229
19294
  function extractorVersionForId(id) {
19230
19295
  return extractorVersionForTool(toolOfId(id));
19231
19296
  }
19232
- var BASE_EXTRACTOR_VERSION, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
19297
+ var BASE_EXTRACTOR_VERSION, ITEM_SOURCE_BUMP, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
19233
19298
  var init_extractor_version = __esm({
19234
19299
  "../engine/dist/core/extractor-version.js"() {
19235
19300
  "use strict";
19236
19301
  BASE_EXTRACTOR_VERSION = 2;
19302
+ ITEM_SOURCE_BUMP = {
19303
+ mcp: 1,
19304
+ skill: 1,
19305
+ agent: 1,
19306
+ command: 1
19307
+ };
19237
19308
  TOOL_EXTRACTOR_BUMP = {
19238
19309
  agy: 2,
19239
19310
  claude: 1,
@@ -19623,7 +19694,7 @@ var init_http = __esm({
19623
19694
  CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19624
19695
  } catch {
19625
19696
  try {
19626
- if (true) CLI_VERSION = "0.5.8";
19697
+ if (true) CLI_VERSION = "0.5.9";
19627
19698
  } catch {
19628
19699
  }
19629
19700
  }
@@ -20135,7 +20206,7 @@ var init_telemetry = __esm({
20135
20206
  init_sync_client();
20136
20207
  cliVersion2 = () => {
20137
20208
  try {
20138
- return true ? "0.5.8" : "dev";
20209
+ return true ? "0.5.9" : "dev";
20139
20210
  } catch {
20140
20211
  return "dev";
20141
20212
  }
@@ -21367,6 +21438,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21367
21438
  }
21368
21439
  const itemVersions = loadItemVersions(base2);
21369
21440
  const seenTools = /* @__PURE__ */ new Set();
21441
+ const seenItemKeys = /* @__PURE__ */ new Map();
21370
21442
  const registry = buildSourceRegistry();
21371
21443
  for (const sourceType of registry.getRegisteredTypes()) {
21372
21444
  if (sourceType === "session") continue;
@@ -21381,7 +21453,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21381
21453
  for await (const item of discovered) {
21382
21454
  const itemTool = toolOfId(item.id);
21383
21455
  seenTools.add(itemTool);
21384
- const versionStale = (itemVersions[itemTool] ?? EXTRACTOR_VERSION) < extractorVersionForId(item.id);
21456
+ const itemKey = `${itemTool}:${item.sourceType}`;
21457
+ seenItemKeys.set(itemKey, extractorVersionForItem(item.id, item.sourceType));
21458
+ const versionStale = (itemVersions[itemKey] ?? EXTRACTOR_VERSION) < extractorVersionForItem(item.id, item.sourceType);
21385
21459
  if ((item.mtime || 0) < (opts.sinceMs ?? 0) && !versionStale) continue;
21386
21460
  if (excluded(item.projectPath || "")) continue;
21387
21461
  if (!includedProject(item.projectPath || "")) continue;
@@ -21464,6 +21538,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21464
21538
  try {
21465
21539
  const rec = {};
21466
21540
  for (const t of seenTools) rec[t] = extractorVersionForTool(t);
21541
+ for (const [k, v] of seenItemKeys) rec[k] = v;
21467
21542
  saveItemVersions(base2, rec);
21468
21543
  } catch {
21469
21544
  }
@@ -21903,7 +21978,7 @@ var init_sync_client = __esm({
21903
21978
  ownVersion = JSON.parse(readFileSync36(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
21904
21979
  } catch {
21905
21980
  try {
21906
- if (true) ownVersion = "0.5.8";
21981
+ if (true) ownVersion = "0.5.9";
21907
21982
  } catch {
21908
21983
  }
21909
21984
  }
@@ -22719,27 +22794,31 @@ var init_install_skills = __esm({
22719
22794
  import { readFileSync as readFileSync41 } from "fs";
22720
22795
  import { join as join50 } from "path";
22721
22796
  import { homedir as homedir17 } from "os";
22722
- function readCommand(filePath, format) {
22723
- const content = readFileSync41(filePath, "utf-8");
22797
+ function parseCommandText(content, format, fallbackName) {
22724
22798
  if (format === "toml") {
22725
22799
  const t = parseScalarToml(content);
22726
- return { name: t.name || base(filePath), description: t.description || "", body: t.prompt || "" };
22800
+ return { name: t.name || fallbackName, description: t.description || "", body: t.prompt || "" };
22727
22801
  }
22728
22802
  const { fm, body } = parseFrontmatter4(content);
22729
- return { name: fm.name || base(filePath), description: fm.description || "", body: body.trim() };
22803
+ return { name: fm.name || fallbackName, description: fm.description || "", body: body.trim() };
22730
22804
  }
22731
- function readAgent(filePath, format) {
22732
- const content = readFileSync41(filePath, "utf-8");
22805
+ function parseAgentText(content, format, fallbackName) {
22733
22806
  if (format === "toml") {
22734
22807
  const t = parseScalarToml(content);
22735
22808
  return {
22736
- name: t.name || base(filePath),
22809
+ name: t.name || fallbackName,
22737
22810
  description: t.description || "",
22738
22811
  body: t.developer_instructions || t.instructions || ""
22739
22812
  };
22740
22813
  }
22741
22814
  const { fm, body } = parseFrontmatter4(content);
22742
- return { name: fm.name || base(filePath), description: fm.description || "", tools: fm.tools, body: body.trim() };
22815
+ return { name: fm.name || fallbackName, description: fm.description || "", tools: fm.tools, body: body.trim() };
22816
+ }
22817
+ function readCommand(filePath, format) {
22818
+ return parseCommandText(readFileSync41(filePath, "utf-8"), format, base(filePath));
22819
+ }
22820
+ function readAgent(filePath, format) {
22821
+ return parseAgentText(readFileSync41(filePath, "utf-8"), format, base(filePath));
22743
22822
  }
22744
22823
  function readInstructions(filePath, name) {
22745
22824
  const content = readFileSync41(filePath, "utf-8");
@@ -23332,8 +23411,8 @@ __export(toolkit_pull_exports, {
23332
23411
  planPull: () => planPull,
23333
23412
  portableCommand: () => portableCommand
23334
23413
  });
23335
- import { statSync as statSync42 } from "node:fs";
23336
- import { delimiter as delimiter3, join as join52, basename as basename25, isAbsolute as isAbsolute3 } from "node:path";
23414
+ import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as statSync42, writeFileSync as writeFileSync15 } from "node:fs";
23415
+ import { delimiter as delimiter3, dirname as dirname21, join as join52, basename as basename25, isAbsolute as isAbsolute3 } from "node:path";
23337
23416
  function isExecutableFile(p) {
23338
23417
  try {
23339
23418
  return statSync42(p).isFile();
@@ -23393,8 +23472,58 @@ function planPull(rows, opts = {}) {
23393
23472
  const wanted = opts.types;
23394
23473
  const seenUnsupported = /* @__PURE__ */ new Map();
23395
23474
  const byName = /* @__PURE__ */ new Map();
23475
+ const skillsByName = /* @__PURE__ */ new Map();
23476
+ const agentsByName = /* @__PURE__ */ new Map();
23477
+ const commandsByName = /* @__PURE__ */ new Map();
23396
23478
  for (const row of rows) {
23397
23479
  const type = row.source_type;
23480
+ if (type === "skill") {
23481
+ if (wanted && !wanted.includes("skill"))
23482
+ continue;
23483
+ const extra2 = parseExtra(row);
23484
+ const name2 = extra2.skillName || row.title;
23485
+ const body = extra2.body;
23486
+ if (!name2 || typeof body !== "string" || !body.trim()) {
23487
+ seenUnsupported.set("skill", (seenUnsupported.get("skill") || 0) + 1);
23488
+ continue;
23489
+ }
23490
+ const tool = String(extra2.tool || "");
23491
+ const rank = SOURCE_PRECEDENCE.skill.indexOf(tool);
23492
+ const prev2 = skillsByName.get(name2);
23493
+ if (!prev2 || rank >= 0 && (prev2.rank < 0 || rank < prev2.rank)) {
23494
+ skillsByName.set(name2, {
23495
+ body,
23496
+ rank,
23497
+ truncated: extra2.bodyTruncated === true,
23498
+ redacted: extra2.bodySecretsRedacted === true
23499
+ });
23500
+ }
23501
+ continue;
23502
+ }
23503
+ if (type === "agent" || type === "command") {
23504
+ if (wanted && !wanted.includes(type))
23505
+ continue;
23506
+ const extra2 = parseExtra(row);
23507
+ const name2 = extra2[type === "agent" ? "agentName" : "commandName"] || row.title;
23508
+ const body = extra2.body;
23509
+ if (!name2 || typeof body !== "string" || !body.trim() || extra2.bodyTruncated === true) {
23510
+ seenUnsupported.set(type, (seenUnsupported.get(type) || 0) + 1);
23511
+ continue;
23512
+ }
23513
+ const tool = String(extra2.tool || "");
23514
+ const rank = SOURCE_PRECEDENCE[type].indexOf(tool);
23515
+ const bucket = type === "agent" ? agentsByName : commandsByName;
23516
+ const prev2 = bucket.get(name2);
23517
+ if (!prev2 || rank >= 0 && (prev2.rank < 0 || rank < prev2.rank)) {
23518
+ bucket.set(name2, {
23519
+ body,
23520
+ rank,
23521
+ format: extra2.format === "toml" ? "toml" : "md",
23522
+ redacted: extra2.bodySecretsRedacted === true
23523
+ });
23524
+ }
23525
+ continue;
23526
+ }
23398
23527
  if (type !== "mcp") {
23399
23528
  if (NOT_REBUILDABLE[type])
23400
23529
  seenUnsupported.set(type, (seenUnsupported.get(type) || 0) + 1);
@@ -23416,16 +23545,93 @@ function planPull(rows, opts = {}) {
23416
23545
  }
23417
23546
  const targets = SUPPORTED_TARGETS.mcp;
23418
23547
  const mcps = [...byName.entries()].map(([name, spec]) => ({ name, spec, tools: targets }));
23548
+ const skills = [...skillsByName.entries()].map(([name, v]) => ({
23549
+ name,
23550
+ body: v.body,
23551
+ truncated: v.truncated,
23552
+ redacted: v.redacted,
23553
+ tools: SUPPORTED_TARGETS.skill
23554
+ }));
23419
23555
  const unsupported = [...seenUnsupported.entries()].map(([type, count]) => ({
23420
23556
  type,
23421
23557
  rows: count,
23422
- reason: NOT_REBUILDABLE[type]
23558
+ reason: NOT_REBUILDABLE[type] ?? "the stored rows carry no full body \u2014 re-index on the device that has them"
23423
23559
  }));
23424
- return { mcps, unsupported };
23560
+ const codecs = [
23561
+ ...[...agentsByName.entries()].map(([name, v]) => ({ type: "agent", name, body: v.body, format: v.format, redacted: v.redacted, tools: SUPPORTED_TARGETS.agent })),
23562
+ ...[...commandsByName.entries()].map(([name, v]) => ({ type: "command", name, body: v.body, format: v.format, redacted: v.redacted, tools: SUPPORTED_TARGETS.command }))
23563
+ ];
23564
+ return { mcps, skills, codecs, unsupported };
23425
23565
  }
23426
23566
  function executePull(rows, opts = {}) {
23427
- const { mcps, unsupported } = planPull(rows, opts);
23567
+ const { mcps, skills, codecs, unsupported } = planPull(rows, opts);
23428
23568
  const outcomes = [];
23569
+ for (const { name, body, truncated, redacted, tools } of skills) {
23570
+ for (const tool of tools) {
23571
+ const dir = join52(skillsDirFor(tool), name);
23572
+ const file = join52(dir, "SKILL.md");
23573
+ if (existsSync48(file)) {
23574
+ outcomes.push({ type: "skill", name, tool, status: "present" });
23575
+ continue;
23576
+ }
23577
+ if (truncated) {
23578
+ outcomes.push({ type: "skill", name, tool, status: "skipped", reason: "the stored body was truncated \u2014 installing it would write a corrupted skill" });
23579
+ continue;
23580
+ }
23581
+ if (opts.dryRun) {
23582
+ outcomes.push({ type: "skill", name, tool, status: "written", reason: "dry run", redactedContent: redacted });
23583
+ continue;
23584
+ }
23585
+ try {
23586
+ mkdirSync14(dir, { recursive: true });
23587
+ writeFileSync15(file, body);
23588
+ outcomes.push({ type: "skill", name, tool, status: "written", path: file, redactedContent: redacted });
23589
+ } catch (e) {
23590
+ outcomes.push({ type: "skill", name, tool, status: "failed", reason: e instanceof Error ? e.message : "write failed" });
23591
+ }
23592
+ }
23593
+ }
23594
+ for (const { type, name, body, format, redacted, tools } of codecs) {
23595
+ let art;
23596
+ try {
23597
+ art = type === "agent" ? parseAgentText(body, format, name) : parseCommandText(body, format, name);
23598
+ } catch (e) {
23599
+ for (const tool of tools) {
23600
+ outcomes.push({ type, name, tool, status: "failed", reason: `could not parse the stored body: ${e instanceof Error ? e.message : "failed"}` });
23601
+ }
23602
+ continue;
23603
+ }
23604
+ if (!art.body.trim()) {
23605
+ for (const tool of tools) {
23606
+ outcomes.push({ type, name, tool, status: "skipped", reason: `the stored body did not parse as ${format}` });
23607
+ }
23608
+ continue;
23609
+ }
23610
+ for (const tool of tools) {
23611
+ let out;
23612
+ try {
23613
+ out = emit(type, art, tool);
23614
+ } catch (e) {
23615
+ outcomes.push({ type, name, tool, status: "failed", reason: e instanceof Error ? e.message : "emit failed" });
23616
+ continue;
23617
+ }
23618
+ if (existsSync48(out.path)) {
23619
+ outcomes.push({ type, name, tool, status: "present" });
23620
+ continue;
23621
+ }
23622
+ if (opts.dryRun) {
23623
+ outcomes.push({ type, name, tool, status: "written", reason: "dry run", redactedContent: redacted });
23624
+ continue;
23625
+ }
23626
+ try {
23627
+ mkdirSync14(dirname21(out.path), { recursive: true });
23628
+ writeFileSync15(out.path, out.content);
23629
+ outcomes.push({ type, name, tool, status: "written", path: out.path, redactedContent: redacted });
23630
+ } catch (e) {
23631
+ outcomes.push({ type, name, tool, status: "failed", reason: e instanceof Error ? e.message : "write failed" });
23632
+ }
23633
+ }
23634
+ }
23429
23635
  for (const { name, spec, tools } of mcps) {
23430
23636
  const built = entryFromSpec(spec);
23431
23637
  for (const tool of tools) {
@@ -23484,11 +23690,12 @@ var init_toolkit_pull = __esm({
23484
23690
  "../engine/dist/core/toolkit-pull.js"() {
23485
23691
  "use strict";
23486
23692
  init_toolkit_sync();
23693
+ init_artifact_codec();
23487
23694
  NOT_REBUILDABLE = {
23488
- skill: "a skill is a directory of files, and file content is not uploaded",
23489
- agent: "only a short body preview is uploaded, not the agent body",
23490
- command: "command bodies are not uploaded",
23491
- instructions: "project-scoped, and file content is not uploaded"
23695
+ // Instructions are the one type that SHOULD not travel by device: a CLAUDE.md
23696
+ // belongs to a repository, so installing one per machine would put a
23697
+ // project's rules somewhere they do not apply.
23698
+ instructions: "project-scoped \u2014 it belongs to a repo, not to a machine"
23492
23699
  };
23493
23700
  }
23494
23701
  });
@@ -23984,11 +24191,11 @@ var verify_repair_exports = {};
23984
24191
  __export(verify_repair_exports, {
23985
24192
  forceFullResync: () => forceFullResync
23986
24193
  });
23987
- import { copyFileSync as copyFileSync3, existsSync as existsSync48 } from "fs";
24194
+ import { copyFileSync as copyFileSync3, existsSync as existsSync49 } from "fs";
23988
24195
  function forceFullResync(server, sessionIds) {
23989
24196
  if (sessionIds.length === 0) return 0;
23990
24197
  const path = ledgerFilePath();
23991
- if (existsSync48(path)) {
24198
+ if (existsSync49(path)) {
23992
24199
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/-/g, "");
23993
24200
  try {
23994
24201
  copyFileSync3(path, `${path}.bak-${stamp}`);
@@ -25063,8 +25270,8 @@ __export(project_tasks_exports, {
25063
25270
  pushProjectTaskStatuses: () => pushProjectTaskStatuses,
25064
25271
  renderTeamTasksMd: () => renderTeamTasksMd
25065
25272
  });
25066
- import { existsSync as existsSync49, readFileSync as readFileSync44, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, statSync as statSync43 } from "node:fs";
25067
- import { join as join54, dirname as dirname21 } from "node:path";
25273
+ import { existsSync as existsSync50, readFileSync as readFileSync44, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, statSync as statSync43 } from "node:fs";
25274
+ import { join as join54, dirname as dirname22 } from "node:path";
25068
25275
  import { homedir as homedir19 } from "node:os";
25069
25276
  function isTeamTaskStatus(v) {
25070
25277
  return !!v && TEAM_STATUSES.has(v);
@@ -25126,11 +25333,11 @@ function readLedger() {
25126
25333
  }
25127
25334
  function writeLedger(l) {
25128
25335
  try {
25129
- mkdirSync14(dirname21(ledgerPath2()), { recursive: true });
25336
+ mkdirSync15(dirname22(ledgerPath2()), { recursive: true });
25130
25337
  } catch {
25131
25338
  }
25132
25339
  try {
25133
- writeFileSync15(ledgerPath2(), JSON.stringify(l));
25340
+ writeFileSync16(ledgerPath2(), JSON.stringify(l));
25134
25341
  } catch {
25135
25342
  }
25136
25343
  }
@@ -25149,7 +25356,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
25149
25356
  }
25150
25357
  for (const project of projects) {
25151
25358
  const file = join54(project, fk.filename);
25152
- if (!existsSync49(file)) continue;
25359
+ if (!existsSync50(file)) continue;
25153
25360
  let mtime;
25154
25361
  try {
25155
25362
  mtime = statSync43(file).mtimeMs;
@@ -25229,18 +25436,18 @@ var intent_drain_exports = {};
25229
25436
  __export(intent_drain_exports, {
25230
25437
  drainSyncIntents: () => drainSyncIntents
25231
25438
  });
25232
- import { existsSync as existsSync50, readFileSync as readFileSync45, writeFileSync as writeFileSync16, appendFileSync, mkdirSync as mkdirSync15 } from "node:fs";
25439
+ import { existsSync as existsSync51, readFileSync as readFileSync45, writeFileSync as writeFileSync17, appendFileSync, mkdirSync as mkdirSync16 } from "node:fs";
25233
25440
  import { join as join55 } from "node:path";
25234
25441
  import { homedir as homedir20 } from "node:os";
25235
25442
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25236
- import { dirname as dirname22 } from "node:path";
25443
+ import { dirname as dirname23 } from "node:path";
25237
25444
  function applyCodeRecommendation(intent) {
25238
25445
  try {
25239
25446
  const meta = JSON.parse(intent.name || "{}");
25240
25447
  if (intent.artifact_type === "write_tasks_file") {
25241
25448
  if (!meta.rootPath || !meta.content) return { status: "error", result: JSON.stringify({ error: "missing rootPath or content" }) };
25242
25449
  const file = join55(meta.rootPath, meta.filename || "CODE_TASKS.md");
25243
- writeFileSync16(file, meta.content);
25450
+ writeFileSync17(file, meta.content);
25244
25451
  return { status: "done", result: JSON.stringify({ wrote: file }) };
25245
25452
  }
25246
25453
  if (intent.artifact_type === "append_claude_md") {
@@ -25251,7 +25458,7 @@ function applyCodeRecommendation(intent) {
25251
25458
  if (isGlobal) {
25252
25459
  const dir = join55(homedir20(), ".claude");
25253
25460
  try {
25254
- mkdirSync15(dir, { recursive: true });
25461
+ mkdirSync16(dir, { recursive: true });
25255
25462
  } catch {
25256
25463
  }
25257
25464
  file = join55(dir, "CLAUDE.md");
@@ -25259,14 +25466,14 @@ function applyCodeRecommendation(intent) {
25259
25466
  if (!meta.rootPath) return { status: "error", result: JSON.stringify({ error: "missing rootPath" }) };
25260
25467
  file = join55(meta.rootPath, "CLAUDE.md");
25261
25468
  }
25262
- const existing = existsSync50(file) ? readFileSync45(file, "utf8") : "";
25469
+ const existing = existsSync51(file) ? readFileSync45(file, "utf8") : "";
25263
25470
  if (existing.includes(text)) return { status: "done", result: JSON.stringify({ skipped: "rule already present", file }) };
25264
25471
  const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}
25265
25472
  ## Rule (added by chat-recall recommendation)
25266
25473
  ${text}
25267
25474
  `;
25268
25475
  if (existing) appendFileSync(file, block);
25269
- else writeFileSync16(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
25476
+ else writeFileSync17(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
25270
25477
  ${block}`);
25271
25478
  return { status: "done", result: JSON.stringify({ appended: file }) };
25272
25479
  }
@@ -25275,6 +25482,46 @@ ${block}`);
25275
25482
  return { status: "error", result: JSON.stringify({ error: e instanceof Error ? e.message : "failed" }) };
25276
25483
  }
25277
25484
  }
25485
+ async function runPull(base2, opts) {
25486
+ const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
25487
+ const { hostname: hostname3 } = await import("node:os");
25488
+ const creds = loadAllCredentials().find((c) => c.serverUrl.replace(/\/+$/, "") === base2.replace(/\/+$/, ""));
25489
+ const headers = creds?.token ? { authorization: `Bearer ${creds.token}` } : {};
25490
+ const wanted = opts.types?.length ? opts.types : ["mcp", "skill", "command", "agent", "instructions"];
25491
+ const rows = [];
25492
+ for (const type of wanted) {
25493
+ try {
25494
+ const res = await fetchWithTimeout(`${base2}/api/toolkit/browse/${type}?limit=1000`, { headers });
25495
+ if (!res.ok) continue;
25496
+ const body = await res.json();
25497
+ for (const it of body.items || []) rows.push(it);
25498
+ } catch {
25499
+ }
25500
+ }
25501
+ const filtered = opts.name ? rows.filter((r) => {
25502
+ try {
25503
+ return (JSON.parse(r.extra_json || "{}").mcpName || r.title) === opts.name;
25504
+ } catch {
25505
+ return r.title === opts.name;
25506
+ }
25507
+ }) : rows;
25508
+ const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted });
25509
+ const written = report.outcomes.filter((o) => o.status === "written");
25510
+ const failed = report.outcomes.filter((o) => o.status === "failed");
25511
+ return {
25512
+ // A skip is a reported outcome, not a failure — the artifact genuinely
25513
+ // cannot be installed here and the reason travels back with the ack.
25514
+ status: failed.length > 0 ? "error" : "done",
25515
+ result: JSON.stringify({
25516
+ installed: written.length,
25517
+ present: report.outcomes.filter((o) => o.status === "present").length,
25518
+ skipped: report.outcomes.filter((o) => o.status === "skipped").map((o) => `${o.name}: ${o.reason}`).slice(0, 20),
25519
+ failed: failed.map((o) => `${o.name}: ${o.reason}`).slice(0, 20),
25520
+ needsEnv: [...new Set(written.flatMap((o) => o.needsEnv || []))],
25521
+ unsupported: report.unsupported
25522
+ })
25523
+ };
25524
+ }
25278
25525
  async function runIntent(intent, ctx) {
25279
25526
  try {
25280
25527
  if (intent.kind === "recheck_session") {
@@ -25287,6 +25534,14 @@ async function runIntent(intent, ctx) {
25287
25534
  if (intent.kind === "code_apply") {
25288
25535
  return applyCodeRecommendation(intent);
25289
25536
  }
25537
+ if (intent.kind === "pull") {
25538
+ const types = (intent.artifact_type || "").split(",").map((t) => t.trim()).filter(Boolean);
25539
+ const r2 = await runPull(ctx.base, {
25540
+ types: types.length ? types : void 0,
25541
+ name: intent.name || void 0
25542
+ });
25543
+ return r2;
25544
+ }
25290
25545
  if (intent.kind === "sync_all") {
25291
25546
  const report = await executeSyncAll();
25292
25547
  const status2 = report.failed.length > 0 ? "error" : "done";
@@ -25330,7 +25585,7 @@ async function drainSyncIntents(opts = {}) {
25330
25585
  if (status === "done") out.done++;
25331
25586
  else out.errored++;
25332
25587
  if (opts.verbose) {
25333
- const label = intent.kind === "sync_all" ? "sync_all" : intent.kind === "recheck_session" ? `recheck ${intent.name}` : `${intent.artifact_type} "${intent.name}" ${intent.from_tool}\u2192${intent.to_tool}`;
25588
+ const label = intent.kind === "sync_all" ? "sync_all" : intent.kind === "pull" ? `pull ${intent.artifact_type || "all"}${intent.name ? ` "${intent.name}"` : ""}` : intent.kind === "recheck_session" ? `recheck ${intent.name}` : `${intent.artifact_type} "${intent.name}" ${intent.from_tool}\u2192${intent.to_tool}`;
25334
25589
  console.error(`[sync-intent] ${label}: ${status}`);
25335
25590
  }
25336
25591
  try {
@@ -25365,11 +25620,11 @@ var init_intent_drain = __esm({
25365
25620
  init_project_tasks();
25366
25621
  ownVersion2 = "0.0.0";
25367
25622
  try {
25368
- const dir = dirname22(fileURLToPath3(import.meta.url));
25623
+ const dir = dirname23(fileURLToPath3(import.meta.url));
25369
25624
  ownVersion2 = JSON.parse(readFileSync45(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
25370
25625
  } catch {
25371
25626
  try {
25372
- if (true) ownVersion2 = "0.5.8";
25627
+ if (true) ownVersion2 = "0.5.9";
25373
25628
  } catch {
25374
25629
  }
25375
25630
  }
@@ -25570,8 +25825,8 @@ var team_merge_exports = {};
25570
25825
  __export(team_merge_exports, {
25571
25826
  mergePullResult: () => mergePullResult
25572
25827
  });
25573
- import { existsSync as existsSync51, mkdirSync as mkdirSync16, readFileSync as readFileSync46, rmSync as rmSync7, writeFileSync as writeFileSync17, statSync as statSync44 } from "fs";
25574
- import { dirname as dirname23, join as join56 } from "path";
25828
+ import { existsSync as existsSync52, mkdirSync as mkdirSync17, readFileSync as readFileSync46, rmSync as rmSync7, writeFileSync as writeFileSync18, statSync as statSync44 } from "fs";
25829
+ import { dirname as dirname24, join as join56 } from "path";
25575
25830
  import { createHash as createHash17 } from "crypto";
25576
25831
  function installPathFor(art, tool) {
25577
25832
  switch (art.type) {
@@ -25664,8 +25919,8 @@ async function mergePullResult(opts) {
25664
25919
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
25665
25920
  continue;
25666
25921
  }
25667
- mkdirSync16(dirname23(path), { recursive: true });
25668
- writeFileSync17(path, body);
25922
+ mkdirSync17(dirname24(path), { recursive: true });
25923
+ writeFileSync18(path, body);
25669
25924
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
25670
25925
  report.written.push({ artifactId: a.id, path });
25671
25926
  } catch (err) {
@@ -25686,7 +25941,7 @@ async function mergePullResult(opts) {
25686
25941
  if (!path)
25687
25942
  continue;
25688
25943
  try {
25689
- if (existsSync51(path))
25944
+ if (existsSync52(path))
25690
25945
  rmSync7(path, { force: true });
25691
25946
  await teamInstallsForget(id, row.tool);
25692
25947
  report.removed.push({ artifactId: id, path });
@@ -25704,7 +25959,7 @@ async function mergePullResult(opts) {
25704
25959
  }
25705
25960
  function readFileIfExists(path) {
25706
25961
  try {
25707
- if (!existsSync51(path))
25962
+ if (!existsSync52(path))
25708
25963
  return null;
25709
25964
  const s = statSync44(path);
25710
25965
  if (!s.isFile())
@@ -25791,7 +26046,7 @@ var init_vault_crypto = __esm({
25791
26046
  });
25792
26047
 
25793
26048
  // ../engine/dist/core/vault-source.js
25794
- import { existsSync as existsSync52, readdirSync as readdirSync34, statSync as statSync45 } from "fs";
26049
+ import { existsSync as existsSync53, readdirSync as readdirSync34, statSync as statSync45 } from "fs";
25795
26050
  import { extname, join as join57 } from "path";
25796
26051
  function* walkVaultSources() {
25797
26052
  const t = loadSettings().team.vault;
@@ -25819,7 +26074,7 @@ function safeStat(path) {
25819
26074
  }
25820
26075
  function* walkClaude() {
25821
26076
  const root = claudeBackend.projectsDir();
25822
- if (!existsSync52(root))
26077
+ if (!existsSync53(root))
25823
26078
  return;
25824
26079
  let projectDirs = [];
25825
26080
  try {
@@ -25852,7 +26107,7 @@ function* walkClaude() {
25852
26107
  }
25853
26108
  function* walkGemini() {
25854
26109
  const tmp = geminiBackend.tmpDir();
25855
- if (!existsSync52(tmp))
26110
+ if (!existsSync53(tmp))
25856
26111
  return;
25857
26112
  let hashDirs = [];
25858
26113
  try {
@@ -25862,7 +26117,7 @@ function* walkGemini() {
25862
26117
  }
25863
26118
  for (const hash of hashDirs) {
25864
26119
  const chatsDir = join57(tmp, hash, "chats");
25865
- if (!existsSync52(chatsDir))
26120
+ if (!existsSync53(chatsDir))
25866
26121
  continue;
25867
26122
  let entries = [];
25868
26123
  try {
@@ -25905,7 +26160,7 @@ function* walkCursor() {
25905
26160
  }
25906
26161
  function* walkCodex() {
25907
26162
  const sessionsRoot = join57(codexBackend.homeDir(), "sessions");
25908
- if (!existsSync52(sessionsRoot))
26163
+ if (!existsSync53(sessionsRoot))
25909
26164
  return;
25910
26165
  const stack = [sessionsRoot];
25911
26166
  while (stack.length) {
@@ -25934,7 +26189,7 @@ function* walkCodex() {
25934
26189
  }
25935
26190
  function* walkAgy() {
25936
26191
  const brainDir = join57(agyBackend.homeDir(), "brain");
25937
- if (!existsSync52(brainDir))
26192
+ if (!existsSync53(brainDir))
25938
26193
  return;
25939
26194
  let sessionDirs = [];
25940
26195
  try {
@@ -25951,10 +26206,10 @@ function* walkAgy() {
25951
26206
  continue;
25952
26207
  }
25953
26208
  let filePath = join57(sessionPath, ".system_generated", "logs", "transcript.jsonl");
25954
- if (!existsSync52(filePath)) {
26209
+ if (!existsSync53(filePath)) {
25955
26210
  filePath = join57(sessionPath, ".system_generated", "logs", "transcript_full.jsonl");
25956
26211
  }
25957
- if (!existsSync52(filePath))
26212
+ if (!existsSync53(filePath))
25958
26213
  continue;
25959
26214
  const s = safeStat(filePath);
25960
26215
  if (!s)
@@ -25991,8 +26246,8 @@ __export(vault_client_exports, {
25991
26246
  vaultStatus: () => vaultStatus,
25992
26247
  vaultSync: () => vaultSync
25993
26248
  });
25994
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync47, writeFileSync as writeFileSync18, existsSync as existsSync53 } from "fs";
25995
- import { dirname as dirname24, join as join58 } from "path";
26249
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync47, writeFileSync as writeFileSync19, existsSync as existsSync54 } from "fs";
26250
+ import { dirname as dirname25, join as join58 } from "path";
25996
26251
  function context2() {
25997
26252
  const s = loadSettings().team;
25998
26253
  if (!s.enabled)
@@ -26204,7 +26459,7 @@ async function vaultRestore(passphrase, opts = {}) {
26204
26459
  }
26205
26460
  try {
26206
26461
  const path = importedPathFor(blob.tool, blob.sessionId);
26207
- if (existsSync53(path)) {
26462
+ if (existsSync54(path)) {
26208
26463
  const localStat = await import("fs").then((m) => m.statSync(path));
26209
26464
  if (Math.abs(localStat.mtimeMs - blob.mtimeSourceMs) < 1) {
26210
26465
  report.skipped.push({ sessionId: blob.sessionId, tool: blob.tool, reason: "unchanged" });
@@ -26220,8 +26475,8 @@ async function vaultRestore(passphrase, opts = {}) {
26220
26475
  throw new Error("cipher integrity check failed");
26221
26476
  }
26222
26477
  const plaintext = decryptBlob(key2, cipher);
26223
- mkdirSync17(dirname24(path), { recursive: true });
26224
- writeFileSync18(path, Buffer.from(plaintext));
26478
+ mkdirSync18(dirname25(path), { recursive: true });
26479
+ writeFileSync19(path, Buffer.from(plaintext));
26225
26480
  const { utimesSync } = await import("fs");
26226
26481
  const t = blob.mtimeSourceMs / 1e3;
26227
26482
  utimesSync(path, t, t);
@@ -26426,7 +26681,7 @@ __export(service_installer_exports, {
26426
26681
  uninstallService: () => uninstallService
26427
26682
  });
26428
26683
  import { execSync as execSync5 } from "child_process";
26429
- import { existsSync as existsSync54, mkdirSync as mkdirSync18, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "fs";
26684
+ import { existsSync as existsSync55, mkdirSync as mkdirSync19, rmSync as rmSync8, writeFileSync as writeFileSync20 } from "fs";
26430
26685
  import { homedir as homedir21 } from "os";
26431
26686
  import { join as join59 } from "path";
26432
26687
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -26459,7 +26714,7 @@ function isServiceRunning() {
26459
26714
  }
26460
26715
  function installService() {
26461
26716
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
26462
- mkdirSync18(dataDir, { recursive: true });
26717
+ mkdirSync19(dataDir, { recursive: true });
26463
26718
  if (process.platform === "linux") {
26464
26719
  return installSystemd(watchJs, node, logFile);
26465
26720
  }
@@ -26478,8 +26733,8 @@ function installSystemd(watchJs, node, logFile) {
26478
26733
  const unitDir = join59(homedir21(), ".config", "systemd", "user");
26479
26734
  const unitPath = join59(unitDir, "chat-recall-watch.service");
26480
26735
  const unit = renderSystemdUnit(watchJs, node, logFile);
26481
- mkdirSync18(unitDir, { recursive: true });
26482
- writeFileSync19(unitPath, unit);
26736
+ mkdirSync19(unitDir, { recursive: true });
26737
+ writeFileSync20(unitPath, unit);
26483
26738
  try {
26484
26739
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
26485
26740
  try {
@@ -26526,8 +26781,8 @@ function installLaunchd(watchJs, node, logFile) {
26526
26781
  const plistDir = join59(homedir21(), "Library", "LaunchAgents");
26527
26782
  const plistPath = join59(plistDir, `${label}.plist`);
26528
26783
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
26529
- mkdirSync18(plistDir, { recursive: true });
26530
- writeFileSync19(plistPath, plist);
26784
+ mkdirSync19(plistDir, { recursive: true });
26785
+ writeFileSync20(plistPath, plist);
26531
26786
  try {
26532
26787
  try {
26533
26788
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -26581,7 +26836,7 @@ function uninstallService() {
26581
26836
  } catch {
26582
26837
  }
26583
26838
  const unitPath = join59(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
26584
- if (existsSync54(unitPath)) {
26839
+ if (existsSync55(unitPath)) {
26585
26840
  rmSync8(unitPath, { force: true });
26586
26841
  removed = true;
26587
26842
  }
@@ -26598,7 +26853,7 @@ function uninstallService() {
26598
26853
  } catch {
26599
26854
  }
26600
26855
  let removed = false;
26601
- if (existsSync54(plistPath)) {
26856
+ if (existsSync55(plistPath)) {
26602
26857
  rmSync8(plistPath, { force: true });
26603
26858
  removed = true;
26604
26859
  }
@@ -26815,9 +27070,9 @@ init_project_resolver();
26815
27070
  import { config } from "dotenv";
26816
27071
  import { Command } from "commander";
26817
27072
  import chalk from "chalk";
26818
- import { existsSync as existsSync55, readFileSync as readFileSync48, writeFileSync as writeFileSync20, realpathSync as realpathSync5 } from "fs";
27073
+ import { existsSync as existsSync56, readFileSync as readFileSync48, writeFileSync as writeFileSync21, realpathSync as realpathSync5 } from "fs";
26819
27074
  import { homedir as homedir22 } from "os";
26820
- import { join as join60, resolve as resolve2, dirname as dirname25, basename as basename28 } from "path";
27075
+ import { join as join60, resolve as resolve2, dirname as dirname26, basename as basename28 } from "path";
26821
27076
 
26822
27077
  // ../engine/dist/core/score-tier.js
26823
27078
  var STRONG_RATIO = 0.85;
@@ -26864,7 +27119,7 @@ function claudeHookConfigFiles() {
26864
27119
  const out = [];
26865
27120
  const seen = /* @__PURE__ */ new Set();
26866
27121
  for (const home of claudeHomeDirs()) {
26867
- if (!existsSync55(join60(home, "projects"))) continue;
27122
+ if (!existsSync56(join60(home, "projects"))) continue;
26868
27123
  const file = join60(home, "hooks.json");
26869
27124
  let key2;
26870
27125
  try {
@@ -27002,7 +27257,9 @@ var DEFAULT_ALLOW = [
27002
27257
  "recall_code_findings",
27003
27258
  "recall_code_actions",
27004
27259
  "recall_claude_suggestions",
27005
- "recall_improvements"
27260
+ "recall_improvements",
27261
+ "recall_toolkit_status",
27262
+ "recall_toolkit_sync"
27006
27263
  ];
27007
27264
  var SELF_HOST_DOCS = "https://github.com/munhq/chat-recall/blob/main/docs/SELF_HOSTING.md";
27008
27265
  var program = new Command();
@@ -27554,7 +27811,7 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
27554
27811
  try {
27555
27812
  const identityFile = getIdentityFilePath();
27556
27813
  let identity = "AI coding assistant";
27557
- if (existsSync55(identityFile)) {
27814
+ if (existsSync56(identityFile)) {
27558
27815
  identity = readFileSync48(identityFile, "utf-8").trim();
27559
27816
  }
27560
27817
  const lines = [
@@ -27612,7 +27869,7 @@ program.command("escalate [sessionId]").description("Escalate a finished session
27612
27869
  }
27613
27870
  });
27614
27871
  program.command("doctor").description("Quick health check across login, server, credentials, hooks, MCP server, and codeindex").option("--purge-local", "Remove the legacy local index (cache.db + vector/KG files) left by pre-thin-collector versions \u2014 storage is server-side now. Requires login; keeps credentials, sync ledger, identity, and the audit log.").action(async (opts) => {
27615
- const { existsSync: existsSync56, readFileSync: readFileSync49, statSync: statSync46 } = await import("fs");
27872
+ const { existsSync: existsSync57, readFileSync: readFileSync49, statSync: statSync46 } = await import("fs");
27616
27873
  const { execSync: execSync6 } = await import("child_process");
27617
27874
  const rows = [];
27618
27875
  const note = (ok, label, detail) => rows.push({ ok, label, detail });
@@ -27676,7 +27933,7 @@ program.command("doctor").description("Quick health check across login, server,
27676
27933
  }
27677
27934
  }
27678
27935
  const credFile = join60(getDataDir(), "credentials.json");
27679
- if (!existsSync56(credFile)) {
27936
+ if (!existsSync57(credFile)) {
27680
27937
  note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
27681
27938
  } else {
27682
27939
  try {
@@ -27689,15 +27946,15 @@ program.command("doctor").description("Quick health check across login, server,
27689
27946
  }
27690
27947
  }
27691
27948
  for (const hooksJson of claudeHookConfigFiles()) {
27692
- const profile = basename28(dirname25(hooksJson));
27949
+ const profile = basename28(dirname26(hooksJson));
27693
27950
  const label = `Claude hooks (${profile})`;
27694
- if (!existsSync56(hooksJson)) {
27951
+ if (!existsSync57(hooksJson)) {
27695
27952
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
27696
27953
  continue;
27697
27954
  }
27698
27955
  try {
27699
27956
  const readHooks = (file) => {
27700
- if (!existsSync56(file)) return {};
27957
+ if (!existsSync57(file)) return {};
27701
27958
  try {
27702
27959
  return JSON.parse(readFileSync49(file, "utf-8")).hooks || {};
27703
27960
  } catch {
@@ -27705,7 +27962,7 @@ program.command("doctor").description("Quick health check across login, server,
27705
27962
  }
27706
27963
  };
27707
27964
  const cfg = readHooks(hooksJson);
27708
- const settings2 = readHooks(join60(dirname25(hooksJson), "settings.json"));
27965
+ const settings2 = readHooks(join60(dirname26(hooksJson), "settings.json"));
27709
27966
  const all = ["SessionStart", "UserPromptSubmit", "Stop", "PreCompact", "SessionEnd"];
27710
27967
  const hits = [];
27711
27968
  for (const ev of all) {
@@ -27729,7 +27986,7 @@ program.command("doctor").description("Quick health check across login, server,
27729
27986
  const want = bundledSkillNames2().length;
27730
27987
  for (const t of skillTargets2()) {
27731
27988
  if (!t.available) continue;
27732
- const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync56(join60(t.dir, n, ".chat-recall-managed"))).length;
27989
+ const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync57(join60(t.dir, n, ".chat-recall-managed"))).length;
27733
27990
  note(
27734
27991
  have === want,
27735
27992
  `Skills \u2014 ${t.label}`,
@@ -27811,7 +28068,7 @@ program.command("doctor").description("Quick health check across login, server,
27811
28068
  join60(dataDir, "index", "knowledge_graph.db"),
27812
28069
  join60(dataDir, "index", "metadata.db")
27813
28070
  ];
27814
- const legacyPresent = legacyPaths.filter((p) => existsSync56(p));
28071
+ const legacyPresent = legacyPaths.filter((p) => existsSync57(p));
27815
28072
  if (opts.purgeLocal) {
27816
28073
  if (!target) {
27817
28074
  note(false, "Purge legacy index", "refused \u2014 log in and `chat-recall sync` first, so your history is on the server before removing local copies");
@@ -28457,10 +28714,10 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
28457
28714
  try {
28458
28715
  const { resolveProjectId: resolveProjectId2 } = await Promise.resolve().then(() => (init_project_resolver(), project_resolver_exports));
28459
28716
  const { renderTeamTasksMd: renderTeamTasksMd2, parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
28460
- const { writeFileSync: writeFileSync21, readFileSync: readFileSync49, existsSync: existsSync56 } = await import("node:fs");
28717
+ const { writeFileSync: writeFileSync22, readFileSync: readFileSync49, existsSync: existsSync57 } = await import("node:fs");
28461
28718
  const { join: join61 } = await import("node:path");
28462
28719
  const file = join61(cwd, "TEAM_TASKS.md");
28463
- if (!opts.force && existsSync56(file) && parseTeamTasksFile2(readFileSync49(file, "utf8")).length > 0) {
28720
+ if (!opts.force && existsSync57(file) && parseTeamTasksFile2(readFileSync49(file, "utf8")).length > 0) {
28464
28721
  console.error(chalk.red("TEAM_TASKS.md has unpushed edits."), "Run", chalk.bold("chat-recall tasks push"), "first, or", chalk.bold("tasks pull --force"), "to discard.");
28465
28722
  process.exit(1);
28466
28723
  }
@@ -28468,7 +28725,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
28468
28725
  const pid = resolved && resolved.source !== "ignored" ? resolved.id : "";
28469
28726
  const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
28470
28727
  const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
28471
- writeFileSync21(file, renderTeamTasksMd2(mine));
28728
+ writeFileSync22(file, renderTeamTasksMd2(mine));
28472
28729
  console.log(chalk.green("\u2713 wrote"), "TEAM_TASKS.md", chalk.gray(`(${mine.length} task(s))`), "\u2014 edit, then", chalk.bold("chat-recall tasks push"));
28473
28730
  } catch (e) {
28474
28731
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
@@ -28479,10 +28736,10 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
28479
28736
  const cwd = dir || process.cwd();
28480
28737
  try {
28481
28738
  const { parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
28482
- const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("node:fs");
28739
+ const { readFileSync: readFileSync49, existsSync: existsSync57 } = await import("node:fs");
28483
28740
  const { join: join61 } = await import("node:path");
28484
28741
  const file = join61(cwd, "TEAM_TASKS.md");
28485
- if (!existsSync56(file)) {
28742
+ if (!existsSync57(file)) {
28486
28743
  console.error(chalk.red("no TEAM_TASKS.md here \u2014"), "run", chalk.bold("chat-recall tasks pull"), "first");
28487
28744
  process.exit(1);
28488
28745
  }
@@ -28780,7 +29037,7 @@ vault.command("forget").description("Remove the stored passphrase from this mach
28780
29037
  console.log(keyringDelete2() ? chalk.green("\u2713 Passphrase removed from the OS keyring.") : chalk.dim("Nothing stored in the OS keyring."));
28781
29038
  }));
28782
29039
  program.command("install-hooks").description("Install Claude Code hooks (Stop + PreCompact + UserPromptSubmit + SessionEnd + SessionStart) into every Claude profile").option("--uninstall", "Remove hooks instead of installing them").option("--no-resume-hint", "Don't install the UserPromptSubmit resume-hint hook").option("--no-escalate", "Don't install the SessionEnd learnings-escalation hook").option("--no-wakeup", "Don't install the SessionStart wake-up hook").action(async (opts) => {
28783
- const { mkdirSync: mkdirSync19, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync56, readFileSync: readFileSync49, writeFileSync: writeFileSync21, statSync: statSync46 } = await import("fs");
29040
+ const { mkdirSync: mkdirSync20, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync57, readFileSync: readFileSync49, writeFileSync: writeFileSync22, statSync: statSync46 } = await import("fs");
28784
29041
  const { fileURLToPath: fileURLToPath5 } = await import("url");
28785
29042
  const here = fileURLToPath5(new URL(".", import.meta.url));
28786
29043
  const findHook = (name) => {
@@ -28788,7 +29045,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28788
29045
  join60(here, "..", "hooks", name),
28789
29046
  join60(here, "..", "..", "hooks", name)
28790
29047
  ];
28791
- return candidates2.find((p) => existsSync56(p));
29048
+ return candidates2.find((p) => existsSync57(p));
28792
29049
  };
28793
29050
  const sourceSaveHook = findHook("chat_recall_save_hook.sh");
28794
29051
  const sourceResumeHook = findHook("chat_recall_resume_hook.sh");
@@ -28805,7 +29062,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28805
29062
  const installedWakeupHook = join60(hooksDir, "chat_recall_wakeup_hook.sh");
28806
29063
  const wakeupWiredInSettings = (home) => {
28807
29064
  const settings2 = join60(home, "settings.json");
28808
- if (!existsSync56(settings2)) return false;
29065
+ if (!existsSync57(settings2)) return false;
28809
29066
  try {
28810
29067
  const cfg = JSON.parse(readFileSync49(settings2, "utf-8"));
28811
29068
  const arr = Array.isArray(cfg.hooks?.SessionStart) ? cfg.hooks.SessionStart : [];
@@ -28816,7 +29073,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28816
29073
  };
28817
29074
  const hookConfigFiles = claudeHookConfigFiles();
28818
29075
  const readHookConfig = (file) => {
28819
- if (!existsSync56(file)) return { hooks: {} };
29076
+ if (!existsSync57(file)) return { hooks: {} };
28820
29077
  try {
28821
29078
  const c = JSON.parse(readFileSync49(file, "utf-8"));
28822
29079
  if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
@@ -28844,8 +29101,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28844
29101
  if (filtered.length) config2.hooks[event] = filtered;
28845
29102
  else delete config2.hooks[event];
28846
29103
  }
28847
- if (removed === 0 && !existsSync56(hooksJson)) continue;
28848
- writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
29104
+ if (removed === 0 && !existsSync57(hooksJson)) continue;
29105
+ writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
28849
29106
  total += removed;
28850
29107
  console.log(chalk.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
28851
29108
  }
@@ -28866,7 +29123,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28866
29123
  console.log(chalk.dim(" Under WSL, run this from inside the WSL shell and it installs normally."));
28867
29124
  return;
28868
29125
  }
28869
- mkdirSync19(hooksDir, { recursive: true });
29126
+ mkdirSync20(hooksDir, { recursive: true });
28870
29127
  copyFileSync4(sourceSaveHook, installedSaveHook);
28871
29128
  chmodSync5(installedSaveHook, 493);
28872
29129
  const installResume = opts.resumeHint !== false && !!sourceResumeHook;
@@ -28898,7 +29155,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28898
29155
  const wakeupSkipped = [];
28899
29156
  for (const hooksJson of hookConfigFiles) {
28900
29157
  const config2 = readHookConfig(hooksJson);
28901
- const home = dirname25(hooksJson);
29158
+ const home = dirname26(hooksJson);
28902
29159
  const alreadyWired = wakeupWiredInSettings(home);
28903
29160
  if (alreadyWired) wakeupSkipped.push(join60(home, "settings.json"));
28904
29161
  const perProfile = installWakeup && !alreadyWired ? [...events, ["SessionStart", wakeupEntry]] : events;
@@ -28910,8 +29167,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28910
29167
  if (without.length) config2.hooks[event] = without;
28911
29168
  else delete config2.hooks[event];
28912
29169
  }
28913
- mkdirSync19(dirname25(hooksJson), { recursive: true });
28914
- writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
29170
+ mkdirSync20(dirname26(hooksJson), { recursive: true });
29171
+ writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
28915
29172
  }
28916
29173
  const saveSz = statSync46(installedSaveHook).size;
28917
29174
  console.log(chalk.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
@@ -28956,7 +29213,7 @@ program.command("dossier <project>").description("Generate a project dossier (ov
28956
29213
  );
28957
29214
  const md = dossier.markdown;
28958
29215
  if (options.out) {
28959
- writeFileSync20(options.out, md);
29216
+ writeFileSync21(options.out, md);
28960
29217
  console.log(chalk.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
28961
29218
  } else {
28962
29219
  console.log(md);