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/cli.js CHANGED
@@ -6689,7 +6689,7 @@ async function archiveRawSession(store, sessionId) {
6689
6689
  return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
6690
6690
  }
6691
6691
  function detectForkPredecessor(sessionPath) {
6692
- const { readFileSync: readFileSync50, readdirSync: readdirSync35 } = requireFs();
6692
+ const { readFileSync: readFileSync50, readdirSync: readdirSync34 } = requireFs();
6693
6693
  const { dirname: dirname30, join: join62, basename: basename28 } = requirePath();
6694
6694
  let text;
6695
6695
  try {
@@ -6719,7 +6719,7 @@ function detectForkPredecessor(sessionPath) {
6719
6719
  const self = basename28(sessionPath);
6720
6720
  let entries;
6721
6721
  try {
6722
- entries = readdirSync35(dir);
6722
+ entries = readdirSync34(dir);
6723
6723
  } catch {
6724
6724
  return null;
6725
6725
  }
@@ -8350,8 +8350,35 @@ var init_codex2 = __esm({
8350
8350
  configToml() {
8351
8351
  return join9(this.homeDir(), "config.toml");
8352
8352
  }
8353
- pluginsDir() {
8354
- return join9(this.homeDir(), ".tmp", "plugins", "plugins");
8353
+ /**
8354
+ * Plugins that are INSTALLED, one folder each:
8355
+ * `plugins/cache/<marketplace>/<plugin>/<version>/`, newest version.
8356
+ *
8357
+ * `.tmp/plugins/plugins/` is Codex's copy of the plugin catalog (62 plugins
8358
+ * and a README on one machine), not what is installed. Reading it reported
8359
+ * every catalog plugin's skills and MCP servers as the user's own, and a
8360
+ * pull then installed them into every tool: build-ios-apps' xcodebuildmcp
8361
+ * reached a Linux PC that never installed the plugin.
8362
+ */
8363
+ installedPluginDirs() {
8364
+ const cache4 = join9(this.homeDir(), "plugins", "cache");
8365
+ const out = [];
8366
+ const dirs = (p) => {
8367
+ try {
8368
+ return readdirSync6(p, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
8369
+ } catch {
8370
+ return [];
8371
+ }
8372
+ };
8373
+ for (const market of dirs(cache4)) {
8374
+ for (const plugin of dirs(join9(cache4, market))) {
8375
+ const versions = dirs(join9(cache4, market, plugin)).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
8376
+ const newest = versions[versions.length - 1];
8377
+ if (newest)
8378
+ out.push({ name: plugin, dir: join9(cache4, market, plugin, newest) });
8379
+ }
8380
+ }
8381
+ return out;
8355
8382
  }
8356
8383
  /** OpenAI-bundled, read-only system skills (imagegen, skill-creator, …). */
8357
8384
  skillsSystemDir() {
@@ -17013,14 +17040,11 @@ var init_skills_source = __esm({
17013
17040
  }
17014
17041
  if (!isSourceEnabled("codex", "skills"))
17015
17042
  return;
17016
- const codexPluginsDir = codexBackend.pluginsDir();
17017
- if (existsSync33(codexPluginsDir)) {
17018
- for (const pluginName of readdirSync25(codexPluginsDir)) {
17019
- const skillsDir = join32(codexPluginsDir, pluginName, "skills");
17020
- if (!existsSync33(skillsDir))
17021
- continue;
17022
- yield* this.fromSkillRoot({ path: skillsDir, tool: "codex" }, pluginName);
17023
- }
17043
+ for (const { name: pluginName, dir } of codexBackend.installedPluginDirs()) {
17044
+ const skillsDir = join32(dir, "skills");
17045
+ if (!existsSync33(skillsDir))
17046
+ continue;
17047
+ yield* this.fromSkillRoot({ path: skillsDir, tool: "codex" }, pluginName);
17024
17048
  }
17025
17049
  }
17026
17050
  async *fromSkillRoot(root, pluginName) {
@@ -17133,7 +17157,7 @@ var init_skills_source = __esm({
17133
17157
  });
17134
17158
 
17135
17159
  // ../engine/dist/parsers/mcps-source.js
17136
- import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
17160
+ import { existsSync as existsSync34, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
17137
17161
  import { homedir as homedir13 } from "os";
17138
17162
  import { join as join33 } from "path";
17139
17163
  function readJson(path) {
@@ -17245,16 +17269,13 @@ var init_mcps_source = __esm({
17245
17269
  scope: "user"
17246
17270
  });
17247
17271
  yield* this.fromCodexToml(codexBackend.configToml());
17248
- const pluginsDir = codexBackend.pluginsDir();
17249
- if (existsSync34(pluginsDir)) {
17250
- for (const plugin of readdirSync26(pluginsDir)) {
17251
- const mcpPath = join33(pluginsDir, plugin, ".mcp.json");
17252
- yield* this.fromObject(readJson(mcpPath)?.mcpServers, {
17253
- tool: "codex",
17254
- filePath: mcpPath,
17255
- scope: "plugin"
17256
- });
17257
- }
17272
+ for (const { dir } of codexBackend.installedPluginDirs()) {
17273
+ const mcpPath = join33(dir, ".mcp.json");
17274
+ yield* this.fromObject(readJson(mcpPath)?.mcpServers, {
17275
+ tool: "codex",
17276
+ filePath: mcpPath,
17277
+ scope: "plugin"
17278
+ });
17258
17279
  }
17259
17280
  }
17260
17281
  async *fromObject(obj, ctx) {
@@ -17292,7 +17313,12 @@ var init_mcps_source = __esm({
17292
17313
  // MCP's url into a shell string, and `alwaysAllow` is truncated to
17293
17314
  // 8 for the preview — rebuilding from either produces a broken or
17294
17315
  // half-permissioned server, silently.
17295
- spec
17316
+ spec,
17317
+ // Where this registration runs. A server that starts through npx
17318
+ // passes the command check on any machine, so this is what tells a
17319
+ // pull that it came from another platform (see toolkit-pull.ts).
17320
+ platform: process.platform,
17321
+ arch: process.arch
17296
17322
  }
17297
17323
  };
17298
17324
  }
@@ -17471,7 +17497,7 @@ var init_toolkit_format = __esm({
17471
17497
  });
17472
17498
 
17473
17499
  // ../engine/dist/parsers/slash-commands-source.js
17474
- import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
17500
+ import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
17475
17501
  import { join as join34, basename as basename18 } from "path";
17476
17502
  function rebuildBody2(content) {
17477
17503
  const { text, redacted } = redactInlineSecrets(content);
@@ -17526,7 +17552,7 @@ var init_slash_commands_source = __esm({
17526
17552
  const projectsRoot = claudeBackend.projectsDir();
17527
17553
  if (existsSync35(projectsRoot)) {
17528
17554
  try {
17529
- for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
17555
+ for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
17530
17556
  if (!entry.isDirectory() || entry.name.startsWith("."))
17531
17557
  continue;
17532
17558
  const projectPath = resolveProjectDirName(entry.name);
@@ -17558,7 +17584,7 @@ var init_slash_commands_source = __esm({
17558
17584
  const ext = root.format === "toml" ? ".toml" : ".md";
17559
17585
  let files;
17560
17586
  try {
17561
- files = readdirSync27(root.path).filter((f) => f.endsWith(ext));
17587
+ files = readdirSync26(root.path).filter((f) => f.endsWith(ext));
17562
17588
  } catch {
17563
17589
  continue;
17564
17590
  }
@@ -17618,7 +17644,7 @@ var init_slash_commands_source = __esm({
17618
17644
  });
17619
17645
 
17620
17646
  // ../engine/dist/parsers/subagents-source.js
17621
- import { existsSync as existsSync36, readdirSync as readdirSync28, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
17647
+ import { existsSync as existsSync36, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
17622
17648
  import { join as join35, basename as basename19 } from "path";
17623
17649
  function rebuildBody3(content) {
17624
17650
  const { text, redacted } = redactInlineSecrets(content);
@@ -17675,7 +17701,7 @@ var init_subagents_source = __esm({
17675
17701
  const projectsRoot = claudeBackend.projectsDir();
17676
17702
  if (existsSync36(projectsRoot)) {
17677
17703
  try {
17678
- for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
17704
+ for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
17679
17705
  if (!entry.isDirectory() || entry.name.startsWith("."))
17680
17706
  continue;
17681
17707
  const projectPath = resolveProjectDirName(entry.name);
@@ -17707,7 +17733,7 @@ var init_subagents_source = __esm({
17707
17733
  const ext = root.format === "toml" ? ".toml" : ".md";
17708
17734
  let files;
17709
17735
  try {
17710
- files = readdirSync28(root.path).filter((f) => f.endsWith(ext));
17736
+ files = readdirSync27(root.path).filter((f) => f.endsWith(ext));
17711
17737
  } catch {
17712
17738
  continue;
17713
17739
  }
@@ -17768,7 +17794,7 @@ var init_subagents_source = __esm({
17768
17794
  });
17769
17795
 
17770
17796
  // ../engine/dist/parsers/hooks-source.js
17771
- import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17797
+ import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync28, statSync as statSync33 } from "fs";
17772
17798
  import { join as join36 } from "path";
17773
17799
  function readJson2(path) {
17774
17800
  if (!existsSync37(path))
@@ -17795,7 +17821,7 @@ var init_hooks_source = __esm({
17795
17821
  const projectsRoot = claudeBackend.projectsDir();
17796
17822
  if (existsSync37(projectsRoot)) {
17797
17823
  try {
17798
- for (const entry of readdirSync29(projectsRoot, { withFileTypes: true })) {
17824
+ for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
17799
17825
  if (!entry.isDirectory() || entry.name.startsWith("."))
17800
17826
  continue;
17801
17827
  const projectPath = resolveProjectDirName(entry.name);
@@ -17891,7 +17917,7 @@ var init_hooks_source = __esm({
17891
17917
  });
17892
17918
 
17893
17919
  // ../engine/dist/parsers/plugins-source.js
17894
- import { existsSync as existsSync38, readFileSync as readFileSync28, readdirSync as readdirSync30, statSync as statSync34 } from "fs";
17920
+ import { existsSync as existsSync38, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync34 } from "fs";
17895
17921
  import { homedir as homedir14 } from "os";
17896
17922
  import { join as join37 } from "path";
17897
17923
  function readJson3(path) {
@@ -17953,7 +17979,7 @@ var init_plugins_source = __esm({
17953
17979
  if (isSourceEnabled("agy", "extensions") && existsSync38(gemRoot)) {
17954
17980
  let entries;
17955
17981
  try {
17956
- entries = readdirSync30(gemRoot);
17982
+ entries = readdirSync29(gemRoot);
17957
17983
  } catch {
17958
17984
  entries = [];
17959
17985
  }
@@ -17997,16 +18023,8 @@ var init_plugins_source = __esm({
17997
18023
  };
17998
18024
  }
17999
18025
  }
18000
- const codexPluginsRoot = codexBackend.pluginsDir();
18001
- if (isSourceEnabled("codex", "plugins") && existsSync38(codexPluginsRoot)) {
18002
- let entries;
18003
- try {
18004
- entries = readdirSync30(codexPluginsRoot);
18005
- } catch {
18006
- entries = [];
18007
- }
18008
- for (const name of entries) {
18009
- const pluginDir = join37(codexPluginsRoot, name);
18026
+ if (isSourceEnabled("codex", "plugins")) {
18027
+ for (const { name, dir: pluginDir } of codexBackend.installedPluginDirs()) {
18010
18028
  let st;
18011
18029
  try {
18012
18030
  st = statSync34(pluginDir);
@@ -18120,7 +18138,8 @@ var init_all_sources = __esm({
18120
18138
 
18121
18139
  // ../engine/dist/core/extractor-version.js
18122
18140
  function extractorVersionForItem(id, sourceType) {
18123
- return extractorVersionForTool(toolOfId(id)) + (ITEM_SOURCE_BUMP[sourceType] ?? 0);
18141
+ const tool = toolOfId(id);
18142
+ return extractorVersionForTool(tool) + (ITEM_SOURCE_BUMP[sourceType] ?? 0) + (ITEM_TOOL_SOURCE_BUMP[`${tool}:${sourceType}`] ?? 0);
18124
18143
  }
18125
18144
  function toolOfId(id) {
18126
18145
  if (id.startsWith("agy_"))
@@ -18141,17 +18160,21 @@ function extractorVersionForTool(tool) {
18141
18160
  function extractorVersionForId(id) {
18142
18161
  return extractorVersionForTool(toolOfId(id));
18143
18162
  }
18144
- var BASE_EXTRACTOR_VERSION, ITEM_SOURCE_BUMP, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
18163
+ var BASE_EXTRACTOR_VERSION, ITEM_SOURCE_BUMP, ITEM_TOOL_SOURCE_BUMP, TOOL_EXTRACTOR_BUMP, EXTRACTOR_VERSION;
18145
18164
  var init_extractor_version = __esm({
18146
18165
  "../engine/dist/core/extractor-version.js"() {
18147
18166
  "use strict";
18148
18167
  BASE_EXTRACTOR_VERSION = 2;
18149
18168
  ITEM_SOURCE_BUMP = {
18150
- mcp: 1,
18169
+ mcp: 2,
18151
18170
  skill: 1,
18152
18171
  agent: 1,
18153
18172
  command: 1
18154
18173
  };
18174
+ ITEM_TOOL_SOURCE_BUMP = {
18175
+ "codex:skill": 1,
18176
+ "codex:plugin": 1
18177
+ };
18155
18178
  TOOL_EXTRACTOR_BUMP = {
18156
18179
  agy: 3,
18157
18180
  claude: 1,
@@ -18263,6 +18286,28 @@ function saveItemVersions(server, versions) {
18263
18286
  writeFileSync5(tmp, JSON.stringify(all));
18264
18287
  renameSync4(tmp, path);
18265
18288
  }
18289
+ function loadInventoryHashes(server) {
18290
+ try {
18291
+ const parsed = JSON.parse(readFileSync29(inventoryPath(), "utf-8"));
18292
+ return { ...(parsed && typeof parsed === "object" ? parsed[server] : null) || {} };
18293
+ } catch {
18294
+ return {};
18295
+ }
18296
+ }
18297
+ function saveInventoryHashes(server, hashes) {
18298
+ let all = {};
18299
+ try {
18300
+ all = JSON.parse(readFileSync29(inventoryPath(), "utf-8"));
18301
+ } catch {
18302
+ }
18303
+ if (!all || typeof all !== "object") all = {};
18304
+ all[server] = { ...all[server] || {}, ...hashes };
18305
+ const path = inventoryPath();
18306
+ mkdirSync6(dirname15(path), { recursive: true });
18307
+ const tmp = `${path}.tmp`;
18308
+ writeFileSync5(tmp, JSON.stringify(all));
18309
+ renameSync4(tmp, path);
18310
+ }
18266
18311
  function getSyncedRows(server) {
18267
18312
  const data = load();
18268
18313
  const out = /* @__PURE__ */ new Map();
@@ -18374,7 +18419,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
18374
18419
  if (row.m >= Math.floor(mtime)) return "skip";
18375
18420
  return "full";
18376
18421
  }
18377
- var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
18422
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, inventoryPath, unackedCursorAttempts, reconcilePath, reconcileCache;
18378
18423
  var init_sync_ledger = __esm({
18379
18424
  "src/sync-ledger.ts"() {
18380
18425
  "use strict";
@@ -18387,6 +18432,7 @@ var init_sync_ledger = __esm({
18387
18432
  flushTimer = null;
18388
18433
  itemVersionsPath = () => join38(getDataDir(), "item-versions.json");
18389
18434
  itemVersionsCache = null;
18435
+ inventoryPath = () => join38(getDataDir(), "toolkit-inventory.json");
18390
18436
  unackedCursorAttempts = 0;
18391
18437
  reconcilePath = () => join38(getDataDir(), "sync-field-reconcile.json");
18392
18438
  reconcileCache = null;
@@ -18541,7 +18587,7 @@ var init_http = __esm({
18541
18587
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18542
18588
  } catch {
18543
18589
  try {
18544
- if (true) CLI_VERSION = "0.7.2";
18590
+ if (true) CLI_VERSION = "0.7.4";
18545
18591
  } catch {
18546
18592
  }
18547
18593
  }
@@ -19040,7 +19086,7 @@ var init_telemetry = __esm({
19040
19086
  init_sync_client();
19041
19087
  cliVersion2 = () => {
19042
19088
  try {
19043
- return true ? "0.7.2" : "dev";
19089
+ return true ? "0.7.4" : "dev";
19044
19090
  } catch {
19045
19091
  return "dev";
19046
19092
  }
@@ -19405,7 +19451,7 @@ __export(auto_update_exports, {
19405
19451
  import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
19406
19452
  import { readFileSync as readFileSync34, existsSync as existsSync41 } from "node:fs";
19407
19453
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19408
- import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync36, mkdirSync as mkdirSync9 } from "node:fs";
19454
+ import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync30, statSync as statSync36, mkdirSync as mkdirSync9 } from "node:fs";
19409
19455
  import { join as join41, dirname as dirname18, sep as sep2, posix as pathPosix, win32 as pathWin32 } from "node:path";
19410
19456
  import { tmpdir as tmpdir2, homedir as homedir15 } from "node:os";
19411
19457
  import { createHash as createHash8 } from "node:crypto";
@@ -19450,7 +19496,7 @@ function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
19450
19496
  let removed = 0;
19451
19497
  let entries;
19452
19498
  try {
19453
- entries = readdirSync31(root);
19499
+ entries = readdirSync30(root);
19454
19500
  } catch {
19455
19501
  return 0;
19456
19502
  }
@@ -19460,7 +19506,7 @@ function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
19460
19506
  try {
19461
19507
  const st = statSync36(dir);
19462
19508
  if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
19463
- const inside = readdirSync31(dir);
19509
+ const inside = readdirSync30(dir);
19464
19510
  if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
19465
19511
  rmSync5(dir, { recursive: true, force: true });
19466
19512
  removed++;
@@ -19664,6 +19710,7 @@ __export(sync_client_exports, {
19664
19710
  DERIVED_MAX_BYTES: () => DERIVED_MAX_BYTES,
19665
19711
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
19666
19712
  SYNC_CHUNK_BYTES: () => SYNC_CHUNK_BYTES,
19713
+ TOOLKIT_INVENTORY_TYPES: () => TOOLKIT_INVENTORY_TYPES,
19667
19714
  _acceptedUploads: () => _acceptedUploads,
19668
19715
  _fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
19669
19716
  _resetCapsCacheForTests: () => _resetCapsCacheForTests,
@@ -19692,7 +19739,7 @@ __export(sync_client_exports, {
19692
19739
  syncSessions: () => syncSessions
19693
19740
  });
19694
19741
  import { join as join42, dirname as dirname19, basename as basename20 } from "node:path";
19695
- 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";
19742
+ 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";
19696
19743
  import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
19697
19744
  import { createHash as createHash9 } from "node:crypto";
19698
19745
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -20095,7 +20142,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
20095
20142
  let reaped = 0;
20096
20143
  let names;
20097
20144
  try {
20098
- names = readdirSync32(dir);
20145
+ names = readdirSync31(dir);
20099
20146
  } catch {
20100
20147
  return 0;
20101
20148
  }
@@ -20736,17 +20783,24 @@ async function syncToTargetInScope(cred, opts = {}) {
20736
20783
  const seenTools = /* @__PURE__ */ new Set();
20737
20784
  const seenItemKeys = /* @__PURE__ */ new Map();
20738
20785
  const registry = buildSourceRegistry();
20786
+ const inventory = /* @__PURE__ */ new Map();
20787
+ const inventoryFailed = /* @__PURE__ */ new Set();
20739
20788
  for (const sourceType of registry.getRegisteredTypes()) {
20740
20789
  if (sourceType === "session") continue;
20790
+ if (TOOLKIT_INVENTORY_TYPES.has(sourceType)) inventory.set(sourceType, /* @__PURE__ */ new Set());
20741
20791
  for (const source of registry.getAll(sourceType)) {
20742
20792
  let discovered;
20743
20793
  try {
20744
20794
  discovered = source.discover();
20745
20795
  } catch {
20796
+ inventoryFailed.add(sourceType);
20746
20797
  continue;
20747
20798
  }
20748
20799
  try {
20749
20800
  for await (const item of discovered) {
20801
+ if (inventory.has(item.sourceType) && !excluded(item.projectPath || "") && includedProject(item.projectPath || "")) {
20802
+ inventory.get(item.sourceType).add(item.id);
20803
+ }
20750
20804
  const itemTool = toolOfId(item.id);
20751
20805
  seenTools.add(itemTool);
20752
20806
  const itemKey = `${itemTool}:${item.sourceType}`;
@@ -20804,6 +20858,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20804
20858
  redactions += count.redactions;
20805
20859
  }
20806
20860
  } catch (err) {
20861
+ inventoryFailed.add(sourceType);
20807
20862
  console.error(`[sync] source '${sourceType}' failed mid-walk (its items may be incomplete this run): ${err instanceof Error ? err.message : err}`);
20808
20863
  }
20809
20864
  }
@@ -20830,6 +20885,27 @@ async function syncToTargetInScope(cred, opts = {}) {
20830
20885
  await kgTripleBatch.drain();
20831
20886
  await findingsBatch.drain();
20832
20887
  await drainInflight();
20888
+ {
20889
+ const sentHashes = loadInventoryHashes(base3);
20890
+ const entries = [];
20891
+ const hashes = {};
20892
+ for (const [sourceType, ids] of inventory) {
20893
+ if (inventoryFailed.has(sourceType)) continue;
20894
+ const sorted = [...ids].sort();
20895
+ const hash = createHash9("sha256").update(sorted.join("\n")).digest("hex").slice(0, 32);
20896
+ if (sentHashes[sourceType] === hash) continue;
20897
+ entries.push({ source_type: sourceType, ids: sorted });
20898
+ hashes[sourceType] = hash;
20899
+ }
20900
+ if (entries.length > 0) {
20901
+ try {
20902
+ await post({ inventory: entries });
20903
+ saveInventoryHashes(base3, hashes);
20904
+ } catch (err) {
20905
+ console.error(`[sync] toolkit inventory not sent (it is retried next sync): ${err instanceof Error ? err.message : err}`);
20906
+ }
20907
+ }
20908
+ }
20833
20909
  if (seenTools.size > 0) {
20834
20910
  try {
20835
20911
  const rec = {};
@@ -21275,7 +21351,7 @@ async function syncIncremental(opts = {}) {
21275
21351
  lock.release();
21276
21352
  }
21277
21353
  }
21278
- var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, 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;
21354
+ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, 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;
21279
21355
  var init_sync_client = __esm({
21280
21356
  "src/sync-client.ts"() {
21281
21357
  "use strict";
@@ -21320,7 +21396,7 @@ var init_sync_client = __esm({
21320
21396
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
21321
21397
  } catch {
21322
21398
  try {
21323
- if (true) ownVersion = "0.7.2";
21399
+ if (true) ownVersion = "0.7.4";
21324
21400
  } catch {
21325
21401
  }
21326
21402
  }
@@ -21350,6 +21426,7 @@ var init_sync_client = __esm({
21350
21426
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
21351
21427
  DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
21352
21428
  SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
21429
+ TOOLKIT_INVENTORY_TYPES = /* @__PURE__ */ new Set(["skill", "mcp", "command", "agent", "plugin"]);
21353
21430
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
21354
21431
  }
21355
21432
  });
@@ -22004,7 +22081,7 @@ __export(install_skills_exports, {
22004
22081
  skillsSourceDir: () => skillsSourceDir,
22005
22082
  uninstallSkills: () => uninstallSkills
22006
22083
  });
22007
- import { readdirSync as readdirSync33, existsSync as existsSync46, mkdirSync as mkdirSync14, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync14, readFileSync as readFileSync39, statSync as statSync40, realpathSync as realpathSync4 } from "fs";
22084
+ import { readdirSync as readdirSync32, existsSync as existsSync46, mkdirSync as mkdirSync14, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync14, readFileSync as readFileSync39, statSync as statSync40, realpathSync as realpathSync4 } from "fs";
22008
22085
  import { createHash as createHash11 } from "crypto";
22009
22086
  import { join as join48, basename as basename21 } from "path";
22010
22087
  function skillsSourceDir() {
@@ -22030,7 +22107,7 @@ function skillsFingerprint() {
22030
22107
  }
22031
22108
  function filesUnder(dir, prefix = "") {
22032
22109
  const out = [];
22033
- for (const e of readdirSync33(dir, { withFileTypes: true })) {
22110
+ for (const e of readdirSync32(dir, { withFileTypes: true })) {
22034
22111
  if (e.name === MARKER) continue;
22035
22112
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
22036
22113
  if (e.isDirectory()) out.push(...filesUnder(join48(dir, e.name), rel));
@@ -22044,7 +22121,7 @@ function skillStamp() {
22044
22121
  function bundledSkillNames() {
22045
22122
  const src = skillsSourceDir();
22046
22123
  if (!existsSync46(src)) return [];
22047
- return readdirSync33(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join48(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22124
+ return readdirSync32(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join48(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22048
22125
  }
22049
22126
  function realPathOrSelf(p) {
22050
22127
  try {
@@ -22134,7 +22211,7 @@ function uninstallSkills() {
22134
22211
  perTarget.push({ id: t.id, label: t.label, removed });
22135
22212
  continue;
22136
22213
  }
22137
- for (const entry of readdirSync33(t.dir, { withFileTypes: true })) {
22214
+ for (const entry of readdirSync32(t.dir, { withFileTypes: true })) {
22138
22215
  if (!entry.isDirectory()) continue;
22139
22216
  const dir = join48(t.dir, entry.name);
22140
22217
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
@@ -22808,6 +22885,7 @@ __export(toolkit_pull_exports, {
22808
22885
  entryFromSpec: () => entryFromSpec,
22809
22886
  executePull: () => executePull,
22810
22887
  planPull: () => planPull,
22888
+ platformRefusal: () => platformRefusal,
22811
22889
  portableCommand: () => portableCommand
22812
22890
  });
22813
22891
  import { existsSync as existsSync48, mkdirSync as mkdirSync16, statSync as statSync41, writeFileSync as writeFileSync16 } from "node:fs";
@@ -22865,6 +22943,7 @@ function planPull(rows, opts = {}) {
22865
22943
  const wanted = opts.types;
22866
22944
  const seenUnsupported = /* @__PURE__ */ new Map();
22867
22945
  const byName = /* @__PURE__ */ new Map();
22946
+ const platformsByName = /* @__PURE__ */ new Map();
22868
22947
  const skillsByName = /* @__PURE__ */ new Map();
22869
22948
  const agentsByName = /* @__PURE__ */ new Map();
22870
22949
  const commandsByName = /* @__PURE__ */ new Map();
@@ -22928,6 +23007,9 @@ function planPull(rows, opts = {}) {
22928
23007
  const name = extra.mcpName || row.title;
22929
23008
  if (!name)
22930
23009
  continue;
23010
+ if (typeof extra.platform === "string" && extra.platform) {
23011
+ (platformsByName.get(name) ?? platformsByName.set(name, /* @__PURE__ */ new Set()).get(name)).add(extra.platform);
23012
+ }
22931
23013
  const spec = extra.spec || null;
22932
23014
  if (!spec)
22933
23015
  continue;
@@ -22937,7 +23019,12 @@ function planPull(rows, opts = {}) {
22937
23019
  byName.set(name, spec);
22938
23020
  }
22939
23021
  const targets = SUPPORTED_TARGETS.mcp;
22940
- const mcps = [...byName.entries()].map(([name, spec]) => ({ name, spec, tools: targets }));
23022
+ const mcps = [...byName.entries()].map(([name, spec]) => ({
23023
+ name,
23024
+ spec,
23025
+ tools: targets,
23026
+ platforms: [...platformsByName.get(name) ?? []].sort()
23027
+ }));
22941
23028
  const skills = [...skillsByName.entries()].map(([name, v]) => ({
22942
23029
  name,
22943
23030
  body: v.body,
@@ -22956,7 +23043,18 @@ function planPull(rows, opts = {}) {
22956
23043
  ];
22957
23044
  return { mcps, skills, codecs, unsupported };
22958
23045
  }
23046
+ function platformRefusal(name, platforms, here, explicit) {
23047
+ if (explicit.has(name) || platforms.includes(here))
23048
+ return null;
23049
+ const hint = `install it by name if it works on ${here}: chat-recall toolkit pull --mcp ${name}`;
23050
+ if (platforms.length === 0) {
23051
+ return `no device recorded its platform \u2014 re-sync from the device that has it, or ${hint}`;
23052
+ }
23053
+ return `registered only on ${platforms.join(", ")} \u2014 ${hint}`;
23054
+ }
22959
23055
  function executePull(rows, opts = {}) {
23056
+ const here = opts.platform ?? process.platform;
23057
+ const explicit = new Set(opts.explicit ?? []);
22960
23058
  const { mcps, skills, codecs, unsupported } = planPull(rows, opts);
22961
23059
  const outcomes = [];
22962
23060
  for (const { name, body, truncated, redacted, tools } of skills) {
@@ -23025,8 +23123,9 @@ function executePull(rows, opts = {}) {
23025
23123
  }
23026
23124
  }
23027
23125
  }
23028
- for (const { name, spec, tools } of mcps) {
23126
+ for (const { name, spec, tools, platforms } of mcps) {
23029
23127
  const built = entryFromSpec(spec);
23128
+ const refusal = platformRefusal(name, platforms, here, explicit);
23030
23129
  for (const tool of tools) {
23031
23130
  if (!built) {
23032
23131
  outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: "stored row has no rebuildable spec (re-index on the source device)" });
@@ -23036,6 +23135,10 @@ function executePull(rows, opts = {}) {
23036
23135
  outcomes.push({ type: "mcp", name, tool, status: "present" });
23037
23136
  continue;
23038
23137
  }
23138
+ if (refusal) {
23139
+ outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: refusal });
23140
+ continue;
23141
+ }
23039
23142
  if (spec.secretsRedacted) {
23040
23143
  outcomes.push({
23041
23144
  type: "mcp",
@@ -25320,7 +25423,7 @@ async function runPull(base3, opts) {
25320
25423
  return r.title === opts.name;
25321
25424
  }
25322
25425
  }) : rows;
25323
- const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted });
25426
+ const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted, explicit: opts.name ? [opts.name] : void 0 });
25324
25427
  const written = report.outcomes.filter((o) => o.status === "written");
25325
25428
  const failed = report.outcomes.filter((o) => o.status === "failed");
25326
25429
  return {
@@ -25454,7 +25557,7 @@ var init_intent_drain = __esm({
25454
25557
  ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
25455
25558
  } catch {
25456
25559
  try {
25457
- if (true) ownVersion2 = "0.7.2";
25560
+ if (true) ownVersion2 = "0.7.4";
25458
25561
  } catch {
25459
25562
  }
25460
25563
  }
@@ -25876,7 +25979,7 @@ var init_vault_crypto = __esm({
25876
25979
  });
25877
25980
 
25878
25981
  // ../engine/dist/core/vault-source.js
25879
- import { existsSync as existsSync54, readdirSync as readdirSync34, statSync as statSync44 } from "fs";
25982
+ import { existsSync as existsSync54, readdirSync as readdirSync33, statSync as statSync44 } from "fs";
25880
25983
  import { extname, join as join58 } from "path";
25881
25984
  function* walkVaultSources() {
25882
25985
  const t = loadSettings().team.vault;
@@ -25906,7 +26009,7 @@ function* walkClaude() {
25906
26009
  return;
25907
26010
  let projectDirs = [];
25908
26011
  try {
25909
- projectDirs = readdirSync34(root);
26012
+ projectDirs = readdirSync33(root);
25910
26013
  } catch {
25911
26014
  return;
25912
26015
  }
@@ -25917,7 +26020,7 @@ function* walkClaude() {
25917
26020
  const projDir = join58(root, proj);
25918
26021
  let entries = [];
25919
26022
  try {
25920
- entries = readdirSync34(projDir);
26023
+ entries = readdirSync33(projDir);
25921
26024
  } catch {
25922
26025
  continue;
25923
26026
  }
@@ -25962,7 +26065,7 @@ function* walkCodex() {
25962
26065
  const dir = stack.pop();
25963
26066
  let entries = [];
25964
26067
  try {
25965
- entries = readdirSync34(dir, { withFileTypes: true });
26068
+ entries = readdirSync33(dir, { withFileTypes: true });
25966
26069
  } catch {
25967
26070
  continue;
25968
26071
  }
@@ -25988,7 +26091,7 @@ function* walkAgy() {
25988
26091
  return;
25989
26092
  let sessionDirs = [];
25990
26093
  try {
25991
- sessionDirs = readdirSync34(brainDir);
26094
+ sessionDirs = readdirSync33(brainDir);
25992
26095
  } catch {
25993
26096
  return;
25994
26097
  }
@@ -29328,7 +29431,7 @@ toolkit.command("sync").description("Fan every artifact out to every tool that i
29328
29431
  console.log(chalk2.bold(`Synced: ${r.copied.length} copied, ${r.skipped.length} already present, ${r.failed.length} failed.`));
29329
29432
  if (r.copied.length > 0) await pushToolkitToServer();
29330
29433
  });
29331
- toolkit.command("pull").description("Install artifacts this device is missing, from the server (cross-device setup)").option("--types <list>", "comma-separated subset: mcp").option("--dry-run", "report what would change, write nothing").action(async (options) => {
29434
+ toolkit.command("pull").description("Install artifacts this device is missing, from the server (cross-device setup)").option("--types <list>", "comma-separated subset: mcp").option("--dry-run", "report what would change, write nothing").option("--mcp <names>", "comma-separated MCP servers to install even if only another platform registered them").action(async (options) => {
29332
29435
  const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
29333
29436
  const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
29334
29437
  const { hostname: hostname3 } = await import("node:os");
@@ -29357,7 +29460,8 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
29357
29460
  return;
29358
29461
  }
29359
29462
  const types = options.types ? options.types.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
29360
- const report = executePull2(rows, { thisDeviceId: hostname3(), types, dryRun: options.dryRun });
29463
+ const explicit = options.mcp ? options.mcp.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
29464
+ const report = executePull2(rows, { thisDeviceId: hostname3(), types, dryRun: options.dryRun, explicit });
29361
29465
  const written = report.outcomes.filter((o) => o.status === "written");
29362
29466
  const present = report.outcomes.filter((o) => o.status === "present");
29363
29467
  const failed = report.outcomes.filter((o) => o.status === "failed");