chat-recall 0.5.27 → 0.5.28

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm](https://img.shields.io/npm/v/chat-recall?logo=npm&color=cb3837)](https://www.npmjs.com/package/chat-recall)
5
5
  [![npm downloads](https://img.shields.io/npm/dm/chat-recall?color=cb3837&label=downloads)](https://www.npmjs.com/package/chat-recall)
6
6
  [![MCP registry](https://img.shields.io/badge/MCP%20registry-io.github.munhq%2Fchat--recall-1f6feb)](https://registry.modelcontextprotocol.io/v0/servers?search=chat-recall)
7
- [![Smithery](https://img.shields.io/badge/Smithery-munhq%2Fchat--recall-7c3aed)](https://smithery.ai/server/munhq/chat-recall)
7
+ [![Smithery](https://img.shields.io/badge/Smithery-munhq%2Fchat--recall-7c3aed)](https://smithery.ai/servers/munhq/chat-recall)
8
8
  [![Glama](https://img.shields.io/badge/Glama-munhq%2Fchat--recall-4f46e5)](https://glama.ai/mcp/servers/munhq/chat-recall)
9
9
 
10
10
  [![Install in Cursor](https://img.shields.io/badge/Install-Cursor-000?logo=cursor)](cursor://anysphere.cursor-deeplink/mcp/install?name=chat-recall&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi1wIiwiY2hhdC1yZWNhbGwiLCJjaGF0LXJlY2FsbC1tY3AiXX0=)
@@ -14,7 +14,7 @@
14
14
 
15
15
  > One memory across every AI coding tool you use. Claude Code, Gemini CLI, Codex, OpenCode, Antigravity and Cursor share a single searchable history — and the agent can search it itself.
16
16
 
17
- Your coding agents each keep their own transcripts, in their own format, in their own directory, and none of them can read another's. `chat-recall` indexes all of them into one place, redacts secrets on the way, and exposes the result to your agent through **60 MCP tools** so it can recall its own past work instead of you re-explaining it.
17
+ Your coding agents each keep their own transcripts, in their own format, in their own directory, and none of them can read another's. `chat-recall` indexes all of them into one place, redacts secrets on the way, and exposes the result to your agent through **61 MCP tools** so it can recall its own past work instead of you re-explaining it.
18
18
 
19
19
  That cross-tool part is the point. A single tool's built-in history stops at its own boundary; this does not.
20
20
 
@@ -95,7 +95,7 @@ Exclusions live on the machine that holds the data, so the CLI owns them; they a
95
95
  ## Four things it actually does
96
96
 
97
97
  1. **Cross-tool unified memory.** One index, one search, one UI over Claude Code (`~/.claude/projects/`), Gemini CLI (`~/.gemini/tmp/`), Codex (`~/.codex/`), OpenCode (`~/.local/share/opencode/`), Antigravity and Cursor (`~/.cursor/` for the CLI, `~/.config/Cursor/` for the IDE). Sessions, plans, tasks, CLAUDE.md files, paste cache, shell history and agent diaries all share one pluggable `MemorySource` interface.
98
- 2. **The agent recalls itself.** 60 MCP tools, so Claude Code can `recall_smart_resume`, `recall_search` (with `like_session` to find similar work), `recall_edits_timeline`, `recall_subagent_search` and `recall_redundant_files` rather than asking you what happened last time. It writes back too, via `recall_decision_record`, `recall_kg_add` and `recall_set`.
98
+ 2. **The agent recalls itself.** 61 MCP tools, so Claude Code can `recall_smart_resume`, `recall_search` (with `like_session` to find similar work), `recall_edits_timeline`, `recall_subagent_search` and `recall_redundant_files` rather than asking you what happened last time. It writes back too, via `recall_decision_record`, `recall_kg_add` and `recall_set`.
99
99
  3. **Warns before you redo work.** A `UserPromptSubmit` hook searches for similar past sessions on every prompt and injects a short "you have done this before, in session X" note into the agent's context.
100
100
  4. **Temporal knowledge graph.** Decisions and tool mentions become entity-relationship triples with `valid_from`/`valid_to` windows, so you can ask what was decided in March and whether it still holds.
101
101
 
@@ -218,7 +218,7 @@ codeindex is open source (MIT) at [github.com/munhq/codeindex](https://github.co
218
218
  | **Paste** | `~/.claude/paste-cache/*.txt` | Large pasted blobs |
219
219
  | **Diary** | `~/.chat-recall/index/diary/<agent>/*.json` | What the agent told its future self via `recall_diary_write` |
220
220
 
221
- ## MCP tools (60, including 4 code-intelligence tools that register when the companion binary is installed)
221
+ ## MCP tools (61, including 4 code-intelligence tools that register when the companion binary is installed)
222
222
 
223
223
  **Search & retrieve** — `recall_search`, `recall_memory_search`, `recall_recent`, `recall_show`, `recall_context`, `recall_summary`, `recall_smart_resume`, `recall_project_context`, `recall_weekly_digest`, `recall_analytics_summary`, `recall_wake_up`.
224
224
 
package/dist/cli.js CHANGED
@@ -7134,13 +7134,13 @@ async function parseCodexSubagents(sessionPath) {
7134
7134
  if (meta?.type !== "session_meta")
7135
7135
  continue;
7136
7136
  const payload = meta.payload || {};
7137
- const spawn3 = payload.source?.subagent?.thread_spawn;
7138
- if (!spawn3)
7137
+ const spawn4 = payload.source?.subagent?.thread_spawn;
7138
+ if (!spawn4)
7139
7139
  continue;
7140
- if (spawn3.parent_thread_id !== parentId)
7140
+ if (spawn4.parent_thread_id !== parentId)
7141
7141
  continue;
7142
7142
  const id = fileName.replace(/\.jsonl$/i, "");
7143
- const role = String(spawn3.agent_role || payload.agent_role || "").toLowerCase();
7143
+ const role = String(spawn4.agent_role || payload.agent_role || "").toLowerCase();
7144
7144
  const kind = role.includes("compact") ? "compact" : role.includes("aside") ? "aside" : role.includes("worker") || role.includes("explore") ? "explore" : "other";
7145
7145
  let messages = [];
7146
7146
  try {
@@ -7152,8 +7152,8 @@ async function parseCodexSubagents(sessionPath) {
7152
7152
  subagents.push({
7153
7153
  id,
7154
7154
  kind,
7155
- agentType: spawn3.agent_role || payload.agent_role,
7156
- description: spawn3.agent_nickname || payload.agent_nickname,
7155
+ agentType: spawn4.agent_role || payload.agent_role,
7156
+ description: spawn4.agent_nickname || payload.agent_nickname,
7157
7157
  filePath,
7158
7158
  messageCount: messages.length,
7159
7159
  toolUseCount,
@@ -9781,8 +9781,8 @@ var init_codex2 = __esm({
9781
9781
  } catch {
9782
9782
  continue;
9783
9783
  }
9784
- const spawn3 = meta?.payload?.source?.subagent?.thread_spawn;
9785
- if (!spawn3 || spawn3.parent_thread_id !== parentUuid)
9784
+ const spawn4 = meta?.payload?.source?.subagent?.thread_spawn;
9785
+ if (!spawn4 || spawn4.parent_thread_id !== parentUuid)
9786
9786
  continue;
9787
9787
  events.push(...this.parseRollout(path));
9788
9788
  }
@@ -9975,10 +9975,10 @@ var init_codex2 = __esm({
9975
9975
  const meta = JSON.parse(first);
9976
9976
  const payload = meta?.payload || {};
9977
9977
  projectPath = payload.cwd || "";
9978
- const spawn3 = payload.source?.subagent?.thread_spawn;
9979
- if (spawn3 || payload.agent_role || payload.agent_nickname) {
9978
+ const spawn4 = payload.source?.subagent?.thread_spawn;
9979
+ if (spawn4 || payload.agent_role || payload.agent_nickname) {
9980
9980
  isSubagent = true;
9981
- parentId = spawn3?.parent_thread_id || null;
9981
+ parentId = spawn4?.parent_thread_id || null;
9982
9982
  }
9983
9983
  }
9984
9984
  } catch {
@@ -19418,7 +19418,7 @@ function pruneLedgerTargets(configuredServers) {
19418
19418
  return 0;
19419
19419
  }
19420
19420
  const rows = names.reduce((n, s) => n + Object.keys(orphans[s]).length, 0);
19421
- console.error(`[sync] ledger: parked ${rows} row(s) for ${names.length} server(s) no longer configured (${names.join(", ")}) in ${side}`);
19421
+ console.error(`[sync] tidied local bookkeeping: ${rows} already-synced marker(s) for ${names.length} server(s) you no longer sync to (${names.join(", ")}) moved to ${side}. Nothing was uploaded there; the file is kept so logging back in re-ships nothing.`);
19422
19422
  persistNow(data);
19423
19423
  return names.length;
19424
19424
  }
@@ -19730,7 +19730,7 @@ var init_http = __esm({
19730
19730
  CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19731
19731
  } catch {
19732
19732
  try {
19733
- if (true) CLI_VERSION = "0.5.27";
19733
+ if (true) CLI_VERSION = "0.5.28";
19734
19734
  } catch {
19735
19735
  }
19736
19736
  }
@@ -20242,7 +20242,7 @@ var init_telemetry = __esm({
20242
20242
  init_sync_client();
20243
20243
  cliVersion2 = () => {
20244
20244
  try {
20245
- return true ? "0.5.27" : "dev";
20245
+ return true ? "0.5.28" : "dev";
20246
20246
  } catch {
20247
20247
  return "dev";
20248
20248
  }
@@ -20253,6 +20253,18 @@ var init_telemetry = __esm({
20253
20253
  });
20254
20254
 
20255
20255
  // ../engine/dist/core/collector-health.js
20256
+ var collector_health_exports = {};
20257
+ __export(collector_health_exports, {
20258
+ CRASHLOOP_RESTARTS: () => CRASHLOOP_RESTARTS,
20259
+ STALE_AFTER_MS: () => STALE_AFTER_MS,
20260
+ collectorHealthPath: () => collectorHealthPath,
20261
+ judgeHealth: () => judgeHealth,
20262
+ progressLine: () => progressLine,
20263
+ readCollectorHealth: () => readCollectorHealth,
20264
+ reportWalkProgress: () => reportWalkProgress,
20265
+ updateCollectorHealth: () => updateCollectorHealth,
20266
+ writeCollectorHealth: () => writeCollectorHealth
20267
+ });
20256
20268
  import { readFileSync as readFileSync34, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "node:fs";
20257
20269
  import { join as join42, dirname as dirname17 } from "node:path";
20258
20270
  function progressLine(h) {
@@ -22238,7 +22250,7 @@ var init_sync_client = __esm({
22238
22250
  ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
22239
22251
  } catch {
22240
22252
  try {
22241
- if (true) ownVersion = "0.5.27";
22253
+ if (true) ownVersion = "0.5.28";
22242
22254
  } catch {
22243
22255
  }
22244
22256
  }
@@ -22410,6 +22422,43 @@ var init_which = __esm({
22410
22422
  }
22411
22423
  });
22412
22424
 
22425
+ // src/init-report.ts
22426
+ var init_report_exports = {};
22427
+ __export(init_report_exports, {
22428
+ createInitReporter: () => createInitReporter
22429
+ });
22430
+ function createInitReporter(opts) {
22431
+ let painted = false;
22432
+ const clear = () => {
22433
+ if (painted && opts.tty) opts.write(`\r${" ".repeat(INSTALLING.length)}\r`);
22434
+ painted = false;
22435
+ };
22436
+ return {
22437
+ start() {
22438
+ if (painted) return;
22439
+ if (opts.tty) {
22440
+ opts.write(INSTALLING);
22441
+ painted = true;
22442
+ } else opts.line(INSTALLING);
22443
+ },
22444
+ warn(text) {
22445
+ clear();
22446
+ opts.line(text);
22447
+ },
22448
+ done(text) {
22449
+ clear();
22450
+ opts.line(text);
22451
+ }
22452
+ };
22453
+ }
22454
+ var INSTALLING;
22455
+ var init_init_report = __esm({
22456
+ "src/init-report.ts"() {
22457
+ "use strict";
22458
+ INSTALLING = "Installing\u2026";
22459
+ }
22460
+ });
22461
+
22413
22462
  // ../engine/dist/core/mcp-clients.js
22414
22463
  var mcp_clients_exports = {};
22415
22464
  __export(mcp_clients_exports, {
@@ -23957,6 +24006,66 @@ var init_toolkit_pull = __esm({
23957
24006
  }
23958
24007
  });
23959
24008
 
24009
+ // src/init-progress.ts
24010
+ var init_progress_exports = {};
24011
+ __export(init_progress_exports, {
24012
+ handOffLine: () => handOffLine,
24013
+ progressLine: () => progressLine2,
24014
+ renderBar: () => renderBar,
24015
+ renderCount: () => renderCount,
24016
+ spawnDetachedSync: () => spawnDetachedSync,
24017
+ watchFirstSync: () => watchFirstSync
24018
+ });
24019
+ import { spawn as spawn2 } from "node:child_process";
24020
+ function renderBar(done, total, width = 12) {
24021
+ if (total <= 0) return "\u2591".repeat(width);
24022
+ const ratio = Math.max(0, Math.min(1, done / total));
24023
+ const filled = Math.round(ratio * width);
24024
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
24025
+ }
24026
+ function renderCount(done, total) {
24027
+ const n = (x) => x.toLocaleString("en-US");
24028
+ return `${n(done)}/${n(total)}`;
24029
+ }
24030
+ function progressLine2(snap) {
24031
+ if (!snap || snap.total <= 0) return "starting\u2026";
24032
+ if (snap.complete) return `${renderCount(snap.done, snap.total)} indexed`;
24033
+ return `${renderBar(snap.done, snap.total)} ${renderCount(snap.done, snap.total)}`;
24034
+ }
24035
+ function handOffLine(snap) {
24036
+ const where = snap && snap.total > 0 ? `${renderCount(snap.done, snap.total)} so far` : "started";
24037
+ return `${where} \u2014 continuing in the background`;
24038
+ }
24039
+ async function watchFirstSync(deps) {
24040
+ const handOffAfterMs = deps.handOffAfterMs ?? 2e4;
24041
+ const pollMs = deps.pollMs ?? 400;
24042
+ const started = deps.now();
24043
+ deps.startWorker();
24044
+ let snap = null;
24045
+ for (; ; ) {
24046
+ snap = deps.readProgress();
24047
+ if (snap?.complete) return { finished: true, snapshot: snap };
24048
+ if (deps.now() - started >= handOffAfterMs) return { finished: false, snapshot: snap };
24049
+ deps.draw(progressLine2(snap));
24050
+ await deps.sleep(pollMs);
24051
+ }
24052
+ }
24053
+ function spawnDetachedSync(extraArgs = []) {
24054
+ const entry = process.argv[1];
24055
+ if (!entry) return;
24056
+ const child = spawn2(process.execPath, [entry, "sync", ...extraArgs], {
24057
+ detached: true,
24058
+ stdio: "ignore",
24059
+ windowsHide: true
24060
+ });
24061
+ child.unref();
24062
+ }
24063
+ var init_init_progress = __esm({
24064
+ "src/init-progress.ts"() {
24065
+ "use strict";
24066
+ }
24067
+ });
24068
+
23960
24069
  // src/repair.ts
23961
24070
  var repair_exports = {};
23962
24071
  __export(repair_exports, {
@@ -24572,7 +24681,7 @@ __export(collector_exports, {
24572
24681
  isSecretRule: () => isSecretRule,
24573
24682
  resolveCodeindexBin: () => resolveCodeindexBin
24574
24683
  });
24575
- import { spawn as spawn2 } from "node:child_process";
24684
+ import { spawn as spawn3 } from "node:child_process";
24576
24685
  import { execSync as execSync4 } from "node:child_process";
24577
24686
  import { readFileSync as readFileSync44 } from "node:fs";
24578
24687
  import { join as join53, basename as basename28 } from "node:path";
@@ -24587,7 +24696,7 @@ async function resolveCodeindexBin(autoInstall = true) {
24587
24696
  }
24588
24697
  function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
24589
24698
  return new Promise((resolve3, reject) => {
24590
- const p = spawn2(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
24699
+ const p = spawn3(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
24591
24700
  const res = /* @__PURE__ */ new Map();
24592
24701
  let pending = "";
24593
24702
  let failed = false;
@@ -25892,7 +26001,7 @@ var init_intent_drain = __esm({
25892
26001
  ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
25893
26002
  } catch {
25894
26003
  try {
25895
- if (true) ownVersion2 = "0.5.27";
26004
+ if (true) ownVersion2 = "0.5.28";
25896
26005
  } catch {
25897
26006
  }
25898
26007
  }
@@ -27603,6 +27712,7 @@ var DEFAULT_ALLOW = [
27603
27712
  "recall_task_create",
27604
27713
  "recall_task_update",
27605
27714
  "recall_task_comment",
27715
+ "recall_task_policy",
27606
27716
  "recall_help",
27607
27717
  "recall_smart_resume",
27608
27718
  "recall_project_context",
@@ -27653,7 +27763,6 @@ var DEFAULT_ALLOW = [
27653
27763
  "recall_toolkit_status",
27654
27764
  "recall_toolkit_sync"
27655
27765
  ];
27656
- var SELF_HOST_DOCS = "https://github.com/munhq/chat-recall/blob/main/docs/SELF_HOSTING.md";
27657
27766
  function enforceNodeVersion() {
27658
27767
  const required = Number(/(\d+)/.exec(String(pkgEngines))?.[1] ?? 0);
27659
27768
  const found = Number(process.versions.node.split(".")[0]);
@@ -27669,8 +27778,13 @@ program.name("chat-recall").description("Semantic search for Claude Code session
27669
27778
  program.command("init").description("Set up chat-recall: connect to your server, configure MCP, ship your local sessions").option("--server <url>", "chat-recall server URL to connect this machine to").option("--token <token>", "Self-host device token (skips the interactive OIDC login)").option("--skip-mcp", "Skip MCP server configuration").option("--skip-sync", "Skip the first session sync").option("--with-codeindex", "Force-download the codeindex binary during init. Default behavior is to detect an already-installed codeindex on PATH and register it as an MCP server.").option("--skip-codeindex", "Skip the codeindex companion entirely (no detection, no registration).").option("--skip-service", "Skip installing the per-user background sync service (Linux/macOS/Windows). By default init installs it so new conversations ship automatically.").option("--yes", "Do not pause before the first upload. The scope summary still prints.", false).option("--prompt-json", "Print the device prompt as one JSON line on stdout, then continue", false).action(async (options) => {
27670
27779
  try {
27671
27780
  console.log(chalk.bold("chat-recall init") + chalk.dim(` v${pkgVersion}`));
27672
- console.log();
27673
- console.log(chalk.bold("1. Detecting AI tools..."));
27781
+ const { createInitReporter: createInitReporter2 } = await Promise.resolve().then(() => (init_init_report(), init_report_exports));
27782
+ const rep = createInitReporter2({
27783
+ tty: process.stdout.isTTY === true,
27784
+ write: (t) => process.stdout.write(t),
27785
+ line: (t) => console.log(t)
27786
+ });
27787
+ rep.start();
27674
27788
  const clis = [
27675
27789
  { name: "Gemini CLI", cmd: "gemini", available: false },
27676
27790
  { name: "Claude CLI", cmd: "claude", available: false },
@@ -27686,12 +27800,9 @@ program.command("init").description("Set up chat-recall: connect to your server,
27686
27800
  } catch {
27687
27801
  }
27688
27802
  }
27689
- for (const cli of clis) {
27690
- const icon = cli.available ? chalk.green("found") : chalk.dim("not found");
27691
- console.log(` ${cli.name}: ${icon}`);
27692
- }
27693
- console.log();
27694
- console.log(chalk.bold("2. Connecting to your server..."));
27803
+ const foundTools = clis.filter((c) => c.available).map((c) => c.name.replace(/ (CLI|Code)$/, ""));
27804
+ if (!foundTools.length) rep.warn(chalk.yellow("No AI tools detected \u2014 chat-recall indexes their transcripts, so there is nothing to ship yet."));
27805
+ const serverBad = (text) => rep.warn(`${chalk.yellow("Not connected")} \u2014 ${text}`);
27695
27806
  let target = firstTarget();
27696
27807
  let deadTarget = false;
27697
27808
  if (options.server) {
@@ -27699,25 +27810,20 @@ program.command("init").description("Set up chat-recall: connect to your server,
27699
27810
  await runLogin(options.server, { token: options.token, fromInit: true, promptJson: options.promptJson });
27700
27811
  target = firstTarget();
27701
27812
  } catch (e) {
27702
- console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
27703
- console.log(` ${chalk.dim(`Run ${chalk.bold(`chat-recall login ${options.server}`)} when ready, or self-host: ${SELF_HOST_DOCS}`)}`);
27813
+ serverBad(describeProbeError(e));
27704
27814
  }
27705
27815
  } else if (!target) {
27706
- console.log(` Connecting to ${chalk.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
27707
- console.log(` ${chalk.dim(`Prefer your own server? ${chalk.bold("--server <url>")}. Self-hosting is free: ${SELF_HOST_DOCS}`)}`);
27708
27816
  try {
27709
27817
  await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
27710
27818
  target = firstTarget();
27711
27819
  } catch (e) {
27712
- console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
27713
- console.log(` ${chalk.dim(`Run ${chalk.bold("chat-recall login <server-url>")} when ready, or self-host: ${SELF_HOST_DOCS}`)}`);
27820
+ serverBad(describeProbeError(e));
27714
27821
  }
27715
27822
  }
27716
27823
  if (target && !options.server) {
27717
27824
  const { probeServer: probeServer2, probeOk: probeOk2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
27718
27825
  const probe = await probeServer2(target.base, { timeoutMs: 1e4 });
27719
27826
  if (probeOk2(probe)) {
27720
- console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
27721
27827
  } else if (probe.kind === "moved" || probe.kind === "not_api") {
27722
27828
  const retired = target.base;
27723
27829
  console.log(` ${chalk.yellow("The saved server no longer serves chat-recall")} \u2192 ${retired}`);
@@ -27732,7 +27838,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
27732
27838
  target = firstTarget();
27733
27839
  if (target) console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
27734
27840
  } catch (e) {
27735
- console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
27841
+ serverBad(describeProbeError(e));
27736
27842
  console.log(` ${chalk.dim(`Run ${chalk.bold(`chat-recall login ${DEFAULT_SERVER}`)} when ready, then ${chalk.bold(`chat-recall logout ${retired}`)}.`)}`);
27737
27843
  deadTarget = true;
27738
27844
  }
@@ -27741,11 +27847,8 @@ program.command("init").description("Set up chat-recall: connect to your server,
27741
27847
  console.log(` ${probeAdvice2(probe, target.base)}`);
27742
27848
  }
27743
27849
  } else if (target) {
27744
- console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
27745
27850
  }
27746
- console.log();
27747
27851
  if (!options.skipMcp) {
27748
- console.log(chalk.bold("3. Configuring MCP server..."));
27749
27852
  const projectRoot = join60(import.meta.dirname, "..");
27750
27853
  let mcpBinOnPath = false;
27751
27854
  mcpBinOnPath = isOnPath("chat-recall-mcp");
@@ -27764,19 +27867,20 @@ program.command("init").description("Set up chat-recall: connect to your server,
27764
27867
  { ...launch, env: MCP_ENV, alwaysAllow: DEFAULT_ALLOW },
27765
27868
  { extraIds: detectedClients }
27766
27869
  );
27767
- for (const r of results) {
27768
- const word = r.state === "current" ? chalk.green("already configured") : r.state === "created" ? chalk.green("configured") : r.state === "repaired" ? chalk.green("repaired") : chalk.yellow("left alone \u2014 file does not parse");
27769
- console.log(` ${r.label}: ${word} \u2192 ${r.path}`);
27870
+ const ok = results.filter((r) => r.state !== "unparseable");
27871
+ const bad = results.filter((r) => r.state === "unparseable");
27872
+ if (ok.length) {
27873
+ }
27874
+ for (const r of bad) {
27875
+ rep.warn(`${chalk.yellow(`${r.label} config left alone \u2014 the file does not parse`)} ${chalk.dim(r.path)}`);
27876
+ }
27877
+ if (process.env.CHAT_RECALL_VERBOSE) {
27770
27878
  }
27771
- console.log(chalk.dim(` Launch: ${launch.command}${launch.args ? " " + launch.args.join(" ") : ""}`));
27772
27879
  } else {
27773
- console.log(chalk.bold("3. Skipping MCP configuration (--skip-mcp)"));
27774
27880
  }
27775
- console.log();
27776
27881
  const skipCodeindex = options.skipCodeindex === true;
27777
27882
  const forceInstall = options.withCodeindex === true || process.env.CHAT_RECALL_WITH_CODEINDEX === "1";
27778
27883
  if (skipCodeindex) {
27779
- console.log(chalk.bold("4. Skipping codeindex companion (--skip-codeindex)"));
27780
27884
  } else {
27781
27885
  const {
27782
27886
  checkCodeindexStatus: checkCodeindexStatus2,
@@ -27786,64 +27890,46 @@ program.command("init").description("Set up chat-recall: connect to your server,
27786
27890
  } = await Promise.resolve().then(() => (init_companions(), companions_exports));
27787
27891
  const detected = checkCodeindexStatus2();
27788
27892
  if (detected.installed && !forceInstall) {
27789
- console.log(chalk.bold("4. Detected codeindex companion"));
27790
- console.log(` ${chalk.green("codeindex")}: ${detected.path}`);
27791
27893
  if (!options.skipMcp) {
27792
27894
  const mcpJsonPath = join60(homedir22(), ".mcp.json");
27793
27895
  const reg = registerCodeindexMcp2(mcpJsonPath, detected.path);
27794
- if (reg.added) console.log(` ${chalk.green("codeindex MCP server registered")} in ${mcpJsonPath}`);
27795
- else console.log(` codeindex MCP server: ${chalk.green("already registered")}`);
27796
27896
  }
27797
27897
  } else if (forceInstall) {
27798
- console.log(chalk.bold("4. Installing codeindex companion (--with-codeindex)..."));
27799
27898
  try {
27800
27899
  const result = await installCodeindex2({ force: true });
27801
27900
  if (result.installed) {
27802
27901
  const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
27803
- console.log(` ${chalk.green("codeindex installed")} (${sizeMb}) \u2192 ${result.path}`);
27804
27902
  if (!options.skipMcp) {
27805
27903
  const mcpJsonPath = join60(homedir22(), ".mcp.json");
27806
27904
  const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
27807
- if (reg.added) console.log(` ${chalk.green("codeindex MCP server registered")} in ${mcpJsonPath}`);
27808
27905
  }
27809
27906
  } else if (!result.prebuiltAvailable) {
27810
- console.log(` ${chalk.yellow("codeindex")}: ${result.unsupportedReason}`);
27907
+ rep.warn(`${chalk.yellow("codeindex")}: ${result.unsupportedReason}`);
27811
27908
  }
27812
27909
  } catch (err) {
27813
27910
  const msg = String(err);
27814
- console.log(` ${chalk.yellow("codeindex install failed")}: ${msg}`);
27911
+ rep.warn(`${chalk.yellow("codeindex install failed")}: ${msg}`);
27815
27912
  if (msg.includes("404")) {
27816
27913
  console.log(chalk.dim(" The codeindex release returned 404. If the repo is private,"));
27817
27914
  console.log(chalk.dim(" authenticate `gh` to it, or build from source."));
27818
27915
  }
27819
27916
  }
27820
27917
  } else {
27821
- console.log(chalk.bold("4. codeindex companion: not installed"));
27822
- console.log(chalk.dim(" codeindex is a separate MCP server for code-level lookup \u2014"));
27823
- console.log(chalk.dim(" find_symbol, plan_change, get_change_impact, etc."));
27824
- console.log(chalk.dim(" To install: `chat-recall init --with-codeindex`"));
27825
- console.log(chalk.dim(" Or grab the binary from https://github.com/munhq/codeindex"));
27826
27918
  }
27827
27919
  }
27828
- console.log();
27829
- console.log(chalk.bold("Installing chat-recall skills into your AI tools..."));
27830
27920
  try {
27831
27921
  const { installSkills: installSkills2 } = await Promise.resolve().then(() => (init_install_skills(), install_skills_exports));
27832
27922
  const r = installSkills2();
27833
27923
  const hits = r.perTarget.filter((t) => t.installed.length > 0);
27834
27924
  if (hits.length) {
27835
- for (const t of hits) console.log(` ${chalk.green(t.label)}: ${t.installed.length} skill(s) \u2192 ${t.dir}`);
27925
+ const skills = Math.max(...hits.map((t) => t.installed.length));
27836
27926
  } else {
27837
- console.log(chalk.dim(" No supported AI tools detected \u2014 skills not installed."));
27838
27927
  }
27839
27928
  const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
27840
- if (userOwned.length) console.log(chalk.yellow(` Kept your existing skill(s) of the same name: ${userOwned.join(", ")}`));
27841
27929
  } catch (err) {
27842
- console.log(` ${chalk.yellow("Skill install failed")} \u2014 ${err instanceof Error ? err.message : err}`);
27930
+ rep.warn(`${chalk.yellow("Skill install failed")} \u2014 ${err instanceof Error ? err.message : err}`);
27843
27931
  }
27844
- console.log();
27845
27932
  if (!options.skipMcp && firstTarget()) {
27846
- console.log(chalk.bold("Installing your other MCP servers from your account..."));
27847
27933
  try {
27848
27934
  const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
27849
27935
  const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
@@ -27879,66 +27965,59 @@ program.command("init").description("Set up chat-recall: connect to your server,
27879
27965
  const written = report.outcomes.filter((o) => o.status === "written");
27880
27966
  const present = report.outcomes.filter((o) => o.status === "present").length;
27881
27967
  if (answered === 0) {
27882
- console.log(` ${chalk.yellow("Could not ask your server what MCP servers are on your account")}`);
27968
+ rep.warn(chalk.yellow("Could not ask your server what MCP servers are on your account"));
27883
27969
  for (const f of askFailed) console.log(` ${chalk.dim(f)}`);
27884
27970
  console.log(` ${chalk.dim("Re-run `chat-recall toolkit pull` once the server answers.")}`);
27885
27971
  } else if (written.length === 0 && present === 0) {
27886
- console.log(chalk.dim(" Nothing on your account to install yet."));
27887
27972
  } else {
27888
- console.log(` ${chalk.green(`Installed ${written.length}`)} ${chalk.dim(`(${present} already present)`)}`);
27889
27973
  const envVars = [...new Set(written.flatMap((o) => o.needsEnv || []))].sort();
27890
27974
  if (envVars.length) {
27891
- console.log(` ${chalk.yellow("Set these env vars \u2014 their values were never uploaded:")} ${envVars.join(", ")}`);
27975
+ rep.warn(`${chalk.yellow("Set these env vars \u2014 their values were never uploaded:")} ${envVars.join(", ")}`);
27892
27976
  }
27893
27977
  }
27894
27978
  } catch (err) {
27895
- console.log(` ${chalk.yellow("Could not install account MCPs")} \u2014 ${err instanceof Error ? err.message : err}`);
27979
+ rep.warn(`${chalk.yellow("Could not install account MCPs")} \u2014 ${err instanceof Error ? err.message : err}`);
27896
27980
  console.log(` ${chalk.dim("Re-run `chat-recall toolkit pull` later.")}`);
27897
27981
  }
27898
- console.log();
27899
27982
  }
27900
27983
  if (deadTarget) {
27901
- console.log(chalk.bold("5. Skipping first sync \u2014 the saved server is not serving chat-recall"));
27984
+ console.log(chalk.bold("6. Skipping first sync \u2014 the saved server is not serving chat-recall"));
27902
27985
  console.log(` ${chalk.dim(`Re-point with the two commands above, then run ${chalk.bold("chat-recall sync")}.`)}`);
27903
27986
  } else if (!options.skipSync && firstTarget()) {
27904
- console.log(chalk.bold("5. Indexing and uploading your sessions"));
27905
- try {
27906
- const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
27907
- const r = await syncSessions2();
27908
- console.log(` ${chalk.green(`Synced ${r.uploaded} session(s), ${r.items} item(s)`)} ${chalk.dim(`\u2014 ${r.links} links, ${r.derived} derived rows, ${r.kgTriples} KG triples, ${r.skipped} skipped, ${r.redactions} secrets redacted, ${r.findings} secret findings${r.scanned ? ` (scanned ${r.scanned} in ${r.scanMs}ms)` : ""}`)}`);
27909
- } catch (err) {
27910
- console.log(` ${chalk.yellow("Sync failed")} \u2014 ${err instanceof Error ? err.message : err}`);
27911
- console.log(` ${chalk.dim("Re-run `chat-recall sync` once your server is reachable.")}`);
27987
+ const { watchFirstSync: watchFirstSync2, spawnDetachedSync: spawnDetachedSync2, handOffLine: handOffLine2, progressLine: renderProgress } = await Promise.resolve().then(() => (init_init_progress(), init_progress_exports));
27988
+ const { readCollectorHealth: readCollectorHealth2 } = await Promise.resolve().then(() => (init_collector_health(), collector_health_exports));
27989
+ const label = (text) => `${chalk.bold("6. Indexing")} ${text}`;
27990
+ const tty = process.stdout.isTTY === true;
27991
+ const outcome = await watchFirstSync2({
27992
+ startWorker: () => spawnDetachedSync2(),
27993
+ readProgress: () => {
27994
+ const p = readCollectorHealth2()?.progress;
27995
+ return p ? { done: p.done, total: p.total, complete: p.complete } : null;
27996
+ },
27997
+ // In place on a terminal; a single final line when piped, so a log or
27998
+ // a CI transcript does not collect one line per poll.
27999
+ draw: (text) => {
28000
+ if (tty) process.stdout.write(`\r${label(text)} `);
28001
+ },
28002
+ now: () => Date.now(),
28003
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms))
28004
+ });
28005
+ if (tty) process.stdout.write("\r");
28006
+ if (outcome.finished) {
28007
+ console.log(label(chalk.green(renderProgress(outcome.snapshot))));
28008
+ } else {
28009
+ console.log(label(chalk.dim(handOffLine2(outcome.snapshot))));
28010
+ console.log(` ${chalk.dim("watch it with: " + chalk.bold("chat-recall status"))}`);
28011
+ }
28012
+ const failed = Object.entries(readCollectorHealth2()?.targets ?? {}).filter(([, t]) => (t.failures ?? 0) > 0 && t.lastError);
28013
+ for (const [server, t] of failed) {
28014
+ console.log(` ${chalk.yellow("sync error")} ${chalk.dim(server)}: ${t.lastError}`);
27912
28015
  }
27913
28016
  } else if (!options.skipSync) {
27914
- console.log(chalk.bold("5. Skipping first sync (not logged in)"));
27915
28017
  } else {
27916
- console.log(chalk.bold("5. Skipping first sync (--skip-sync)"));
27917
28018
  }
27918
- console.log();
27919
- console.log(chalk.bold("6. Background sync"));
27920
- console.log(chalk.dim(" Runs via the MCP server while Claude Code is open (no extra service to install)."));
27921
- console.log(chalk.dim(" Always-on / headless? Opt in with `chat-recall watch --install-service`."));
27922
28019
  void options.skipService;
27923
- console.log();
27924
- console.log(chalk.green.bold("Setup complete!"));
27925
- console.log();
27926
- console.log(`chat-recall MCP tools (${DEFAULT_ALLOW.length}):`);
27927
- for (let i = 0; i < DEFAULT_ALLOW.length; i += 3) {
27928
- console.log(" " + DEFAULT_ALLOW.slice(i, i + 3).join(", "));
27929
- }
27930
- if (!skipCodeindex) {
27931
- const { checkCodeindexStatus: checkCodeindexStatus2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
27932
- if (checkCodeindexStatus2().installed) {
27933
- console.log();
27934
- console.log("codeindex MCP tools (16): status, search, find_symbol, find_word, get_outline,");
27935
- console.log(" get_tree, get_imports, get_imported_by, find_callers, plan_change,");
27936
- console.log(" get_hot_files, index_workspace, analyze, read_file, read_symbol,");
27937
- console.log(" get_change_impact");
27938
- }
27939
- }
27940
- console.log();
27941
- console.log(`${chalk.dim("Background sync is running \u2014 new conversations ship automatically. Manage it with `chat-recall service status|uninstall`.")}`);
28020
+ rep.done(chalk.green.bold("\u2713 Done"));
27942
28021
  } catch (err) {
27943
28022
  console.error(chalk.red("Error:"), err);
27944
28023
  process.exit(1);
@@ -29850,13 +29929,13 @@ async function runLogin(serverUrl, opts) {
29850
29929
  return;
29851
29930
  }
29852
29931
  console.log();
29853
- console.log(chalk.bold("To log in, open:"));
29932
+ console.log(chalk.bold("Open this to approve the machine:"));
29854
29933
  console.log(" " + chalk.cyan(p.url));
29855
- console.log(chalk.dim(` (if prompted, enter code: ${chalk.bold(p.userCode)} at ${p.verificationUri})`));
29856
- const opened = openBrowser(p.url);
29857
- if (opened) console.log(chalk.dim(" (opening your browser\u2026 if nothing happens, use the link above)"));
29858
- console.log(chalk.dim("After approving, come back to THIS terminal \u2014 it finishes the login by itself."));
29859
- console.log(chalk.dim("Waiting for approval\u2026"));
29934
+ if (openBrowser(p.url)) {
29935
+ console.log(chalk.dim(" (tried to open it for you \u2014 if no window appeared, use the link above)"));
29936
+ }
29937
+ console.log(chalk.dim(` If the page asks for a code: ${chalk.bold(p.userCode)}`));
29938
+ console.log(chalk.dim("Then come back here \u2014 this finishes on its own. Waiting\u2026"));
29860
29939
  };
29861
29940
  const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
29862
29941
  const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };