chat-recall 0.5.21 → 0.5.23
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 +398 -271
- package/dist/cli.js.map +1 -1
- package/dist/conformant-server.d.ts +17 -0
- package/dist/conformant-server.d.ts.map +1 -0
- package/dist/conformant-server.js +116 -0
- package/dist/conformant-server.js.map +1 -0
- package/dist/device-auth.d.ts +0 -4
- package/dist/device-auth.d.ts.map +1 -1
- package/dist/device-auth.js +43 -2
- package/dist/device-auth.js.map +1 -1
- package/dist/hostile-server.d.ts +47 -0
- package/dist/hostile-server.d.ts.map +1 -0
- package/dist/hostile-server.js +95 -0
- package/dist/hostile-server.js.map +1 -0
- package/dist/install-report.d.ts +21 -0
- package/dist/install-report.d.ts.map +1 -0
- package/dist/install-report.js +76 -0
- package/dist/install-report.js.map +1 -0
- package/dist/mcp.js +160 -8
- package/dist/server-probe.d.ts +35 -1
- package/dist/server-probe.d.ts.map +1 -1
- package/dist/server-probe.js +124 -3
- package/dist/server-probe.js.map +1 -1
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +47 -2
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +250 -103
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -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.
|
|
19733
|
+
if (true) CLI_VERSION = "0.5.23";
|
|
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.
|
|
20245
|
+
return true ? "0.5.23" : "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
|
-
|
|
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
|
|
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.
|
|
22220
|
+
if (true) ownVersion = "0.5.23";
|
|
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;
|
|
@@ -22237,75 +22389,6 @@ var init_which = __esm({
|
|
|
22237
22389
|
}
|
|
22238
22390
|
});
|
|
22239
22391
|
|
|
22240
|
-
// ../engine/dist/core/sync-scope.js
|
|
22241
|
-
var sync_scope_exports = {};
|
|
22242
|
-
__export(sync_scope_exports, {
|
|
22243
|
-
summariseSyncScope: () => summariseSyncScope
|
|
22244
|
-
});
|
|
22245
|
-
function summariseSyncScope(sinceMs = 0) {
|
|
22246
|
-
const sync = loadSettings().sync;
|
|
22247
|
-
const excludeProjects = sync?.excludeProjects ?? [];
|
|
22248
|
-
const excludeTools = new Set(sync?.excludeTools ?? []);
|
|
22249
|
-
const allowlistMode = (sync?.syncMode ?? "all") === "only";
|
|
22250
|
-
const syncOnly2 = new Set(sync?.syncOnlyProjects ?? []);
|
|
22251
|
-
const perProject = /* @__PURE__ */ new Map();
|
|
22252
|
-
const perTool = /* @__PURE__ */ new Map();
|
|
22253
|
-
for (const backend of listAvailableBackends()) {
|
|
22254
|
-
let refs = [];
|
|
22255
|
-
try {
|
|
22256
|
-
refs = backend.listSessions({ sinceMs });
|
|
22257
|
-
} catch {
|
|
22258
|
-
continue;
|
|
22259
|
-
}
|
|
22260
|
-
for (const ref of refs) {
|
|
22261
|
-
const projectPath = ref.projectPath || "";
|
|
22262
|
-
const toolId = ref.toolId || backend.id;
|
|
22263
|
-
let heldBackBy = null;
|
|
22264
|
-
if (excludeTools.has(toolId))
|
|
22265
|
-
heldBackBy = "excluded-tool";
|
|
22266
|
-
else if (excludeProjects.some((x) => projectPathIncludes(projectPath, x)))
|
|
22267
|
-
heldBackBy = "excluded-project";
|
|
22268
|
-
else if (!isProjectSyncable(idOf(projectPath), projectPath, { syncMode: sync?.syncMode, syncOnly: syncOnly2 })) {
|
|
22269
|
-
heldBackBy = "not-in-allowlist";
|
|
22270
|
-
}
|
|
22271
|
-
const id = idOf(projectPath);
|
|
22272
|
-
const key2 = `${id}\0${projectPath}`;
|
|
22273
|
-
const row = perProject.get(key2) ?? { id, projectPath, sessions: 0, heldBackBy };
|
|
22274
|
-
row.sessions++;
|
|
22275
|
-
row.heldBackBy = row.heldBackBy ?? heldBackBy;
|
|
22276
|
-
perProject.set(key2, row);
|
|
22277
|
-
if (!heldBackBy)
|
|
22278
|
-
perTool.set(toolId, (perTool.get(toolId) ?? 0) + 1);
|
|
22279
|
-
}
|
|
22280
|
-
}
|
|
22281
|
-
const projects = [...perProject.values()].sort((a, b) => b.sessions - a.sessions);
|
|
22282
|
-
const noPathSessions = projects.filter((p) => !p.projectPath && !p.heldBackBy).reduce((n, p) => n + p.sessions, 0);
|
|
22283
|
-
return {
|
|
22284
|
-
included: projects.filter((p) => !p.heldBackBy).reduce((n, p) => n + p.sessions, 0),
|
|
22285
|
-
heldBack: projects.filter((p) => p.heldBackBy).reduce((n, p) => n + p.sessions, 0),
|
|
22286
|
-
byTool: [...perTool.entries()].map(([tool, sessions]) => ({ tool, sessions })).sort((a, b) => b.sessions - a.sessions),
|
|
22287
|
-
projects,
|
|
22288
|
-
allowlistMode,
|
|
22289
|
-
noPathSessions
|
|
22290
|
-
};
|
|
22291
|
-
}
|
|
22292
|
-
function idOf(projectPath) {
|
|
22293
|
-
if (!projectPath)
|
|
22294
|
-
return "";
|
|
22295
|
-
const r = resolveProjectId(projectPath);
|
|
22296
|
-
return r.source === "ignored" ? "" : r.id;
|
|
22297
|
-
}
|
|
22298
|
-
var init_sync_scope = __esm({
|
|
22299
|
-
"../engine/dist/core/sync-scope.js"() {
|
|
22300
|
-
"use strict";
|
|
22301
|
-
init_tool_backend();
|
|
22302
|
-
init_backends();
|
|
22303
|
-
init_project_resolver();
|
|
22304
|
-
init_settings();
|
|
22305
|
-
init_project_path_match();
|
|
22306
|
-
}
|
|
22307
|
-
});
|
|
22308
|
-
|
|
22309
22392
|
// ../engine/dist/core/mcp-clients.js
|
|
22310
22393
|
var mcp_clients_exports = {};
|
|
22311
22394
|
__export(mcp_clients_exports, {
|
|
@@ -23571,7 +23654,7 @@ __export(toolkit_pull_exports, {
|
|
|
23571
23654
|
portableCommand: () => portableCommand
|
|
23572
23655
|
});
|
|
23573
23656
|
import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as statSync42, writeFileSync as writeFileSync15 } from "node:fs";
|
|
23574
|
-
import {
|
|
23657
|
+
import { dirname as dirname22, join as join52, basename as basename25, isAbsolute as isAbsolute3 } from "node:path";
|
|
23575
23658
|
function isExecutableFile(p) {
|
|
23576
23659
|
try {
|
|
23577
23660
|
return statSync42(p).isFile();
|
|
@@ -23580,13 +23663,7 @@ function isExecutableFile(p) {
|
|
|
23580
23663
|
}
|
|
23581
23664
|
}
|
|
23582
23665
|
function onPath(name) {
|
|
23583
|
-
|
|
23584
|
-
for (const d of dirs) {
|
|
23585
|
-
const candidate = join52(d, name);
|
|
23586
|
-
if (isExecutableFile(candidate))
|
|
23587
|
-
return candidate;
|
|
23588
|
-
}
|
|
23589
|
-
return null;
|
|
23666
|
+
return resolveOnPath(name);
|
|
23590
23667
|
}
|
|
23591
23668
|
function portableCommand(cmd) {
|
|
23592
23669
|
if (!isAbsolute3(cmd)) {
|
|
@@ -23848,6 +23925,7 @@ var NOT_REBUILDABLE;
|
|
|
23848
23925
|
var init_toolkit_pull = __esm({
|
|
23849
23926
|
"../engine/dist/core/toolkit-pull.js"() {
|
|
23850
23927
|
"use strict";
|
|
23928
|
+
init_which();
|
|
23851
23929
|
init_toolkit_sync();
|
|
23852
23930
|
init_artifact_codec();
|
|
23853
23931
|
NOT_REBUILDABLE = {
|
|
@@ -24214,74 +24292,6 @@ var init_escalate = __esm({
|
|
|
24214
24292
|
}
|
|
24215
24293
|
});
|
|
24216
24294
|
|
|
24217
|
-
// src/server-probe.ts
|
|
24218
|
-
var server_probe_exports = {};
|
|
24219
|
-
__export(server_probe_exports, {
|
|
24220
|
-
probeAdvice: () => probeAdvice,
|
|
24221
|
-
probeOk: () => probeOk,
|
|
24222
|
-
probeServer: () => probeServer
|
|
24223
|
-
});
|
|
24224
|
-
function probeOk(r) {
|
|
24225
|
-
return r.kind === "ok" && r.apiVersion >= 2;
|
|
24226
|
-
}
|
|
24227
|
-
async function probeServer(base2, opts = {}) {
|
|
24228
|
-
const f = opts.fetchImpl || fetch;
|
|
24229
|
-
let res;
|
|
24230
|
-
try {
|
|
24231
|
-
res = await f(`${base2.replace(/\/+$/, "")}/api/capabilities`, {
|
|
24232
|
-
signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
|
|
24233
|
-
});
|
|
24234
|
-
} catch (err) {
|
|
24235
|
-
return { kind: "unreachable", error: err instanceof Error ? err.message : String(err) };
|
|
24236
|
-
}
|
|
24237
|
-
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
24238
|
-
if (res.status === 404 || res.status === 410) return { kind: "moved", status: res.status };
|
|
24239
|
-
if (res.status >= 500) return { kind: "server_error", status: res.status };
|
|
24240
|
-
if (!res.ok) return { kind: "moved", status: res.status };
|
|
24241
|
-
let body;
|
|
24242
|
-
try {
|
|
24243
|
-
body = await res.text();
|
|
24244
|
-
} catch (err) {
|
|
24245
|
-
return { kind: "unreachable", error: err instanceof Error ? err.message : String(err) };
|
|
24246
|
-
}
|
|
24247
|
-
let parsed;
|
|
24248
|
-
try {
|
|
24249
|
-
parsed = JSON.parse(body);
|
|
24250
|
-
} catch {
|
|
24251
|
-
return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
|
|
24252
|
-
}
|
|
24253
|
-
if (typeof parsed?.apiVersion !== "number") {
|
|
24254
|
-
return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
|
|
24255
|
-
}
|
|
24256
|
-
return {
|
|
24257
|
-
kind: "ok",
|
|
24258
|
-
apiVersion: parsed.apiVersion,
|
|
24259
|
-
edition: parsed.edition,
|
|
24260
|
-
cliVersion: parsed.cli?.version
|
|
24261
|
-
};
|
|
24262
|
-
}
|
|
24263
|
-
function probeAdvice(r, base2) {
|
|
24264
|
-
switch (r.kind) {
|
|
24265
|
-
case "ok":
|
|
24266
|
-
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`;
|
|
24267
|
-
case "moved":
|
|
24268
|
-
return `HTTP ${r.status} \u2014 nothing is served at ${base2}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
|
|
24269
|
-
case "unauthorized":
|
|
24270
|
-
return `HTTP ${r.status} \u2014 ${base2} rejected the credential. Re-login: \`chat-recall login ` + base2 + "`";
|
|
24271
|
-
case "server_error":
|
|
24272
|
-
return `HTTP ${r.status} \u2014 ${base2} is up but failing. Nothing to fix locally; check the server.`;
|
|
24273
|
-
case "not_api":
|
|
24274
|
-
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`;
|
|
24275
|
-
case "unreachable":
|
|
24276
|
-
return `no HTTP response from ${base2} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
|
|
24277
|
-
}
|
|
24278
|
-
}
|
|
24279
|
-
var init_server_probe = __esm({
|
|
24280
|
-
"src/server-probe.ts"() {
|
|
24281
|
-
"use strict";
|
|
24282
|
-
}
|
|
24283
|
-
});
|
|
24284
|
-
|
|
24285
24295
|
// src/verify-deep.ts
|
|
24286
24296
|
var verify_deep_exports = {};
|
|
24287
24297
|
__export(verify_deep_exports, {
|
|
@@ -25862,7 +25872,7 @@ var init_intent_drain = __esm({
|
|
|
25862
25872
|
ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
25863
25873
|
} catch {
|
|
25864
25874
|
try {
|
|
25865
|
-
if (true) ownVersion2 = "0.5.
|
|
25875
|
+
if (true) ownVersion2 = "0.5.23";
|
|
25866
25876
|
} catch {
|
|
25867
25877
|
}
|
|
25868
25878
|
}
|
|
@@ -27137,6 +27147,24 @@ __export(device_auth_exports, {
|
|
|
27137
27147
|
deviceLogin: () => deviceLogin
|
|
27138
27148
|
});
|
|
27139
27149
|
import { createHash as createHash19, randomBytes } from "node:crypto";
|
|
27150
|
+
async function readDeviceCode(res, where) {
|
|
27151
|
+
const text = await res.text().catch(() => "");
|
|
27152
|
+
let parsed;
|
|
27153
|
+
try {
|
|
27154
|
+
parsed = JSON.parse(text);
|
|
27155
|
+
} catch {
|
|
27156
|
+
throw new Error(
|
|
27157
|
+
`${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`
|
|
27158
|
+
);
|
|
27159
|
+
}
|
|
27160
|
+
const d = parsed;
|
|
27161
|
+
if (typeof d?.device_code !== "string" || typeof d?.user_code !== "string" || typeof d?.verification_uri !== "string" || typeof d?.expires_in !== "number") {
|
|
27162
|
+
throw new Error(
|
|
27163
|
+
`${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`
|
|
27164
|
+
);
|
|
27165
|
+
}
|
|
27166
|
+
return d;
|
|
27167
|
+
}
|
|
27140
27168
|
async function deviceLogin(opts, onPrompt) {
|
|
27141
27169
|
const issuer = (opts.issuer || DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
27142
27170
|
if (!issuer) {
|
|
@@ -27156,7 +27184,7 @@ async function deviceLogin(opts, onPrompt) {
|
|
|
27156
27184
|
if (!startRes.ok) {
|
|
27157
27185
|
throw new Error(`device-auth init failed: HTTP ${startRes.status} ${await startRes.text().catch(() => "")}`);
|
|
27158
27186
|
}
|
|
27159
|
-
const start = await startRes
|
|
27187
|
+
const start = await readDeviceCode(startRes, issuer);
|
|
27160
27188
|
onPrompt({
|
|
27161
27189
|
url: start.verification_uri_complete || start.verification_uri,
|
|
27162
27190
|
userCode: start.user_code,
|
|
@@ -27210,7 +27238,7 @@ async function betterAuthDeviceLogin(serverUrl, onPrompt) {
|
|
|
27210
27238
|
if (!startRes.ok) {
|
|
27211
27239
|
throw new Error(`device-auth init failed: HTTP ${startRes.status} ${await startRes.text().catch(() => "")}`);
|
|
27212
27240
|
}
|
|
27213
|
-
const start = await startRes
|
|
27241
|
+
const start = await readDeviceCode(startRes, base2);
|
|
27214
27242
|
onPrompt({
|
|
27215
27243
|
url: start.verification_uri_complete || start.verification_uri,
|
|
27216
27244
|
userCode: start.user_code,
|
|
@@ -27261,6 +27289,50 @@ var init_device_auth = __esm({
|
|
|
27261
27289
|
}
|
|
27262
27290
|
});
|
|
27263
27291
|
|
|
27292
|
+
// src/install-report.ts
|
|
27293
|
+
var install_report_exports = {};
|
|
27294
|
+
__export(install_report_exports, {
|
|
27295
|
+
reportInstallFailure: () => reportInstallFailure
|
|
27296
|
+
});
|
|
27297
|
+
async function reportInstallFailure(serverUrl, failure, opts = {}) {
|
|
27298
|
+
if (!serverUrl) return false;
|
|
27299
|
+
if (!userConsents() && !mayReport(serverUrl)) return false;
|
|
27300
|
+
const payload = {
|
|
27301
|
+
step: failure.step,
|
|
27302
|
+
reason: trim(failure.reason, 40),
|
|
27303
|
+
message: trim(failure.message, 200),
|
|
27304
|
+
cliVersion: trim(process.env.CHAT_RECALL_CLI_VERSION, 20),
|
|
27305
|
+
node: trim(process.versions.node, 20),
|
|
27306
|
+
os: `${process.platform}-${process.arch}`
|
|
27307
|
+
};
|
|
27308
|
+
try {
|
|
27309
|
+
assertNoSensitiveKeys(payload);
|
|
27310
|
+
} catch {
|
|
27311
|
+
return false;
|
|
27312
|
+
}
|
|
27313
|
+
const f = opts.fetchImpl || fetch;
|
|
27314
|
+
try {
|
|
27315
|
+
const res = await f(`${serverUrl.replace(/\/+$/, "")}/install/report`, {
|
|
27316
|
+
method: "POST",
|
|
27317
|
+
headers: { "content-type": "application/json" },
|
|
27318
|
+
body: JSON.stringify(payload),
|
|
27319
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 3e3)
|
|
27320
|
+
});
|
|
27321
|
+
return res.ok || res.status === 204;
|
|
27322
|
+
} catch {
|
|
27323
|
+
return false;
|
|
27324
|
+
}
|
|
27325
|
+
}
|
|
27326
|
+
function trim(v, n) {
|
|
27327
|
+
return typeof v === "string" && v.trim() ? v.trim().slice(0, n) : null;
|
|
27328
|
+
}
|
|
27329
|
+
var init_install_report = __esm({
|
|
27330
|
+
"src/install-report.ts"() {
|
|
27331
|
+
"use strict";
|
|
27332
|
+
init_telemetry_consent();
|
|
27333
|
+
}
|
|
27334
|
+
});
|
|
27335
|
+
|
|
27264
27336
|
// ../engine/dist/core/resume-command.js
|
|
27265
27337
|
function toolOfSessionId(sessionId) {
|
|
27266
27338
|
if (sessionId.startsWith("codex_"))
|
|
@@ -27337,6 +27409,7 @@ function tierAll(results) {
|
|
|
27337
27409
|
// src/cli.ts
|
|
27338
27410
|
init_sync_client();
|
|
27339
27411
|
init_update_notice();
|
|
27412
|
+
init_server_probe();
|
|
27340
27413
|
init_which();
|
|
27341
27414
|
init_collector_health();
|
|
27342
27415
|
init_telemetry_consent();
|
|
@@ -27377,54 +27450,15 @@ function firstTarget() {
|
|
|
27377
27450
|
if (!cred) return null;
|
|
27378
27451
|
return { base: cred.serverUrl.replace(/\/+$/, ""), token: cred.token };
|
|
27379
27452
|
}
|
|
27380
|
-
|
|
27381
|
-
const
|
|
27382
|
-
|
|
27383
|
-
|
|
27384
|
-
|
|
27385
|
-
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27389
|
-
console.log(chalk.bold("6. What would leave this machine"));
|
|
27390
|
-
if (scope.included === 0 && scope.heldBack === 0) {
|
|
27391
|
-
console.log(chalk.dim(" No local sessions found yet \u2014 nothing to ship."));
|
|
27392
|
-
return true;
|
|
27393
|
-
}
|
|
27394
|
-
const tools = scope.byTool.map((t) => `${t.tool} ${t.sessions}`).join(", ");
|
|
27395
|
-
console.log(` ${chalk.bold(String(scope.included))} session(s) would upload${tools ? chalk.dim(` (${tools})`) : ""}`);
|
|
27396
|
-
if (scope.heldBack > 0) {
|
|
27397
|
-
console.log(` ${chalk.green(String(scope.heldBack))} held back by your existing rules`);
|
|
27398
|
-
}
|
|
27399
|
-
if (scope.allowlistMode) {
|
|
27400
|
-
console.log(chalk.yellow(" Allowlist mode is on \u2014 only the projects you listed would ship."));
|
|
27401
|
-
}
|
|
27402
|
-
if (scope.noPathSessions > 0) {
|
|
27403
|
-
console.log(` ${chalk.yellow(String(scope.noPathSessions))} of those carry no project path \u2014 only \`exclude tool\` can hold those back`);
|
|
27404
|
-
}
|
|
27405
|
-
const shown = scope.projects.filter((p) => !p.heldBackBy).slice(0, 8);
|
|
27406
|
-
for (const p of shown) {
|
|
27407
|
-
console.log(` ${chalk.cyan(p.projectPath || p.id)} ${chalk.dim(`\xB7 ${p.sessions}`)}`);
|
|
27408
|
-
}
|
|
27409
|
-
const more = scope.projects.filter((p) => !p.heldBackBy).length - shown.length;
|
|
27410
|
-
if (more > 0) console.log(chalk.dim(` \u2026and ${more} more project(s)`));
|
|
27411
|
-
console.log(chalk.dim(" Secrets are masked on this machine before anything uploads."));
|
|
27412
|
-
console.log(chalk.dim(" Hold one back: chat-recall exclude project <path>"));
|
|
27413
|
-
console.log(chalk.dim(" Or invert it: chat-recall sync-only add <project>"));
|
|
27414
|
-
if (skipPause || !process.stdin.isTTY) {
|
|
27415
|
-
console.log();
|
|
27416
|
-
return true;
|
|
27417
|
-
}
|
|
27418
|
-
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
27419
|
-
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
27420
|
-
const answer = (await rl.question(" Upload these now? [Y/n] ")).trim().toLowerCase();
|
|
27421
|
-
rl.close();
|
|
27422
|
-
if (answer === "n" || answer === "no") {
|
|
27423
|
-
console.log(chalk.dim(" Skipped. Set your rules, then run `chat-recall sync`."));
|
|
27424
|
-
return false;
|
|
27425
|
-
}
|
|
27426
|
-
console.log();
|
|
27427
|
-
return true;
|
|
27453
|
+
function describeProbeError(err) {
|
|
27454
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
27455
|
+
if (!/fetch failed|ECONN|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|CERT_|aborted|timeout/i.test(msg)) return msg;
|
|
27456
|
+
return describeUnreachable(err);
|
|
27457
|
+
}
|
|
27458
|
+
function failLogin(opts, message) {
|
|
27459
|
+
if (opts.fromInit) throw new Error(message);
|
|
27460
|
+
console.error(chalk.red("login failed:"), message);
|
|
27461
|
+
process.exit(1);
|
|
27428
27462
|
}
|
|
27429
27463
|
function requireTarget() {
|
|
27430
27464
|
const t = firstTarget();
|
|
@@ -27434,11 +27468,21 @@ function requireTarget() {
|
|
|
27434
27468
|
}
|
|
27435
27469
|
return t;
|
|
27436
27470
|
}
|
|
27471
|
+
async function readJsonBody(res, base2, what) {
|
|
27472
|
+
const text = await res.text().catch(() => "");
|
|
27473
|
+
try {
|
|
27474
|
+
return JSON.parse(text);
|
|
27475
|
+
} catch {
|
|
27476
|
+
throw new Error(
|
|
27477
|
+
`${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`
|
|
27478
|
+
);
|
|
27479
|
+
}
|
|
27480
|
+
}
|
|
27437
27481
|
async function serverGet(path) {
|
|
27438
27482
|
const t = requireTarget();
|
|
27439
27483
|
const res = await fetch(t.base + path, { headers: t.token ? { authorization: `Bearer ${t.token}` } : {} });
|
|
27440
27484
|
if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
27441
|
-
return res.
|
|
27485
|
+
return readJsonBody(res, t.base, `GET ${path}`);
|
|
27442
27486
|
}
|
|
27443
27487
|
async function serverGetSoft(path) {
|
|
27444
27488
|
const t = requireTarget();
|
|
@@ -27448,7 +27492,7 @@ async function serverGetSoft(path) {
|
|
|
27448
27492
|
return { status: res.status, data: null, message: body.message };
|
|
27449
27493
|
}
|
|
27450
27494
|
if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
27451
|
-
return { status: res.status, data: await res.
|
|
27495
|
+
return { status: res.status, data: await readJsonBody(res, t.base, `GET ${path}`) };
|
|
27452
27496
|
}
|
|
27453
27497
|
async function serverPost(path, body) {
|
|
27454
27498
|
const t = requireTarget();
|
|
@@ -27458,7 +27502,7 @@ async function serverPost(path, body) {
|
|
|
27458
27502
|
body: JSON.stringify(body)
|
|
27459
27503
|
});
|
|
27460
27504
|
if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
27461
|
-
return res.
|
|
27505
|
+
return readJsonBody(res, t.base, `POST ${path}`);
|
|
27462
27506
|
}
|
|
27463
27507
|
async function serverPatch(path, body) {
|
|
27464
27508
|
const t = requireTarget();
|
|
@@ -27468,7 +27512,7 @@ async function serverPatch(path, body) {
|
|
|
27468
27512
|
body: JSON.stringify(body)
|
|
27469
27513
|
});
|
|
27470
27514
|
if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
27471
|
-
return res.
|
|
27515
|
+
return readJsonBody(res, t.base, `PATCH ${path}`);
|
|
27472
27516
|
}
|
|
27473
27517
|
async function serverDelete(path, body) {
|
|
27474
27518
|
const t = requireTarget();
|
|
@@ -27478,12 +27522,19 @@ async function serverDelete(path, body) {
|
|
|
27478
27522
|
body: JSON.stringify(body)
|
|
27479
27523
|
});
|
|
27480
27524
|
if (!res.ok) throw new Error(`server ${path}: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
27481
|
-
return res.
|
|
27525
|
+
return readJsonBody(res, t.base, `DELETE ${path}`);
|
|
27482
27526
|
}
|
|
27527
|
+
var pkgEngines = (() => {
|
|
27528
|
+
try {
|
|
27529
|
+
return JSON.parse(readFileSync49(new URL("../package.json", import.meta.url), "utf-8")).engines?.node ?? "";
|
|
27530
|
+
} catch {
|
|
27531
|
+
return "";
|
|
27532
|
+
}
|
|
27533
|
+
})();
|
|
27483
27534
|
var pkgVersion = JSON.parse(
|
|
27484
27535
|
readFileSync49(new URL("../package.json", import.meta.url), "utf-8")
|
|
27485
27536
|
).version;
|
|
27486
|
-
var DEFAULT_SERVER = "https://chatrecall.dev";
|
|
27537
|
+
var DEFAULT_SERVER = (process.env.CHAT_RECALL_DEFAULT_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
|
|
27487
27538
|
var DEFAULT_ALLOW = [
|
|
27488
27539
|
"recall_search",
|
|
27489
27540
|
"recall_show",
|
|
@@ -27556,11 +27607,21 @@ var DEFAULT_ALLOW = [
|
|
|
27556
27607
|
"recall_toolkit_sync"
|
|
27557
27608
|
];
|
|
27558
27609
|
var SELF_HOST_DOCS = "https://github.com/munhq/chat-recall/blob/main/docs/SELF_HOSTING.md";
|
|
27610
|
+
function enforceNodeVersion() {
|
|
27611
|
+
const required = Number(/(\d+)/.exec(String(pkgEngines))?.[1] ?? 0);
|
|
27612
|
+
const found = Number(process.versions.node.split(".")[0]);
|
|
27613
|
+
if (!required || !found || found >= required) return;
|
|
27614
|
+
console.error(`chat-recall needs Node ${required} or newer. This is Node ${process.versions.node}.`);
|
|
27615
|
+
console.error(`Install it with your version manager (\`nvm install ${required}\`, \`fnm install ${required}\`, \`brew upgrade node\`), then run this again.`);
|
|
27616
|
+
process.exit(1);
|
|
27617
|
+
}
|
|
27618
|
+
process.env.CHAT_RECALL_CLI_VERSION = pkgVersion;
|
|
27619
|
+
enforceNodeVersion();
|
|
27559
27620
|
var program = new Command();
|
|
27560
27621
|
program.name("chat-recall").description("Semantic search for Claude Code sessions - recall and resume past conversations").version(pkgVersion);
|
|
27561
27622
|
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) => {
|
|
27562
27623
|
try {
|
|
27563
|
-
console.log(chalk.bold("chat-recall init"));
|
|
27624
|
+
console.log(chalk.bold("chat-recall init") + chalk.dim(` v${pkgVersion}`));
|
|
27564
27625
|
console.log();
|
|
27565
27626
|
console.log(chalk.bold("1. Detecting AI tools..."));
|
|
27566
27627
|
const clis = [
|
|
@@ -27585,9 +27646,15 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27585
27646
|
console.log();
|
|
27586
27647
|
console.log(chalk.bold("2. Connecting to your server..."));
|
|
27587
27648
|
let target = firstTarget();
|
|
27649
|
+
let deadTarget = false;
|
|
27588
27650
|
if (options.server) {
|
|
27589
|
-
|
|
27590
|
-
|
|
27651
|
+
try {
|
|
27652
|
+
await runLogin(options.server, { token: options.token, fromInit: true, promptJson: options.promptJson });
|
|
27653
|
+
target = firstTarget();
|
|
27654
|
+
} catch (e) {
|
|
27655
|
+
console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
|
|
27656
|
+
console.log(` ${chalk.dim(`Run ${chalk.bold(`chat-recall login ${options.server}`)} when ready, or self-host: ${SELF_HOST_DOCS}`)}`);
|
|
27657
|
+
}
|
|
27591
27658
|
} else if (!target) {
|
|
27592
27659
|
console.log(` Connecting to ${chalk.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
|
|
27593
27660
|
console.log(` ${chalk.dim(`Prefer your own server? ${chalk.bold("--server <url>")}. Self-hosting is free: ${SELF_HOST_DOCS}`)}`);
|
|
@@ -27595,16 +27662,43 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27595
27662
|
await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
|
|
27596
27663
|
target = firstTarget();
|
|
27597
27664
|
} catch (e) {
|
|
27598
|
-
console.log(` ${chalk.yellow("Not connected.")} ${e
|
|
27665
|
+
console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
|
|
27599
27666
|
console.log(` ${chalk.dim(`Run ${chalk.bold("chat-recall login <server-url>")} when ready, or self-host: ${SELF_HOST_DOCS}`)}`);
|
|
27600
27667
|
}
|
|
27601
27668
|
}
|
|
27602
|
-
if (target) {
|
|
27669
|
+
if (target && !options.server) {
|
|
27670
|
+
const { probeServer: probeServer2, probeOk: probeOk2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
27671
|
+
const probe = await probeServer2(target.base, { timeoutMs: 1e4 });
|
|
27672
|
+
if (probeOk2(probe)) {
|
|
27673
|
+
console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
|
|
27674
|
+
} else if (probe.kind === "moved" || probe.kind === "not_api") {
|
|
27675
|
+
const retired = target.base;
|
|
27676
|
+
console.log(` ${chalk.yellow("The saved server no longer serves chat-recall")} \u2192 ${retired}`);
|
|
27677
|
+
console.log(` ${probeAdvice2(probe, retired)}`);
|
|
27678
|
+
console.log(` Switching to ${chalk.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
|
|
27679
|
+
try {
|
|
27680
|
+
await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
|
|
27681
|
+
const { removeCredentials: removeCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
27682
|
+
if (removeCredentials2(retired)) {
|
|
27683
|
+
console.log(` ${chalk.dim(`Removed the retired target ${retired} (log in there again any time).`)}`);
|
|
27684
|
+
}
|
|
27685
|
+
target = firstTarget();
|
|
27686
|
+
if (target) console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
|
|
27687
|
+
} catch (e) {
|
|
27688
|
+
console.log(` ${chalk.yellow("Not connected.")} ${describeProbeError(e)}`);
|
|
27689
|
+
console.log(` ${chalk.dim(`Run ${chalk.bold(`chat-recall login ${DEFAULT_SERVER}`)} when ready, then ${chalk.bold(`chat-recall logout ${retired}`)}.`)}`);
|
|
27690
|
+
deadTarget = true;
|
|
27691
|
+
}
|
|
27692
|
+
} else {
|
|
27693
|
+
console.log(` ${chalk.yellow("Saved server did not answer")} \u2192 ${target.base}`);
|
|
27694
|
+
console.log(` ${probeAdvice2(probe, target.base)}`);
|
|
27695
|
+
}
|
|
27696
|
+
} else if (target) {
|
|
27603
27697
|
console.log(` ${chalk.green("Connected")} \u2192 ${target.base}`);
|
|
27604
27698
|
}
|
|
27605
27699
|
console.log();
|
|
27606
27700
|
if (!options.skipMcp) {
|
|
27607
|
-
console.log(chalk.bold("
|
|
27701
|
+
console.log(chalk.bold("3. Configuring MCP server..."));
|
|
27608
27702
|
const projectRoot = join60(import.meta.dirname, "..");
|
|
27609
27703
|
let mcpBinOnPath = false;
|
|
27610
27704
|
mcpBinOnPath = isOnPath("chat-recall-mcp");
|
|
@@ -27629,13 +27723,13 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27629
27723
|
}
|
|
27630
27724
|
console.log(chalk.dim(` Launch: ${launch.command}${launch.args ? " " + launch.args.join(" ") : ""}`));
|
|
27631
27725
|
} else {
|
|
27632
|
-
console.log(chalk.bold("
|
|
27726
|
+
console.log(chalk.bold("3. Skipping MCP configuration (--skip-mcp)"));
|
|
27633
27727
|
}
|
|
27634
27728
|
console.log();
|
|
27635
27729
|
const skipCodeindex = options.skipCodeindex === true;
|
|
27636
27730
|
const forceInstall = options.withCodeindex === true || process.env.CHAT_RECALL_WITH_CODEINDEX === "1";
|
|
27637
27731
|
if (skipCodeindex) {
|
|
27638
|
-
console.log(chalk.bold("
|
|
27732
|
+
console.log(chalk.bold("4. Skipping codeindex companion (--skip-codeindex)"));
|
|
27639
27733
|
} else {
|
|
27640
27734
|
const {
|
|
27641
27735
|
checkCodeindexStatus: checkCodeindexStatus2,
|
|
@@ -27645,7 +27739,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27645
27739
|
} = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
27646
27740
|
const detected = checkCodeindexStatus2();
|
|
27647
27741
|
if (detected.installed && !forceInstall) {
|
|
27648
|
-
console.log(chalk.bold("
|
|
27742
|
+
console.log(chalk.bold("4. Detected codeindex companion"));
|
|
27649
27743
|
console.log(` ${chalk.green("codeindex")}: ${detected.path}`);
|
|
27650
27744
|
if (!options.skipMcp) {
|
|
27651
27745
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
@@ -27654,7 +27748,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27654
27748
|
else console.log(` codeindex MCP server: ${chalk.green("already registered")}`);
|
|
27655
27749
|
}
|
|
27656
27750
|
} else if (forceInstall) {
|
|
27657
|
-
console.log(chalk.bold("
|
|
27751
|
+
console.log(chalk.bold("4. Installing codeindex companion (--with-codeindex)..."));
|
|
27658
27752
|
try {
|
|
27659
27753
|
const result = await installCodeindex2({ force: true });
|
|
27660
27754
|
if (result.installed) {
|
|
@@ -27677,7 +27771,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27677
27771
|
}
|
|
27678
27772
|
}
|
|
27679
27773
|
} else {
|
|
27680
|
-
console.log(chalk.bold("
|
|
27774
|
+
console.log(chalk.bold("4. codeindex companion: not installed"));
|
|
27681
27775
|
console.log(chalk.dim(" codeindex is a separate MCP server for code-level lookup \u2014"));
|
|
27682
27776
|
console.log(chalk.dim(" find_symbol, plan_change, get_change_impact, etc."));
|
|
27683
27777
|
console.log(chalk.dim(" To install: `chat-recall init --with-codeindex`"));
|
|
@@ -27708,22 +27802,40 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27708
27802
|
const { executePull: executePull2 } = await Promise.resolve().then(() => (init_toolkit_pull(), toolkit_pull_exports));
|
|
27709
27803
|
const { hostname: hostname3 } = await import("node:os");
|
|
27710
27804
|
const rows = [];
|
|
27805
|
+
let answered = 0;
|
|
27806
|
+
const askFailed = [];
|
|
27711
27807
|
for (const t of loadAllCredentials2()) {
|
|
27712
27808
|
try {
|
|
27713
27809
|
const res = await fetch(`${t.serverUrl}/api/toolkit/browse/mcp?limit=1000`, {
|
|
27714
27810
|
headers: t.token ? { authorization: `Bearer ${t.token}` } : {},
|
|
27715
27811
|
signal: AbortSignal.timeout(2e4)
|
|
27716
27812
|
});
|
|
27717
|
-
if (!res.ok)
|
|
27718
|
-
|
|
27813
|
+
if (!res.ok) {
|
|
27814
|
+
askFailed.push(`${t.serverUrl} (HTTP ${res.status})`);
|
|
27815
|
+
continue;
|
|
27816
|
+
}
|
|
27817
|
+
const text = await res.text();
|
|
27818
|
+
let body;
|
|
27819
|
+
try {
|
|
27820
|
+
body = JSON.parse(text);
|
|
27821
|
+
} catch {
|
|
27822
|
+
askFailed.push(`${t.serverUrl} (not JSON: ${JSON.stringify(text.trim().slice(0, 40))})`);
|
|
27823
|
+
continue;
|
|
27824
|
+
}
|
|
27825
|
+
answered++;
|
|
27719
27826
|
for (const it of body.items || []) rows.push(it);
|
|
27720
|
-
} catch {
|
|
27827
|
+
} catch (e) {
|
|
27828
|
+
askFailed.push(`${t.serverUrl} (${describeProbeError(e)})`);
|
|
27721
27829
|
}
|
|
27722
27830
|
}
|
|
27723
27831
|
const report = executePull2(rows, { thisDeviceId: hostname3(), types: ["mcp"] });
|
|
27724
27832
|
const written = report.outcomes.filter((o) => o.status === "written");
|
|
27725
27833
|
const present = report.outcomes.filter((o) => o.status === "present").length;
|
|
27726
|
-
if (
|
|
27834
|
+
if (answered === 0) {
|
|
27835
|
+
console.log(` ${chalk.yellow("Could not ask your server what MCP servers are on your account")}`);
|
|
27836
|
+
for (const f of askFailed) console.log(` ${chalk.dim(f)}`);
|
|
27837
|
+
console.log(` ${chalk.dim("Re-run `chat-recall toolkit pull` once the server answers.")}`);
|
|
27838
|
+
} else if (written.length === 0 && present === 0) {
|
|
27727
27839
|
console.log(chalk.dim(" Nothing on your account to install yet."));
|
|
27728
27840
|
} else {
|
|
27729
27841
|
console.log(` ${chalk.green(`Installed ${written.length}`)} ${chalk.dim(`(${present} already present)`)}`);
|
|
@@ -27738,28 +27850,26 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27738
27850
|
}
|
|
27739
27851
|
console.log();
|
|
27740
27852
|
}
|
|
27741
|
-
if (
|
|
27742
|
-
|
|
27743
|
-
|
|
27744
|
-
|
|
27745
|
-
|
|
27746
|
-
|
|
27747
|
-
|
|
27748
|
-
|
|
27749
|
-
|
|
27750
|
-
|
|
27751
|
-
}
|
|
27752
|
-
|
|
27753
|
-
console.log(` ${chalk.dim("Re-run `chat-recall sync` once your server is reachable.")}`);
|
|
27754
|
-
}
|
|
27853
|
+
if (deadTarget) {
|
|
27854
|
+
console.log(chalk.bold("5. Skipping first sync \u2014 the saved server is not serving chat-recall"));
|
|
27855
|
+
console.log(` ${chalk.dim(`Re-point with the two commands above, then run ${chalk.bold("chat-recall sync")}.`)}`);
|
|
27856
|
+
} else if (!options.skipSync && firstTarget()) {
|
|
27857
|
+
console.log(chalk.bold("5. Indexing and uploading your sessions"));
|
|
27858
|
+
try {
|
|
27859
|
+
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
27860
|
+
const r = await syncSessions2();
|
|
27861
|
+
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)` : ""}`)}`);
|
|
27862
|
+
} catch (err) {
|
|
27863
|
+
console.log(` ${chalk.yellow("Sync failed")} \u2014 ${err instanceof Error ? err.message : err}`);
|
|
27864
|
+
console.log(` ${chalk.dim("Re-run `chat-recall sync` once your server is reachable.")}`);
|
|
27755
27865
|
}
|
|
27756
27866
|
} else if (!options.skipSync) {
|
|
27757
|
-
console.log(chalk.bold("
|
|
27867
|
+
console.log(chalk.bold("5. Skipping first sync (not logged in)"));
|
|
27758
27868
|
} else {
|
|
27759
|
-
console.log(chalk.bold("
|
|
27869
|
+
console.log(chalk.bold("5. Skipping first sync (--skip-sync)"));
|
|
27760
27870
|
}
|
|
27761
27871
|
console.log();
|
|
27762
|
-
console.log(chalk.bold("
|
|
27872
|
+
console.log(chalk.bold("6. Background sync"));
|
|
27763
27873
|
console.log(chalk.dim(" Runs via the MCP server while Claude Code is open (no extra service to install)."));
|
|
27764
27874
|
console.log(chalk.dim(" Always-on / headless? Opt in with `chat-recall watch --install-service`."));
|
|
27765
27875
|
void options.skipService;
|
|
@@ -29601,25 +29711,31 @@ async function runLogin(serverUrl, opts) {
|
|
|
29601
29711
|
});
|
|
29602
29712
|
return { ok: res.ok, status: res.status };
|
|
29603
29713
|
} catch (err) {
|
|
29604
|
-
return { ok: false, error:
|
|
29714
|
+
return { ok: false, error: describeUnreachable(err) };
|
|
29605
29715
|
}
|
|
29606
29716
|
};
|
|
29607
29717
|
if (opts.token) {
|
|
29608
29718
|
const token = opts.token.trim();
|
|
29609
29719
|
if (/[^\x21-\x7e]/.test(token)) {
|
|
29610
|
-
console.error(chalk.red('login failed: the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026").'));
|
|
29611
29720
|
console.error(chalk.dim("Use the Copy button next to the token in the dashboard and paste the full line."));
|
|
29612
|
-
|
|
29721
|
+
return failLogin(opts, 'the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026")');
|
|
29613
29722
|
}
|
|
29723
|
+
const { probeServer: identifyServer, probeOk: identified, probeAdvice: identifyAdvice } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
29724
|
+
const who = await identifyServer(base2, { timeoutMs: 1e4 });
|
|
29725
|
+
if (!identified(who)) return failLogin(opts, identifyAdvice(who, base2));
|
|
29614
29726
|
const check = await verifyToken(token);
|
|
29615
29727
|
if (!check.ok) {
|
|
29616
29728
|
if (check.status === 401 || check.status === 403) {
|
|
29617
|
-
console.error(chalk.red(`login failed: ${base2} rejected the token (HTTP ${check.status}).`));
|
|
29618
29729
|
console.error(chalk.dim("Tokens are shown once \u2014 mint a fresh one (Account \u2192 Connect your machine) and copy the whole line."));
|
|
29619
|
-
|
|
29620
|
-
console.error(chalk.red(`login failed: could not verify the token against ${base2}:`), check.error || `HTTP ${check.status}`);
|
|
29730
|
+
return failLogin(opts, `${base2} rejected the token (HTTP ${check.status})`);
|
|
29621
29731
|
}
|
|
29622
|
-
|
|
29732
|
+
if (check.status !== void 0 && check.status >= 500) {
|
|
29733
|
+
return failLogin(opts, `${base2} answered HTTP ${check.status} \u2014 the server is up but failing. The token was never checked. Nothing to fix locally.`);
|
|
29734
|
+
}
|
|
29735
|
+
if (check.status === 404 || check.status === 410) {
|
|
29736
|
+
return failLogin(opts, `${base2} does not serve the chat-recall API (HTTP ${check.status}). If the server moved, log in to its new URL.`);
|
|
29737
|
+
}
|
|
29738
|
+
return failLogin(opts, `could not verify the token against ${base2}: ${check.error || `HTTP ${check.status}`}`);
|
|
29623
29739
|
}
|
|
29624
29740
|
saveCredentials2({ serverUrl, token });
|
|
29625
29741
|
console.log(chalk.green("\u2713 Logged in.") + chalk.dim(` server: ${serverUrl} (token verified)`));
|
|
@@ -29631,11 +29747,15 @@ async function runLogin(serverUrl, opts) {
|
|
|
29631
29747
|
return;
|
|
29632
29748
|
}
|
|
29633
29749
|
try {
|
|
29634
|
-
const
|
|
29635
|
-
|
|
29636
|
-
|
|
29637
|
-
|
|
29638
|
-
|
|
29750
|
+
const { probeServer: probeServer2, probeOk: probeOk2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
29751
|
+
const identified = await probeServer2(base2, { timeoutMs: 8e3 });
|
|
29752
|
+
if (probeOk2(identified)) {
|
|
29753
|
+
const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
|
|
29754
|
+
if (probe.status === 200) {
|
|
29755
|
+
saveCredentials2({ serverUrl, token: "" });
|
|
29756
|
+
console.log(chalk.green("\u2713 Logged in.") + chalk.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
|
|
29757
|
+
return;
|
|
29758
|
+
}
|
|
29639
29759
|
}
|
|
29640
29760
|
} catch {
|
|
29641
29761
|
}
|
|
@@ -29649,19 +29769,18 @@ async function runLogin(serverUrl, opts) {
|
|
|
29649
29769
|
let authProvider;
|
|
29650
29770
|
let capsError = null;
|
|
29651
29771
|
if (!issuer) {
|
|
29772
|
+
const { probeServer: probeServer2, probeOk: probeOk2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
29652
29773
|
for (let attempt = 1; attempt <= 3 && !authProvider; attempt++) {
|
|
29653
|
-
|
|
29654
|
-
|
|
29655
|
-
|
|
29656
|
-
|
|
29657
|
-
|
|
29658
|
-
|
|
29659
|
-
|
|
29660
|
-
|
|
29661
|
-
|
|
29662
|
-
|
|
29663
|
-
} catch (err) {
|
|
29664
|
-
capsError = err instanceof Error ? err.message : String(err);
|
|
29774
|
+
const probe = await probeServer2(base2, { timeoutMs: 15e3 });
|
|
29775
|
+
if (probeOk2(probe)) {
|
|
29776
|
+
const caps = probe.raw;
|
|
29777
|
+
authProvider = caps?.authProvider;
|
|
29778
|
+
if (caps?.oidcIssuer) issuer = caps.oidcIssuer;
|
|
29779
|
+
capsError = null;
|
|
29780
|
+
} else if (probe.kind === "moved" || probe.kind === "not_api") {
|
|
29781
|
+
return failLogin(opts, probeAdvice2(probe, base2));
|
|
29782
|
+
} else {
|
|
29783
|
+
capsError = probeAdvice2(probe, base2);
|
|
29665
29784
|
}
|
|
29666
29785
|
if (!authProvider && attempt < 3) await new Promise((r) => setTimeout(r, attempt * 1e3));
|
|
29667
29786
|
}
|
|
@@ -29670,6 +29789,12 @@ async function runLogin(serverUrl, opts) {
|
|
|
29670
29789
|
authProvider = "better-auth";
|
|
29671
29790
|
if (capsError) {
|
|
29672
29791
|
console.log(` ${chalk.yellow("Could not read the server's capabilities")} (${capsError}) \u2014 assuming the standard sign-in flow.`);
|
|
29792
|
+
void Promise.resolve().then(() => (init_install_report(), install_report_exports)).then(({ reportInstallFailure: reportInstallFailure2 }) => reportInstallFailure2(base2, {
|
|
29793
|
+
step: "cli_login_probe",
|
|
29794
|
+
reason: capsError?.slice(0, 40) ?? null,
|
|
29795
|
+
message: capsError
|
|
29796
|
+
})).catch(() => {
|
|
29797
|
+
});
|
|
29673
29798
|
}
|
|
29674
29799
|
}
|
|
29675
29800
|
const onPrompt = (p) => {
|
|
@@ -29686,7 +29811,9 @@ async function runLogin(serverUrl, opts) {
|
|
|
29686
29811
|
};
|
|
29687
29812
|
const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
|
|
29688
29813
|
const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };
|
|
29689
|
-
const
|
|
29814
|
+
const meRes = await fetch(`${base2}/api/me`, { headers: authHdr });
|
|
29815
|
+
if (!meRes.ok) throw new Error(`${base2}/api/me: HTTP ${meRes.status} ${await meRes.text().catch(() => "")}`);
|
|
29816
|
+
const me = await readJsonBody(meRes, base2, "GET /api/me");
|
|
29690
29817
|
const teams = me.teams || [];
|
|
29691
29818
|
let slug = opts.team;
|
|
29692
29819
|
if (!slug) {
|
|
@@ -29695,12 +29822,11 @@ async function runLogin(serverUrl, opts) {
|
|
|
29695
29822
|
const name = me.user?.email?.split("@")[0]?.replace(/[^a-z0-9]/gi, "") || "workspace";
|
|
29696
29823
|
const created = await fetch(`${base2}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
|
|
29697
29824
|
if (!created.ok) throw new Error(`workspace create failed: HTTP ${created.status} ${await created.text().catch(() => "")}`);
|
|
29698
|
-
slug = (await created
|
|
29825
|
+
slug = (await readJsonBody(created, base2, "POST /api/teams")).slug;
|
|
29699
29826
|
console.log(chalk.dim(`Created workspace "${name}" (${slug}) \u2014 you're its owner.`));
|
|
29700
29827
|
} else {
|
|
29701
|
-
console.error(chalk.red("You belong to multiple teams \u2014 pass --team <slug>:"));
|
|
29702
29828
|
teams.forEach((t) => console.error(` ${t.team_slug} ${chalk.dim(t.name)}`));
|
|
29703
|
-
|
|
29829
|
+
throw new Error("you belong to multiple teams \u2014 pass --team <slug> (listed above)");
|
|
29704
29830
|
}
|
|
29705
29831
|
}
|
|
29706
29832
|
const deviceId = opts.deviceId || (await import("node:os")).hostname();
|
|
@@ -29710,13 +29836,14 @@ async function runLogin(serverUrl, opts) {
|
|
|
29710
29836
|
body: JSON.stringify({ device_id: deviceId })
|
|
29711
29837
|
});
|
|
29712
29838
|
if (!mint.ok) throw new Error(`token mint failed: HTTP ${mint.status} ${await mint.text().catch(() => "")}`);
|
|
29713
|
-
const { token } = await mint
|
|
29839
|
+
const { token } = await readJsonBody(mint, base2, "POST /api/teams/:slug/tokens");
|
|
29714
29840
|
saveCredentials2({ serverUrl, token });
|
|
29715
29841
|
console.log(chalk.green(`\u2713 Logged in to ${chalk.bold(slug)}`) + chalk.dim(` (device: ${deviceId}) server: ${serverUrl}`));
|
|
29716
29842
|
if (!opts.fromInit) {
|
|
29717
29843
|
console.log(chalk.dim("Run `chat-recall sync` to push redacted conversations."));
|
|
29718
29844
|
}
|
|
29719
29845
|
} catch (err) {
|
|
29846
|
+
if (opts.fromInit) throw err instanceof Error ? err : new Error(String(err));
|
|
29720
29847
|
console.error(chalk.red("login failed:"), err instanceof Error ? err.message : err);
|
|
29721
29848
|
process.exit(1);
|
|
29722
29849
|
}
|