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/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.3";
18590
+ if (true) CLI_VERSION = "0.7.5";
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.3" : "dev";
19089
+ return true ? "0.7.5" : "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";
@@ -19442,7 +19488,7 @@ function planAutoUpdate(base3, caps, ownVersion3, flag) {
19442
19488
  if (!rel || !rel.version || !rel.sha256) return { update: false, reason: "server advertises no CLI release" };
19443
19489
  if (!isAutoUpdateEnabled(caps.edition, flag)) return { update: false, reason: `auto-update disabled (edition ${caps.edition ?? "?"})` };
19444
19490
  if (compareVersions(rel.version, ownVersion3) <= 0) return { update: false, reason: `already current (own ${ownVersion3} >= server ${rel.version})` };
19445
- return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base3), sha256: rel.sha256, from: ownVersion3, to: rel.version };
19491
+ return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base3, rel.version), fallbackUrl: tarballUrl(base3), sha256: rel.sha256, from: ownVersion3, to: rel.version };
19446
19492
  }
19447
19493
  function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
19448
19494
  const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
@@ -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++;
@@ -19475,7 +19521,13 @@ async function executeAutoUpdate(plan, deps) {
19475
19521
  try {
19476
19522
  buf = await deps.download(plan.url);
19477
19523
  } catch (e) {
19478
- return { updated: false, reason: `download failed: ${e instanceof Error ? e.message : e}` };
19524
+ const notFound = e instanceof Error && e.message === "HTTP 404";
19525
+ if (!notFound || !plan.fallbackUrl) return { updated: false, reason: `download failed: ${e instanceof Error ? e.message : e}` };
19526
+ try {
19527
+ buf = await deps.download(plan.fallbackUrl);
19528
+ } catch (e2) {
19529
+ return { updated: false, reason: `download failed: ${e2 instanceof Error ? e2.message : e2}` };
19530
+ }
19479
19531
  }
19480
19532
  const got = createHash8("sha256").update(buf).digest("hex");
19481
19533
  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` };
@@ -19651,7 +19703,7 @@ var init_auto_update = __esm({
19651
19703
  "src/auto-update.ts"() {
19652
19704
  "use strict";
19653
19705
  init_http();
19654
- tarballUrl = (base3) => `${base3.replace(/\/+$/, "")}/install/chat-recall.tgz`;
19706
+ tarballUrl = (base3, version2) => `${base3.replace(/\/+$/, "")}/install/${version2 ? `chat-recall-${version2}.tgz` : "chat-recall.tgz"}`;
19655
19707
  GIVE_UP_AFTER = 3;
19656
19708
  reportedStandingSkip = /* @__PURE__ */ new Set();
19657
19709
  }
@@ -19664,6 +19716,7 @@ __export(sync_client_exports, {
19664
19716
  DERIVED_MAX_BYTES: () => DERIVED_MAX_BYTES,
19665
19717
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
19666
19718
  SYNC_CHUNK_BYTES: () => SYNC_CHUNK_BYTES,
19719
+ TOOLKIT_INVENTORY_TYPES: () => TOOLKIT_INVENTORY_TYPES,
19667
19720
  _acceptedUploads: () => _acceptedUploads,
19668
19721
  _fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
19669
19722
  _resetCapsCacheForTests: () => _resetCapsCacheForTests,
@@ -19692,7 +19745,7 @@ __export(sync_client_exports, {
19692
19745
  syncSessions: () => syncSessions
19693
19746
  });
19694
19747
  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";
19748
+ 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
19749
  import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
19697
19750
  import { createHash as createHash9 } from "node:crypto";
19698
19751
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -20095,7 +20148,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
20095
20148
  let reaped = 0;
20096
20149
  let names;
20097
20150
  try {
20098
- names = readdirSync32(dir);
20151
+ names = readdirSync31(dir);
20099
20152
  } catch {
20100
20153
  return 0;
20101
20154
  }
@@ -20736,17 +20789,24 @@ async function syncToTargetInScope(cred, opts = {}) {
20736
20789
  const seenTools = /* @__PURE__ */ new Set();
20737
20790
  const seenItemKeys = /* @__PURE__ */ new Map();
20738
20791
  const registry = buildSourceRegistry();
20792
+ const inventory = /* @__PURE__ */ new Map();
20793
+ const inventoryFailed = /* @__PURE__ */ new Set();
20739
20794
  for (const sourceType of registry.getRegisteredTypes()) {
20740
20795
  if (sourceType === "session") continue;
20796
+ if (TOOLKIT_INVENTORY_TYPES.has(sourceType)) inventory.set(sourceType, /* @__PURE__ */ new Set());
20741
20797
  for (const source of registry.getAll(sourceType)) {
20742
20798
  let discovered;
20743
20799
  try {
20744
20800
  discovered = source.discover();
20745
20801
  } catch {
20802
+ inventoryFailed.add(sourceType);
20746
20803
  continue;
20747
20804
  }
20748
20805
  try {
20749
20806
  for await (const item of discovered) {
20807
+ if (inventory.has(item.sourceType) && !excluded(item.projectPath || "") && includedProject(item.projectPath || "")) {
20808
+ inventory.get(item.sourceType).add(item.id);
20809
+ }
20750
20810
  const itemTool = toolOfId(item.id);
20751
20811
  seenTools.add(itemTool);
20752
20812
  const itemKey = `${itemTool}:${item.sourceType}`;
@@ -20804,6 +20864,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20804
20864
  redactions += count.redactions;
20805
20865
  }
20806
20866
  } catch (err) {
20867
+ inventoryFailed.add(sourceType);
20807
20868
  console.error(`[sync] source '${sourceType}' failed mid-walk (its items may be incomplete this run): ${err instanceof Error ? err.message : err}`);
20808
20869
  }
20809
20870
  }
@@ -20830,6 +20891,27 @@ async function syncToTargetInScope(cred, opts = {}) {
20830
20891
  await kgTripleBatch.drain();
20831
20892
  await findingsBatch.drain();
20832
20893
  await drainInflight();
20894
+ {
20895
+ const sentHashes = loadInventoryHashes(base3);
20896
+ const entries = [];
20897
+ const hashes = {};
20898
+ for (const [sourceType, ids] of inventory) {
20899
+ if (inventoryFailed.has(sourceType)) continue;
20900
+ const sorted = [...ids].sort();
20901
+ const hash = createHash9("sha256").update(sorted.join("\n")).digest("hex").slice(0, 32);
20902
+ if (sentHashes[sourceType] === hash) continue;
20903
+ entries.push({ source_type: sourceType, ids: sorted });
20904
+ hashes[sourceType] = hash;
20905
+ }
20906
+ if (entries.length > 0) {
20907
+ try {
20908
+ await post({ inventory: entries });
20909
+ saveInventoryHashes(base3, hashes);
20910
+ } catch (err) {
20911
+ console.error(`[sync] toolkit inventory not sent (it is retried next sync): ${err instanceof Error ? err.message : err}`);
20912
+ }
20913
+ }
20914
+ }
20833
20915
  if (seenTools.size > 0) {
20834
20916
  try {
20835
20917
  const rec = {};
@@ -21275,7 +21357,7 @@ async function syncIncremental(opts = {}) {
21275
21357
  lock.release();
21276
21358
  }
21277
21359
  }
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;
21360
+ 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
21361
  var init_sync_client = __esm({
21280
21362
  "src/sync-client.ts"() {
21281
21363
  "use strict";
@@ -21320,7 +21402,7 @@ var init_sync_client = __esm({
21320
21402
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
21321
21403
  } catch {
21322
21404
  try {
21323
- if (true) ownVersion = "0.7.3";
21405
+ if (true) ownVersion = "0.7.5";
21324
21406
  } catch {
21325
21407
  }
21326
21408
  }
@@ -21350,6 +21432,7 @@ var init_sync_client = __esm({
21350
21432
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
21351
21433
  DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
21352
21434
  SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
21435
+ TOOLKIT_INVENTORY_TYPES = /* @__PURE__ */ new Set(["skill", "mcp", "command", "agent", "plugin"]);
21353
21436
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
21354
21437
  }
21355
21438
  });
@@ -22004,7 +22087,7 @@ __export(install_skills_exports, {
22004
22087
  skillsSourceDir: () => skillsSourceDir,
22005
22088
  uninstallSkills: () => uninstallSkills
22006
22089
  });
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";
22090
+ 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
22091
  import { createHash as createHash11 } from "crypto";
22009
22092
  import { join as join48, basename as basename21 } from "path";
22010
22093
  function skillsSourceDir() {
@@ -22030,7 +22113,7 @@ function skillsFingerprint() {
22030
22113
  }
22031
22114
  function filesUnder(dir, prefix = "") {
22032
22115
  const out = [];
22033
- for (const e of readdirSync33(dir, { withFileTypes: true })) {
22116
+ for (const e of readdirSync32(dir, { withFileTypes: true })) {
22034
22117
  if (e.name === MARKER) continue;
22035
22118
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
22036
22119
  if (e.isDirectory()) out.push(...filesUnder(join48(dir, e.name), rel));
@@ -22044,7 +22127,7 @@ function skillStamp() {
22044
22127
  function bundledSkillNames() {
22045
22128
  const src = skillsSourceDir();
22046
22129
  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();
22130
+ return readdirSync32(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join48(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22048
22131
  }
22049
22132
  function realPathOrSelf(p) {
22050
22133
  try {
@@ -22134,7 +22217,7 @@ function uninstallSkills() {
22134
22217
  perTarget.push({ id: t.id, label: t.label, removed });
22135
22218
  continue;
22136
22219
  }
22137
- for (const entry of readdirSync33(t.dir, { withFileTypes: true })) {
22220
+ for (const entry of readdirSync32(t.dir, { withFileTypes: true })) {
22138
22221
  if (!entry.isDirectory()) continue;
22139
22222
  const dir = join48(t.dir, entry.name);
22140
22223
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
@@ -22808,6 +22891,7 @@ __export(toolkit_pull_exports, {
22808
22891
  entryFromSpec: () => entryFromSpec,
22809
22892
  executePull: () => executePull,
22810
22893
  planPull: () => planPull,
22894
+ platformRefusal: () => platformRefusal,
22811
22895
  portableCommand: () => portableCommand
22812
22896
  });
22813
22897
  import { existsSync as existsSync48, mkdirSync as mkdirSync16, statSync as statSync41, writeFileSync as writeFileSync16 } from "node:fs";
@@ -22865,6 +22949,7 @@ function planPull(rows, opts = {}) {
22865
22949
  const wanted = opts.types;
22866
22950
  const seenUnsupported = /* @__PURE__ */ new Map();
22867
22951
  const byName = /* @__PURE__ */ new Map();
22952
+ const platformsByName = /* @__PURE__ */ new Map();
22868
22953
  const skillsByName = /* @__PURE__ */ new Map();
22869
22954
  const agentsByName = /* @__PURE__ */ new Map();
22870
22955
  const commandsByName = /* @__PURE__ */ new Map();
@@ -22928,6 +23013,9 @@ function planPull(rows, opts = {}) {
22928
23013
  const name = extra.mcpName || row.title;
22929
23014
  if (!name)
22930
23015
  continue;
23016
+ if (typeof extra.platform === "string" && extra.platform) {
23017
+ (platformsByName.get(name) ?? platformsByName.set(name, /* @__PURE__ */ new Set()).get(name)).add(extra.platform);
23018
+ }
22931
23019
  const spec = extra.spec || null;
22932
23020
  if (!spec)
22933
23021
  continue;
@@ -22937,7 +23025,12 @@ function planPull(rows, opts = {}) {
22937
23025
  byName.set(name, spec);
22938
23026
  }
22939
23027
  const targets = SUPPORTED_TARGETS.mcp;
22940
- const mcps = [...byName.entries()].map(([name, spec]) => ({ name, spec, tools: targets }));
23028
+ const mcps = [...byName.entries()].map(([name, spec]) => ({
23029
+ name,
23030
+ spec,
23031
+ tools: targets,
23032
+ platforms: [...platformsByName.get(name) ?? []].sort()
23033
+ }));
22941
23034
  const skills = [...skillsByName.entries()].map(([name, v]) => ({
22942
23035
  name,
22943
23036
  body: v.body,
@@ -22956,7 +23049,18 @@ function planPull(rows, opts = {}) {
22956
23049
  ];
22957
23050
  return { mcps, skills, codecs, unsupported };
22958
23051
  }
23052
+ function platformRefusal(name, platforms, here, explicit) {
23053
+ if (explicit.has(name) || platforms.includes(here))
23054
+ return null;
23055
+ const hint = `install it by name if it works on ${here}: chat-recall toolkit pull --mcp ${name}`;
23056
+ if (platforms.length === 0) {
23057
+ return `no device recorded its platform \u2014 re-sync from the device that has it, or ${hint}`;
23058
+ }
23059
+ return `registered only on ${platforms.join(", ")} \u2014 ${hint}`;
23060
+ }
22959
23061
  function executePull(rows, opts = {}) {
23062
+ const here = opts.platform ?? process.platform;
23063
+ const explicit = new Set(opts.explicit ?? []);
22960
23064
  const { mcps, skills, codecs, unsupported } = planPull(rows, opts);
22961
23065
  const outcomes = [];
22962
23066
  for (const { name, body, truncated, redacted, tools } of skills) {
@@ -23025,8 +23129,9 @@ function executePull(rows, opts = {}) {
23025
23129
  }
23026
23130
  }
23027
23131
  }
23028
- for (const { name, spec, tools } of mcps) {
23132
+ for (const { name, spec, tools, platforms } of mcps) {
23029
23133
  const built = entryFromSpec(spec);
23134
+ const refusal = platformRefusal(name, platforms, here, explicit);
23030
23135
  for (const tool of tools) {
23031
23136
  if (!built) {
23032
23137
  outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: "stored row has no rebuildable spec (re-index on the source device)" });
@@ -23036,6 +23141,10 @@ function executePull(rows, opts = {}) {
23036
23141
  outcomes.push({ type: "mcp", name, tool, status: "present" });
23037
23142
  continue;
23038
23143
  }
23144
+ if (refusal) {
23145
+ outcomes.push({ type: "mcp", name, tool, status: "skipped", reason: refusal });
23146
+ continue;
23147
+ }
23039
23148
  if (spec.secretsRedacted) {
23040
23149
  outcomes.push({
23041
23150
  type: "mcp",
@@ -25320,7 +25429,7 @@ async function runPull(base3, opts) {
25320
25429
  return r.title === opts.name;
25321
25430
  }
25322
25431
  }) : rows;
25323
- const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted });
25432
+ const report = executePull2(filtered, { thisDeviceId: hostname3(), types: wanted, explicit: opts.name ? [opts.name] : void 0 });
25324
25433
  const written = report.outcomes.filter((o) => o.status === "written");
25325
25434
  const failed = report.outcomes.filter((o) => o.status === "failed");
25326
25435
  return {
@@ -25454,7 +25563,7 @@ var init_intent_drain = __esm({
25454
25563
  ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
25455
25564
  } catch {
25456
25565
  try {
25457
- if (true) ownVersion2 = "0.7.3";
25566
+ if (true) ownVersion2 = "0.7.5";
25458
25567
  } catch {
25459
25568
  }
25460
25569
  }
@@ -25876,7 +25985,7 @@ var init_vault_crypto = __esm({
25876
25985
  });
25877
25986
 
25878
25987
  // ../engine/dist/core/vault-source.js
25879
- import { existsSync as existsSync54, readdirSync as readdirSync34, statSync as statSync44 } from "fs";
25988
+ import { existsSync as existsSync54, readdirSync as readdirSync33, statSync as statSync44 } from "fs";
25880
25989
  import { extname, join as join58 } from "path";
25881
25990
  function* walkVaultSources() {
25882
25991
  const t = loadSettings().team.vault;
@@ -25906,7 +26015,7 @@ function* walkClaude() {
25906
26015
  return;
25907
26016
  let projectDirs = [];
25908
26017
  try {
25909
- projectDirs = readdirSync34(root);
26018
+ projectDirs = readdirSync33(root);
25910
26019
  } catch {
25911
26020
  return;
25912
26021
  }
@@ -25917,7 +26026,7 @@ function* walkClaude() {
25917
26026
  const projDir = join58(root, proj);
25918
26027
  let entries = [];
25919
26028
  try {
25920
- entries = readdirSync34(projDir);
26029
+ entries = readdirSync33(projDir);
25921
26030
  } catch {
25922
26031
  continue;
25923
26032
  }
@@ -25962,7 +26071,7 @@ function* walkCodex() {
25962
26071
  const dir = stack.pop();
25963
26072
  let entries = [];
25964
26073
  try {
25965
- entries = readdirSync34(dir, { withFileTypes: true });
26074
+ entries = readdirSync33(dir, { withFileTypes: true });
25966
26075
  } catch {
25967
26076
  continue;
25968
26077
  }
@@ -25988,7 +26097,7 @@ function* walkAgy() {
25988
26097
  return;
25989
26098
  let sessionDirs = [];
25990
26099
  try {
25991
- sessionDirs = readdirSync34(brainDir);
26100
+ sessionDirs = readdirSync33(brainDir);
25992
26101
  } catch {
25993
26102
  return;
25994
26103
  }
@@ -29328,7 +29437,7 @@ toolkit.command("sync").description("Fan every artifact out to every tool that i
29328
29437
  console.log(chalk2.bold(`Synced: ${r.copied.length} copied, ${r.skipped.length} already present, ${r.failed.length} failed.`));
29329
29438
  if (r.copied.length > 0) await pushToolkitToServer();
29330
29439
  });
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) => {
29440
+ 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
29441
  const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
29333
29442
  const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
29334
29443
  const { hostname: hostname3 } = await import("node:os");
@@ -29357,7 +29466,8 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
29357
29466
  return;
29358
29467
  }
29359
29468
  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 });
29469
+ const explicit = options.mcp ? options.mcp.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
29470
+ const report = executePull2(rows, { thisDeviceId: hostname3(), types, dryRun: options.dryRun, explicit });
29361
29471
  const written = report.outcomes.filter((o) => o.status === "written");
29362
29472
  const present = report.outcomes.filter((o) => o.status === "present");
29363
29473
  const failed = report.outcomes.filter((o) => o.status === "failed");