chat-recall 0.5.28 → 0.5.30
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/auto-update.d.ts +24 -0
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +71 -1
- package/dist/auto-update.js.map +1 -1
- package/dist/cli.js +708 -551
- package/dist/cli.js.map +1 -1
- package/dist/env-needed.d.ts +17 -0
- package/dist/env-needed.d.ts.map +1 -0
- package/dist/env-needed.js +93 -0
- package/dist/env-needed.js.map +1 -0
- package/dist/intent-drain.d.ts.map +1 -1
- package/dist/intent-drain.js +2 -1
- package/dist/intent-drain.js.map +1 -1
- package/dist/mcp.js +148 -60
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +4 -2
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +109 -52
- package/package.json +1 -1
- package/skills/chat-recall-project/SKILL.md +17 -0
package/dist/cli.js
CHANGED
|
@@ -7487,7 +7487,7 @@ async function archiveRawSession(store, sessionId) {
|
|
|
7487
7487
|
return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
|
|
7488
7488
|
}
|
|
7489
7489
|
function detectForkPredecessor(sessionPath) {
|
|
7490
|
-
const { readFileSync: readFileSync50, readdirSync:
|
|
7490
|
+
const { readFileSync: readFileSync50, readdirSync: readdirSync36 } = requireFs();
|
|
7491
7491
|
const { dirname: dirname28, join: join61, basename: basename30 } = requirePath();
|
|
7492
7492
|
let text;
|
|
7493
7493
|
try {
|
|
@@ -7517,7 +7517,7 @@ function detectForkPredecessor(sessionPath) {
|
|
|
7517
7517
|
const self = basename30(sessionPath);
|
|
7518
7518
|
let entries;
|
|
7519
7519
|
try {
|
|
7520
|
-
entries =
|
|
7520
|
+
entries = readdirSync36(dir);
|
|
7521
7521
|
} catch {
|
|
7522
7522
|
return null;
|
|
7523
7523
|
}
|
|
@@ -13266,9 +13266,9 @@ function doResolve(realPath) {
|
|
|
13266
13266
|
const bot = resolveBotWorktree(realPath);
|
|
13267
13267
|
if (bot)
|
|
13268
13268
|
return bot;
|
|
13269
|
-
const
|
|
13270
|
-
if (
|
|
13271
|
-
return
|
|
13269
|
+
const git2 = resolveGit(realPath);
|
|
13270
|
+
if (git2)
|
|
13271
|
+
return git2;
|
|
13272
13272
|
const ws = resolveAutoWorkspace(realPath, cfg.autoWorkspaceMinRepos ?? DEFAULT_AUTO_WORKSPACE_MIN_REPOS);
|
|
13273
13273
|
if (ws)
|
|
13274
13274
|
return ws;
|
|
@@ -13337,9 +13337,9 @@ function resolveGit(realPath) {
|
|
|
13337
13337
|
const toplevel = findGitToplevel(realPath);
|
|
13338
13338
|
if (!toplevel)
|
|
13339
13339
|
return null;
|
|
13340
|
-
const
|
|
13341
|
-
if (remote) {
|
|
13342
|
-
const parsed = parseGitRemote(
|
|
13340
|
+
const probe = getGitRemote(toplevel);
|
|
13341
|
+
if (probe.kind === "remote") {
|
|
13342
|
+
const parsed = parseGitRemote(probe.url);
|
|
13343
13343
|
if (parsed) {
|
|
13344
13344
|
return {
|
|
13345
13345
|
id: `git:${parsed.host}/${parsed.owner}/${parsed.repo}`,
|
|
@@ -13348,6 +13348,9 @@ function resolveGit(realPath) {
|
|
|
13348
13348
|
};
|
|
13349
13349
|
}
|
|
13350
13350
|
}
|
|
13351
|
+
if (probe.kind === "failed") {
|
|
13352
|
+
console.error(`[project] could not read git remotes in ${toplevel} \u2014 filing it as a local-only repo for now, which is probably wrong: ${probe.reason}`);
|
|
13353
|
+
}
|
|
13351
13354
|
const hash = createHash6("sha1").update(toplevel).digest("hex").slice(0, 12);
|
|
13352
13355
|
return {
|
|
13353
13356
|
id: `git-local:${hash}`,
|
|
@@ -13366,23 +13369,39 @@ function findGitToplevel(realPath) {
|
|
|
13366
13369
|
}
|
|
13367
13370
|
return null;
|
|
13368
13371
|
}
|
|
13372
|
+
function git(toplevel, args) {
|
|
13373
|
+
return execSync(`git ${args}`, {
|
|
13374
|
+
cwd: toplevel,
|
|
13375
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
13376
|
+
encoding: "utf-8",
|
|
13377
|
+
timeout: GIT_TIMEOUT_MS
|
|
13378
|
+
});
|
|
13379
|
+
}
|
|
13380
|
+
function probeGitRemote(toplevel) {
|
|
13381
|
+
let names;
|
|
13382
|
+
try {
|
|
13383
|
+
names = git(toplevel, "remote").split("\n").map((n) => n.trim()).filter(Boolean);
|
|
13384
|
+
} catch (err) {
|
|
13385
|
+
return { kind: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
13386
|
+
}
|
|
13387
|
+
if (names.length === 0)
|
|
13388
|
+
return { kind: "none" };
|
|
13389
|
+
const pick = names.includes("origin") ? "origin" : names[0];
|
|
13390
|
+
try {
|
|
13391
|
+
const url = git(toplevel, `remote get-url ${pick}`).trim();
|
|
13392
|
+
return url ? { kind: "remote", url } : { kind: "none" };
|
|
13393
|
+
} catch (err) {
|
|
13394
|
+
return { kind: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
13395
|
+
}
|
|
13396
|
+
}
|
|
13369
13397
|
function getGitRemote(toplevel) {
|
|
13370
13398
|
const cached = gitCache.get(toplevel);
|
|
13371
13399
|
if (cached)
|
|
13372
|
-
return cached
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
13378
|
-
encoding: "utf-8",
|
|
13379
|
-
timeout: 2e3
|
|
13380
|
-
}).trim() || null;
|
|
13381
|
-
} catch {
|
|
13382
|
-
remote = null;
|
|
13383
|
-
}
|
|
13384
|
-
gitCache.set(toplevel, { remote });
|
|
13385
|
-
return remote;
|
|
13400
|
+
return cached;
|
|
13401
|
+
const probe = probeGitRemote(toplevel);
|
|
13402
|
+
if (probe.kind !== "failed")
|
|
13403
|
+
gitCache.set(toplevel, probe);
|
|
13404
|
+
return probe;
|
|
13386
13405
|
}
|
|
13387
13406
|
function parseGitRemote(url) {
|
|
13388
13407
|
const trimmed = url.trim().replace(/\.git$/, "");
|
|
@@ -13512,7 +13531,7 @@ function globToRegex(glob) {
|
|
|
13512
13531
|
function escapeRe(s) {
|
|
13513
13532
|
return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
13514
13533
|
}
|
|
13515
|
-
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, AUTO_WORKSPACE_MAX_DEPTH;
|
|
13534
|
+
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
13516
13535
|
var init_project_resolver = __esm({
|
|
13517
13536
|
"../engine/dist/core/project-resolver.js"() {
|
|
13518
13537
|
"use strict";
|
|
@@ -13524,6 +13543,7 @@ var init_project_resolver = __esm({
|
|
|
13524
13543
|
workspaceCache = /* @__PURE__ */ new Map();
|
|
13525
13544
|
wsIdCache = /* @__PURE__ */ new Map();
|
|
13526
13545
|
cachedConfig = null;
|
|
13546
|
+
GIT_TIMEOUT_MS = 1e4;
|
|
13527
13547
|
AUTO_WORKSPACE_MAX_DEPTH = 2;
|
|
13528
13548
|
}
|
|
13529
13549
|
});
|
|
@@ -19730,7 +19750,7 @@ var init_http = __esm({
|
|
|
19730
19750
|
CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
19731
19751
|
} catch {
|
|
19732
19752
|
try {
|
|
19733
|
-
if (true) CLI_VERSION = "0.5.
|
|
19753
|
+
if (true) CLI_VERSION = "0.5.30";
|
|
19734
19754
|
} catch {
|
|
19735
19755
|
}
|
|
19736
19756
|
}
|
|
@@ -20242,7 +20262,7 @@ var init_telemetry = __esm({
|
|
|
20242
20262
|
init_sync_client();
|
|
20243
20263
|
cliVersion2 = () => {
|
|
20244
20264
|
try {
|
|
20245
|
-
return true ? "0.5.
|
|
20265
|
+
return true ? "0.5.30" : "dev";
|
|
20246
20266
|
} catch {
|
|
20247
20267
|
return "dev";
|
|
20248
20268
|
}
|
|
@@ -20579,12 +20599,13 @@ __export(auto_update_exports, {
|
|
|
20579
20599
|
planAutoUpdate: () => planAutoUpdate,
|
|
20580
20600
|
runAutoUpdate: () => runAutoUpdate,
|
|
20581
20601
|
runningPrefix: () => runningPrefix,
|
|
20602
|
+
sweepStaleStaging: () => sweepStaleStaging,
|
|
20582
20603
|
tarballUrl: () => tarballUrl
|
|
20583
20604
|
});
|
|
20584
20605
|
import { execSync as execSync2 } from "node:child_process";
|
|
20585
20606
|
import { readFileSync as readFileSync36 } from "node:fs";
|
|
20586
20607
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20587
|
-
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
20608
|
+
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync32, statSync as statSync38 } from "node:fs";
|
|
20588
20609
|
import { join as join43, dirname as dirname18, sep as sep2 } from "node:path";
|
|
20589
20610
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
20590
20611
|
import { createHash as createHash12 } from "node:crypto";
|
|
@@ -20623,6 +20644,31 @@ function planAutoUpdate(base2, caps, ownVersion3, flag) {
|
|
|
20623
20644
|
if (compareVersions(rel.version, ownVersion3) <= 0) return { update: false, reason: `already current (own ${ownVersion3} >= server ${rel.version})` };
|
|
20624
20645
|
return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base2), sha256: rel.sha256, from: ownVersion3, to: rel.version };
|
|
20625
20646
|
}
|
|
20647
|
+
function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
|
|
20648
|
+
const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
|
|
20649
|
+
const COLD_MS = 60 * 60 * 1e3;
|
|
20650
|
+
let removed = 0;
|
|
20651
|
+
let entries;
|
|
20652
|
+
try {
|
|
20653
|
+
entries = readdirSync32(root);
|
|
20654
|
+
} catch {
|
|
20655
|
+
return 0;
|
|
20656
|
+
}
|
|
20657
|
+
for (const name of entries) {
|
|
20658
|
+
if (!MKDTEMP_SUFFIX.test(name)) continue;
|
|
20659
|
+
const dir = join43(root, name);
|
|
20660
|
+
try {
|
|
20661
|
+
const st = statSync38(dir);
|
|
20662
|
+
if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
|
|
20663
|
+
const inside = readdirSync32(dir);
|
|
20664
|
+
if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
|
|
20665
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
20666
|
+
removed++;
|
|
20667
|
+
} catch {
|
|
20668
|
+
}
|
|
20669
|
+
}
|
|
20670
|
+
return removed;
|
|
20671
|
+
}
|
|
20626
20672
|
async function executeAutoUpdate(plan, deps) {
|
|
20627
20673
|
if (!plan.update || !plan.url || !plan.sha256) return { updated: false, reason: plan.reason };
|
|
20628
20674
|
let buf;
|
|
@@ -20634,13 +20680,21 @@ async function executeAutoUpdate(plan, deps) {
|
|
|
20634
20680
|
const got = createHash12("sha256").update(buf).digest("hex");
|
|
20635
20681
|
if (got !== plan.sha256) return { updated: false, reason: `checksum mismatch (expected ${plan.sha256.slice(0, 12)}\u2026, got ${got.slice(0, 12)}\u2026) \u2014 NOT installing` };
|
|
20636
20682
|
const dir = mkdtempSync3(join43(tmpdir2(), "cr-update-"));
|
|
20683
|
+
const discardStaging = () => {
|
|
20684
|
+
try {
|
|
20685
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
20686
|
+
} catch {
|
|
20687
|
+
}
|
|
20688
|
+
};
|
|
20637
20689
|
const tgz = join43(dir, "chat-recall.tgz");
|
|
20638
20690
|
writeFileSync8(tgz, buf);
|
|
20639
20691
|
try {
|
|
20640
20692
|
deps.install(tgz);
|
|
20641
20693
|
} catch (e) {
|
|
20694
|
+
discardStaging();
|
|
20642
20695
|
return { updated: false, reason: `install failed: ${e instanceof Error ? e.message : e}` };
|
|
20643
20696
|
}
|
|
20697
|
+
discardStaging();
|
|
20644
20698
|
const landed = (deps.verify ?? installedVersion)();
|
|
20645
20699
|
if (landed && plan.to && compareVersions(landed, plan.to) < 0) {
|
|
20646
20700
|
return {
|
|
@@ -20747,7 +20801,7 @@ __export(sync_client_exports, {
|
|
|
20747
20801
|
syncSessions: () => syncSessions
|
|
20748
20802
|
});
|
|
20749
20803
|
import { join as join44, dirname as dirname19, basename as basename22 } from "node:path";
|
|
20750
|
-
import { readFileSync as readFileSync37, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as
|
|
20804
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync33, statSync as statSync39 } from "node:fs";
|
|
20751
20805
|
import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
|
|
20752
20806
|
import { createHash as createHash13 } from "node:crypto";
|
|
20753
20807
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -21119,7 +21173,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21119
21173
|
let reaped = 0;
|
|
21120
21174
|
let names;
|
|
21121
21175
|
try {
|
|
21122
|
-
names =
|
|
21176
|
+
names = readdirSync33(dir);
|
|
21123
21177
|
} catch {
|
|
21124
21178
|
return 0;
|
|
21125
21179
|
}
|
|
@@ -21127,8 +21181,8 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21127
21181
|
if (!name.startsWith("cr-batchscan-")) continue;
|
|
21128
21182
|
const p = join44(dir, name);
|
|
21129
21183
|
try {
|
|
21130
|
-
if (now -
|
|
21131
|
-
|
|
21184
|
+
if (now - statSync39(p).mtimeMs < maxAgeMs) continue;
|
|
21185
|
+
rmSync6(p, { recursive: true, force: true });
|
|
21132
21186
|
reaped++;
|
|
21133
21187
|
} catch {
|
|
21134
21188
|
}
|
|
@@ -21160,7 +21214,7 @@ function batchScanExternal(items, opts = {}) {
|
|
|
21160
21214
|
slices++;
|
|
21161
21215
|
}
|
|
21162
21216
|
try {
|
|
21163
|
-
|
|
21217
|
+
rmSync6(scanDir, { recursive: true, force: true });
|
|
21164
21218
|
} catch {
|
|
21165
21219
|
}
|
|
21166
21220
|
safeToId = /* @__PURE__ */ new Map();
|
|
@@ -21185,7 +21239,7 @@ function batchScanExternal(items, opts = {}) {
|
|
|
21185
21239
|
flushSlice();
|
|
21186
21240
|
} finally {
|
|
21187
21241
|
try {
|
|
21188
|
-
|
|
21242
|
+
rmSync6(scanDir, { recursive: true, force: true });
|
|
21189
21243
|
} catch {
|
|
21190
21244
|
}
|
|
21191
21245
|
}
|
|
@@ -21318,9 +21372,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21318
21372
|
setServerIngestConcurrency(base2, Math.floor(adv));
|
|
21319
21373
|
}
|
|
21320
21374
|
if (body2.cli && body2.cli.version) {
|
|
21321
|
-
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21375
|
+
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21322
21376
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21323
21377
|
const plan = planAutoUpdate2(base2, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21378
|
+
sweepStaleStaging2();
|
|
21324
21379
|
if (plan.update) {
|
|
21325
21380
|
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21326
21381
|
});
|
|
@@ -21446,9 +21501,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21446
21501
|
if (!res.ok) throw new Error(`append sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
21447
21502
|
const body = await res.json().catch(() => ({}));
|
|
21448
21503
|
if (body.cli && body.cli.version) {
|
|
21449
|
-
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21504
|
+
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21450
21505
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21451
21506
|
const plan = planAutoUpdate2(base2, { cli: body.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21507
|
+
sweepStaleStaging2();
|
|
21452
21508
|
if (plan.update) {
|
|
21453
21509
|
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21454
21510
|
});
|
|
@@ -22250,7 +22306,7 @@ var init_sync_client = __esm({
|
|
|
22250
22306
|
ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22251
22307
|
} catch {
|
|
22252
22308
|
try {
|
|
22253
|
-
if (true) ownVersion = "0.5.
|
|
22309
|
+
if (true) ownVersion = "0.5.30";
|
|
22254
22310
|
} catch {
|
|
22255
22311
|
}
|
|
22256
22312
|
}
|
|
@@ -22364,11 +22420,11 @@ var init_update_notice = __esm({
|
|
|
22364
22420
|
});
|
|
22365
22421
|
|
|
22366
22422
|
// ../engine/dist/core/which.js
|
|
22367
|
-
import { existsSync as existsSync43, statSync as
|
|
22423
|
+
import { existsSync as existsSync43, statSync as statSync40, accessSync, constants } from "node:fs";
|
|
22368
22424
|
import { join as join46, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep3 } from "node:path";
|
|
22369
22425
|
function executable(p) {
|
|
22370
22426
|
try {
|
|
22371
|
-
if (!
|
|
22427
|
+
if (!statSync40(p).isFile())
|
|
22372
22428
|
return false;
|
|
22373
22429
|
} catch {
|
|
22374
22430
|
return false;
|
|
@@ -22479,11 +22535,17 @@ function writeFileMkdir(path, body) {
|
|
|
22479
22535
|
mkdirSync10(dirname20(path), { recursive: true });
|
|
22480
22536
|
writeFileSync11(path, body);
|
|
22481
22537
|
}
|
|
22538
|
+
function readJsonObject(path) {
|
|
22539
|
+
const raw = readFileSync39(path, "utf-8");
|
|
22540
|
+
if (raw.trim() === "")
|
|
22541
|
+
return {};
|
|
22542
|
+
return JSON.parse(raw);
|
|
22543
|
+
}
|
|
22482
22544
|
function registerMcpServersJson(target, path, spec) {
|
|
22483
22545
|
let cfg = {};
|
|
22484
22546
|
if (existsSync44(path)) {
|
|
22485
22547
|
try {
|
|
22486
|
-
cfg =
|
|
22548
|
+
cfg = readJsonObject(path);
|
|
22487
22549
|
} catch (err) {
|
|
22488
22550
|
return { id: target.id, label: target.label, path, state: "unparseable", error: String(err) };
|
|
22489
22551
|
}
|
|
@@ -22498,7 +22560,7 @@ function registerMcpServersJson(target, path, spec) {
|
|
|
22498
22560
|
entry.alwaysAllow = existing?.alwaysAllow ?? spec.alwaysAllow;
|
|
22499
22561
|
if (spec.env)
|
|
22500
22562
|
entry.env = { ...existing?.env, ...spec.env };
|
|
22501
|
-
const isCurrent = !!existing && existing.command === spec.command && JSON.stringify(existing.args ?? null) === JSON.stringify(spec.args ?? null) && Object.entries(spec.env ?? {}).every(([k, v]) => existing.env?.[k] === v);
|
|
22563
|
+
const isCurrent = !!existing && !existing.disabled && existing.command === spec.command && JSON.stringify(existing.args ?? null) === JSON.stringify(spec.args ?? null) && Object.entries(spec.env ?? {}).every(([k, v]) => existing.env?.[k] === v);
|
|
22502
22564
|
if (isCurrent)
|
|
22503
22565
|
return { id: target.id, label: target.label, path, state: "current" };
|
|
22504
22566
|
servers[MCP_SERVER_NAME] = entry;
|
|
@@ -22512,7 +22574,7 @@ function registerOpencodeJson(path, spec) {
|
|
|
22512
22574
|
let cfg = {};
|
|
22513
22575
|
if (existsSync44(path)) {
|
|
22514
22576
|
try {
|
|
22515
|
-
cfg =
|
|
22577
|
+
cfg = readJsonObject(path);
|
|
22516
22578
|
} catch (err) {
|
|
22517
22579
|
return { id, label, path, state: "unparseable", error: String(err) };
|
|
22518
22580
|
}
|
|
@@ -22525,7 +22587,7 @@ function registerOpencodeJson(path, spec) {
|
|
|
22525
22587
|
const entry = { type: "local", command: argv, enabled: true };
|
|
22526
22588
|
if (spec.env)
|
|
22527
22589
|
entry.environment = { ...existing?.environment, ...spec.env };
|
|
22528
|
-
const isCurrent = !!existing && JSON.stringify(existing.command ?? null) === JSON.stringify(argv) &&
|
|
22590
|
+
const isCurrent = !!existing && existing.type === "local" && existing.enabled === true && JSON.stringify(existing.command ?? null) === JSON.stringify(argv) && Object.entries(spec.env ?? {}).every(([k, v]) => existing.environment?.[k] === v);
|
|
22529
22591
|
if (isCurrent)
|
|
22530
22592
|
return { id, label, path, state: "current" };
|
|
22531
22593
|
mcp[MCP_SERVER_NAME] = entry;
|
|
@@ -22704,7 +22766,7 @@ __export(companions_exports, {
|
|
|
22704
22766
|
uninstallCodeindex: () => uninstallCodeindex,
|
|
22705
22767
|
unregisterCodeindexMcp: () => unregisterCodeindexMcp
|
|
22706
22768
|
});
|
|
22707
|
-
import { existsSync as existsSync45, mkdirSync as mkdirSync11, statSync as
|
|
22769
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync11, statSync as statSync41, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync40, writeFileSync as writeFileSync12 } from "node:fs";
|
|
22708
22770
|
import { homedir as homedir16 } from "node:os";
|
|
22709
22771
|
import { join as join48 } from "node:path";
|
|
22710
22772
|
import { execSync as execSync3 } from "node:child_process";
|
|
@@ -22755,7 +22817,7 @@ function checkCodeindexStatus() {
|
|
|
22755
22817
|
status.installed = true;
|
|
22756
22818
|
status.path = foundPath;
|
|
22757
22819
|
try {
|
|
22758
|
-
status.size =
|
|
22820
|
+
status.size = statSync41(foundPath).size;
|
|
22759
22821
|
} catch {
|
|
22760
22822
|
}
|
|
22761
22823
|
try {
|
|
@@ -22905,7 +22967,7 @@ __export(install_skills_exports, {
|
|
|
22905
22967
|
skillsSourceDir: () => skillsSourceDir,
|
|
22906
22968
|
uninstallSkills: () => uninstallSkills
|
|
22907
22969
|
});
|
|
22908
|
-
import { readdirSync as
|
|
22970
|
+
import { readdirSync as readdirSync34, existsSync as existsSync46, mkdirSync as mkdirSync12, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync41, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
|
|
22909
22971
|
import { createHash as createHash15 } from "crypto";
|
|
22910
22972
|
import { join as join49, basename as basename23 } from "path";
|
|
22911
22973
|
function skillsSourceDir() {
|
|
@@ -22931,7 +22993,7 @@ function skillsFingerprint() {
|
|
|
22931
22993
|
}
|
|
22932
22994
|
function filesUnder(dir, prefix = "") {
|
|
22933
22995
|
const out = [];
|
|
22934
|
-
for (const e of
|
|
22996
|
+
for (const e of readdirSync34(dir, { withFileTypes: true })) {
|
|
22935
22997
|
if (e.name === MARKER) continue;
|
|
22936
22998
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
22937
22999
|
if (e.isDirectory()) out.push(...filesUnder(join49(dir, e.name), rel));
|
|
@@ -22945,7 +23007,7 @@ function skillStamp() {
|
|
|
22945
23007
|
function bundledSkillNames() {
|
|
22946
23008
|
const src = skillsSourceDir();
|
|
22947
23009
|
if (!existsSync46(src)) return [];
|
|
22948
|
-
return
|
|
23010
|
+
return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
22949
23011
|
}
|
|
22950
23012
|
function realPathOrSelf(p) {
|
|
22951
23013
|
try {
|
|
@@ -23018,7 +23080,7 @@ function installSkills(opts = {}) {
|
|
|
23018
23080
|
continue;
|
|
23019
23081
|
}
|
|
23020
23082
|
mkdirSync12(t.dir, { recursive: true });
|
|
23021
|
-
|
|
23083
|
+
rmSync7(dest, { recursive: true, force: true });
|
|
23022
23084
|
cpSync(join49(src, name), dest, { recursive: true });
|
|
23023
23085
|
writeFileSync13(join49(dest, MARKER), `${stamp}
|
|
23024
23086
|
`);
|
|
@@ -23037,11 +23099,11 @@ function uninstallSkills() {
|
|
|
23037
23099
|
perTarget.push({ id: t.id, label: t.label, removed });
|
|
23038
23100
|
continue;
|
|
23039
23101
|
}
|
|
23040
|
-
for (const entry of
|
|
23102
|
+
for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
|
|
23041
23103
|
if (!entry.isDirectory()) continue;
|
|
23042
23104
|
const dir = join49(t.dir, entry.name);
|
|
23043
23105
|
if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
|
|
23044
|
-
|
|
23106
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
23045
23107
|
removed.push(entry.name);
|
|
23046
23108
|
}
|
|
23047
23109
|
}
|
|
@@ -23722,11 +23784,11 @@ __export(toolkit_pull_exports, {
|
|
|
23722
23784
|
planPull: () => planPull,
|
|
23723
23785
|
portableCommand: () => portableCommand
|
|
23724
23786
|
});
|
|
23725
|
-
import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as
|
|
23787
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as statSync43, writeFileSync as writeFileSync15 } from "node:fs";
|
|
23726
23788
|
import { dirname as dirname22, join as join52, basename as basename26, isAbsolute as isAbsolute3 } from "node:path";
|
|
23727
23789
|
function isExecutableFile(p) {
|
|
23728
23790
|
try {
|
|
23729
|
-
return
|
|
23791
|
+
return statSync43(p).isFile();
|
|
23730
23792
|
} catch {
|
|
23731
23793
|
return false;
|
|
23732
23794
|
}
|
|
@@ -25647,7 +25709,7 @@ __export(project_tasks_exports, {
|
|
|
25647
25709
|
pushProjectTaskStatuses: () => pushProjectTaskStatuses,
|
|
25648
25710
|
renderTeamTasksMd: () => renderTeamTasksMd
|
|
25649
25711
|
});
|
|
25650
|
-
import { existsSync as existsSync50, readFileSync as readFileSync45, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, statSync as
|
|
25712
|
+
import { existsSync as existsSync50, readFileSync as readFileSync45, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, statSync as statSync44 } from "node:fs";
|
|
25651
25713
|
import { join as join54, dirname as dirname23 } from "node:path";
|
|
25652
25714
|
import { homedir as homedir19 } from "node:os";
|
|
25653
25715
|
function isTeamTaskStatus(v) {
|
|
@@ -25736,7 +25798,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
|
|
|
25736
25798
|
if (!existsSync50(file)) continue;
|
|
25737
25799
|
let mtime;
|
|
25738
25800
|
try {
|
|
25739
|
-
mtime =
|
|
25801
|
+
mtime = statSync44(file).mtimeMs;
|
|
25740
25802
|
} catch {
|
|
25741
25803
|
continue;
|
|
25742
25804
|
}
|
|
@@ -25946,8 +26008,9 @@ async function drainSyncIntents(opts = {}) {
|
|
|
25946
26008
|
const data = await res.json();
|
|
25947
26009
|
pending = data.intents || [];
|
|
25948
26010
|
if (data.cli && data.cli.version) {
|
|
25949
|
-
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
26011
|
+
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
25950
26012
|
const plan = planAutoUpdate2(base2, { cli: data.cli }, ownVersion2, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
26013
|
+
sweepStaleStaging2();
|
|
25951
26014
|
if (plan.update) {
|
|
25952
26015
|
void runAutoUpdate2(base2, authHeaders, ownVersion2).catch(() => {
|
|
25953
26016
|
});
|
|
@@ -26001,7 +26064,7 @@ var init_intent_drain = __esm({
|
|
|
26001
26064
|
ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
26002
26065
|
} catch {
|
|
26003
26066
|
try {
|
|
26004
|
-
if (true) ownVersion2 = "0.5.
|
|
26067
|
+
if (true) ownVersion2 = "0.5.30";
|
|
26005
26068
|
} catch {
|
|
26006
26069
|
}
|
|
26007
26070
|
}
|
|
@@ -26202,7 +26265,7 @@ var team_merge_exports = {};
|
|
|
26202
26265
|
__export(team_merge_exports, {
|
|
26203
26266
|
mergePullResult: () => mergePullResult
|
|
26204
26267
|
});
|
|
26205
|
-
import { existsSync as existsSync52, mkdirSync as mkdirSync17, readFileSync as readFileSync47, rmSync as
|
|
26268
|
+
import { existsSync as existsSync52, mkdirSync as mkdirSync17, readFileSync as readFileSync47, rmSync as rmSync8, writeFileSync as writeFileSync18, statSync as statSync45 } from "fs";
|
|
26206
26269
|
import { dirname as dirname25, join as join56 } from "path";
|
|
26207
26270
|
import { createHash as createHash18 } from "crypto";
|
|
26208
26271
|
function installPathFor(art, tool) {
|
|
@@ -26319,7 +26382,7 @@ async function mergePullResult(opts) {
|
|
|
26319
26382
|
continue;
|
|
26320
26383
|
try {
|
|
26321
26384
|
if (existsSync52(path))
|
|
26322
|
-
|
|
26385
|
+
rmSync8(path, { force: true });
|
|
26323
26386
|
await teamInstallsForget(id, row.tool);
|
|
26324
26387
|
report.removed.push({ artifactId: id, path });
|
|
26325
26388
|
} catch (err) {
|
|
@@ -26338,7 +26401,7 @@ function readFileIfExists(path) {
|
|
|
26338
26401
|
try {
|
|
26339
26402
|
if (!existsSync52(path))
|
|
26340
26403
|
return null;
|
|
26341
|
-
const s =
|
|
26404
|
+
const s = statSync45(path);
|
|
26342
26405
|
if (!s.isFile())
|
|
26343
26406
|
return null;
|
|
26344
26407
|
return readFileSync47(path);
|
|
@@ -26423,7 +26486,7 @@ var init_vault_crypto = __esm({
|
|
|
26423
26486
|
});
|
|
26424
26487
|
|
|
26425
26488
|
// ../engine/dist/core/vault-source.js
|
|
26426
|
-
import { existsSync as existsSync53, readdirSync as
|
|
26489
|
+
import { existsSync as existsSync53, readdirSync as readdirSync35, statSync as statSync46 } from "fs";
|
|
26427
26490
|
import { extname, join as join57 } from "path";
|
|
26428
26491
|
function* walkVaultSources() {
|
|
26429
26492
|
const t = loadSettings().team.vault;
|
|
@@ -26441,7 +26504,7 @@ function* walkVaultSources() {
|
|
|
26441
26504
|
}
|
|
26442
26505
|
function safeStat(path) {
|
|
26443
26506
|
try {
|
|
26444
|
-
const s =
|
|
26507
|
+
const s = statSync46(path);
|
|
26445
26508
|
if (!s.isFile())
|
|
26446
26509
|
return null;
|
|
26447
26510
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
@@ -26455,7 +26518,7 @@ function* walkClaude() {
|
|
|
26455
26518
|
return;
|
|
26456
26519
|
let projectDirs = [];
|
|
26457
26520
|
try {
|
|
26458
|
-
projectDirs =
|
|
26521
|
+
projectDirs = readdirSync35(root);
|
|
26459
26522
|
} catch {
|
|
26460
26523
|
return;
|
|
26461
26524
|
}
|
|
@@ -26466,7 +26529,7 @@ function* walkClaude() {
|
|
|
26466
26529
|
const projDir = join57(root, proj);
|
|
26467
26530
|
let entries = [];
|
|
26468
26531
|
try {
|
|
26469
|
-
entries =
|
|
26532
|
+
entries = readdirSync35(projDir);
|
|
26470
26533
|
} catch {
|
|
26471
26534
|
continue;
|
|
26472
26535
|
}
|
|
@@ -26488,7 +26551,7 @@ function* walkGemini() {
|
|
|
26488
26551
|
return;
|
|
26489
26552
|
let hashDirs = [];
|
|
26490
26553
|
try {
|
|
26491
|
-
hashDirs =
|
|
26554
|
+
hashDirs = readdirSync35(tmp);
|
|
26492
26555
|
} catch {
|
|
26493
26556
|
return;
|
|
26494
26557
|
}
|
|
@@ -26498,7 +26561,7 @@ function* walkGemini() {
|
|
|
26498
26561
|
continue;
|
|
26499
26562
|
let entries = [];
|
|
26500
26563
|
try {
|
|
26501
|
-
entries =
|
|
26564
|
+
entries = readdirSync35(chatsDir);
|
|
26502
26565
|
} catch {
|
|
26503
26566
|
continue;
|
|
26504
26567
|
}
|
|
@@ -26544,7 +26607,7 @@ function* walkCodex() {
|
|
|
26544
26607
|
const dir = stack.pop();
|
|
26545
26608
|
let entries = [];
|
|
26546
26609
|
try {
|
|
26547
|
-
entries =
|
|
26610
|
+
entries = readdirSync35(dir, { withFileTypes: true });
|
|
26548
26611
|
} catch {
|
|
26549
26612
|
continue;
|
|
26550
26613
|
}
|
|
@@ -26570,14 +26633,14 @@ function* walkAgy() {
|
|
|
26570
26633
|
return;
|
|
26571
26634
|
let sessionDirs = [];
|
|
26572
26635
|
try {
|
|
26573
|
-
sessionDirs =
|
|
26636
|
+
sessionDirs = readdirSync35(brainDir);
|
|
26574
26637
|
} catch {
|
|
26575
26638
|
return;
|
|
26576
26639
|
}
|
|
26577
26640
|
for (const rawId of sessionDirs) {
|
|
26578
26641
|
const sessionPath = join57(brainDir, rawId);
|
|
26579
26642
|
try {
|
|
26580
|
-
if (!
|
|
26643
|
+
if (!statSync46(sessionPath).isDirectory())
|
|
26581
26644
|
continue;
|
|
26582
26645
|
} catch {
|
|
26583
26646
|
continue;
|
|
@@ -27058,7 +27121,7 @@ __export(service_installer_exports, {
|
|
|
27058
27121
|
uninstallService: () => uninstallService
|
|
27059
27122
|
});
|
|
27060
27123
|
import { execSync as execSync5 } from "child_process";
|
|
27061
|
-
import { existsSync as existsSync55, mkdirSync as mkdirSync19, rmSync as
|
|
27124
|
+
import { existsSync as existsSync55, mkdirSync as mkdirSync19, rmSync as rmSync9, writeFileSync as writeFileSync20 } from "fs";
|
|
27062
27125
|
import { homedir as homedir21 } from "os";
|
|
27063
27126
|
import { join as join59 } from "path";
|
|
27064
27127
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -27214,7 +27277,7 @@ function uninstallService() {
|
|
|
27214
27277
|
}
|
|
27215
27278
|
const unitPath = join59(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
27216
27279
|
if (existsSync55(unitPath)) {
|
|
27217
|
-
|
|
27280
|
+
rmSync9(unitPath, { force: true });
|
|
27218
27281
|
removed = true;
|
|
27219
27282
|
}
|
|
27220
27283
|
try {
|
|
@@ -27231,7 +27294,7 @@ function uninstallService() {
|
|
|
27231
27294
|
}
|
|
27232
27295
|
let removed = false;
|
|
27233
27296
|
if (existsSync55(plistPath)) {
|
|
27234
|
-
|
|
27297
|
+
rmSync9(plistPath, { force: true });
|
|
27235
27298
|
removed = true;
|
|
27236
27299
|
}
|
|
27237
27300
|
return removed;
|
|
@@ -27508,7 +27571,7 @@ init_tool_paths();
|
|
|
27508
27571
|
init_project_resolver();
|
|
27509
27572
|
import { config } from "dotenv";
|
|
27510
27573
|
import { Command } from "commander";
|
|
27511
|
-
import
|
|
27574
|
+
import chalk2 from "chalk";
|
|
27512
27575
|
import { existsSync as existsSync56, readFileSync as readFileSync49, writeFileSync as writeFileSync21, realpathSync as realpathSync5 } from "fs";
|
|
27513
27576
|
import { homedir as homedir22 } from "os";
|
|
27514
27577
|
import { join as join60, resolve as resolve2, dirname as dirname27, basename as basename29 } from "path";
|
|
@@ -27569,16 +27632,64 @@ function openBrowser(url) {
|
|
|
27569
27632
|
init_which();
|
|
27570
27633
|
init_collector_health();
|
|
27571
27634
|
init_telemetry_consent();
|
|
27635
|
+
|
|
27636
|
+
// src/env-needed.ts
|
|
27637
|
+
import chalk from "chalk";
|
|
27638
|
+
function isSecret(name) {
|
|
27639
|
+
return /(TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL|API_?KEY|PAT)\b/i.test(name);
|
|
27640
|
+
}
|
|
27641
|
+
function unusableHere(server) {
|
|
27642
|
+
const mac = /xcode|swift|apple|ios|macos/i.test(server);
|
|
27643
|
+
if (mac && process.platform !== "darwin") return "macOS only \u2014 nothing to do on this machine";
|
|
27644
|
+
return null;
|
|
27645
|
+
}
|
|
27646
|
+
function collectEnvNeeds(outcomes) {
|
|
27647
|
+
const byServer = /* @__PURE__ */ new Map();
|
|
27648
|
+
for (const o of outcomes) {
|
|
27649
|
+
if (!o.needsEnv?.length) continue;
|
|
27650
|
+
const set = byServer.get(o.name) ?? /* @__PURE__ */ new Set();
|
|
27651
|
+
for (const v of o.needsEnv) set.add(v);
|
|
27652
|
+
byServer.set(o.name, set);
|
|
27653
|
+
}
|
|
27654
|
+
return [...byServer.entries()].map(([server, vars]) => ({ server, vars: [...vars].sort() })).sort((a, b) => a.server.localeCompare(b.server));
|
|
27655
|
+
}
|
|
27656
|
+
function renderEnvNeeds(needs, installedCount) {
|
|
27657
|
+
if (!needs.length) return [];
|
|
27658
|
+
const lines = [];
|
|
27659
|
+
const plural = installedCount === 1 ? "server" : "servers";
|
|
27660
|
+
lines.push(
|
|
27661
|
+
chalk.yellow(`Installed ${installedCount} MCP ${plural} from your account. `) + chalk.yellow(`${needs.length === 1 ? "One needs" : `${needs.length} need`} values you must set here:`)
|
|
27662
|
+
);
|
|
27663
|
+
lines.push("");
|
|
27664
|
+
const width = Math.max(...needs.map((n) => n.server.length));
|
|
27665
|
+
let anySecret = false;
|
|
27666
|
+
for (const n of needs) {
|
|
27667
|
+
const note = unusableHere(n.server);
|
|
27668
|
+
anySecret ||= n.vars.some(isSecret);
|
|
27669
|
+
const name = chalk.bold(n.server.padEnd(width));
|
|
27670
|
+
const vars = n.vars.join(", ");
|
|
27671
|
+
lines.push(` ${name} ${vars}${note ? chalk.dim(` (${note})`) : ""}`);
|
|
27672
|
+
}
|
|
27673
|
+
lines.push("");
|
|
27674
|
+
lines.push(chalk.dim(" Add them to your shell profile, then restart your AI tool."));
|
|
27675
|
+
lines.push(chalk.dim(" Until then these servers will fail to start."));
|
|
27676
|
+
if (anySecret) {
|
|
27677
|
+
lines.push(chalk.dim(" chat-recall never uploads secret values \u2014 only the variable names."));
|
|
27678
|
+
}
|
|
27679
|
+
return lines;
|
|
27680
|
+
}
|
|
27681
|
+
|
|
27682
|
+
// src/cli.ts
|
|
27572
27683
|
function matchLabel(tier) {
|
|
27573
27684
|
switch (tier) {
|
|
27574
27685
|
case "strong":
|
|
27575
|
-
return
|
|
27686
|
+
return chalk2.green("strong");
|
|
27576
27687
|
case "good":
|
|
27577
|
-
return
|
|
27688
|
+
return chalk2.yellow("good");
|
|
27578
27689
|
case "weak":
|
|
27579
|
-
return
|
|
27690
|
+
return chalk2.dim("weak");
|
|
27580
27691
|
default:
|
|
27581
|
-
return
|
|
27692
|
+
return chalk2.dim("unranked");
|
|
27582
27693
|
}
|
|
27583
27694
|
}
|
|
27584
27695
|
config({ quiet: true });
|
|
@@ -27613,13 +27724,13 @@ function describeProbeError(err) {
|
|
|
27613
27724
|
}
|
|
27614
27725
|
function failLogin(opts, message) {
|
|
27615
27726
|
if (opts.fromInit) throw new Error(message);
|
|
27616
|
-
console.error(
|
|
27727
|
+
console.error(chalk2.red("login failed:"), message);
|
|
27617
27728
|
process.exit(1);
|
|
27618
27729
|
}
|
|
27619
27730
|
function requireTarget() {
|
|
27620
27731
|
const t = firstTarget();
|
|
27621
27732
|
if (!t) {
|
|
27622
|
-
console.error(
|
|
27733
|
+
console.error(chalk2.red("Not logged in."), "Run", chalk2.bold("chat-recall login <server-url>"), "first.");
|
|
27623
27734
|
process.exit(1);
|
|
27624
27735
|
}
|
|
27625
27736
|
return t;
|
|
@@ -27777,7 +27888,7 @@ var program = new Command();
|
|
|
27777
27888
|
program.name("chat-recall").description("Semantic search for Claude Code sessions - recall and resume past conversations").version(pkgVersion);
|
|
27778
27889
|
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) => {
|
|
27779
27890
|
try {
|
|
27780
|
-
console.log(
|
|
27891
|
+
console.log(chalk2.bold("chat-recall init") + chalk2.dim(` v${pkgVersion}`));
|
|
27781
27892
|
const { createInitReporter: createInitReporter2 } = await Promise.resolve().then(() => (init_init_report(), init_report_exports));
|
|
27782
27893
|
const rep = createInitReporter2({
|
|
27783
27894
|
tty: process.stdout.isTTY === true,
|
|
@@ -27801,8 +27912,8 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27801
27912
|
}
|
|
27802
27913
|
}
|
|
27803
27914
|
const foundTools = clis.filter((c) => c.available).map((c) => c.name.replace(/ (CLI|Code)$/, ""));
|
|
27804
|
-
if (!foundTools.length) rep.warn(
|
|
27805
|
-
const serverBad = (text) => rep.warn(`${
|
|
27915
|
+
if (!foundTools.length) rep.warn(chalk2.yellow("No AI tools detected \u2014 chat-recall indexes their transcripts, so there is nothing to ship yet."));
|
|
27916
|
+
const serverBad = (text) => rep.warn(`${chalk2.yellow("Not connected")} \u2014 ${text}`);
|
|
27806
27917
|
let target = firstTarget();
|
|
27807
27918
|
let deadTarget = false;
|
|
27808
27919
|
if (options.server) {
|
|
@@ -27826,24 +27937,24 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27826
27937
|
if (probeOk2(probe)) {
|
|
27827
27938
|
} else if (probe.kind === "moved" || probe.kind === "not_api") {
|
|
27828
27939
|
const retired = target.base;
|
|
27829
|
-
console.log(` ${
|
|
27940
|
+
console.log(` ${chalk2.yellow("The saved server no longer serves chat-recall")} \u2192 ${retired}`);
|
|
27830
27941
|
console.log(` ${probeAdvice2(probe, retired)}`);
|
|
27831
|
-
console.log(` Switching to ${
|
|
27942
|
+
console.log(` Switching to ${chalk2.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
|
|
27832
27943
|
try {
|
|
27833
27944
|
await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
|
|
27834
27945
|
const { removeCredentials: removeCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
27835
27946
|
if (removeCredentials2(retired)) {
|
|
27836
|
-
console.log(` ${
|
|
27947
|
+
console.log(` ${chalk2.dim(`Removed the retired target ${retired} (log in there again any time).`)}`);
|
|
27837
27948
|
}
|
|
27838
27949
|
target = firstTarget();
|
|
27839
|
-
if (target) console.log(` ${
|
|
27950
|
+
if (target) console.log(` ${chalk2.green("Connected")} \u2192 ${target.base}`);
|
|
27840
27951
|
} catch (e) {
|
|
27841
27952
|
serverBad(describeProbeError(e));
|
|
27842
|
-
console.log(` ${
|
|
27953
|
+
console.log(` ${chalk2.dim(`Run ${chalk2.bold(`chat-recall login ${DEFAULT_SERVER}`)} when ready, then ${chalk2.bold(`chat-recall logout ${retired}`)}.`)}`);
|
|
27843
27954
|
deadTarget = true;
|
|
27844
27955
|
}
|
|
27845
27956
|
} else {
|
|
27846
|
-
console.log(` ${
|
|
27957
|
+
console.log(` ${chalk2.yellow("Saved server did not answer")} \u2192 ${target.base}`);
|
|
27847
27958
|
console.log(` ${probeAdvice2(probe, target.base)}`);
|
|
27848
27959
|
}
|
|
27849
27960
|
} else if (target) {
|
|
@@ -27872,7 +27983,9 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27872
27983
|
if (ok.length) {
|
|
27873
27984
|
}
|
|
27874
27985
|
for (const r of bad) {
|
|
27875
|
-
|
|
27986
|
+
const why = (r.error ?? "").replace(/^SyntaxError:\s*/, "").split("\n")[0];
|
|
27987
|
+
rep.warn(`${chalk2.yellow(`${r.label} config left alone \u2014 ${why || "the file does not parse"}`)} ${chalk2.dim(r.path)}`);
|
|
27988
|
+
rep.warn(`${chalk2.dim(` fix that file, then: chat-recall init (nothing else needs redoing)`)}`);
|
|
27876
27989
|
}
|
|
27877
27990
|
if (process.env.CHAT_RECALL_VERBOSE) {
|
|
27878
27991
|
}
|
|
@@ -27904,14 +28017,14 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27904
28017
|
const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
|
|
27905
28018
|
}
|
|
27906
28019
|
} else if (!result.prebuiltAvailable) {
|
|
27907
|
-
rep.warn(`${
|
|
28020
|
+
rep.warn(`${chalk2.yellow("codeindex")}: ${result.unsupportedReason}`);
|
|
27908
28021
|
}
|
|
27909
28022
|
} catch (err) {
|
|
27910
28023
|
const msg = String(err);
|
|
27911
|
-
rep.warn(`${
|
|
28024
|
+
rep.warn(`${chalk2.yellow("codeindex install failed")}: ${msg}`);
|
|
27912
28025
|
if (msg.includes("404")) {
|
|
27913
|
-
console.log(
|
|
27914
|
-
console.log(
|
|
28026
|
+
console.log(chalk2.dim(" The codeindex release returned 404. If the repo is private,"));
|
|
28027
|
+
console.log(chalk2.dim(" authenticate `gh` to it, or build from source."));
|
|
27915
28028
|
}
|
|
27916
28029
|
}
|
|
27917
28030
|
} else {
|
|
@@ -27927,7 +28040,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27927
28040
|
}
|
|
27928
28041
|
const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
|
|
27929
28042
|
} catch (err) {
|
|
27930
|
-
rep.warn(`${
|
|
28043
|
+
rep.warn(`${chalk2.yellow("Skill install failed")} \u2014 ${err instanceof Error ? err.message : err}`);
|
|
27931
28044
|
}
|
|
27932
28045
|
if (!options.skipMcp && firstTarget()) {
|
|
27933
28046
|
try {
|
|
@@ -27965,28 +28078,30 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27965
28078
|
const written = report.outcomes.filter((o) => o.status === "written");
|
|
27966
28079
|
const present = report.outcomes.filter((o) => o.status === "present").length;
|
|
27967
28080
|
if (answered === 0) {
|
|
27968
|
-
rep.warn(
|
|
27969
|
-
for (const f of askFailed) console.log(` ${
|
|
27970
|
-
console.log(` ${
|
|
28081
|
+
rep.warn(chalk2.yellow("Could not ask your server what MCP servers are on your account"));
|
|
28082
|
+
for (const f of askFailed) console.log(` ${chalk2.dim(f)}`);
|
|
28083
|
+
console.log(` ${chalk2.dim("Re-run `chat-recall toolkit pull` once the server answers.")}`);
|
|
27971
28084
|
} else if (written.length === 0 && present === 0) {
|
|
27972
28085
|
} else {
|
|
27973
|
-
const
|
|
27974
|
-
if (
|
|
27975
|
-
|
|
28086
|
+
const needs = collectEnvNeeds(written);
|
|
28087
|
+
if (needs.length) {
|
|
28088
|
+
const [head, ...rest] = renderEnvNeeds(needs, written.length);
|
|
28089
|
+
rep.warn(head);
|
|
28090
|
+
for (const line of rest) console.log(line);
|
|
27976
28091
|
}
|
|
27977
28092
|
}
|
|
27978
28093
|
} catch (err) {
|
|
27979
|
-
rep.warn(`${
|
|
27980
|
-
console.log(` ${
|
|
28094
|
+
rep.warn(`${chalk2.yellow("Could not install account MCPs")} \u2014 ${err instanceof Error ? err.message : err}`);
|
|
28095
|
+
console.log(` ${chalk2.dim("Re-run `chat-recall toolkit pull` later.")}`);
|
|
27981
28096
|
}
|
|
27982
28097
|
}
|
|
27983
28098
|
if (deadTarget) {
|
|
27984
|
-
console.log(
|
|
27985
|
-
console.log(` ${
|
|
28099
|
+
console.log(chalk2.bold("Skipping first sync \u2014 the saved server is not serving chat-recall"));
|
|
28100
|
+
console.log(` ${chalk2.dim(`Re-point with the two commands above, then run ${chalk2.bold("chat-recall sync")}.`)}`);
|
|
27986
28101
|
} else if (!options.skipSync && firstTarget()) {
|
|
27987
28102
|
const { watchFirstSync: watchFirstSync2, spawnDetachedSync: spawnDetachedSync2, handOffLine: handOffLine2, progressLine: renderProgress } = await Promise.resolve().then(() => (init_init_progress(), init_progress_exports));
|
|
27988
28103
|
const { readCollectorHealth: readCollectorHealth2 } = await Promise.resolve().then(() => (init_collector_health(), collector_health_exports));
|
|
27989
|
-
const label = (text) => `${
|
|
28104
|
+
const label = (text) => `${chalk2.bold("Indexing")} ${text}`;
|
|
27990
28105
|
const tty = process.stdout.isTTY === true;
|
|
27991
28106
|
const outcome = await watchFirstSync2({
|
|
27992
28107
|
startWorker: () => spawnDetachedSync2(),
|
|
@@ -28004,22 +28119,22 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
28004
28119
|
});
|
|
28005
28120
|
if (tty) process.stdout.write("\r");
|
|
28006
28121
|
if (outcome.finished) {
|
|
28007
|
-
console.log(label(
|
|
28122
|
+
console.log(label(chalk2.green(renderProgress(outcome.snapshot))));
|
|
28008
28123
|
} else {
|
|
28009
|
-
console.log(label(
|
|
28010
|
-
console.log(`
|
|
28124
|
+
console.log(label(chalk2.dim(handOffLine2(outcome.snapshot))));
|
|
28125
|
+
console.log(` ${chalk2.dim("watch it with: " + chalk2.bold("chat-recall status"))}`);
|
|
28011
28126
|
}
|
|
28012
28127
|
const failed = Object.entries(readCollectorHealth2()?.targets ?? {}).filter(([, t]) => (t.failures ?? 0) > 0 && t.lastError);
|
|
28013
28128
|
for (const [server, t] of failed) {
|
|
28014
|
-
console.log(`
|
|
28129
|
+
console.log(` ${chalk2.yellow("sync error")} ${chalk2.dim(server)}: ${t.lastError}`);
|
|
28015
28130
|
}
|
|
28016
28131
|
} else if (!options.skipSync) {
|
|
28017
28132
|
} else {
|
|
28018
28133
|
}
|
|
28019
28134
|
void options.skipService;
|
|
28020
|
-
rep.done(
|
|
28135
|
+
rep.done(chalk2.green.bold("\u2713 Done"));
|
|
28021
28136
|
} catch (err) {
|
|
28022
|
-
console.error(
|
|
28137
|
+
console.error(chalk2.red("Error:"), err);
|
|
28023
28138
|
process.exit(1);
|
|
28024
28139
|
}
|
|
28025
28140
|
});
|
|
@@ -28029,57 +28144,57 @@ program.command("install-skills").description("Install the chat-recall skills in
|
|
|
28029
28144
|
const r2 = mod.uninstallSkills();
|
|
28030
28145
|
const hits2 = r2.perTarget.filter((t) => t.removed.length > 0);
|
|
28031
28146
|
if (!hits2.length) {
|
|
28032
|
-
console.log(
|
|
28147
|
+
console.log(chalk2.dim("No chat-recall skills were installed."));
|
|
28033
28148
|
return;
|
|
28034
28149
|
}
|
|
28035
|
-
for (const t of hits2) console.log(`${
|
|
28150
|
+
for (const t of hits2) console.log(`${chalk2.green("\u2713")} ${t.label}: removed ${t.removed.length} skill(s)`);
|
|
28036
28151
|
return;
|
|
28037
28152
|
}
|
|
28038
28153
|
const r = mod.installSkills({ onlyAvailable: !opts.allTools });
|
|
28039
28154
|
const names = mod.bundledSkillNames();
|
|
28040
|
-
console.log(
|
|
28155
|
+
console.log(chalk2.bold(`Installing ${names.length} chat-recall skill(s) (v${r.version}):`), chalk2.dim(names.join(", ")));
|
|
28041
28156
|
const hits = r.perTarget.filter((t) => t.installed.length > 0);
|
|
28042
28157
|
if (!hits.length) {
|
|
28043
|
-
console.log(
|
|
28158
|
+
console.log(chalk2.yellow("No supported AI tools detected. Use --all-tools to install anyway."));
|
|
28044
28159
|
return;
|
|
28045
28160
|
}
|
|
28046
|
-
for (const t of hits) console.log(`${
|
|
28161
|
+
for (const t of hits) console.log(`${chalk2.green("\u2713")} ${t.label}: ${t.installed.length} \u2192 ${chalk2.dim(t.dir)}`);
|
|
28047
28162
|
const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
|
|
28048
|
-
if (userOwned.length) console.log(
|
|
28163
|
+
if (userOwned.length) console.log(chalk2.yellow(`Kept your existing skill(s) of the same name: ${userOwned.join(", ")}`));
|
|
28049
28164
|
});
|
|
28050
28165
|
program.command("index").description("Collect local sessions and ship them to your server (alias for sync)").option("-f, --force", "Ignore the per-server ledger and re-ship everything", false).action(async (options) => {
|
|
28051
28166
|
try {
|
|
28052
28167
|
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28053
28168
|
const r = await syncSessions2(options.force ? { useLedger: false } : {});
|
|
28054
|
-
console.log(
|
|
28169
|
+
console.log(chalk2.green(`\u2713 Synced ${r.uploaded} session(s), ${r.items} item(s)`) + chalk2.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)` : ""}`));
|
|
28055
28170
|
} catch (err) {
|
|
28056
|
-
console.error(
|
|
28171
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28057
28172
|
process.exit(1);
|
|
28058
28173
|
}
|
|
28059
28174
|
});
|
|
28060
28175
|
program.command("repair [sessionIds...]").description("Rebuild sessions an upstream tool truncated in place (e.g. Claude Code resume rewrite) from the shrink-protected raw archive. DRY-RUN by default \u2014 pass --apply to write.").option("--all", "Scan the recency window and repair EVERY damaged session (no ids needed)", false).option("--since-hours <n>", "With --all: only scan sessions modified in the last N hours", "72").option("--apply", "Actually write the recovered conversations to the server(s). Without this, nothing is mutated.", false).option("--server <url>", "Target ONLY this server (e.g. the SaaS), instead of every configured sync endpoint").option("--force", "Push even to servers that already look full", false).option("-v, --verbose", "Per-session detail", false).action(async (sessionIds, options) => {
|
|
28061
28176
|
try {
|
|
28062
|
-
const mode = options.apply ?
|
|
28177
|
+
const mode = options.apply ? chalk2.red("APPLY (writing to server)") : chalk2.cyan("DRY-RUN (read-only)");
|
|
28063
28178
|
const render = (r) => {
|
|
28064
28179
|
const head = `${r.sessionId.slice(0, 8)}\u2026`;
|
|
28065
28180
|
if (r.status === "no-archive") {
|
|
28066
|
-
console.log(
|
|
28181
|
+
console.log(chalk2.yellow(` ? ${head} no archive found`));
|
|
28067
28182
|
return;
|
|
28068
28183
|
}
|
|
28069
28184
|
if (r.status === "already-full") {
|
|
28070
|
-
console.log(
|
|
28185
|
+
console.log(chalk2.dim(` = ${head} already full (${r.fullestMessages} msgs)`));
|
|
28071
28186
|
return;
|
|
28072
28187
|
}
|
|
28073
28188
|
if (r.status === "error") {
|
|
28074
|
-
console.log(
|
|
28189
|
+
console.log(chalk2.red(` \u2717 ${head} ${r.note}`));
|
|
28075
28190
|
return;
|
|
28076
28191
|
}
|
|
28077
28192
|
const verb = r.status === "would-repair" ? "would recover" : "recovered";
|
|
28078
|
-
console.log(
|
|
28079
|
-
for (const p of r.pushed) console.log(
|
|
28193
|
+
console.log(chalk2.green(` ${r.status === "would-repair" ? "\xB7" : "+"} ${head} ${verb} ${r.fullestMessages} msgs`) + chalk2.dim(` from ${r.fullestSource}`));
|
|
28194
|
+
for (const p of r.pushed) console.log(chalk2.dim(` ${p.server}: ${p.before} \u2192 ${p.after}`));
|
|
28080
28195
|
};
|
|
28081
28196
|
if (options.all) {
|
|
28082
|
-
console.log(
|
|
28197
|
+
console.log(chalk2.bold(`repair --all \xB7 ${mode} \xB7 window ${options.sinceHours}h`));
|
|
28083
28198
|
const { repairAll: repairAll2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
|
|
28084
28199
|
const report = await repairAll2({
|
|
28085
28200
|
sinceHours: Number(options.sinceHours) || 72,
|
|
@@ -28090,24 +28205,24 @@ program.command("repair [sessionIds...]").description("Rebuild sessions an upstr
|
|
|
28090
28205
|
report.candidates.forEach(render);
|
|
28091
28206
|
const fixed2 = report.candidates.filter((r) => r.status === "repaired").length;
|
|
28092
28207
|
const would2 = report.candidates.filter((r) => r.status === "would-repair").length;
|
|
28093
|
-
console.log(
|
|
28208
|
+
console.log(chalk2.bold(
|
|
28094
28209
|
`Scanned ${report.scanned} session(s) on ${report.discoveryServer}; ` + (options.apply ? `repaired ${fixed2}.` : `${would2} damaged, would repair. Re-run with --apply to write.`)
|
|
28095
28210
|
));
|
|
28096
28211
|
return;
|
|
28097
28212
|
}
|
|
28098
28213
|
if (!sessionIds || sessionIds.length === 0) {
|
|
28099
|
-
console.error(
|
|
28214
|
+
console.error(chalk2.red("Give session ids, or use --all. e.g. `chat-recall repair <uuid>` or `chat-recall repair --all`."));
|
|
28100
28215
|
process.exit(1);
|
|
28101
28216
|
}
|
|
28102
|
-
console.log(
|
|
28217
|
+
console.log(chalk2.bold(`repair \xB7 ${mode}`));
|
|
28103
28218
|
const { repairSessions: repairSessions2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
|
|
28104
28219
|
const results = await repairSessions2(sessionIds, { dryRun: !options.apply, force: options.force, verbose: options.verbose, server: options.server });
|
|
28105
28220
|
results.forEach(render);
|
|
28106
28221
|
const fixed = results.filter((r) => r.status === "repaired").length;
|
|
28107
28222
|
const would = results.filter((r) => r.status === "would-repair").length;
|
|
28108
|
-
console.log(
|
|
28223
|
+
console.log(chalk2.bold(options.apply ? `Repaired ${fixed} session(s).` : `${would} session(s) would be repaired. Re-run with --apply to write.`));
|
|
28109
28224
|
} catch (err) {
|
|
28110
|
-
console.error(
|
|
28225
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28111
28226
|
process.exit(1);
|
|
28112
28227
|
}
|
|
28113
28228
|
});
|
|
@@ -28117,11 +28232,11 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28117
28232
|
const remote = await serverPost("/api/search", { query, topK, projectFilter: options.project });
|
|
28118
28233
|
const results = remote.results;
|
|
28119
28234
|
if (results.length === 0) {
|
|
28120
|
-
console.log(
|
|
28235
|
+
console.log(chalk2.yellow("No matching sessions found."));
|
|
28121
28236
|
process.exit(0);
|
|
28122
28237
|
}
|
|
28123
28238
|
console.log();
|
|
28124
|
-
console.log(`${
|
|
28239
|
+
console.log(`${chalk2.bold("Results for:")} "${query}"`);
|
|
28125
28240
|
console.log();
|
|
28126
28241
|
const tiers = tierAll(results);
|
|
28127
28242
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -28130,13 +28245,13 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28130
28245
|
if (projectPath.length > 50) projectPath = "..." + projectPath.slice(-47);
|
|
28131
28246
|
let title = (result.firstPrompt || "").replace(/\n/g, " ").trim();
|
|
28132
28247
|
if (title.length > 80) title = title.slice(0, 80) + "...";
|
|
28133
|
-
console.log(`${
|
|
28134
|
-
if (projectPath) console.log(` ${
|
|
28135
|
-
console.log(` ${
|
|
28248
|
+
console.log(`${chalk2.bold.cyan(`#${i + 1}`)} ${title}`);
|
|
28249
|
+
if (projectPath) console.log(` ${chalk2.dim("Project:")} ${projectPath}`);
|
|
28250
|
+
console.log(` ${chalk2.dim("Match:")} ${matchLabel(tiers[i])}`);
|
|
28136
28251
|
if (result.summary) {
|
|
28137
28252
|
let summary = result.summary.replace(/\n/g, " ").trim();
|
|
28138
28253
|
if (summary.length > 200) summary = summary.slice(0, 200) + "...";
|
|
28139
|
-
console.log(` ${
|
|
28254
|
+
console.log(` ${chalk2.yellow("Summary:")} ${summary}`);
|
|
28140
28255
|
}
|
|
28141
28256
|
const chunk = (result.matchedChunks || []).find(
|
|
28142
28257
|
(c) => c.chunkType !== "summary" && c.chunkType !== "first_prompt"
|
|
@@ -28145,16 +28260,16 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28145
28260
|
const label = chunk.chunkType === "assistant" ? "Discussed" : chunk.chunkType === "user_context" ? "Asked about" : chunk.chunkType === "tool_result" ? "Tool result" : "Context";
|
|
28146
28261
|
let text = chunk.text.replace(/\n/g, " ").trim();
|
|
28147
28262
|
if (text.length > 150) text = text.slice(0, 150) + "...";
|
|
28148
|
-
console.log(` ${
|
|
28263
|
+
console.log(` ${chalk2.magenta(label + ":")} ${text}`);
|
|
28149
28264
|
}
|
|
28150
28265
|
{
|
|
28151
28266
|
const rc = resumeCommandFor(result.sessionId);
|
|
28152
|
-
if (rc) console.log(` ${
|
|
28267
|
+
if (rc) console.log(` ${chalk2.green("Resume:")} ${rc}`);
|
|
28153
28268
|
}
|
|
28154
28269
|
console.log();
|
|
28155
28270
|
}
|
|
28156
28271
|
} catch (err) {
|
|
28157
|
-
console.error(
|
|
28272
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28158
28273
|
process.exit(1);
|
|
28159
28274
|
}
|
|
28160
28275
|
});
|
|
@@ -28162,7 +28277,7 @@ program.command("status").description("Show your server's sync status and statis
|
|
|
28162
28277
|
try {
|
|
28163
28278
|
const status = await serverGet("/api/status");
|
|
28164
28279
|
const sync = await serverGet("/api/status/sync");
|
|
28165
|
-
console.log(
|
|
28280
|
+
console.log(chalk2.bold("Chat-Recall Server Status"));
|
|
28166
28281
|
console.log();
|
|
28167
28282
|
console.log(`Synced sessions: ${sync.sessions}`);
|
|
28168
28283
|
console.log(`FTS5 chunks: ${status.totalChunks}`);
|
|
@@ -28174,34 +28289,34 @@ program.command("status").description("Show your server's sync status and statis
|
|
|
28174
28289
|
const types = Object.entries(sync.sourceTypes).filter(([, n]) => Number(n) > 0);
|
|
28175
28290
|
if (types.length > 0) {
|
|
28176
28291
|
console.log();
|
|
28177
|
-
console.log(
|
|
28292
|
+
console.log(chalk2.bold("By source type:"));
|
|
28178
28293
|
for (const [type, n] of types) console.log(` ${type}: ${n} items`);
|
|
28179
28294
|
}
|
|
28180
28295
|
const projects = Object.entries(status.projects || {}).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
28181
28296
|
if (projects.length > 0) {
|
|
28182
28297
|
console.log();
|
|
28183
|
-
console.log(
|
|
28298
|
+
console.log(chalk2.bold("Top projects:"));
|
|
28184
28299
|
for (const [proj, n] of projects) console.log(` ${proj}: ${n} sessions`);
|
|
28185
28300
|
}
|
|
28186
28301
|
} catch (err) {
|
|
28187
|
-
console.error(
|
|
28302
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28188
28303
|
process.exit(1);
|
|
28189
28304
|
}
|
|
28190
28305
|
});
|
|
28191
28306
|
program.command("optimize").description("Deprecated: storage is server-side now, nothing to optimize locally").action(() => {
|
|
28192
|
-
console.log(
|
|
28193
|
-
console.log(
|
|
28307
|
+
console.log(chalk2.dim("Storage is server-side now; there is no local index to optimize."));
|
|
28308
|
+
console.log(chalk2.dim("Run `chat-recall sync` to ship sessions; the server manages its own storage."));
|
|
28194
28309
|
process.exit(0);
|
|
28195
28310
|
});
|
|
28196
28311
|
program.command("show <session_id>").description("Show conversation content from a session (requires login)").option("-m, --messages <number>", "Number of messages to show", "10").option("-f, --full", "Show full conversation (all messages)", false).action(async (sessionId, options) => {
|
|
28197
28312
|
try {
|
|
28198
28313
|
const soft = await serverGetSoft(`/api/conversations/${encodeURIComponent(sessionId)}?limit=0`);
|
|
28199
28314
|
if (!soft.data || soft.data.messages.length === 0) {
|
|
28200
|
-
console.log(
|
|
28315
|
+
console.log(chalk2.yellow(soft.message || `Session not found: ${sessionId}`));
|
|
28201
28316
|
process.exit(soft.status === 404 ? 1 : 0);
|
|
28202
28317
|
}
|
|
28203
28318
|
const messagesList = soft.data.messages;
|
|
28204
|
-
console.log(
|
|
28319
|
+
console.log(chalk2.bold("Session:"), sessionId);
|
|
28205
28320
|
console.log();
|
|
28206
28321
|
const maxMessages = parseInt(options.messages, 10);
|
|
28207
28322
|
const displayMessages = options.full ? messagesList : messagesList.slice(0, maxMessages);
|
|
@@ -28209,22 +28324,22 @@ program.command("show <session_id>").description("Show conversation content from
|
|
|
28209
28324
|
let text = msg.content;
|
|
28210
28325
|
if (!options.full && text.length > 1e3) text = text.slice(0, 1e3) + "...";
|
|
28211
28326
|
if (msg.role === "user") {
|
|
28212
|
-
console.log(`${
|
|
28327
|
+
console.log(`${chalk2.bold.blue("User")} ${chalk2.dim(`(line ${msg.line})`)}`);
|
|
28213
28328
|
} else if (msg.role === "assistant") {
|
|
28214
|
-
console.log(`${
|
|
28329
|
+
console.log(`${chalk2.bold.green("Assistant")} ${chalk2.dim(`(line ${msg.line})`)}`);
|
|
28215
28330
|
} else {
|
|
28216
|
-
console.log(
|
|
28331
|
+
console.log(chalk2.bold.yellow(msg.role));
|
|
28217
28332
|
}
|
|
28218
28333
|
console.log(text);
|
|
28219
28334
|
console.log();
|
|
28220
28335
|
}
|
|
28221
|
-
console.log(
|
|
28336
|
+
console.log(chalk2.dim(`Showing ${displayMessages.length} of ${messagesList.length} messages. Use --full for the complete conversation.`));
|
|
28222
28337
|
{
|
|
28223
28338
|
const rc = resumeCommandFor(sessionId);
|
|
28224
|
-
if (rc) console.log(
|
|
28339
|
+
if (rc) console.log(chalk2.dim(`Resume: ${rc}`));
|
|
28225
28340
|
}
|
|
28226
28341
|
} catch (err) {
|
|
28227
|
-
console.error(
|
|
28342
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28228
28343
|
process.exit(1);
|
|
28229
28344
|
}
|
|
28230
28345
|
});
|
|
@@ -28233,9 +28348,9 @@ memory.command("index").description("Collect local sessions and ship them to you
|
|
|
28233
28348
|
try {
|
|
28234
28349
|
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28235
28350
|
const r = await syncSessions2(options.force ? { useLedger: false } : {});
|
|
28236
|
-
console.log(
|
|
28351
|
+
console.log(chalk2.green(`\u2713 Synced ${r.uploaded} session(s), ${r.items} item(s)`) + chalk2.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)` : ""}`));
|
|
28237
28352
|
} catch (err) {
|
|
28238
|
-
console.error(
|
|
28353
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28239
28354
|
process.exit(1);
|
|
28240
28355
|
}
|
|
28241
28356
|
});
|
|
@@ -28246,55 +28361,55 @@ memory.command("search <query>").description("Search across all memory types on
|
|
|
28246
28361
|
const remote = await serverPost("/api/memory/search", { query, topK, sourceTypes });
|
|
28247
28362
|
const results = remote.results;
|
|
28248
28363
|
if (results.length === 0) {
|
|
28249
|
-
console.log(
|
|
28364
|
+
console.log(chalk2.yellow("No matching results found."));
|
|
28250
28365
|
process.exit(0);
|
|
28251
28366
|
}
|
|
28252
28367
|
console.log();
|
|
28253
|
-
console.log(`${
|
|
28368
|
+
console.log(`${chalk2.bold("Memory search:")} "${query}"`);
|
|
28254
28369
|
console.log();
|
|
28255
28370
|
const tiers = tierAll(results);
|
|
28256
28371
|
for (let i = 0; i < results.length; i++) {
|
|
28257
28372
|
const r = results[i];
|
|
28258
28373
|
const typeColor = {
|
|
28259
|
-
session:
|
|
28260
|
-
plan:
|
|
28261
|
-
task:
|
|
28262
|
-
claude_md:
|
|
28263
|
-
history:
|
|
28264
|
-
paste:
|
|
28265
|
-
diary:
|
|
28266
|
-
}[r.sourceType] ||
|
|
28374
|
+
session: chalk2.blue,
|
|
28375
|
+
plan: chalk2.green,
|
|
28376
|
+
task: chalk2.yellow,
|
|
28377
|
+
claude_md: chalk2.magenta,
|
|
28378
|
+
history: chalk2.cyan,
|
|
28379
|
+
paste: chalk2.gray,
|
|
28380
|
+
diary: chalk2.redBright
|
|
28381
|
+
}[r.sourceType] || chalk2.white;
|
|
28267
28382
|
const typeBadge = typeColor(`[${r.sourceType}]`);
|
|
28268
28383
|
let title = (r.title || "").replace(/\n/g, " ").trim();
|
|
28269
28384
|
if (title.length > 70) title = title.slice(0, 67) + "...";
|
|
28270
|
-
console.log(`${
|
|
28385
|
+
console.log(`${chalk2.bold.cyan(`#${i + 1}`)} ${typeBadge} ${title}`);
|
|
28271
28386
|
if (r.projectPath) {
|
|
28272
28387
|
let pp = r.projectPath;
|
|
28273
28388
|
if (pp.length > 50) pp = "..." + pp.slice(-47);
|
|
28274
|
-
console.log(` ${
|
|
28389
|
+
console.log(` ${chalk2.dim("Project:")} ${pp}`);
|
|
28275
28390
|
}
|
|
28276
|
-
const typeSuffix = r.chunkType ? ` ${
|
|
28277
|
-
console.log(` ${
|
|
28391
|
+
const typeSuffix = r.chunkType ? ` ${chalk2.dim("Type:")} ${r.chunkType}` : "";
|
|
28392
|
+
console.log(` ${chalk2.dim("Match:")} ${matchLabel(tiers[i])}${typeSuffix}`);
|
|
28278
28393
|
let preview = (r.text || "").replace(/\n/g, " ").trim();
|
|
28279
28394
|
if (preview.length > 150) preview = preview.slice(0, 147) + "...";
|
|
28280
28395
|
if (preview) console.log(` ${preview}`);
|
|
28281
28396
|
if (r.sourceType === "session") {
|
|
28282
28397
|
{
|
|
28283
28398
|
const rc = resumeCommandFor(r.itemId);
|
|
28284
|
-
if (rc) console.log(` ${
|
|
28399
|
+
if (rc) console.log(` ${chalk2.green("Resume:")} ${rc}`);
|
|
28285
28400
|
}
|
|
28286
28401
|
}
|
|
28287
28402
|
console.log();
|
|
28288
28403
|
}
|
|
28289
28404
|
} catch (err) {
|
|
28290
|
-
console.error(
|
|
28405
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28291
28406
|
process.exit(1);
|
|
28292
28407
|
}
|
|
28293
28408
|
});
|
|
28294
28409
|
memory.command("status").description("Show your server's memory statistics across all source types (requires login)").action(async () => {
|
|
28295
28410
|
try {
|
|
28296
28411
|
const status = await serverGet("/api/memory/status");
|
|
28297
|
-
console.log(
|
|
28412
|
+
console.log(chalk2.bold("Memory System Status"));
|
|
28298
28413
|
console.log();
|
|
28299
28414
|
console.log(`Total items: ${status.totalItems}`);
|
|
28300
28415
|
console.log(`FTS5 chunks: ${status.totalChunks}`);
|
|
@@ -28302,7 +28417,7 @@ memory.command("status").description("Show your server's memory statistics acros
|
|
|
28302
28417
|
const bySource = Object.entries(status.bySourceType || {});
|
|
28303
28418
|
if (bySource.length > 0) {
|
|
28304
28419
|
console.log();
|
|
28305
|
-
console.log(
|
|
28420
|
+
console.log(chalk2.bold("By source type:"));
|
|
28306
28421
|
for (const [type, data] of bySource) {
|
|
28307
28422
|
console.log(` ${type}: ${data.items} items, ${data.chunks} chunks`);
|
|
28308
28423
|
}
|
|
@@ -28310,14 +28425,14 @@ memory.command("status").description("Show your server's memory statistics acros
|
|
|
28310
28425
|
const byTool = Object.entries(status.bySourceAndTool || {});
|
|
28311
28426
|
if (byTool.length > 0) {
|
|
28312
28427
|
console.log();
|
|
28313
|
-
console.log(
|
|
28428
|
+
console.log(chalk2.bold("By source type and tool:"));
|
|
28314
28429
|
for (const [type, tools] of byTool) {
|
|
28315
28430
|
const parts = Object.entries(tools).map(([t, n]) => `${t}: ${n}`).join(", ");
|
|
28316
28431
|
console.log(` ${type}: ${parts}`);
|
|
28317
28432
|
}
|
|
28318
28433
|
}
|
|
28319
28434
|
} catch (err) {
|
|
28320
|
-
console.error(
|
|
28435
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28321
28436
|
process.exit(1);
|
|
28322
28437
|
}
|
|
28323
28438
|
});
|
|
@@ -28325,20 +28440,20 @@ memory.command("links <source_type> <item_id>").description("Show relationships
|
|
|
28325
28440
|
try {
|
|
28326
28441
|
const { links } = await serverGet(`/api/memory/links/${encodeURIComponent(sourceType)}/${encodeURIComponent(itemId)}`);
|
|
28327
28442
|
if (links.length === 0) {
|
|
28328
|
-
console.log(
|
|
28443
|
+
console.log(chalk2.yellow("No links found."));
|
|
28329
28444
|
return;
|
|
28330
28445
|
}
|
|
28331
|
-
console.log(
|
|
28446
|
+
console.log(chalk2.bold(`Links for ${sourceType}:${itemId}`));
|
|
28332
28447
|
console.log();
|
|
28333
28448
|
for (const link of links) {
|
|
28334
28449
|
const direction = link.source_id === itemId ? "->" : "<-";
|
|
28335
28450
|
const otherType = link.source_id === itemId ? link.target_type : link.source_type;
|
|
28336
28451
|
const otherId = link.source_id === itemId ? link.target_id : link.source_id;
|
|
28337
28452
|
const confidence = Math.round(link.confidence * 100);
|
|
28338
|
-
console.log(` ${direction} ${
|
|
28453
|
+
console.log(` ${direction} ${chalk2.bold(otherType)}:${otherId.slice(0, 20)} [${link.link_type}] (${confidence}%)`);
|
|
28339
28454
|
}
|
|
28340
28455
|
} catch (err) {
|
|
28341
|
-
console.error(
|
|
28456
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28342
28457
|
process.exit(1);
|
|
28343
28458
|
}
|
|
28344
28459
|
});
|
|
@@ -28350,27 +28465,27 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
|
|
|
28350
28465
|
identity = readFileSync49(identityFile, "utf-8").trim();
|
|
28351
28466
|
}
|
|
28352
28467
|
const lines = [
|
|
28353
|
-
|
|
28354
|
-
|
|
28468
|
+
chalk2.bold("# Wake-Up Context"),
|
|
28469
|
+
chalk2.bold("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
28355
28470
|
"",
|
|
28356
|
-
|
|
28471
|
+
chalk2.bold("## Identity"),
|
|
28357
28472
|
identity,
|
|
28358
28473
|
""
|
|
28359
28474
|
];
|
|
28360
28475
|
const qs = options.project ? `?project_filter=${encodeURIComponent(options.project)}` : "";
|
|
28361
28476
|
const wake = await serverGet(`/api/memory/wake-up${qs}`);
|
|
28362
28477
|
if (wake.highFacts.length > 0) {
|
|
28363
|
-
lines.push(
|
|
28478
|
+
lines.push(chalk2.bold("## High-Importance Facts"));
|
|
28364
28479
|
for (const fact of wake.highFacts) {
|
|
28365
28480
|
lines.push(` [${fact.type}] ${fact.text}`);
|
|
28366
28481
|
}
|
|
28367
28482
|
lines.push("");
|
|
28368
28483
|
}
|
|
28369
28484
|
if (wake.kg.facts.length > 0) {
|
|
28370
|
-
lines.push(
|
|
28485
|
+
lines.push(chalk2.bold("## Knowledge Graph"));
|
|
28371
28486
|
const { entities, current_facts } = wake.kg.stats;
|
|
28372
28487
|
if (entities !== void 0 || current_facts !== void 0) {
|
|
28373
|
-
lines.push(
|
|
28488
|
+
lines.push(chalk2.dim(`${entities ?? 0} entities, ${current_facts ?? 0} current facts`));
|
|
28374
28489
|
}
|
|
28375
28490
|
for (const fact of wake.kg.facts) {
|
|
28376
28491
|
lines.push(` ${fact.subject} \u2192 ${fact.predicate} \u2192 ${fact.object}`);
|
|
@@ -28379,32 +28494,75 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
|
|
|
28379
28494
|
}
|
|
28380
28495
|
console.log(lines.join("\n"));
|
|
28381
28496
|
} catch (err) {
|
|
28382
|
-
console.error(
|
|
28497
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28383
28498
|
process.exit(1);
|
|
28384
28499
|
}
|
|
28385
28500
|
});
|
|
28386
28501
|
program.command("escalate [sessionId]").description("Escalate a finished session's learnings (decisions, user corrections, outcome) into the knowledge graph (requires login)").option("--latest", "Pick the most recent synced session of the current project").option("-p, --project <name>", "Knowledge-graph entity name for the facts (default: project folder name)").option("--dry-run", "Extract and print the learnings without writing them").action(async (sessionId, opts) => {
|
|
28387
28502
|
if (!sessionId && !opts.latest) {
|
|
28388
|
-
console.error(
|
|
28503
|
+
console.error(chalk2.red("Pass a session id or --latest."));
|
|
28389
28504
|
process.exit(1);
|
|
28390
28505
|
}
|
|
28391
28506
|
const target = firstTarget();
|
|
28392
28507
|
if (!target) {
|
|
28393
|
-
console.log(
|
|
28508
|
+
console.log(chalk2.dim("escalate: not logged in \u2014 nothing to do. Run `chat-recall login <server-url>` to enable learnings escalation."));
|
|
28394
28509
|
return;
|
|
28395
28510
|
}
|
|
28396
28511
|
try {
|
|
28397
28512
|
const { runEscalate: runEscalate2 } = await Promise.resolve().then(() => (init_escalate(), escalate_exports));
|
|
28398
28513
|
await runEscalate2(
|
|
28399
|
-
{ get: serverGet, getSoft: serverGetSoft, post: serverPost, log: (line) => console.log(
|
|
28514
|
+
{ get: serverGet, getSoft: serverGetSoft, post: serverPost, log: (line) => console.log(chalk2.dim(line)) },
|
|
28400
28515
|
{ sessionId, latest: opts.latest, project: opts.project, dryRun: opts.dryRun, cwd: process.cwd() }
|
|
28401
28516
|
);
|
|
28402
28517
|
} catch (err) {
|
|
28403
|
-
console.log(
|
|
28518
|
+
console.log(chalk2.dim(`escalate: skipped \u2014 ${err instanceof Error ? err.message : err}`));
|
|
28519
|
+
}
|
|
28520
|
+
});
|
|
28521
|
+
program.command("feedback").argument("<message...>", "what you want to say \u2014 anything at all").description("Send a message to the people who build chat-recall. No account needed.").option("--email <address>", "so we can reply; optional").action(async (words, opts) => {
|
|
28522
|
+
const message = words.join(" ").trim();
|
|
28523
|
+
if (message.length < 3) {
|
|
28524
|
+
console.error(chalk2.red("Say a little more than that."));
|
|
28525
|
+
process.exitCode = 1;
|
|
28526
|
+
return;
|
|
28527
|
+
}
|
|
28528
|
+
const target = firstTarget();
|
|
28529
|
+
const base2 = target?.base || (process.env.CHAT_RECALL_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
|
|
28530
|
+
try {
|
|
28531
|
+
const res = await fetch(`${base2}/api/contact/feedback`, {
|
|
28532
|
+
method: "POST",
|
|
28533
|
+
headers: {
|
|
28534
|
+
"content-type": "application/json",
|
|
28535
|
+
// Attached when logged in so a reply can find the account. Absent is
|
|
28536
|
+
// fine and expected.
|
|
28537
|
+
...target ? { authorization: `Bearer ${target.token}` } : {}
|
|
28538
|
+
},
|
|
28539
|
+
body: JSON.stringify({
|
|
28540
|
+
message,
|
|
28541
|
+
email: opts.email ?? null,
|
|
28542
|
+
cliVersion: pkgVersion,
|
|
28543
|
+
os: `${process.platform}-${process.arch}`
|
|
28544
|
+
}),
|
|
28545
|
+
signal: AbortSignal.timeout(1e4)
|
|
28546
|
+
});
|
|
28547
|
+
if (!res.ok) {
|
|
28548
|
+
const body = await res.json().catch(() => ({}));
|
|
28549
|
+
console.error(chalk2.red(`Could not send: ${body.error || res.status}`));
|
|
28550
|
+
process.exitCode = 1;
|
|
28551
|
+
return;
|
|
28552
|
+
}
|
|
28553
|
+
console.log(chalk2.green("Sent. Thank you \u2014 this genuinely gets read."));
|
|
28554
|
+
if (!opts.email) {
|
|
28555
|
+
console.log(chalk2.dim(" Add --email <address> next time if you want a reply."));
|
|
28556
|
+
}
|
|
28557
|
+
} catch (err) {
|
|
28558
|
+
console.error(chalk2.red(`Could not reach ${base2}.`));
|
|
28559
|
+
console.error(chalk2.dim(" Your message, so it is not lost:"));
|
|
28560
|
+
console.error(chalk2.dim(` ${message}`));
|
|
28561
|
+
process.exitCode = 1;
|
|
28404
28562
|
}
|
|
28405
28563
|
});
|
|
28406
28564
|
program.command("doctor").description("Quick health check across login, server, credentials, hooks, MCP server, and codeindex").option("--purge-local", "Remove the legacy local index (cache.db + vector/KG files) left by pre-thin-collector versions \u2014 storage is server-side now. Requires login; keeps credentials, sync ledger, identity, and the audit log.").action(async (opts) => {
|
|
28407
|
-
const { existsSync: existsSync57, readFileSync: readFileSync50, statSync:
|
|
28565
|
+
const { existsSync: existsSync57, readFileSync: readFileSync50, statSync: statSync47 } = await import("fs");
|
|
28408
28566
|
const { execSync: execSync6 } = await import("child_process");
|
|
28409
28567
|
const rows = [];
|
|
28410
28568
|
const note = (ok, label, detail) => rows.push({ ok, label, detail });
|
|
@@ -28472,7 +28630,7 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28472
28630
|
note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
|
|
28473
28631
|
} else {
|
|
28474
28632
|
try {
|
|
28475
|
-
const mode =
|
|
28633
|
+
const mode = statSync47(credFile).mode & 511;
|
|
28476
28634
|
const isWin = process.platform === "win32";
|
|
28477
28635
|
const safe = isWin || (mode & 63) === 0;
|
|
28478
28636
|
note(safe, "Credentials file", isWin ? `${credFile} (Windows ACL \u2014 POSIX mode does not apply)` : `${credFile} (mode ${mode.toString(8).padStart(3, "0")}${safe ? "" : " \u2014 should be 600"})`);
|
|
@@ -28610,11 +28768,11 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28610
28768
|
} else if (legacyPresent.length === 0) {
|
|
28611
28769
|
note(true, "Purge legacy index", "nothing to remove");
|
|
28612
28770
|
} else {
|
|
28613
|
-
const { rmSync:
|
|
28771
|
+
const { rmSync: rmSync10 } = await import("fs");
|
|
28614
28772
|
let removed = 0;
|
|
28615
28773
|
for (const p of legacyPresent) {
|
|
28616
28774
|
try {
|
|
28617
|
-
|
|
28775
|
+
rmSync10(p, { recursive: true, force: true });
|
|
28618
28776
|
removed++;
|
|
28619
28777
|
} catch {
|
|
28620
28778
|
}
|
|
@@ -28626,67 +28784,67 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28626
28784
|
} else {
|
|
28627
28785
|
note(true, "Legacy local index", "none (clean)");
|
|
28628
28786
|
}
|
|
28629
|
-
console.log(
|
|
28787
|
+
console.log(chalk2.bold("\nchat-recall doctor\n"));
|
|
28630
28788
|
for (const r of rows) {
|
|
28631
|
-
const icon = r.ok ?
|
|
28632
|
-
console.log(` ${icon} ${r.label.padEnd(28)} ${
|
|
28789
|
+
const icon = r.ok ? chalk2.green("\u2713") : chalk2.yellow("\u2022");
|
|
28790
|
+
console.log(` ${icon} ${r.label.padEnd(28)} ${chalk2.dim(r.detail || "")}`);
|
|
28633
28791
|
}
|
|
28634
28792
|
const failures = rows.filter((r) => !r.ok).length;
|
|
28635
28793
|
console.log();
|
|
28636
28794
|
if (failures === 0) {
|
|
28637
|
-
console.log(
|
|
28795
|
+
console.log(chalk2.green("All systems green."));
|
|
28638
28796
|
} else {
|
|
28639
|
-
console.log(
|
|
28797
|
+
console.log(chalk2.dim(`${failures} item${failures === 1 ? "" : "s"} need attention.`));
|
|
28640
28798
|
}
|
|
28641
28799
|
});
|
|
28642
28800
|
var companions = program.command("companions").description("Manage companion tools (codeindex)");
|
|
28643
28801
|
companions.command("status").description("Show whether codeindex is installed and what it provides").action(async () => {
|
|
28644
28802
|
const { checkCodeindexStatus: checkCodeindexStatus2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28645
28803
|
const s = checkCodeindexStatus2();
|
|
28646
|
-
console.log(
|
|
28804
|
+
console.log(chalk2.bold("codeindex status"));
|
|
28647
28805
|
if (s.installed) {
|
|
28648
|
-
console.log(` ${
|
|
28806
|
+
console.log(` ${chalk2.green("\u2713 installed")}`);
|
|
28649
28807
|
if (s.path) console.log(` path: ${s.path}`);
|
|
28650
28808
|
if (s.size) console.log(` size: ${(s.size / 1024 / 1024).toFixed(1)} MB`);
|
|
28651
28809
|
if (s.version) console.log(` version: ${s.version}`);
|
|
28652
28810
|
} else {
|
|
28653
|
-
console.log(` ${
|
|
28811
|
+
console.log(` ${chalk2.yellow("not installed")}`);
|
|
28654
28812
|
if (!s.prebuiltAvailable) {
|
|
28655
|
-
console.log(` ${
|
|
28813
|
+
console.log(` ${chalk2.dim(s.unsupportedReason || "no prebuilt binary for this platform")}`);
|
|
28656
28814
|
} else {
|
|
28657
|
-
console.log(` ${
|
|
28815
|
+
console.log(` ${chalk2.dim(`run \`chat-recall companions install\` to fetch ${s.artifactName}`)}`);
|
|
28658
28816
|
}
|
|
28659
28817
|
}
|
|
28660
28818
|
console.log();
|
|
28661
|
-
console.log(
|
|
28662
|
-
console.log(
|
|
28663
|
-
console.log(
|
|
28819
|
+
console.log(chalk2.dim("codeindex provides code-level lookup as a separate MCP server:"));
|
|
28820
|
+
console.log(chalk2.dim(" find_symbol, find_callers, get_imports, plan_change,"));
|
|
28821
|
+
console.log(chalk2.dim(" get_change_impact, analyze (security/dead_code/coupling/cycles/...)"));
|
|
28664
28822
|
});
|
|
28665
28823
|
companions.command("install").description("Install codeindex (downloads from munhq/codeindex GitHub release)").option("--force", "Re-download even if already installed").action(async (opts) => {
|
|
28666
28824
|
const { installCodeindex: installCodeindex2, registerCodeindexMcp: registerCodeindexMcp2, CODEINDEX_BIN_PATH: CODEINDEX_BIN_PATH2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28667
28825
|
try {
|
|
28668
28826
|
const result = await installCodeindex2({ force: opts.force });
|
|
28669
28827
|
if (!result.prebuiltAvailable && !result.installed) {
|
|
28670
|
-
console.log(
|
|
28671
|
-
console.log(
|
|
28828
|
+
console.log(chalk2.yellow(`codeindex: ${result.unsupportedReason}`));
|
|
28829
|
+
console.log(chalk2.dim(" Build from source: https://github.com/munhq/codeindex#install"));
|
|
28672
28830
|
return;
|
|
28673
28831
|
}
|
|
28674
28832
|
if (result.installed) {
|
|
28675
28833
|
const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
|
|
28676
|
-
console.log(
|
|
28834
|
+
console.log(chalk2.green(`\u2713 codeindex installed (${sizeMb}) \u2192 ${result.path}`));
|
|
28677
28835
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
28678
28836
|
const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
|
|
28679
|
-
if (reg.added) console.log(
|
|
28680
|
-
else console.log(
|
|
28837
|
+
if (reg.added) console.log(chalk2.dim(` Registered as MCP server in ${mcpJsonPath}`));
|
|
28838
|
+
else console.log(chalk2.dim(` Already registered as MCP server`));
|
|
28681
28839
|
}
|
|
28682
28840
|
} catch (err) {
|
|
28683
28841
|
const msg = String(err);
|
|
28684
|
-
console.error(
|
|
28842
|
+
console.error(chalk2.red(`codeindex install failed: ${msg}`));
|
|
28685
28843
|
if (msg.includes("404")) {
|
|
28686
|
-
console.error(
|
|
28687
|
-
console.error(
|
|
28844
|
+
console.error(chalk2.dim(" The codeindex release is currently private. Either authenticate `gh`"));
|
|
28845
|
+
console.error(chalk2.dim(" to munhq/codeindex, or build from source: https://github.com/munhq/codeindex"));
|
|
28688
28846
|
} else {
|
|
28689
|
-
console.error(
|
|
28847
|
+
console.error(chalk2.dim(" This does not block chat-recall \u2014 it just disables code-level lookup."));
|
|
28690
28848
|
}
|
|
28691
28849
|
process.exit(1);
|
|
28692
28850
|
}
|
|
@@ -28694,15 +28852,15 @@ companions.command("install").description("Install codeindex (downloads from mun
|
|
|
28694
28852
|
companions.command("uninstall").description("Remove the codeindex binary chat-recall installed (does not touch your other tools)").action(async () => {
|
|
28695
28853
|
const { uninstallCodeindex: uninstallCodeindex2, unregisterCodeindexMcp: unregisterCodeindexMcp2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28696
28854
|
const r = uninstallCodeindex2();
|
|
28697
|
-
if (r.removed) console.log(
|
|
28698
|
-
else console.log(
|
|
28855
|
+
if (r.removed) console.log(chalk2.green(`\u2713 Removed ${r.path}`));
|
|
28856
|
+
else console.log(chalk2.dim(` Nothing to remove at ${r.path}`));
|
|
28699
28857
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
28700
28858
|
const u = unregisterCodeindexMcp2(mcpJsonPath);
|
|
28701
|
-
if (u.removed) console.log(
|
|
28859
|
+
if (u.removed) console.log(chalk2.dim(` Unregistered MCP server from ${mcpJsonPath}`));
|
|
28702
28860
|
});
|
|
28703
28861
|
program.command("verify").description("Check that the server holds everything this machine has, per session").option("--deep", "Compare per-session content size against each server", false).option("--records", "With --deep: also diff the exact record (uuid) sets \u2014 proof, not inference", false).option("--since-hours <n>", "Only check sessions modified in the last N hours (default: all)").option("--repair", "Re-ship every STRANDED session found (clears its ledger cursor)", false).option("--json", "Machine-readable output", false).action(async (opts) => {
|
|
28704
28862
|
if (!opts.deep) {
|
|
28705
|
-
console.log(
|
|
28863
|
+
console.log(chalk2.yellow("verify currently implements only --deep; re-run with --deep."));
|
|
28706
28864
|
return;
|
|
28707
28865
|
}
|
|
28708
28866
|
const { verifyAgainstServer: verifyAgainstServer2, verifyTargets: verifyTargets2 } = await Promise.resolve().then(() => (init_verify_deep(), verify_deep_exports));
|
|
@@ -28713,7 +28871,7 @@ program.command("verify").description("Check that the server holds everything th
|
|
|
28713
28871
|
const { forceFullResync: forceFullResync2 } = await Promise.resolve().then(() => (init_verify_repair(), verify_repair_exports));
|
|
28714
28872
|
const targets = verifyTargets2();
|
|
28715
28873
|
if (targets.length === 0) {
|
|
28716
|
-
console.error(
|
|
28874
|
+
console.error(chalk2.red("No server configured \u2014 run `chat-recall login` first."));
|
|
28717
28875
|
process.exit(1);
|
|
28718
28876
|
}
|
|
28719
28877
|
const sinceMs = opts.sinceHours ? Date.now() - Number(opts.sinceHours) * 36e5 : 0;
|
|
@@ -28778,37 +28936,37 @@ program.command("verify").description("Check that the server holds everything th
|
|
|
28778
28936
|
try {
|
|
28779
28937
|
report = await verifyAgainstServer2(t.serverUrl, t.token, sinceMs, deps);
|
|
28780
28938
|
} catch (e) {
|
|
28781
|
-
console.error(
|
|
28939
|
+
console.error(chalk2.red(`${t.serverUrl}: ${e instanceof Error ? e.message : e}`));
|
|
28782
28940
|
exitCode = 1;
|
|
28783
28941
|
continue;
|
|
28784
28942
|
}
|
|
28785
28943
|
reports.push(report);
|
|
28786
28944
|
if (opts.json) continue;
|
|
28787
|
-
console.log(
|
|
28945
|
+
console.log(chalk2.bold(`
|
|
28788
28946
|
${t.serverUrl}`));
|
|
28789
28947
|
console.log(` checked ${report.checked} \xB7 complete ${report.complete} \xB7 no archive ${report.missingArchive}`);
|
|
28790
28948
|
if (report.pending.length > 0) {
|
|
28791
|
-
console.log(
|
|
28949
|
+
console.log(chalk2.dim(` ${report.pending.length} pending (the ledger knows there is more to send \u2014 these go on their own)`));
|
|
28792
28950
|
}
|
|
28793
28951
|
if (report.stranded.length === 0) {
|
|
28794
|
-
console.log(
|
|
28952
|
+
console.log(chalk2.green(" \u2713 nothing stranded"));
|
|
28795
28953
|
} else {
|
|
28796
28954
|
exitCode = 2;
|
|
28797
|
-
console.log(
|
|
28955
|
+
console.log(chalk2.red(` \u2717 ${report.stranded.length} STRANDED \u2014 the ledger claims complete while the server holds less:`));
|
|
28798
28956
|
for (const f of report.stranded.slice(0, 25)) {
|
|
28799
28957
|
console.log(` ${f.sessionId.slice(0, 8)} server short by ${String(f.deficit).padStart(9)} B ${(f.projectPath || "").slice(-44)}`);
|
|
28800
28958
|
}
|
|
28801
|
-
if (report.stranded.length > 25) console.log(
|
|
28959
|
+
if (report.stranded.length > 25) console.log(chalk2.dim(` \u2026and ${report.stranded.length - 25} more`));
|
|
28802
28960
|
if (!opts.repair) {
|
|
28803
|
-
console.log(
|
|
28961
|
+
console.log(chalk2.dim(" Re-run with --repair to clear their cursors and re-ship."));
|
|
28804
28962
|
}
|
|
28805
28963
|
}
|
|
28806
28964
|
if (opts.records) {
|
|
28807
28965
|
const suspects = [...report.stranded, ...report.pending];
|
|
28808
28966
|
if (suspects.length === 0) {
|
|
28809
|
-
console.log(
|
|
28967
|
+
console.log(chalk2.dim(" --records: nothing to deep-check (size pass found no gaps)"));
|
|
28810
28968
|
} else {
|
|
28811
|
-
console.log(
|
|
28969
|
+
console.log(chalk2.bold(`
|
|
28812
28970
|
record-level diff (${suspects.length} session(s)):`));
|
|
28813
28971
|
for (const f of suspects) {
|
|
28814
28972
|
let serverIds;
|
|
@@ -28833,9 +28991,9 @@ ${t.serverUrl}`));
|
|
|
28833
28991
|
}
|
|
28834
28992
|
const missing = [...localIds].filter((u) => !serverIds.has(u)).length;
|
|
28835
28993
|
const onlyServer = [...serverIds].filter((u) => !localIds.has(u)).length;
|
|
28836
|
-
const verdict = missing === 0 ?
|
|
28994
|
+
const verdict = missing === 0 ? chalk2.green("in sync") : chalk2.red(`${missing} MISSING server-side`);
|
|
28837
28995
|
console.log(
|
|
28838
|
-
` ${f.sessionId.slice(0, 8)} local ${String(localIds.size).padStart(6)} server ${String(serverIds.size).padStart(6)} ${verdict}` + (onlyServer > 0 ?
|
|
28996
|
+
` ${f.sessionId.slice(0, 8)} local ${String(localIds.size).padStart(6)} server ${String(serverIds.size).padStart(6)} ${verdict}` + (onlyServer > 0 ? chalk2.dim(` (+${onlyServer} server-only \u2014 resume truncation)`) : "")
|
|
28839
28997
|
);
|
|
28840
28998
|
if (missing > 0) exitCode = 2;
|
|
28841
28999
|
}
|
|
@@ -28843,7 +29001,7 @@ ${t.serverUrl}`));
|
|
|
28843
29001
|
}
|
|
28844
29002
|
if (opts.repair && report.stranded.length > 0) {
|
|
28845
29003
|
const n = forceFullResync2(t.serverUrl, report.stranded.map((f) => f.sessionId));
|
|
28846
|
-
console.log(
|
|
29004
|
+
console.log(chalk2.green(` \u21BB cleared ${n} ledger cursor(s) \u2014 run \`chat-recall sync\` to re-ship`));
|
|
28847
29005
|
}
|
|
28848
29006
|
}
|
|
28849
29007
|
if (opts.json) console.log(JSON.stringify({ reports }, null, 2));
|
|
@@ -28851,19 +29009,19 @@ ${t.serverUrl}`));
|
|
|
28851
29009
|
});
|
|
28852
29010
|
var sources2 = program.command("sources").description("See and choose which AI-tool transcript folders get synced");
|
|
28853
29011
|
function decisionBadge(d) {
|
|
28854
|
-
if (d === "primary") return
|
|
28855
|
-
if (d === "approved") return
|
|
28856
|
-
if (d === "declined") return
|
|
28857
|
-
return
|
|
29012
|
+
if (d === "primary") return chalk2.green("syncing") + chalk2.dim(" (main)");
|
|
29013
|
+
if (d === "approved") return chalk2.green("syncing");
|
|
29014
|
+
if (d === "declined") return chalk2.dim("not syncing");
|
|
29015
|
+
return chalk2.yellow("NEEDS A DECISION");
|
|
28858
29016
|
}
|
|
28859
29017
|
async function listSources() {
|
|
28860
29018
|
const { discoverHomes: discoverHomes2 } = await Promise.resolve().then(() => (init_home_discovery(), home_discovery_exports));
|
|
28861
29019
|
const { homeDecision: homeDecision2, grandfatherLegacyHomes: grandfatherLegacyHomes3 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28862
29020
|
grandfatherLegacyHomes3();
|
|
28863
29021
|
const homes = discoverHomes2();
|
|
28864
|
-
console.log(
|
|
29022
|
+
console.log(chalk2.bold("Transcript folders found on this machine"));
|
|
28865
29023
|
if (homes.length === 0) {
|
|
28866
|
-
console.log(
|
|
29024
|
+
console.log(chalk2.dim(" none \u2014 no AI tool transcripts detected"));
|
|
28867
29025
|
return { pending: 0 };
|
|
28868
29026
|
}
|
|
28869
29027
|
let pending = 0;
|
|
@@ -28872,14 +29030,14 @@ async function listSources() {
|
|
|
28872
29030
|
if (d === "pending") pending++;
|
|
28873
29031
|
const count = h.sessions > 0 ? `${h.sessions} session${h.sessions === 1 ? "" : "s"}` : "\u2014";
|
|
28874
29032
|
console.log(
|
|
28875
|
-
` ${h.tool.padEnd(9)} ${count.padStart(14)} ${decisionBadge(d).padEnd(30)} ${h.path}` + (h.via === "declared" ?
|
|
29033
|
+
` ${h.tool.padEnd(9)} ${count.padStart(14)} ${decisionBadge(d).padEnd(30)} ${h.path}` + (h.via === "declared" ? chalk2.dim(" (you configured this)") : "")
|
|
28876
29034
|
);
|
|
28877
29035
|
}
|
|
28878
29036
|
if (pending > 0) {
|
|
28879
29037
|
console.log();
|
|
28880
|
-
console.log(
|
|
28881
|
-
console.log(
|
|
28882
|
-
console.log(
|
|
29038
|
+
console.log(chalk2.yellow(` ${pending} folder(s) need a decision \u2014 they are NOT being synced yet.`));
|
|
29039
|
+
console.log(chalk2.dim(" chat-recall sources approve <path> start syncing it"));
|
|
29040
|
+
console.log(chalk2.dim(" chat-recall sources decline <path> keep it out (e.g. a work account)"));
|
|
28883
29041
|
}
|
|
28884
29042
|
return { pending };
|
|
28885
29043
|
}
|
|
@@ -28892,74 +29050,74 @@ sources2.command("add <path>").description("Sync a transcript folder we did not
|
|
|
28892
29050
|
const abs = normalizeHomePath2(path);
|
|
28893
29051
|
const tool = identifyHome2(abs);
|
|
28894
29052
|
if (!tool) {
|
|
28895
|
-
console.error(
|
|
28896
|
-
console.error(
|
|
28897
|
-
console.error(
|
|
28898
|
-
console.error(
|
|
28899
|
-
console.error(
|
|
28900
|
-
console.error(
|
|
28901
|
-
console.error(
|
|
29053
|
+
console.error(chalk2.red(`Not a transcript folder: ${abs}`));
|
|
29054
|
+
console.error(chalk2.dim(" Expected one of:"));
|
|
29055
|
+
console.error(chalk2.dim(" <dir>/projects/<project>/<uuid>.jsonl (Claude Code)"));
|
|
29056
|
+
console.error(chalk2.dim(" <dir>/sessions/YYYY/MM/DD/rollout-*.jsonl (Codex)"));
|
|
29057
|
+
console.error(chalk2.dim(" <dir>/tmp/<project>/chats/session-*.json (Gemini)"));
|
|
29058
|
+
console.error(chalk2.dim(" <dir>/brain/<id>/.system_generated/logs/* (Antigravity)"));
|
|
29059
|
+
console.error(chalk2.dim(" <dir>/opencode.db (OpenCode)"));
|
|
28902
29060
|
process.exit(1);
|
|
28903
29061
|
}
|
|
28904
29062
|
if (!approveHome2(abs)) {
|
|
28905
|
-
console.error(
|
|
29063
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28906
29064
|
process.exit(1);
|
|
28907
29065
|
}
|
|
28908
|
-
console.log(
|
|
28909
|
-
console.log(
|
|
29066
|
+
console.log(chalk2.green(`\u2713 syncing ${tool} transcripts from ${abs}`));
|
|
29067
|
+
console.log(chalk2.dim(" Takes effect on the next sync; run `chat-recall sync` to do it now."));
|
|
28910
29068
|
});
|
|
28911
29069
|
sources2.command("approve <path>").description("Start syncing a folder that was waiting for a decision").action(async (path) => {
|
|
28912
29070
|
const { approveHome: approveHome2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28913
29071
|
const abs = normalizeHomePath2(path);
|
|
28914
29072
|
if (!approveHome2(abs)) {
|
|
28915
|
-
console.error(
|
|
29073
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28916
29074
|
process.exit(1);
|
|
28917
29075
|
}
|
|
28918
|
-
console.log(
|
|
29076
|
+
console.log(chalk2.green(`\u2713 now syncing ${abs}`));
|
|
28919
29077
|
});
|
|
28920
29078
|
sources2.command("decline <path>").description("Keep a folder out of sync (e.g. a work account you do not want here)").option("--delete-remote", "Also delete sessions already uploaded FROM this folder (irreversible)", false).option("--yes", "Skip the confirmation prompt for --delete-remote", false).action(async (path, opts) => {
|
|
28921
29079
|
const { declineHome: declineHome2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28922
29080
|
const { identifyHome: identifyHome2, sessionIdsInHome: sessionIdsInHome2 } = await Promise.resolve().then(() => (init_home_discovery(), home_discovery_exports));
|
|
28923
29081
|
const abs = normalizeHomePath2(path);
|
|
28924
29082
|
if (!declineHome2(abs)) {
|
|
28925
|
-
console.error(
|
|
29083
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28926
29084
|
process.exit(1);
|
|
28927
29085
|
}
|
|
28928
|
-
console.log(
|
|
29086
|
+
console.log(chalk2.green(`\u2713 not syncing ${abs}`));
|
|
28929
29087
|
if (!opts.deleteRemote) {
|
|
28930
|
-
console.log(
|
|
28931
|
-
console.log(
|
|
29088
|
+
console.log(chalk2.dim(" Sessions already uploaded from it stay on the server."));
|
|
29089
|
+
console.log(chalk2.dim(` To remove those too: chat-recall sources decline ${path} --delete-remote`));
|
|
28932
29090
|
return;
|
|
28933
29091
|
}
|
|
28934
29092
|
const tool = identifyHome2(abs);
|
|
28935
29093
|
if (!tool) {
|
|
28936
|
-
console.error(
|
|
29094
|
+
console.error(chalk2.red(`Not a transcript folder: ${abs}`));
|
|
28937
29095
|
process.exit(1);
|
|
28938
29096
|
}
|
|
28939
29097
|
const ids = sessionIdsInHome2(abs, tool);
|
|
28940
29098
|
if (ids.length === 0) {
|
|
28941
|
-
console.log(
|
|
29099
|
+
console.log(chalk2.dim(" Nothing to delete \u2014 no session files found in that folder."));
|
|
28942
29100
|
if (tool === "opencode") {
|
|
28943
|
-
console.log(
|
|
29101
|
+
console.log(chalk2.dim(" (OpenCode keys sessions by database row, so they cannot be enumerated from the path.)"));
|
|
28944
29102
|
}
|
|
28945
29103
|
return;
|
|
28946
29104
|
}
|
|
28947
29105
|
const targets = loadAllCredentials();
|
|
28948
29106
|
if (targets.length === 0) {
|
|
28949
|
-
console.error(
|
|
29107
|
+
console.error(chalk2.red("Not logged in \u2014 nothing to delete from."));
|
|
28950
29108
|
process.exit(1);
|
|
28951
29109
|
}
|
|
28952
29110
|
if (!opts.yes) {
|
|
28953
29111
|
console.log();
|
|
28954
|
-
console.log(
|
|
28955
|
-
console.log(
|
|
28956
|
-
console.log(
|
|
29112
|
+
console.log(chalk2.yellow(`About to DELETE ${ids.length} session(s) from ${targets.length} server(s).`));
|
|
29113
|
+
console.log(chalk2.yellow("This is irreversible \u2014 the transcripts stay on this machine, but the"));
|
|
29114
|
+
console.log(chalk2.yellow("server copies (search index, conversation view, archive) are removed."));
|
|
28957
29115
|
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
28958
29116
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
28959
29117
|
const answer = (await rl.question("Type the number of sessions to confirm: ")).trim();
|
|
28960
29118
|
rl.close();
|
|
28961
29119
|
if (answer !== String(ids.length)) {
|
|
28962
|
-
console.log(
|
|
29120
|
+
console.log(chalk2.dim("Cancelled \u2014 nothing deleted."));
|
|
28963
29121
|
return;
|
|
28964
29122
|
}
|
|
28965
29123
|
}
|
|
@@ -28979,8 +29137,8 @@ sources2.command("decline <path>").description("Keep a folder out of sync (e.g.
|
|
|
28979
29137
|
failed++;
|
|
28980
29138
|
}
|
|
28981
29139
|
}
|
|
28982
|
-
const line = ` ${base2}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ?
|
|
28983
|
-
console.log(failed ?
|
|
29140
|
+
const line = ` ${base2}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ? chalk2.red(`, ${failed} FAILED`) : "");
|
|
29141
|
+
console.log(failed ? chalk2.yellow(line) : chalk2.green(line));
|
|
28984
29142
|
if (failed) process.exitCode = 1;
|
|
28985
29143
|
}
|
|
28986
29144
|
});
|
|
@@ -28988,30 +29146,30 @@ sources2.command("forget <path>").description("Undo a decision, so the folder is
|
|
|
28988
29146
|
const { resetHomeDecision: resetHomeDecision2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28989
29147
|
const abs = normalizeHomePath2(path);
|
|
28990
29148
|
resetHomeDecision2(abs);
|
|
28991
|
-
console.log(
|
|
29149
|
+
console.log(chalk2.green(`\u2713 ${abs} is undecided again (not syncing until approved)`));
|
|
28992
29150
|
});
|
|
28993
29151
|
var detectors = program.command("detectors").description("Manage the optional external secret detectors (gitleaks, trufflehog)");
|
|
28994
29152
|
detectors.command("status").description("Show which detectors are installed, pinned and verified").action(async () => {
|
|
28995
29153
|
const { detectorStatus: detectorStatus2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
28996
29154
|
const { externalScannersEnabled: externalScannersEnabled2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
28997
|
-
console.log(
|
|
29155
|
+
console.log(chalk2.bold("external secret detectors"));
|
|
28998
29156
|
console.log(
|
|
28999
|
-
externalScannersEnabled2() ? ` gate: ${
|
|
29157
|
+
externalScannersEnabled2() ? ` gate: ${chalk2.green("enabled")} (CHAT_RECALL_EXTERNAL_SCANNERS)` : ` gate: ${chalk2.yellow("disabled")} \u2014 set CHAT_RECALL_EXTERNAL_SCANNERS=1 to use them`
|
|
29000
29158
|
);
|
|
29001
29159
|
if (process.env.CHAT_RECALL_DETECTOR_DIR) {
|
|
29002
|
-
console.log(` dir: ${
|
|
29160
|
+
console.log(` dir: ${chalk2.dim(process.env.CHAT_RECALL_DETECTOR_DIR)} ${chalk2.dim("(operator-supplied, unverified)")}`);
|
|
29003
29161
|
}
|
|
29004
29162
|
console.log();
|
|
29005
29163
|
for (const d of detectorStatus2()) {
|
|
29006
|
-
const label = `${d.name} ${
|
|
29007
|
-
if (d.state === "ready") console.log(` ${
|
|
29008
|
-
else if (d.state === "configured") console.log(` ${
|
|
29009
|
-
else if (d.state === "not-installed") console.log(` ${
|
|
29010
|
-
else if (!d.supported) console.log(` ${
|
|
29011
|
-
else console.log(` ${
|
|
29164
|
+
const label = `${d.name} ${chalk2.dim(d.pinnedVersion)} ${chalk2.dim(`[${d.license}]`)}`;
|
|
29165
|
+
if (d.state === "ready") console.log(` ${chalk2.green("\u2713")} ${label} \u2192 ${chalk2.dim(d.path)}`);
|
|
29166
|
+
else if (d.state === "configured") console.log(` ${chalk2.green("\u2713")} ${label} \u2192 ${chalk2.dim(d.path)} ${chalk2.dim("(operator-supplied)")}`);
|
|
29167
|
+
else if (d.state === "not-installed") console.log(` ${chalk2.dim("\xB7")} ${label} \u2014 not installed`);
|
|
29168
|
+
else if (!d.supported) console.log(` ${chalk2.dim("\xB7")} ${label} \u2014 no build for this platform`);
|
|
29169
|
+
else console.log(` ${chalk2.red("\u2717")} ${label} \u2014 ${d.state}, reinstall with \`chat-recall detectors install ${d.name}\``);
|
|
29012
29170
|
}
|
|
29013
29171
|
console.log();
|
|
29014
|
-
console.log(
|
|
29172
|
+
console.log(chalk2.dim("Builtin in-process detection runs for every user and needs none of this."));
|
|
29015
29173
|
});
|
|
29016
29174
|
detectors.command("install [name]").description("Download + verify a pinned detector into ~/.chat-recall/bin (default: both)").option("--force", "Re-download even if the pinned version is already installed").action(async (name, opts) => {
|
|
29017
29175
|
const { installDetector: installDetector2, DETECTOR_MANIFEST: DETECTOR_MANIFEST2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
@@ -29020,33 +29178,33 @@ detectors.command("install [name]").description("Download + verify a pinned dete
|
|
|
29020
29178
|
let failed = false;
|
|
29021
29179
|
for (const n of names) {
|
|
29022
29180
|
if (!valid.includes(n)) {
|
|
29023
|
-
console.error(
|
|
29181
|
+
console.error(chalk2.red(`unknown detector "${n}" \u2014 expected one of: ${valid.join(", ")}`));
|
|
29024
29182
|
failed = true;
|
|
29025
29183
|
continue;
|
|
29026
29184
|
}
|
|
29027
29185
|
try {
|
|
29028
29186
|
const r = await installDetector2(n, { force: opts.force });
|
|
29029
|
-
console.log(r.installed ?
|
|
29030
|
-
if (r.installed) console.log(
|
|
29187
|
+
console.log(r.installed ? chalk2.green(`\u2713 ${r.name} ${r.version} installed \u2192 ${r.path}`) : chalk2.dim(`\xB7 ${r.name} ${r.version} already installed and verified \u2192 ${r.path}`));
|
|
29188
|
+
if (r.installed) console.log(chalk2.dim(` sha256 ${r.binarySha256}`));
|
|
29031
29189
|
} catch (err) {
|
|
29032
29190
|
failed = true;
|
|
29033
|
-
console.error(
|
|
29191
|
+
console.error(chalk2.red(`${n} install failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
29034
29192
|
}
|
|
29035
29193
|
}
|
|
29036
29194
|
if (failed) process.exit(1);
|
|
29037
29195
|
console.log();
|
|
29038
|
-
console.log(
|
|
29039
|
-
console.log(
|
|
29196
|
+
console.log(chalk2.dim("Enable them with CHAT_RECALL_EXTERNAL_SCANNERS=1. They enrich the findings"));
|
|
29197
|
+
console.log(chalk2.dim("dashboard; they are not what redacts your data before it syncs."));
|
|
29040
29198
|
});
|
|
29041
29199
|
detectors.command("remove [name]").description("Remove detectors chat-recall installed (never touches your own binaries)").action(async (name) => {
|
|
29042
29200
|
const { removeDetector: removeDetector2, DETECTOR_MANIFEST: DETECTOR_MANIFEST2, managedDetectorPath: managedDetectorPath2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
29043
29201
|
for (const n of name ? [name] : Object.keys(DETECTOR_MANIFEST2)) {
|
|
29044
29202
|
if (!(n in DETECTOR_MANIFEST2)) {
|
|
29045
|
-
console.error(
|
|
29203
|
+
console.error(chalk2.red(`unknown detector "${n}"`));
|
|
29046
29204
|
continue;
|
|
29047
29205
|
}
|
|
29048
29206
|
const removed = removeDetector2(n);
|
|
29049
|
-
console.log(removed ?
|
|
29207
|
+
console.log(removed ? chalk2.green(`\u2713 removed ${n}`) : chalk2.dim(`\xB7 nothing to remove at ${managedDetectorPath2(n)}`));
|
|
29050
29208
|
}
|
|
29051
29209
|
});
|
|
29052
29210
|
var code = program.command("code").description("Index a codebase with codeindex and ship findings/hotspots/actions to your server");
|
|
@@ -29055,22 +29213,22 @@ code.command("index [path]").description("Run codeindex on a repo (default: cwd)
|
|
|
29055
29213
|
const target = requireTarget();
|
|
29056
29214
|
const { collectCode: collectCode2 } = await Promise.resolve().then(() => (init_collector(), collector_exports));
|
|
29057
29215
|
const workspace = resolve2(path || process.cwd());
|
|
29058
|
-
console.log(
|
|
29216
|
+
console.log(chalk2.bold(`code index ${workspace}`));
|
|
29059
29217
|
const result = await collectCode2({
|
|
29060
29218
|
workspace,
|
|
29061
29219
|
autoInstall: opts.install !== false,
|
|
29062
|
-
log: (m) => console.log(
|
|
29220
|
+
log: (m) => console.log(chalk2.dim(" \xB7 " + m))
|
|
29063
29221
|
});
|
|
29064
29222
|
const resp = await serverPost(
|
|
29065
29223
|
"/api/code/index",
|
|
29066
29224
|
result
|
|
29067
29225
|
);
|
|
29068
|
-
console.log(
|
|
29226
|
+
console.log(chalk2.green("\u2713 synced"), chalk2.dim(`\u2192 ${target.base}`));
|
|
29069
29227
|
console.log(` project: ${resp.projectId}`);
|
|
29070
29228
|
console.log(` health: ${result.project.health.score}/100`);
|
|
29071
29229
|
console.log(` findings: ${resp.findings} hotspots: ${resp.hotspots} actions: ${resp.actions}`);
|
|
29072
29230
|
} catch (err) {
|
|
29073
|
-
console.error(
|
|
29231
|
+
console.error(chalk2.red("code index failed:"), err instanceof Error ? err.message : err);
|
|
29074
29232
|
process.exit(1);
|
|
29075
29233
|
}
|
|
29076
29234
|
});
|
|
@@ -29078,17 +29236,17 @@ code.command("status").description("List code-indexed projects on your server").
|
|
|
29078
29236
|
try {
|
|
29079
29237
|
const { projects } = await serverGet("/api/code/projects");
|
|
29080
29238
|
if (!projects.length) {
|
|
29081
|
-
console.log(
|
|
29239
|
+
console.log(chalk2.dim("No code-indexed projects yet. Run `chat-recall code index` in a repo."));
|
|
29082
29240
|
return;
|
|
29083
29241
|
}
|
|
29084
|
-
console.log(
|
|
29242
|
+
console.log(chalk2.bold(`${projects.length} code project(s)`));
|
|
29085
29243
|
for (const p of projects) {
|
|
29086
29244
|
const when = new Date(p.lastIndexedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
29087
|
-
console.log(` ${
|
|
29088
|
-
console.log(
|
|
29245
|
+
console.log(` ${chalk2.bold(p.projectId)}${p.label ? chalk2.cyan(` [${p.label}]`) : ""}`);
|
|
29246
|
+
console.log(chalk2.dim(` health ${p.health.score}/100 \xB7 ${p.health.findings} findings \xB7 ${when}`));
|
|
29089
29247
|
}
|
|
29090
29248
|
} catch (err) {
|
|
29091
|
-
console.error(
|
|
29249
|
+
console.error(chalk2.red("code status failed:"), err instanceof Error ? err.message : err);
|
|
29092
29250
|
process.exit(1);
|
|
29093
29251
|
}
|
|
29094
29252
|
});
|
|
@@ -29097,12 +29255,12 @@ code.command("show <project>").description("Show a project: health, findings sum
|
|
|
29097
29255
|
const enc = encodeURIComponent(project);
|
|
29098
29256
|
const p = await serverGet(`/api/code/projects/${enc}`);
|
|
29099
29257
|
const { actions } = await serverGet(`/api/code/actions?project=${enc}&limit=12`);
|
|
29100
|
-
console.log(
|
|
29258
|
+
console.log(chalk2.bold(p.projectId), chalk2.dim(p.rootPath));
|
|
29101
29259
|
console.log(` health ${p.health.score}/100 \u2014 ${p.health.critical}C ${p.health.high}H ${p.health.medium}M ${p.health.low}L \xB7 ${p.health.hotspots} hotspots \xB7 ${Math.round((p.health.aiAuthoredPct || 0) * 100)}% AI-authored`);
|
|
29102
|
-
console.log(
|
|
29103
|
-
for (const a of actions) console.log(` ${
|
|
29260
|
+
console.log(chalk2.bold("\n Top actions:"));
|
|
29261
|
+
for (const a of actions) console.log(` ${chalk2.yellow("P" + a.pri)} [${a.category}] ${a.title}`);
|
|
29104
29262
|
} catch (err) {
|
|
29105
|
-
console.error(
|
|
29263
|
+
console.error(chalk2.red("code show failed:"), err instanceof Error ? err.message : err);
|
|
29106
29264
|
process.exit(1);
|
|
29107
29265
|
}
|
|
29108
29266
|
});
|
|
@@ -29111,18 +29269,18 @@ async function pushToolkitToServer() {
|
|
|
29111
29269
|
try {
|
|
29112
29270
|
const { syncIncremental: syncIncremental2, isSyncSkip: isSyncSkip2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
29113
29271
|
const res = await syncIncremental2();
|
|
29114
|
-
if (!isSyncSkip2(res) && res.items > 0) console.log(
|
|
29272
|
+
if (!isSyncSkip2(res) && res.items > 0) console.log(chalk2.dim(` synced ${res.items} toolkit item(s) to the server.`));
|
|
29115
29273
|
} catch (err) {
|
|
29116
|
-
console.error(
|
|
29274
|
+
console.error(chalk2.dim(` (could not sync to server: ${err instanceof Error ? err.message : err})`));
|
|
29117
29275
|
}
|
|
29118
29276
|
}
|
|
29119
29277
|
toolkit.command("sync").description("Fan every artifact out to every tool that is missing it (runs locally now)").option("--types <list>", "comma-separated subset: skill,mcp,command,agent").action(async (options) => {
|
|
29120
29278
|
const { executeSyncAll: executeSyncAll2 } = await Promise.resolve().then(() => (init_toolkit_sync(), toolkit_sync_exports));
|
|
29121
29279
|
const types = options.types ? options.types.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
29122
29280
|
const r = await executeSyncAll2(types);
|
|
29123
|
-
for (const c of r.copied) console.log(
|
|
29124
|
-
for (const f of r.failed) console.log(
|
|
29125
|
-
console.log(
|
|
29281
|
+
for (const c of r.copied) console.log(chalk2.green(` + ${c.type} ${c.name} \u2192 ${c.toTool}`) + chalk2.dim(` ${c.path || ""}`));
|
|
29282
|
+
for (const f of r.failed) console.log(chalk2.red(` \u2717 ${f.type} ${f.name} \u2192 ${f.toTool}: ${f.error}`));
|
|
29283
|
+
console.log(chalk2.bold(`Synced: ${r.copied.length} copied, ${r.skipped.length} already present, ${r.failed.length} failed.`));
|
|
29126
29284
|
if (r.copied.length > 0) await pushToolkitToServer();
|
|
29127
29285
|
});
|
|
29128
29286
|
toolkit.command("pull").description("Install artifacts this device is missing, from the server (cross-device setup)").option("--types <list>", "comma-separated subset: mcp").option("--dry-run", "report what would change, write nothing").action(async (options) => {
|
|
@@ -29131,7 +29289,7 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29131
29289
|
const { hostname: hostname3 } = await import("node:os");
|
|
29132
29290
|
const targets = loadAllCredentials2();
|
|
29133
29291
|
if (targets.length === 0) {
|
|
29134
|
-
console.error(
|
|
29292
|
+
console.error(chalk2.red("Not logged in."), "Run `chat-recall login <server-url>` first.");
|
|
29135
29293
|
process.exit(1);
|
|
29136
29294
|
}
|
|
29137
29295
|
const rows = [];
|
|
@@ -29145,7 +29303,7 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29145
29303
|
const body = await res.json();
|
|
29146
29304
|
for (const it of body.items || []) rows.push(it);
|
|
29147
29305
|
} catch (err) {
|
|
29148
|
-
console.log(
|
|
29306
|
+
console.log(chalk2.dim(` (${t.serverUrl} ${type}: ${err instanceof Error ? err.message : err})`));
|
|
29149
29307
|
}
|
|
29150
29308
|
}
|
|
29151
29309
|
}
|
|
@@ -29160,41 +29318,40 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29160
29318
|
const failed = report.outcomes.filter((o) => o.status === "failed");
|
|
29161
29319
|
const skipped = report.outcomes.filter((o) => o.status === "skipped");
|
|
29162
29320
|
for (const o of written) {
|
|
29163
|
-
const note = o.rewrittenCommand ?
|
|
29164
|
-
console.log(
|
|
29321
|
+
const note = o.rewrittenCommand ? chalk2.cyan(` [using this machine's ${o.rewrittenCommand}]`) : "";
|
|
29322
|
+
console.log(chalk2.green(` + ${o.type} ${o.name} \u2192 ${o.tool}`) + chalk2.dim(` ${o.path || "(dry run)"}`) + note);
|
|
29165
29323
|
}
|
|
29166
|
-
for (const o of failed) console.log(
|
|
29167
|
-
for (const o of skipped) console.log(
|
|
29168
|
-
console.log(
|
|
29324
|
+
for (const o of failed) console.log(chalk2.red(` \u2717 ${o.type} ${o.name} \u2192 ${o.tool}: ${o.reason}`));
|
|
29325
|
+
for (const o of skipped) console.log(chalk2.yellow(` ~ ${o.type} ${o.name} \u2192 ${o.tool}: ${o.reason}`));
|
|
29326
|
+
console.log(chalk2.bold(
|
|
29169
29327
|
`${options.dryRun ? "Would install" : "Installed"}: ${written.length} \xB7 already present: ${present.length} \xB7 failed: ${failed.length} \xB7 skipped: ${skipped.length}`
|
|
29170
29328
|
));
|
|
29171
|
-
const
|
|
29172
|
-
if (
|
|
29329
|
+
const needs = collectEnvNeeds(written);
|
|
29330
|
+
if (needs.length) {
|
|
29173
29331
|
console.log("");
|
|
29174
|
-
|
|
29175
|
-
for (const v of envVars) console.log(` ${v}`);
|
|
29332
|
+
for (const line of renderEnvNeeds(needs, written.length)) console.log(line);
|
|
29176
29333
|
}
|
|
29177
29334
|
if (report.unsupported.length) {
|
|
29178
29335
|
console.log("");
|
|
29179
|
-
console.log(
|
|
29336
|
+
console.log(chalk2.dim("Not installable from the server yet:"));
|
|
29180
29337
|
for (const u of report.unsupported) {
|
|
29181
|
-
console.log(
|
|
29338
|
+
console.log(chalk2.dim(` ${u.type} (${u.rows} on the server) \u2014 ${u.reason}`));
|
|
29182
29339
|
}
|
|
29183
|
-
console.log(
|
|
29340
|
+
console.log(chalk2.dim(" Those need artifact content upload; the server holds an inventory only."));
|
|
29184
29341
|
}
|
|
29185
29342
|
});
|
|
29186
29343
|
toolkit.command("drain").description("Process cross-tool sync intents queued from the web UI, now").action(async () => {
|
|
29187
29344
|
const { drainSyncIntents: drainSyncIntents2 } = await Promise.resolve().then(() => (init_intent_drain(), intent_drain_exports));
|
|
29188
29345
|
const r = await drainSyncIntents2({ verbose: true });
|
|
29189
|
-
console.log(
|
|
29346
|
+
console.log(chalk2.bold(`Processed ${r.processed} intent(s): ${r.done} done, ${r.errored} errored.`));
|
|
29190
29347
|
if (r.done > 0) await pushToolkitToServer();
|
|
29191
29348
|
});
|
|
29192
29349
|
toolkit.command("copy <type> <name> <fromTool> <toTool>").description("Copy one artifact (skill|mcp|command|agent) from one tool to another").action(async (type, name, fromTool, toTool) => {
|
|
29193
29350
|
const { executeCopy: executeCopy2 } = await Promise.resolve().then(() => (init_toolkit_sync(), toolkit_sync_exports));
|
|
29194
29351
|
const r = await executeCopy2(type, name, fromTool, toTool);
|
|
29195
|
-
if (r.ok) console.log(
|
|
29352
|
+
if (r.ok) console.log(chalk2.green(`Copied to ${r.targetPath}`));
|
|
29196
29353
|
else {
|
|
29197
|
-
console.error(
|
|
29354
|
+
console.error(chalk2.red(r.error || "failed"));
|
|
29198
29355
|
process.exit(1);
|
|
29199
29356
|
}
|
|
29200
29357
|
});
|
|
@@ -29202,31 +29359,31 @@ program.command("shares").description("List the projects you share with your tea
|
|
|
29202
29359
|
try {
|
|
29203
29360
|
const { shares } = await serverGet("/api/shares");
|
|
29204
29361
|
if (!shares.length) {
|
|
29205
|
-
console.log(
|
|
29362
|
+
console.log(chalk2.gray("You are not sharing any projects. Share one with"), chalk2.bold("chat-recall share <project_id>"));
|
|
29206
29363
|
return;
|
|
29207
29364
|
}
|
|
29208
|
-
console.log(
|
|
29209
|
-
for (const s of shares) console.log(` ${
|
|
29365
|
+
console.log(chalk2.bold(`Shared projects (${shares.length}):`));
|
|
29366
|
+
for (const s of shares) console.log(` ${chalk2.cyan(s.projectId)} ${chalk2.gray(s.scope)}`);
|
|
29210
29367
|
} catch (e) {
|
|
29211
|
-
console.error(
|
|
29368
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29212
29369
|
process.exit(1);
|
|
29213
29370
|
}
|
|
29214
29371
|
});
|
|
29215
29372
|
program.command("share <project_id>").description("Share a project with your team (teammates can then see your work on it)").action(async (projectId) => {
|
|
29216
29373
|
try {
|
|
29217
29374
|
await serverPost("/api/shares", { project_id: projectId });
|
|
29218
|
-
console.log(
|
|
29375
|
+
console.log(chalk2.green("\u2713 shared"), chalk2.cyan(projectId), chalk2.gray("with your team"));
|
|
29219
29376
|
} catch (e) {
|
|
29220
|
-
console.error(
|
|
29377
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29221
29378
|
process.exit(1);
|
|
29222
29379
|
}
|
|
29223
29380
|
});
|
|
29224
29381
|
program.command("unshare <project_id>").description("Stop sharing a project with your team").action(async (projectId) => {
|
|
29225
29382
|
try {
|
|
29226
29383
|
const r = await serverDelete("/api/shares", { project_id: projectId });
|
|
29227
|
-
console.log(r.removed ?
|
|
29384
|
+
console.log(r.removed ? chalk2.green("\u2713 unshared") : chalk2.yellow("was not shared"), chalk2.cyan(projectId));
|
|
29228
29385
|
} catch (e) {
|
|
29229
|
-
console.error(
|
|
29386
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29230
29387
|
process.exit(1);
|
|
29231
29388
|
}
|
|
29232
29389
|
});
|
|
@@ -29235,12 +29392,12 @@ tasks.command("list").description("List team tasks assigned to you").action(asyn
|
|
|
29235
29392
|
try {
|
|
29236
29393
|
const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
|
|
29237
29394
|
if (!tasks2.length) {
|
|
29238
|
-
console.log(
|
|
29395
|
+
console.log(chalk2.gray("No tasks assigned to you."));
|
|
29239
29396
|
return;
|
|
29240
29397
|
}
|
|
29241
|
-
for (const t of tasks2) console.log(` [${t.status}] ${t.title} ${
|
|
29398
|
+
for (const t of tasks2) console.log(` [${t.status}] ${t.title} ${chalk2.gray(t.id)}${t.projectId ? chalk2.gray(" \xB7 " + t.projectId) : ""}`);
|
|
29242
29399
|
} catch (e) {
|
|
29243
|
-
console.error(
|
|
29400
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29244
29401
|
process.exit(1);
|
|
29245
29402
|
}
|
|
29246
29403
|
});
|
|
@@ -29253,7 +29410,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
|
|
|
29253
29410
|
const { join: join61 } = await import("node:path");
|
|
29254
29411
|
const file = join61(cwd, "TEAM_TASKS.md");
|
|
29255
29412
|
if (!opts.force && existsSync57(file) && parseTeamTasksFile2(readFileSync50(file, "utf8")).length > 0) {
|
|
29256
|
-
console.error(
|
|
29413
|
+
console.error(chalk2.red("TEAM_TASKS.md has unpushed edits."), "Run", chalk2.bold("chat-recall tasks push"), "first, or", chalk2.bold("tasks pull --force"), "to discard.");
|
|
29257
29414
|
process.exit(1);
|
|
29258
29415
|
}
|
|
29259
29416
|
const resolved = resolveProjectId2(cwd);
|
|
@@ -29261,9 +29418,9 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
|
|
|
29261
29418
|
const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
|
|
29262
29419
|
const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
|
|
29263
29420
|
writeFileSync22(file, renderTeamTasksMd2(mine));
|
|
29264
|
-
console.log(
|
|
29421
|
+
console.log(chalk2.green("\u2713 wrote"), "TEAM_TASKS.md", chalk2.gray(`(${mine.length} task(s))`), "\u2014 edit, then", chalk2.bold("chat-recall tasks push"));
|
|
29265
29422
|
} catch (e) {
|
|
29266
|
-
console.error(
|
|
29423
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29267
29424
|
process.exit(1);
|
|
29268
29425
|
}
|
|
29269
29426
|
});
|
|
@@ -29275,7 +29432,7 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
|
|
|
29275
29432
|
const { join: join61 } = await import("node:path");
|
|
29276
29433
|
const file = join61(cwd, "TEAM_TASKS.md");
|
|
29277
29434
|
if (!existsSync57(file)) {
|
|
29278
|
-
console.error(
|
|
29435
|
+
console.error(chalk2.red("no TEAM_TASKS.md here \u2014"), "run", chalk2.bold("chat-recall tasks pull"), "first");
|
|
29279
29436
|
process.exit(1);
|
|
29280
29437
|
}
|
|
29281
29438
|
const edits = parseTeamTasksFile2(readFileSync50(file, "utf8"));
|
|
@@ -29284,9 +29441,9 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
|
|
|
29284
29441
|
await serverPatch(`/api/tasks/${encodeURIComponent(e.id)}`, { status: e.intent });
|
|
29285
29442
|
n++;
|
|
29286
29443
|
}
|
|
29287
|
-
console.log(n ?
|
|
29444
|
+
console.log(n ? chalk2.green(`\u2713 pushed ${n} status change(s)`) : chalk2.gray("no changes to push"));
|
|
29288
29445
|
} catch (e) {
|
|
29289
|
-
console.error(
|
|
29446
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29290
29447
|
process.exit(1);
|
|
29291
29448
|
}
|
|
29292
29449
|
});
|
|
@@ -29297,12 +29454,12 @@ async function runTeam(label, fn) {
|
|
|
29297
29454
|
await fn();
|
|
29298
29455
|
} catch (err) {
|
|
29299
29456
|
if (err instanceof TeamConfigError2) {
|
|
29300
|
-
console.error(
|
|
29457
|
+
console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29301
29458
|
} else if (err instanceof TeamAuthError2) {
|
|
29302
|
-
console.error(
|
|
29303
|
-
console.error(
|
|
29459
|
+
console.error(chalk2.red(`${label}: auth failed \u2014 ${err.message}`));
|
|
29460
|
+
console.error(chalk2.dim(" Check the env var named in settings.team.tokenRef holds a valid Keycloak access token."));
|
|
29304
29461
|
} else if (err instanceof TeamHttpError2) {
|
|
29305
|
-
console.error(
|
|
29462
|
+
console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29306
29463
|
} else {
|
|
29307
29464
|
throw err;
|
|
29308
29465
|
}
|
|
@@ -29312,76 +29469,76 @@ async function runTeam(label, fn) {
|
|
|
29312
29469
|
team.command("whoami").description("Show the user + team memberships the server sees for your token").action(async () => runTeam("team whoami", async () => {
|
|
29313
29470
|
const { teamMe: teamMe2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29314
29471
|
const me = await teamMe2();
|
|
29315
|
-
console.log(
|
|
29472
|
+
console.log(chalk2.bold(me.user.email) + chalk2.dim(` (${me.user.id})`));
|
|
29316
29473
|
if (me.memberships.length === 0) {
|
|
29317
|
-
console.log(
|
|
29474
|
+
console.log(chalk2.dim(" no team memberships yet \u2014 run `chat-recall team create <name>` or `team join <token>`"));
|
|
29318
29475
|
return;
|
|
29319
29476
|
}
|
|
29320
29477
|
for (const m of me.memberships) {
|
|
29321
|
-
const role = m.role === "owner" ?
|
|
29322
|
-
console.log(` ${
|
|
29478
|
+
const role = m.role === "owner" ? chalk2.yellow(m.role) : chalk2.dim(m.role);
|
|
29479
|
+
console.log(` ${chalk2.cyan(m.teamName)} ${role} ${chalk2.dim(m.plan)} ${chalk2.dim(m.teamId)}`);
|
|
29323
29480
|
}
|
|
29324
29481
|
}));
|
|
29325
29482
|
team.command("create <name>").description("Create a new team. You become the owner. Stores teamId in settings.").action(async (name) => runTeam("team create", async () => {
|
|
29326
29483
|
const { teamCreate: teamCreate2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29327
29484
|
const t = await teamCreate2(name);
|
|
29328
|
-
console.log(
|
|
29329
|
-
console.log(
|
|
29330
|
-
console.log(
|
|
29485
|
+
console.log(chalk2.green(`\u2713 Team created: ${chalk2.bold(t.name)}`));
|
|
29486
|
+
console.log(chalk2.dim(` id: ${t.id}`));
|
|
29487
|
+
console.log(chalk2.dim(` Now generate an invite: chat-recall team invite`));
|
|
29331
29488
|
}));
|
|
29332
29489
|
team.command("invite").description("Generate a single-use invite token for your team (owner-only)").option("--email <hint>", "Display hint (e.g. teammate@company.com); not validated").action(async (opts) => runTeam("team invite", async () => {
|
|
29333
29490
|
const { teamInvite: teamInvite2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29334
29491
|
const r = await teamInvite2(opts.email);
|
|
29335
|
-
console.log(
|
|
29336
|
-
console.log(" " +
|
|
29337
|
-
console.log(
|
|
29492
|
+
console.log(chalk2.bold("Invite token (copy now \u2014 shown once):"));
|
|
29493
|
+
console.log(" " + chalk2.cyan(r.inviteToken));
|
|
29494
|
+
console.log(chalk2.dim(` expires: ${r.expiresAt}`));
|
|
29338
29495
|
console.log();
|
|
29339
|
-
console.log(
|
|
29496
|
+
console.log(chalk2.dim("Recipient runs: chat-recall team join <token>"));
|
|
29340
29497
|
}));
|
|
29341
29498
|
team.command("join <invite-token>").description("Accept a team invite. Stores team id locally.").action(async (inviteToken) => runTeam("team join", async () => {
|
|
29342
29499
|
const { teamJoin: teamJoin2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29343
29500
|
const t = await teamJoin2(inviteToken);
|
|
29344
|
-
console.log(
|
|
29345
|
-
console.log(
|
|
29346
|
-
console.log(
|
|
29501
|
+
console.log(chalk2.green(`\u2713 Joined team: ${chalk2.bold(t.name)}`));
|
|
29502
|
+
console.log(chalk2.dim(` id: ${t.id}`));
|
|
29503
|
+
console.log(chalk2.dim(" Run `chat-recall team pull` to fetch the team library."));
|
|
29347
29504
|
}));
|
|
29348
29505
|
team.command("pull").description("Pull artifacts changed since the last pull and write them into your local ~/.claude/, ~/.gemini/, ~/.codex/ dirs").option("--dry-run", "Fetch but don't write to disk (preview only)").action(async (opts) => runTeam("team pull", async () => {
|
|
29349
29506
|
const { teamPull: teamPull2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29350
29507
|
const r = await teamPull2();
|
|
29351
29508
|
if (r.pulled.length === 0 && r.removed.length === 0) {
|
|
29352
|
-
console.log(
|
|
29509
|
+
console.log(chalk2.dim("Up to date."));
|
|
29353
29510
|
return;
|
|
29354
29511
|
}
|
|
29355
29512
|
for (const a of r.pulled) {
|
|
29356
|
-
console.log(` ${
|
|
29513
|
+
console.log(` ${chalk2.green("+")} ${chalk2.cyan(a.type + "/" + a.name)} v${a.version} ${chalk2.dim("(" + a.tool + ")")}`);
|
|
29357
29514
|
}
|
|
29358
29515
|
for (const id of r.removed) {
|
|
29359
|
-
console.log(` ${
|
|
29516
|
+
console.log(` ${chalk2.red("-")} ${chalk2.dim(id + " (revoked)")}`);
|
|
29360
29517
|
}
|
|
29361
29518
|
if (opts.dryRun) {
|
|
29362
|
-
console.log(
|
|
29519
|
+
console.log(chalk2.dim(`Watermark: ${r.serverNow} (dry-run \u2014 nothing written)`));
|
|
29363
29520
|
return;
|
|
29364
29521
|
}
|
|
29365
29522
|
const { mergePullResult: mergePullResult2 } = await Promise.resolve().then(() => (init_team_merge(), team_merge_exports));
|
|
29366
29523
|
const m = await mergePullResult2({ pulled: r.pulled, removed: r.removed });
|
|
29367
29524
|
console.log();
|
|
29368
|
-
if (m.written.length) console.log(
|
|
29369
|
-
if (m.skipped.length) console.log(
|
|
29370
|
-
if (m.removed.length) console.log(
|
|
29525
|
+
if (m.written.length) console.log(chalk2.green(`\u2713 Wrote ${m.written.length} file${m.written.length === 1 ? "" : "s"}`));
|
|
29526
|
+
if (m.skipped.length) console.log(chalk2.dim(` Skipped ${m.skipped.length} (unchanged or no target tool)`));
|
|
29527
|
+
if (m.removed.length) console.log(chalk2.yellow(` Removed ${m.removed.length} revoked file${m.removed.length === 1 ? "" : "s"}`));
|
|
29371
29528
|
for (const f of m.failures) {
|
|
29372
|
-
console.log(
|
|
29529
|
+
console.log(chalk2.red(` ! ${f.path}: ${f.error}`));
|
|
29373
29530
|
}
|
|
29374
|
-
console.log(
|
|
29531
|
+
console.log(chalk2.dim(`Watermark: ${r.serverNow}`));
|
|
29375
29532
|
}));
|
|
29376
29533
|
team.command("publish <type> <name> <file>").description("Publish a local file as an artifact (skill|command|agent|mcp|plan|plugin|instructions|hook)").option("--tool <tool>", "Target tool: claude|gemini|opencode|codex|agy|cursor|cross_tool", "cross_tool").option("--pinned-to <glob>", "Limit which projects pull this artifact").action(async (type, name, file, opts) => runTeam("team publish", async () => {
|
|
29377
29534
|
const validTypes = ["skill", "command", "agent", "mcp", "plan", "plugin", "instructions", "hook"];
|
|
29378
29535
|
if (!validTypes.includes(type)) {
|
|
29379
|
-
console.error(
|
|
29536
|
+
console.error(chalk2.red(`Invalid type: ${type}. Must be one of: ${validTypes.join(", ")}`));
|
|
29380
29537
|
process.exit(1);
|
|
29381
29538
|
}
|
|
29382
29539
|
const validTools = ["claude", "gemini", "opencode", "codex", "agy", "cursor", "cross_tool"];
|
|
29383
29540
|
if (!validTools.includes(opts.tool)) {
|
|
29384
|
-
console.error(
|
|
29541
|
+
console.error(chalk2.red(`Invalid tool: ${opts.tool}. Must be one of: ${validTools.join(", ")}`));
|
|
29385
29542
|
process.exit(1);
|
|
29386
29543
|
}
|
|
29387
29544
|
const { readFileSync: readFileSync50 } = await import("fs");
|
|
@@ -29394,32 +29551,32 @@ team.command("publish <type> <name> <file>").description("Publish a local file a
|
|
|
29394
29551
|
body,
|
|
29395
29552
|
pinnedTo: opts.pinnedTo
|
|
29396
29553
|
});
|
|
29397
|
-
console.log(
|
|
29398
|
-
console.log(
|
|
29399
|
-
console.log(
|
|
29554
|
+
console.log(chalk2.green(`\u2713 Published ${chalk2.bold(type + "/" + name)} v${a.version}`));
|
|
29555
|
+
console.log(chalk2.dim(` id: ${a.id}`));
|
|
29556
|
+
console.log(chalk2.dim(` sha256: ${a.sha256}`));
|
|
29400
29557
|
}));
|
|
29401
29558
|
team.command("list").description("List artifacts in the team library (metadata only, no bodies)").action(async () => runTeam("team list", async () => {
|
|
29402
29559
|
const { teamList: teamList2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29403
29560
|
const items = await teamList2();
|
|
29404
29561
|
if (items.length === 0) {
|
|
29405
|
-
console.log(
|
|
29562
|
+
console.log(chalk2.dim("Library is empty. Try `chat-recall team publish <type> <name> <file>`."));
|
|
29406
29563
|
return;
|
|
29407
29564
|
}
|
|
29408
29565
|
for (const a of items) {
|
|
29409
|
-
const pinned = a.pinnedTo ?
|
|
29410
|
-
console.log(` ${
|
|
29566
|
+
const pinned = a.pinnedTo ? chalk2.dim(` pinned=${a.pinnedTo}`) : "";
|
|
29567
|
+
console.log(` ${chalk2.cyan(a.type + "/" + a.name)} v${a.version} ${chalk2.dim("(" + a.tool + ", " + a.bytes + " bytes)")}${pinned}`);
|
|
29411
29568
|
}
|
|
29412
29569
|
}));
|
|
29413
29570
|
team.command("revoke <artifact-id>").alias("unpublish").description("Revoke an artifact from the team library (owner-only)").action(async (artifactId) => runTeam("team revoke", async () => {
|
|
29414
29571
|
const { teamRevoke: teamRevoke2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29415
29572
|
const r = await teamRevoke2(artifactId);
|
|
29416
|
-
console.log(
|
|
29573
|
+
console.log(chalk2.green(`\u2713 Revoked ${chalk2.bold(r.type + "/" + r.name)} (${r.id})`));
|
|
29417
29574
|
}));
|
|
29418
29575
|
team.command("leave").description("Clear local team config (does not delete pulled artifacts; does not remove your server-side membership)").action(async () => runTeam("team leave", async () => {
|
|
29419
29576
|
const { teamLeave: teamLeave2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29420
29577
|
await teamLeave2();
|
|
29421
|
-
console.log(
|
|
29422
|
-
console.log(
|
|
29578
|
+
console.log(chalk2.green("\u2713 Local team config cleared."));
|
|
29579
|
+
console.log(chalk2.dim(" Server-side membership unchanged. Owner must remove you to fully leave."));
|
|
29423
29580
|
}));
|
|
29424
29581
|
var vault = program.command("vault").description("Encrypted multi-device chat backup (E2EE \u2014 server cannot decrypt)");
|
|
29425
29582
|
async function runVault(label, fn) {
|
|
@@ -29427,9 +29584,9 @@ async function runVault(label, fn) {
|
|
|
29427
29584
|
try {
|
|
29428
29585
|
await fn();
|
|
29429
29586
|
} catch (err) {
|
|
29430
|
-
if (err instanceof VaultConfigError2) console.error(
|
|
29431
|
-
else if (err instanceof VaultAuthError2) console.error(
|
|
29432
|
-
else if (err instanceof VaultHttpError2) console.error(
|
|
29587
|
+
if (err instanceof VaultConfigError2) console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29588
|
+
else if (err instanceof VaultAuthError2) console.error(chalk2.red(`${label}: auth failed \u2014 ${err.message}`));
|
|
29589
|
+
else if (err instanceof VaultHttpError2) console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29433
29590
|
else throw err;
|
|
29434
29591
|
process.exit(1);
|
|
29435
29592
|
}
|
|
@@ -29439,7 +29596,7 @@ async function resolvePassphrase(opts) {
|
|
|
29439
29596
|
if (opts.envVar) {
|
|
29440
29597
|
const v = process.env[opts.envVar];
|
|
29441
29598
|
if (v) return v;
|
|
29442
|
-
console.error(
|
|
29599
|
+
console.error(chalk2.red(`Env var ${opts.envVar} is not set.`));
|
|
29443
29600
|
process.exit(1);
|
|
29444
29601
|
}
|
|
29445
29602
|
if (!opts.noKeyring) {
|
|
@@ -29476,49 +29633,49 @@ vault.command("enable").description("Set up the Vault on this device. Generates
|
|
|
29476
29633
|
r = await vaultEnableWithRemote2(passphrase, { baseUrl: cred.serverUrl, token: cred.token });
|
|
29477
29634
|
} catch (err) {
|
|
29478
29635
|
if (err instanceof VaultPassphraseMismatchError2) {
|
|
29479
|
-
console.error(
|
|
29636
|
+
console.error(chalk2.red(`vault enable: ${err.message}`));
|
|
29480
29637
|
process.exit(1);
|
|
29481
29638
|
}
|
|
29482
29639
|
throw err;
|
|
29483
29640
|
}
|
|
29484
29641
|
}
|
|
29485
|
-
console.log(
|
|
29486
|
-
console.log(
|
|
29487
|
-
console.log(
|
|
29642
|
+
console.log(chalk2.green("\u2713 Vault enabled on this device."));
|
|
29643
|
+
console.log(chalk2.dim(` keyId: ${r.keyId}`));
|
|
29644
|
+
console.log(chalk2.dim(` salt: ${r.saltHex}`) + (r.saltSource === "server" ? chalk2.dim(" (from your workspace)") : ""));
|
|
29488
29645
|
if (opts.keyring === false) {
|
|
29489
|
-
console.log(
|
|
29646
|
+
console.log(chalk2.yellow(" keyring: skipped (--no-keyring) \u2014 the background daemon will not be able to decrypt."));
|
|
29490
29647
|
} else {
|
|
29491
29648
|
const { keyringSet: keyringSet2, probeKeyring: probeKeyring2 } = await Promise.resolve().then(() => (init_keyring(), keyring_exports));
|
|
29492
29649
|
try {
|
|
29493
29650
|
keyringSet2(passphrase);
|
|
29494
|
-
console.log(
|
|
29651
|
+
console.log(chalk2.dim(` keyring: stored (${probeKeyring2().backend}) \u2014 the daemon can decrypt unattended.`));
|
|
29495
29652
|
} catch (err) {
|
|
29496
|
-
console.log(
|
|
29497
|
-
console.log(
|
|
29653
|
+
console.log(chalk2.yellow(` keyring: unavailable \u2014 ${err instanceof Error ? err.message : err}`));
|
|
29654
|
+
console.log(chalk2.yellow(" The vault still works for foreground commands; cross-device artifacts will not apply on this machine."));
|
|
29498
29655
|
}
|
|
29499
29656
|
}
|
|
29500
29657
|
console.log();
|
|
29501
29658
|
if (r.saltSource === "server") {
|
|
29502
|
-
console.log(
|
|
29659
|
+
console.log(chalk2.dim("This device joined your existing vault \u2014 same passphrase, same key."));
|
|
29503
29660
|
} else {
|
|
29504
|
-
console.log(
|
|
29661
|
+
console.log(chalk2.yellow("To set up another device, run there:"));
|
|
29505
29662
|
console.log(" chat-recall vault enable");
|
|
29506
|
-
console.log(
|
|
29663
|
+
console.log(chalk2.dim(" (it picks up this workspace's salt automatically \u2014 only the passphrase is needed)"));
|
|
29507
29664
|
}
|
|
29508
|
-
console.log(
|
|
29665
|
+
console.log(chalk2.yellow("Lose the passphrase = lose encrypted history. There is no recovery."));
|
|
29509
29666
|
}));
|
|
29510
29667
|
vault.command("sync").description("Walk local chat sources, encrypt new/changed sessions, upload ciphertext to the server").option("--passphrase <s>", "Passphrase (insecure \u2014 prefer interactive or --passphrase-env)").option("--passphrase-env <name>", "Read passphrase from this env var").action(async (opts) => runVault("vault sync", async () => {
|
|
29511
29668
|
const passphrase = await resolvePassphrase({ passphrase: opts.passphrase, envVar: opts.passphraseEnv });
|
|
29512
29669
|
const { vaultSync: vaultSync2 } = await Promise.resolve().then(() => (init_vault_client(), vault_client_exports));
|
|
29513
29670
|
const r = await vaultSync2(passphrase);
|
|
29514
29671
|
if (r.uploaded.length === 0 && r.skipped.length === 0 && r.failures.length === 0) {
|
|
29515
|
-
console.log(
|
|
29672
|
+
console.log(chalk2.dim("No chat sources found."));
|
|
29516
29673
|
return;
|
|
29517
29674
|
}
|
|
29518
|
-
if (r.uploaded.length) console.log(
|
|
29519
|
-
if (r.skipped.length) console.log(
|
|
29675
|
+
if (r.uploaded.length) console.log(chalk2.green(`\u2713 Uploaded ${r.uploaded.length} session${r.uploaded.length === 1 ? "" : "s"}`));
|
|
29676
|
+
if (r.skipped.length) console.log(chalk2.dim(` Skipped ${r.skipped.length} (unchanged or denylisted)`));
|
|
29520
29677
|
for (const f of r.failures) {
|
|
29521
|
-
console.log(
|
|
29678
|
+
console.log(chalk2.red(` ! ${f.tool}/${f.sessionId}: ${f.error}`));
|
|
29522
29679
|
}
|
|
29523
29680
|
}));
|
|
29524
29681
|
vault.command("restore").description("Pull encrypted blobs from the server, decrypt locally, write them where chat-recall can index them").option("--passphrase <s>", "Passphrase (insecure \u2014 prefer interactive or --passphrase-env)").option("--passphrase-env <name>", "Read passphrase from this env var").option("--since <ms>", "Only restore blobs uploaded after this ms-epoch (default 0 = all)").action(async (opts) => runVault("vault restore", async () => {
|
|
@@ -29526,13 +29683,13 @@ vault.command("restore").description("Pull encrypted blobs from the server, decr
|
|
|
29526
29683
|
const { vaultRestore: vaultRestore2 } = await Promise.resolve().then(() => (init_vault_client(), vault_client_exports));
|
|
29527
29684
|
const r = await vaultRestore2(passphrase, { since: opts.since ? Number(opts.since) : void 0 });
|
|
29528
29685
|
if (r.restored.length === 0 && r.skipped.length === 0 && r.failures.length === 0) {
|
|
29529
|
-
console.log(
|
|
29686
|
+
console.log(chalk2.dim("No blobs to restore."));
|
|
29530
29687
|
return;
|
|
29531
29688
|
}
|
|
29532
|
-
if (r.restored.length) console.log(
|
|
29533
|
-
if (r.skipped.length) console.log(
|
|
29689
|
+
if (r.restored.length) console.log(chalk2.green(`\u2713 Restored ${r.restored.length} session${r.restored.length === 1 ? "" : "s"}`));
|
|
29690
|
+
if (r.skipped.length) console.log(chalk2.dim(` Skipped ${r.skipped.length} (unchanged or different key)`));
|
|
29534
29691
|
for (const f of r.failures) {
|
|
29535
|
-
console.log(
|
|
29692
|
+
console.log(chalk2.red(` ! ${f.tool}/${f.sessionId}: ${f.error}`));
|
|
29536
29693
|
}
|
|
29537
29694
|
}));
|
|
29538
29695
|
vault.command("status").description("Show whether the Vault is enabled, salt fingerprint, last sync time, and whether this machine can decrypt unattended").action(async () => runVault("vault status", async () => {
|
|
@@ -29542,8 +29699,8 @@ vault.command("status").description("Show whether the Vault is enabled, salt fin
|
|
|
29542
29699
|
const probe = probeKeyring2();
|
|
29543
29700
|
const reportKeyring = () => {
|
|
29544
29701
|
if (!probe.available) {
|
|
29545
|
-
console.log(
|
|
29546
|
-
if (probe.hint) console.log(
|
|
29702
|
+
console.log(chalk2.yellow(` keyring: unavailable (${probe.backend}) \u2014 cross-device artifacts will not apply on this machine`));
|
|
29703
|
+
if (probe.hint) console.log(chalk2.dim(` ${probe.hint}`));
|
|
29547
29704
|
return;
|
|
29548
29705
|
}
|
|
29549
29706
|
let stored = false;
|
|
@@ -29552,27 +29709,27 @@ vault.command("status").description("Show whether the Vault is enabled, salt fin
|
|
|
29552
29709
|
} catch {
|
|
29553
29710
|
stored = false;
|
|
29554
29711
|
}
|
|
29555
|
-
console.log(stored ?
|
|
29712
|
+
console.log(stored ? chalk2.dim(` keyring: ${probe.backend}, passphrase stored \u2014 the daemon can decrypt unattended`) : chalk2.yellow(` keyring: ${probe.backend}, no passphrase stored \u2014 run \`chat-recall vault enable\` to let the daemon decrypt`));
|
|
29556
29713
|
};
|
|
29557
29714
|
if (!s.enabled) {
|
|
29558
|
-
console.log(
|
|
29715
|
+
console.log(chalk2.dim("Vault: disabled. Run `chat-recall vault enable`."));
|
|
29559
29716
|
reportKeyring();
|
|
29560
29717
|
return;
|
|
29561
29718
|
}
|
|
29562
|
-
console.log(
|
|
29563
|
-
console.log(
|
|
29564
|
-
console.log(
|
|
29565
|
-
console.log(
|
|
29566
|
-
console.log(
|
|
29567
|
-
console.log(
|
|
29719
|
+
console.log(chalk2.green("Vault: enabled"));
|
|
29720
|
+
console.log(chalk2.dim(` keyId: ${s.keyId ?? "(unknown \u2014 re-enable)"}`));
|
|
29721
|
+
console.log(chalk2.dim(` salt: ${s.saltHex ? s.saltHex.slice(0, 16) + "\u2026" : "(missing)"}`));
|
|
29722
|
+
console.log(chalk2.dim(` lastSyncAt: ${s.lastSyncAt ? new Date(s.lastSyncAt).toISOString() : "never"}`));
|
|
29723
|
+
console.log(chalk2.dim(` syncTools: ${s.syncTools.join(", ")}`));
|
|
29724
|
+
console.log(chalk2.dim(` excludeProj: ${s.excludeProjects.length ? s.excludeProjects.join(", ") : "(none)"}`));
|
|
29568
29725
|
reportKeyring();
|
|
29569
29726
|
}));
|
|
29570
29727
|
vault.command("forget").description("Remove the stored passphrase from this machine's OS keyring (the vault stays enabled; the daemon stops being able to decrypt)").action(async () => runVault("vault forget", async () => {
|
|
29571
29728
|
const { keyringDelete: keyringDelete2 } = await Promise.resolve().then(() => (init_keyring(), keyring_exports));
|
|
29572
|
-
console.log(keyringDelete2() ?
|
|
29729
|
+
console.log(keyringDelete2() ? chalk2.green("\u2713 Passphrase removed from the OS keyring.") : chalk2.dim("Nothing stored in the OS keyring."));
|
|
29573
29730
|
}));
|
|
29574
29731
|
program.command("install-hooks").description("Install Claude Code hooks (Stop + PreCompact + UserPromptSubmit + SessionEnd + SessionStart) into every Claude profile").option("--uninstall", "Remove hooks instead of installing them").option("--no-resume-hint", "Don't install the UserPromptSubmit resume-hint hook").option("--no-escalate", "Don't install the SessionEnd learnings-escalation hook").option("--no-wakeup", "Don't install the SessionStart wake-up hook").action(async (opts) => {
|
|
29575
|
-
const { mkdirSync: mkdirSync20, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync57, readFileSync: readFileSync50, writeFileSync: writeFileSync22, statSync:
|
|
29732
|
+
const { mkdirSync: mkdirSync20, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync57, readFileSync: readFileSync50, writeFileSync: writeFileSync22, statSync: statSync47 } = await import("fs");
|
|
29576
29733
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
29577
29734
|
const here = fileURLToPath6(new URL(".", import.meta.url));
|
|
29578
29735
|
const findHook = (name) => {
|
|
@@ -29587,7 +29744,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29587
29744
|
const sourceEscalateHook = findHook("chat_recall_escalate_hook.sh");
|
|
29588
29745
|
const sourceWakeupHook = findHook("chat_recall_wakeup_hook.sh");
|
|
29589
29746
|
if (!sourceSaveHook) {
|
|
29590
|
-
console.error(
|
|
29747
|
+
console.error(chalk2.red("Could not locate chat_recall_save_hook.sh in the package."));
|
|
29591
29748
|
process.exit(1);
|
|
29592
29749
|
}
|
|
29593
29750
|
const hooksDir = getHooksDir();
|
|
@@ -29614,8 +29771,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29614
29771
|
if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
|
|
29615
29772
|
return c;
|
|
29616
29773
|
} catch (err) {
|
|
29617
|
-
console.error(
|
|
29618
|
-
console.error(
|
|
29774
|
+
console.error(chalk2.red(`Could not parse ${file}: ${err}`));
|
|
29775
|
+
console.error(chalk2.dim("Fix the file or move it aside before re-running."));
|
|
29619
29776
|
process.exit(1);
|
|
29620
29777
|
}
|
|
29621
29778
|
};
|
|
@@ -29639,23 +29796,23 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29639
29796
|
if (removed === 0 && !existsSync57(hooksJson)) continue;
|
|
29640
29797
|
writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
|
|
29641
29798
|
total += removed;
|
|
29642
|
-
console.log(
|
|
29799
|
+
console.log(chalk2.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
|
|
29643
29800
|
}
|
|
29644
|
-
if (total === 0) console.log(
|
|
29645
|
-
console.log(
|
|
29801
|
+
if (total === 0) console.log(chalk2.dim("No chat-recall hook entries were registered."));
|
|
29802
|
+
console.log(chalk2.dim(` (Hook scripts left in ${hooksDir} \u2014 delete manually if you want.)`));
|
|
29646
29803
|
return;
|
|
29647
29804
|
}
|
|
29648
29805
|
if (process.platform === "win32") {
|
|
29649
|
-
console.log(
|
|
29650
|
-
console.log(
|
|
29651
|
-
console.log(
|
|
29652
|
-
console.log(
|
|
29653
|
-
console.log(
|
|
29654
|
-
console.log(
|
|
29655
|
-
console.log(
|
|
29656
|
-
console.log(
|
|
29657
|
-
console.log(
|
|
29658
|
-
console.log(
|
|
29806
|
+
console.log(chalk2.yellow("Hooks are not installed on Windows."));
|
|
29807
|
+
console.log(chalk2.dim(" They are POSIX shell scripts, and Claude Code runs hook commands through"));
|
|
29808
|
+
console.log(chalk2.dim(" cmd.exe, which cannot execute them. Registering them would put an error on"));
|
|
29809
|
+
console.log(chalk2.dim(" every turn instead of saving anything."));
|
|
29810
|
+
console.log(chalk2.dim(""));
|
|
29811
|
+
console.log(chalk2.dim(" Everything else works: indexing, sync, search and the MCP server are"));
|
|
29812
|
+
console.log(chalk2.dim(" unaffected. Hooks only add automatic fact-saving and the wake-up bundle,"));
|
|
29813
|
+
console.log(chalk2.dim(" and you can get the same context by calling recall_wake_up from the agent."));
|
|
29814
|
+
console.log(chalk2.dim(""));
|
|
29815
|
+
console.log(chalk2.dim(" Under WSL, run this from inside the WSL shell and it installs normally."));
|
|
29659
29816
|
return;
|
|
29660
29817
|
}
|
|
29661
29818
|
mkdirSync20(hooksDir, { recursive: true });
|
|
@@ -29705,35 +29862,35 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29705
29862
|
mkdirSync20(dirname27(hooksJson), { recursive: true });
|
|
29706
29863
|
writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
|
|
29707
29864
|
}
|
|
29708
|
-
const saveSz =
|
|
29709
|
-
console.log(
|
|
29865
|
+
const saveSz = statSync47(installedSaveHook).size;
|
|
29866
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
|
|
29710
29867
|
if (installResume) {
|
|
29711
|
-
const resumeSz =
|
|
29712
|
-
console.log(
|
|
29868
|
+
const resumeSz = statSync47(installedResumeHook).size;
|
|
29869
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall resume-hint hook (${resumeSz} bytes)`));
|
|
29713
29870
|
}
|
|
29714
29871
|
if (installEscalate) {
|
|
29715
|
-
const escalateSz =
|
|
29716
|
-
console.log(
|
|
29872
|
+
const escalateSz = statSync47(installedEscalateHook).size;
|
|
29873
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall learnings-escalation hook (${escalateSz} bytes)`));
|
|
29717
29874
|
}
|
|
29718
29875
|
if (installWakeup) {
|
|
29719
|
-
const wakeupSz =
|
|
29720
|
-
console.log(
|
|
29876
|
+
const wakeupSz = statSync47(installedWakeupHook).size;
|
|
29877
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall wake-up hook (${wakeupSz} bytes)`));
|
|
29721
29878
|
for (const f of wakeupSkipped) {
|
|
29722
|
-
console.log(
|
|
29879
|
+
console.log(chalk2.dim(` (SessionStart already wired in ${f} \u2014 left as is, not duplicated)`));
|
|
29723
29880
|
}
|
|
29724
29881
|
}
|
|
29725
|
-
console.log(
|
|
29882
|
+
console.log(chalk2.dim(` scripts: ${hooksDir}`));
|
|
29726
29883
|
for (const [i, f] of hookConfigFiles.entries()) {
|
|
29727
|
-
console.log(
|
|
29884
|
+
console.log(chalk2.dim(` ${i === 0 ? "config: " : " "} ${f}`));
|
|
29728
29885
|
}
|
|
29729
|
-
console.log(
|
|
29886
|
+
console.log(chalk2.dim(` events: Stop, PreCompact${installResume ? ", UserPromptSubmit" : ""}${installEscalate ? ", SessionEnd" : ""}${installWakeup ? ", SessionStart" : ""}`));
|
|
29730
29887
|
console.log();
|
|
29731
|
-
console.log(
|
|
29888
|
+
console.log(chalk2.dim("Run `chat-recall install-hooks --uninstall` to remove later."));
|
|
29732
29889
|
if (!installResume) {
|
|
29733
|
-
console.log(
|
|
29890
|
+
console.log(chalk2.dim("(--no-resume-hint passed \u2014 UserPromptSubmit hook skipped)"));
|
|
29734
29891
|
}
|
|
29735
29892
|
if (!installEscalate) {
|
|
29736
|
-
console.log(
|
|
29893
|
+
console.log(chalk2.dim("(--no-escalate passed \u2014 SessionEnd hook skipped)"));
|
|
29737
29894
|
}
|
|
29738
29895
|
});
|
|
29739
29896
|
program.command("dossier <project>").description("Generate a project dossier (overview/architecture/decisions/etc) as markdown (requires login)").option("--sessions <n>", "Max sessions to enumerate", "10").option("--tasks <n>", "Max open tasks to list", "20").option("--plans <n>", "Max plans to list", "20").option("--out <file>", "Write report to this file instead of stdout").action(async (project, options) => {
|
|
@@ -29749,12 +29906,12 @@ program.command("dossier <project>").description("Generate a project dossier (ov
|
|
|
29749
29906
|
const md = dossier.markdown;
|
|
29750
29907
|
if (options.out) {
|
|
29751
29908
|
writeFileSync21(options.out, md);
|
|
29752
|
-
console.log(
|
|
29909
|
+
console.log(chalk2.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
|
|
29753
29910
|
} else {
|
|
29754
29911
|
console.log(md);
|
|
29755
29912
|
}
|
|
29756
29913
|
} catch (err) {
|
|
29757
|
-
console.error(
|
|
29914
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
29758
29915
|
process.exit(1);
|
|
29759
29916
|
}
|
|
29760
29917
|
});
|
|
@@ -29767,17 +29924,17 @@ program.command("watch").description("Run the live indexer daemon (watches all A
|
|
|
29767
29924
|
try {
|
|
29768
29925
|
const { installService: installService2, isServiceRunning: isServiceRunning2, platformName: platformName2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
|
|
29769
29926
|
if (isServiceRunning2()) {
|
|
29770
|
-
console.log(
|
|
29927
|
+
console.log(chalk2.green(`\u2713 chat-recall-watch service already running (${platformName2()}).`));
|
|
29771
29928
|
return;
|
|
29772
29929
|
}
|
|
29773
29930
|
const paths = installService2();
|
|
29774
|
-
console.log(
|
|
29775
|
-
if (paths.definitionPath) console.log(
|
|
29776
|
-
console.log(
|
|
29931
|
+
console.log(chalk2.green(`\u2713 chat-recall-watch service installed and started (${platformName2()}).`));
|
|
29932
|
+
if (paths.definitionPath) console.log(chalk2.dim(` unit: ${paths.definitionPath}`));
|
|
29933
|
+
console.log(chalk2.dim(` logs: ${paths.logFile} \xB7 manage: \`chat-recall service status\``));
|
|
29777
29934
|
} catch (err) {
|
|
29778
29935
|
const se = err;
|
|
29779
|
-
console.error(
|
|
29780
|
-
if (se.hint) console.error(
|
|
29936
|
+
console.error(chalk2.red(se.message || String(err)));
|
|
29937
|
+
if (se.hint) console.error(chalk2.dim(` ${se.hint}`));
|
|
29781
29938
|
process.exit(1);
|
|
29782
29939
|
}
|
|
29783
29940
|
return;
|
|
@@ -29795,7 +29952,7 @@ program.command("watch").description("Run the live indexer daemon (watches all A
|
|
|
29795
29952
|
lastErr = err;
|
|
29796
29953
|
}
|
|
29797
29954
|
}
|
|
29798
|
-
console.error(
|
|
29955
|
+
console.error(chalk2.red("Could not load the watch daemon:"), lastErr instanceof Error ? lastErr.message : lastErr);
|
|
29799
29956
|
process.exit(1);
|
|
29800
29957
|
});
|
|
29801
29958
|
program.command("service <action>").description("Manage the per-user background sync service (Linux: systemd --user \xB7 macOS: launchd \xB7 Windows: Scheduled Task). No admin rights.").action(async (action) => {
|
|
@@ -29804,25 +29961,25 @@ program.command("service <action>").description("Manage the per-user background
|
|
|
29804
29961
|
if (action === "install") {
|
|
29805
29962
|
const wasRunning = isServiceRunning2();
|
|
29806
29963
|
const paths = installService2();
|
|
29807
|
-
console.log(
|
|
29808
|
-
if (paths.definitionPath) console.log(
|
|
29809
|
-
console.log(
|
|
29964
|
+
console.log(chalk2.green(wasRunning ? `\u2713 chat-recall-watch service restarted with the current binary (${platformName2()}).` : `\u2713 chat-recall-watch service installed and started (${platformName2()}).`));
|
|
29965
|
+
if (paths.definitionPath) console.log(chalk2.dim(` unit: ${paths.definitionPath}`));
|
|
29966
|
+
console.log(chalk2.dim(` logs: ${paths.logFile}`));
|
|
29810
29967
|
} else if (action === "uninstall") {
|
|
29811
29968
|
const removed = uninstallService2();
|
|
29812
|
-
if (removed) console.log(
|
|
29813
|
-
else console.log(
|
|
29969
|
+
if (removed) console.log(chalk2.green(`\u2713 chat-recall-watch service removed (${platformName2()}).`));
|
|
29970
|
+
else console.log(chalk2.dim("Service was not installed \u2014 nothing to remove."));
|
|
29814
29971
|
} else if (action === "status") {
|
|
29815
29972
|
const running = isServiceRunning2();
|
|
29816
|
-
console.log(`chat-recall-watch service (${platformName2()}): ${running ?
|
|
29817
|
-
if (!running) console.log(
|
|
29973
|
+
console.log(`chat-recall-watch service (${platformName2()}): ${running ? chalk2.green("running") : chalk2.yellow("not running")}`);
|
|
29974
|
+
if (!running) console.log(chalk2.dim(" install: `chat-recall service install`"));
|
|
29818
29975
|
} else {
|
|
29819
|
-
console.error(
|
|
29976
|
+
console.error(chalk2.red(`Unknown action '${action}'.`) + chalk2.dim(" Use: install | uninstall | status"));
|
|
29820
29977
|
process.exit(1);
|
|
29821
29978
|
}
|
|
29822
29979
|
} catch (err) {
|
|
29823
29980
|
const se = err;
|
|
29824
|
-
console.error(
|
|
29825
|
-
if (se.hint) console.error(
|
|
29981
|
+
console.error(chalk2.red(se.message || String(err)));
|
|
29982
|
+
if (se.hint) console.error(chalk2.dim(` ${se.hint}`));
|
|
29826
29983
|
process.exit(1);
|
|
29827
29984
|
}
|
|
29828
29985
|
});
|
|
@@ -29843,7 +30000,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29843
30000
|
if (opts.token) {
|
|
29844
30001
|
const token = opts.token.trim();
|
|
29845
30002
|
if (/[^\x21-\x7e]/.test(token)) {
|
|
29846
|
-
console.error(
|
|
30003
|
+
console.error(chalk2.dim("Use the Copy button next to the token in the dashboard and paste the full line."));
|
|
29847
30004
|
return failLogin(opts, 'the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026")');
|
|
29848
30005
|
}
|
|
29849
30006
|
const { probeServer: identifyServer, probeOk: identified, probeAdvice: identifyAdvice } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
@@ -29852,7 +30009,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29852
30009
|
const check = await verifyToken(token);
|
|
29853
30010
|
if (!check.ok) {
|
|
29854
30011
|
if (check.status === 401 || check.status === 403) {
|
|
29855
|
-
console.error(
|
|
30012
|
+
console.error(chalk2.dim("Tokens are shown once \u2014 mint a fresh one (Account \u2192 Connect your machine) and copy the whole line."));
|
|
29856
30013
|
return failLogin(opts, `${base2} rejected the token (HTTP ${check.status})`);
|
|
29857
30014
|
}
|
|
29858
30015
|
if (check.status !== void 0 && check.status >= 500) {
|
|
@@ -29864,12 +30021,12 @@ async function runLogin(serverUrl, opts) {
|
|
|
29864
30021
|
return failLogin(opts, `could not verify the token against ${base2}: ${check.error || `HTTP ${check.status}`}`);
|
|
29865
30022
|
}
|
|
29866
30023
|
saveCredentials2({ serverUrl, token });
|
|
29867
|
-
console.log(
|
|
30024
|
+
console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` server: ${serverUrl} (token verified)`));
|
|
29868
30025
|
return;
|
|
29869
30026
|
}
|
|
29870
30027
|
const existing = loadAllCredentials2().find((t) => t.serverUrl.replace(/\/+$/, "") === base2);
|
|
29871
30028
|
if (existing && (await verifyToken(existing.token)).ok) {
|
|
29872
|
-
console.log(
|
|
30029
|
+
console.log(chalk2.green("\u2713 Already connected.") + chalk2.dim(` server: ${serverUrl} (existing token verified)`));
|
|
29873
30030
|
return;
|
|
29874
30031
|
}
|
|
29875
30032
|
try {
|
|
@@ -29879,14 +30036,14 @@ async function runLogin(serverUrl, opts) {
|
|
|
29879
30036
|
const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
|
|
29880
30037
|
if (probe.status === 200) {
|
|
29881
30038
|
saveCredentials2({ serverUrl, token: "" });
|
|
29882
|
-
console.log(
|
|
30039
|
+
console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
|
|
29883
30040
|
return;
|
|
29884
30041
|
}
|
|
29885
30042
|
}
|
|
29886
30043
|
} catch {
|
|
29887
30044
|
}
|
|
29888
30045
|
if (opts.check) {
|
|
29889
|
-
console.error(
|
|
30046
|
+
console.error(chalk2.dim(`Not connected to ${serverUrl}.`));
|
|
29890
30047
|
process.exit(1);
|
|
29891
30048
|
}
|
|
29892
30049
|
try {
|
|
@@ -29914,7 +30071,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29914
30071
|
if (!authProvider && !issuer) {
|
|
29915
30072
|
authProvider = "better-auth";
|
|
29916
30073
|
if (capsError) {
|
|
29917
|
-
console.log(` ${
|
|
30074
|
+
console.log(` ${chalk2.yellow("Could not read the server's capabilities")} (${capsError}) \u2014 assuming the standard sign-in flow.`);
|
|
29918
30075
|
void Promise.resolve().then(() => (init_install_report(), install_report_exports)).then(({ reportInstallFailure: reportInstallFailure2 }) => reportInstallFailure2(base2, {
|
|
29919
30076
|
step: "cli_login_probe",
|
|
29920
30077
|
reason: capsError?.slice(0, 40) ?? null,
|
|
@@ -29929,13 +30086,13 @@ async function runLogin(serverUrl, opts) {
|
|
|
29929
30086
|
return;
|
|
29930
30087
|
}
|
|
29931
30088
|
console.log();
|
|
29932
|
-
console.log(
|
|
29933
|
-
console.log(" " +
|
|
30089
|
+
console.log(chalk2.bold("Open this to approve the machine:"));
|
|
30090
|
+
console.log(" " + chalk2.cyan(p.url));
|
|
29934
30091
|
if (openBrowser(p.url)) {
|
|
29935
|
-
console.log(
|
|
30092
|
+
console.log(chalk2.dim(" (tried to open it for you \u2014 if no window appeared, use the link above)"));
|
|
29936
30093
|
}
|
|
29937
|
-
console.log(
|
|
29938
|
-
console.log(
|
|
30094
|
+
console.log(chalk2.dim(` If the page asks for a code: ${chalk2.bold(p.userCode)}`));
|
|
30095
|
+
console.log(chalk2.dim("Then come back here \u2014 this finishes on its own. Waiting\u2026"));
|
|
29939
30096
|
};
|
|
29940
30097
|
const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
|
|
29941
30098
|
const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };
|
|
@@ -29951,9 +30108,9 @@ async function runLogin(serverUrl, opts) {
|
|
|
29951
30108
|
const created = await fetch(`${base2}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
|
|
29952
30109
|
if (!created.ok) throw new Error(`workspace create failed: HTTP ${created.status} ${await created.text().catch(() => "")}`);
|
|
29953
30110
|
slug = (await readJsonBody(created, base2, "POST /api/teams")).slug;
|
|
29954
|
-
console.log(
|
|
30111
|
+
console.log(chalk2.dim(`Created workspace "${name}" (${slug}) \u2014 you're its owner.`));
|
|
29955
30112
|
} else {
|
|
29956
|
-
teams.forEach((t) => console.error(` ${t.team_slug} ${
|
|
30113
|
+
teams.forEach((t) => console.error(` ${t.team_slug} ${chalk2.dim(t.name)}`));
|
|
29957
30114
|
throw new Error("you belong to multiple teams \u2014 pass --team <slug> (listed above)");
|
|
29958
30115
|
}
|
|
29959
30116
|
}
|
|
@@ -29966,13 +30123,13 @@ async function runLogin(serverUrl, opts) {
|
|
|
29966
30123
|
if (!mint.ok) throw new Error(`token mint failed: HTTP ${mint.status} ${await mint.text().catch(() => "")}`);
|
|
29967
30124
|
const { token } = await readJsonBody(mint, base2, "POST /api/teams/:slug/tokens");
|
|
29968
30125
|
saveCredentials2({ serverUrl, token });
|
|
29969
|
-
console.log(
|
|
30126
|
+
console.log(chalk2.green(`\u2713 Logged in to ${chalk2.bold(slug)}`) + chalk2.dim(` (device: ${deviceId}) server: ${serverUrl}`));
|
|
29970
30127
|
if (!opts.fromInit) {
|
|
29971
|
-
console.log(
|
|
30128
|
+
console.log(chalk2.dim("Run `chat-recall sync` to push redacted conversations."));
|
|
29972
30129
|
}
|
|
29973
30130
|
} catch (err) {
|
|
29974
30131
|
if (opts.fromInit) throw err instanceof Error ? err : new Error(String(err));
|
|
29975
|
-
console.error(
|
|
30132
|
+
console.error(chalk2.red("login failed:"), err instanceof Error ? err.message : err);
|
|
29976
30133
|
process.exit(1);
|
|
29977
30134
|
}
|
|
29978
30135
|
}
|
|
@@ -29980,15 +30137,15 @@ var exclude = program.command("exclude").description("Control what never leaves
|
|
|
29980
30137
|
exclude.command("list", { isDefault: true }).description("Show current exclusions").action(async () => {
|
|
29981
30138
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
29982
30139
|
const s = loadSettings2();
|
|
29983
|
-
console.log(
|
|
29984
|
-
console.log(` tools: ${s.sync.excludeTools.length ? s.sync.excludeTools.join(", ") :
|
|
29985
|
-
console.log(` projects: ${s.sync.excludeProjects.length ? "" :
|
|
30140
|
+
console.log(chalk2.bold("Sync exclusions") + chalk2.dim(" (~/.chat-recall/settings/settings.json)"));
|
|
30141
|
+
console.log(` tools: ${s.sync.excludeTools.length ? s.sync.excludeTools.join(", ") : chalk2.dim("none")}`);
|
|
30142
|
+
console.log(` projects: ${s.sync.excludeProjects.length ? "" : chalk2.dim("none")}`);
|
|
29986
30143
|
for (const p of s.sync.excludeProjects) console.log(` ${p}`);
|
|
29987
30144
|
const deny = s.privacy.projectDenylist || [];
|
|
29988
|
-
console.log(` index denylist (never even indexed): ${deny.length ? "" :
|
|
30145
|
+
console.log(` index denylist (never even indexed): ${deny.length ? "" : chalk2.dim("none")}`);
|
|
29989
30146
|
for (const p of deny) console.log(` ${p}`);
|
|
29990
|
-
console.log(
|
|
29991
|
-
console.log(
|
|
30147
|
+
console.log(chalk2.dim("\nPersonal folders (Pictures, Music, Documents, Desktop, Downloads, \u2026) are"));
|
|
30148
|
+
console.log(chalk2.dim("skipped from code indexing by default. Opt one back in: `chat-recall include project <path>`."));
|
|
29992
30149
|
const inc = s.sync.includeProjects || [];
|
|
29993
30150
|
if (inc.length) {
|
|
29994
30151
|
console.log(` personal paths opted back in:`);
|
|
@@ -29999,8 +30156,8 @@ var include = program.command("include").description("Opt a personal folder (Pic
|
|
|
29999
30156
|
include.command("list", { isDefault: true }).description("Show paths opted back into indexing").action(async () => {
|
|
30000
30157
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30001
30158
|
const inc = loadSettings2().sync.includeProjects || [];
|
|
30002
|
-
console.log(
|
|
30003
|
-
if (!inc.length) console.log(
|
|
30159
|
+
console.log(chalk2.bold("Personal paths opted back into code indexing"));
|
|
30160
|
+
if (!inc.length) console.log(chalk2.dim(" none \u2014 personal folders are all skipped"));
|
|
30004
30161
|
for (const p of inc) console.log(` ${p}`);
|
|
30005
30162
|
});
|
|
30006
30163
|
include.command("project <path>").description("Re-permit a path under a personal folder (substring match; e.g. ~/Documents/code)").action(async (path) => {
|
|
@@ -30011,12 +30168,12 @@ include.command("project <path>").description("Re-permit a path under a personal
|
|
|
30011
30168
|
const s = loadSettings2();
|
|
30012
30169
|
s.sync.includeProjects = s.sync.includeProjects || [];
|
|
30013
30170
|
if (s.sync.includeProjects.includes(abs)) {
|
|
30014
|
-
console.log(
|
|
30171
|
+
console.log(chalk2.dim(`Already included: ${abs}`));
|
|
30015
30172
|
return;
|
|
30016
30173
|
}
|
|
30017
30174
|
s.sync.includeProjects.push(abs);
|
|
30018
30175
|
saveSettings2(s);
|
|
30019
|
-
console.log(
|
|
30176
|
+
console.log(chalk2.green(`\u2713 Included ${abs}`) + chalk2.dim(" \u2014 code indexing may now walk it (takes effect next discovery tick)."));
|
|
30020
30177
|
});
|
|
30021
30178
|
include.command("remove <path>").description("Undo an opt-in (personal folder goes back to skipped)").action(async (path) => {
|
|
30022
30179
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30027,30 +30184,30 @@ include.command("remove <path>").description("Undo an opt-in (personal folder go
|
|
|
30027
30184
|
const before = (s.sync.includeProjects || []).length;
|
|
30028
30185
|
s.sync.includeProjects = (s.sync.includeProjects || []).filter((p) => p !== path && p !== abs);
|
|
30029
30186
|
if (s.sync.includeProjects.length === before) {
|
|
30030
|
-
console.error(
|
|
30187
|
+
console.error(chalk2.red(`Not in the include list: ${path}`));
|
|
30031
30188
|
process.exit(1);
|
|
30032
30189
|
}
|
|
30033
30190
|
saveSettings2(s);
|
|
30034
|
-
console.log(
|
|
30191
|
+
console.log(chalk2.green(`\u2713 Removed ${path}`) + chalk2.dim(" \u2014 back to skipped."));
|
|
30035
30192
|
});
|
|
30036
30193
|
var retention = program.command("retention").description("How long the server keeps your synced sessions. Bare `retention` shows the current window.");
|
|
30037
30194
|
retention.command("show", { isDefault: true }).description("Show the window and how many sessions it would delete").action(async () => {
|
|
30038
30195
|
const t = requireTarget();
|
|
30039
30196
|
const r = await serverGet("/api/data/retention");
|
|
30040
|
-
console.log(
|
|
30197
|
+
console.log(chalk2.bold("Retention") + chalk2.dim(` (server: ${t.base})`));
|
|
30041
30198
|
if (!r.days) {
|
|
30042
|
-
console.log(` window: ${
|
|
30199
|
+
console.log(` window: ${chalk2.green("none")} \u2014 the server keeps everything you sync`);
|
|
30043
30200
|
} else {
|
|
30044
|
-
console.log(` window: ${
|
|
30201
|
+
console.log(` window: ${chalk2.yellow(`${r.days} days`)}`);
|
|
30045
30202
|
console.log(` sessions currently outside it: ${r.wouldDelete}`);
|
|
30046
30203
|
}
|
|
30047
|
-
console.log(
|
|
30204
|
+
console.log(chalk2.dim(` Set one: chat-recall retention set <${r.min}-${r.max}> \xB7 clear it: chat-recall retention set 0`));
|
|
30048
30205
|
});
|
|
30049
30206
|
retention.command("set <days>").description("Set the window in days, or 0 to keep everything. Prints what it would delete and asks first.").option("--yes", "Skip the confirmation (for scripts). The warning still prints.", false).action(async (daysArg, opts) => {
|
|
30050
30207
|
requireTarget();
|
|
30051
30208
|
const days = Number(daysArg);
|
|
30052
30209
|
if (!Number.isInteger(days) || days < 0) {
|
|
30053
|
-
console.error(
|
|
30210
|
+
console.error(chalk2.red(`'${daysArg}' is not a whole number of days.`));
|
|
30054
30211
|
process.exit(1);
|
|
30055
30212
|
}
|
|
30056
30213
|
const preview = await serverGet(
|
|
@@ -30058,23 +30215,23 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30058
30215
|
);
|
|
30059
30216
|
if (days === 0) {
|
|
30060
30217
|
await serverPost("/api/data/retention", { days: 0 });
|
|
30061
|
-
console.log(
|
|
30218
|
+
console.log(chalk2.green("\u2713 Window cleared") + chalk2.dim(" \u2014 the server keeps everything you sync."));
|
|
30062
30219
|
return;
|
|
30063
30220
|
}
|
|
30064
30221
|
if (preview.wouldDelete > 0) {
|
|
30065
30222
|
console.log();
|
|
30066
|
-
console.log(
|
|
30067
|
-
console.log(
|
|
30223
|
+
console.log(chalk2.yellow(`This deletes ${preview.wouldDelete} session(s) from the server, and keeps deleting`));
|
|
30224
|
+
console.log(chalk2.yellow(`anything older than ${days} days from now on.`));
|
|
30068
30225
|
console.log();
|
|
30069
|
-
console.log(
|
|
30070
|
-
console.log(
|
|
30071
|
-
console.log(
|
|
30072
|
-
console.log(
|
|
30226
|
+
console.log(chalk2.dim(" Re-syncing can restore a session ONLY if its transcript is still on a"));
|
|
30227
|
+
console.log(chalk2.dim(" machine you have. For a laptop you no longer own, history your AI tool"));
|
|
30228
|
+
console.log(chalk2.dim(" has rotated, or files you deleted, our copy is the only copy."));
|
|
30229
|
+
console.log(chalk2.dim(" Take one first if you want it: chat-recall export"));
|
|
30073
30230
|
console.log();
|
|
30074
30231
|
if (!opts.yes) {
|
|
30075
30232
|
if (!process.stdin.isTTY) {
|
|
30076
|
-
console.error(
|
|
30077
|
-
console.error(
|
|
30233
|
+
console.error(chalk2.red("Refusing: this would delete data and there is no terminal to confirm at."));
|
|
30234
|
+
console.error(chalk2.dim("Pass --yes if you mean it."));
|
|
30078
30235
|
process.exit(1);
|
|
30079
30236
|
}
|
|
30080
30237
|
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
@@ -30082,7 +30239,7 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30082
30239
|
const answer = (await rl.question(`Type ${preview.wouldDelete} to confirm: `)).trim();
|
|
30083
30240
|
rl.close();
|
|
30084
30241
|
if (answer !== String(preview.wouldDelete)) {
|
|
30085
|
-
console.log(
|
|
30242
|
+
console.log(chalk2.dim("Cancelled \u2014 nothing changed."));
|
|
30086
30243
|
return;
|
|
30087
30244
|
}
|
|
30088
30245
|
}
|
|
@@ -30091,20 +30248,20 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30091
30248
|
"/api/data/retention",
|
|
30092
30249
|
{ days, acknowledge: true }
|
|
30093
30250
|
);
|
|
30094
|
-
console.log(
|
|
30251
|
+
console.log(chalk2.green(`\u2713 Window set to ${res.days} days`) + chalk2.dim(` \u2014 ${res.wouldDelete} session(s) will be removed on the next sweep.`));
|
|
30095
30252
|
});
|
|
30096
30253
|
var syncOnly = program.command("sync-only").description("Opt-in sync: ship ONLY chosen projects. Bare `sync-only` shows the mode + allowlist.");
|
|
30097
30254
|
syncOnly.command("list", { isDefault: true }).description("Show the sync mode and allowlisted projects").action(async () => {
|
|
30098
30255
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30099
30256
|
const s = loadSettings2();
|
|
30100
30257
|
const mode = s.sync.syncMode ?? "all";
|
|
30101
|
-
console.log(
|
|
30102
|
-
console.log(` mode: ${mode === "only" ?
|
|
30258
|
+
console.log(chalk2.bold("Selective sync") + chalk2.dim(" (~/.chat-recall/settings/settings.json)"));
|
|
30259
|
+
console.log(` mode: ${mode === "only" ? chalk2.yellow("only (allowlist)") : chalk2.green("all (default)")}`);
|
|
30103
30260
|
const list = s.sync.syncOnlyProjects ?? [];
|
|
30104
|
-
console.log(` projects: ${list.length ? "" :
|
|
30261
|
+
console.log(` projects: ${list.length ? "" : chalk2.dim("none")}`);
|
|
30105
30262
|
for (const p of list) console.log(` ${p}`);
|
|
30106
|
-
if (mode === "only" && list.length === 0) console.log(
|
|
30107
|
-
console.log(
|
|
30263
|
+
if (mode === "only" && list.length === 0) console.log(chalk2.red(' \u26A0 mode is "only" but the allowlist is empty \u2192 NOTHING syncs.'));
|
|
30264
|
+
console.log(chalk2.dim("\nList ids with `chat-recall projects`, then `chat-recall sync-only add <id>`."));
|
|
30108
30265
|
});
|
|
30109
30266
|
syncOnly.command("add <project>").description('Add a project id (git:\u2026/ws:\u2026/path:\u2026) or path substring to the allowlist (switches mode to "only")').action(async (project) => {
|
|
30110
30267
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30113,7 +30270,7 @@ syncOnly.command("add <project>").description('Add a project id (git:\u2026/ws:\
|
|
|
30113
30270
|
if (!s.sync.syncOnlyProjects.includes(project)) s.sync.syncOnlyProjects.push(project);
|
|
30114
30271
|
s.sync.syncMode = "only";
|
|
30115
30272
|
saveSettings2(s);
|
|
30116
|
-
console.log(
|
|
30273
|
+
console.log(chalk2.green(`\u2713 Added ${project}`) + chalk2.dim(' \u2014 mode is now "only"; next sync ships only allowlisted projects.'));
|
|
30117
30274
|
});
|
|
30118
30275
|
syncOnly.command("remove <project>").description("Remove a project from the allowlist").action(async (project) => {
|
|
30119
30276
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30121,18 +30278,18 @@ syncOnly.command("remove <project>").description("Remove a project from the allo
|
|
|
30121
30278
|
const before = (s.sync.syncOnlyProjects || []).length;
|
|
30122
30279
|
s.sync.syncOnlyProjects = (s.sync.syncOnlyProjects || []).filter((p) => p !== project);
|
|
30123
30280
|
if (s.sync.syncOnlyProjects.length === before) {
|
|
30124
|
-
console.error(
|
|
30281
|
+
console.error(chalk2.red(`Not in the allowlist: ${project}`));
|
|
30125
30282
|
process.exit(1);
|
|
30126
30283
|
}
|
|
30127
30284
|
saveSettings2(s);
|
|
30128
|
-
console.log(
|
|
30285
|
+
console.log(chalk2.green(`\u2713 Removed ${project}`));
|
|
30129
30286
|
});
|
|
30130
30287
|
syncOnly.command("all").description("Switch back to syncing ALL non-excluded projects (keeps the list, just disables opt-in mode)").action(async () => {
|
|
30131
30288
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30132
30289
|
const s = loadSettings2();
|
|
30133
30290
|
s.sync.syncMode = "all";
|
|
30134
30291
|
saveSettings2(s);
|
|
30135
|
-
console.log(
|
|
30292
|
+
console.log(chalk2.green("\u2713 Sync mode: all") + chalk2.dim(" \u2014 every non-excluded project syncs again."));
|
|
30136
30293
|
});
|
|
30137
30294
|
program.command("projects").description("List distinct project ids discovered on this machine (for `sync-only add`)").action(async () => {
|
|
30138
30295
|
const { listAvailableBackends: listAvailableBackends2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
|
|
@@ -30152,11 +30309,11 @@ program.command("projects").description("List distinct project ids discovered on
|
|
|
30152
30309
|
}
|
|
30153
30310
|
const rows = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
30154
30311
|
if (!rows.length) {
|
|
30155
|
-
console.log(
|
|
30312
|
+
console.log(chalk2.dim("No projects discovered."));
|
|
30156
30313
|
return;
|
|
30157
30314
|
}
|
|
30158
|
-
console.log(
|
|
30159
|
-
for (const [id, n] of rows) console.log(` ${
|
|
30315
|
+
console.log(chalk2.bold("Discovered projects") + chalk2.dim(" (id \xB7 sessions)"));
|
|
30316
|
+
for (const [id, n] of rows) console.log(` ${chalk2.cyan(id)} ${chalk2.dim("\xB7 " + n)}`);
|
|
30160
30317
|
});
|
|
30161
30318
|
exclude.command("project <path>").description("Exclude a project path from syncing (substring match on the project path; e.g. ~/.claude-pr-bot)").action(async (path) => {
|
|
30162
30319
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30165,28 +30322,28 @@ exclude.command("project <path>").description("Exclude a project path from synci
|
|
|
30165
30322
|
const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
|
|
30166
30323
|
const s = loadSettings2();
|
|
30167
30324
|
if (s.sync.excludeProjects.includes(abs)) {
|
|
30168
|
-
console.log(
|
|
30325
|
+
console.log(chalk2.dim(`Already excluded: ${abs}`));
|
|
30169
30326
|
return;
|
|
30170
30327
|
}
|
|
30171
30328
|
s.sync.excludeProjects.push(abs);
|
|
30172
30329
|
saveSettings2(s);
|
|
30173
|
-
console.log(
|
|
30330
|
+
console.log(chalk2.green(`\u2713 Excluded ${abs}`) + chalk2.dim(" \u2014 takes effect next sync; already-synced rows stay until deleted."));
|
|
30174
30331
|
});
|
|
30175
30332
|
exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude | gemini | codex | opencode | agy | cursor)").action(async (tool) => {
|
|
30176
30333
|
const valid = ["claude", "gemini", "codex", "opencode", "agy", "cursor"];
|
|
30177
30334
|
if (!valid.includes(tool)) {
|
|
30178
|
-
console.error(
|
|
30335
|
+
console.error(chalk2.red(`Unknown tool '${tool}'.`) + chalk2.dim(` Use one of: ${valid.join(" | ")}`));
|
|
30179
30336
|
process.exit(1);
|
|
30180
30337
|
}
|
|
30181
30338
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30182
30339
|
const s = loadSettings2();
|
|
30183
30340
|
if (s.sync.excludeTools.includes(tool)) {
|
|
30184
|
-
console.log(
|
|
30341
|
+
console.log(chalk2.dim(`Already excluded: ${tool}`));
|
|
30185
30342
|
return;
|
|
30186
30343
|
}
|
|
30187
30344
|
s.sync.excludeTools.push(tool);
|
|
30188
30345
|
saveSettings2(s);
|
|
30189
|
-
console.log(
|
|
30346
|
+
console.log(chalk2.green(`\u2713 Excluded ${tool}`) + chalk2.dim(" \u2014 nothing from it leaves this machine from the next sync on."));
|
|
30190
30347
|
});
|
|
30191
30348
|
exclude.command("remove <value>").description("Remove an exclusion (tool name or project path)").action(async (value) => {
|
|
30192
30349
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30199,20 +30356,20 @@ exclude.command("remove <value>").description("Remove an exclusion (tool name or
|
|
|
30199
30356
|
s.sync.excludeTools = s.sync.excludeTools.filter((t) => t !== value);
|
|
30200
30357
|
s.sync.excludeProjects = s.sync.excludeProjects.filter((p) => p !== value && p !== abs);
|
|
30201
30358
|
if (s.sync.excludeTools.length === beforeTools && s.sync.excludeProjects.length === beforeProjects) {
|
|
30202
|
-
console.error(
|
|
30359
|
+
console.error(chalk2.red(`No exclusion matches '${value}'.`) + chalk2.dim(" See: chat-recall exclude list"));
|
|
30203
30360
|
process.exit(1);
|
|
30204
30361
|
}
|
|
30205
30362
|
saveSettings2(s);
|
|
30206
|
-
console.log(
|
|
30363
|
+
console.log(chalk2.green(`\u2713 Removed ${value}`));
|
|
30207
30364
|
});
|
|
30208
30365
|
program.command("login [server-url]").description(`Log in (device flow) and mint a sync device token \u2192 ~/.chat-recall/credentials.json (0600). Defaults to ${DEFAULT_SERVER}.`).option("--token <token>", "Self-host: skip OIDC and save this device token directly").option("--check", "Report-only: exit 0 if a working credential for this server exists, 1 otherwise (never interactive)").option("--issuer <url>", "OIDC issuer URL (default: learned from the server via /api/capabilities)").option("--client-id <id>", "OIDC client id (default: chat-recall-web)").option("--team <slug>", "Team to mint the device token for (default: your only team)").option("--device-id <id>", "Device id for this machine (default: hostname)").option("--prompt-json", "Print the device prompt as one JSON line on stdout, then wait for approval", false).action((serverUrl, opts) => runLogin(serverUrl || DEFAULT_SERVER, opts));
|
|
30209
30366
|
program.command("logout <server-url>").description("Remove one sync target (other targets keep syncing)").action(async (serverUrl) => {
|
|
30210
30367
|
const { removeCredentials: removeCredentials2, loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30211
30368
|
if (removeCredentials2(serverUrl)) {
|
|
30212
30369
|
const left = loadAllCredentials2();
|
|
30213
|
-
console.log(
|
|
30370
|
+
console.log(chalk2.green(`\u2713 Removed ${serverUrl}`) + chalk2.dim(` \u2014 ${left.length} target(s) remain`));
|
|
30214
30371
|
} else {
|
|
30215
|
-
console.error(
|
|
30372
|
+
console.error(chalk2.red(`No such target: ${serverUrl}`));
|
|
30216
30373
|
process.exit(1);
|
|
30217
30374
|
}
|
|
30218
30375
|
});
|
|
@@ -30220,7 +30377,7 @@ program.command("delete <session-id>").description("Delete a session from chat-r
|
|
|
30220
30377
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30221
30378
|
const targets = loadAllCredentials2();
|
|
30222
30379
|
if (targets.length === 0) {
|
|
30223
|
-
console.error(
|
|
30380
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30224
30381
|
process.exit(1);
|
|
30225
30382
|
}
|
|
30226
30383
|
let ok = 0;
|
|
@@ -30232,20 +30389,20 @@ program.command("delete <session-id>").description("Delete a session from chat-r
|
|
|
30232
30389
|
});
|
|
30233
30390
|
if (res.ok) {
|
|
30234
30391
|
ok++;
|
|
30235
|
-
console.log(
|
|
30236
|
-
} else console.error(
|
|
30392
|
+
console.log(chalk2.green(`\u2713 Deleted on ${t.serverUrl}`));
|
|
30393
|
+
} else console.error(chalk2.red(`\u2717 ${t.serverUrl}: HTTP ${res.status}`));
|
|
30237
30394
|
} catch (e) {
|
|
30238
|
-
console.error(
|
|
30395
|
+
console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
|
|
30239
30396
|
}
|
|
30240
30397
|
}
|
|
30241
30398
|
if (ok === 0) process.exit(1);
|
|
30242
|
-
console.log(
|
|
30399
|
+
console.log(chalk2.dim(`Tombstoned on ${ok}/${targets.length} server(s) \u2014 re-sync cannot resurrect it.`));
|
|
30243
30400
|
});
|
|
30244
30401
|
program.command("rename <session-id> <name>").description('Give a session a memorable name on every logged-in server (mirrors Claude Code\'s /rename \u2014 shows in `recall recent` and the web UI in place of the auto summary). Pass an empty name "" to clear it and revert to the auto title.').action(async (sessionId, name) => {
|
|
30245
30402
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30246
30403
|
const targets = loadAllCredentials2();
|
|
30247
30404
|
if (targets.length === 0) {
|
|
30248
|
-
console.error(
|
|
30405
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30249
30406
|
process.exit(1);
|
|
30250
30407
|
}
|
|
30251
30408
|
let ok = 0;
|
|
@@ -30258,14 +30415,14 @@ program.command("rename <session-id> <name>").description('Give a session a memo
|
|
|
30258
30415
|
});
|
|
30259
30416
|
if (res.ok) {
|
|
30260
30417
|
ok++;
|
|
30261
|
-
console.log(
|
|
30262
|
-
} else console.error(
|
|
30418
|
+
console.log(chalk2.green(`\u2713 ${name.trim() ? "Named" : "Cleared name"} on ${t.serverUrl}`));
|
|
30419
|
+
} else console.error(chalk2.red(`\u2717 ${t.serverUrl}: HTTP ${res.status}`));
|
|
30263
30420
|
} catch (e) {
|
|
30264
|
-
console.error(
|
|
30421
|
+
console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
|
|
30265
30422
|
}
|
|
30266
30423
|
}
|
|
30267
30424
|
if (ok === 0) process.exit(1);
|
|
30268
|
-
console.log(
|
|
30425
|
+
console.log(chalk2.dim(name.trim() ? `Named "${name.trim()}" on ${ok}/${targets.length} server(s).` : `Cleared name on ${ok}/${targets.length} server(s).`));
|
|
30269
30426
|
});
|
|
30270
30427
|
program.command("sync").description("Push redacted conversations to the configured server (secrets always masked). Bare `sync` is incremental (watermark-based); flags force an explicit window.").option("--since-hours <n>", "Only sync sessions modified in the last N hours").option("--limit <n>", "Max sessions to sync").option("--full", "Ignore the watermark and push everything").option("--paths-cleartext", "Send project paths in cleartext (self-host only; default hashes them)").option("--throttle <ms>", "Pause between upload batches in ms (default: 1000, or 3000 with --full)").option("--prune", "After syncing, drop server-side session rows that have no content (ghost rows)").action(async (opts) => {
|
|
30271
30428
|
const { syncSessions: syncSessions2, syncIncremental: syncIncremental2, isSyncSkip: isSyncSkip2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
@@ -30274,17 +30431,17 @@ program.command("sync").description("Push redacted conversations to the configur
|
|
|
30274
30431
|
const r2 = await syncIncremental2();
|
|
30275
30432
|
if (isSyncSkip2(r2)) {
|
|
30276
30433
|
if (r2.skipped === "lock-held") {
|
|
30277
|
-
console.log(
|
|
30434
|
+
console.log(chalk2.dim("Another sync is already running (MCP tick or watch daemon) \u2014 your data is on its way."));
|
|
30278
30435
|
return;
|
|
30279
30436
|
}
|
|
30280
30437
|
const msg = {
|
|
30281
30438
|
"no-credentials": "Not logged in \u2014 run `chat-recall login <server-url>` first.",
|
|
30282
30439
|
"paused": "Sync is paused in settings \u2014 re-run `chat-recall login <server-url>` to resume."
|
|
30283
30440
|
}[r2.skipped];
|
|
30284
|
-
console.error(
|
|
30441
|
+
console.error(chalk2.red(msg));
|
|
30285
30442
|
process.exit(1);
|
|
30286
30443
|
}
|
|
30287
|
-
console.log(
|
|
30444
|
+
console.log(chalk2.green(`\u2713 Synced ${r2.uploaded} session(s), ${r2.items} item(s)`) + chalk2.dim(` \u2014 ${r2.links} links, ${r2.derived} derived rows, ${r2.kgTriples} KG triples, ${r2.skipped} skipped, ${r2.redactions} secrets redacted, ${r2.findings} secret findings${r2.scanned ? ` (scanned ${r2.scanned} in ${r2.scanMs}ms)` : ""} (incremental)`));
|
|
30288
30445
|
return;
|
|
30289
30446
|
}
|
|
30290
30447
|
const sinceMs = opts.sinceHours ? Date.now() - Number(opts.sinceHours) * 36e5 : void 0;
|
|
@@ -30299,9 +30456,9 @@ program.command("sync").description("Push redacted conversations to the configur
|
|
|
30299
30456
|
throttleMs: opts.throttle !== void 0 ? Number(opts.throttle) : opts.full ? 3e3 : void 0,
|
|
30300
30457
|
prune: !!opts.prune
|
|
30301
30458
|
});
|
|
30302
|
-
console.log(
|
|
30459
|
+
console.log(chalk2.green(`\u2713 Synced ${r.uploaded} session(s), ${r.items} item(s)`) + chalk2.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)` : ""}`));
|
|
30303
30460
|
} catch (err) {
|
|
30304
|
-
console.error(
|
|
30461
|
+
console.error(chalk2.red("sync failed:"), err instanceof Error ? err.message : err);
|
|
30305
30462
|
process.exit(1);
|
|
30306
30463
|
}
|
|
30307
30464
|
});
|
|
@@ -30310,11 +30467,11 @@ program.command("reconcile").description("Reconcile DERIVED FIELDS (e.g. native
|
|
|
30310
30467
|
try {
|
|
30311
30468
|
const r = await reconcileFields2({ force: !!opts.force });
|
|
30312
30469
|
const per = Object.entries(r.perTarget).map(([s, v]) => `${s}: ${v.pushed} pushed`).join(", ");
|
|
30313
|
-
console.log(
|
|
30470
|
+
console.log(chalk2.green(`\u2713 Reconciled derived fields`) + chalk2.dim(
|
|
30314
30471
|
` \u2014 ${r.sessions} sessions, ${r.scanned} scanned, ${r.pushed} pushed, ${r.absent} absent${per ? ` (${per})` : ""}`
|
|
30315
30472
|
));
|
|
30316
30473
|
} catch (err) {
|
|
30317
|
-
console.error(
|
|
30474
|
+
console.error(chalk2.red("reconcile failed:"), err instanceof Error ? err.message : err);
|
|
30318
30475
|
process.exit(1);
|
|
30319
30476
|
}
|
|
30320
30477
|
});
|
|
@@ -30324,7 +30481,7 @@ program.command("update").description("Update this CLI now from the server you s
|
|
|
30324
30481
|
const { cliVersion: cliVersion3 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
30325
30482
|
const targets = loadAllCredentials();
|
|
30326
30483
|
if (!targets.length) {
|
|
30327
|
-
console.error(
|
|
30484
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30328
30485
|
process.exit(1);
|
|
30329
30486
|
}
|
|
30330
30487
|
let installFailed = false;
|
|
@@ -30333,17 +30490,17 @@ program.command("update").description("Update this CLI now from the server you s
|
|
|
30333
30490
|
const headers = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
30334
30491
|
const r = await runAutoUpdate2(base2, headers, cliVersion3());
|
|
30335
30492
|
if (r.updated) {
|
|
30336
|
-
console.log(
|
|
30493
|
+
console.log(chalk2.green(`\u2713 ${r.reason}`) + chalk2.dim(` (${base2})`));
|
|
30337
30494
|
continue;
|
|
30338
30495
|
}
|
|
30339
30496
|
const benign = /already current|no CLI release|disabled/.test(r.reason);
|
|
30340
30497
|
if (/install failed|download failed|checksum/.test(r.reason)) installFailed = true;
|
|
30341
|
-
console.log((benign ?
|
|
30498
|
+
console.log((benign ? chalk2.dim("\u2022 ") : chalk2.red("\u2717 ")) + r.reason + chalk2.dim(` (${base2})`));
|
|
30342
30499
|
}
|
|
30343
30500
|
await refreshUpdateCheck2(true);
|
|
30344
30501
|
if (installFailed) {
|
|
30345
|
-
console.log(
|
|
30346
|
-
console.log(
|
|
30502
|
+
console.log(chalk2.dim("\nThe update was available but did not install \u2014 usually npm cannot write the global prefix. Retry with:"));
|
|
30503
|
+
console.log(chalk2.dim(` npm install -g chat-recall (or re-run the installer: curl -fsSL ${targets[0].serverUrl.replace(/\/+$/, "")}/install | sh)`));
|
|
30347
30504
|
}
|
|
30348
30505
|
});
|
|
30349
30506
|
program.hook("preAction", () => {
|