chat-recall 0.7.2 → 0.7.4

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.2";
18122
+ if (true) CLI_VERSION = "0.7.4";
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.2" : "dev";
18617
+ return true ? "0.7.4" : "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";
@@ -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++;
@@ -19054,7 +19102,7 @@ var init_auto_update = __esm({
19054
19102
 
19055
19103
  // src/sync-client.ts
19056
19104
  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";
19105
+ 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
19106
  import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
19059
19107
  import { createHash as createHash8 } from "node:crypto";
19060
19108
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -19401,7 +19449,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
19401
19449
  let reaped = 0;
19402
19450
  let names;
19403
19451
  try {
19404
- names = readdirSync32(dir);
19452
+ names = readdirSync31(dir);
19405
19453
  } catch {
19406
19454
  return 0;
19407
19455
  }
@@ -20037,17 +20085,24 @@ async function syncToTargetInScope(cred, opts = {}) {
20037
20085
  const seenTools = /* @__PURE__ */ new Set();
20038
20086
  const seenItemKeys = /* @__PURE__ */ new Map();
20039
20087
  const registry = buildSourceRegistry();
20088
+ const inventory = /* @__PURE__ */ new Map();
20089
+ const inventoryFailed = /* @__PURE__ */ new Set();
20040
20090
  for (const sourceType of registry.getRegisteredTypes()) {
20041
20091
  if (sourceType === "session") continue;
20092
+ if (TOOLKIT_INVENTORY_TYPES.has(sourceType)) inventory.set(sourceType, /* @__PURE__ */ new Set());
20042
20093
  for (const source of registry.getAll(sourceType)) {
20043
20094
  let discovered;
20044
20095
  try {
20045
20096
  discovered = source.discover();
20046
20097
  } catch {
20098
+ inventoryFailed.add(sourceType);
20047
20099
  continue;
20048
20100
  }
20049
20101
  try {
20050
20102
  for await (const item of discovered) {
20103
+ if (inventory.has(item.sourceType) && !excluded(item.projectPath || "") && includedProject(item.projectPath || "")) {
20104
+ inventory.get(item.sourceType).add(item.id);
20105
+ }
20051
20106
  const itemTool = toolOfId(item.id);
20052
20107
  seenTools.add(itemTool);
20053
20108
  const itemKey = `${itemTool}:${item.sourceType}`;
@@ -20105,6 +20160,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20105
20160
  redactions += count.redactions;
20106
20161
  }
20107
20162
  } catch (err) {
20163
+ inventoryFailed.add(sourceType);
20108
20164
  console.error(`[sync] source '${sourceType}' failed mid-walk (its items may be incomplete this run): ${err instanceof Error ? err.message : err}`);
20109
20165
  }
20110
20166
  }
@@ -20131,6 +20187,27 @@ async function syncToTargetInScope(cred, opts = {}) {
20131
20187
  await kgTripleBatch.drain();
20132
20188
  await findingsBatch.drain();
20133
20189
  await drainInflight();
20190
+ {
20191
+ const sentHashes = loadInventoryHashes(base2);
20192
+ const entries = [];
20193
+ const hashes = {};
20194
+ for (const [sourceType, ids] of inventory) {
20195
+ if (inventoryFailed.has(sourceType)) continue;
20196
+ const sorted = [...ids].sort();
20197
+ const hash = createHash8("sha256").update(sorted.join("\n")).digest("hex").slice(0, 32);
20198
+ if (sentHashes[sourceType] === hash) continue;
20199
+ entries.push({ source_type: sourceType, ids: sorted });
20200
+ hashes[sourceType] = hash;
20201
+ }
20202
+ if (entries.length > 0) {
20203
+ try {
20204
+ await post({ inventory: entries });
20205
+ saveInventoryHashes(base2, hashes);
20206
+ } catch (err) {
20207
+ console.error(`[sync] toolkit inventory not sent (it is retried next sync): ${err instanceof Error ? err.message : err}`);
20208
+ }
20209
+ }
20210
+ }
20134
20211
  if (seenTools.size > 0) {
20135
20212
  try {
20136
20213
  const rec = {};
@@ -20576,7 +20653,7 @@ async function syncIncremental(opts = {}) {
20576
20653
  lock.release();
20577
20654
  }
20578
20655
  }
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;
20656
+ 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
20657
  var init_sync_client = __esm({
20581
20658
  "src/sync-client.ts"() {
20582
20659
  "use strict";
@@ -20621,7 +20698,7 @@ var init_sync_client = __esm({
20621
20698
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20622
20699
  } catch {
20623
20700
  try {
20624
- if (true) ownVersion = "0.7.2";
20701
+ if (true) ownVersion = "0.7.4";
20625
20702
  } catch {
20626
20703
  }
20627
20704
  }
@@ -20650,6 +20727,7 @@ var init_sync_client = __esm({
20650
20727
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
20651
20728
  DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
20652
20729
  SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
20730
+ TOOLKIT_INVENTORY_TYPES = /* @__PURE__ */ new Set(["skill", "mcp", "command", "agent", "plugin"]);
20653
20731
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
20654
20732
  }
20655
20733
  });
@@ -21284,6 +21362,7 @@ __export(toolkit_pull_exports, {
21284
21362
  entryFromSpec: () => entryFromSpec,
21285
21363
  executePull: () => executePull,
21286
21364
  planPull: () => planPull,
21365
+ platformRefusal: () => platformRefusal,
21287
21366
  portableCommand: () => portableCommand
21288
21367
  });
21289
21368
  import { existsSync as existsSync45, mkdirSync as mkdirSync13, statSync as statSync40, writeFileSync as writeFileSync12 } from "node:fs";
@@ -21341,6 +21420,7 @@ function planPull(rows, opts = {}) {
21341
21420
  const wanted = opts.types;
21342
21421
  const seenUnsupported = /* @__PURE__ */ new Map();
21343
21422
  const byName = /* @__PURE__ */ new Map();
21423
+ const platformsByName = /* @__PURE__ */ new Map();
21344
21424
  const skillsByName = /* @__PURE__ */ new Map();
21345
21425
  const agentsByName = /* @__PURE__ */ new Map();
21346
21426
  const commandsByName = /* @__PURE__ */ new Map();
@@ -21404,6 +21484,9 @@ function planPull(rows, opts = {}) {
21404
21484
  const name = extra.mcpName || row.title;
21405
21485
  if (!name)
21406
21486
  continue;
21487
+ if (typeof extra.platform === "string" && extra.platform) {
21488
+ (platformsByName.get(name) ?? platformsByName.set(name, /* @__PURE__ */ new Set()).get(name)).add(extra.platform);
21489
+ }
21407
21490
  const spec = extra.spec || null;
21408
21491
  if (!spec)
21409
21492
  continue;
@@ -21413,7 +21496,12 @@ function planPull(rows, opts = {}) {
21413
21496
  byName.set(name, spec);
21414
21497
  }
21415
21498
  const targets = SUPPORTED_TARGETS.mcp;
21416
- const mcps = [...byName.entries()].map(([name, spec]) => ({ name, spec, tools: targets }));
21499
+ const mcps = [...byName.entries()].map(([name, spec]) => ({
21500
+ name,
21501
+ spec,
21502
+ tools: targets,
21503
+ platforms: [...platformsByName.get(name) ?? []].sort()
21504
+ }));
21417
21505
  const skills = [...skillsByName.entries()].map(([name, v]) => ({
21418
21506
  name,
21419
21507
  body: v.body,
@@ -21432,7 +21520,18 @@ function planPull(rows, opts = {}) {
21432
21520
  ];
21433
21521
  return { mcps, skills, codecs, unsupported };
21434
21522
  }
21523
+ function platformRefusal(name, platforms, here, explicit) {
21524
+ if (explicit.has(name) || platforms.includes(here))
21525
+ return null;
21526
+ const hint = `install it by name if it works on ${here}: chat-recall toolkit pull --mcp ${name}`;
21527
+ if (platforms.length === 0) {
21528
+ return `no device recorded its platform \u2014 re-sync from the device that has it, or ${hint}`;
21529
+ }
21530
+ return `registered only on ${platforms.join(", ")} \u2014 ${hint}`;
21531
+ }
21435
21532
  function executePull(rows, opts = {}) {
21533
+ const here = opts.platform ?? process.platform;
21534
+ const explicit = new Set(opts.explicit ?? []);
21436
21535
  const { mcps, skills, codecs, unsupported } = planPull(rows, opts);
21437
21536
  const outcomes = [];
21438
21537
  for (const { name, body, truncated, redacted, tools } of skills) {
@@ -21501,8 +21600,9 @@ function executePull(rows, opts = {}) {
21501
21600
  }
21502
21601
  }
21503
21602
  }
21504
- for (const { name, spec, tools } of mcps) {
21603
+ for (const { name, spec, tools, platforms } of mcps) {
21505
21604
  const built = entryFromSpec(spec);
21605
+ const refusal = platformRefusal(name, platforms, here, explicit);
21506
21606
  for (const tool of tools) {
21507
21607
  if (!built) {
21508
21608
  outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: "stored row has no rebuildable spec (re-index on the source device)" });
@@ -21512,6 +21612,10 @@ function executePull(rows, opts = {}) {
21512
21612
  outcomes.push({ type: "mcp", name, tool, status: "present" });
21513
21613
  continue;
21514
21614
  }
21615
+ if (refusal) {
21616
+ outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: refusal });
21617
+ continue;
21618
+ }
21515
21619
  if (spec.secretsRedacted) {
21516
21620
  outcomes.push({
21517
21621
  type: "mcp",
@@ -21798,14 +21902,14 @@ __export(shadow_prune_exports, {
21798
21902
  DEFAULT_MAX_TOTAL_BYTES: () => DEFAULT_MAX_TOTAL_BYTES,
21799
21903
  pruneShadow: () => pruneShadow
21800
21904
  });
21801
- import { readdirSync as readdirSync33, statSync as statSync43, unlinkSync as unlinkSync3 } from "fs";
21905
+ import { readdirSync as readdirSync32, statSync as statSync43, unlinkSync as unlinkSync3 } from "fs";
21802
21906
  import { join as join50 } from "path";
21803
21907
  function listShadows(isFullyAcked) {
21804
21908
  const out = [];
21805
21909
  const root = shadowRoot();
21806
21910
  let tools;
21807
21911
  try {
21808
- tools = readdirSync33(root);
21912
+ tools = readdirSync32(root);
21809
21913
  } catch {
21810
21914
  return out;
21811
21915
  }
@@ -21813,7 +21917,7 @@ function listShadows(isFullyAcked) {
21813
21917
  const dir = join50(root, tool);
21814
21918
  let names;
21815
21919
  try {
21816
- names = readdirSync33(dir);
21920
+ names = readdirSync32(dir);
21817
21921
  } catch {
21818
21922
  continue;
21819
21923
  }
@@ -23672,7 +23776,7 @@ async function runPull(base2, opts) {
23672
23776
  return r.title === opts.name;
23673
23777
  }
23674
23778
  }) : rows;
23675
- const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted });
23779
+ const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted, explicit: opts.name ? [opts.name] : void 0 });
23676
23780
  const written = report.outcomes.filter((o) => o.status === "written");
23677
23781
  const failed = report.outcomes.filter((o) => o.status === "failed");
23678
23782
  return {
@@ -23730,7 +23834,7 @@ try {
23730
23834
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23731
23835
  } catch {
23732
23836
  try {
23733
- if (true) ownVersion2 = "0.7.2";
23837
+ if (true) ownVersion2 = "0.7.4";
23734
23838
  } catch {
23735
23839
  }
23736
23840
  }
@@ -24115,7 +24219,7 @@ async function runCollectorMigration(opts) {
24115
24219
  init_sync_ledger();
24116
24220
  init_collector_health();
24117
24221
  import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync45 } from "fs";
24118
- var CLI_VERSION3 = true ? "0.7.2" : "0.0.0";
24222
+ var CLI_VERSION3 = true ? "0.7.4" : "0.0.0";
24119
24223
  var DEBOUNCE_MS = 5e3;
24120
24224
  var CLAUDE_DIR = claudeBackend.projectsDir();
24121
24225
  var PLANS_DIR = claudeBackend.plansDir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"