chat-recall 0.5.7 → 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;
@@ -18264,18 +18314,27 @@ function reconstructionSpec(cfg) {
18264
18314
  type: cfg.type || (cfg.url ? "remote" : "local"),
18265
18315
  enabled: cfg.enabled !== false
18266
18316
  };
18317
+ let hidSecret = false;
18318
+ const clean = (v) => {
18319
+ const r = redactInlineSecrets(v);
18320
+ if (r.redacted)
18321
+ hidSecret = true;
18322
+ return r.text;
18323
+ };
18267
18324
  if (typeof cfg.url === "string" && cfg.url)
18268
- out.url = cfg.url;
18325
+ out.url = clean(cfg.url);
18269
18326
  if (Array.isArray(cfg.command))
18270
- out.command = cfg.command;
18327
+ out.command = cfg.command.map(clean);
18271
18328
  else if (typeof cfg.command === "string")
18272
- out.command = cfg.command;
18329
+ out.command = clean(cfg.command);
18273
18330
  if (Array.isArray(cfg.args))
18274
- out.args = cfg.args;
18331
+ out.args = cfg.args.map((a) => clean(String(a)));
18275
18332
  if (Array.isArray(cfg.alwaysAllow))
18276
18333
  out.alwaysAllow = cfg.alwaysAllow;
18277
18334
  if (cfg.env && typeof cfg.env === "object")
18278
18335
  out.envKeys = Object.keys(cfg.env).sort();
18336
+ if (hidSecret)
18337
+ out.secretsRedacted = true;
18279
18338
  return out;
18280
18339
  }
18281
18340
  var McpsSource;
