chat-recall 0.5.29 → 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 +658 -529
- 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 +59 -23
- 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 +68 -31
- package/package.json +1 -1
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
|
}
|
|
@@ -19750,7 +19750,7 @@ var init_http = __esm({
|
|
|
19750
19750
|
CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
19751
19751
|
} catch {
|
|
19752
19752
|
try {
|
|
19753
|
-
if (true) CLI_VERSION = "0.5.
|
|
19753
|
+
if (true) CLI_VERSION = "0.5.30";
|
|
19754
19754
|
} catch {
|
|
19755
19755
|
}
|
|
19756
19756
|
}
|
|
@@ -20262,7 +20262,7 @@ var init_telemetry = __esm({
|
|
|
20262
20262
|
init_sync_client();
|
|
20263
20263
|
cliVersion2 = () => {
|
|
20264
20264
|
try {
|
|
20265
|
-
return true ? "0.5.
|
|
20265
|
+
return true ? "0.5.30" : "dev";
|
|
20266
20266
|
} catch {
|
|
20267
20267
|
return "dev";
|
|
20268
20268
|
}
|
|
@@ -20599,12 +20599,13 @@ __export(auto_update_exports, {
|
|
|
20599
20599
|
planAutoUpdate: () => planAutoUpdate,
|
|
20600
20600
|
runAutoUpdate: () => runAutoUpdate,
|
|
20601
20601
|
runningPrefix: () => runningPrefix,
|
|
20602
|
+
sweepStaleStaging: () => sweepStaleStaging,
|
|
20602
20603
|
tarballUrl: () => tarballUrl
|
|
20603
20604
|
});
|
|
20604
20605
|
import { execSync as execSync2 } from "node:child_process";
|
|
20605
20606
|
import { readFileSync as readFileSync36 } from "node:fs";
|
|
20606
20607
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20607
|
-
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";
|
|
20608
20609
|
import { join as join43, dirname as dirname18, sep as sep2 } from "node:path";
|
|
20609
20610
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
20610
20611
|
import { createHash as createHash12 } from "node:crypto";
|
|
@@ -20643,6 +20644,31 @@ function planAutoUpdate(base2, caps, ownVersion3, flag) {
|
|
|
20643
20644
|
if (compareVersions(rel.version, ownVersion3) <= 0) return { update: false, reason: `already current (own ${ownVersion3} >= server ${rel.version})` };
|
|
20644
20645
|
return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base2), sha256: rel.sha256, from: ownVersion3, to: rel.version };
|
|
20645
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
|
+
}
|
|
20646
20672
|
async function executeAutoUpdate(plan, deps) {
|
|
20647
20673
|
if (!plan.update || !plan.url || !plan.sha256) return { updated: false, reason: plan.reason };
|
|
20648
20674
|
let buf;
|
|
@@ -20654,13 +20680,21 @@ async function executeAutoUpdate(plan, deps) {
|
|
|
20654
20680
|
const got = createHash12("sha256").update(buf).digest("hex");
|
|
20655
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` };
|
|
20656
20682
|
const dir = mkdtempSync3(join43(tmpdir2(), "cr-update-"));
|
|
20683
|
+
const discardStaging = () => {
|
|
20684
|
+
try {
|
|
20685
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
20686
|
+
} catch {
|
|
20687
|
+
}
|
|
20688
|
+
};
|
|
20657
20689
|
const tgz = join43(dir, "chat-recall.tgz");
|
|
20658
20690
|
writeFileSync8(tgz, buf);
|
|
20659
20691
|
try {
|
|
20660
20692
|
deps.install(tgz);
|
|
20661
20693
|
} catch (e) {
|
|
20694
|
+
discardStaging();
|
|
20662
20695
|
return { updated: false, reason: `install failed: ${e instanceof Error ? e.message : e}` };
|
|
20663
20696
|
}
|
|
20697
|
+
discardStaging();
|
|
20664
20698
|
const landed = (deps.verify ?? installedVersion)();
|
|
20665
20699
|
if (landed && plan.to && compareVersions(landed, plan.to) < 0) {
|
|
20666
20700
|
return {
|
|
@@ -20767,7 +20801,7 @@ __export(sync_client_exports, {
|
|
|
20767
20801
|
syncSessions: () => syncSessions
|
|
20768
20802
|
});
|
|
20769
20803
|
import { join as join44, dirname as dirname19, basename as basename22 } from "node:path";
|
|
20770
|
-
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";
|
|
20771
20805
|
import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
|
|
20772
20806
|
import { createHash as createHash13 } from "node:crypto";
|
|
20773
20807
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -21139,7 +21173,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21139
21173
|
let reaped = 0;
|
|
21140
21174
|
let names;
|
|
21141
21175
|
try {
|
|
21142
|
-
names =
|
|
21176
|
+
names = readdirSync33(dir);
|
|
21143
21177
|
} catch {
|
|
21144
21178
|
return 0;
|
|
21145
21179
|
}
|
|
@@ -21147,8 +21181,8 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21147
21181
|
if (!name.startsWith("cr-batchscan-")) continue;
|
|
21148
21182
|
const p = join44(dir, name);
|
|
21149
21183
|
try {
|
|
21150
|
-
if (now -
|
|
21151
|
-
|
|
21184
|
+
if (now - statSync39(p).mtimeMs < maxAgeMs) continue;
|
|
21185
|
+
rmSync6(p, { recursive: true, force: true });
|
|
21152
21186
|
reaped++;
|
|
21153
21187
|
} catch {
|
|
21154
21188
|
}
|
|
@@ -21180,7 +21214,7 @@ function batchScanExternal(items, opts = {}) {
|
|
|
21180
21214
|
slices++;
|
|
21181
21215
|
}
|
|
21182
21216
|
try {
|
|
21183
|
-
|
|
21217
|
+
rmSync6(scanDir, { recursive: true, force: true });
|
|
21184
21218
|
} catch {
|
|
21185
21219
|
}
|
|
21186
21220
|
safeToId = /* @__PURE__ */ new Map();
|
|
@@ -21205,7 +21239,7 @@ function batchScanExternal(items, opts = {}) {
|
|
|
21205
21239
|
flushSlice();
|
|
21206
21240
|
} finally {
|
|
21207
21241
|
try {
|
|
21208
|
-
|
|
21242
|
+
rmSync6(scanDir, { recursive: true, force: true });
|
|
21209
21243
|
} catch {
|
|
21210
21244
|
}
|
|
21211
21245
|
}
|
|
@@ -21338,9 +21372,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21338
21372
|
setServerIngestConcurrency(base2, Math.floor(adv));
|
|
21339
21373
|
}
|
|
21340
21374
|
if (body2.cli && body2.cli.version) {
|
|
21341
|
-
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));
|
|
21342
21376
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21343
21377
|
const plan = planAutoUpdate2(base2, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21378
|
+
sweepStaleStaging2();
|
|
21344
21379
|
if (plan.update) {
|
|
21345
21380
|
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21346
21381
|
});
|
|
@@ -21466,9 +21501,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21466
21501
|
if (!res.ok) throw new Error(`append sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
21467
21502
|
const body = await res.json().catch(() => ({}));
|
|
21468
21503
|
if (body.cli && body.cli.version) {
|
|
21469
|
-
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));
|
|
21470
21505
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21471
21506
|
const plan = planAutoUpdate2(base2, { cli: body.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21507
|
+
sweepStaleStaging2();
|
|
21472
21508
|
if (plan.update) {
|
|
21473
21509
|
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21474
21510
|
});
|
|
@@ -22270,7 +22306,7 @@ var init_sync_client = __esm({
|
|
|
22270
22306
|
ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22271
22307
|
} catch {
|
|
22272
22308
|
try {
|
|
22273
|
-
if (true) ownVersion = "0.5.
|
|
22309
|
+
if (true) ownVersion = "0.5.30";
|
|
22274
22310
|
} catch {
|
|
22275
22311
|
}
|
|
22276
22312
|
}
|
|
@@ -22384,11 +22420,11 @@ var init_update_notice = __esm({
|
|
|
22384
22420
|
});
|
|
22385
22421
|
|
|
22386
22422
|
// ../engine/dist/core/which.js
|
|
22387
|
-
import { existsSync as existsSync43, statSync as
|
|
22423
|
+
import { existsSync as existsSync43, statSync as statSync40, accessSync, constants } from "node:fs";
|
|
22388
22424
|
import { join as join46, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep3 } from "node:path";
|
|
22389
22425
|
function executable(p) {
|
|
22390
22426
|
try {
|
|
22391
|
-
if (!
|
|
22427
|
+
if (!statSync40(p).isFile())
|
|
22392
22428
|
return false;
|
|
22393
22429
|
} catch {
|
|
22394
22430
|
return false;
|
|
@@ -22524,7 +22560,7 @@ function registerMcpServersJson(target, path, spec) {
|
|
|
22524
22560
|
entry.alwaysAllow = existing?.alwaysAllow ?? spec.alwaysAllow;
|
|
22525
22561
|
if (spec.env)
|
|
22526
22562
|
entry.env = { ...existing?.env, ...spec.env };
|
|
22527
|
-
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);
|
|
22528
22564
|
if (isCurrent)
|
|
22529
22565
|
return { id: target.id, label: target.label, path, state: "current" };
|
|
22530
22566
|
servers[MCP_SERVER_NAME] = entry;
|
|
@@ -22551,7 +22587,7 @@ function registerOpencodeJson(path, spec) {
|
|
|
22551
22587
|
const entry = { type: "local", command: argv, enabled: true };
|
|
22552
22588
|
if (spec.env)
|
|
22553
22589
|
entry.environment = { ...existing?.environment, ...spec.env };
|
|
22554
|
-
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);
|
|
22555
22591
|
if (isCurrent)
|
|
22556
22592
|
return { id, label, path, state: "current" };
|
|
22557
22593
|
mcp[MCP_SERVER_NAME] = entry;
|
|
@@ -22730,7 +22766,7 @@ __export(companions_exports, {
|
|
|
22730
22766
|
uninstallCodeindex: () => uninstallCodeindex,
|
|
22731
22767
|
unregisterCodeindexMcp: () => unregisterCodeindexMcp
|
|
22732
22768
|
});
|
|
22733
|
-
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";
|
|
22734
22770
|
import { homedir as homedir16 } from "node:os";
|
|
22735
22771
|
import { join as join48 } from "node:path";
|
|
22736
22772
|
import { execSync as execSync3 } from "node:child_process";
|
|
@@ -22781,7 +22817,7 @@ function checkCodeindexStatus() {
|
|
|
22781
22817
|
status.installed = true;
|
|
22782
22818
|
status.path = foundPath;
|
|
22783
22819
|
try {
|
|
22784
|
-
status.size =
|
|
22820
|
+
status.size = statSync41(foundPath).size;
|
|
22785
22821
|
} catch {
|
|
22786
22822
|
}
|
|
22787
22823
|
try {
|
|
@@ -22931,7 +22967,7 @@ __export(install_skills_exports, {
|
|
|
22931
22967
|
skillsSourceDir: () => skillsSourceDir,
|
|
22932
22968
|
uninstallSkills: () => uninstallSkills
|
|
22933
22969
|
});
|
|
22934
|
-
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";
|
|
22935
22971
|
import { createHash as createHash15 } from "crypto";
|
|
22936
22972
|
import { join as join49, basename as basename23 } from "path";
|
|
22937
22973
|
function skillsSourceDir() {
|
|
@@ -22957,7 +22993,7 @@ function skillsFingerprint() {
|
|
|
22957
22993
|
}
|
|
22958
22994
|
function filesUnder(dir, prefix = "") {
|
|
22959
22995
|
const out = [];
|
|
22960
|
-
for (const e of
|
|
22996
|
+
for (const e of readdirSync34(dir, { withFileTypes: true })) {
|
|
22961
22997
|
if (e.name === MARKER) continue;
|
|
22962
22998
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
22963
22999
|
if (e.isDirectory()) out.push(...filesUnder(join49(dir, e.name), rel));
|
|
@@ -22971,7 +23007,7 @@ function skillStamp() {
|
|
|
22971
23007
|
function bundledSkillNames() {
|
|
22972
23008
|
const src = skillsSourceDir();
|
|
22973
23009
|
if (!existsSync46(src)) return [];
|
|
22974
|
-
return
|
|
23010
|
+
return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
22975
23011
|
}
|
|
22976
23012
|
function realPathOrSelf(p) {
|
|
22977
23013
|
try {
|
|
@@ -23044,7 +23080,7 @@ function installSkills(opts = {}) {
|
|
|
23044
23080
|
continue;
|
|
23045
23081
|
}
|
|
23046
23082
|
mkdirSync12(t.dir, { recursive: true });
|
|
23047
|
-
|
|
23083
|
+
rmSync7(dest, { recursive: true, force: true });
|
|
23048
23084
|
cpSync(join49(src, name), dest, { recursive: true });
|
|
23049
23085
|
writeFileSync13(join49(dest, MARKER), `${stamp}
|
|
23050
23086
|
`);
|
|
@@ -23063,11 +23099,11 @@ function uninstallSkills() {
|
|
|
23063
23099
|
perTarget.push({ id: t.id, label: t.label, removed });
|
|
23064
23100
|
continue;
|
|
23065
23101
|
}
|
|
23066
|
-
for (const entry of
|
|
23102
|
+
for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
|
|
23067
23103
|
if (!entry.isDirectory()) continue;
|
|
23068
23104
|
const dir = join49(t.dir, entry.name);
|
|
23069
23105
|
if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
|
|
23070
|
-
|
|
23106
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
23071
23107
|
removed.push(entry.name);
|
|
23072
23108
|
}
|
|
23073
23109
|
}
|
|
@@ -23748,11 +23784,11 @@ __export(toolkit_pull_exports, {
|
|
|
23748
23784
|
planPull: () => planPull,
|
|
23749
23785
|
portableCommand: () => portableCommand
|
|
23750
23786
|
});
|
|
23751
|
-
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";
|
|
23752
23788
|
import { dirname as dirname22, join as join52, basename as basename26, isAbsolute as isAbsolute3 } from "node:path";
|
|
23753
23789
|
function isExecutableFile(p) {
|
|
23754
23790
|
try {
|
|
23755
|
-
return
|
|
23791
|
+
return statSync43(p).isFile();
|
|
23756
23792
|
} catch {
|
|
23757
23793
|
return false;
|
|
23758
23794
|
}
|
|
@@ -25673,7 +25709,7 @@ __export(project_tasks_exports, {
|
|
|
25673
25709
|
pushProjectTaskStatuses: () => pushProjectTaskStatuses,
|
|
25674
25710
|
renderTeamTasksMd: () => renderTeamTasksMd
|
|
25675
25711
|
});
|
|
25676
|
-
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";
|
|
25677
25713
|
import { join as join54, dirname as dirname23 } from "node:path";
|
|
25678
25714
|
import { homedir as homedir19 } from "node:os";
|
|
25679
25715
|
function isTeamTaskStatus(v) {
|
|
@@ -25762,7 +25798,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
|
|
|
25762
25798
|
if (!existsSync50(file)) continue;
|
|
25763
25799
|
let mtime;
|
|
25764
25800
|
try {
|
|
25765
|
-
mtime =
|
|
25801
|
+
mtime = statSync44(file).mtimeMs;
|
|
25766
25802
|
} catch {
|
|
25767
25803
|
continue;
|
|
25768
25804
|
}
|
|
@@ -25972,8 +26008,9 @@ async function drainSyncIntents(opts = {}) {
|
|
|
25972
26008
|
const data = await res.json();
|
|
25973
26009
|
pending = data.intents || [];
|
|
25974
26010
|
if (data.cli && data.cli.version) {
|
|
25975
|
-
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));
|
|
25976
26012
|
const plan = planAutoUpdate2(base2, { cli: data.cli }, ownVersion2, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
26013
|
+
sweepStaleStaging2();
|
|
25977
26014
|
if (plan.update) {
|
|
25978
26015
|
void runAutoUpdate2(base2, authHeaders, ownVersion2).catch(() => {
|
|
25979
26016
|
});
|
|
@@ -26027,7 +26064,7 @@ var init_intent_drain = __esm({
|
|
|
26027
26064
|
ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
26028
26065
|
} catch {
|
|
26029
26066
|
try {
|
|
26030
|
-
if (true) ownVersion2 = "0.5.
|
|
26067
|
+
if (true) ownVersion2 = "0.5.30";
|
|
26031
26068
|
} catch {
|
|
26032
26069
|
}
|
|
26033
26070
|
}
|
|
@@ -26228,7 +26265,7 @@ var team_merge_exports = {};
|
|
|
26228
26265
|
__export(team_merge_exports, {
|
|
26229
26266
|
mergePullResult: () => mergePullResult
|
|
26230
26267
|
});
|
|
26231
|
-
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";
|
|
26232
26269
|
import { dirname as dirname25, join as join56 } from "path";
|
|
26233
26270
|
import { createHash as createHash18 } from "crypto";
|
|
26234
26271
|
function installPathFor(art, tool) {
|
|
@@ -26345,7 +26382,7 @@ async function mergePullResult(opts) {
|
|
|
26345
26382
|
continue;
|
|
26346
26383
|
try {
|
|
26347
26384
|
if (existsSync52(path))
|
|
26348
|
-
|
|
26385
|
+
rmSync8(path, { force: true });
|
|
26349
26386
|
await teamInstallsForget(id, row.tool);
|
|
26350
26387
|
report.removed.push({ artifactId: id, path });
|
|
26351
26388
|
} catch (err) {
|
|
@@ -26364,7 +26401,7 @@ function readFileIfExists(path) {
|
|
|
26364
26401
|
try {
|
|
26365
26402
|
if (!existsSync52(path))
|
|
26366
26403
|
return null;
|
|
26367
|
-
const s =
|
|
26404
|
+
const s = statSync45(path);
|
|
26368
26405
|
if (!s.isFile())
|
|
26369
26406
|
return null;
|
|
26370
26407
|
return readFileSync47(path);
|
|
@@ -26449,7 +26486,7 @@ var init_vault_crypto = __esm({
|
|
|
26449
26486
|
});
|
|
26450
26487
|
|
|
26451
26488
|
// ../engine/dist/core/vault-source.js
|
|
26452
|
-
import { existsSync as existsSync53, readdirSync as
|
|
26489
|
+
import { existsSync as existsSync53, readdirSync as readdirSync35, statSync as statSync46 } from "fs";
|
|
26453
26490
|
import { extname, join as join57 } from "path";
|
|
26454
26491
|
function* walkVaultSources() {
|
|
26455
26492
|
const t = loadSettings().team.vault;
|
|
@@ -26467,7 +26504,7 @@ function* walkVaultSources() {
|
|
|
26467
26504
|
}
|
|
26468
26505
|
function safeStat(path) {
|
|
26469
26506
|
try {
|
|
26470
|
-
const s =
|
|
26507
|
+
const s = statSync46(path);
|
|
26471
26508
|
if (!s.isFile())
|
|
26472
26509
|
return null;
|
|
26473
26510
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
@@ -26481,7 +26518,7 @@ function* walkClaude() {
|
|
|
26481
26518
|
return;
|
|
26482
26519
|
let projectDirs = [];
|
|
26483
26520
|
try {
|
|
26484
|
-
projectDirs =
|
|
26521
|
+
projectDirs = readdirSync35(root);
|
|
26485
26522
|
} catch {
|
|
26486
26523
|
return;
|
|
26487
26524
|
}
|
|
@@ -26492,7 +26529,7 @@ function* walkClaude() {
|
|
|
26492
26529
|
const projDir = join57(root, proj);
|
|
26493
26530
|
let entries = [];
|
|
26494
26531
|
try {
|
|
26495
|
-
entries =
|
|
26532
|
+
entries = readdirSync35(projDir);
|
|
26496
26533
|
} catch {
|
|
26497
26534
|
continue;
|
|
26498
26535
|
}
|
|
@@ -26514,7 +26551,7 @@ function* walkGemini() {
|
|
|
26514
26551
|
return;
|
|
26515
26552
|
let hashDirs = [];
|
|
26516
26553
|
try {
|
|
26517
|
-
hashDirs =
|
|
26554
|
+
hashDirs = readdirSync35(tmp);
|
|
26518
26555
|
} catch {
|
|
26519
26556
|
return;
|
|
26520
26557
|
}
|
|
@@ -26524,7 +26561,7 @@ function* walkGemini() {
|
|
|
26524
26561
|
continue;
|
|
26525
26562
|
let entries = [];
|
|
26526
26563
|
try {
|
|
26527
|
-
entries =
|
|
26564
|
+
entries = readdirSync35(chatsDir);
|
|
26528
26565
|
} catch {
|
|
26529
26566
|
continue;
|
|
26530
26567
|
}
|
|
@@ -26570,7 +26607,7 @@ function* walkCodex() {
|
|
|
26570
26607
|
const dir = stack.pop();
|
|
26571
26608
|
let entries = [];
|
|
26572
26609
|
try {
|
|
26573
|
-
entries =
|
|
26610
|
+
entries = readdirSync35(dir, { withFileTypes: true });
|
|
26574
26611
|
} catch {
|
|
26575
26612
|
continue;
|
|
26576
26613
|
}
|
|
@@ -26596,14 +26633,14 @@ function* walkAgy() {
|
|
|
26596
26633
|
return;
|
|
26597
26634
|
let sessionDirs = [];
|
|
26598
26635
|
try {
|
|
26599
|
-
sessionDirs =
|
|
26636
|
+
sessionDirs = readdirSync35(brainDir);
|
|
26600
26637
|
} catch {
|
|
26601
26638
|
return;
|
|
26602
26639
|
}
|
|
26603
26640
|
for (const rawId of sessionDirs) {
|
|
26604
26641
|
const sessionPath = join57(brainDir, rawId);
|
|
26605
26642
|
try {
|
|
26606
|
-
if (!
|
|
26643
|
+
if (!statSync46(sessionPath).isDirectory())
|
|
26607
26644
|
continue;
|
|
26608
26645
|
} catch {
|
|
26609
26646
|
continue;
|
|
@@ -27084,7 +27121,7 @@ __export(service_installer_exports, {
|
|
|
27084
27121
|
uninstallService: () => uninstallService
|
|
27085
27122
|
});
|
|
27086
27123
|
import { execSync as execSync5 } from "child_process";
|
|
27087
|
-
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";
|
|
27088
27125
|
import { homedir as homedir21 } from "os";
|
|
27089
27126
|
import { join as join59 } from "path";
|
|
27090
27127
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -27240,7 +27277,7 @@ function uninstallService() {
|
|
|
27240
27277
|
}
|
|
27241
27278
|
const unitPath = join59(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
27242
27279
|
if (existsSync55(unitPath)) {
|
|
27243
|
-
|
|
27280
|
+
rmSync9(unitPath, { force: true });
|
|
27244
27281
|
removed = true;
|
|
27245
27282
|
}
|
|
27246
27283
|
try {
|
|
@@ -27257,7 +27294,7 @@ function uninstallService() {
|
|
|
27257
27294
|
}
|
|
27258
27295
|
let removed = false;
|
|
27259
27296
|
if (existsSync55(plistPath)) {
|
|
27260
|
-
|
|
27297
|
+
rmSync9(plistPath, { force: true });
|
|
27261
27298
|
removed = true;
|
|
27262
27299
|
}
|
|
27263
27300
|
return removed;
|
|
@@ -27534,7 +27571,7 @@ init_tool_paths();
|
|
|
27534
27571
|
init_project_resolver();
|
|
27535
27572
|
import { config } from "dotenv";
|
|
27536
27573
|
import { Command } from "commander";
|
|
27537
|
-
import
|
|
27574
|
+
import chalk2 from "chalk";
|
|
27538
27575
|
import { existsSync as existsSync56, readFileSync as readFileSync49, writeFileSync as writeFileSync21, realpathSync as realpathSync5 } from "fs";
|
|
27539
27576
|
import { homedir as homedir22 } from "os";
|
|
27540
27577
|
import { join as join60, resolve as resolve2, dirname as dirname27, basename as basename29 } from "path";
|
|
@@ -27595,16 +27632,64 @@ function openBrowser(url) {
|
|
|
27595
27632
|
init_which();
|
|
27596
27633
|
init_collector_health();
|
|
27597
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
|
|
27598
27683
|
function matchLabel(tier) {
|
|
27599
27684
|
switch (tier) {
|
|
27600
27685
|
case "strong":
|
|
27601
|
-
return
|
|
27686
|
+
return chalk2.green("strong");
|
|
27602
27687
|
case "good":
|
|
27603
|
-
return
|
|
27688
|
+
return chalk2.yellow("good");
|
|
27604
27689
|
case "weak":
|
|
27605
|
-
return
|
|
27690
|
+
return chalk2.dim("weak");
|
|
27606
27691
|
default:
|
|
27607
|
-
return
|
|
27692
|
+
return chalk2.dim("unranked");
|
|
27608
27693
|
}
|
|
27609
27694
|
}
|
|
27610
27695
|
config({ quiet: true });
|
|
@@ -27639,13 +27724,13 @@ function describeProbeError(err) {
|
|
|
27639
27724
|
}
|
|
27640
27725
|
function failLogin(opts, message) {
|
|
27641
27726
|
if (opts.fromInit) throw new Error(message);
|
|
27642
|
-
console.error(
|
|
27727
|
+
console.error(chalk2.red("login failed:"), message);
|
|
27643
27728
|
process.exit(1);
|
|
27644
27729
|
}
|
|
27645
27730
|
function requireTarget() {
|
|
27646
27731
|
const t = firstTarget();
|
|
27647
27732
|
if (!t) {
|
|
27648
|
-
console.error(
|
|
27733
|
+
console.error(chalk2.red("Not logged in."), "Run", chalk2.bold("chat-recall login <server-url>"), "first.");
|
|
27649
27734
|
process.exit(1);
|
|
27650
27735
|
}
|
|
27651
27736
|
return t;
|
|
@@ -27803,7 +27888,7 @@ var program = new Command();
|
|
|
27803
27888
|
program.name("chat-recall").description("Semantic search for Claude Code sessions - recall and resume past conversations").version(pkgVersion);
|
|
27804
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) => {
|
|
27805
27890
|
try {
|
|
27806
|
-
console.log(
|
|
27891
|
+
console.log(chalk2.bold("chat-recall init") + chalk2.dim(` v${pkgVersion}`));
|
|
27807
27892
|
const { createInitReporter: createInitReporter2 } = await Promise.resolve().then(() => (init_init_report(), init_report_exports));
|
|
27808
27893
|
const rep = createInitReporter2({
|
|
27809
27894
|
tty: process.stdout.isTTY === true,
|
|
@@ -27827,8 +27912,8 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27827
27912
|
}
|
|
27828
27913
|
}
|
|
27829
27914
|
const foundTools = clis.filter((c) => c.available).map((c) => c.name.replace(/ (CLI|Code)$/, ""));
|
|
27830
|
-
if (!foundTools.length) rep.warn(
|
|
27831
|
-
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}`);
|
|
27832
27917
|
let target = firstTarget();
|
|
27833
27918
|
let deadTarget = false;
|
|
27834
27919
|
if (options.server) {
|
|
@@ -27852,24 +27937,24 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27852
27937
|
if (probeOk2(probe)) {
|
|
27853
27938
|
} else if (probe.kind === "moved" || probe.kind === "not_api") {
|
|
27854
27939
|
const retired = target.base;
|
|
27855
|
-
console.log(` ${
|
|
27940
|
+
console.log(` ${chalk2.yellow("The saved server no longer serves chat-recall")} \u2192 ${retired}`);
|
|
27856
27941
|
console.log(` ${probeAdvice2(probe, retired)}`);
|
|
27857
|
-
console.log(` Switching to ${
|
|
27942
|
+
console.log(` Switching to ${chalk2.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
|
|
27858
27943
|
try {
|
|
27859
27944
|
await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
|
|
27860
27945
|
const { removeCredentials: removeCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
27861
27946
|
if (removeCredentials2(retired)) {
|
|
27862
|
-
console.log(` ${
|
|
27947
|
+
console.log(` ${chalk2.dim(`Removed the retired target ${retired} (log in there again any time).`)}`);
|
|
27863
27948
|
}
|
|
27864
27949
|
target = firstTarget();
|
|
27865
|
-
if (target) console.log(` ${
|
|
27950
|
+
if (target) console.log(` ${chalk2.green("Connected")} \u2192 ${target.base}`);
|
|
27866
27951
|
} catch (e) {
|
|
27867
27952
|
serverBad(describeProbeError(e));
|
|
27868
|
-
console.log(` ${
|
|
27953
|
+
console.log(` ${chalk2.dim(`Run ${chalk2.bold(`chat-recall login ${DEFAULT_SERVER}`)} when ready, then ${chalk2.bold(`chat-recall logout ${retired}`)}.`)}`);
|
|
27869
27954
|
deadTarget = true;
|
|
27870
27955
|
}
|
|
27871
27956
|
} else {
|
|
27872
|
-
console.log(` ${
|
|
27957
|
+
console.log(` ${chalk2.yellow("Saved server did not answer")} \u2192 ${target.base}`);
|
|
27873
27958
|
console.log(` ${probeAdvice2(probe, target.base)}`);
|
|
27874
27959
|
}
|
|
27875
27960
|
} else if (target) {
|
|
@@ -27899,8 +27984,8 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27899
27984
|
}
|
|
27900
27985
|
for (const r of bad) {
|
|
27901
27986
|
const why = (r.error ?? "").replace(/^SyntaxError:\s*/, "").split("\n")[0];
|
|
27902
|
-
rep.warn(`${
|
|
27903
|
-
rep.warn(`${
|
|
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)`)}`);
|
|
27904
27989
|
}
|
|
27905
27990
|
if (process.env.CHAT_RECALL_VERBOSE) {
|
|
27906
27991
|
}
|
|
@@ -27932,14 +28017,14 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27932
28017
|
const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
|
|
27933
28018
|
}
|
|
27934
28019
|
} else if (!result.prebuiltAvailable) {
|
|
27935
|
-
rep.warn(`${
|
|
28020
|
+
rep.warn(`${chalk2.yellow("codeindex")}: ${result.unsupportedReason}`);
|
|
27936
28021
|
}
|
|
27937
28022
|
} catch (err) {
|
|
27938
28023
|
const msg = String(err);
|
|
27939
|
-
rep.warn(`${
|
|
28024
|
+
rep.warn(`${chalk2.yellow("codeindex install failed")}: ${msg}`);
|
|
27940
28025
|
if (msg.includes("404")) {
|
|
27941
|
-
console.log(
|
|
27942
|
-
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."));
|
|
27943
28028
|
}
|
|
27944
28029
|
}
|
|
27945
28030
|
} else {
|
|
@@ -27955,7 +28040,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27955
28040
|
}
|
|
27956
28041
|
const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
|
|
27957
28042
|
} catch (err) {
|
|
27958
|
-
rep.warn(`${
|
|
28043
|
+
rep.warn(`${chalk2.yellow("Skill install failed")} \u2014 ${err instanceof Error ? err.message : err}`);
|
|
27959
28044
|
}
|
|
27960
28045
|
if (!options.skipMcp && firstTarget()) {
|
|
27961
28046
|
try {
|
|
@@ -27993,28 +28078,30 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27993
28078
|
const written = report.outcomes.filter((o) => o.status === "written");
|
|
27994
28079
|
const present = report.outcomes.filter((o) => o.status === "present").length;
|
|
27995
28080
|
if (answered === 0) {
|
|
27996
|
-
rep.warn(
|
|
27997
|
-
for (const f of askFailed) console.log(` ${
|
|
27998
|
-
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.")}`);
|
|
27999
28084
|
} else if (written.length === 0 && present === 0) {
|
|
28000
28085
|
} else {
|
|
28001
|
-
const
|
|
28002
|
-
if (
|
|
28003
|
-
|
|
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);
|
|
28004
28091
|
}
|
|
28005
28092
|
}
|
|
28006
28093
|
} catch (err) {
|
|
28007
|
-
rep.warn(`${
|
|
28008
|
-
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.")}`);
|
|
28009
28096
|
}
|
|
28010
28097
|
}
|
|
28011
28098
|
if (deadTarget) {
|
|
28012
|
-
console.log(
|
|
28013
|
-
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")}.`)}`);
|
|
28014
28101
|
} else if (!options.skipSync && firstTarget()) {
|
|
28015
28102
|
const { watchFirstSync: watchFirstSync2, spawnDetachedSync: spawnDetachedSync2, handOffLine: handOffLine2, progressLine: renderProgress } = await Promise.resolve().then(() => (init_init_progress(), init_progress_exports));
|
|
28016
28103
|
const { readCollectorHealth: readCollectorHealth2 } = await Promise.resolve().then(() => (init_collector_health(), collector_health_exports));
|
|
28017
|
-
const label = (text) => `${
|
|
28104
|
+
const label = (text) => `${chalk2.bold("Indexing")} ${text}`;
|
|
28018
28105
|
const tty = process.stdout.isTTY === true;
|
|
28019
28106
|
const outcome = await watchFirstSync2({
|
|
28020
28107
|
startWorker: () => spawnDetachedSync2(),
|
|
@@ -28032,22 +28119,22 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
28032
28119
|
});
|
|
28033
28120
|
if (tty) process.stdout.write("\r");
|
|
28034
28121
|
if (outcome.finished) {
|
|
28035
|
-
console.log(label(
|
|
28122
|
+
console.log(label(chalk2.green(renderProgress(outcome.snapshot))));
|
|
28036
28123
|
} else {
|
|
28037
|
-
console.log(label(
|
|
28038
|
-
console.log(` ${
|
|
28124
|
+
console.log(label(chalk2.dim(handOffLine2(outcome.snapshot))));
|
|
28125
|
+
console.log(` ${chalk2.dim("watch it with: " + chalk2.bold("chat-recall status"))}`);
|
|
28039
28126
|
}
|
|
28040
28127
|
const failed = Object.entries(readCollectorHealth2()?.targets ?? {}).filter(([, t]) => (t.failures ?? 0) > 0 && t.lastError);
|
|
28041
28128
|
for (const [server, t] of failed) {
|
|
28042
|
-
console.log(` ${
|
|
28129
|
+
console.log(` ${chalk2.yellow("sync error")} ${chalk2.dim(server)}: ${t.lastError}`);
|
|
28043
28130
|
}
|
|
28044
28131
|
} else if (!options.skipSync) {
|
|
28045
28132
|
} else {
|
|
28046
28133
|
}
|
|
28047
28134
|
void options.skipService;
|
|
28048
|
-
rep.done(
|
|
28135
|
+
rep.done(chalk2.green.bold("\u2713 Done"));
|
|
28049
28136
|
} catch (err) {
|
|
28050
|
-
console.error(
|
|
28137
|
+
console.error(chalk2.red("Error:"), err);
|
|
28051
28138
|
process.exit(1);
|
|
28052
28139
|
}
|
|
28053
28140
|
});
|
|
@@ -28057,57 +28144,57 @@ program.command("install-skills").description("Install the chat-recall skills in
|
|
|
28057
28144
|
const r2 = mod.uninstallSkills();
|
|
28058
28145
|
const hits2 = r2.perTarget.filter((t) => t.removed.length > 0);
|
|
28059
28146
|
if (!hits2.length) {
|
|
28060
|
-
console.log(
|
|
28147
|
+
console.log(chalk2.dim("No chat-recall skills were installed."));
|
|
28061
28148
|
return;
|
|
28062
28149
|
}
|
|
28063
|
-
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)`);
|
|
28064
28151
|
return;
|
|
28065
28152
|
}
|
|
28066
28153
|
const r = mod.installSkills({ onlyAvailable: !opts.allTools });
|
|
28067
28154
|
const names = mod.bundledSkillNames();
|
|
28068
|
-
console.log(
|
|
28155
|
+
console.log(chalk2.bold(`Installing ${names.length} chat-recall skill(s) (v${r.version}):`), chalk2.dim(names.join(", ")));
|
|
28069
28156
|
const hits = r.perTarget.filter((t) => t.installed.length > 0);
|
|
28070
28157
|
if (!hits.length) {
|
|
28071
|
-
console.log(
|
|
28158
|
+
console.log(chalk2.yellow("No supported AI tools detected. Use --all-tools to install anyway."));
|
|
28072
28159
|
return;
|
|
28073
28160
|
}
|
|
28074
|
-
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)}`);
|
|
28075
28162
|
const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
|
|
28076
|
-
if (userOwned.length) console.log(
|
|
28163
|
+
if (userOwned.length) console.log(chalk2.yellow(`Kept your existing skill(s) of the same name: ${userOwned.join(", ")}`));
|
|
28077
28164
|
});
|
|
28078
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) => {
|
|
28079
28166
|
try {
|
|
28080
28167
|
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28081
28168
|
const r = await syncSessions2(options.force ? { useLedger: false } : {});
|
|
28082
|
-
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)` : ""}`));
|
|
28083
28170
|
} catch (err) {
|
|
28084
|
-
console.error(
|
|
28171
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28085
28172
|
process.exit(1);
|
|
28086
28173
|
}
|
|
28087
28174
|
});
|
|
28088
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) => {
|
|
28089
28176
|
try {
|
|
28090
|
-
const mode = options.apply ?
|
|
28177
|
+
const mode = options.apply ? chalk2.red("APPLY (writing to server)") : chalk2.cyan("DRY-RUN (read-only)");
|
|
28091
28178
|
const render = (r) => {
|
|
28092
28179
|
const head = `${r.sessionId.slice(0, 8)}\u2026`;
|
|
28093
28180
|
if (r.status === "no-archive") {
|
|
28094
|
-
console.log(
|
|
28181
|
+
console.log(chalk2.yellow(` ? ${head} no archive found`));
|
|
28095
28182
|
return;
|
|
28096
28183
|
}
|
|
28097
28184
|
if (r.status === "already-full") {
|
|
28098
|
-
console.log(
|
|
28185
|
+
console.log(chalk2.dim(` = ${head} already full (${r.fullestMessages} msgs)`));
|
|
28099
28186
|
return;
|
|
28100
28187
|
}
|
|
28101
28188
|
if (r.status === "error") {
|
|
28102
|
-
console.log(
|
|
28189
|
+
console.log(chalk2.red(` \u2717 ${head} ${r.note}`));
|
|
28103
28190
|
return;
|
|
28104
28191
|
}
|
|
28105
28192
|
const verb = r.status === "would-repair" ? "would recover" : "recovered";
|
|
28106
|
-
console.log(
|
|
28107
|
-
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}`));
|
|
28108
28195
|
};
|
|
28109
28196
|
if (options.all) {
|
|
28110
|
-
console.log(
|
|
28197
|
+
console.log(chalk2.bold(`repair --all \xB7 ${mode} \xB7 window ${options.sinceHours}h`));
|
|
28111
28198
|
const { repairAll: repairAll2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
|
|
28112
28199
|
const report = await repairAll2({
|
|
28113
28200
|
sinceHours: Number(options.sinceHours) || 72,
|
|
@@ -28118,24 +28205,24 @@ program.command("repair [sessionIds...]").description("Rebuild sessions an upstr
|
|
|
28118
28205
|
report.candidates.forEach(render);
|
|
28119
28206
|
const fixed2 = report.candidates.filter((r) => r.status === "repaired").length;
|
|
28120
28207
|
const would2 = report.candidates.filter((r) => r.status === "would-repair").length;
|
|
28121
|
-
console.log(
|
|
28208
|
+
console.log(chalk2.bold(
|
|
28122
28209
|
`Scanned ${report.scanned} session(s) on ${report.discoveryServer}; ` + (options.apply ? `repaired ${fixed2}.` : `${would2} damaged, would repair. Re-run with --apply to write.`)
|
|
28123
28210
|
));
|
|
28124
28211
|
return;
|
|
28125
28212
|
}
|
|
28126
28213
|
if (!sessionIds || sessionIds.length === 0) {
|
|
28127
|
-
console.error(
|
|
28214
|
+
console.error(chalk2.red("Give session ids, or use --all. e.g. `chat-recall repair <uuid>` or `chat-recall repair --all`."));
|
|
28128
28215
|
process.exit(1);
|
|
28129
28216
|
}
|
|
28130
|
-
console.log(
|
|
28217
|
+
console.log(chalk2.bold(`repair \xB7 ${mode}`));
|
|
28131
28218
|
const { repairSessions: repairSessions2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
|
|
28132
28219
|
const results = await repairSessions2(sessionIds, { dryRun: !options.apply, force: options.force, verbose: options.verbose, server: options.server });
|
|
28133
28220
|
results.forEach(render);
|
|
28134
28221
|
const fixed = results.filter((r) => r.status === "repaired").length;
|
|
28135
28222
|
const would = results.filter((r) => r.status === "would-repair").length;
|
|
28136
|
-
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.`));
|
|
28137
28224
|
} catch (err) {
|
|
28138
|
-
console.error(
|
|
28225
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28139
28226
|
process.exit(1);
|
|
28140
28227
|
}
|
|
28141
28228
|
});
|
|
@@ -28145,11 +28232,11 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28145
28232
|
const remote = await serverPost("/api/search", { query, topK, projectFilter: options.project });
|
|
28146
28233
|
const results = remote.results;
|
|
28147
28234
|
if (results.length === 0) {
|
|
28148
|
-
console.log(
|
|
28235
|
+
console.log(chalk2.yellow("No matching sessions found."));
|
|
28149
28236
|
process.exit(0);
|
|
28150
28237
|
}
|
|
28151
28238
|
console.log();
|
|
28152
|
-
console.log(`${
|
|
28239
|
+
console.log(`${chalk2.bold("Results for:")} "${query}"`);
|
|
28153
28240
|
console.log();
|
|
28154
28241
|
const tiers = tierAll(results);
|
|
28155
28242
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -28158,13 +28245,13 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28158
28245
|
if (projectPath.length > 50) projectPath = "..." + projectPath.slice(-47);
|
|
28159
28246
|
let title = (result.firstPrompt || "").replace(/\n/g, " ").trim();
|
|
28160
28247
|
if (title.length > 80) title = title.slice(0, 80) + "...";
|
|
28161
|
-
console.log(`${
|
|
28162
|
-
if (projectPath) console.log(` ${
|
|
28163
|
-
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])}`);
|
|
28164
28251
|
if (result.summary) {
|
|
28165
28252
|
let summary = result.summary.replace(/\n/g, " ").trim();
|
|
28166
28253
|
if (summary.length > 200) summary = summary.slice(0, 200) + "...";
|
|
28167
|
-
console.log(` ${
|
|
28254
|
+
console.log(` ${chalk2.yellow("Summary:")} ${summary}`);
|
|
28168
28255
|
}
|
|
28169
28256
|
const chunk = (result.matchedChunks || []).find(
|
|
28170
28257
|
(c) => c.chunkType !== "summary" && c.chunkType !== "first_prompt"
|
|
@@ -28173,16 +28260,16 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28173
28260
|
const label = chunk.chunkType === "assistant" ? "Discussed" : chunk.chunkType === "user_context" ? "Asked about" : chunk.chunkType === "tool_result" ? "Tool result" : "Context";
|
|
28174
28261
|
let text = chunk.text.replace(/\n/g, " ").trim();
|
|
28175
28262
|
if (text.length > 150) text = text.slice(0, 150) + "...";
|
|
28176
|
-
console.log(` ${
|
|
28263
|
+
console.log(` ${chalk2.magenta(label + ":")} ${text}`);
|
|
28177
28264
|
}
|
|
28178
28265
|
{
|
|
28179
28266
|
const rc = resumeCommandFor(result.sessionId);
|
|
28180
|
-
if (rc) console.log(` ${
|
|
28267
|
+
if (rc) console.log(` ${chalk2.green("Resume:")} ${rc}`);
|
|
28181
28268
|
}
|
|
28182
28269
|
console.log();
|
|
28183
28270
|
}
|
|
28184
28271
|
} catch (err) {
|
|
28185
|
-
console.error(
|
|
28272
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28186
28273
|
process.exit(1);
|
|
28187
28274
|
}
|
|
28188
28275
|
});
|
|
@@ -28190,7 +28277,7 @@ program.command("status").description("Show your server's sync status and statis
|
|
|
28190
28277
|
try {
|
|
28191
28278
|
const status = await serverGet("/api/status");
|
|
28192
28279
|
const sync = await serverGet("/api/status/sync");
|
|
28193
|
-
console.log(
|
|
28280
|
+
console.log(chalk2.bold("Chat-Recall Server Status"));
|
|
28194
28281
|
console.log();
|
|
28195
28282
|
console.log(`Synced sessions: ${sync.sessions}`);
|
|
28196
28283
|
console.log(`FTS5 chunks: ${status.totalChunks}`);
|
|
@@ -28202,34 +28289,34 @@ program.command("status").description("Show your server's sync status and statis
|
|
|
28202
28289
|
const types = Object.entries(sync.sourceTypes).filter(([, n]) => Number(n) > 0);
|
|
28203
28290
|
if (types.length > 0) {
|
|
28204
28291
|
console.log();
|
|
28205
|
-
console.log(
|
|
28292
|
+
console.log(chalk2.bold("By source type:"));
|
|
28206
28293
|
for (const [type, n] of types) console.log(` ${type}: ${n} items`);
|
|
28207
28294
|
}
|
|
28208
28295
|
const projects = Object.entries(status.projects || {}).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
28209
28296
|
if (projects.length > 0) {
|
|
28210
28297
|
console.log();
|
|
28211
|
-
console.log(
|
|
28298
|
+
console.log(chalk2.bold("Top projects:"));
|
|
28212
28299
|
for (const [proj, n] of projects) console.log(` ${proj}: ${n} sessions`);
|
|
28213
28300
|
}
|
|
28214
28301
|
} catch (err) {
|
|
28215
|
-
console.error(
|
|
28302
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28216
28303
|
process.exit(1);
|
|
28217
28304
|
}
|
|
28218
28305
|
});
|
|
28219
28306
|
program.command("optimize").description("Deprecated: storage is server-side now, nothing to optimize locally").action(() => {
|
|
28220
|
-
console.log(
|
|
28221
|
-
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."));
|
|
28222
28309
|
process.exit(0);
|
|
28223
28310
|
});
|
|
28224
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) => {
|
|
28225
28312
|
try {
|
|
28226
28313
|
const soft = await serverGetSoft(`/api/conversations/${encodeURIComponent(sessionId)}?limit=0`);
|
|
28227
28314
|
if (!soft.data || soft.data.messages.length === 0) {
|
|
28228
|
-
console.log(
|
|
28315
|
+
console.log(chalk2.yellow(soft.message || `Session not found: ${sessionId}`));
|
|
28229
28316
|
process.exit(soft.status === 404 ? 1 : 0);
|
|
28230
28317
|
}
|
|
28231
28318
|
const messagesList = soft.data.messages;
|
|
28232
|
-
console.log(
|
|
28319
|
+
console.log(chalk2.bold("Session:"), sessionId);
|
|
28233
28320
|
console.log();
|
|
28234
28321
|
const maxMessages = parseInt(options.messages, 10);
|
|
28235
28322
|
const displayMessages = options.full ? messagesList : messagesList.slice(0, maxMessages);
|
|
@@ -28237,22 +28324,22 @@ program.command("show <session_id>").description("Show conversation content from
|
|
|
28237
28324
|
let text = msg.content;
|
|
28238
28325
|
if (!options.full && text.length > 1e3) text = text.slice(0, 1e3) + "...";
|
|
28239
28326
|
if (msg.role === "user") {
|
|
28240
|
-
console.log(`${
|
|
28327
|
+
console.log(`${chalk2.bold.blue("User")} ${chalk2.dim(`(line ${msg.line})`)}`);
|
|
28241
28328
|
} else if (msg.role === "assistant") {
|
|
28242
|
-
console.log(`${
|
|
28329
|
+
console.log(`${chalk2.bold.green("Assistant")} ${chalk2.dim(`(line ${msg.line})`)}`);
|
|
28243
28330
|
} else {
|
|
28244
|
-
console.log(
|
|
28331
|
+
console.log(chalk2.bold.yellow(msg.role));
|
|
28245
28332
|
}
|
|
28246
28333
|
console.log(text);
|
|
28247
28334
|
console.log();
|
|
28248
28335
|
}
|
|
28249
|
-
console.log(
|
|
28336
|
+
console.log(chalk2.dim(`Showing ${displayMessages.length} of ${messagesList.length} messages. Use --full for the complete conversation.`));
|
|
28250
28337
|
{
|
|
28251
28338
|
const rc = resumeCommandFor(sessionId);
|
|
28252
|
-
if (rc) console.log(
|
|
28339
|
+
if (rc) console.log(chalk2.dim(`Resume: ${rc}`));
|
|
28253
28340
|
}
|
|
28254
28341
|
} catch (err) {
|
|
28255
|
-
console.error(
|
|
28342
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28256
28343
|
process.exit(1);
|
|
28257
28344
|
}
|
|
28258
28345
|
});
|
|
@@ -28261,9 +28348,9 @@ memory.command("index").description("Collect local sessions and ship them to you
|
|
|
28261
28348
|
try {
|
|
28262
28349
|
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28263
28350
|
const r = await syncSessions2(options.force ? { useLedger: false } : {});
|
|
28264
|
-
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)` : ""}`));
|
|
28265
28352
|
} catch (err) {
|
|
28266
|
-
console.error(
|
|
28353
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28267
28354
|
process.exit(1);
|
|
28268
28355
|
}
|
|
28269
28356
|
});
|
|
@@ -28274,55 +28361,55 @@ memory.command("search <query>").description("Search across all memory types on
|
|
|
28274
28361
|
const remote = await serverPost("/api/memory/search", { query, topK, sourceTypes });
|
|
28275
28362
|
const results = remote.results;
|
|
28276
28363
|
if (results.length === 0) {
|
|
28277
|
-
console.log(
|
|
28364
|
+
console.log(chalk2.yellow("No matching results found."));
|
|
28278
28365
|
process.exit(0);
|
|
28279
28366
|
}
|
|
28280
28367
|
console.log();
|
|
28281
|
-
console.log(`${
|
|
28368
|
+
console.log(`${chalk2.bold("Memory search:")} "${query}"`);
|
|
28282
28369
|
console.log();
|
|
28283
28370
|
const tiers = tierAll(results);
|
|
28284
28371
|
for (let i = 0; i < results.length; i++) {
|
|
28285
28372
|
const r = results[i];
|
|
28286
28373
|
const typeColor = {
|
|
28287
|
-
session:
|
|
28288
|
-
plan:
|
|
28289
|
-
task:
|
|
28290
|
-
claude_md:
|
|
28291
|
-
history:
|
|
28292
|
-
paste:
|
|
28293
|
-
diary:
|
|
28294
|
-
}[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;
|
|
28295
28382
|
const typeBadge = typeColor(`[${r.sourceType}]`);
|
|
28296
28383
|
let title = (r.title || "").replace(/\n/g, " ").trim();
|
|
28297
28384
|
if (title.length > 70) title = title.slice(0, 67) + "...";
|
|
28298
|
-
console.log(`${
|
|
28385
|
+
console.log(`${chalk2.bold.cyan(`#${i + 1}`)} ${typeBadge} ${title}`);
|
|
28299
28386
|
if (r.projectPath) {
|
|
28300
28387
|
let pp = r.projectPath;
|
|
28301
28388
|
if (pp.length > 50) pp = "..." + pp.slice(-47);
|
|
28302
|
-
console.log(` ${
|
|
28389
|
+
console.log(` ${chalk2.dim("Project:")} ${pp}`);
|
|
28303
28390
|
}
|
|
28304
|
-
const typeSuffix = r.chunkType ? ` ${
|
|
28305
|
-
console.log(` ${
|
|
28391
|
+
const typeSuffix = r.chunkType ? ` ${chalk2.dim("Type:")} ${r.chunkType}` : "";
|
|
28392
|
+
console.log(` ${chalk2.dim("Match:")} ${matchLabel(tiers[i])}${typeSuffix}`);
|
|
28306
28393
|
let preview = (r.text || "").replace(/\n/g, " ").trim();
|
|
28307
28394
|
if (preview.length > 150) preview = preview.slice(0, 147) + "...";
|
|
28308
28395
|
if (preview) console.log(` ${preview}`);
|
|
28309
28396
|
if (r.sourceType === "session") {
|
|
28310
28397
|
{
|
|
28311
28398
|
const rc = resumeCommandFor(r.itemId);
|
|
28312
|
-
if (rc) console.log(` ${
|
|
28399
|
+
if (rc) console.log(` ${chalk2.green("Resume:")} ${rc}`);
|
|
28313
28400
|
}
|
|
28314
28401
|
}
|
|
28315
28402
|
console.log();
|
|
28316
28403
|
}
|
|
28317
28404
|
} catch (err) {
|
|
28318
|
-
console.error(
|
|
28405
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28319
28406
|
process.exit(1);
|
|
28320
28407
|
}
|
|
28321
28408
|
});
|
|
28322
28409
|
memory.command("status").description("Show your server's memory statistics across all source types (requires login)").action(async () => {
|
|
28323
28410
|
try {
|
|
28324
28411
|
const status = await serverGet("/api/memory/status");
|
|
28325
|
-
console.log(
|
|
28412
|
+
console.log(chalk2.bold("Memory System Status"));
|
|
28326
28413
|
console.log();
|
|
28327
28414
|
console.log(`Total items: ${status.totalItems}`);
|
|
28328
28415
|
console.log(`FTS5 chunks: ${status.totalChunks}`);
|
|
@@ -28330,7 +28417,7 @@ memory.command("status").description("Show your server's memory statistics acros
|
|
|
28330
28417
|
const bySource = Object.entries(status.bySourceType || {});
|
|
28331
28418
|
if (bySource.length > 0) {
|
|
28332
28419
|
console.log();
|
|
28333
|
-
console.log(
|
|
28420
|
+
console.log(chalk2.bold("By source type:"));
|
|
28334
28421
|
for (const [type, data] of bySource) {
|
|
28335
28422
|
console.log(` ${type}: ${data.items} items, ${data.chunks} chunks`);
|
|
28336
28423
|
}
|
|
@@ -28338,14 +28425,14 @@ memory.command("status").description("Show your server's memory statistics acros
|
|
|
28338
28425
|
const byTool = Object.entries(status.bySourceAndTool || {});
|
|
28339
28426
|
if (byTool.length > 0) {
|
|
28340
28427
|
console.log();
|
|
28341
|
-
console.log(
|
|
28428
|
+
console.log(chalk2.bold("By source type and tool:"));
|
|
28342
28429
|
for (const [type, tools] of byTool) {
|
|
28343
28430
|
const parts = Object.entries(tools).map(([t, n]) => `${t}: ${n}`).join(", ");
|
|
28344
28431
|
console.log(` ${type}: ${parts}`);
|
|
28345
28432
|
}
|
|
28346
28433
|
}
|
|
28347
28434
|
} catch (err) {
|
|
28348
|
-
console.error(
|
|
28435
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28349
28436
|
process.exit(1);
|
|
28350
28437
|
}
|
|
28351
28438
|
});
|
|
@@ -28353,20 +28440,20 @@ memory.command("links <source_type> <item_id>").description("Show relationships
|
|
|
28353
28440
|
try {
|
|
28354
28441
|
const { links } = await serverGet(`/api/memory/links/${encodeURIComponent(sourceType)}/${encodeURIComponent(itemId)}`);
|
|
28355
28442
|
if (links.length === 0) {
|
|
28356
|
-
console.log(
|
|
28443
|
+
console.log(chalk2.yellow("No links found."));
|
|
28357
28444
|
return;
|
|
28358
28445
|
}
|
|
28359
|
-
console.log(
|
|
28446
|
+
console.log(chalk2.bold(`Links for ${sourceType}:${itemId}`));
|
|
28360
28447
|
console.log();
|
|
28361
28448
|
for (const link of links) {
|
|
28362
28449
|
const direction = link.source_id === itemId ? "->" : "<-";
|
|
28363
28450
|
const otherType = link.source_id === itemId ? link.target_type : link.source_type;
|
|
28364
28451
|
const otherId = link.source_id === itemId ? link.target_id : link.source_id;
|
|
28365
28452
|
const confidence = Math.round(link.confidence * 100);
|
|
28366
|
-
console.log(` ${direction} ${
|
|
28453
|
+
console.log(` ${direction} ${chalk2.bold(otherType)}:${otherId.slice(0, 20)} [${link.link_type}] (${confidence}%)`);
|
|
28367
28454
|
}
|
|
28368
28455
|
} catch (err) {
|
|
28369
|
-
console.error(
|
|
28456
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28370
28457
|
process.exit(1);
|
|
28371
28458
|
}
|
|
28372
28459
|
});
|
|
@@ -28378,27 +28465,27 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
|
|
|
28378
28465
|
identity = readFileSync49(identityFile, "utf-8").trim();
|
|
28379
28466
|
}
|
|
28380
28467
|
const lines = [
|
|
28381
|
-
|
|
28382
|
-
|
|
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"),
|
|
28383
28470
|
"",
|
|
28384
|
-
|
|
28471
|
+
chalk2.bold("## Identity"),
|
|
28385
28472
|
identity,
|
|
28386
28473
|
""
|
|
28387
28474
|
];
|
|
28388
28475
|
const qs = options.project ? `?project_filter=${encodeURIComponent(options.project)}` : "";
|
|
28389
28476
|
const wake = await serverGet(`/api/memory/wake-up${qs}`);
|
|
28390
28477
|
if (wake.highFacts.length > 0) {
|
|
28391
|
-
lines.push(
|
|
28478
|
+
lines.push(chalk2.bold("## High-Importance Facts"));
|
|
28392
28479
|
for (const fact of wake.highFacts) {
|
|
28393
28480
|
lines.push(` [${fact.type}] ${fact.text}`);
|
|
28394
28481
|
}
|
|
28395
28482
|
lines.push("");
|
|
28396
28483
|
}
|
|
28397
28484
|
if (wake.kg.facts.length > 0) {
|
|
28398
|
-
lines.push(
|
|
28485
|
+
lines.push(chalk2.bold("## Knowledge Graph"));
|
|
28399
28486
|
const { entities, current_facts } = wake.kg.stats;
|
|
28400
28487
|
if (entities !== void 0 || current_facts !== void 0) {
|
|
28401
|
-
lines.push(
|
|
28488
|
+
lines.push(chalk2.dim(`${entities ?? 0} entities, ${current_facts ?? 0} current facts`));
|
|
28402
28489
|
}
|
|
28403
28490
|
for (const fact of wake.kg.facts) {
|
|
28404
28491
|
lines.push(` ${fact.subject} \u2192 ${fact.predicate} \u2192 ${fact.object}`);
|
|
@@ -28407,32 +28494,75 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
|
|
|
28407
28494
|
}
|
|
28408
28495
|
console.log(lines.join("\n"));
|
|
28409
28496
|
} catch (err) {
|
|
28410
|
-
console.error(
|
|
28497
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28411
28498
|
process.exit(1);
|
|
28412
28499
|
}
|
|
28413
28500
|
});
|
|
28414
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) => {
|
|
28415
28502
|
if (!sessionId && !opts.latest) {
|
|
28416
|
-
console.error(
|
|
28503
|
+
console.error(chalk2.red("Pass a session id or --latest."));
|
|
28417
28504
|
process.exit(1);
|
|
28418
28505
|
}
|
|
28419
28506
|
const target = firstTarget();
|
|
28420
28507
|
if (!target) {
|
|
28421
|
-
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."));
|
|
28422
28509
|
return;
|
|
28423
28510
|
}
|
|
28424
28511
|
try {
|
|
28425
28512
|
const { runEscalate: runEscalate2 } = await Promise.resolve().then(() => (init_escalate(), escalate_exports));
|
|
28426
28513
|
await runEscalate2(
|
|
28427
|
-
{ get: serverGet, getSoft: serverGetSoft, post: serverPost, log: (line) => console.log(
|
|
28514
|
+
{ get: serverGet, getSoft: serverGetSoft, post: serverPost, log: (line) => console.log(chalk2.dim(line)) },
|
|
28428
28515
|
{ sessionId, latest: opts.latest, project: opts.project, dryRun: opts.dryRun, cwd: process.cwd() }
|
|
28429
28516
|
);
|
|
28430
28517
|
} catch (err) {
|
|
28431
|
-
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;
|
|
28432
28562
|
}
|
|
28433
28563
|
});
|
|
28434
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) => {
|
|
28435
|
-
const { existsSync: existsSync57, readFileSync: readFileSync50, statSync:
|
|
28565
|
+
const { existsSync: existsSync57, readFileSync: readFileSync50, statSync: statSync47 } = await import("fs");
|
|
28436
28566
|
const { execSync: execSync6 } = await import("child_process");
|
|
28437
28567
|
const rows = [];
|
|
28438
28568
|
const note = (ok, label, detail) => rows.push({ ok, label, detail });
|
|
@@ -28500,7 +28630,7 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28500
28630
|
note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
|
|
28501
28631
|
} else {
|
|
28502
28632
|
try {
|
|
28503
|
-
const mode =
|
|
28633
|
+
const mode = statSync47(credFile).mode & 511;
|
|
28504
28634
|
const isWin = process.platform === "win32";
|
|
28505
28635
|
const safe = isWin || (mode & 63) === 0;
|
|
28506
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"})`);
|
|
@@ -28638,11 +28768,11 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28638
28768
|
} else if (legacyPresent.length === 0) {
|
|
28639
28769
|
note(true, "Purge legacy index", "nothing to remove");
|
|
28640
28770
|
} else {
|
|
28641
|
-
const { rmSync:
|
|
28771
|
+
const { rmSync: rmSync10 } = await import("fs");
|
|
28642
28772
|
let removed = 0;
|
|
28643
28773
|
for (const p of legacyPresent) {
|
|
28644
28774
|
try {
|
|
28645
|
-
|
|
28775
|
+
rmSync10(p, { recursive: true, force: true });
|
|
28646
28776
|
removed++;
|
|
28647
28777
|
} catch {
|
|
28648
28778
|
}
|
|
@@ -28654,67 +28784,67 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28654
28784
|
} else {
|
|
28655
28785
|
note(true, "Legacy local index", "none (clean)");
|
|
28656
28786
|
}
|
|
28657
|
-
console.log(
|
|
28787
|
+
console.log(chalk2.bold("\nchat-recall doctor\n"));
|
|
28658
28788
|
for (const r of rows) {
|
|
28659
|
-
const icon = r.ok ?
|
|
28660
|
-
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 || "")}`);
|
|
28661
28791
|
}
|
|
28662
28792
|
const failures = rows.filter((r) => !r.ok).length;
|
|
28663
28793
|
console.log();
|
|
28664
28794
|
if (failures === 0) {
|
|
28665
|
-
console.log(
|
|
28795
|
+
console.log(chalk2.green("All systems green."));
|
|
28666
28796
|
} else {
|
|
28667
|
-
console.log(
|
|
28797
|
+
console.log(chalk2.dim(`${failures} item${failures === 1 ? "" : "s"} need attention.`));
|
|
28668
28798
|
}
|
|
28669
28799
|
});
|
|
28670
28800
|
var companions = program.command("companions").description("Manage companion tools (codeindex)");
|
|
28671
28801
|
companions.command("status").description("Show whether codeindex is installed and what it provides").action(async () => {
|
|
28672
28802
|
const { checkCodeindexStatus: checkCodeindexStatus2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28673
28803
|
const s = checkCodeindexStatus2();
|
|
28674
|
-
console.log(
|
|
28804
|
+
console.log(chalk2.bold("codeindex status"));
|
|
28675
28805
|
if (s.installed) {
|
|
28676
|
-
console.log(` ${
|
|
28806
|
+
console.log(` ${chalk2.green("\u2713 installed")}`);
|
|
28677
28807
|
if (s.path) console.log(` path: ${s.path}`);
|
|
28678
28808
|
if (s.size) console.log(` size: ${(s.size / 1024 / 1024).toFixed(1)} MB`);
|
|
28679
28809
|
if (s.version) console.log(` version: ${s.version}`);
|
|
28680
28810
|
} else {
|
|
28681
|
-
console.log(` ${
|
|
28811
|
+
console.log(` ${chalk2.yellow("not installed")}`);
|
|
28682
28812
|
if (!s.prebuiltAvailable) {
|
|
28683
|
-
console.log(` ${
|
|
28813
|
+
console.log(` ${chalk2.dim(s.unsupportedReason || "no prebuilt binary for this platform")}`);
|
|
28684
28814
|
} else {
|
|
28685
|
-
console.log(` ${
|
|
28815
|
+
console.log(` ${chalk2.dim(`run \`chat-recall companions install\` to fetch ${s.artifactName}`)}`);
|
|
28686
28816
|
}
|
|
28687
28817
|
}
|
|
28688
28818
|
console.log();
|
|
28689
|
-
console.log(
|
|
28690
|
-
console.log(
|
|
28691
|
-
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/...)"));
|
|
28692
28822
|
});
|
|
28693
28823
|
companions.command("install").description("Install codeindex (downloads from munhq/codeindex GitHub release)").option("--force", "Re-download even if already installed").action(async (opts) => {
|
|
28694
28824
|
const { installCodeindex: installCodeindex2, registerCodeindexMcp: registerCodeindexMcp2, CODEINDEX_BIN_PATH: CODEINDEX_BIN_PATH2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28695
28825
|
try {
|
|
28696
28826
|
const result = await installCodeindex2({ force: opts.force });
|
|
28697
28827
|
if (!result.prebuiltAvailable && !result.installed) {
|
|
28698
|
-
console.log(
|
|
28699
|
-
console.log(
|
|
28828
|
+
console.log(chalk2.yellow(`codeindex: ${result.unsupportedReason}`));
|
|
28829
|
+
console.log(chalk2.dim(" Build from source: https://github.com/munhq/codeindex#install"));
|
|
28700
28830
|
return;
|
|
28701
28831
|
}
|
|
28702
28832
|
if (result.installed) {
|
|
28703
28833
|
const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
|
|
28704
|
-
console.log(
|
|
28834
|
+
console.log(chalk2.green(`\u2713 codeindex installed (${sizeMb}) \u2192 ${result.path}`));
|
|
28705
28835
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
28706
28836
|
const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
|
|
28707
|
-
if (reg.added) console.log(
|
|
28708
|
-
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`));
|
|
28709
28839
|
}
|
|
28710
28840
|
} catch (err) {
|
|
28711
28841
|
const msg = String(err);
|
|
28712
|
-
console.error(
|
|
28842
|
+
console.error(chalk2.red(`codeindex install failed: ${msg}`));
|
|
28713
28843
|
if (msg.includes("404")) {
|
|
28714
|
-
console.error(
|
|
28715
|
-
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"));
|
|
28716
28846
|
} else {
|
|
28717
|
-
console.error(
|
|
28847
|
+
console.error(chalk2.dim(" This does not block chat-recall \u2014 it just disables code-level lookup."));
|
|
28718
28848
|
}
|
|
28719
28849
|
process.exit(1);
|
|
28720
28850
|
}
|
|
@@ -28722,15 +28852,15 @@ companions.command("install").description("Install codeindex (downloads from mun
|
|
|
28722
28852
|
companions.command("uninstall").description("Remove the codeindex binary chat-recall installed (does not touch your other tools)").action(async () => {
|
|
28723
28853
|
const { uninstallCodeindex: uninstallCodeindex2, unregisterCodeindexMcp: unregisterCodeindexMcp2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28724
28854
|
const r = uninstallCodeindex2();
|
|
28725
|
-
if (r.removed) console.log(
|
|
28726
|
-
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}`));
|
|
28727
28857
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
28728
28858
|
const u = unregisterCodeindexMcp2(mcpJsonPath);
|
|
28729
|
-
if (u.removed) console.log(
|
|
28859
|
+
if (u.removed) console.log(chalk2.dim(` Unregistered MCP server from ${mcpJsonPath}`));
|
|
28730
28860
|
});
|
|
28731
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) => {
|
|
28732
28862
|
if (!opts.deep) {
|
|
28733
|
-
console.log(
|
|
28863
|
+
console.log(chalk2.yellow("verify currently implements only --deep; re-run with --deep."));
|
|
28734
28864
|
return;
|
|
28735
28865
|
}
|
|
28736
28866
|
const { verifyAgainstServer: verifyAgainstServer2, verifyTargets: verifyTargets2 } = await Promise.resolve().then(() => (init_verify_deep(), verify_deep_exports));
|
|
@@ -28741,7 +28871,7 @@ program.command("verify").description("Check that the server holds everything th
|
|
|
28741
28871
|
const { forceFullResync: forceFullResync2 } = await Promise.resolve().then(() => (init_verify_repair(), verify_repair_exports));
|
|
28742
28872
|
const targets = verifyTargets2();
|
|
28743
28873
|
if (targets.length === 0) {
|
|
28744
|
-
console.error(
|
|
28874
|
+
console.error(chalk2.red("No server configured \u2014 run `chat-recall login` first."));
|
|
28745
28875
|
process.exit(1);
|
|
28746
28876
|
}
|
|
28747
28877
|
const sinceMs = opts.sinceHours ? Date.now() - Number(opts.sinceHours) * 36e5 : 0;
|
|
@@ -28806,37 +28936,37 @@ program.command("verify").description("Check that the server holds everything th
|
|
|
28806
28936
|
try {
|
|
28807
28937
|
report = await verifyAgainstServer2(t.serverUrl, t.token, sinceMs, deps);
|
|
28808
28938
|
} catch (e) {
|
|
28809
|
-
console.error(
|
|
28939
|
+
console.error(chalk2.red(`${t.serverUrl}: ${e instanceof Error ? e.message : e}`));
|
|
28810
28940
|
exitCode = 1;
|
|
28811
28941
|
continue;
|
|
28812
28942
|
}
|
|
28813
28943
|
reports.push(report);
|
|
28814
28944
|
if (opts.json) continue;
|
|
28815
|
-
console.log(
|
|
28945
|
+
console.log(chalk2.bold(`
|
|
28816
28946
|
${t.serverUrl}`));
|
|
28817
28947
|
console.log(` checked ${report.checked} \xB7 complete ${report.complete} \xB7 no archive ${report.missingArchive}`);
|
|
28818
28948
|
if (report.pending.length > 0) {
|
|
28819
|
-
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)`));
|
|
28820
28950
|
}
|
|
28821
28951
|
if (report.stranded.length === 0) {
|
|
28822
|
-
console.log(
|
|
28952
|
+
console.log(chalk2.green(" \u2713 nothing stranded"));
|
|
28823
28953
|
} else {
|
|
28824
28954
|
exitCode = 2;
|
|
28825
|
-
console.log(
|
|
28955
|
+
console.log(chalk2.red(` \u2717 ${report.stranded.length} STRANDED \u2014 the ledger claims complete while the server holds less:`));
|
|
28826
28956
|
for (const f of report.stranded.slice(0, 25)) {
|
|
28827
28957
|
console.log(` ${f.sessionId.slice(0, 8)} server short by ${String(f.deficit).padStart(9)} B ${(f.projectPath || "").slice(-44)}`);
|
|
28828
28958
|
}
|
|
28829
|
-
if (report.stranded.length > 25) console.log(
|
|
28959
|
+
if (report.stranded.length > 25) console.log(chalk2.dim(` \u2026and ${report.stranded.length - 25} more`));
|
|
28830
28960
|
if (!opts.repair) {
|
|
28831
|
-
console.log(
|
|
28961
|
+
console.log(chalk2.dim(" Re-run with --repair to clear their cursors and re-ship."));
|
|
28832
28962
|
}
|
|
28833
28963
|
}
|
|
28834
28964
|
if (opts.records) {
|
|
28835
28965
|
const suspects = [...report.stranded, ...report.pending];
|
|
28836
28966
|
if (suspects.length === 0) {
|
|
28837
|
-
console.log(
|
|
28967
|
+
console.log(chalk2.dim(" --records: nothing to deep-check (size pass found no gaps)"));
|
|
28838
28968
|
} else {
|
|
28839
|
-
console.log(
|
|
28969
|
+
console.log(chalk2.bold(`
|
|
28840
28970
|
record-level diff (${suspects.length} session(s)):`));
|
|
28841
28971
|
for (const f of suspects) {
|
|
28842
28972
|
let serverIds;
|
|
@@ -28861,9 +28991,9 @@ ${t.serverUrl}`));
|
|
|
28861
28991
|
}
|
|
28862
28992
|
const missing = [...localIds].filter((u) => !serverIds.has(u)).length;
|
|
28863
28993
|
const onlyServer = [...serverIds].filter((u) => !localIds.has(u)).length;
|
|
28864
|
-
const verdict = missing === 0 ?
|
|
28994
|
+
const verdict = missing === 0 ? chalk2.green("in sync") : chalk2.red(`${missing} MISSING server-side`);
|
|
28865
28995
|
console.log(
|
|
28866
|
-
` ${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)`) : "")
|
|
28867
28997
|
);
|
|
28868
28998
|
if (missing > 0) exitCode = 2;
|
|
28869
28999
|
}
|
|
@@ -28871,7 +29001,7 @@ ${t.serverUrl}`));
|
|
|
28871
29001
|
}
|
|
28872
29002
|
if (opts.repair && report.stranded.length > 0) {
|
|
28873
29003
|
const n = forceFullResync2(t.serverUrl, report.stranded.map((f) => f.sessionId));
|
|
28874
|
-
console.log(
|
|
29004
|
+
console.log(chalk2.green(` \u21BB cleared ${n} ledger cursor(s) \u2014 run \`chat-recall sync\` to re-ship`));
|
|
28875
29005
|
}
|
|
28876
29006
|
}
|
|
28877
29007
|
if (opts.json) console.log(JSON.stringify({ reports }, null, 2));
|
|
@@ -28879,19 +29009,19 @@ ${t.serverUrl}`));
|
|
|
28879
29009
|
});
|
|
28880
29010
|
var sources2 = program.command("sources").description("See and choose which AI-tool transcript folders get synced");
|
|
28881
29011
|
function decisionBadge(d) {
|
|
28882
|
-
if (d === "primary") return
|
|
28883
|
-
if (d === "approved") return
|
|
28884
|
-
if (d === "declined") return
|
|
28885
|
-
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");
|
|
28886
29016
|
}
|
|
28887
29017
|
async function listSources() {
|
|
28888
29018
|
const { discoverHomes: discoverHomes2 } = await Promise.resolve().then(() => (init_home_discovery(), home_discovery_exports));
|
|
28889
29019
|
const { homeDecision: homeDecision2, grandfatherLegacyHomes: grandfatherLegacyHomes3 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28890
29020
|
grandfatherLegacyHomes3();
|
|
28891
29021
|
const homes = discoverHomes2();
|
|
28892
|
-
console.log(
|
|
29022
|
+
console.log(chalk2.bold("Transcript folders found on this machine"));
|
|
28893
29023
|
if (homes.length === 0) {
|
|
28894
|
-
console.log(
|
|
29024
|
+
console.log(chalk2.dim(" none \u2014 no AI tool transcripts detected"));
|
|
28895
29025
|
return { pending: 0 };
|
|
28896
29026
|
}
|
|
28897
29027
|
let pending = 0;
|
|
@@ -28900,14 +29030,14 @@ async function listSources() {
|
|
|
28900
29030
|
if (d === "pending") pending++;
|
|
28901
29031
|
const count = h.sessions > 0 ? `${h.sessions} session${h.sessions === 1 ? "" : "s"}` : "\u2014";
|
|
28902
29032
|
console.log(
|
|
28903
|
-
` ${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)") : "")
|
|
28904
29034
|
);
|
|
28905
29035
|
}
|
|
28906
29036
|
if (pending > 0) {
|
|
28907
29037
|
console.log();
|
|
28908
|
-
console.log(
|
|
28909
|
-
console.log(
|
|
28910
|
-
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)"));
|
|
28911
29041
|
}
|
|
28912
29042
|
return { pending };
|
|
28913
29043
|
}
|
|
@@ -28920,74 +29050,74 @@ sources2.command("add <path>").description("Sync a transcript folder we did not
|
|
|
28920
29050
|
const abs = normalizeHomePath2(path);
|
|
28921
29051
|
const tool = identifyHome2(abs);
|
|
28922
29052
|
if (!tool) {
|
|
28923
|
-
console.error(
|
|
28924
|
-
console.error(
|
|
28925
|
-
console.error(
|
|
28926
|
-
console.error(
|
|
28927
|
-
console.error(
|
|
28928
|
-
console.error(
|
|
28929
|
-
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)"));
|
|
28930
29060
|
process.exit(1);
|
|
28931
29061
|
}
|
|
28932
29062
|
if (!approveHome2(abs)) {
|
|
28933
|
-
console.error(
|
|
29063
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28934
29064
|
process.exit(1);
|
|
28935
29065
|
}
|
|
28936
|
-
console.log(
|
|
28937
|
-
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."));
|
|
28938
29068
|
});
|
|
28939
29069
|
sources2.command("approve <path>").description("Start syncing a folder that was waiting for a decision").action(async (path) => {
|
|
28940
29070
|
const { approveHome: approveHome2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28941
29071
|
const abs = normalizeHomePath2(path);
|
|
28942
29072
|
if (!approveHome2(abs)) {
|
|
28943
|
-
console.error(
|
|
29073
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28944
29074
|
process.exit(1);
|
|
28945
29075
|
}
|
|
28946
|
-
console.log(
|
|
29076
|
+
console.log(chalk2.green(`\u2713 now syncing ${abs}`));
|
|
28947
29077
|
});
|
|
28948
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) => {
|
|
28949
29079
|
const { declineHome: declineHome2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28950
29080
|
const { identifyHome: identifyHome2, sessionIdsInHome: sessionIdsInHome2 } = await Promise.resolve().then(() => (init_home_discovery(), home_discovery_exports));
|
|
28951
29081
|
const abs = normalizeHomePath2(path);
|
|
28952
29082
|
if (!declineHome2(abs)) {
|
|
28953
|
-
console.error(
|
|
29083
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28954
29084
|
process.exit(1);
|
|
28955
29085
|
}
|
|
28956
|
-
console.log(
|
|
29086
|
+
console.log(chalk2.green(`\u2713 not syncing ${abs}`));
|
|
28957
29087
|
if (!opts.deleteRemote) {
|
|
28958
|
-
console.log(
|
|
28959
|
-
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`));
|
|
28960
29090
|
return;
|
|
28961
29091
|
}
|
|
28962
29092
|
const tool = identifyHome2(abs);
|
|
28963
29093
|
if (!tool) {
|
|
28964
|
-
console.error(
|
|
29094
|
+
console.error(chalk2.red(`Not a transcript folder: ${abs}`));
|
|
28965
29095
|
process.exit(1);
|
|
28966
29096
|
}
|
|
28967
29097
|
const ids = sessionIdsInHome2(abs, tool);
|
|
28968
29098
|
if (ids.length === 0) {
|
|
28969
|
-
console.log(
|
|
29099
|
+
console.log(chalk2.dim(" Nothing to delete \u2014 no session files found in that folder."));
|
|
28970
29100
|
if (tool === "opencode") {
|
|
28971
|
-
console.log(
|
|
29101
|
+
console.log(chalk2.dim(" (OpenCode keys sessions by database row, so they cannot be enumerated from the path.)"));
|
|
28972
29102
|
}
|
|
28973
29103
|
return;
|
|
28974
29104
|
}
|
|
28975
29105
|
const targets = loadAllCredentials();
|
|
28976
29106
|
if (targets.length === 0) {
|
|
28977
|
-
console.error(
|
|
29107
|
+
console.error(chalk2.red("Not logged in \u2014 nothing to delete from."));
|
|
28978
29108
|
process.exit(1);
|
|
28979
29109
|
}
|
|
28980
29110
|
if (!opts.yes) {
|
|
28981
29111
|
console.log();
|
|
28982
|
-
console.log(
|
|
28983
|
-
console.log(
|
|
28984
|
-
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."));
|
|
28985
29115
|
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
28986
29116
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
28987
29117
|
const answer = (await rl.question("Type the number of sessions to confirm: ")).trim();
|
|
28988
29118
|
rl.close();
|
|
28989
29119
|
if (answer !== String(ids.length)) {
|
|
28990
|
-
console.log(
|
|
29120
|
+
console.log(chalk2.dim("Cancelled \u2014 nothing deleted."));
|
|
28991
29121
|
return;
|
|
28992
29122
|
}
|
|
28993
29123
|
}
|
|
@@ -29007,8 +29137,8 @@ sources2.command("decline <path>").description("Keep a folder out of sync (e.g.
|
|
|
29007
29137
|
failed++;
|
|
29008
29138
|
}
|
|
29009
29139
|
}
|
|
29010
|
-
const line = ` ${base2}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ?
|
|
29011
|
-
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));
|
|
29012
29142
|
if (failed) process.exitCode = 1;
|
|
29013
29143
|
}
|
|
29014
29144
|
});
|
|
@@ -29016,30 +29146,30 @@ sources2.command("forget <path>").description("Undo a decision, so the folder is
|
|
|
29016
29146
|
const { resetHomeDecision: resetHomeDecision2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
29017
29147
|
const abs = normalizeHomePath2(path);
|
|
29018
29148
|
resetHomeDecision2(abs);
|
|
29019
|
-
console.log(
|
|
29149
|
+
console.log(chalk2.green(`\u2713 ${abs} is undecided again (not syncing until approved)`));
|
|
29020
29150
|
});
|
|
29021
29151
|
var detectors = program.command("detectors").description("Manage the optional external secret detectors (gitleaks, trufflehog)");
|
|
29022
29152
|
detectors.command("status").description("Show which detectors are installed, pinned and verified").action(async () => {
|
|
29023
29153
|
const { detectorStatus: detectorStatus2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
29024
29154
|
const { externalScannersEnabled: externalScannersEnabled2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
29025
|
-
console.log(
|
|
29155
|
+
console.log(chalk2.bold("external secret detectors"));
|
|
29026
29156
|
console.log(
|
|
29027
|
-
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`
|
|
29028
29158
|
);
|
|
29029
29159
|
if (process.env.CHAT_RECALL_DETECTOR_DIR) {
|
|
29030
|
-
console.log(` dir: ${
|
|
29160
|
+
console.log(` dir: ${chalk2.dim(process.env.CHAT_RECALL_DETECTOR_DIR)} ${chalk2.dim("(operator-supplied, unverified)")}`);
|
|
29031
29161
|
}
|
|
29032
29162
|
console.log();
|
|
29033
29163
|
for (const d of detectorStatus2()) {
|
|
29034
|
-
const label = `${d.name} ${
|
|
29035
|
-
if (d.state === "ready") console.log(` ${
|
|
29036
|
-
else if (d.state === "configured") console.log(` ${
|
|
29037
|
-
else if (d.state === "not-installed") console.log(` ${
|
|
29038
|
-
else if (!d.supported) console.log(` ${
|
|
29039
|
-
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}\``);
|
|
29040
29170
|
}
|
|
29041
29171
|
console.log();
|
|
29042
|
-
console.log(
|
|
29172
|
+
console.log(chalk2.dim("Builtin in-process detection runs for every user and needs none of this."));
|
|
29043
29173
|
});
|
|
29044
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) => {
|
|
29045
29175
|
const { installDetector: installDetector2, DETECTOR_MANIFEST: DETECTOR_MANIFEST2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
@@ -29048,33 +29178,33 @@ detectors.command("install [name]").description("Download + verify a pinned dete
|
|
|
29048
29178
|
let failed = false;
|
|
29049
29179
|
for (const n of names) {
|
|
29050
29180
|
if (!valid.includes(n)) {
|
|
29051
|
-
console.error(
|
|
29181
|
+
console.error(chalk2.red(`unknown detector "${n}" \u2014 expected one of: ${valid.join(", ")}`));
|
|
29052
29182
|
failed = true;
|
|
29053
29183
|
continue;
|
|
29054
29184
|
}
|
|
29055
29185
|
try {
|
|
29056
29186
|
const r = await installDetector2(n, { force: opts.force });
|
|
29057
|
-
console.log(r.installed ?
|
|
29058
|
-
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}`));
|
|
29059
29189
|
} catch (err) {
|
|
29060
29190
|
failed = true;
|
|
29061
|
-
console.error(
|
|
29191
|
+
console.error(chalk2.red(`${n} install failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
29062
29192
|
}
|
|
29063
29193
|
}
|
|
29064
29194
|
if (failed) process.exit(1);
|
|
29065
29195
|
console.log();
|
|
29066
|
-
console.log(
|
|
29067
|
-
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."));
|
|
29068
29198
|
});
|
|
29069
29199
|
detectors.command("remove [name]").description("Remove detectors chat-recall installed (never touches your own binaries)").action(async (name) => {
|
|
29070
29200
|
const { removeDetector: removeDetector2, DETECTOR_MANIFEST: DETECTOR_MANIFEST2, managedDetectorPath: managedDetectorPath2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
29071
29201
|
for (const n of name ? [name] : Object.keys(DETECTOR_MANIFEST2)) {
|
|
29072
29202
|
if (!(n in DETECTOR_MANIFEST2)) {
|
|
29073
|
-
console.error(
|
|
29203
|
+
console.error(chalk2.red(`unknown detector "${n}"`));
|
|
29074
29204
|
continue;
|
|
29075
29205
|
}
|
|
29076
29206
|
const removed = removeDetector2(n);
|
|
29077
|
-
console.log(removed ?
|
|
29207
|
+
console.log(removed ? chalk2.green(`\u2713 removed ${n}`) : chalk2.dim(`\xB7 nothing to remove at ${managedDetectorPath2(n)}`));
|
|
29078
29208
|
}
|
|
29079
29209
|
});
|
|
29080
29210
|
var code = program.command("code").description("Index a codebase with codeindex and ship findings/hotspots/actions to your server");
|
|
@@ -29083,22 +29213,22 @@ code.command("index [path]").description("Run codeindex on a repo (default: cwd)
|
|
|
29083
29213
|
const target = requireTarget();
|
|
29084
29214
|
const { collectCode: collectCode2 } = await Promise.resolve().then(() => (init_collector(), collector_exports));
|
|
29085
29215
|
const workspace = resolve2(path || process.cwd());
|
|
29086
|
-
console.log(
|
|
29216
|
+
console.log(chalk2.bold(`code index ${workspace}`));
|
|
29087
29217
|
const result = await collectCode2({
|
|
29088
29218
|
workspace,
|
|
29089
29219
|
autoInstall: opts.install !== false,
|
|
29090
|
-
log: (m) => console.log(
|
|
29220
|
+
log: (m) => console.log(chalk2.dim(" \xB7 " + m))
|
|
29091
29221
|
});
|
|
29092
29222
|
const resp = await serverPost(
|
|
29093
29223
|
"/api/code/index",
|
|
29094
29224
|
result
|
|
29095
29225
|
);
|
|
29096
|
-
console.log(
|
|
29226
|
+
console.log(chalk2.green("\u2713 synced"), chalk2.dim(`\u2192 ${target.base}`));
|
|
29097
29227
|
console.log(` project: ${resp.projectId}`);
|
|
29098
29228
|
console.log(` health: ${result.project.health.score}/100`);
|
|
29099
29229
|
console.log(` findings: ${resp.findings} hotspots: ${resp.hotspots} actions: ${resp.actions}`);
|
|
29100
29230
|
} catch (err) {
|
|
29101
|
-
console.error(
|
|
29231
|
+
console.error(chalk2.red("code index failed:"), err instanceof Error ? err.message : err);
|
|
29102
29232
|
process.exit(1);
|
|
29103
29233
|
}
|
|
29104
29234
|
});
|
|
@@ -29106,17 +29236,17 @@ code.command("status").description("List code-indexed projects on your server").
|
|
|
29106
29236
|
try {
|
|
29107
29237
|
const { projects } = await serverGet("/api/code/projects");
|
|
29108
29238
|
if (!projects.length) {
|
|
29109
|
-
console.log(
|
|
29239
|
+
console.log(chalk2.dim("No code-indexed projects yet. Run `chat-recall code index` in a repo."));
|
|
29110
29240
|
return;
|
|
29111
29241
|
}
|
|
29112
|
-
console.log(
|
|
29242
|
+
console.log(chalk2.bold(`${projects.length} code project(s)`));
|
|
29113
29243
|
for (const p of projects) {
|
|
29114
29244
|
const when = new Date(p.lastIndexedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
29115
|
-
console.log(` ${
|
|
29116
|
-
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}`));
|
|
29117
29247
|
}
|
|
29118
29248
|
} catch (err) {
|
|
29119
|
-
console.error(
|
|
29249
|
+
console.error(chalk2.red("code status failed:"), err instanceof Error ? err.message : err);
|
|
29120
29250
|
process.exit(1);
|
|
29121
29251
|
}
|
|
29122
29252
|
});
|
|
@@ -29125,12 +29255,12 @@ code.command("show <project>").description("Show a project: health, findings sum
|
|
|
29125
29255
|
const enc = encodeURIComponent(project);
|
|
29126
29256
|
const p = await serverGet(`/api/code/projects/${enc}`);
|
|
29127
29257
|
const { actions } = await serverGet(`/api/code/actions?project=${enc}&limit=12`);
|
|
29128
|
-
console.log(
|
|
29258
|
+
console.log(chalk2.bold(p.projectId), chalk2.dim(p.rootPath));
|
|
29129
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`);
|
|
29130
|
-
console.log(
|
|
29131
|
-
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}`);
|
|
29132
29262
|
} catch (err) {
|
|
29133
|
-
console.error(
|
|
29263
|
+
console.error(chalk2.red("code show failed:"), err instanceof Error ? err.message : err);
|
|
29134
29264
|
process.exit(1);
|
|
29135
29265
|
}
|
|
29136
29266
|
});
|
|
@@ -29139,18 +29269,18 @@ async function pushToolkitToServer() {
|
|
|
29139
29269
|
try {
|
|
29140
29270
|
const { syncIncremental: syncIncremental2, isSyncSkip: isSyncSkip2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
29141
29271
|
const res = await syncIncremental2();
|
|
29142
|
-
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.`));
|
|
29143
29273
|
} catch (err) {
|
|
29144
|
-
console.error(
|
|
29274
|
+
console.error(chalk2.dim(` (could not sync to server: ${err instanceof Error ? err.message : err})`));
|
|
29145
29275
|
}
|
|
29146
29276
|
}
|
|
29147
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) => {
|
|
29148
29278
|
const { executeSyncAll: executeSyncAll2 } = await Promise.resolve().then(() => (init_toolkit_sync(), toolkit_sync_exports));
|
|
29149
29279
|
const types = options.types ? options.types.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
29150
29280
|
const r = await executeSyncAll2(types);
|
|
29151
|
-
for (const c of r.copied) console.log(
|
|
29152
|
-
for (const f of r.failed) console.log(
|
|
29153
|
-
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.`));
|
|
29154
29284
|
if (r.copied.length > 0) await pushToolkitToServer();
|
|
29155
29285
|
});
|
|
29156
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) => {
|
|
@@ -29159,7 +29289,7 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29159
29289
|
const { hostname: hostname3 } = await import("node:os");
|
|
29160
29290
|
const targets = loadAllCredentials2();
|
|
29161
29291
|
if (targets.length === 0) {
|
|
29162
|
-
console.error(
|
|
29292
|
+
console.error(chalk2.red("Not logged in."), "Run `chat-recall login <server-url>` first.");
|
|
29163
29293
|
process.exit(1);
|
|
29164
29294
|
}
|
|
29165
29295
|
const rows = [];
|
|
@@ -29173,7 +29303,7 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29173
29303
|
const body = await res.json();
|
|
29174
29304
|
for (const it of body.items || []) rows.push(it);
|
|
29175
29305
|
} catch (err) {
|
|
29176
|
-
console.log(
|
|
29306
|
+
console.log(chalk2.dim(` (${t.serverUrl} ${type}: ${err instanceof Error ? err.message : err})`));
|
|
29177
29307
|
}
|
|
29178
29308
|
}
|
|
29179
29309
|
}
|
|
@@ -29188,41 +29318,40 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29188
29318
|
const failed = report.outcomes.filter((o) => o.status === "failed");
|
|
29189
29319
|
const skipped = report.outcomes.filter((o) => o.status === "skipped");
|
|
29190
29320
|
for (const o of written) {
|
|
29191
|
-
const note = o.rewrittenCommand ?
|
|
29192
|
-
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);
|
|
29193
29323
|
}
|
|
29194
|
-
for (const o of failed) console.log(
|
|
29195
|
-
for (const o of skipped) console.log(
|
|
29196
|
-
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(
|
|
29197
29327
|
`${options.dryRun ? "Would install" : "Installed"}: ${written.length} \xB7 already present: ${present.length} \xB7 failed: ${failed.length} \xB7 skipped: ${skipped.length}`
|
|
29198
29328
|
));
|
|
29199
|
-
const
|
|
29200
|
-
if (
|
|
29329
|
+
const needs = collectEnvNeeds(written);
|
|
29330
|
+
if (needs.length) {
|
|
29201
29331
|
console.log("");
|
|
29202
|
-
|
|
29203
|
-
for (const v of envVars) console.log(` ${v}`);
|
|
29332
|
+
for (const line of renderEnvNeeds(needs, written.length)) console.log(line);
|
|
29204
29333
|
}
|
|
29205
29334
|
if (report.unsupported.length) {
|
|
29206
29335
|
console.log("");
|
|
29207
|
-
console.log(
|
|
29336
|
+
console.log(chalk2.dim("Not installable from the server yet:"));
|
|
29208
29337
|
for (const u of report.unsupported) {
|
|
29209
|
-
console.log(
|
|
29338
|
+
console.log(chalk2.dim(` ${u.type} (${u.rows} on the server) \u2014 ${u.reason}`));
|
|
29210
29339
|
}
|
|
29211
|
-
console.log(
|
|
29340
|
+
console.log(chalk2.dim(" Those need artifact content upload; the server holds an inventory only."));
|
|
29212
29341
|
}
|
|
29213
29342
|
});
|
|
29214
29343
|
toolkit.command("drain").description("Process cross-tool sync intents queued from the web UI, now").action(async () => {
|
|
29215
29344
|
const { drainSyncIntents: drainSyncIntents2 } = await Promise.resolve().then(() => (init_intent_drain(), intent_drain_exports));
|
|
29216
29345
|
const r = await drainSyncIntents2({ verbose: true });
|
|
29217
|
-
console.log(
|
|
29346
|
+
console.log(chalk2.bold(`Processed ${r.processed} intent(s): ${r.done} done, ${r.errored} errored.`));
|
|
29218
29347
|
if (r.done > 0) await pushToolkitToServer();
|
|
29219
29348
|
});
|
|
29220
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) => {
|
|
29221
29350
|
const { executeCopy: executeCopy2 } = await Promise.resolve().then(() => (init_toolkit_sync(), toolkit_sync_exports));
|
|
29222
29351
|
const r = await executeCopy2(type, name, fromTool, toTool);
|
|
29223
|
-
if (r.ok) console.log(
|
|
29352
|
+
if (r.ok) console.log(chalk2.green(`Copied to ${r.targetPath}`));
|
|
29224
29353
|
else {
|
|
29225
|
-
console.error(
|
|
29354
|
+
console.error(chalk2.red(r.error || "failed"));
|
|
29226
29355
|
process.exit(1);
|
|
29227
29356
|
}
|
|
29228
29357
|
});
|
|
@@ -29230,31 +29359,31 @@ program.command("shares").description("List the projects you share with your tea
|
|
|
29230
29359
|
try {
|
|
29231
29360
|
const { shares } = await serverGet("/api/shares");
|
|
29232
29361
|
if (!shares.length) {
|
|
29233
|
-
console.log(
|
|
29362
|
+
console.log(chalk2.gray("You are not sharing any projects. Share one with"), chalk2.bold("chat-recall share <project_id>"));
|
|
29234
29363
|
return;
|
|
29235
29364
|
}
|
|
29236
|
-
console.log(
|
|
29237
|
-
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)}`);
|
|
29238
29367
|
} catch (e) {
|
|
29239
|
-
console.error(
|
|
29368
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29240
29369
|
process.exit(1);
|
|
29241
29370
|
}
|
|
29242
29371
|
});
|
|
29243
29372
|
program.command("share <project_id>").description("Share a project with your team (teammates can then see your work on it)").action(async (projectId) => {
|
|
29244
29373
|
try {
|
|
29245
29374
|
await serverPost("/api/shares", { project_id: projectId });
|
|
29246
|
-
console.log(
|
|
29375
|
+
console.log(chalk2.green("\u2713 shared"), chalk2.cyan(projectId), chalk2.gray("with your team"));
|
|
29247
29376
|
} catch (e) {
|
|
29248
|
-
console.error(
|
|
29377
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29249
29378
|
process.exit(1);
|
|
29250
29379
|
}
|
|
29251
29380
|
});
|
|
29252
29381
|
program.command("unshare <project_id>").description("Stop sharing a project with your team").action(async (projectId) => {
|
|
29253
29382
|
try {
|
|
29254
29383
|
const r = await serverDelete("/api/shares", { project_id: projectId });
|
|
29255
|
-
console.log(r.removed ?
|
|
29384
|
+
console.log(r.removed ? chalk2.green("\u2713 unshared") : chalk2.yellow("was not shared"), chalk2.cyan(projectId));
|
|
29256
29385
|
} catch (e) {
|
|
29257
|
-
console.error(
|
|
29386
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29258
29387
|
process.exit(1);
|
|
29259
29388
|
}
|
|
29260
29389
|
});
|
|
@@ -29263,12 +29392,12 @@ tasks.command("list").description("List team tasks assigned to you").action(asyn
|
|
|
29263
29392
|
try {
|
|
29264
29393
|
const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
|
|
29265
29394
|
if (!tasks2.length) {
|
|
29266
|
-
console.log(
|
|
29395
|
+
console.log(chalk2.gray("No tasks assigned to you."));
|
|
29267
29396
|
return;
|
|
29268
29397
|
}
|
|
29269
|
-
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) : ""}`);
|
|
29270
29399
|
} catch (e) {
|
|
29271
|
-
console.error(
|
|
29400
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29272
29401
|
process.exit(1);
|
|
29273
29402
|
}
|
|
29274
29403
|
});
|
|
@@ -29281,7 +29410,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
|
|
|
29281
29410
|
const { join: join61 } = await import("node:path");
|
|
29282
29411
|
const file = join61(cwd, "TEAM_TASKS.md");
|
|
29283
29412
|
if (!opts.force && existsSync57(file) && parseTeamTasksFile2(readFileSync50(file, "utf8")).length > 0) {
|
|
29284
|
-
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.");
|
|
29285
29414
|
process.exit(1);
|
|
29286
29415
|
}
|
|
29287
29416
|
const resolved = resolveProjectId2(cwd);
|
|
@@ -29289,9 +29418,9 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
|
|
|
29289
29418
|
const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
|
|
29290
29419
|
const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
|
|
29291
29420
|
writeFileSync22(file, renderTeamTasksMd2(mine));
|
|
29292
|
-
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"));
|
|
29293
29422
|
} catch (e) {
|
|
29294
|
-
console.error(
|
|
29423
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29295
29424
|
process.exit(1);
|
|
29296
29425
|
}
|
|
29297
29426
|
});
|
|
@@ -29303,7 +29432,7 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
|
|
|
29303
29432
|
const { join: join61 } = await import("node:path");
|
|
29304
29433
|
const file = join61(cwd, "TEAM_TASKS.md");
|
|
29305
29434
|
if (!existsSync57(file)) {
|
|
29306
|
-
console.error(
|
|
29435
|
+
console.error(chalk2.red("no TEAM_TASKS.md here \u2014"), "run", chalk2.bold("chat-recall tasks pull"), "first");
|
|
29307
29436
|
process.exit(1);
|
|
29308
29437
|
}
|
|
29309
29438
|
const edits = parseTeamTasksFile2(readFileSync50(file, "utf8"));
|
|
@@ -29312,9 +29441,9 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
|
|
|
29312
29441
|
await serverPatch(`/api/tasks/${encodeURIComponent(e.id)}`, { status: e.intent });
|
|
29313
29442
|
n++;
|
|
29314
29443
|
}
|
|
29315
|
-
console.log(n ?
|
|
29444
|
+
console.log(n ? chalk2.green(`\u2713 pushed ${n} status change(s)`) : chalk2.gray("no changes to push"));
|
|
29316
29445
|
} catch (e) {
|
|
29317
|
-
console.error(
|
|
29446
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29318
29447
|
process.exit(1);
|
|
29319
29448
|
}
|
|
29320
29449
|
});
|
|
@@ -29325,12 +29454,12 @@ async function runTeam(label, fn) {
|
|
|
29325
29454
|
await fn();
|
|
29326
29455
|
} catch (err) {
|
|
29327
29456
|
if (err instanceof TeamConfigError2) {
|
|
29328
|
-
console.error(
|
|
29457
|
+
console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29329
29458
|
} else if (err instanceof TeamAuthError2) {
|
|
29330
|
-
console.error(
|
|
29331
|
-
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."));
|
|
29332
29461
|
} else if (err instanceof TeamHttpError2) {
|
|
29333
|
-
console.error(
|
|
29462
|
+
console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29334
29463
|
} else {
|
|
29335
29464
|
throw err;
|
|
29336
29465
|
}
|
|
@@ -29340,76 +29469,76 @@ async function runTeam(label, fn) {
|
|
|
29340
29469
|
team.command("whoami").description("Show the user + team memberships the server sees for your token").action(async () => runTeam("team whoami", async () => {
|
|
29341
29470
|
const { teamMe: teamMe2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29342
29471
|
const me = await teamMe2();
|
|
29343
|
-
console.log(
|
|
29472
|
+
console.log(chalk2.bold(me.user.email) + chalk2.dim(` (${me.user.id})`));
|
|
29344
29473
|
if (me.memberships.length === 0) {
|
|
29345
|
-
console.log(
|
|
29474
|
+
console.log(chalk2.dim(" no team memberships yet \u2014 run `chat-recall team create <name>` or `team join <token>`"));
|
|
29346
29475
|
return;
|
|
29347
29476
|
}
|
|
29348
29477
|
for (const m of me.memberships) {
|
|
29349
|
-
const role = m.role === "owner" ?
|
|
29350
|
-
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)}`);
|
|
29351
29480
|
}
|
|
29352
29481
|
}));
|
|
29353
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 () => {
|
|
29354
29483
|
const { teamCreate: teamCreate2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29355
29484
|
const t = await teamCreate2(name);
|
|
29356
|
-
console.log(
|
|
29357
|
-
console.log(
|
|
29358
|
-
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`));
|
|
29359
29488
|
}));
|
|
29360
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 () => {
|
|
29361
29490
|
const { teamInvite: teamInvite2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29362
29491
|
const r = await teamInvite2(opts.email);
|
|
29363
|
-
console.log(
|
|
29364
|
-
console.log(" " +
|
|
29365
|
-
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}`));
|
|
29366
29495
|
console.log();
|
|
29367
|
-
console.log(
|
|
29496
|
+
console.log(chalk2.dim("Recipient runs: chat-recall team join <token>"));
|
|
29368
29497
|
}));
|
|
29369
29498
|
team.command("join <invite-token>").description("Accept a team invite. Stores team id locally.").action(async (inviteToken) => runTeam("team join", async () => {
|
|
29370
29499
|
const { teamJoin: teamJoin2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29371
29500
|
const t = await teamJoin2(inviteToken);
|
|
29372
|
-
console.log(
|
|
29373
|
-
console.log(
|
|
29374
|
-
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."));
|
|
29375
29504
|
}));
|
|
29376
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 () => {
|
|
29377
29506
|
const { teamPull: teamPull2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29378
29507
|
const r = await teamPull2();
|
|
29379
29508
|
if (r.pulled.length === 0 && r.removed.length === 0) {
|
|
29380
|
-
console.log(
|
|
29509
|
+
console.log(chalk2.dim("Up to date."));
|
|
29381
29510
|
return;
|
|
29382
29511
|
}
|
|
29383
29512
|
for (const a of r.pulled) {
|
|
29384
|
-
console.log(` ${
|
|
29513
|
+
console.log(` ${chalk2.green("+")} ${chalk2.cyan(a.type + "/" + a.name)} v${a.version} ${chalk2.dim("(" + a.tool + ")")}`);
|
|
29385
29514
|
}
|
|
29386
29515
|
for (const id of r.removed) {
|
|
29387
|
-
console.log(` ${
|
|
29516
|
+
console.log(` ${chalk2.red("-")} ${chalk2.dim(id + " (revoked)")}`);
|
|
29388
29517
|
}
|
|
29389
29518
|
if (opts.dryRun) {
|
|
29390
|
-
console.log(
|
|
29519
|
+
console.log(chalk2.dim(`Watermark: ${r.serverNow} (dry-run \u2014 nothing written)`));
|
|
29391
29520
|
return;
|
|
29392
29521
|
}
|
|
29393
29522
|
const { mergePullResult: mergePullResult2 } = await Promise.resolve().then(() => (init_team_merge(), team_merge_exports));
|
|
29394
29523
|
const m = await mergePullResult2({ pulled: r.pulled, removed: r.removed });
|
|
29395
29524
|
console.log();
|
|
29396
|
-
if (m.written.length) console.log(
|
|
29397
|
-
if (m.skipped.length) console.log(
|
|
29398
|
-
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"}`));
|
|
29399
29528
|
for (const f of m.failures) {
|
|
29400
|
-
console.log(
|
|
29529
|
+
console.log(chalk2.red(` ! ${f.path}: ${f.error}`));
|
|
29401
29530
|
}
|
|
29402
|
-
console.log(
|
|
29531
|
+
console.log(chalk2.dim(`Watermark: ${r.serverNow}`));
|
|
29403
29532
|
}));
|
|
29404
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 () => {
|
|
29405
29534
|
const validTypes = ["skill", "command", "agent", "mcp", "plan", "plugin", "instructions", "hook"];
|
|
29406
29535
|
if (!validTypes.includes(type)) {
|
|
29407
|
-
console.error(
|
|
29536
|
+
console.error(chalk2.red(`Invalid type: ${type}. Must be one of: ${validTypes.join(", ")}`));
|
|
29408
29537
|
process.exit(1);
|
|
29409
29538
|
}
|
|
29410
29539
|
const validTools = ["claude", "gemini", "opencode", "codex", "agy", "cursor", "cross_tool"];
|
|
29411
29540
|
if (!validTools.includes(opts.tool)) {
|
|
29412
|
-
console.error(
|
|
29541
|
+
console.error(chalk2.red(`Invalid tool: ${opts.tool}. Must be one of: ${validTools.join(", ")}`));
|
|
29413
29542
|
process.exit(1);
|
|
29414
29543
|
}
|
|
29415
29544
|
const { readFileSync: readFileSync50 } = await import("fs");
|
|
@@ -29422,32 +29551,32 @@ team.command("publish <type> <name> <file>").description("Publish a local file a
|
|
|
29422
29551
|
body,
|
|
29423
29552
|
pinnedTo: opts.pinnedTo
|
|
29424
29553
|
});
|
|
29425
|
-
console.log(
|
|
29426
|
-
console.log(
|
|
29427
|
-
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}`));
|
|
29428
29557
|
}));
|
|
29429
29558
|
team.command("list").description("List artifacts in the team library (metadata only, no bodies)").action(async () => runTeam("team list", async () => {
|
|
29430
29559
|
const { teamList: teamList2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29431
29560
|
const items = await teamList2();
|
|
29432
29561
|
if (items.length === 0) {
|
|
29433
|
-
console.log(
|
|
29562
|
+
console.log(chalk2.dim("Library is empty. Try `chat-recall team publish <type> <name> <file>`."));
|
|
29434
29563
|
return;
|
|
29435
29564
|
}
|
|
29436
29565
|
for (const a of items) {
|
|
29437
|
-
const pinned = a.pinnedTo ?
|
|
29438
|
-
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}`);
|
|
29439
29568
|
}
|
|
29440
29569
|
}));
|
|
29441
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 () => {
|
|
29442
29571
|
const { teamRevoke: teamRevoke2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29443
29572
|
const r = await teamRevoke2(artifactId);
|
|
29444
|
-
console.log(
|
|
29573
|
+
console.log(chalk2.green(`\u2713 Revoked ${chalk2.bold(r.type + "/" + r.name)} (${r.id})`));
|
|
29445
29574
|
}));
|
|
29446
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 () => {
|
|
29447
29576
|
const { teamLeave: teamLeave2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29448
29577
|
await teamLeave2();
|
|
29449
|
-
console.log(
|
|
29450
|
-
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."));
|
|
29451
29580
|
}));
|
|
29452
29581
|
var vault = program.command("vault").description("Encrypted multi-device chat backup (E2EE \u2014 server cannot decrypt)");
|
|
29453
29582
|
async function runVault(label, fn) {
|
|
@@ -29455,9 +29584,9 @@ async function runVault(label, fn) {
|
|
|
29455
29584
|
try {
|
|
29456
29585
|
await fn();
|
|
29457
29586
|
} catch (err) {
|
|
29458
|
-
if (err instanceof VaultConfigError2) console.error(
|
|
29459
|
-
else if (err instanceof VaultAuthError2) console.error(
|
|
29460
|
-
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}`));
|
|
29461
29590
|
else throw err;
|
|
29462
29591
|
process.exit(1);
|
|
29463
29592
|
}
|
|
@@ -29467,7 +29596,7 @@ async function resolvePassphrase(opts) {
|
|
|
29467
29596
|
if (opts.envVar) {
|
|
29468
29597
|
const v = process.env[opts.envVar];
|
|
29469
29598
|
if (v) return v;
|
|
29470
|
-
console.error(
|
|
29599
|
+
console.error(chalk2.red(`Env var ${opts.envVar} is not set.`));
|
|
29471
29600
|
process.exit(1);
|
|
29472
29601
|
}
|
|
29473
29602
|
if (!opts.noKeyring) {
|
|
@@ -29504,49 +29633,49 @@ vault.command("enable").description("Set up the Vault on this device. Generates
|
|
|
29504
29633
|
r = await vaultEnableWithRemote2(passphrase, { baseUrl: cred.serverUrl, token: cred.token });
|
|
29505
29634
|
} catch (err) {
|
|
29506
29635
|
if (err instanceof VaultPassphraseMismatchError2) {
|
|
29507
|
-
console.error(
|
|
29636
|
+
console.error(chalk2.red(`vault enable: ${err.message}`));
|
|
29508
29637
|
process.exit(1);
|
|
29509
29638
|
}
|
|
29510
29639
|
throw err;
|
|
29511
29640
|
}
|
|
29512
29641
|
}
|
|
29513
|
-
console.log(
|
|
29514
|
-
console.log(
|
|
29515
|
-
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)") : ""));
|
|
29516
29645
|
if (opts.keyring === false) {
|
|
29517
|
-
console.log(
|
|
29646
|
+
console.log(chalk2.yellow(" keyring: skipped (--no-keyring) \u2014 the background daemon will not be able to decrypt."));
|
|
29518
29647
|
} else {
|
|
29519
29648
|
const { keyringSet: keyringSet2, probeKeyring: probeKeyring2 } = await Promise.resolve().then(() => (init_keyring(), keyring_exports));
|
|
29520
29649
|
try {
|
|
29521
29650
|
keyringSet2(passphrase);
|
|
29522
|
-
console.log(
|
|
29651
|
+
console.log(chalk2.dim(` keyring: stored (${probeKeyring2().backend}) \u2014 the daemon can decrypt unattended.`));
|
|
29523
29652
|
} catch (err) {
|
|
29524
|
-
console.log(
|
|
29525
|
-
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."));
|
|
29526
29655
|
}
|
|
29527
29656
|
}
|
|
29528
29657
|
console.log();
|
|
29529
29658
|
if (r.saltSource === "server") {
|
|
29530
|
-
console.log(
|
|
29659
|
+
console.log(chalk2.dim("This device joined your existing vault \u2014 same passphrase, same key."));
|
|
29531
29660
|
} else {
|
|
29532
|
-
console.log(
|
|
29661
|
+
console.log(chalk2.yellow("To set up another device, run there:"));
|
|
29533
29662
|
console.log(" chat-recall vault enable");
|
|
29534
|
-
console.log(
|
|
29663
|
+
console.log(chalk2.dim(" (it picks up this workspace's salt automatically \u2014 only the passphrase is needed)"));
|
|
29535
29664
|
}
|
|
29536
|
-
console.log(
|
|
29665
|
+
console.log(chalk2.yellow("Lose the passphrase = lose encrypted history. There is no recovery."));
|
|
29537
29666
|
}));
|
|
29538
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 () => {
|
|
29539
29668
|
const passphrase = await resolvePassphrase({ passphrase: opts.passphrase, envVar: opts.passphraseEnv });
|
|
29540
29669
|
const { vaultSync: vaultSync2 } = await Promise.resolve().then(() => (init_vault_client(), vault_client_exports));
|
|
29541
29670
|
const r = await vaultSync2(passphrase);
|
|
29542
29671
|
if (r.uploaded.length === 0 && r.skipped.length === 0 && r.failures.length === 0) {
|
|
29543
|
-
console.log(
|
|
29672
|
+
console.log(chalk2.dim("No chat sources found."));
|
|
29544
29673
|
return;
|
|
29545
29674
|
}
|
|
29546
|
-
if (r.uploaded.length) console.log(
|
|
29547
|
-
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)`));
|
|
29548
29677
|
for (const f of r.failures) {
|
|
29549
|
-
console.log(
|
|
29678
|
+
console.log(chalk2.red(` ! ${f.tool}/${f.sessionId}: ${f.error}`));
|
|
29550
29679
|
}
|
|
29551
29680
|
}));
|
|
29552
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 () => {
|
|
@@ -29554,13 +29683,13 @@ vault.command("restore").description("Pull encrypted blobs from the server, decr
|
|
|
29554
29683
|
const { vaultRestore: vaultRestore2 } = await Promise.resolve().then(() => (init_vault_client(), vault_client_exports));
|
|
29555
29684
|
const r = await vaultRestore2(passphrase, { since: opts.since ? Number(opts.since) : void 0 });
|
|
29556
29685
|
if (r.restored.length === 0 && r.skipped.length === 0 && r.failures.length === 0) {
|
|
29557
|
-
console.log(
|
|
29686
|
+
console.log(chalk2.dim("No blobs to restore."));
|
|
29558
29687
|
return;
|
|
29559
29688
|
}
|
|
29560
|
-
if (r.restored.length) console.log(
|
|
29561
|
-
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)`));
|
|
29562
29691
|
for (const f of r.failures) {
|
|
29563
|
-
console.log(
|
|
29692
|
+
console.log(chalk2.red(` ! ${f.tool}/${f.sessionId}: ${f.error}`));
|
|
29564
29693
|
}
|
|
29565
29694
|
}));
|
|
29566
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 () => {
|
|
@@ -29570,8 +29699,8 @@ vault.command("status").description("Show whether the Vault is enabled, salt fin
|
|
|
29570
29699
|
const probe = probeKeyring2();
|
|
29571
29700
|
const reportKeyring = () => {
|
|
29572
29701
|
if (!probe.available) {
|
|
29573
|
-
console.log(
|
|
29574
|
-
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}`));
|
|
29575
29704
|
return;
|
|
29576
29705
|
}
|
|
29577
29706
|
let stored = false;
|
|
@@ -29580,27 +29709,27 @@ vault.command("status").description("Show whether the Vault is enabled, salt fin
|
|
|
29580
29709
|
} catch {
|
|
29581
29710
|
stored = false;
|
|
29582
29711
|
}
|
|
29583
|
-
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`));
|
|
29584
29713
|
};
|
|
29585
29714
|
if (!s.enabled) {
|
|
29586
|
-
console.log(
|
|
29715
|
+
console.log(chalk2.dim("Vault: disabled. Run `chat-recall vault enable`."));
|
|
29587
29716
|
reportKeyring();
|
|
29588
29717
|
return;
|
|
29589
29718
|
}
|
|
29590
|
-
console.log(
|
|
29591
|
-
console.log(
|
|
29592
|
-
console.log(
|
|
29593
|
-
console.log(
|
|
29594
|
-
console.log(
|
|
29595
|
-
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)"}`));
|
|
29596
29725
|
reportKeyring();
|
|
29597
29726
|
}));
|
|
29598
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 () => {
|
|
29599
29728
|
const { keyringDelete: keyringDelete2 } = await Promise.resolve().then(() => (init_keyring(), keyring_exports));
|
|
29600
|
-
console.log(keyringDelete2() ?
|
|
29729
|
+
console.log(keyringDelete2() ? chalk2.green("\u2713 Passphrase removed from the OS keyring.") : chalk2.dim("Nothing stored in the OS keyring."));
|
|
29601
29730
|
}));
|
|
29602
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) => {
|
|
29603
|
-
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");
|
|
29604
29733
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
29605
29734
|
const here = fileURLToPath6(new URL(".", import.meta.url));
|
|
29606
29735
|
const findHook = (name) => {
|
|
@@ -29615,7 +29744,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29615
29744
|
const sourceEscalateHook = findHook("chat_recall_escalate_hook.sh");
|
|
29616
29745
|
const sourceWakeupHook = findHook("chat_recall_wakeup_hook.sh");
|
|
29617
29746
|
if (!sourceSaveHook) {
|
|
29618
|
-
console.error(
|
|
29747
|
+
console.error(chalk2.red("Could not locate chat_recall_save_hook.sh in the package."));
|
|
29619
29748
|
process.exit(1);
|
|
29620
29749
|
}
|
|
29621
29750
|
const hooksDir = getHooksDir();
|
|
@@ -29642,8 +29771,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29642
29771
|
if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
|
|
29643
29772
|
return c;
|
|
29644
29773
|
} catch (err) {
|
|
29645
|
-
console.error(
|
|
29646
|
-
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."));
|
|
29647
29776
|
process.exit(1);
|
|
29648
29777
|
}
|
|
29649
29778
|
};
|
|
@@ -29667,23 +29796,23 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29667
29796
|
if (removed === 0 && !existsSync57(hooksJson)) continue;
|
|
29668
29797
|
writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
|
|
29669
29798
|
total += removed;
|
|
29670
|
-
console.log(
|
|
29799
|
+
console.log(chalk2.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
|
|
29671
29800
|
}
|
|
29672
|
-
if (total === 0) console.log(
|
|
29673
|
-
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.)`));
|
|
29674
29803
|
return;
|
|
29675
29804
|
}
|
|
29676
29805
|
if (process.platform === "win32") {
|
|
29677
|
-
console.log(
|
|
29678
|
-
console.log(
|
|
29679
|
-
console.log(
|
|
29680
|
-
console.log(
|
|
29681
|
-
console.log(
|
|
29682
|
-
console.log(
|
|
29683
|
-
console.log(
|
|
29684
|
-
console.log(
|
|
29685
|
-
console.log(
|
|
29686
|
-
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."));
|
|
29687
29816
|
return;
|
|
29688
29817
|
}
|
|
29689
29818
|
mkdirSync20(hooksDir, { recursive: true });
|
|
@@ -29733,35 +29862,35 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29733
29862
|
mkdirSync20(dirname27(hooksJson), { recursive: true });
|
|
29734
29863
|
writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
|
|
29735
29864
|
}
|
|
29736
|
-
const saveSz =
|
|
29737
|
-
console.log(
|
|
29865
|
+
const saveSz = statSync47(installedSaveHook).size;
|
|
29866
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
|
|
29738
29867
|
if (installResume) {
|
|
29739
|
-
const resumeSz =
|
|
29740
|
-
console.log(
|
|
29868
|
+
const resumeSz = statSync47(installedResumeHook).size;
|
|
29869
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall resume-hint hook (${resumeSz} bytes)`));
|
|
29741
29870
|
}
|
|
29742
29871
|
if (installEscalate) {
|
|
29743
|
-
const escalateSz =
|
|
29744
|
-
console.log(
|
|
29872
|
+
const escalateSz = statSync47(installedEscalateHook).size;
|
|
29873
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall learnings-escalation hook (${escalateSz} bytes)`));
|
|
29745
29874
|
}
|
|
29746
29875
|
if (installWakeup) {
|
|
29747
|
-
const wakeupSz =
|
|
29748
|
-
console.log(
|
|
29876
|
+
const wakeupSz = statSync47(installedWakeupHook).size;
|
|
29877
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall wake-up hook (${wakeupSz} bytes)`));
|
|
29749
29878
|
for (const f of wakeupSkipped) {
|
|
29750
|
-
console.log(
|
|
29879
|
+
console.log(chalk2.dim(` (SessionStart already wired in ${f} \u2014 left as is, not duplicated)`));
|
|
29751
29880
|
}
|
|
29752
29881
|
}
|
|
29753
|
-
console.log(
|
|
29882
|
+
console.log(chalk2.dim(` scripts: ${hooksDir}`));
|
|
29754
29883
|
for (const [i, f] of hookConfigFiles.entries()) {
|
|
29755
|
-
console.log(
|
|
29884
|
+
console.log(chalk2.dim(` ${i === 0 ? "config: " : " "} ${f}`));
|
|
29756
29885
|
}
|
|
29757
|
-
console.log(
|
|
29886
|
+
console.log(chalk2.dim(` events: Stop, PreCompact${installResume ? ", UserPromptSubmit" : ""}${installEscalate ? ", SessionEnd" : ""}${installWakeup ? ", SessionStart" : ""}`));
|
|
29758
29887
|
console.log();
|
|
29759
|
-
console.log(
|
|
29888
|
+
console.log(chalk2.dim("Run `chat-recall install-hooks --uninstall` to remove later."));
|
|
29760
29889
|
if (!installResume) {
|
|
29761
|
-
console.log(
|
|
29890
|
+
console.log(chalk2.dim("(--no-resume-hint passed \u2014 UserPromptSubmit hook skipped)"));
|
|
29762
29891
|
}
|
|
29763
29892
|
if (!installEscalate) {
|
|
29764
|
-
console.log(
|
|
29893
|
+
console.log(chalk2.dim("(--no-escalate passed \u2014 SessionEnd hook skipped)"));
|
|
29765
29894
|
}
|
|
29766
29895
|
});
|
|
29767
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) => {
|
|
@@ -29777,12 +29906,12 @@ program.command("dossier <project>").description("Generate a project dossier (ov
|
|
|
29777
29906
|
const md = dossier.markdown;
|
|
29778
29907
|
if (options.out) {
|
|
29779
29908
|
writeFileSync21(options.out, md);
|
|
29780
|
-
console.log(
|
|
29909
|
+
console.log(chalk2.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
|
|
29781
29910
|
} else {
|
|
29782
29911
|
console.log(md);
|
|
29783
29912
|
}
|
|
29784
29913
|
} catch (err) {
|
|
29785
|
-
console.error(
|
|
29914
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
29786
29915
|
process.exit(1);
|
|
29787
29916
|
}
|
|
29788
29917
|
});
|
|
@@ -29795,17 +29924,17 @@ program.command("watch").description("Run the live indexer daemon (watches all A
|
|
|
29795
29924
|
try {
|
|
29796
29925
|
const { installService: installService2, isServiceRunning: isServiceRunning2, platformName: platformName2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
|
|
29797
29926
|
if (isServiceRunning2()) {
|
|
29798
|
-
console.log(
|
|
29927
|
+
console.log(chalk2.green(`\u2713 chat-recall-watch service already running (${platformName2()}).`));
|
|
29799
29928
|
return;
|
|
29800
29929
|
}
|
|
29801
29930
|
const paths = installService2();
|
|
29802
|
-
console.log(
|
|
29803
|
-
if (paths.definitionPath) console.log(
|
|
29804
|
-
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\``));
|
|
29805
29934
|
} catch (err) {
|
|
29806
29935
|
const se = err;
|
|
29807
|
-
console.error(
|
|
29808
|
-
if (se.hint) console.error(
|
|
29936
|
+
console.error(chalk2.red(se.message || String(err)));
|
|
29937
|
+
if (se.hint) console.error(chalk2.dim(` ${se.hint}`));
|
|
29809
29938
|
process.exit(1);
|
|
29810
29939
|
}
|
|
29811
29940
|
return;
|
|
@@ -29823,7 +29952,7 @@ program.command("watch").description("Run the live indexer daemon (watches all A
|
|
|
29823
29952
|
lastErr = err;
|
|
29824
29953
|
}
|
|
29825
29954
|
}
|
|
29826
|
-
console.error(
|
|
29955
|
+
console.error(chalk2.red("Could not load the watch daemon:"), lastErr instanceof Error ? lastErr.message : lastErr);
|
|
29827
29956
|
process.exit(1);
|
|
29828
29957
|
});
|
|
29829
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) => {
|
|
@@ -29832,25 +29961,25 @@ program.command("service <action>").description("Manage the per-user background
|
|
|
29832
29961
|
if (action === "install") {
|
|
29833
29962
|
const wasRunning = isServiceRunning2();
|
|
29834
29963
|
const paths = installService2();
|
|
29835
|
-
console.log(
|
|
29836
|
-
if (paths.definitionPath) console.log(
|
|
29837
|
-
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}`));
|
|
29838
29967
|
} else if (action === "uninstall") {
|
|
29839
29968
|
const removed = uninstallService2();
|
|
29840
|
-
if (removed) console.log(
|
|
29841
|
-
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."));
|
|
29842
29971
|
} else if (action === "status") {
|
|
29843
29972
|
const running = isServiceRunning2();
|
|
29844
|
-
console.log(`chat-recall-watch service (${platformName2()}): ${running ?
|
|
29845
|
-
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`"));
|
|
29846
29975
|
} else {
|
|
29847
|
-
console.error(
|
|
29976
|
+
console.error(chalk2.red(`Unknown action '${action}'.`) + chalk2.dim(" Use: install | uninstall | status"));
|
|
29848
29977
|
process.exit(1);
|
|
29849
29978
|
}
|
|
29850
29979
|
} catch (err) {
|
|
29851
29980
|
const se = err;
|
|
29852
|
-
console.error(
|
|
29853
|
-
if (se.hint) console.error(
|
|
29981
|
+
console.error(chalk2.red(se.message || String(err)));
|
|
29982
|
+
if (se.hint) console.error(chalk2.dim(` ${se.hint}`));
|
|
29854
29983
|
process.exit(1);
|
|
29855
29984
|
}
|
|
29856
29985
|
});
|
|
@@ -29871,7 +30000,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29871
30000
|
if (opts.token) {
|
|
29872
30001
|
const token = opts.token.trim();
|
|
29873
30002
|
if (/[^\x21-\x7e]/.test(token)) {
|
|
29874
|
-
console.error(
|
|
30003
|
+
console.error(chalk2.dim("Use the Copy button next to the token in the dashboard and paste the full line."));
|
|
29875
30004
|
return failLogin(opts, 'the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026")');
|
|
29876
30005
|
}
|
|
29877
30006
|
const { probeServer: identifyServer, probeOk: identified, probeAdvice: identifyAdvice } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
@@ -29880,7 +30009,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29880
30009
|
const check = await verifyToken(token);
|
|
29881
30010
|
if (!check.ok) {
|
|
29882
30011
|
if (check.status === 401 || check.status === 403) {
|
|
29883
|
-
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."));
|
|
29884
30013
|
return failLogin(opts, `${base2} rejected the token (HTTP ${check.status})`);
|
|
29885
30014
|
}
|
|
29886
30015
|
if (check.status !== void 0 && check.status >= 500) {
|
|
@@ -29892,12 +30021,12 @@ async function runLogin(serverUrl, opts) {
|
|
|
29892
30021
|
return failLogin(opts, `could not verify the token against ${base2}: ${check.error || `HTTP ${check.status}`}`);
|
|
29893
30022
|
}
|
|
29894
30023
|
saveCredentials2({ serverUrl, token });
|
|
29895
|
-
console.log(
|
|
30024
|
+
console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` server: ${serverUrl} (token verified)`));
|
|
29896
30025
|
return;
|
|
29897
30026
|
}
|
|
29898
30027
|
const existing = loadAllCredentials2().find((t) => t.serverUrl.replace(/\/+$/, "") === base2);
|
|
29899
30028
|
if (existing && (await verifyToken(existing.token)).ok) {
|
|
29900
|
-
console.log(
|
|
30029
|
+
console.log(chalk2.green("\u2713 Already connected.") + chalk2.dim(` server: ${serverUrl} (existing token verified)`));
|
|
29901
30030
|
return;
|
|
29902
30031
|
}
|
|
29903
30032
|
try {
|
|
@@ -29907,14 +30036,14 @@ async function runLogin(serverUrl, opts) {
|
|
|
29907
30036
|
const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
|
|
29908
30037
|
if (probe.status === 200) {
|
|
29909
30038
|
saveCredentials2({ serverUrl, token: "" });
|
|
29910
|
-
console.log(
|
|
30039
|
+
console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
|
|
29911
30040
|
return;
|
|
29912
30041
|
}
|
|
29913
30042
|
}
|
|
29914
30043
|
} catch {
|
|
29915
30044
|
}
|
|
29916
30045
|
if (opts.check) {
|
|
29917
|
-
console.error(
|
|
30046
|
+
console.error(chalk2.dim(`Not connected to ${serverUrl}.`));
|
|
29918
30047
|
process.exit(1);
|
|
29919
30048
|
}
|
|
29920
30049
|
try {
|
|
@@ -29942,7 +30071,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29942
30071
|
if (!authProvider && !issuer) {
|
|
29943
30072
|
authProvider = "better-auth";
|
|
29944
30073
|
if (capsError) {
|
|
29945
|
-
console.log(` ${
|
|
30074
|
+
console.log(` ${chalk2.yellow("Could not read the server's capabilities")} (${capsError}) \u2014 assuming the standard sign-in flow.`);
|
|
29946
30075
|
void Promise.resolve().then(() => (init_install_report(), install_report_exports)).then(({ reportInstallFailure: reportInstallFailure2 }) => reportInstallFailure2(base2, {
|
|
29947
30076
|
step: "cli_login_probe",
|
|
29948
30077
|
reason: capsError?.slice(0, 40) ?? null,
|
|
@@ -29957,13 +30086,13 @@ async function runLogin(serverUrl, opts) {
|
|
|
29957
30086
|
return;
|
|
29958
30087
|
}
|
|
29959
30088
|
console.log();
|
|
29960
|
-
console.log(
|
|
29961
|
-
console.log(" " +
|
|
30089
|
+
console.log(chalk2.bold("Open this to approve the machine:"));
|
|
30090
|
+
console.log(" " + chalk2.cyan(p.url));
|
|
29962
30091
|
if (openBrowser(p.url)) {
|
|
29963
|
-
console.log(
|
|
30092
|
+
console.log(chalk2.dim(" (tried to open it for you \u2014 if no window appeared, use the link above)"));
|
|
29964
30093
|
}
|
|
29965
|
-
console.log(
|
|
29966
|
-
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"));
|
|
29967
30096
|
};
|
|
29968
30097
|
const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
|
|
29969
30098
|
const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };
|
|
@@ -29979,9 +30108,9 @@ async function runLogin(serverUrl, opts) {
|
|
|
29979
30108
|
const created = await fetch(`${base2}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
|
|
29980
30109
|
if (!created.ok) throw new Error(`workspace create failed: HTTP ${created.status} ${await created.text().catch(() => "")}`);
|
|
29981
30110
|
slug = (await readJsonBody(created, base2, "POST /api/teams")).slug;
|
|
29982
|
-
console.log(
|
|
30111
|
+
console.log(chalk2.dim(`Created workspace "${name}" (${slug}) \u2014 you're its owner.`));
|
|
29983
30112
|
} else {
|
|
29984
|
-
teams.forEach((t) => console.error(` ${t.team_slug} ${
|
|
30113
|
+
teams.forEach((t) => console.error(` ${t.team_slug} ${chalk2.dim(t.name)}`));
|
|
29985
30114
|
throw new Error("you belong to multiple teams \u2014 pass --team <slug> (listed above)");
|
|
29986
30115
|
}
|
|
29987
30116
|
}
|
|
@@ -29994,13 +30123,13 @@ async function runLogin(serverUrl, opts) {
|
|
|
29994
30123
|
if (!mint.ok) throw new Error(`token mint failed: HTTP ${mint.status} ${await mint.text().catch(() => "")}`);
|
|
29995
30124
|
const { token } = await readJsonBody(mint, base2, "POST /api/teams/:slug/tokens");
|
|
29996
30125
|
saveCredentials2({ serverUrl, token });
|
|
29997
|
-
console.log(
|
|
30126
|
+
console.log(chalk2.green(`\u2713 Logged in to ${chalk2.bold(slug)}`) + chalk2.dim(` (device: ${deviceId}) server: ${serverUrl}`));
|
|
29998
30127
|
if (!opts.fromInit) {
|
|
29999
|
-
console.log(
|
|
30128
|
+
console.log(chalk2.dim("Run `chat-recall sync` to push redacted conversations."));
|
|
30000
30129
|
}
|
|
30001
30130
|
} catch (err) {
|
|
30002
30131
|
if (opts.fromInit) throw err instanceof Error ? err : new Error(String(err));
|
|
30003
|
-
console.error(
|
|
30132
|
+
console.error(chalk2.red("login failed:"), err instanceof Error ? err.message : err);
|
|
30004
30133
|
process.exit(1);
|
|
30005
30134
|
}
|
|
30006
30135
|
}
|
|
@@ -30008,15 +30137,15 @@ var exclude = program.command("exclude").description("Control what never leaves
|
|
|
30008
30137
|
exclude.command("list", { isDefault: true }).description("Show current exclusions").action(async () => {
|
|
30009
30138
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30010
30139
|
const s = loadSettings2();
|
|
30011
|
-
console.log(
|
|
30012
|
-
console.log(` tools: ${s.sync.excludeTools.length ? s.sync.excludeTools.join(", ") :
|
|
30013
|
-
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")}`);
|
|
30014
30143
|
for (const p of s.sync.excludeProjects) console.log(` ${p}`);
|
|
30015
30144
|
const deny = s.privacy.projectDenylist || [];
|
|
30016
|
-
console.log(` index denylist (never even indexed): ${deny.length ? "" :
|
|
30145
|
+
console.log(` index denylist (never even indexed): ${deny.length ? "" : chalk2.dim("none")}`);
|
|
30017
30146
|
for (const p of deny) console.log(` ${p}`);
|
|
30018
|
-
console.log(
|
|
30019
|
-
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>`."));
|
|
30020
30149
|
const inc = s.sync.includeProjects || [];
|
|
30021
30150
|
if (inc.length) {
|
|
30022
30151
|
console.log(` personal paths opted back in:`);
|
|
@@ -30027,8 +30156,8 @@ var include = program.command("include").description("Opt a personal folder (Pic
|
|
|
30027
30156
|
include.command("list", { isDefault: true }).description("Show paths opted back into indexing").action(async () => {
|
|
30028
30157
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30029
30158
|
const inc = loadSettings2().sync.includeProjects || [];
|
|
30030
|
-
console.log(
|
|
30031
|
-
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"));
|
|
30032
30161
|
for (const p of inc) console.log(` ${p}`);
|
|
30033
30162
|
});
|
|
30034
30163
|
include.command("project <path>").description("Re-permit a path under a personal folder (substring match; e.g. ~/Documents/code)").action(async (path) => {
|
|
@@ -30039,12 +30168,12 @@ include.command("project <path>").description("Re-permit a path under a personal
|
|
|
30039
30168
|
const s = loadSettings2();
|
|
30040
30169
|
s.sync.includeProjects = s.sync.includeProjects || [];
|
|
30041
30170
|
if (s.sync.includeProjects.includes(abs)) {
|
|
30042
|
-
console.log(
|
|
30171
|
+
console.log(chalk2.dim(`Already included: ${abs}`));
|
|
30043
30172
|
return;
|
|
30044
30173
|
}
|
|
30045
30174
|
s.sync.includeProjects.push(abs);
|
|
30046
30175
|
saveSettings2(s);
|
|
30047
|
-
console.log(
|
|
30176
|
+
console.log(chalk2.green(`\u2713 Included ${abs}`) + chalk2.dim(" \u2014 code indexing may now walk it (takes effect next discovery tick)."));
|
|
30048
30177
|
});
|
|
30049
30178
|
include.command("remove <path>").description("Undo an opt-in (personal folder goes back to skipped)").action(async (path) => {
|
|
30050
30179
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30055,30 +30184,30 @@ include.command("remove <path>").description("Undo an opt-in (personal folder go
|
|
|
30055
30184
|
const before = (s.sync.includeProjects || []).length;
|
|
30056
30185
|
s.sync.includeProjects = (s.sync.includeProjects || []).filter((p) => p !== path && p !== abs);
|
|
30057
30186
|
if (s.sync.includeProjects.length === before) {
|
|
30058
|
-
console.error(
|
|
30187
|
+
console.error(chalk2.red(`Not in the include list: ${path}`));
|
|
30059
30188
|
process.exit(1);
|
|
30060
30189
|
}
|
|
30061
30190
|
saveSettings2(s);
|
|
30062
|
-
console.log(
|
|
30191
|
+
console.log(chalk2.green(`\u2713 Removed ${path}`) + chalk2.dim(" \u2014 back to skipped."));
|
|
30063
30192
|
});
|
|
30064
30193
|
var retention = program.command("retention").description("How long the server keeps your synced sessions. Bare `retention` shows the current window.");
|
|
30065
30194
|
retention.command("show", { isDefault: true }).description("Show the window and how many sessions it would delete").action(async () => {
|
|
30066
30195
|
const t = requireTarget();
|
|
30067
30196
|
const r = await serverGet("/api/data/retention");
|
|
30068
|
-
console.log(
|
|
30197
|
+
console.log(chalk2.bold("Retention") + chalk2.dim(` (server: ${t.base})`));
|
|
30069
30198
|
if (!r.days) {
|
|
30070
|
-
console.log(` window: ${
|
|
30199
|
+
console.log(` window: ${chalk2.green("none")} \u2014 the server keeps everything you sync`);
|
|
30071
30200
|
} else {
|
|
30072
|
-
console.log(` window: ${
|
|
30201
|
+
console.log(` window: ${chalk2.yellow(`${r.days} days`)}`);
|
|
30073
30202
|
console.log(` sessions currently outside it: ${r.wouldDelete}`);
|
|
30074
30203
|
}
|
|
30075
|
-
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`));
|
|
30076
30205
|
});
|
|
30077
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) => {
|
|
30078
30207
|
requireTarget();
|
|
30079
30208
|
const days = Number(daysArg);
|
|
30080
30209
|
if (!Number.isInteger(days) || days < 0) {
|
|
30081
|
-
console.error(
|
|
30210
|
+
console.error(chalk2.red(`'${daysArg}' is not a whole number of days.`));
|
|
30082
30211
|
process.exit(1);
|
|
30083
30212
|
}
|
|
30084
30213
|
const preview = await serverGet(
|
|
@@ -30086,23 +30215,23 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30086
30215
|
);
|
|
30087
30216
|
if (days === 0) {
|
|
30088
30217
|
await serverPost("/api/data/retention", { days: 0 });
|
|
30089
|
-
console.log(
|
|
30218
|
+
console.log(chalk2.green("\u2713 Window cleared") + chalk2.dim(" \u2014 the server keeps everything you sync."));
|
|
30090
30219
|
return;
|
|
30091
30220
|
}
|
|
30092
30221
|
if (preview.wouldDelete > 0) {
|
|
30093
30222
|
console.log();
|
|
30094
|
-
console.log(
|
|
30095
|
-
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.`));
|
|
30096
30225
|
console.log();
|
|
30097
|
-
console.log(
|
|
30098
|
-
console.log(
|
|
30099
|
-
console.log(
|
|
30100
|
-
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"));
|
|
30101
30230
|
console.log();
|
|
30102
30231
|
if (!opts.yes) {
|
|
30103
30232
|
if (!process.stdin.isTTY) {
|
|
30104
|
-
console.error(
|
|
30105
|
-
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."));
|
|
30106
30235
|
process.exit(1);
|
|
30107
30236
|
}
|
|
30108
30237
|
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
@@ -30110,7 +30239,7 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30110
30239
|
const answer = (await rl.question(`Type ${preview.wouldDelete} to confirm: `)).trim();
|
|
30111
30240
|
rl.close();
|
|
30112
30241
|
if (answer !== String(preview.wouldDelete)) {
|
|
30113
|
-
console.log(
|
|
30242
|
+
console.log(chalk2.dim("Cancelled \u2014 nothing changed."));
|
|
30114
30243
|
return;
|
|
30115
30244
|
}
|
|
30116
30245
|
}
|
|
@@ -30119,20 +30248,20 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30119
30248
|
"/api/data/retention",
|
|
30120
30249
|
{ days, acknowledge: true }
|
|
30121
30250
|
);
|
|
30122
|
-
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.`));
|
|
30123
30252
|
});
|
|
30124
30253
|
var syncOnly = program.command("sync-only").description("Opt-in sync: ship ONLY chosen projects. Bare `sync-only` shows the mode + allowlist.");
|
|
30125
30254
|
syncOnly.command("list", { isDefault: true }).description("Show the sync mode and allowlisted projects").action(async () => {
|
|
30126
30255
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30127
30256
|
const s = loadSettings2();
|
|
30128
30257
|
const mode = s.sync.syncMode ?? "all";
|
|
30129
|
-
console.log(
|
|
30130
|
-
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)")}`);
|
|
30131
30260
|
const list = s.sync.syncOnlyProjects ?? [];
|
|
30132
|
-
console.log(` projects: ${list.length ? "" :
|
|
30261
|
+
console.log(` projects: ${list.length ? "" : chalk2.dim("none")}`);
|
|
30133
30262
|
for (const p of list) console.log(` ${p}`);
|
|
30134
|
-
if (mode === "only" && list.length === 0) console.log(
|
|
30135
|
-
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>`."));
|
|
30136
30265
|
});
|
|
30137
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) => {
|
|
30138
30267
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30141,7 +30270,7 @@ syncOnly.command("add <project>").description('Add a project id (git:\u2026/ws:\
|
|
|
30141
30270
|
if (!s.sync.syncOnlyProjects.includes(project)) s.sync.syncOnlyProjects.push(project);
|
|
30142
30271
|
s.sync.syncMode = "only";
|
|
30143
30272
|
saveSettings2(s);
|
|
30144
|
-
console.log(
|
|
30273
|
+
console.log(chalk2.green(`\u2713 Added ${project}`) + chalk2.dim(' \u2014 mode is now "only"; next sync ships only allowlisted projects.'));
|
|
30145
30274
|
});
|
|
30146
30275
|
syncOnly.command("remove <project>").description("Remove a project from the allowlist").action(async (project) => {
|
|
30147
30276
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30149,18 +30278,18 @@ syncOnly.command("remove <project>").description("Remove a project from the allo
|
|
|
30149
30278
|
const before = (s.sync.syncOnlyProjects || []).length;
|
|
30150
30279
|
s.sync.syncOnlyProjects = (s.sync.syncOnlyProjects || []).filter((p) => p !== project);
|
|
30151
30280
|
if (s.sync.syncOnlyProjects.length === before) {
|
|
30152
|
-
console.error(
|
|
30281
|
+
console.error(chalk2.red(`Not in the allowlist: ${project}`));
|
|
30153
30282
|
process.exit(1);
|
|
30154
30283
|
}
|
|
30155
30284
|
saveSettings2(s);
|
|
30156
|
-
console.log(
|
|
30285
|
+
console.log(chalk2.green(`\u2713 Removed ${project}`));
|
|
30157
30286
|
});
|
|
30158
30287
|
syncOnly.command("all").description("Switch back to syncing ALL non-excluded projects (keeps the list, just disables opt-in mode)").action(async () => {
|
|
30159
30288
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30160
30289
|
const s = loadSettings2();
|
|
30161
30290
|
s.sync.syncMode = "all";
|
|
30162
30291
|
saveSettings2(s);
|
|
30163
|
-
console.log(
|
|
30292
|
+
console.log(chalk2.green("\u2713 Sync mode: all") + chalk2.dim(" \u2014 every non-excluded project syncs again."));
|
|
30164
30293
|
});
|
|
30165
30294
|
program.command("projects").description("List distinct project ids discovered on this machine (for `sync-only add`)").action(async () => {
|
|
30166
30295
|
const { listAvailableBackends: listAvailableBackends2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
|
|
@@ -30180,11 +30309,11 @@ program.command("projects").description("List distinct project ids discovered on
|
|
|
30180
30309
|
}
|
|
30181
30310
|
const rows = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
30182
30311
|
if (!rows.length) {
|
|
30183
|
-
console.log(
|
|
30312
|
+
console.log(chalk2.dim("No projects discovered."));
|
|
30184
30313
|
return;
|
|
30185
30314
|
}
|
|
30186
|
-
console.log(
|
|
30187
|
-
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)}`);
|
|
30188
30317
|
});
|
|
30189
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) => {
|
|
30190
30319
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30193,28 +30322,28 @@ exclude.command("project <path>").description("Exclude a project path from synci
|
|
|
30193
30322
|
const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
|
|
30194
30323
|
const s = loadSettings2();
|
|
30195
30324
|
if (s.sync.excludeProjects.includes(abs)) {
|
|
30196
|
-
console.log(
|
|
30325
|
+
console.log(chalk2.dim(`Already excluded: ${abs}`));
|
|
30197
30326
|
return;
|
|
30198
30327
|
}
|
|
30199
30328
|
s.sync.excludeProjects.push(abs);
|
|
30200
30329
|
saveSettings2(s);
|
|
30201
|
-
console.log(
|
|
30330
|
+
console.log(chalk2.green(`\u2713 Excluded ${abs}`) + chalk2.dim(" \u2014 takes effect next sync; already-synced rows stay until deleted."));
|
|
30202
30331
|
});
|
|
30203
30332
|
exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude | gemini | codex | opencode | agy | cursor)").action(async (tool) => {
|
|
30204
30333
|
const valid = ["claude", "gemini", "codex", "opencode", "agy", "cursor"];
|
|
30205
30334
|
if (!valid.includes(tool)) {
|
|
30206
|
-
console.error(
|
|
30335
|
+
console.error(chalk2.red(`Unknown tool '${tool}'.`) + chalk2.dim(` Use one of: ${valid.join(" | ")}`));
|
|
30207
30336
|
process.exit(1);
|
|
30208
30337
|
}
|
|
30209
30338
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30210
30339
|
const s = loadSettings2();
|
|
30211
30340
|
if (s.sync.excludeTools.includes(tool)) {
|
|
30212
|
-
console.log(
|
|
30341
|
+
console.log(chalk2.dim(`Already excluded: ${tool}`));
|
|
30213
30342
|
return;
|
|
30214
30343
|
}
|
|
30215
30344
|
s.sync.excludeTools.push(tool);
|
|
30216
30345
|
saveSettings2(s);
|
|
30217
|
-
console.log(
|
|
30346
|
+
console.log(chalk2.green(`\u2713 Excluded ${tool}`) + chalk2.dim(" \u2014 nothing from it leaves this machine from the next sync on."));
|
|
30218
30347
|
});
|
|
30219
30348
|
exclude.command("remove <value>").description("Remove an exclusion (tool name or project path)").action(async (value) => {
|
|
30220
30349
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30227,20 +30356,20 @@ exclude.command("remove <value>").description("Remove an exclusion (tool name or
|
|
|
30227
30356
|
s.sync.excludeTools = s.sync.excludeTools.filter((t) => t !== value);
|
|
30228
30357
|
s.sync.excludeProjects = s.sync.excludeProjects.filter((p) => p !== value && p !== abs);
|
|
30229
30358
|
if (s.sync.excludeTools.length === beforeTools && s.sync.excludeProjects.length === beforeProjects) {
|
|
30230
|
-
console.error(
|
|
30359
|
+
console.error(chalk2.red(`No exclusion matches '${value}'.`) + chalk2.dim(" See: chat-recall exclude list"));
|
|
30231
30360
|
process.exit(1);
|
|
30232
30361
|
}
|
|
30233
30362
|
saveSettings2(s);
|
|
30234
|
-
console.log(
|
|
30363
|
+
console.log(chalk2.green(`\u2713 Removed ${value}`));
|
|
30235
30364
|
});
|
|
30236
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));
|
|
30237
30366
|
program.command("logout <server-url>").description("Remove one sync target (other targets keep syncing)").action(async (serverUrl) => {
|
|
30238
30367
|
const { removeCredentials: removeCredentials2, loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30239
30368
|
if (removeCredentials2(serverUrl)) {
|
|
30240
30369
|
const left = loadAllCredentials2();
|
|
30241
|
-
console.log(
|
|
30370
|
+
console.log(chalk2.green(`\u2713 Removed ${serverUrl}`) + chalk2.dim(` \u2014 ${left.length} target(s) remain`));
|
|
30242
30371
|
} else {
|
|
30243
|
-
console.error(
|
|
30372
|
+
console.error(chalk2.red(`No such target: ${serverUrl}`));
|
|
30244
30373
|
process.exit(1);
|
|
30245
30374
|
}
|
|
30246
30375
|
});
|
|
@@ -30248,7 +30377,7 @@ program.command("delete <session-id>").description("Delete a session from chat-r
|
|
|
30248
30377
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30249
30378
|
const targets = loadAllCredentials2();
|
|
30250
30379
|
if (targets.length === 0) {
|
|
30251
|
-
console.error(
|
|
30380
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30252
30381
|
process.exit(1);
|
|
30253
30382
|
}
|
|
30254
30383
|
let ok = 0;
|
|
@@ -30260,20 +30389,20 @@ program.command("delete <session-id>").description("Delete a session from chat-r
|
|
|
30260
30389
|
});
|
|
30261
30390
|
if (res.ok) {
|
|
30262
30391
|
ok++;
|
|
30263
|
-
console.log(
|
|
30264
|
-
} 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}`));
|
|
30265
30394
|
} catch (e) {
|
|
30266
|
-
console.error(
|
|
30395
|
+
console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
|
|
30267
30396
|
}
|
|
30268
30397
|
}
|
|
30269
30398
|
if (ok === 0) process.exit(1);
|
|
30270
|
-
console.log(
|
|
30399
|
+
console.log(chalk2.dim(`Tombstoned on ${ok}/${targets.length} server(s) \u2014 re-sync cannot resurrect it.`));
|
|
30271
30400
|
});
|
|
30272
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) => {
|
|
30273
30402
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30274
30403
|
const targets = loadAllCredentials2();
|
|
30275
30404
|
if (targets.length === 0) {
|
|
30276
|
-
console.error(
|
|
30405
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30277
30406
|
process.exit(1);
|
|
30278
30407
|
}
|
|
30279
30408
|
let ok = 0;
|
|
@@ -30286,14 +30415,14 @@ program.command("rename <session-id> <name>").description('Give a session a memo
|
|
|
30286
30415
|
});
|
|
30287
30416
|
if (res.ok) {
|
|
30288
30417
|
ok++;
|
|
30289
|
-
console.log(
|
|
30290
|
-
} 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}`));
|
|
30291
30420
|
} catch (e) {
|
|
30292
|
-
console.error(
|
|
30421
|
+
console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
|
|
30293
30422
|
}
|
|
30294
30423
|
}
|
|
30295
30424
|
if (ok === 0) process.exit(1);
|
|
30296
|
-
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).`));
|
|
30297
30426
|
});
|
|
30298
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) => {
|
|
30299
30428
|
const { syncSessions: syncSessions2, syncIncremental: syncIncremental2, isSyncSkip: isSyncSkip2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
@@ -30302,17 +30431,17 @@ program.command("sync").description("Push redacted conversations to the configur
|
|
|
30302
30431
|
const r2 = await syncIncremental2();
|
|
30303
30432
|
if (isSyncSkip2(r2)) {
|
|
30304
30433
|
if (r2.skipped === "lock-held") {
|
|
30305
|
-
console.log(
|
|
30434
|
+
console.log(chalk2.dim("Another sync is already running (MCP tick or watch daemon) \u2014 your data is on its way."));
|
|
30306
30435
|
return;
|
|
30307
30436
|
}
|
|
30308
30437
|
const msg = {
|
|
30309
30438
|
"no-credentials": "Not logged in \u2014 run `chat-recall login <server-url>` first.",
|
|
30310
30439
|
"paused": "Sync is paused in settings \u2014 re-run `chat-recall login <server-url>` to resume."
|
|
30311
30440
|
}[r2.skipped];
|
|
30312
|
-
console.error(
|
|
30441
|
+
console.error(chalk2.red(msg));
|
|
30313
30442
|
process.exit(1);
|
|
30314
30443
|
}
|
|
30315
|
-
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)`));
|
|
30316
30445
|
return;
|
|
30317
30446
|
}
|
|
30318
30447
|
const sinceMs = opts.sinceHours ? Date.now() - Number(opts.sinceHours) * 36e5 : void 0;
|
|
@@ -30327,9 +30456,9 @@ program.command("sync").description("Push redacted conversations to the configur
|
|
|
30327
30456
|
throttleMs: opts.throttle !== void 0 ? Number(opts.throttle) : opts.full ? 3e3 : void 0,
|
|
30328
30457
|
prune: !!opts.prune
|
|
30329
30458
|
});
|
|
30330
|
-
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)` : ""}`));
|
|
30331
30460
|
} catch (err) {
|
|
30332
|
-
console.error(
|
|
30461
|
+
console.error(chalk2.red("sync failed:"), err instanceof Error ? err.message : err);
|
|
30333
30462
|
process.exit(1);
|
|
30334
30463
|
}
|
|
30335
30464
|
});
|
|
@@ -30338,11 +30467,11 @@ program.command("reconcile").description("Reconcile DERIVED FIELDS (e.g. native
|
|
|
30338
30467
|
try {
|
|
30339
30468
|
const r = await reconcileFields2({ force: !!opts.force });
|
|
30340
30469
|
const per = Object.entries(r.perTarget).map(([s, v]) => `${s}: ${v.pushed} pushed`).join(", ");
|
|
30341
|
-
console.log(
|
|
30470
|
+
console.log(chalk2.green(`\u2713 Reconciled derived fields`) + chalk2.dim(
|
|
30342
30471
|
` \u2014 ${r.sessions} sessions, ${r.scanned} scanned, ${r.pushed} pushed, ${r.absent} absent${per ? ` (${per})` : ""}`
|
|
30343
30472
|
));
|
|
30344
30473
|
} catch (err) {
|
|
30345
|
-
console.error(
|
|
30474
|
+
console.error(chalk2.red("reconcile failed:"), err instanceof Error ? err.message : err);
|
|
30346
30475
|
process.exit(1);
|
|
30347
30476
|
}
|
|
30348
30477
|
});
|
|
@@ -30352,7 +30481,7 @@ program.command("update").description("Update this CLI now from the server you s
|
|
|
30352
30481
|
const { cliVersion: cliVersion3 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
30353
30482
|
const targets = loadAllCredentials();
|
|
30354
30483
|
if (!targets.length) {
|
|
30355
|
-
console.error(
|
|
30484
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30356
30485
|
process.exit(1);
|
|
30357
30486
|
}
|
|
30358
30487
|
let installFailed = false;
|
|
@@ -30361,17 +30490,17 @@ program.command("update").description("Update this CLI now from the server you s
|
|
|
30361
30490
|
const headers = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
30362
30491
|
const r = await runAutoUpdate2(base2, headers, cliVersion3());
|
|
30363
30492
|
if (r.updated) {
|
|
30364
|
-
console.log(
|
|
30493
|
+
console.log(chalk2.green(`\u2713 ${r.reason}`) + chalk2.dim(` (${base2})`));
|
|
30365
30494
|
continue;
|
|
30366
30495
|
}
|
|
30367
30496
|
const benign = /already current|no CLI release|disabled/.test(r.reason);
|
|
30368
30497
|
if (/install failed|download failed|checksum/.test(r.reason)) installFailed = true;
|
|
30369
|
-
console.log((benign ?
|
|
30498
|
+
console.log((benign ? chalk2.dim("\u2022 ") : chalk2.red("\u2717 ")) + r.reason + chalk2.dim(` (${base2})`));
|
|
30370
30499
|
}
|
|
30371
30500
|
await refreshUpdateCheck2(true);
|
|
30372
30501
|
if (installFailed) {
|
|
30373
|
-
console.log(
|
|
30374
|
-
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)`));
|
|
30375
30504
|
}
|
|
30376
30505
|
});
|
|
30377
30506
|
program.hook("preAction", () => {
|