chat-recall 0.5.22 → 0.5.24

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
@@ -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 spawn2 = payload.source?.subagent?.thread_spawn;
7138
- if (!spawn2)
7137
+ const spawn3 = payload.source?.subagent?.thread_spawn;
7138
+ if (!spawn3)
7139
7139
  continue;
7140
- if (spawn2.parent_thread_id !== parentId)
7140
+ if (spawn3.parent_thread_id !== parentId)
7141
7141
  continue;
7142
7142
  const id = fileName.replace(/\.jsonl$/i, "");
7143
- const role = String(spawn2.agent_role || payload.agent_role || "").toLowerCase();
7143
+ const role = String(spawn3.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: spawn2.agent_role || payload.agent_role,
7156
- description: spawn2.agent_nickname || payload.agent_nickname,
7155
+ agentType: spawn3.agent_role || payload.agent_role,
7156
+ description: spawn3.agent_nickname || payload.agent_nickname,
7157
7157
  filePath,
7158
7158
  messageCount: messages.length,
7159
7159
  toolUseCount,
@@ -7488,7 +7488,7 @@ async function archiveRawSession(store, sessionId) {
7488
7488
  }
7489
7489
  function detectForkPredecessor(sessionPath) {
7490
7490
  const { readFileSync: readFileSync50, readdirSync: readdirSync35 } = requireFs();
7491
- const { dirname: dirname28, join: join61, basename: basename29 } = requirePath();
7491
+ const { dirname: dirname28, join: join61, basename: basename30 } = requirePath();
7492
7492
  let text;
7493
7493
  try {
7494
7494
  text = readFileSync50(sessionPath, "utf-8");
@@ -7514,7 +7514,7 @@ function detectForkPredecessor(sessionPath) {
7514
7514
  if (text.includes(`"uuid":"${headParent}"`))
7515
7515
  return null;
7516
7516
  const dir = dirname28(sessionPath);
7517
- const self = basename29(sessionPath);
7517
+ const self = basename30(sessionPath);
7518
7518
  let entries;
7519
7519
  try {
7520
7520
  entries = readdirSync35(dir);
@@ -7526,7 +7526,7 @@ function detectForkPredecessor(sessionPath) {
7526
7526
  continue;
7527
7527
  try {
7528
7528
  if (readFileSync50(join61(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7529
- return basename29(f, ".jsonl");
7529
+ return basename30(f, ".jsonl");
7530
7530
  }
7531
7531
  } catch {
7532
7532
  }
@@ -9781,8 +9781,8 @@ var init_codex2 = __esm({
9781
9781
  } catch {
9782
9782
  continue;
9783
9783
  }
9784
- const spawn2 = meta?.payload?.source?.subagent?.thread_spawn;
9785
- if (!spawn2 || spawn2.parent_thread_id !== parentUuid)
9784
+ const spawn3 = meta?.payload?.source?.subagent?.thread_spawn;
9785
+ if (!spawn3 || spawn3.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 spawn2 = payload.source?.subagent?.thread_spawn;
9979
- if (spawn2 || payload.agent_role || payload.agent_nickname) {
9978
+ const spawn3 = payload.source?.subagent?.thread_spawn;
9979
+ if (spawn3 || payload.agent_role || payload.agent_nickname) {
9980
9980
  isSubagent = true;
9981
- parentId = spawn2?.parent_thread_id || null;
9981
+ parentId = spawn3?.parent_thread_id || null;
9982
9982
  }
9983
9983
  }
9984
9984
  } catch {
@@ -13470,8 +13470,11 @@ function isInside(child, parent) {
13470
13470
  return c.startsWith(p);
13471
13471
  }
13472
13472
  function globMatch(pattern, path) {
13473
- const re = new RegExp("^" + globToRegex(pattern) + "$");
13474
- return re.test(path);
13473
+ const re = new RegExp("^" + globToRegex(toPosix(pattern)) + "$");
13474
+ return re.test(toPosix(path));
13475
+ }
13476
+ function toPosix(p) {
13477
+ return p.replace(/\\/g, "/");
13475
13478
  }
13476
13479
  function globToRegex(glob) {
13477
13480
  let out = "";
@@ -19727,7 +19730,7 @@ var init_http = __esm({
19727
19730
  CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19728
19731
  } catch {
19729
19732
  try {
19730
- if (true) CLI_VERSION = "0.5.22";
19733
+ if (true) CLI_VERSION = "0.5.24";
19731
19734
  } catch {
19732
19735
  }
19733
19736
  }
@@ -20239,7 +20242,7 @@ var init_telemetry = __esm({
20239
20242
  init_sync_client();
20240
20243
  cliVersion2 = () => {
20241
20244
  try {
20242
- return true ? "0.5.22" : "dev";
20245
+ return true ? "0.5.24" : "dev";
20243
20246
  } catch {
20244
20247
  return "dev";
20245
20248
  }
@@ -20351,6 +20354,143 @@ var init_collector_health = __esm({
20351
20354
  }
20352
20355
  });
20353
20356
 
20357
+ // src/server-probe.ts
20358
+ var server_probe_exports = {};
20359
+ __export(server_probe_exports, {
20360
+ classifyUnreachable: () => classifyUnreachable,
20361
+ describeCause: () => describeCause,
20362
+ describeUnreachable: () => describeUnreachable,
20363
+ probeAdvice: () => probeAdvice,
20364
+ probeOk: () => probeOk,
20365
+ probeServer: () => probeServer
20366
+ });
20367
+ function probeOk(r) {
20368
+ return r.kind === "ok" && r.apiVersion >= 2;
20369
+ }
20370
+ async function probeServer(base2, opts = {}) {
20371
+ const f = opts.fetchImpl || fetch;
20372
+ let res;
20373
+ try {
20374
+ res = await f(`${base2.replace(/\/+$/, "")}/api/capabilities`, {
20375
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
20376
+ });
20377
+ } catch (err) {
20378
+ return {
20379
+ kind: "unreachable",
20380
+ error: describeCause(err),
20381
+ reason: classifyUnreachable(err)
20382
+ };
20383
+ }
20384
+ if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
20385
+ if (res.status === 404 || res.status === 410) return { kind: "moved", status: res.status };
20386
+ if (res.status >= 500) return { kind: "server_error", status: res.status };
20387
+ if (!res.ok) return { kind: "moved", status: res.status };
20388
+ let body;
20389
+ try {
20390
+ body = await res.text();
20391
+ } catch (err) {
20392
+ return { kind: "unreachable", error: describeCause(err), reason: classifyUnreachable(err) };
20393
+ }
20394
+ let parsed;
20395
+ try {
20396
+ parsed = JSON.parse(body);
20397
+ } catch {
20398
+ return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
20399
+ }
20400
+ if (typeof parsed?.apiVersion !== "number") {
20401
+ return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
20402
+ }
20403
+ return {
20404
+ kind: "ok",
20405
+ apiVersion: parsed.apiVersion,
20406
+ edition: parsed.edition,
20407
+ cliVersion: parsed.cli?.version,
20408
+ raw: parsed
20409
+ };
20410
+ }
20411
+ function probeAdvice(r, base2) {
20412
+ switch (r.kind) {
20413
+ case "ok":
20414
+ return r.apiVersion >= 2 ? `apiVersion ${r.apiVersion}${r.edition ? `, edition ${r.edition}` : ""}` : `apiVersion ${r.apiVersion} is too old for this CLI \u2014 upgrade the server`;
20415
+ case "moved":
20416
+ return `HTTP ${r.status} \u2014 nothing is served at ${base2}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
20417
+ case "unauthorized":
20418
+ return `HTTP ${r.status} \u2014 ${base2} rejected the credential. Re-login: \`chat-recall login ` + base2 + "`";
20419
+ case "server_error":
20420
+ return `HTTP ${r.status} \u2014 ${base2} is up but failing. Nothing to fix locally; check the server.`;
20421
+ case "not_api":
20422
+ return `${base2} answered ${r.status} with something that is not the chat-recall API (${JSON.stringify(r.snippet)}) \u2014 check the URL, a proxy or a captive portal`;
20423
+ case "unreachable":
20424
+ switch (r.reason) {
20425
+ case "dns":
20426
+ return `cannot resolve the host in ${base2} (${r.error}) \u2014 check the URL for a typo, and your DNS or VPN`;
20427
+ case "refused":
20428
+ return `${base2} resolves but refused the connection (${r.error}) \u2014 the server is not running, or the port is wrong`;
20429
+ case "tls":
20430
+ return `the TLS certificate at ${base2} was rejected (${r.error}) \u2014 expired, self-signed, or issued for another host. A corporate proxy that re-signs traffic does this.`;
20431
+ case "timeout":
20432
+ return `${base2} did not answer in time (${r.error}) \u2014 a firewall or proxy that drops traffic silently looks exactly like this`;
20433
+ case "reset":
20434
+ return `the connection to ${base2} was cut (${r.error}) \u2014 often a proxy or a captive portal`;
20435
+ case "bad_port":
20436
+ return `${base2} names a port that Node and every browser refuse to open (the WHATWG bad-port list) \u2014 use the server's real port`;
20437
+ default:
20438
+ return `no HTTP response from ${base2} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
20439
+ }
20440
+ }
20441
+ }
20442
+ function classifyUnreachable(err) {
20443
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) return "timeout";
20444
+ for (const e of walkErrorChain(err)) {
20445
+ const code2 = String(e.code ?? "");
20446
+ const msg = String(e.message ?? "");
20447
+ if (code2 === "ENOTFOUND" || code2 === "EAI_AGAIN") return "dns";
20448
+ if (code2 === "ECONNREFUSED") return "refused";
20449
+ if (code2 === "ECONNRESET" || code2 === "EPIPE") return "reset";
20450
+ if (code2 === "ETIMEDOUT" || code2 === "UND_ERR_CONNECT_TIMEOUT" || code2 === "UND_ERR_HEADERS_TIMEOUT") return "timeout";
20451
+ if (code2.startsWith("CERT_") || code2.startsWith("ERR_TLS") || code2 === "DEPTH_ZERO_SELF_SIGNED_CERT" || code2 === "SELF_SIGNED_CERT_IN_CHAIN" || code2 === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || /certificate/i.test(msg)) return "tls";
20452
+ if (/^bad port$/i.test(msg)) return "bad_port";
20453
+ }
20454
+ return "unknown";
20455
+ }
20456
+ function* walkErrorChain(err, depth = 0) {
20457
+ if (!err || typeof err !== "object" || depth > 5) return;
20458
+ yield err;
20459
+ const members = err.errors;
20460
+ if (Array.isArray(members)) {
20461
+ for (const m of members) yield* walkErrorChain(m, depth + 1);
20462
+ }
20463
+ yield* walkErrorChain(err.cause, depth + 1);
20464
+ }
20465
+ function describeCause(err) {
20466
+ const parts = [];
20467
+ for (const e of walkErrorChain(err)) {
20468
+ const code2 = e.code;
20469
+ if (typeof code2 === "string" && code2 && !parts.includes(code2)) parts.push(code2);
20470
+ }
20471
+ const base2 = err instanceof Error ? err.message : String(err);
20472
+ return parts.length ? `${base2}: ${parts.join(" \u2192 ")}` : base2;
20473
+ }
20474
+ function describeUnreachable(err) {
20475
+ const reason = classifyUnreachable(err);
20476
+ const detail = describeCause(err);
20477
+ const phrase = {
20478
+ dns: "the host name does not resolve",
20479
+ refused: "the connection was refused \u2014 nothing is listening there",
20480
+ tls: "the TLS certificate was rejected",
20481
+ timeout: "no answer before the timeout",
20482
+ reset: "the connection was cut mid-request",
20483
+ bad_port: "that port is one Node and every browser refuse to open",
20484
+ unknown: "no HTTP response"
20485
+ };
20486
+ return `${phrase[reason]} (${detail})`;
20487
+ }
20488
+ var init_server_probe = __esm({
20489
+ "src/server-probe.ts"() {
20490
+ "use strict";
20491
+ }
20492
+ });
20493
+
20354
20494
  // src/client-events.ts
20355
20495
  var client_events_exports = {};
20356
20496
  __export(client_events_exports, {
@@ -20758,6 +20898,7 @@ async function fetchTenantSyncConfig(cred) {
20758
20898
  persistSyncConfig(base2, config2);
20759
20899
  return config2;
20760
20900
  } catch (err) {
20901
+ if (isSyncRulesRefusal(err)) throw err;
20761
20902
  return lastKnownSyncConfig(base2, err instanceof Error ? err.message : "request failed");
20762
20903
  }
20763
20904
  }
@@ -20767,9 +20908,14 @@ function lastKnownSyncConfig(base2, reason) {
20767
20908
  console.error(`[sync] could not read server-side sync rules (${reason}) \u2014 applying the last known set from ${new Date(remembered.fetchedAt).toISOString()}`);
20768
20909
  return remembered.config;
20769
20910
  }
20770
- throw new Error(
20911
+ const refusal = new Error(
20771
20912
  `Cannot read the server-side sync rules from ${base2} (${reason}), and this machine has never successfully read them. Refusing to sync: uploading without knowing your exclusions could ship a project you told the dashboard to hold back. Fix the server or retry.`
20772
20913
  );
20914
+ refusal[SYNC_RULES_REFUSAL] = true;
20915
+ throw refusal;
20916
+ }
20917
+ function isSyncRulesRefusal(err) {
20918
+ return !!err && typeof err === "object" && err[SYNC_RULES_REFUSAL] === true;
20773
20919
  }
20774
20920
  function deviceLabel() {
20775
20921
  try {
@@ -21030,7 +21176,12 @@ async function verifyServerApi(serverUrl, minApi) {
21030
21176
  const base2 = serverUrl.replace(/\/$/, "");
21031
21177
  const seen = capsOk.get(base2);
21032
21178
  if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
21033
- const caps = await fetchWithTimeout(`${base2}/api/capabilities`).then((r) => r.json());
21179
+ const { probeServer: probeServer2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
21180
+ const probe = await probeServer2(base2, {
21181
+ fetchImpl: ((url, init) => fetchWithTimeout(String(url), init ?? {}))
21182
+ });
21183
+ if (probe.kind !== "ok") throw new Error(probeAdvice2(probe, base2));
21184
+ const caps = probe.raw;
21034
21185
  if ((caps.apiVersion ?? 0) < minApi) {
21035
21186
  throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
21036
21187
  }
@@ -22020,7 +22171,7 @@ async function syncIncremental(opts = {}) {
22020
22171
  lock.release();
22021
22172
  }
22022
22173
  }
22023
- var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
22174
+ var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
22024
22175
  var init_sync_client = __esm({
22025
22176
  "src/sync-client.ts"() {
22026
22177
  "use strict";
@@ -22066,7 +22217,7 @@ var init_sync_client = __esm({
22066
22217
  ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
22067
22218
  } catch {
22068
22219
  try {
22069
- if (true) ownVersion = "0.5.22";
22220
+ if (true) ownVersion = "0.5.24";
22070
22221
  } catch {
22071
22222
  }
22072
22223
  }
@@ -22078,6 +22229,7 @@ var init_sync_client = __esm({
22078
22229
  _syncConfigCache = /* @__PURE__ */ new Map();
22079
22230
  EMPTY_SYNC_CONFIG = { excludeTools: [], excludeProjects: [], excludeSources: [], approveSources: [] };
22080
22231
  _fetchTenantSyncConfigForTests = fetchTenantSyncConfig;
22232
+ SYNC_RULES_REFUSAL = /* @__PURE__ */ Symbol.for("chat-recall.syncRulesRefusal");
22081
22233
  hashPath = (p) => p ? "p_" + createHash13("sha256").update(p).digest("hex").slice(0, 12) : "";
22082
22234
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
22083
22235
  isLocalHost = isPrivateHost;
@@ -22882,7 +23034,7 @@ var init_install_skills = __esm({
22882
23034
 
22883
23035
  // ../engine/dist/core/artifact-codec.js
22884
23036
  import { readFileSync as readFileSync42 } from "fs";
22885
- import { join as join50 } from "path";
23037
+ import { basename as basename24, join as join50 } from "path";
22886
23038
  import { homedir as homedir17 } from "os";
22887
23039
  function parseCommandText(content, format, fallbackName) {
22888
23040
  if (format === "toml") {
@@ -23010,8 +23162,7 @@ function emit(type, art, toTool, projectPath) {
23010
23162
  };
23011
23163
  }
23012
23164
  function base(filePath) {
23013
- const b = filePath.split("/").pop() || filePath;
23014
- return b.replace(/\.(md|toml)$/, "");
23165
+ return basename24(filePath).replace(/\.(md|toml)$/, "");
23015
23166
  }
23016
23167
  var init_artifact_codec = __esm({
23017
23168
  "../engine/dist/core/artifact-codec.js"() {
@@ -23046,7 +23197,7 @@ __export(toolkit_sync_exports, {
23046
23197
  writeMcpEntry: () => writeMcpEntry
23047
23198
  });
23048
23199
  import { existsSync as existsSync47, mkdirSync as mkdirSync13, readFileSync as readFileSync43, writeFileSync as writeFileSync14, cpSync as cpSync2 } from "fs";
23049
- import { dirname as dirname21, join as join51, basename as basename24 } from "path";
23200
+ import { dirname as dirname21, join as join51, basename as basename25 } from "path";
23050
23201
  import { homedir as homedir18 } from "os";
23051
23202
  function supportedTargetsFor(type) {
23052
23203
  return SUPPORTED_TARGETS[type];
@@ -23104,7 +23255,7 @@ function copySkill(row, toTool) {
23104
23255
  const skillDir = row.extra.skillDir || dirname21(row.filePath);
23105
23256
  if (!existsSync47(skillDir))
23106
23257
  return { ok: false, status: 404, error: `Source dir missing: ${skillDir}` };
23107
- const name = row.extra.skillName || basename24(skillDir);
23258
+ const name = row.extra.skillName || basename25(skillDir);
23108
23259
  const targetDir = join51(skillsDirFor(toTool), name);
23109
23260
  if (existsSync47(targetDir))
23110
23261
  return { ok: false, status: 409, error: `Already exists: ${targetDir}` };
@@ -23502,7 +23653,7 @@ __export(toolkit_pull_exports, {
23502
23653
  portableCommand: () => portableCommand
23503
23654
  });
23504
23655
  import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as statSync42, writeFileSync as writeFileSync15 } from "node:fs";
23505
- import { delimiter as delimiter3, dirname as dirname22, join as join52, basename as basename25, isAbsolute as isAbsolute3 } from "node:path";
23656
+ import { dirname as dirname22, join as join52, basename as basename26, isAbsolute as isAbsolute3 } from "node:path";
23506
23657
  function isExecutableFile(p) {
23507
23658
  try {
23508
23659
  return statSync42(p).isFile();
@@ -23511,13 +23662,7 @@ function isExecutableFile(p) {
23511
23662
  }
23512
23663
  }
23513
23664
  function onPath(name) {
23514
- const dirs = (process.env.PATH || "").split(delimiter3).filter(Boolean);
23515
- for (const d of dirs) {
23516
- const candidate = join52(d, name);
23517
- if (isExecutableFile(candidate))
23518
- return candidate;
23519
- }
23520
- return null;
23665
+ return resolveOnPath(name);
23521
23666
  }
23522
23667
  function portableCommand(cmd) {
23523
23668
  if (!isAbsolute3(cmd)) {
@@ -23525,7 +23670,7 @@ function portableCommand(cmd) {
23525
23670
  }
23526
23671
  if (isExecutableFile(cmd))
23527
23672
  return { command: cmd, rewritten: false };
23528
- const base2 = basename25(cmd);
23673
+ const base2 = basename26(cmd);
23529
23674
  if (onPath(base2))
23530
23675
  return { command: base2, rewritten: true };
23531
23676
  return { missing: cmd };
@@ -23779,6 +23924,7 @@ var NOT_REBUILDABLE;
23779
23924
  var init_toolkit_pull = __esm({
23780
23925
  "../engine/dist/core/toolkit-pull.js"() {
23781
23926
  "use strict";
23927
+ init_which();
23782
23928
  init_toolkit_sync();
23783
23929
  init_artifact_codec();
23784
23930
  NOT_REBUILDABLE = {
@@ -24005,7 +24151,7 @@ __export(escalate_exports, {
24005
24151
  extractLearnings: () => extractLearnings,
24006
24152
  runEscalate: () => runEscalate
24007
24153
  });
24008
- import { basename as basename26 } from "node:path";
24154
+ import { basename as basename27 } from "node:path";
24009
24155
  function isNoiseUserMessage(content) {
24010
24156
  const c = content.trimStart();
24011
24157
  return c.length === 0 || c.length > 4e3 || c.startsWith("Caveat:") || c.startsWith("[Request interrupted") || c.includes("<command-name>") || c.includes("<local-command") || c.startsWith("<system-reminder>");
@@ -24072,10 +24218,10 @@ async function runEscalate(deps, opts) {
24072
24218
  return null;
24073
24219
  }
24074
24220
  sessionId = row.sessionId;
24075
- if (!projectName && row.projectPath) projectName = basename26(row.projectPath);
24221
+ if (!projectName && row.projectPath) projectName = basename27(row.projectPath);
24076
24222
  }
24077
24223
  if (!sessionId) throw new Error("pass a session id or --latest");
24078
- if (!projectName) projectName = basename26(opts.cwd);
24224
+ if (!projectName) projectName = basename27(opts.cwd);
24079
24225
  const convo = await deps.getSoft(`/api/conversations/${encodeURIComponent(sessionId)}?limit=0`);
24080
24226
  if (!convo.data || convo.data.messages.length === 0) {
24081
24227
  deps.log(`escalate: session ${sessionId} not synced yet${convo.message ? ` (${convo.message})` : ""} \u2014 nothing to do.`);
@@ -24145,117 +24291,6 @@ var init_escalate = __esm({
24145
24291
  }
24146
24292
  });
24147
24293
 
24148
- // src/server-probe.ts
24149
- var server_probe_exports = {};
24150
- __export(server_probe_exports, {
24151
- classifyUnreachable: () => classifyUnreachable,
24152
- describeCause: () => describeCause,
24153
- probeAdvice: () => probeAdvice,
24154
- probeOk: () => probeOk,
24155
- probeServer: () => probeServer
24156
- });
24157
- function probeOk(r) {
24158
- return r.kind === "ok" && r.apiVersion >= 2;
24159
- }
24160
- async function probeServer(base2, opts = {}) {
24161
- const f = opts.fetchImpl || fetch;
24162
- let res;
24163
- try {
24164
- res = await f(`${base2.replace(/\/+$/, "")}/api/capabilities`, {
24165
- signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
24166
- });
24167
- } catch (err) {
24168
- return {
24169
- kind: "unreachable",
24170
- error: describeCause(err),
24171
- reason: classifyUnreachable(err)
24172
- };
24173
- }
24174
- if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
24175
- if (res.status === 404 || res.status === 410) return { kind: "moved", status: res.status };
24176
- if (res.status >= 500) return { kind: "server_error", status: res.status };
24177
- if (!res.ok) return { kind: "moved", status: res.status };
24178
- let body;
24179
- try {
24180
- body = await res.text();
24181
- } catch (err) {
24182
- return { kind: "unreachable", error: describeCause(err), reason: classifyUnreachable(err) };
24183
- }
24184
- let parsed;
24185
- try {
24186
- parsed = JSON.parse(body);
24187
- } catch {
24188
- return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
24189
- }
24190
- if (typeof parsed?.apiVersion !== "number") {
24191
- return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
24192
- }
24193
- return {
24194
- kind: "ok",
24195
- apiVersion: parsed.apiVersion,
24196
- edition: parsed.edition,
24197
- cliVersion: parsed.cli?.version
24198
- };
24199
- }
24200
- function probeAdvice(r, base2) {
24201
- switch (r.kind) {
24202
- case "ok":
24203
- return r.apiVersion >= 2 ? `apiVersion ${r.apiVersion}${r.edition ? `, edition ${r.edition}` : ""}` : `apiVersion ${r.apiVersion} is too old for this CLI \u2014 upgrade the server`;
24204
- case "moved":
24205
- return `HTTP ${r.status} \u2014 nothing is served at ${base2}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
24206
- case "unauthorized":
24207
- return `HTTP ${r.status} \u2014 ${base2} rejected the credential. Re-login: \`chat-recall login ` + base2 + "`";
24208
- case "server_error":
24209
- return `HTTP ${r.status} \u2014 ${base2} is up but failing. Nothing to fix locally; check the server.`;
24210
- case "not_api":
24211
- return `${base2} answered ${r.status} with something that is not the chat-recall API (${JSON.stringify(r.snippet)}) \u2014 check the URL, a proxy or a captive portal`;
24212
- case "unreachable":
24213
- switch (r.reason) {
24214
- case "dns":
24215
- return `cannot resolve the host in ${base2} (${r.error}) \u2014 check the URL for a typo, and your DNS or VPN`;
24216
- case "refused":
24217
- return `${base2} resolves but refused the connection (${r.error}) \u2014 the server is not running, or the port is wrong`;
24218
- case "tls":
24219
- return `the TLS certificate at ${base2} was rejected (${r.error}) \u2014 expired, self-signed, or issued for another host. A corporate proxy that re-signs traffic does this.`;
24220
- case "timeout":
24221
- return `${base2} did not answer in time (${r.error}) \u2014 a firewall or proxy that drops traffic silently looks exactly like this`;
24222
- case "reset":
24223
- return `the connection to ${base2} was cut (${r.error}) \u2014 often a proxy or a captive portal`;
24224
- default:
24225
- return `no HTTP response from ${base2} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
24226
- }
24227
- }
24228
- }
24229
- function classifyUnreachable(err) {
24230
- if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) return "timeout";
24231
- for (let e = err, depth = 0; e && depth < 5; depth++) {
24232
- const code2 = String(e.code ?? "");
24233
- const msg = String(e.message ?? "");
24234
- if (code2 === "ENOTFOUND" || code2 === "EAI_AGAIN") return "dns";
24235
- if (code2 === "ECONNREFUSED") return "refused";
24236
- if (code2 === "ECONNRESET" || code2 === "EPIPE") return "reset";
24237
- if (code2 === "ETIMEDOUT" || code2 === "UND_ERR_CONNECT_TIMEOUT" || code2 === "UND_ERR_HEADERS_TIMEOUT") return "timeout";
24238
- if (code2.startsWith("CERT_") || code2.startsWith("ERR_TLS") || code2 === "DEPTH_ZERO_SELF_SIGNED_CERT" || code2 === "SELF_SIGNED_CERT_IN_CHAIN" || code2 === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || /certificate/i.test(msg)) return "tls";
24239
- e = e.cause;
24240
- }
24241
- return "unknown";
24242
- }
24243
- function describeCause(err) {
24244
- const parts = [];
24245
- for (let e = err, depth = 0; e && depth < 5; depth++) {
24246
- const code2 = e.code;
24247
- if (typeof code2 === "string" && code2 && !parts.includes(code2)) parts.push(code2);
24248
- e = e.cause;
24249
- }
24250
- const base2 = err instanceof Error ? err.message : String(err);
24251
- return parts.length ? `${base2}: ${parts.join(" \u2192 ")}` : base2;
24252
- }
24253
- var init_server_probe = __esm({
24254
- "src/server-probe.ts"() {
24255
- "use strict";
24256
- }
24257
- });
24258
-
24259
24294
  // src/verify-deep.ts
24260
24295
  var verify_deep_exports = {};
24261
24296
  __export(verify_deep_exports, {
@@ -24516,10 +24551,10 @@ __export(collector_exports, {
24516
24551
  isSecretRule: () => isSecretRule,
24517
24552
  resolveCodeindexBin: () => resolveCodeindexBin
24518
24553
  });
24519
- import { spawn } from "node:child_process";
24554
+ import { spawn as spawn2 } from "node:child_process";
24520
24555
  import { execSync as execSync4 } from "node:child_process";
24521
24556
  import { readFileSync as readFileSync44 } from "node:fs";
24522
- import { join as join53, basename as basename27 } from "node:path";
24557
+ import { join as join53, basename as basename28 } from "node:path";
24523
24558
  async function resolveCodeindexBin(autoInstall = true) {
24524
24559
  let st = checkCodeindexStatus();
24525
24560
  if (!st.installed && autoInstall)
@@ -24531,7 +24566,7 @@ async function resolveCodeindexBin(autoInstall = true) {
24531
24566
  }
24532
24567
  function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
24533
24568
  return new Promise((resolve3, reject) => {
24534
- const p = spawn(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
24569
+ const p = spawn2(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
24535
24570
  const res = /* @__PURE__ */ new Map();
24536
24571
  let pending = "";
24537
24572
  let failed = false;
@@ -24897,7 +24932,7 @@ async function collectCode(opts) {
24897
24932
  file,
24898
24933
  line: null,
24899
24934
  rule: "god_module",
24900
- title: `God module ${basename27(file)}`,
24935
+ title: `God module ${basename28(file)}`,
24901
24936
  snippet: `fan-in ${m.fan_in}, fan-out ${m.fan_out}`,
24902
24937
  why: FINDING_WHY.coupling,
24903
24938
  agentPrompt: couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100)
@@ -25065,7 +25100,7 @@ async function collectCode(opts) {
25065
25100
  file,
25066
25101
  line: v.line ?? null,
25067
25102
  rule: v.violation_type ?? "manifest_violation",
25068
- title: `${v.violation_type} in ${basename27(file)}`,
25103
+ title: `${v.violation_type} in ${basename28(file)}`,
25069
25104
  snippet: v.description ?? "",
25070
25105
  why: FINDING_WHY.manifest,
25071
25106
  agentPrompt: manifestPrompt(v.violation_type ?? "", file, v.line ?? null, v.description ?? "")
@@ -25836,7 +25871,7 @@ var init_intent_drain = __esm({
25836
25871
  ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
25837
25872
  } catch {
25838
25873
  try {
25839
- if (true) ownVersion2 = "0.5.22";
25874
+ if (true) ownVersion2 = "0.5.24";
25840
25875
  } catch {
25841
25876
  }
25842
25877
  }
@@ -27111,6 +27146,24 @@ __export(device_auth_exports, {
27111
27146
  deviceLogin: () => deviceLogin
27112
27147
  });
27113
27148
  import { createHash as createHash19, randomBytes } from "node:crypto";
27149
+ async function readDeviceCode(res, where) {
27150
+ const text = await res.text().catch(() => "");
27151
+ let parsed;
27152
+ try {
27153
+ parsed = JSON.parse(text);
27154
+ } catch {
27155
+ throw new Error(
27156
+ `${where} answered ${res.status} with something that is not a device-code response (${JSON.stringify(text.trim().slice(0, 60))}) \u2014 this host is not serving the chat-recall API`
27157
+ );
27158
+ }
27159
+ const d = parsed;
27160
+ if (typeof d?.device_code !== "string" || typeof d?.user_code !== "string" || typeof d?.verification_uri !== "string" || typeof d?.expires_in !== "number") {
27161
+ throw new Error(
27162
+ `${where} answered ${res.status} with JSON that is not a device-code response (${JSON.stringify(text.trim().slice(0, 60))}) \u2014 this host is not serving the chat-recall API`
27163
+ );
27164
+ }
27165
+ return d;
27166
+ }
27114
27167
  async function deviceLogin(opts, onPrompt) {
27115
27168
  const issuer = (opts.issuer || DEFAULT_ISSUER).replace(/\/+$/, "");
27116
27169
  if (!issuer) {
@@ -27130,7 +27183,7 @@ async function deviceLogin(opts, onPrompt) {
27130
27183
  if (!startRes.ok) {
27131
27184
  throw new Error(`device-auth init failed: HTTP ${startRes.status} ${await startRes.text().catch(() => "")}`);
27132
27185
  }
27133
- const start = await startRes.json();
27186
+ const start = await readDeviceCode(startRes, issuer);
27134
27187
  onPrompt({
27135
27188
  url: start.verification_uri_complete || start.verification_uri,
27136
27189
  userCode: start.user_code,
@@ -27184,7 +27237,7 @@ async function betterAuthDeviceLogin(serverUrl, onPrompt) {
27184
27237
  if (!startRes.ok) {
27185
27238
  throw new Error(`device-auth init failed: HTTP ${startRes.status} ${await startRes.text().catch(() => "")}`);
27186
27239
  }
27187
- const start = await startRes.json();
27240
+ const start = await readDeviceCode(startRes, base2);
27188
27241
  onPrompt({
27189
27242
  url: start.verification_uri_complete || start.verification_uri,
27190
27243
  userCode: start.user_code,
@@ -27328,7 +27381,7 @@ import { Command } from "commander";
27328
27381
  import chalk from "chalk";
27329
27382
  import { existsSync as existsSync56, readFileSync as readFileSync49, writeFileSync as writeFileSync21, realpathSync as realpathSync5 } from "fs";
27330
27383
  import { homedir as homedir22 } from "os";
27331
- import { join as join60, resolve as resolve2, dirname as dirname27, basename as basename28 } from "path";
27384
+ import { join as join60, resolve as resolve2, dirname as dirname27, basename as basename29 } from "path";
27332
27385
 
27333
27386
  // ../engine/dist/core/score-tier.js
27334
27387
  var STRONG_RATIO = 0.85;
@@ -27355,6 +27408,34 @@ function tierAll(results) {
27355
27408
  // src/cli.ts
27356
27409
  init_sync_client();
27357
27410
  init_update_notice();
27411
+ init_server_probe();
27412
+
27413
+ // src/open-browser.ts
27414
+ import { spawn } from "node:child_process";
27415
+ function isSafeUrl(url) {
27416
+ try {
27417
+ const u = new URL(url);
27418
+ return u.protocol === "https:" || u.protocol === "http:";
27419
+ } catch {
27420
+ return false;
27421
+ }
27422
+ }
27423
+ function openBrowser(url) {
27424
+ if (!isSafeUrl(url)) return false;
27425
+ if (process.env.CHAT_RECALL_NO_BROWSER === "1" || process.env.CI) return false;
27426
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
27427
+ try {
27428
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
27429
+ child.on("error", () => {
27430
+ });
27431
+ child.unref();
27432
+ return true;
27433
+ } catch {
27434
+ return false;
27435
+ }
27436
+ }
27437
+
27438
+ // src/cli.ts
27358
27439
  init_which();
27359
27440
  init_collector_health();
27360
27441
  init_telemetry_consent();
@@ -27396,14 +27477,9 @@ function firstTarget() {
27396
27477
  return { base: cred.serverUrl.replace(/\/+$/, ""), token: cred.token };
27397
27478
  }
27398
27479
  function describeProbeError(err) {
27399
- const codes = [];
27400
- for (let e = err, depth = 0; e && depth < 5; depth++) {
27401
- const code2 = e.code;
27402
- if (typeof code2 === "string" && code2 && !codes.includes(code2)) codes.push(code2);
27403
- e = e.cause;
27404
- }
27405
- const base2 = err instanceof Error ? err.message : String(err);
27406
- return codes.length ? `${base2}: ${codes.join(" \u2192 ")}` : base2;
27480
+ const msg = err instanceof Error ? err.message : String(err);
27481
+ if (!/fetch failed|ECONN|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|CERT_|aborted|timeout/i.test(msg)) return msg;
27482
+ return describeUnreachable(err);
27407
27483
  }
27408
27484
  function failLogin(opts, message) {
27409
27485
  if (opts.fromInit) throw new Error(message);
@@ -27418,11 +27494,21 @@ function requireTarget() {
27418
27494
  }
27419
27495
  return t;
27420
27496
  }
27497
+ async function readJsonBody(res, base2, what) {
27498
+ const text = await res.text().catch(() => "");
27499
+ try {
27500
+ return JSON.parse(text);
27501
+ } catch {
27502
+ throw new Error(
27503
+ `${base2} answered ${res.status} with something that is not JSON (${JSON.stringify(text.trim().slice(0, 60))}) for ${what} \u2014 a proxy, a captive portal or a host that no longer serves the chat-recall API`
27504
+ );
27505
+ }
27506
+ }
27421
27507
  async function serverGet(path) {
27422
27508
  const t = requireTarget();
27423
27509
  const res = await fetch(t.base + path, { headers: t.token ? { authorization: `Bearer ${t.token}` } : {} });
27424
27510
  if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
27425
- return res.json();
27511
+ return readJsonBody(res, t.base, `GET ${path}`);
27426
27512
  }
27427
27513
  async function serverGetSoft(path) {
27428
27514
  const t = requireTarget();
@@ -27432,7 +27518,7 @@ async function serverGetSoft(path) {
27432
27518
  return { status: res.status, data: null, message: body.message };
27433
27519
  }
27434
27520
  if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
27435
- return { status: res.status, data: await res.json() };
27521
+ return { status: res.status, data: await readJsonBody(res, t.base, `GET ${path}`) };
27436
27522
  }
27437
27523
  async function serverPost(path, body) {
27438
27524
  const t = requireTarget();
@@ -27442,7 +27528,7 @@ async function serverPost(path, body) {
27442
27528
  body: JSON.stringify(body)
27443
27529
  });
27444
27530
  if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
27445
- return res.json();
27531
+ return readJsonBody(res, t.base, `POST ${path}`);
27446
27532
  }
27447
27533
  async function serverPatch(path, body) {
27448
27534
  const t = requireTarget();
@@ -27452,7 +27538,7 @@ async function serverPatch(path, body) {
27452
27538
  body: JSON.stringify(body)
27453
27539
  });
27454
27540
  if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
27455
- return res.json();
27541
+ return readJsonBody(res, t.base, `PATCH ${path}`);
27456
27542
  }
27457
27543
  async function serverDelete(path, body) {
27458
27544
  const t = requireTarget();
@@ -27462,7 +27548,7 @@ async function serverDelete(path, body) {
27462
27548
  body: JSON.stringify(body)
27463
27549
  });
27464
27550
  if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
27465
- return res.json();
27551
+ return readJsonBody(res, t.base, `DELETE ${path}`);
27466
27552
  }
27467
27553
  var pkgEngines = (() => {
27468
27554
  try {
@@ -27474,7 +27560,7 @@ var pkgEngines = (() => {
27474
27560
  var pkgVersion = JSON.parse(
27475
27561
  readFileSync49(new URL("../package.json", import.meta.url), "utf-8")
27476
27562
  ).version;
27477
- var DEFAULT_SERVER = "https://chatrecall.dev";
27563
+ var DEFAULT_SERVER = (process.env.CHAT_RECALL_DEFAULT_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
27478
27564
  var DEFAULT_ALLOW = [
27479
27565
  "recall_search",
27480
27566
  "recall_show",
@@ -27586,6 +27672,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
27586
27672
  console.log();
27587
27673
  console.log(chalk.bold("2. Connecting to your server..."));
27588
27674
  let target = firstTarget();
27675
+ let deadTarget = false;
27589
27676
  if (options.server) {
27590
27677
  try {
27591
27678
  await runLogin(options.server, { token: options.token, fromInit: true, promptJson: options.promptJson });
@@ -27605,7 +27692,34 @@ program.command("init").description("Set up chat-recall: connect to your server,
27605
27692
  console.log(` ${chalk.dim(`Run ${chalk.bold("chat-recall login <server-url>")} when ready, or self-host: ${SELF_HOST_DOCS}`)}`);
27606
27693
  }
27607
27694
  }
27608
- if (target) {
27695
+ if (target && !options.server) {
27696
+ const { probeServer: probeServer2, probeOk: probeOk2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
27697
+ const probe = await probeServer2(target.base, { timeoutMs: 1e4 });
27698
+ if (probeOk2(probe)) {
27699
+ console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
27700
+ } else if (probe.kind === "moved" || probe.kind === "not_api") {
27701
+ const retired = target.base;
27702
+ console.log(` ${chalk.yellow("The saved server no longer serves chat-recall")} \u2192 ${retired}`);
27703
+ console.log(` ${probeAdvice2(probe, retired)}`);
27704
+ console.log(` Switching to ${chalk.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
27705
+ try {
27706
+ await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
27707
+ const { removeCredentials: removeCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
27708
+ if (removeCredentials2(retired)) {
27709
+ console.log(` ${chalk.dim(`Removed the retired target ${retired} (log in there again any time).`)}`);
27710
+ }
27711
+ target = firstTarget();
27712
+ if (target) console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
27713
+ } catch (e) {
27714
+ console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
27715
+ console.log(` ${chalk.dim(`Run ${chalk.bold(`chat-recall login ${DEFAULT_SERVER}`)} when ready, then ${chalk.bold(`chat-recall logout ${retired}`)}.`)}`);
27716
+ deadTarget = true;
27717
+ }
27718
+ } else {
27719
+ console.log(` ${chalk.yellow("Saved server did not answer")} \u2192 ${target.base}`);
27720
+ console.log(` ${probeAdvice2(probe, target.base)}`);
27721
+ }
27722
+ } else if (target) {
27609
27723
  console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
27610
27724
  }
27611
27725
  console.log();
@@ -27714,22 +27828,40 @@ program.command("init").description("Set up chat-recall: connect to your server,
27714
27828
  const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
27715
27829
  const { hostname: hostname3 } = await import("node:os");
27716
27830
  const rows = [];
27831
+ let answered = 0;
27832
+ const askFailed = [];
27717
27833
  for (const t of loadAllCredentials2()) {
27718
27834
  try {
27719
27835
  const res = await fetch(`${t.serverUrl}/api/toolkit/browse/mcp?limit=1000`, {
27720
27836
  headers: t.token ? { authorization: `Bearer ${t.token}` } : {},
27721
27837
  signal: AbortSignal.timeout(2e4)
27722
27838
  });
27723
- if (!res.ok) continue;
27724
- const body = await res.json();
27839
+ if (!res.ok) {
27840
+ askFailed.push(`${t.serverUrl} (HTTP ${res.status})`);
27841
+ continue;
27842
+ }
27843
+ const text = await res.text();
27844
+ let body;
27845
+ try {
27846
+ body = JSON.parse(text);
27847
+ } catch {
27848
+ askFailed.push(`${t.serverUrl} (not JSON: ${JSON.stringify(text.trim().slice(0, 40))})`);
27849
+ continue;
27850
+ }
27851
+ answered++;
27725
27852
  for (const it of body.items || []) rows.push(it);
27726
- } catch {
27853
+ } catch (e) {
27854
+ askFailed.push(`${t.serverUrl} (${describeProbeError(e)})`);
27727
27855
  }
27728
27856
  }
27729
27857
  const report = executePull2(rows, { thisDeviceId: hostname3(), types: ["mcp"] });
27730
27858
  const written = report.outcomes.filter((o) => o.status === "written");
27731
27859
  const present = report.outcomes.filter((o) => o.status === "present").length;
27732
- if (written.length === 0 && present === 0) {
27860
+ if (answered === 0) {
27861
+ console.log(` ${chalk.yellow("Could not ask your server what MCP servers are on your account")}`);
27862
+ for (const f of askFailed) console.log(` ${chalk.dim(f)}`);
27863
+ console.log(` ${chalk.dim("Re-run `chat-recall toolkit pull` once the server answers.")}`);
27864
+ } else if (written.length === 0 && present === 0) {
27733
27865
  console.log(chalk.dim(" Nothing on your account to install yet."));
27734
27866
  } else {
27735
27867
  console.log(` ${chalk.green(`Installed ${written.length}`)} ${chalk.dim(`(${present} already present)`)}`);
@@ -27744,7 +27876,10 @@ program.command("init").description("Set up chat-recall: connect to your server,
27744
27876
  }
27745
27877
  console.log();
27746
27878
  }
27747
- if (!options.skipSync && firstTarget()) {
27879
+ if (deadTarget) {
27880
+ console.log(chalk.bold("5. Skipping first sync \u2014 the saved server is not serving chat-recall"));
27881
+ console.log(` ${chalk.dim(`Re-point with the two commands above, then run ${chalk.bold("chat-recall sync")}.`)}`);
27882
+ } else if (!options.skipSync && firstTarget()) {
27748
27883
  console.log(chalk.bold("5. Indexing and uploading your sessions"));
27749
27884
  try {
27750
27885
  const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
@@ -28246,7 +28381,7 @@ program.command("doctor").description("Quick health check across login, server,
28246
28381
  }
28247
28382
  }
28248
28383
  for (const hooksJson of claudeHookConfigFiles()) {
28249
- const profile = basename28(dirname27(hooksJson));
28384
+ const profile = basename29(dirname27(hooksJson));
28250
28385
  const label = `Claude hooks (${profile})`;
28251
28386
  if (!existsSync57(hooksJson)) {
28252
28387
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
@@ -29602,7 +29737,7 @@ async function runLogin(serverUrl, opts) {
29602
29737
  });
29603
29738
  return { ok: res.ok, status: res.status };
29604
29739
  } catch (err) {
29605
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
29740
+ return { ok: false, error: describeUnreachable(err) };
29606
29741
  }
29607
29742
  };
29608
29743
  if (opts.token) {
@@ -29611,12 +29746,21 @@ async function runLogin(serverUrl, opts) {
29611
29746
  console.error(chalk.dim("Use the Copy button next to the token in the dashboard and paste the full line."));
29612
29747
  return failLogin(opts, 'the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026")');
29613
29748
  }
29749
+ const { probeServer: identifyServer, probeOk: identified, probeAdvice: identifyAdvice } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
29750
+ const who = await identifyServer(base2, { timeoutMs: 1e4 });
29751
+ if (!identified(who)) return failLogin(opts, identifyAdvice(who, base2));
29614
29752
  const check = await verifyToken(token);
29615
29753
  if (!check.ok) {
29616
29754
  if (check.status === 401 || check.status === 403) {
29617
29755
  console.error(chalk.dim("Tokens are shown once \u2014 mint a fresh one (Account \u2192 Connect your machine) and copy the whole line."));
29618
29756
  return failLogin(opts, `${base2} rejected the token (HTTP ${check.status})`);
29619
29757
  }
29758
+ if (check.status !== void 0 && check.status >= 500) {
29759
+ return failLogin(opts, `${base2} answered HTTP ${check.status} \u2014 the server is up but failing. The token was never checked. Nothing to fix locally.`);
29760
+ }
29761
+ if (check.status === 404 || check.status === 410) {
29762
+ return failLogin(opts, `${base2} does not serve the chat-recall API (HTTP ${check.status}). If the server moved, log in to its new URL.`);
29763
+ }
29620
29764
  return failLogin(opts, `could not verify the token against ${base2}: ${check.error || `HTTP ${check.status}`}`);
29621
29765
  }
29622
29766
  saveCredentials2({ serverUrl, token });
@@ -29629,11 +29773,15 @@ async function runLogin(serverUrl, opts) {
29629
29773
  return;
29630
29774
  }
29631
29775
  try {
29632
- const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
29633
- if (probe.status === 200) {
29634
- saveCredentials2({ serverUrl, token: "" });
29635
- console.log(chalk.green("\u2713 Logged in.") + chalk.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
29636
- return;
29776
+ const { probeServer: probeServer2, probeOk: probeOk2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
29777
+ const identified = await probeServer2(base2, { timeoutMs: 8e3 });
29778
+ if (probeOk2(identified)) {
29779
+ const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
29780
+ if (probe.status === 200) {
29781
+ saveCredentials2({ serverUrl, token: "" });
29782
+ console.log(chalk.green("\u2713 Logged in.") + chalk.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
29783
+ return;
29784
+ }
29637
29785
  }
29638
29786
  } catch {
29639
29787
  }
@@ -29651,14 +29799,12 @@ async function runLogin(serverUrl, opts) {
29651
29799
  for (let attempt = 1; attempt <= 3 && !authProvider; attempt++) {
29652
29800
  const probe = await probeServer2(base2, { timeoutMs: 15e3 });
29653
29801
  if (probeOk2(probe)) {
29654
- try {
29655
- const caps = await fetch(`${base2}/api/capabilities`, { signal: AbortSignal.timeout(15e3) }).then((r) => r.json());
29656
- authProvider = caps?.authProvider;
29657
- if (caps?.oidcIssuer) issuer = caps.oidcIssuer;
29658
- capsError = null;
29659
- } catch (err) {
29660
- capsError = describeProbeError(err);
29661
- }
29802
+ const caps = probe.raw;
29803
+ authProvider = caps?.authProvider;
29804
+ if (caps?.oidcIssuer) issuer = caps.oidcIssuer;
29805
+ capsError = null;
29806
+ } else if (probe.kind === "moved" || probe.kind === "not_api") {
29807
+ return failLogin(opts, probeAdvice2(probe, base2));
29662
29808
  } else {
29663
29809
  capsError = probeAdvice2(probe, base2);
29664
29810
  }
@@ -29686,12 +29832,16 @@ async function runLogin(serverUrl, opts) {
29686
29832
  console.log(chalk.bold("To log in, open:"));
29687
29833
  console.log(" " + chalk.cyan(p.url));
29688
29834
  console.log(chalk.dim(` (if prompted, enter code: ${chalk.bold(p.userCode)} at ${p.verificationUri})`));
29835
+ const opened = openBrowser(p.url);
29836
+ if (opened) console.log(chalk.dim(" (opening your browser\u2026 if nothing happens, use the link above)"));
29689
29837
  console.log(chalk.dim("After approving, come back to THIS terminal \u2014 it finishes the login by itself."));
29690
29838
  console.log(chalk.dim("Waiting for approval\u2026"));
29691
29839
  };
29692
29840
  const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
29693
29841
  const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };
29694
- const me = await fetch(`${base2}/api/me`, { headers: authHdr }).then((r) => r.json());
29842
+ const meRes = await fetch(`${base2}/api/me`, { headers: authHdr });
29843
+ if (!meRes.ok) throw new Error(`${base2}/api/me: HTTP ${meRes.status} ${await meRes.text().catch(() => "")}`);
29844
+ const me = await readJsonBody(meRes, base2, "GET /api/me");
29695
29845
  const teams = me.teams || [];
29696
29846
  let slug = opts.team;
29697
29847
  if (!slug) {
@@ -29700,7 +29850,7 @@ async function runLogin(serverUrl, opts) {
29700
29850
  const name = me.user?.email?.split("@")[0]?.replace(/[^a-z0-9]/gi, "") || "workspace";
29701
29851
  const created = await fetch(`${base2}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
29702
29852
  if (!created.ok) throw new Error(`workspace create failed: HTTP ${created.status} ${await created.text().catch(() => "")}`);
29703
- slug = (await created.json()).slug;
29853
+ slug = (await readJsonBody(created, base2, "POST /api/teams")).slug;
29704
29854
  console.log(chalk.dim(`Created workspace "${name}" (${slug}) \u2014 you're its owner.`));
29705
29855
  } else {
29706
29856
  teams.forEach((t) => console.error(` ${t.team_slug} ${chalk.dim(t.name)}`));
@@ -29714,7 +29864,7 @@ async function runLogin(serverUrl, opts) {
29714
29864
  body: JSON.stringify({ device_id: deviceId })
29715
29865
  });
29716
29866
  if (!mint.ok) throw new Error(`token mint failed: HTTP ${mint.status} ${await mint.text().catch(() => "")}`);
29717
- const { token } = await mint.json();
29867
+ const { token } = await readJsonBody(mint, base2, "POST /api/teams/:slug/tokens");
29718
29868
  saveCredentials2({ serverUrl, token });
29719
29869
  console.log(chalk.green(`\u2713 Logged in to ${chalk.bold(slug)}`) + chalk.dim(` (device: ${deviceId}) server: ${serverUrl}`));
29720
29870
  if (!opts.fromInit) {