@@ -18286,6 +18345,7 @@ var init_mcps_source = __esm({
18286
18345
  init_codex2();
18287
18346
  init_settings();
18288
18347
  init_tool_paths();
18348
+ init_redact_inline();
18289
18349
  McpsSource = class {
18290
18350
  sourceType = "mcp";
18291
18351
  async *discover() {
@@ -18358,7 +18418,7 @@ var init_mcps_source = __esm({
18358
18418
  }
18359
18419
  for (const [name, cfgRaw] of Object.entries(obj)) {
18360
18420
  const cfg = cfgRaw || {};
18361
- const cmd = commandPreview(cfg);
18421
+ const cmd = redactInlineSecrets(commandPreview(cfg)).text;
18362
18422
  const allow = Array.isArray(cfg.alwaysAllow) ? cfg.alwaysAllow.slice(0, 8) : [];
18363
18423
  const spec = reconstructionSpec(cfg);
18364
18424
  yield {
@@ -18564,6 +18624,19 @@ var init_toolkit_format = __esm({
18564
18624
  // ../engine/dist/parsers/slash-commands-source.js
18565
18625
  import { existsSync as existsSync37, readdirSync as readdirSync28, readFileSync as readFileSync27, statSync as statSync33 } from "fs";
18566
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
+ }
18567
18640
  function parseCommand(content, fallbackName, format) {
18568
18641
  if (format === "toml") {
18569
18642
  const t = parseScalarToml(content);
@@ -18580,7 +18653,7 @@ function parseCommand(content, fallbackName, format) {
18580
18653
  body: body.trim()
18581
18654
  };
18582
18655
  }
18583
- var MAX_CHUNK_CHARS13, SlashCommandsSource;
18656
+ var MAX_CHUNK_CHARS13, MAX_BODY_CHARS2, SlashCommandsSource;
18584
18657
  var init_slash_commands_source = __esm({
18585
18658
  "../engine/dist/parsers/slash-commands-source.js"() {
18586
18659
  "use strict";
@@ -18592,7 +18665,9 @@ var init_slash_commands_source = __esm({
18592
18665
  init_tool_paths();
18593
18666
  init_toolkit_format();
18594
18667
  init_project_dir_name();
18668
+ init_redact_inline();
18595
18669
  MAX_CHUNK_CHARS13 = 2e3;
18670
+ MAX_BODY_CHARS2 = 512 * 1024;
18596
18671
  SlashCommandsSource = class {
18597
18672
  sourceType = "command";
18598
18673
  roots() {
@@ -18656,7 +18731,10 @@ var init_slash_commands_source = __esm({
18656
18731
  commandName: parsed.name,
18657
18732
  description: parsed.description,
18658
18733
  scope: root.scope,
18659
- 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)
18660
18738
  }
18661
18739
  };
18662
18740
  } catch {
@@ -18692,6 +18770,19 @@ var init_slash_commands_source = __esm({
18692
18770
  // ../engine/dist/parsers/subagents-source.js
18693
18771
  import { existsSync as existsSync38, readdirSync as readdirSync29, readFileSync as readFileSync28, statSync as statSync34 } from "fs";
18694
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
+ }
18695
18786
  function parseAgent(content, fallbackName, format) {
18696
18787
  if (format === "toml") {
18697
18788
  const t = parseScalarToml(content);
@@ -18710,7 +18801,7 @@ function parseAgent(content, fallbackName, format) {
18710
18801
  body: body.trim()
18711
18802
  };
18712
18803
  }
18713
- var MAX_CHUNK_CHARS14, SubagentsSource;
18804
+ var MAX_CHUNK_CHARS14, MAX_BODY_CHARS3, SubagentsSource;
18714
18805
  var init_subagents_source = __esm({
18715
18806
  "../engine/dist/parsers/subagents-source.js"() {
18716
18807
  "use strict";
@@ -18722,7 +18813,9 @@ var init_subagents_source = __esm({
18722
18813
  init_tool_paths();
18723
18814
  init_toolkit_format();
18724
18815
  init_project_dir_name();
18816
+ init_redact_inline();
18725
18817
  MAX_CHUNK_CHARS14 = 2e3;
18818
+ MAX_BODY_CHARS3 = 512 * 1024;
18726
18819
  SubagentsSource = class {
18727
18820
  sourceType = "agent";
18728
18821
  roots() {
@@ -18787,7 +18880,10 @@ var init_subagents_source = __esm({
18787
18880
  description: parsed.description,
18788
18881
  tools: parsed.tools,
18789
18882
  scope: root.scope,
18790
- 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)
18791
18887
  }
18792
18888
  };
18793
18889
  } catch {
@@ -19176,6 +19272,9 @@ var init_all_sources = __esm({
19176
19272
  });
19177
19273
 
19178
19274
  // ../engine/dist/core/extractor-version.js
19275
+ function extractorVersionForItem(id, sourceType) {
19276
+ return extractorVersionForTool(toolOfId(id)) + (ITEM_SOURCE_BUMP[sourceType] ?? 0);
19277
+ }
19179
19278
  function toolOfId(id) {
19180
19279
  if (id.startsWith("agy_"))
19181
19280
  return "agy";
@@ -19195,11 +19294,17 @@ function extractorVersionForTool(tool) {
19195
19294
  function extractorVersionForId(id) {
19196
19295
  return extractorVersionForTool(toolOfId(id));
19197
19296
  }
19198
- var BASE_EXTRACTOR_VERSION, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
19297
+ var BASE_EXTRACTOR_VERSION, ITEM_SOURCE_BUMP, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
19199
19298
  var init_extractor_version = __esm({
19200
19299
  "../engine/dist/core/extractor-version.js"() {
19201
19300
  "use strict";
19202
19301
  BASE_EXTRACTOR_VERSION = 2;
19302
+ ITEM_SOURCE_BUMP = {
19303
+ mcp: 1,
19304
+ skill: 1,
19305
+ agent: 1,
19306
+ command: 1
19307
+ };
19203
19308
  TOOL_EXTRACTOR_BUMP = {
19204
19309
  agy: 2,
19205
19310
  claude: 1,
@@ -19589,7 +19694,7 @@ var init_http = __esm({
19589
19694
  CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19590
19695
  } catch {
19591
19696
  try {
19592
- if (true) CLI_VERSION = "0.5.7";
19697
+ if (true) CLI_VERSION = "0.5.9";
19593
19698
  } catch {
19594
19699
  }
19595
19700
  }
@@ -20101,7 +20206,7 @@ var init_telemetry = __esm({
20101
20206
  init_sync_client();
20102
20207
  cliVersion2 = () => {
20103
20208
  try {
20104
- return true ? "0.5.7" : "dev";
20209
+ return true ? "0.5.9" : "dev";
20105
20210
  } catch {
20106
20211
  return "dev";
20107
20212
  }
@@ -21333,6 +21438,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21333
21438
  }
21334
21439
  const itemVersions = loadItemVersions(base2);
21335
21440
  const seenTools = /* @__PURE__ */ new Set();
21441
+ const seenItemKeys = /* @__PURE__ */ new Map();
21336
21442
  const registry = buildSourceRegistry();
21337
21443
  for (const sourceType of registry.getRegisteredTypes()) {
21338
21444
  if (sourceType === "session") continue;
@@ -21347,7 +21453,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21347
21453
  for await (const item of discovered) {
21348
21454
  const itemTool = toolOfId(item.id);
21349
21455
  seenTools.add(itemTool);
21350
- 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);
21351
21459
  if ((item.mtime || 0) < (opts.sinceMs ?? 0) && !versionStale) continue;
21352
21460
  if (excluded(item.projectPath || "")) continue;
21353
21461
  if (!includedProject(item.projectPath || "")) continue;
@@ -21430,6 +21538,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21430
21538
  try {
21431
21539
  const rec = {};
21432
21540
  for (const t of seenTools) rec[t] = extractorVersionForTool(t);
21541
+ for (const [k, v] of seenItemKeys) rec[k] = v;
21433
21542
  saveItemVersions(base2, rec);
21434
21543
  } catch {
21435
21544
  }
@@ -21869,7 +21978,7 @@ var init_sync_client = __esm({
21869
21978
  ownVersion = JSON.parse(readFileSync36(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
21870
21979
  } catch {
21871
21980
  try {
21872
- if (true) ownVersion = "0.5.7";
21981
+ if (true) ownVersion = "0.5.9";
21873
21982
  } catch {
21874
21983
  }
21875
21984
  }
@@ -22685,27 +22794,31 @@ var init_install_skills = __esm({
22685
22794
  import { readFileSync as readFileSync41 } from "fs";
22686
22795
  import { join as join50 } from "path";
22687
22796
  import { homedir as homedir17 } from "os";
22688
- function readCommand(filePath, format) {
22689
- const content = readFileSync41(filePath, "utf-8");
22797
+ function parseCommandText(content, format, fallbackName) {
22690
22798
  if (format === "toml") {
22691
22799
  const t = parseScalarToml(content);
22692
- return { name: t.name || base(filePath), description: t.description || "", body: t.prompt || "" };
22800
+ return { name: t.name || fallbackName, description: t.description || "", body: t.prompt || "" };
22693
22801
  }
22694
22802
  const { fm, body } = parseFrontmatter4(content);
22695
- return { name: fm.name || base(filePath), description: fm.description || "", body: body.trim() };
22803
+ return { name: fm.name || fallbackName, description: fm.description || "", body: body.trim() };
22696
22804
  }
22697
- function readAgent(filePath, format) {
22698
- const content = readFileSync41(filePath, "utf-8");
22805
+ function parseAgentText(content, format, fallbackName) {
22699
22806
  if (format === "toml") {
22700
22807
  const t = parseScalarToml(content);
22701
22808
  return {
22702
- name: t.name || base(filePath),
22809
+ name: t.name || fallbackName,
22703
22810
  description: t.description || "",
22704
22811
  body: t.developer_instructions || t.instructions || ""
22705
22812
  };
22706
22813
  }
22707
22814
  const { fm, body } = parseFrontmatter4(content);
22708
- 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));
22709
22822
  }
22710
22823
  function readInstructions(filePath, name) {
22711
22824
  const content = readFileSync41(filePath, "utf-8");
@@ -23298,8 +23411,8 @@ __export(toolkit_pull_exports, {
23298
23411
  planPull: () => planPull,
23299
23412
  portableCommand: () => portableCommand
23300
23413
  });
23301
- import { statSync as statSync42 } from "node:fs";
23302
- 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";
23303
23416
  function isExecutableFile(p) {
23304
23417
  try {
23305
23418
  return statSync42(p).isFile();
@@ -23359,8 +23472,58 @@ function planPull(rows, opts = {}) {
23359
23472
  const wanted = opts.types;
23360
23473
  const seenUnsupported = /* @__PURE__ */ new Map();
23361
23474
  const byName = /* @__PURE__ */ new Map();
23475
+ const skillsByName = /* @__PURE__ */ new Map();
23476
+ const agentsByName = /* @__PURE__ */ new Map();
23477
+ const commandsByName = /* @__PURE__ */ new Map();
23362
23478
  for (const row of rows) {
23363
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
+ }
23364
23527
  if (type !== "mcp") {
23365
23528
  if (NOT_REBUILDABLE[type])
23366
23529
  seenUnsupported.set(type, (seenUnsupported.get(type) || 0) + 1);
@@ -23382,16 +23545,93 @@ function planPull(rows, opts = {}) {
23382
23545
  }
23383
23546
  const targets = SUPPORTED_TARGETS.mcp;
23384
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
+ }));
23385
23555
  const unsupported = [...seenUnsupported.entries()].map(([type, count]) => ({
23386
23556
  type,
23387
23557
  rows: count,
23388
- 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"
23389
23559
  }));
23390
- 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 };
23391
23565
  }
23392
23566
  function executePull(rows, opts = {}) {
23393
- const { mcps, unsupported } = planPull(rows, opts);
23567
+ const { mcps, skills, codecs, unsupported } = planPull(rows, opts);
23394
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
+ }
23395
23635
  for (const { name, spec, tools } of mcps) {
23396
23636
  const built = entryFromSpec(spec);
23397
23637
  for (const tool of tools) {
@@ -23403,6 +23643,16 @@ function executePull(rows, opts = {}) {
23403
23643
  outcomes.push({ type: "mcp", name, tool, status: "present" });
23404
23644
  continue;
23405
23645
  }
23646
+ if (spec.secretsRedacted) {
23647
+ outcomes.push({
23648
+ type: "mcp",
23649
+ name,
23650
+ tool,
23651
+ status: "skipped",
23652
+ reason: "its command contains a secret, which is never uploaded \u2014 register this one by hand"
23653
+ });
23654
+ continue;
23655
+ }
23406
23656
  const entry = { ...built.entry };
23407
23657
  let rewrittenCommand;
23408
23658
  if (!entry.url) {
@@ -23440,11 +23690,12 @@ var init_toolkit_pull = __esm({
23440
23690
  "../engine/dist/core/toolkit-pull.js"() {
23441
23691
  "use strict";
23442
23692
  init_toolkit_sync();
23693
+ init_artifact_codec();
23443
23694
  NOT_REBUILDABLE = {
23444
- skill: "a skill is a directory of files, and file content is not uploaded",
23445
- agent: "only a short body preview is uploaded, not the agent body",
23446
- command: "command bodies are not uploaded",
23447
- 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"
23448
23699
  };
23449
23700
  }
23450
23701
  });
@@ -23940,11 +24191,11 @@ var verify_repair_exports = {};
23940
24191
  __export(verify_repair_exports, {
23941
24192
  forceFullResync: () => forceFullResync
23942
24193
  });
23943
- import { copyFileSync as copyFileSync3, existsSync as existsSync48 } from "fs";
24194
+ import { copyFileSync as copyFileSync3, existsSync as existsSync49 } from "fs";
23944
24195
  function forceFullResync(server, sessionIds) {
23945
24196
  if (sessionIds.length === 0) return 0;
23946
24197
  const path = ledgerFilePath();
23947
- if (existsSync48(path)) {
24198
+ if (existsSync49(path)) {
23948
24199
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/-/g, "");
23949
24200
  try {
23950
24201
  copyFileSync3(path, `${path}.bak-${stamp}`);
@@ -25019,8 +25270,8 @@ __export(project_tasks_exports, {
25019
25270
  pushProjectTaskStatuses: () => pushProjectTaskStatuses,
25020
25271
  renderTeamTasksMd: () => renderTeamTasksMd
25021
25272
  });
25022
- import { existsSync as existsSync49, readFileSync as readFileSync44, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, statSync as statSync43 } from "node:fs";
25023
- 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";
25024
25275
  import { homedir as homedir19 } from "node:os";
25025
25276
  function isTeamTaskStatus(v) {
25026
25277
  return !!v && TEAM_STATUSES.has(v);
@@ -25082,11 +25333,11 @@ function readLedger() {
25082
25333
  }
25083
25334
  function writeLedger(l) {
25084
25335
  try {
25085
- mkdirSync14(dirname21(ledgerPath2()), { recursive: true });
25336
+ mkdirSync15(dirname22(ledgerPath2()), { recursive: true });
25086
25337
  } catch {
25087
25338
  }
25088
25339
  try {
25089
- writeFileSync15(ledgerPath2(), JSON.stringify(l));
25340
+ writeFileSync16(ledgerPath2(), JSON.stringify(l));
25090
25341
  } catch {
25091
25342
  }
25092
25343
  }
@@ -25105,7 +25356,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
25105
25356
  }
25106
25357
  for (const project of projects) {
25107
25358
  const file = join54(project, fk.filename);
25108
- if (!existsSync49(file)) continue;
25359
+ if (!existsSync50(file)) continue;
25109
25360
  let mtime;
25110
25361
  try {
25111
25362
  mtime = statSync43(file).mtimeMs;
@@ -25185,18 +25436,18 @@ var intent_drain_exports = {};
25185
25436
  __export(intent_drain_exports, {
25186
25437
  drainSyncIntents: () => drainSyncIntents
25187
25438
  });
25188
- 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";
25189
25440
  import { join as join55 } from "node:path";
25190
25441
  import { homedir as homedir20 } from "node:os";
25191
25442
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25192
- import { dirname as dirname22 } from "node:path";
25443
+ import { dirname as dirname23 } from "node:path";
25193
25444
  function applyCodeRecommendation(intent) {
25194
25445
  try {
25195
25446
  const meta = JSON.parse(intent.name || "{}");
25196
25447
  if (intent.artifact_type === "write_tasks_file") {
25197
25448
  if (!meta.rootPath || !meta.content) return { status: "error", result: JSON.stringify({ error: "missing rootPath or content" }) };
25198
25449
  const file = join55(meta.rootPath, meta.filename || "CODE_TASKS.md");
25199
- writeFileSync16(file, meta.content);
25450
+ writeFileSync17(file, meta.content);
25200
25451
  return { status: "done", result: JSON.stringify({ wrote: file }) };
25201
25452
  }
25202
25453
  if (intent.artifact_type === "append_claude_md") {
@@ -25207,7 +25458,7 @@ function applyCodeRecommendation(intent) {
25207
25458
  if (isGlobal) {
25208
25459
  const dir = join55(homedir20(), ".claude");
25209
25460
  try {
25210
- mkdirSync15(dir, { recursive: true });
25461
+ mkdirSync16(dir, { recursive: true });
25211
25462
  } catch {
25212
25463
  }
25213
25464
  file = join55(dir, "CLAUDE.md");
@@ -25215,14 +25466,14 @@ function applyCodeRecommendation(intent) {
25215
25466
  if (!meta.rootPath) return { status: "error", result: JSON.stringify({ error: "missing rootPath" }) };
25216
25467
  file = join55(meta.rootPath, "CLAUDE.md");
25217
25468
  }
25218
- const existing = existsSync50(file) ? readFileSync45(file, "utf8") : "";
25469
+ const existing = existsSync51(file) ? readFileSync45(file, "utf8") : "";
25219
25470
  if (existing.includes(text)) return { status: "done", result: JSON.stringify({ skipped: "rule already present", file }) };
25220
25471
  const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}
25221
25472
  ## Rule (added by chat-recall recommendation)
25222
25473
  ${text}
25223
25474
  `;
25224
25475
  if (existing) appendFileSync(file, block);
25225
- else writeFileSync16(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
25476
+ else writeFileSync17(file, `# ${meta.rootPath?.split("/").pop() || "Global"} instructions
25226
25477
  ${block}`);
25227
25478
  return { status: "done", result: JSON.stringify({ appended: file }) };
25228
25479
  }
@@ -25231,6 +25482,46 @@ ${block}`);
25231
25482
  return { status: "error", result: JSON.stringify({ error: e instanceof Error ? e.message : "failed" }) };
25232
25483
  }
25233
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
+ }
25234
25525
  async function runIntent(intent, ctx) {
25235
25526
  try {
25236
25527
  if (intent.kind === "recheck_session") {
@@ -25243,6 +25534,14 @@ async function runIntent(intent, ctx) {
25243
25534
  if (intent.kind === "code_apply") {
25244
25535
  return applyCodeRecommendation(intent);
25245
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
+ }
25246
25545
  if (intent.kind === "sync_all") {
25247
25546
  const report = await executeSyncAll();
25248
25547
  const status2 = report.failed.length > 0 ? "error" : "done";
@@ -25286,7 +25585,7 @@ async function drainSyncIntents(opts = {}) {
25286
25585
  if (status === "done") out.done++;
25287
25586
  else out.errored++;
25288
25587
  if (opts.verbose) {
25289
- 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}`;
25290
25589
  console.error(`[sync-intent] ${label}: ${status}`);
25291
25590
  }
25292
25591
  try {
@@ -25321,11 +25620,11 @@ var init_intent_drain = __esm({
25321
25620
  init_project_tasks();
25322
25621
  ownVersion2 = "0.0.0";
25323
25622
  try {
25324
- const dir = dirname22(fileURLToPath3(import.meta.url));
25623
+ const dir = dirname23(fileURLToPath3(import.meta.url));
25325
25624
  ownVersion2 = JSON.parse(readFileSync45(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
25326
25625
  } catch {
25327
25626
  try {
25328
- if (true) ownVersion2 = "0.5.7";
25627
+ if (true) ownVersion2 = "0.5.9";
25329
25628
  } catch {
25330
25629
  }
25331
25630
  }
@@ -25526,8 +25825,8 @@ var team_merge_exports = {};
25526
25825
  __export(team_merge_exports, {
25527
25826
  mergePullResult: () => mergePullResult
25528
25827
  });
25529
- import { existsSync as existsSync51, mkdirSync as mkdirSync16, readFileSync as readFileSync46, rmSync as rmSync7, writeFileSync as writeFileSync17, statSync as statSync44 } from "fs";
25530
- 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";
25531
25830
  import { createHash as createHash17 } from "crypto";
25532
25831
  function installPathFor(art, tool) {
25533
25832
  switch (art.type) {
@@ -25620,8 +25919,8 @@ async function mergePullResult(opts) {
25620
25919
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
25621
25920
  continue;
25622
25921
  }
25623
- mkdirSync16(dirname23(path), { recursive: true });
25624
- writeFileSync17(path, body);
25922
+ mkdirSync17(dirname24(path), { recursive: true });
25923
+ writeFileSync18(path, body);
25625
25924
  pending.push({ artifactId: a.id, tool, artifactType: a.type, artifactName: a.name, sha256: sha, installedAt: Date.now() });
25626
25925
  report.written.push({ artifactId: a.id, path });
25627
25926
  } catch (err) {
@@ -25642,7 +25941,7 @@ async function mergePullResult(opts) {
25642
25941
  if (!path)
25643
25942
  continue;
25644
25943
  try {
25645
- if (existsSync51(path))
25944
+ if (existsSync52(path))
25646
25945
  rmSync7(path, { force: true });
25647
25946
  await teamInstallsForget(id, row.tool);
25648
25947
  report.removed.push({ artifactId: id, path });
@@ -25660,7 +25959,7 @@ async function mergePullResult(opts) {
25660
25959
  }
25661
25960
  function readFileIfExists(path) {
25662
25961
  try {
25663
- if (!existsSync51(path))
25962
+ if (!existsSync52(path))
25664
25963
  return null;
25665
25964
  const s = statSync44(path);
25666
25965
  if (!s.isFile())
@@ -25747,7 +26046,7 @@ var init_vault_crypto = __esm({
25747
26046
  });
25748
26047
 
25749
26048
  // ../engine/dist/core/vault-source.js
25750
- 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";
25751
26050
  import { extname, join as join57 } from "path";
25752
26051
  function* walkVaultSources() {
25753
26052
  const t = loadSettings().team.vault;
@@ -25775,7 +26074,7 @@ function safeStat(path) {
25775
26074
  }
25776
26075
  function* walkClaude() {
25777
26076
  const root = claudeBackend.projectsDir();
25778
- if (!existsSync52(root))
26077
+ if (!existsSync53(root))
25779
26078
  return;
25780
26079
  let projectDirs = [];
25781
26080
  try {
@@ -25808,7 +26107,7 @@ function* walkClaude() {
25808
26107
  }
25809
26108
  function* walkGemini() {
25810
26109
  const tmp = geminiBackend.tmpDir();
25811
- if (!existsSync52(tmp))
26110
+ if (!existsSync53(tmp))
25812
26111
  return;
25813
26112
  let hashDirs = [];
25814
26113
  try {
@@ -25818,7 +26117,7 @@ function* walkGemini() {
25818
26117
  }
25819
26118
  for (const hash of hashDirs) {
25820
26119
  const chatsDir = join57(tmp, hash, "chats");
25821
- if (!existsSync52(chatsDir))
26120
+ if (!existsSync53(chatsDir))
25822
26121
  continue;
25823
26122
  let entries = [];
25824
26123
  try {
@@ -25861,7 +26160,7 @@ function* walkCursor() {
25861
26160
  }
25862
26161
  function* walkCodex() {
25863
26162
  const sessionsRoot = join57(codexBackend.homeDir(), "sessions");
25864
- if (!existsSync52(sessionsRoot))
26163
+ if (!existsSync53(sessionsRoot))
25865
26164
  return;
25866
26165
  const stack = [sessionsRoot];
25867
26166
  while (stack.length) {
@@ -25890,7 +26189,7 @@ function* walkCodex() {
25890
26189
  }
25891
26190
  function* walkAgy() {
25892
26191
  const brainDir = join57(agyBackend.homeDir(), "brain");
25893
- if (!existsSync52(brainDir))
26192
+ if (!existsSync53(brainDir))
25894
26193
  return;
25895
26194
  let sessionDirs = [];
25896
26195
  try {
@@ -25907,10 +26206,10 @@ function* walkAgy() {
25907
26206
  continue;
25908
26207
  }
25909
26208
  let filePath = join57(sessionPath, ".system_generated", "logs", "transcript.jsonl");
25910
- if (!existsSync52(filePath)) {
26209
+ if (!existsSync53(filePath)) {
25911
26210
  filePath = join57(sessionPath, ".system_generated", "logs", "transcript_full.jsonl");
25912
26211
  }
25913
- if (!existsSync52(filePath))
26212
+ if (!existsSync53(filePath))
25914
26213
  continue;
25915
26214
  const s = safeStat(filePath);
25916
26215
  if (!s)
@@ -25947,8 +26246,8 @@ __export(vault_client_exports, {
25947
26246
  vaultStatus: () => vaultStatus,
25948
26247
  vaultSync: () => vaultSync
25949
26248
  });
25950
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync47, writeFileSync as writeFileSync18, existsSync as existsSync53 } from "fs";
25951
- 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";
25952
26251
  function context2() {
25953
26252
  const s = loadSettings().team;
25954
26253
  if (!s.enabled)
@@ -26160,7 +26459,7 @@ async function vaultRestore(passphrase, opts = {}) {
26160
26459
  }
26161
26460
  try {
26162
26461
  const path = importedPathFor(blob.tool, blob.sessionId);
26163
- if (existsSync53(path)) {
26462
+ if (existsSync54(path)) {
26164
26463
  const localStat = await import("fs").then((m) => m.statSync(path));
26165
26464
  if (Math.abs(localStat.mtimeMs - blob.mtimeSourceMs) < 1) {
26166
26465
  report.skipped.push({ sessionId: blob.sessionId, tool: blob.tool, reason: "unchanged" });
@@ -26176,8 +26475,8 @@ async function vaultRestore(passphrase, opts = {}) {
26176
26475
  throw new Error("cipher integrity check failed");
26177
26476
  }
26178
26477
  const plaintext = decryptBlob(key2, cipher);
26179
- mkdirSync17(dirname24(path), { recursive: true });
26180
- writeFileSync18(path, Buffer.from(plaintext));
26478
+ mkdirSync18(dirname25(path), { recursive: true });
26479
+ writeFileSync19(path, Buffer.from(plaintext));
26181
26480
  const { utimesSync } = await import("fs");
26182
26481
  const t = blob.mtimeSourceMs / 1e3;
26183
26482
  utimesSync(path, t, t);
@@ -26382,7 +26681,7 @@ __export(service_installer_exports, {
26382
26681
  uninstallService: () => uninstallService
26383
26682
  });
26384
26683
  import { execSync as execSync5 } from "child_process";
26385
- 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";
26386
26685
  import { homedir as homedir21 } from "os";
26387
26686
  import { join as join59 } from "path";
26388
26687
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -26415,7 +26714,7 @@ function isServiceRunning() {
26415
26714
  }
26416
26715
  function installService() {
26417
26716
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
26418
- mkdirSync18(dataDir, { recursive: true });
26717
+ mkdirSync19(dataDir, { recursive: true });
26419
26718
  if (process.platform === "linux") {
26420
26719
  return installSystemd(watchJs, node, logFile);
26421
26720
  }
@@ -26434,8 +26733,8 @@ function installSystemd(watchJs, node, logFile) {
26434
26733
  const unitDir = join59(homedir21(), ".config", "systemd", "user");
26435
26734
  const unitPath = join59(unitDir, "chat-recall-watch.service");
26436
26735
  const unit = renderSystemdUnit(watchJs, node, logFile);
26437
- mkdirSync18(unitDir, { recursive: true });
26438
- writeFileSync19(unitPath, unit);
26736
+ mkdirSync19(unitDir, { recursive: true });
26737
+ writeFileSync20(unitPath, unit);
26439
26738
  try {
26440
26739
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
26441
26740
  try {
@@ -26482,8 +26781,8 @@ function installLaunchd(watchJs, node, logFile) {
26482
26781
  const plistDir = join59(homedir21(), "Library", "LaunchAgents");
26483
26782
  const plistPath = join59(plistDir, `${label}.plist`);
26484
26783
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
26485
- mkdirSync18(plistDir, { recursive: true });
26486
- writeFileSync19(plistPath, plist);
26784
+ mkdirSync19(plistDir, { recursive: true });
26785
+ writeFileSync20(plistPath, plist);
26487
26786
  try {
26488
26787
  try {
26489
26788
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -26537,7 +26836,7 @@ function uninstallService() {
26537
26836
  } catch {
26538
26837
  }
26539
26838
  const unitPath = join59(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
26540
- if (existsSync54(unitPath)) {
26839
+ if (existsSync55(unitPath)) {
26541
26840
  rmSync8(unitPath, { force: true });
26542
26841
  removed = true;
26543
26842
  }
@@ -26554,7 +26853,7 @@ function uninstallService() {
26554
26853
  } catch {
26555
26854
  }
26556
26855
  let removed = false;
26557
- if (existsSync54(plistPath)) {
26856
+ if (existsSync55(plistPath)) {
26558
26857
  rmSync8(plistPath, { force: true });
26559
26858
  removed = true;
26560
26859
  }
@@ -26771,9 +27070,9 @@ init_project_resolver();
26771
27070
  import { config } from "dotenv";
26772
27071
  import { Command } from "commander";
26773
27072
  import chalk from "chalk";
26774
- 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";
26775
27074
  import { homedir as homedir22 } from "os";
26776
- 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";
26777
27076
 
26778
27077
  // ../engine/dist/core/score-tier.js
26779
27078
  var STRONG_RATIO = 0.85;
@@ -26820,7 +27119,7 @@ function claudeHookConfigFiles() {
26820
27119
  const out = [];
26821
27120
  const seen = /* @__PURE__ */ new Set();
26822
27121
  for (const home of claudeHomeDirs()) {
26823
- if (!existsSync55(join60(home, "projects"))) continue;
27122
+ if (!existsSync56(join60(home, "projects"))) continue;
26824
27123
  const file = join60(home, "hooks.json");
26825
27124
  let key2;
26826
27125
  try {
@@ -26958,7 +27257,9 @@ var DEFAULT_ALLOW = [
26958
27257
  "recall_code_findings",
26959
27258
  "recall_code_actions",
26960
27259
  "recall_claude_suggestions",
26961
- "recall_improvements"
27260
+ "recall_improvements",
27261
+ "recall_toolkit_status",
27262
+ "recall_toolkit_sync"
26962
27263
  ];
26963
27264
  var SELF_HOST_DOCS = "https://github.com/munhq/chat-recall/blob/main/docs/SELF_HOSTING.md";
26964
27265
  var program = new Command();
@@ -27510,7 +27811,7 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
27510
27811
  try {
27511
27812
  const identityFile = getIdentityFilePath();
27512
27813
  let identity = "AI coding assistant";
27513
- if (existsSync55(identityFile)) {
27814
+ if (existsSync56(identityFile)) {
27514
27815
  identity = readFileSync48(identityFile, "utf-8").trim();
27515
27816
  }
27516
27817
  const lines = [
@@ -27568,7 +27869,7 @@ program.command("escalate [sessionId]").description("Escalate a finished session
27568
27869
  }
27569
27870
  });
27570
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) => {
27571
- const { existsSync: existsSync56, readFileSync: readFileSync49, statSync: statSync46 } = await import("fs");
27872
+ const { existsSync: existsSync57, readFileSync: readFileSync49, statSync: statSync46 } = await import("fs");
27572
27873
  const { execSync: execSync6 } = await import("child_process");
27573
27874
  const rows = [];
27574
27875
  const note = (ok, label, detail) => rows.push({ ok, label, detail });
@@ -27632,7 +27933,7 @@ program.command("doctor").description("Quick health check across login, server,
27632
27933
  }
27633
27934
  }
27634
27935
  const credFile = join60(getDataDir(), "credentials.json");
27635
- if (!existsSync56(credFile)) {
27936
+ if (!existsSync57(credFile)) {
27636
27937
  note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
27637
27938
  } else {
27638
27939
  try {
@@ -27645,15 +27946,15 @@ program.command("doctor").description("Quick health check across login, server,
27645
27946
  }
27646
27947
  }
27647
27948
  for (const hooksJson of claudeHookConfigFiles()) {
27648
- const profile = basename28(dirname25(hooksJson));
27949
+ const profile = basename28(dirname26(hooksJson));
27649
27950
  const label = `Claude hooks (${profile})`;
27650
- if (!existsSync56(hooksJson)) {
27951
+ if (!existsSync57(hooksJson)) {
27651
27952
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
27652
27953
  continue;
27653
27954
  }
27654
27955
  try {
27655
27956
  const readHooks = (file) => {
27656
- if (!existsSync56(file)) return {};
27957
+ if (!existsSync57(file)) return {};
27657
27958
  try {
27658
27959
  return JSON.parse(readFileSync49(file, "utf-8")).hooks || {};
27659
27960
  } catch {
@@ -27661,7 +27962,7 @@ program.command("doctor").description("Quick health check across login, server,
27661
27962
  }
27662
27963
  };
27663
27964
  const cfg = readHooks(hooksJson);
27664
- const settings2 = readHooks(join60(dirname25(hooksJson), "settings.json"));
27965
+ const settings2 = readHooks(join60(dirname26(hooksJson), "settings.json"));
27665
27966
  const all = ["SessionStart", "UserPromptSubmit", "Stop", "PreCompact", "SessionEnd"];
27666
27967
  const hits = [];
27667
27968
  for (const ev of all) {
@@ -27685,7 +27986,7 @@ program.command("doctor").description("Quick health check across login, server,
27685
27986
  const want = bundledSkillNames2().length;
27686
27987
  for (const t of skillTargets2()) {
27687
27988
  if (!t.available) continue;
27688
- 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;
27689
27990
  note(
27690
27991
  have === want,
27691
27992
  `Skills \u2014 ${t.label}`,
@@ -27767,7 +28068,7 @@ program.command("doctor").description("Quick health check across login, server,
27767
28068
  join60(dataDir, "index", "knowledge_graph.db"),
27768
28069
  join60(dataDir, "index", "metadata.db")
27769
28070
  ];
27770
- const legacyPresent = legacyPaths.filter((p) => existsSync56(p));
28071
+ const legacyPresent = legacyPaths.filter((p) => existsSync57(p));
27771
28072
  if (opts.purgeLocal) {
27772
28073
  if (!target) {
27773
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");
@@ -28413,10 +28714,10 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
28413
28714
  try {
28414
28715
  const { resolveProjectId: resolveProjectId2 } = await Promise.resolve().then(() => (init_project_resolver(), project_resolver_exports));
28415
28716
  const { renderTeamTasksMd: renderTeamTasksMd2, parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
28416
- const { writeFileSync: writeFileSync21, readFileSync: readFileSync49, existsSync: existsSync56 } = await import("node:fs");
28717
+ const { writeFileSync: writeFileSync22, readFileSync: readFileSync49, existsSync: existsSync57 } = await import("node:fs");
28417
28718
  const { join: join61 } = await import("node:path");
28418
28719
  const file = join61(cwd, "TEAM_TASKS.md");
28419
- if (!opts.force && existsSync56(file) && parseTeamTasksFile2(readFileSync49(file, "utf8")).length > 0) {
28720
+ if (!opts.force && existsSync57(file) && parseTeamTasksFile2(readFileSync49(file, "utf8")).length > 0) {
28420
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.");
28421
28722
  process.exit(1);
28422
28723
  }
@@ -28424,7 +28725,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
28424
28725
  const pid = resolved && resolved.source !== "ignored" ? resolved.id : "";
28425
28726
  const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
28426
28727
  const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
28427
- writeFileSync21(file, renderTeamTasksMd2(mine));
28728
+ writeFileSync22(file, renderTeamTasksMd2(mine));
28428
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"));
28429
28730
  } catch (e) {
28430
28731
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
@@ -28435,10 +28736,10 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
28435
28736
  const cwd = dir || process.cwd();
28436
28737
  try {
28437
28738
  const { parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
28438
- const { readFileSync: readFileSync49, existsSync: existsSync56 } = await import("node:fs");
28739
+ const { readFileSync: readFileSync49, existsSync: existsSync57 } = await import("node:fs");
28439
28740
  const { join: join61 } = await import("node:path");
28440
28741
  const file = join61(cwd, "TEAM_TASKS.md");
28441
- if (!existsSync56(file)) {
28742
+ if (!existsSync57(file)) {
28442
28743
  console.error(chalk.red("no TEAM_TASKS.md here \u2014"), "run", chalk.bold("chat-recall tasks pull"), "first");
28443
28744
  process.exit(1);
28444
28745
  }
@@ -28736,7 +29037,7 @@ vault.command("forget").description("Remove the stored passphrase from this mach
28736
29037
  console.log(keyringDelete2() ? chalk.green("\u2713 Passphrase removed from the OS keyring.") : chalk.dim("Nothing stored in the OS keyring."));
28737
29038
  }));
28738
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) => {
28739
- 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");
28740
29041
  const { fileURLToPath: fileURLToPath5 } = await import("url");
28741
29042
  const here = fileURLToPath5(new URL(".", import.meta.url));
28742
29043
  const findHook = (name) => {
@@ -28744,7 +29045,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28744
29045
  join60(here, "..", "hooks", name),
28745
29046
  join60(here, "..", "..", "hooks", name)
28746
29047
  ];
28747
- return candidates2.find((p) => existsSync56(p));
29048
+ return candidates2.find((p) => existsSync57(p));
28748
29049
  };
28749
29050
  const sourceSaveHook = findHook("chat_recall_save_hook.sh");
28750
29051
  const sourceResumeHook = findHook("chat_recall_resume_hook.sh");
@@ -28761,7 +29062,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28761
29062
  const installedWakeupHook = join60(hooksDir, "chat_recall_wakeup_hook.sh");
28762
29063
  const wakeupWiredInSettings = (home) => {
28763
29064
  const settings2 = join60(home, "settings.json");
28764
- if (!existsSync56(settings2)) return false;
29065
+ if (!existsSync57(settings2)) return false;
28765
29066
  try {
28766
29067
  const cfg = JSON.parse(readFileSync49(settings2, "utf-8"));
28767
29068
  const arr = Array.isArray(cfg.hooks?.SessionStart) ? cfg.hooks.SessionStart : [];
@@ -28772,7 +29073,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28772
29073
  };
28773
29074
  const hookConfigFiles = claudeHookConfigFiles();
28774
29075
  const readHookConfig = (file) => {
28775
- if (!existsSync56(file)) return { hooks: {} };
29076
+ if (!existsSync57(file)) return { hooks: {} };
28776
29077
  try {
28777
29078
  const c = JSON.parse(readFileSync49(file, "utf-8"));
28778
29079
  if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
@@ -28800,8 +29101,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28800
29101
  if (filtered.length) config2.hooks[event] = filtered;
28801
29102
  else delete config2.hooks[event];
28802
29103
  }
28803
- if (removed === 0 && !existsSync56(hooksJson)) continue;
28804
- writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
29104
+ if (removed === 0 && !existsSync57(hooksJson)) continue;
29105
+ writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
28805
29106
  total += removed;
28806
29107
  console.log(chalk.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
28807
29108
  }
@@ -28822,7 +29123,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28822
29123
  console.log(chalk.dim(" Under WSL, run this from inside the WSL shell and it installs normally."));
28823
29124
  return;
28824
29125
  }
28825
- mkdirSync19(hooksDir, { recursive: true });
29126
+ mkdirSync20(hooksDir, { recursive: true });
28826
29127
  copyFileSync4(sourceSaveHook, installedSaveHook);
28827
29128
  chmodSync5(installedSaveHook, 493);
28828
29129
  const installResume = opts.resumeHint !== false && !!sourceResumeHook;
@@ -28854,7 +29155,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28854
29155
  const wakeupSkipped = [];
28855
29156
  for (const hooksJson of hookConfigFiles) {
28856
29157
  const config2 = readHookConfig(hooksJson);
28857
- const home = dirname25(hooksJson);
29158
+ const home = dirname26(hooksJson);
28858
29159
  const alreadyWired = wakeupWiredInSettings(home);
28859
29160
  if (alreadyWired) wakeupSkipped.push(join60(home, "settings.json"));
28860
29161
  const perProfile = installWakeup && !alreadyWired ? [...events, ["SessionStart", wakeupEntry]] : events;
@@ -28866,8 +29167,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
28866
29167
  if (without.length) config2.hooks[event] = without;
28867
29168
  else delete config2.hooks[event];
28868
29169
  }
28869
- mkdirSync19(dirname25(hooksJson), { recursive: true });
28870
- writeFileSync21(hooksJson, JSON.stringify(config2, null, 2) + "\n");
29170
+ mkdirSync20(dirname26(hooksJson), { recursive: true });
29171
+ writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
28871
29172
  }
28872
29173
  const saveSz = statSync46(installedSaveHook).size;
28873
29174
  console.log(chalk.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
@@ -28912,7 +29213,7 @@ program.command("dossier <project>").description("Generate a project dossier (ov
28912
29213
  );
28913
29214
  const md = dossier.markdown;
28914
29215
  if (options.out) {
28915
- writeFileSync20(options.out, md);
29216
+ writeFileSync21(options.out, md);
28916
29217
  console.log(chalk.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
28917
29218
  } else {
28918
29219
  console.log(md);