chat-recall 0.7.3 → 0.7.5

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/watch.js CHANGED
@@ -6520,7 +6520,7 @@ async function archiveRawSession(store, sessionId) {
6520
6520
  return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
6521
6521
  }
6522
6522
  function detectForkPredecessor(sessionPath) {
6523
- const { readFileSync: readFileSync46, readdirSync: readdirSync34 } = requireFs();
6523
+ const { readFileSync: readFileSync46, readdirSync: readdirSync33 } = requireFs();
6524
6524
  const { dirname: dirname26, join: join55, basename: basename26 } = requirePath();
6525
6525
  let text;
6526
6526
  try {
@@ -6550,7 +6550,7 @@ function detectForkPredecessor(sessionPath) {
6550
6550
  const self = basename26(sessionPath);
6551
6551
  let entries;
6552
6552
  try {
6553
- entries = readdirSync34(dir);
6553
+ entries = readdirSync33(dir);
6554
6554
  } catch {
6555
6555
  return null;
6556
6556
  }
@@ -9404,8 +9404,35 @@ var init_codex2 = __esm({
9404
9404
  configToml() {
9405
9405
  return join12(this.homeDir(), "config.toml");
9406
9406
  }
9407
- pluginsDir() {
9408
- return join12(this.homeDir(), ".tmp", "plugins", "plugins");
9407
+ /**
9408
+ * Plugins that are INSTALLED, one folder each:
9409
+ * `plugins/cache/<marketplace>/<plugin>/<version>/`, newest version.
9410
+ *
9411
+ * `.tmp/plugins/plugins/` is Codex's copy of the plugin catalog (62 plugins
9412
+ * and a README on one machine), not what is installed. Reading it reported
9413
+ * every catalog plugin's skills and MCP servers as the user's own, and a
9414
+ * pull then installed them into every tool: build-ios-apps' xcodebuildmcp
9415
+ * reached a Linux PC that never installed the plugin.
9416
+ */
9417
+ installedPluginDirs() {
9418
+ const cache4 = join12(this.homeDir(), "plugins", "cache");
9419
+ const out = [];
9420
+ const dirs = (p) => {
9421
+ try {
9422
+ return readdirSync8(p, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
9423
+ } catch {
9424
+ return [];
9425
+ }
9426
+ };
9427
+ for (const market of dirs(cache4)) {
9428
+ for (const plugin of dirs(join12(cache4, market))) {
9429
+ const versions = dirs(join12(cache4, market, plugin)).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
9430
+ const newest = versions[versions.length - 1];
9431
+ if (newest)
9432
+ out.push({ name: plugin, dir: join12(cache4, market, plugin, newest) });
9433
+ }
9434
+ }
9435
+ return out;
9409
9436
  }
9410
9437
  /** OpenAI-bundled, read-only system skills (imagegen, skill-creator, …). */
9411
9438
  skillsSystemDir() {
@@ -16502,14 +16529,11 @@ var init_skills_source = __esm({
16502
16529
  }
16503
16530
  if (!isSourceEnabled("codex", "skills"))
16504
16531
  return;
16505
- const codexPluginsDir = codexBackend.pluginsDir();
16506
- if (existsSync33(codexPluginsDir)) {
16507
- for (const pluginName of readdirSync25(codexPluginsDir)) {
16508
- const skillsDir = join33(codexPluginsDir, pluginName, "skills");
16509
- if (!existsSync33(skillsDir))
16510
- continue;
16511
- yield* this.fromSkillRoot({ path: skillsDir, tool: "codex" }, pluginName);
16512
- }
16532
+ for (const { name: pluginName, dir } of codexBackend.installedPluginDirs()) {
16533
+ const skillsDir = join33(dir, "skills");
16534
+ if (!existsSync33(skillsDir))
16535
+ continue;
16536
+ yield* this.fromSkillRoot({ path: skillsDir, tool: "codex" }, pluginName);
16513
16537
  }
16514
16538
  }
16515
16539
  async *fromSkillRoot(root, pluginName) {
@@ -16622,7 +16646,7 @@ var init_skills_source = __esm({
16622
16646
  });
16623
16647
 
16624
16648
  // ../engine/dist/parsers/mcps-source.js
16625
- import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
16649
+ import { existsSync as existsSync34, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
16626
16650
  import { homedir as homedir13 } from "os";
16627
16651
  import { join as join34 } from "path";
16628
16652
  function readJson(path) {
@@ -16734,16 +16758,13 @@ var init_mcps_source = __esm({
16734
16758
  scope: "user"
16735
16759
  });
16736
16760
  yield* this.fromCodexToml(codexBackend.configToml());
16737
- const pluginsDir = codexBackend.pluginsDir();
16738
- if (existsSync34(pluginsDir)) {
16739
- for (const plugin of readdirSync26(pluginsDir)) {
16740
- const mcpPath = join34(pluginsDir, plugin, ".mcp.json");
16741
- yield* this.fromObject(readJson(mcpPath)?.mcpServers, {
16742
- tool: "codex",
16743
- filePath: mcpPath,
16744
- scope: "plugin"
16745
- });
16746
- }
16761
+ for (const { dir } of codexBackend.installedPluginDirs()) {
16762
+ const mcpPath = join34(dir, ".mcp.json");
16763
+ yield* this.fromObject(readJson(mcpPath)?.mcpServers, {
16764
+ tool: "codex",
16765
+ filePath: mcpPath,
16766
+ scope: "plugin"
16767
+ });
16747
16768
  }
16748
16769
  }
16749
16770
  async *fromObject(obj, ctx) {
@@ -16781,7 +16802,12 @@ var init_mcps_source = __esm({
16781
16802
  // MCP's url into a shell string, and `alwaysAllow` is truncated to
16782
16803
  // 8 for the preview — rebuilding from either produces a broken or
16783
16804
  // half-permissioned server, silently.
16784
- spec
16805
+ spec,
16806
+ // Where this registration runs. A server that starts through npx
16807
+ // passes the command check on any machine, so this is what tells a
16808
+ // pull that it came from another platform (see toolkit-pull.ts).
16809
+ platform: process.platform,
16810
+ arch: process.arch
16785
16811
  }
16786
16812
  };
16787
16813
  }
@@ -16960,7 +16986,7 @@ var init_toolkit_format = __esm({
16960
16986
  });
16961
16987
 
16962
16988
  // ../engine/dist/parsers/slash-commands-source.js
16963
- import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync31 } from "fs";
16989
+ import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync26, statSync as statSync31 } from "fs";
16964
16990
  import { join as join35, basename as basename18 } from "path";
16965
16991
  function rebuildBody2(content) {
16966
16992
  const { text, redacted } = redactInlineSecrets(content);
@@ -17015,7 +17041,7 @@ var init_slash_commands_source = __esm({
17015
17041
  const projectsRoot = claudeBackend.projectsDir();
17016
17042
  if (existsSync35(projectsRoot)) {
17017
17043
  try {
17018
- for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
17044
+ for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
17019
17045
  if (!entry.isDirectory() || entry.name.startsWith("."))
17020
17046
  continue;
17021
17047
  const projectPath = resolveProjectDirName(entry.name);
@@ -17047,7 +17073,7 @@ var init_slash_commands_source = __esm({
17047
17073
  const ext = root.format === "toml" ? ".toml" : ".md";
17048
17074
  let files;
17049
17075
  try {
17050
- files = readdirSync27(root.path).filter((f) => f.endsWith(ext));
17076
+ files = readdirSync26(root.path).filter((f) => f.endsWith(ext));
17051
17077
  } catch {
17052
17078
  continue;
17053
17079
  }
@@ -17107,7 +17133,7 @@ var init_slash_commands_source = __esm({
17107
17133
  });
17108
17134
 
17109
17135
  // ../engine/dist/parsers/subagents-source.js
17110
- import { existsSync as existsSync36, readdirSync as readdirSync28, readFileSync as readFileSync27, statSync as statSync32 } from "fs";
17136
+ import { existsSync as existsSync36, readdirSync as readdirSync27, readFileSync as readFileSync27, statSync as statSync32 } from "fs";
17111
17137
  import { join as join36, basename as basename19 } from "path";
17112
17138
  function rebuildBody3(content) {
17113
17139
  const { text, redacted } = redactInlineSecrets(content);
@@ -17164,7 +17190,7 @@ var init_subagents_source = __esm({
17164
17190
  const projectsRoot = claudeBackend.projectsDir();
17165
17191
  if (existsSync36(projectsRoot)) {
17166
17192
  try {
17167
- for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
17193
+ for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
17168
17194
  if (!entry.isDirectory() || entry.name.startsWith("."))
17169
17195
  continue;
17170
17196
  const projectPath = resolveProjectDirName(entry.name);
@@ -17196,7 +17222,7 @@ var init_subagents_source = __esm({
17196
17222
  const ext = root.format === "toml" ? ".toml" : ".md";
17197
17223
  let files;
17198
17224
  try {
17199
- files = readdirSync28(root.path).filter((f) => f.endsWith(ext));
17225
+ files = readdirSync27(root.path).filter((f) => f.endsWith(ext));
17200
17226
  } catch {
17201
17227
  continue;
17202
17228
  }
@@ -17257,7 +17283,7 @@ var init_subagents_source = __esm({
17257
17283
  });
17258
17284
 
17259
17285
  // ../engine/dist/parsers/hooks-source.js
17260
- import { existsSync as existsSync37, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17286
+ import { existsSync as existsSync37, readFileSync as readFileSync28, readdirSync as readdirSync28, statSync as statSync33 } from "fs";
17261
17287
  import { join as join37 } from "path";
17262
17288
  function readJson2(path) {
17263
17289
  if (!existsSync37(path))
@@ -17284,7 +17310,7 @@ var init_hooks_source = __esm({
17284
17310
  const projectsRoot = claudeBackend.projectsDir();
17285
17311
  if (existsSync37(projectsRoot)) {
17286
17312
  try {
17287
- for (const entry of readdirSync29(projectsRoot, { withFileTypes: true })) {
17313
+ for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
17288
17314
  if (!entry.isDirectory() || entry.name.startsWith("."))
17289
17315
  continue;
17290
17316
  const projectPath = resolveProjectDirName(entry.name);
@@ -17380,7 +17406,7 @@ var init_hooks_source = __esm({
17380
17406
  });
17381
17407
 
17382
17408
  // ../engine/dist/parsers/plugins-source.js
17383
- import { existsSync as existsSync38, readFileSync as readFileSync29, readdirSync as readdirSync30, statSync as statSync34 } from "fs";
17409
+ import { existsSync as existsSync38, readFileSync as readFileSync29, readdirSync as readdirSync29, statSync as statSync34 } from "fs";
17384
17410
  import { homedir as homedir14 } from "os";
17385
17411
  import { join as join38 } from "path";
17386
17412
  function readJson3(path) {
@@ -17442,7 +17468,7 @@ var init_plugins_source = __esm({
17442
17468
  if (isSourceEnabled("agy", "extensions") && existsSync38(gemRoot)) {
17443
17469
  let entries;
17444
17470
  try {
17445
- entries = readdirSync30(gemRoot);
17471
+ entries = readdirSync29(gemRoot);
17446
17472
  } catch {
17447
17473
  entries = [];
17448
17474
  }
@@ -17486,16 +17512,8 @@ var init_plugins_source = __esm({
17486
17512
  };
17487
17513
  }
17488
17514
  }
17489
- const codexPluginsRoot = codexBackend.pluginsDir();
17490
- if (isSourceEnabled("codex", "plugins") && existsSync38(codexPluginsRoot)) {
17491
- let entries;
17492
- try {
17493
- entries = readdirSync30(codexPluginsRoot);
17494
- } catch {
17495
- entries = [];
17496
- }
17497
- for (const name of entries) {
17498
- const pluginDir = join38(codexPluginsRoot, name);
17515
+ if (isSourceEnabled("codex", "plugins")) {
17516
+ for (const { name, dir: pluginDir } of codexBackend.installedPluginDirs()) {
17499
17517
  let st;
17500
17518
  try {
17501
17519
  st = statSync34(pluginDir);
@@ -17609,7 +17627,8 @@ var init_all_sources = __esm({
17609
17627
 
17610
17628
  // ../engine/dist/core/extractor-version.js
17611
17629
  function extractorVersionForItem(id, sourceType) {
17612
- return extractorVersionForTool(toolOfId(id)) + (ITEM_SOURCE_BUMP[sourceType] ?? 0);
17630
+ const tool = toolOfId(id);
17631
+ return extractorVersionForTool(tool) + (ITEM_SOURCE_BUMP[sourceType] ?? 0) + (ITEM_TOOL_SOURCE_BUMP[`${tool}:${sourceType}`] ?? 0);
17613
17632
  }
17614
17633
  function toolOfId(id) {
17615
17634
  if (id.startsWith("agy_"))
@@ -17630,17 +17649,21 @@ function extractorVersionForTool(tool) {
17630
17649
  function extractorVersionForId(id) {
17631
17650
  return extractorVersionForTool(toolOfId(id));
17632
17651
  }
17633
- var BASE_EXTRACTOR_VERSION, ITEM_SOURCE_BUMP, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
17652
+ var BASE_EXTRACTOR_VERSION, ITEM_SOURCE_BUMP, ITEM_TOOL_SOURCE_BUMP, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
17634
17653
  var init_extractor_version = __esm({
17635
17654
  "../engine/dist/core/extractor-version.js"() {
17636
17655
  "use strict";
17637
17656
  BASE_EXTRACTOR_VERSION = 2;
17638
17657
  ITEM_SOURCE_BUMP = {
17639
- mcp: 1,
17658
+ mcp: 2,
17640
17659
  skill: 1,
17641
17660
  agent: 1,
17642
17661
  command: 1
17643
17662
  };
17663
+ ITEM_TOOL_SOURCE_BUMP = {
17664
+ "codex:skill": 1,
17665
+ "codex:plugin": 1
17666
+ };
17644
17667
  TOOL_EXTRACTOR_BUMP = {
17645
17668
  agy: 3,
17646
17669
  claude: 1,
@@ -17663,6 +17686,7 @@ __export(sync_ledger_exports, {
17663
17686
  getSyncedMtimes: () => getSyncedMtimes,
17664
17687
  getSyncedRows: () => getSyncedRows,
17665
17688
  ledgerFilePath: () => ledgerFilePath,
17689
+ loadInventoryHashes: () => loadInventoryHashes,
17666
17690
  loadItemVersions: () => loadItemVersions,
17667
17691
  markFieldCoverage: () => markFieldCoverage,
17668
17692
  markFieldFullPassDone: () => markFieldFullPassDone,
@@ -17670,6 +17694,7 @@ __export(sync_ledger_exports, {
17670
17694
  markSynced: () => markSynced,
17671
17695
  persistLedgerData: () => persistLedgerData,
17672
17696
  pruneLedgerTargets: () => pruneLedgerTargets,
17697
+ saveInventoryHashes: () => saveInventoryHashes,
17673
17698
  saveItemVersions: () => saveItemVersions,
17674
17699
  syncMode: () => syncMode,
17675
17700
  unackedCursorAttemptCount: () => unackedCursorAttemptCount
@@ -17775,6 +17800,28 @@ function saveItemVersions(server, versions) {
17775
17800
  writeFileSync6(tmp, JSON.stringify(all));
17776
17801
  renameSync4(tmp, path);
17777
17802
  }
17803
+ function loadInventoryHashes(server) {
17804
+ try {
17805
+ const parsed = JSON.parse(readFileSync30(inventoryPath(), "utf-8"));
17806
+ return { ...(parsed && typeof parsed === "object" ? parsed[server] : null) || {} };
17807
+ } catch {
17808
+ return {};
17809
+ }
17810
+ }
17811
+ function saveInventoryHashes(server, hashes) {
17812
+ let all = {};
17813
+ try {
17814
+ all = JSON.parse(readFileSync30(inventoryPath(), "utf-8"));
17815
+ } catch {
17816
+ }
17817
+ if (!all || typeof all !== "object") all = {};
17818
+ all[server] = { ...all[server] || {}, ...hashes };
17819
+ const path = inventoryPath();
17820
+ mkdirSync7(dirname16(path), { recursive: true });
17821
+ const tmp = `${path}.tmp`;
17822
+ writeFileSync6(tmp, JSON.stringify(all));
17823
+ renameSync4(tmp, path);
17824
+ }
17778
17825
  function getSyncedMtimes(server) {
17779
17826
  const data = load();
17780
17827
  const out = /* @__PURE__ */ new Map();
@@ -17913,7 +17960,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
17913
17960
  if (row.m >= Math.floor(mtime)) return "skip";
17914
17961
  return "full";
17915
17962
  }
17916
- var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17963
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, inventoryPath, unackedCursorAttempts, reconcilePath, reconcileCache;
17917
17964
  var init_sync_ledger = __esm({
17918
17965
  "src/sync-ledger.ts"() {
17919
17966
  "use strict";
@@ -17926,6 +17973,7 @@ var init_sync_ledger = __esm({
17926
17973
  flushTimer = null;
17927
17974
  itemVersionsPath = () => join39(getDataDir(), "item-versions.json");
17928
17975
  itemVersionsCache = null;
17976
+ inventoryPath = () => join39(getDataDir(), "toolkit-inventory.json");
17929
17977
  unackedCursorAttempts = 0;
17930
17978
  reconcilePath = () => join39(getDataDir(), "sync-field-reconcile.json");
17931
17979
  reconcileCache = null;
@@ -18071,7 +18119,7 @@ var init_http = __esm({
18071
18119
  CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18072
18120
  } catch {
18073
18121
  try {
18074
- if (true) CLI_VERSION = "0.7.3";
18122
+ if (true) CLI_VERSION = "0.7.5";
18075
18123
  } catch {
18076
18124
  }
18077
18125
  }
@@ -18566,7 +18614,7 @@ var init_telemetry = __esm({
18566
18614
  init_sync_client();
18567
18615
  cliVersion = () => {
18568
18616
  try {
18569
- return true ? "0.7.3" : "dev";
18617
+ return true ? "0.7.5" : "dev";
18570
18618
  } catch {
18571
18619
  return "dev";
18572
18620
  }
@@ -18800,7 +18848,7 @@ __export(auto_update_exports, {
18800
18848
  import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
18801
18849
  import { readFileSync as readFileSync34, existsSync as existsSync41 } from "node:fs";
18802
18850
  import { fileURLToPath as fileURLToPath2 } from "node:url";
18803
- import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync36, mkdirSync as mkdirSync9 } from "node:fs";
18851
+ import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync30, statSync as statSync36, mkdirSync as mkdirSync9 } from "node:fs";
18804
18852
  import { join as join41, dirname as dirname18, sep as sep2, posix as pathPosix, win32 as pathWin32 } from "node:path";
18805
18853
  import { tmpdir as tmpdir2, homedir as homedir15 } from "node:os";
18806
18854
  import { createHash as createHash7 } from "node:crypto";
@@ -18837,7 +18885,7 @@ function planAutoUpdate(base2, caps, ownVersion3, flag) {
18837
18885
  if (!rel || !rel.version || !rel.sha256) return { update: false, reason: "server advertises no CLI release" };
18838
18886
  if (!isAutoUpdateEnabled(caps.edition, flag)) return { update: false, reason: `auto-update disabled (edition ${caps.edition ?? "?"})` };
18839
18887
  if (compareVersions(rel.version, ownVersion3) <= 0) return { update: false, reason: `already current (own ${ownVersion3} >= server ${rel.version})` };
18840
- return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base2), sha256: rel.sha256, from: ownVersion3, to: rel.version };
18888
+ return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base2, rel.version), fallbackUrl: tarballUrl(base2), sha256: rel.sha256, from: ownVersion3, to: rel.version };
18841
18889
  }
18842
18890
  function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
18843
18891
  const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
@@ -18845,7 +18893,7 @@ function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
18845
18893
  let removed = 0;
18846
18894
  let entries;
18847
18895
  try {
18848
- entries = readdirSync31(root);
18896
+ entries = readdirSync30(root);
18849
18897
  } catch {
18850
18898
  return 0;
18851
18899
  }
@@ -18855,7 +18903,7 @@ function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
18855
18903
  try {
18856
18904
  const st = statSync36(dir);
18857
18905
  if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
18858
- const inside = readdirSync31(dir);
18906
+ const inside = readdirSync30(dir);
18859
18907
  if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
18860
18908
  rmSync5(dir, { recursive: true, force: true });
18861
18909
  removed++;
@@ -18870,7 +18918,13 @@ async function executeAutoUpdate(plan, deps) {
18870
18918
  try {
18871
18919
  buf = await deps.download(plan.url);
18872
18920
  } catch (e) {
18873
- return { updated: false, reason: `download failed: ${e instanceof Error ? e.message : e}` };
18921
+ const notFound = e instanceof Error && e.message === "HTTP 404";
18922
+ if (!notFound || !plan.fallbackUrl) return { updated: false, reason: `download failed: ${e instanceof Error ? e.message : e}` };
18923
+ try {
18924
+ buf = await deps.download(plan.fallbackUrl);
18925
+ } catch (e2) {
18926
+ return { updated: false, reason: `download failed: ${e2 instanceof Error ? e2.message : e2}` };
18927
+ }
18874
18928
  }
18875
18929
  const got = createHash7("sha256").update(buf).digest("hex");
18876
18930
  if (got !== plan.sha256) return { updated: false, reason: `checksum mismatch (expected ${plan.sha256.slice(0, 12)}\u2026, got ${got.slice(0, 12)}\u2026) \u2014 NOT installing` };
@@ -19046,7 +19100,7 @@ var init_auto_update = __esm({
19046
19100
  "src/auto-update.ts"() {
19047
19101
  "use strict";
19048
19102
  init_http();
19049
- tarballUrl = (base2) => `${base2.replace(/\/+$/, "")}/install/chat-recall.tgz`;
19103
+ tarballUrl = (base2, version) => `${base2.replace(/\/+$/, "")}/install/${version ? `chat-recall-${version}.tgz` : "chat-recall.tgz"}`;
19050
19104
  GIVE_UP_AFTER = 3;
19051
19105
  reportedStandingSkip = /* @__PURE__ */ new Set();
19052
19106
  }
@@ -19054,7 +19108,7 @@ var init_auto_update = __esm({
19054
19108
 
19055
19109
  // src/sync-client.ts
19056
19110
  import { join as join42, dirname as dirname19, basename as basename20 } from "node:path";
19057
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync32, statSync as statSync37 } from "node:fs";
19111
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync31, statSync as statSync37 } from "node:fs";
19058
19112
  import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
19059
19113
  import { createHash as createHash8 } from "node:crypto";
19060
19114
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -19401,7 +19455,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
19401
19455
  let reaped = 0;
19402
19456
  let names;
19403
19457
  try {
19404
- names = readdirSync32(dir);
19458
+ names = readdirSync31(dir);
19405
19459
  } catch {
19406
19460
  return 0;
19407
19461
  }
@@ -20037,17 +20091,24 @@ async function syncToTargetInScope(cred, opts = {}) {
20037
20091
  const seenTools = /* @__PURE__ */ new Set();
20038
20092
  const seenItemKeys = /* @__PURE__ */ new Map();
20039
20093
  const registry = buildSourceRegistry();
20094
+ const inventory = /* @__PURE__ */ new Map();
20095
+ const inventoryFailed = /* @__PURE__ */ new Set();
20040
20096
  for (const sourceType of registry.getRegisteredTypes()) {
20041
20097
  if (sourceType === "session") continue;
20098
+ if (TOOLKIT_INVENTORY_TYPES.has(sourceType)) inventory.set(sourceType, /* @__PURE__ */ new Set());
20042
20099
  for (const source of registry.getAll(sourceType)) {
20043
20100
  let discovered;
20044
20101
  try {
20045
20102
  discovered = source.discover();
20046
20103
  } catch {
20104
+ inventoryFailed.add(sourceType);
20047
20105
  continue;
20048
20106
  }
20049
20107
  try {
20050
20108
  for await (const item of discovered) {
20109
+ if (inventory.has(item.sourceType) && !excluded(item.projectPath || "") && includedProject(item.projectPath || "")) {
20110
+ inventory.get(item.sourceType).add(item.id);
20111
+ }
20051
20112
  const itemTool = toolOfId(item.id);
20052
20113
  seenTools.add(itemTool);
20053
20114
  const itemKey = `${itemTool}:${item.sourceType}`;
@@ -20105,6 +20166,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20105
20166
  redactions += count.redactions;
20106
20167
  }
20107
20168
  } catch (err) {
20169
+ inventoryFailed.add(sourceType);
20108
20170
  console.error(`[sync] source '${sourceType}' failed mid-walk (its items may be incomplete this run): ${err instanceof Error ? err.message : err}`);
20109
20171
  }
20110
20172
  }
@@ -20131,6 +20193,27 @@ async function syncToTargetInScope(cred, opts = {}) {
20131
20193
  await kgTripleBatch.drain();
20132
20194
  await findingsBatch.drain();
20133
20195
  await drainInflight();
20196
+ {
20197
+ const sentHashes = loadInventoryHashes(base2);
20198
+ const entries = [];
20199
+ const hashes = {};
20200
+ for (const [sourceType, ids] of inventory) {
20201
+ if (inventoryFailed.has(sourceType)) continue;
20202
+ const sorted = [...ids].sort();
20203
+ const hash = createHash8("sha256").update(sorted.join("\n")).digest("hex").slice(0, 32);
20204
+ if (sentHashes[sourceType] === hash) continue;
20205
+ entries.push({ source_type: sourceType, ids: sorted });
20206
+ hashes[sourceType] = hash;
20207
+ }
20208
+ if (entries.length > 0) {
20209
+ try {
20210
+ await post({ inventory: entries });
20211
+ saveInventoryHashes(base2, hashes);
20212
+ } catch (err) {
20213
+ console.error(`[sync] toolkit inventory not sent (it is retried next sync): ${err instanceof Error ? err.message : err}`);
20214
+ }
20215
+ }
20216
+ }
20134
20217
  if (seenTools.size > 0) {
20135
20218
  try {
20136
20219
  const rec = {};
@@ -20576,7 +20659,7 @@ async function syncIncremental(opts = {}) {
20576
20659
  lock.release();
20577
20660
  }
20578
20661
  }
20579
- var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, DERIVED_MAX_BYTES, SYNC_CHUNK_BYTES, BATCH_SCAN_MAX_BYTES;
20662
+ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, DERIVED_MAX_BYTES, SYNC_CHUNK_BYTES, TOOLKIT_INVENTORY_TYPES, BATCH_SCAN_MAX_BYTES;
20580
20663
  var init_sync_client = __esm({
20581
20664
  "src/sync-client.ts"() {
20582
20665
  "use strict";
@@ -20621,7 +20704,7 @@ var init_sync_client = __esm({
20621
20704
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20622
20705
  } catch {
20623
20706
  try {
20624
- if (true) ownVersion = "0.7.3";
20707
+ if (true) ownVersion = "0.7.5";
20625
20708
  } catch {
20626
20709
  }
20627
20710
  }
@@ -20650,6 +20733,7 @@ var init_sync_client = __esm({
20650
20733
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
20651
20734
  DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
20652
20735
  SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
20736
+ TOOLKIT_INVENTORY_TYPES = /* @__PURE__ */ new Set(["skill", "mcp", "command", "agent", "plugin"]);
20653
20737
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
20654
20738
  }
20655
20739
  });
@@ -21284,6 +21368,7 @@ __export(toolkit_pull_exports, {
21284
21368
  entryFromSpec: () => entryFromSpec,
21285
21369
  executePull: () => executePull,
21286
21370
  planPull: () => planPull,
21371
+ platformRefusal: () => platformRefusal,
21287
21372
  portableCommand: () => portableCommand
21288
21373
  });
21289
21374
  import { existsSync as existsSync45, mkdirSync as mkdirSync13, statSync as statSync40, writeFileSync as writeFileSync12 } from "node:fs";
@@ -21341,6 +21426,7 @@ function planPull(rows, opts = {}) {
21341
21426
  const wanted = opts.types;
21342
21427
  const seenUnsupported = /* @__PURE__ */ new Map();
21343
21428
  const byName = /* @__PURE__ */ new Map();
21429
+ const platformsByName = /* @__PURE__ */ new Map();
21344
21430
  const skillsByName = /* @__PURE__ */ new Map();
21345
21431
  const agentsByName = /* @__PURE__ */ new Map();
21346
21432
  const commandsByName = /* @__PURE__ */ new Map();
@@ -21404,6 +21490,9 @@ function planPull(rows, opts = {}) {
21404
21490
  const name = extra.mcpName || row.title;
21405
21491
  if (!name)
21406
21492
  continue;
21493
+ if (typeof extra.platform === "string" && extra.platform) {
21494
+ (platformsByName.get(name) ?? platformsByName.set(name, /* @__PURE__ */ new Set()).get(name)).add(extra.platform);
21495
+ }
21407
21496
  const spec = extra.spec || null;
21408
21497
  if (!spec)
21409
21498
  continue;
@@ -21413,7 +21502,12 @@ function planPull(rows, opts = {}) {
21413
21502
  byName.set(name, spec);
21414
21503
  }
21415
21504
  const targets = SUPPORTED_TARGETS.mcp;
21416
- const mcps = [...byName.entries()].map(([name, spec]) => ({ name, spec, tools: targets }));
21505
+ const mcps = [...byName.entries()].map(([name, spec]) => ({
21506
+ name,
21507
+ spec,
21508
+ tools: targets,
21509
+ platforms: [...platformsByName.get(name) ?? []].sort()
21510
+ }));
21417
21511
  const skills = [...skillsByName.entries()].map(([name, v]) => ({
21418
21512
  name,
21419
21513
  body: v.body,
@@ -21432,7 +21526,18 @@ function planPull(rows, opts = {}) {
21432
21526
  ];
21433
21527
  return { mcps, skills, codecs, unsupported };
21434
21528
  }
21529
+ function platformRefusal(name, platforms, here, explicit) {
21530
+ if (explicit.has(name) || platforms.includes(here))
21531
+ return null;
21532
+ const hint = `install it by name if it works on ${here}: chat-recall toolkit pull --mcp ${name}`;
21533
+ if (platforms.length === 0) {
21534
+ return `no device recorded its platform \u2014 re-sync from the device that has it, or ${hint}`;
21535
+ }
21536
+ return `registered only on ${platforms.join(", ")} \u2014 ${hint}`;
21537
+ }
21435
21538
  function executePull(rows, opts = {}) {
21539
+ const here = opts.platform ?? process.platform;
21540
+ const explicit = new Set(opts.explicit ?? []);
21436
21541
  const { mcps, skills, codecs, unsupported } = planPull(rows, opts);
21437
21542
  const outcomes = [];
21438
21543
  for (const { name, body, truncated, redacted, tools } of skills) {
@@ -21501,8 +21606,9 @@ function executePull(rows, opts = {}) {
21501
21606
  }
21502
21607
  }
21503
21608
  }
21504
- for (const { name, spec, tools } of mcps) {
21609
+ for (const { name, spec, tools, platforms } of mcps) {
21505
21610
  const built = entryFromSpec(spec);
21611
+ const refusal = platformRefusal(name, platforms, here, explicit);
21506
21612
  for (const tool of tools) {
21507
21613
  if (!built) {
21508
21614
  outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: "stored row has no rebuildable spec (re-index on the source device)" });
@@ -21512,6 +21618,10 @@ function executePull(rows, opts = {}) {
21512
21618
  outcomes.push({ type: "mcp", name, tool, status: "present" });
21513
21619
  continue;
21514
21620
  }
21621
+ if (refusal) {
21622
+ outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: refusal });
21623
+ continue;
21624
+ }
21515
21625
  if (spec.secretsRedacted) {
21516
21626
  outcomes.push({
21517
21627
  type: "mcp",
@@ -21798,14 +21908,14 @@ __export(shadow_prune_exports, {
21798
21908
  DEFAULT_MAX_TOTAL_BYTES: () => DEFAULT_MAX_TOTAL_BYTES,
21799
21909
  pruneShadow: () => pruneShadow
21800
21910
  });
21801
- import { readdirSync as readdirSync33, statSync as statSync43, unlinkSync as unlinkSync3 } from "fs";
21911
+ import { readdirSync as readdirSync32, statSync as statSync43, unlinkSync as unlinkSync3 } from "fs";
21802
21912
  import { join as join50 } from "path";
21803
21913
  function listShadows(isFullyAcked) {
21804
21914
  const out = [];
21805
21915
  const root = shadowRoot();
21806
21916
  let tools;
21807
21917
  try {
21808
- tools = readdirSync33(root);
21918
+ tools = readdirSync32(root);
21809
21919
  } catch {
21810
21920
  return out;
21811
21921
  }
@@ -21813,7 +21923,7 @@ function listShadows(isFullyAcked) {
21813
21923
  const dir = join50(root, tool);
21814
21924
  let names;
21815
21925
  try {
21816
- names = readdirSync33(dir);
21926
+ names = readdirSync32(dir);
21817
21927
  } catch {
21818
21928
  continue;
21819
21929
  }
@@ -23672,7 +23782,7 @@ async function runPull(base2, opts) {
23672
23782
  return r.title === opts.name;
23673
23783
  }
23674
23784
  }) : rows;
23675
- const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted });
23785
+ const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted, explicit: opts.name ? [opts.name] : void 0 });
23676
23786
  const written = report.outcomes.filter((o) => o.status === "written");
23677
23787
  const failed = report.outcomes.filter((o) => o.status === "failed");
23678
23788
  return {
@@ -23730,7 +23840,7 @@ try {
23730
23840
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23731
23841
  } catch {
23732
23842
  try {
23733
- if (true) ownVersion2 = "0.7.3";
23843
+ if (true) ownVersion2 = "0.7.5";
23734
23844
  } catch {
23735
23845
  }
23736
23846
  }
@@ -24115,7 +24225,7 @@ async function runCollectorMigration(opts) {
24115
24225
  init_sync_ledger();
24116
24226
  init_collector_health();
24117
24227
  import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync45 } from "fs";
24118
- var CLI_VERSION3 = true ? "0.7.3" : "0.0.0";
24228
+ var CLI_VERSION3 = true ? "0.7.5" : "0.0.0";
24119
24229
  var DEBOUNCE_MS = 5e3;
24120
24230
  var CLAUDE_DIR = claudeBackend.projectsDir();
24121
24231
  var PLANS_DIR = claudeBackend.plansDir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"