chat-recall 0.5.29 → 0.5.31
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 -4
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +91 -2
- package/dist/auto-update.js.map +1 -1
- package/dist/cli.js +740 -564
- 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 +141 -58
- package/dist/scan-worker.js +2 -15
- package/dist/sync-client.d.ts +37 -0
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +113 -18
- package/dist/sync-client.js.map +1 -1
- package/dist/target-breaker.d.ts +13 -0
- package/dist/target-breaker.d.ts.map +1 -1
- package/dist/target-breaker.js +6 -4
- package/dist/target-breaker.js.map +1 -1
- package/dist/telemetry.d.ts +14 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +23 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/watch.js +150 -67
- 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
|
}
|
|
@@ -10086,19 +10086,6 @@ var init_codex2 = __esm({
|
|
|
10086
10086
|
// ../engine/dist/core/backends/agy.js
|
|
10087
10087
|
import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync8 } from "fs";
|
|
10088
10088
|
import { basename as basename9, dirname as dirname10, join as join11 } from "path";
|
|
10089
|
-
function getFallbackProjectPath(homeDir) {
|
|
10090
|
-
try {
|
|
10091
|
-
const settingsPath2 = join11(homeDir, "settings.json");
|
|
10092
|
-
if (existsSync9(settingsPath2)) {
|
|
10093
|
-
const json = JSON.parse(readFileSync5(settingsPath2, "utf-8"));
|
|
10094
|
-
if (Array.isArray(json?.trustedWorkspaces) && json.trustedWorkspaces[0]) {
|
|
10095
|
-
return json.trustedWorkspaces[0];
|
|
10096
|
-
}
|
|
10097
|
-
}
|
|
10098
|
-
} catch {
|
|
10099
|
-
}
|
|
10100
|
-
return "";
|
|
10101
|
-
}
|
|
10102
10089
|
function agyTranscriptPath(logsDir) {
|
|
10103
10090
|
const full = join11(logsDir, "transcript_full.jsonl");
|
|
10104
10091
|
if (existsSync9(full))
|
|
@@ -10111,8 +10098,8 @@ function agyTranscriptPath(logsDir) {
|
|
|
10111
10098
|
function resolveAgyProject(logsDir, homeDir) {
|
|
10112
10099
|
const deriveFrom = agyTranscriptPath(logsDir);
|
|
10113
10100
|
if (!deriveFrom)
|
|
10114
|
-
return
|
|
10115
|
-
return extractProjectPathFromTranscript(deriveFrom, homeDir)
|
|
10101
|
+
return "";
|
|
10102
|
+
return extractProjectPathFromTranscript(deriveFrom, homeDir);
|
|
10116
10103
|
}
|
|
10117
10104
|
function extractProjectPathFromTranscript(path, homeDir) {
|
|
10118
10105
|
let content;
|
|
@@ -19362,7 +19349,7 @@ var init_extractor_version = __esm({
|
|
|
19362
19349
|
command: 1
|
|
19363
19350
|
};
|
|
19364
19351
|
TOOL_EXTRACTOR_BUMP = {
|
|
19365
|
-
agy:
|
|
19352
|
+
agy: 3,
|
|
19366
19353
|
claude: 1,
|
|
19367
19354
|
codex: 1
|
|
19368
19355
|
};
|
|
@@ -19750,7 +19737,7 @@ var init_http = __esm({
|
|
|
19750
19737
|
CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
19751
19738
|
} catch {
|
|
19752
19739
|
try {
|
|
19753
|
-
if (true) CLI_VERSION = "0.5.
|
|
19740
|
+
if (true) CLI_VERSION = "0.5.31";
|
|
19754
19741
|
} catch {
|
|
19755
19742
|
}
|
|
19756
19743
|
}
|
|
@@ -20100,9 +20087,9 @@ function cooldownFor(failures) {
|
|
|
20100
20087
|
function breakerState(serverUrl, now = Date.now()) {
|
|
20101
20088
|
const s = state.get(key(serverUrl));
|
|
20102
20089
|
if (!s || s.openUntil <= now) {
|
|
20103
|
-
return { open: false, retryInMs: 0, failures: s?.failures ?? 0, lastError: s?.lastError ?? "" };
|
|
20090
|
+
return { open: false, retryInMs: 0, failures: s?.failures ?? 0, lastError: s?.lastError ?? "", tripped: false };
|
|
20104
20091
|
}
|
|
20105
|
-
return { open: true, retryInMs: s.openUntil - now, failures: s.failures, lastError: s.lastError };
|
|
20092
|
+
return { open: true, retryInMs: s.openUntil - now, failures: s.failures, lastError: s.lastError, tripped: false };
|
|
20106
20093
|
}
|
|
20107
20094
|
function noteTargetSuccess(serverUrl) {
|
|
20108
20095
|
state.delete(key(serverUrl));
|
|
@@ -20114,11 +20101,11 @@ function noteTargetFailure(serverUrl, error, now = Date.now()) {
|
|
|
20114
20101
|
const lastError = error.slice(0, 200);
|
|
20115
20102
|
if (failures < TRIP_AFTER_FAILURES) {
|
|
20116
20103
|
state.set(k, { failures, openUntil: 0, lastError });
|
|
20117
|
-
return { open: false, retryInMs: 0, failures, lastError };
|
|
20104
|
+
return { open: false, retryInMs: 0, failures, lastError, tripped: false };
|
|
20118
20105
|
}
|
|
20119
20106
|
const cooldown = cooldownFor(failures);
|
|
20120
20107
|
state.set(k, { failures, openUntil: now + cooldown, lastError });
|
|
20121
|
-
return { open: true, retryInMs: cooldown, failures, lastError };
|
|
20108
|
+
return { open: true, retryInMs: cooldown, failures, lastError, tripped: failures === TRIP_AFTER_FAILURES };
|
|
20122
20109
|
}
|
|
20123
20110
|
function breakerNotice(serverUrl, v) {
|
|
20124
20111
|
if (!v.open) return null;
|
|
@@ -20245,6 +20232,14 @@ async function flush() {
|
|
|
20245
20232
|
}
|
|
20246
20233
|
}));
|
|
20247
20234
|
}
|
|
20235
|
+
function stableMark(value) {
|
|
20236
|
+
try {
|
|
20237
|
+
const { createHash: createHash20 } = __require("node:crypto");
|
|
20238
|
+
return createHash20("sha256").update(value).digest("hex").slice(0, 12);
|
|
20239
|
+
} catch {
|
|
20240
|
+
return "";
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20248
20243
|
function hashHost() {
|
|
20249
20244
|
try {
|
|
20250
20245
|
const { createHash: createHash20 } = __require("node:crypto");
|
|
@@ -20262,7 +20257,7 @@ var init_telemetry = __esm({
|
|
|
20262
20257
|
init_sync_client();
|
|
20263
20258
|
cliVersion2 = () => {
|
|
20264
20259
|
try {
|
|
20265
|
-
return true ? "0.5.
|
|
20260
|
+
return true ? "0.5.31" : "dev";
|
|
20266
20261
|
} catch {
|
|
20267
20262
|
return "dev";
|
|
20268
20263
|
}
|
|
@@ -20599,12 +20594,13 @@ __export(auto_update_exports, {
|
|
|
20599
20594
|
planAutoUpdate: () => planAutoUpdate,
|
|
20600
20595
|
runAutoUpdate: () => runAutoUpdate,
|
|
20601
20596
|
runningPrefix: () => runningPrefix,
|
|
20597
|
+
sweepStaleStaging: () => sweepStaleStaging,
|
|
20602
20598
|
tarballUrl: () => tarballUrl
|
|
20603
20599
|
});
|
|
20604
20600
|
import { execSync as execSync2 } from "node:child_process";
|
|
20605
20601
|
import { readFileSync as readFileSync36 } from "node:fs";
|
|
20606
20602
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20607
|
-
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
20603
|
+
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync32, statSync as statSync38 } from "node:fs";
|
|
20608
20604
|
import { join as join43, dirname as dirname18, sep as sep2 } from "node:path";
|
|
20609
20605
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
20610
20606
|
import { createHash as createHash12 } from "node:crypto";
|
|
@@ -20643,6 +20639,31 @@ function planAutoUpdate(base2, caps, ownVersion3, flag) {
|
|
|
20643
20639
|
if (compareVersions(rel.version, ownVersion3) <= 0) return { update: false, reason: `already current (own ${ownVersion3} >= server ${rel.version})` };
|
|
20644
20640
|
return { update: true, reason: `server ${rel.version} > own ${ownVersion3}`, url: tarballUrl(base2), sha256: rel.sha256, from: ownVersion3, to: rel.version };
|
|
20645
20641
|
}
|
|
20642
|
+
function sweepStaleStaging(root = tmpdir2(), now = Date.now()) {
|
|
20643
|
+
const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
|
|
20644
|
+
const COLD_MS = 60 * 60 * 1e3;
|
|
20645
|
+
let removed = 0;
|
|
20646
|
+
let entries;
|
|
20647
|
+
try {
|
|
20648
|
+
entries = readdirSync32(root);
|
|
20649
|
+
} catch {
|
|
20650
|
+
return 0;
|
|
20651
|
+
}
|
|
20652
|
+
for (const name of entries) {
|
|
20653
|
+
if (!MKDTEMP_SUFFIX.test(name)) continue;
|
|
20654
|
+
const dir = join43(root, name);
|
|
20655
|
+
try {
|
|
20656
|
+
const st = statSync38(dir);
|
|
20657
|
+
if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
|
|
20658
|
+
const inside = readdirSync32(dir);
|
|
20659
|
+
if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
|
|
20660
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
20661
|
+
removed++;
|
|
20662
|
+
} catch {
|
|
20663
|
+
}
|
|
20664
|
+
}
|
|
20665
|
+
return removed;
|
|
20666
|
+
}
|
|
20646
20667
|
async function executeAutoUpdate(plan, deps) {
|
|
20647
20668
|
if (!plan.update || !plan.url || !plan.sha256) return { updated: false, reason: plan.reason };
|
|
20648
20669
|
let buf;
|
|
@@ -20654,13 +20675,21 @@ async function executeAutoUpdate(plan, deps) {
|
|
|
20654
20675
|
const got = createHash12("sha256").update(buf).digest("hex");
|
|
20655
20676
|
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
20677
|
const dir = mkdtempSync3(join43(tmpdir2(), "cr-update-"));
|
|
20678
|
+
const discardStaging = () => {
|
|
20679
|
+
try {
|
|
20680
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
20681
|
+
} catch {
|
|
20682
|
+
}
|
|
20683
|
+
};
|
|
20657
20684
|
const tgz = join43(dir, "chat-recall.tgz");
|
|
20658
20685
|
writeFileSync8(tgz, buf);
|
|
20659
20686
|
try {
|
|
20660
20687
|
deps.install(tgz);
|
|
20661
20688
|
} catch (e) {
|
|
20689
|
+
discardStaging();
|
|
20662
20690
|
return { updated: false, reason: `install failed: ${e instanceof Error ? e.message : e}` };
|
|
20663
20691
|
}
|
|
20692
|
+
discardStaging();
|
|
20664
20693
|
const landed = (deps.verify ?? installedVersion)();
|
|
20665
20694
|
if (landed && plan.to && compareVersions(landed, plan.to) < 0) {
|
|
20666
20695
|
return {
|
|
@@ -20715,7 +20744,17 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
|
|
|
20715
20744
|
}
|
|
20716
20745
|
const effectiveOwn = newerOf(ownVersion3, installedVersion());
|
|
20717
20746
|
const plan = planAutoUpdate(base2, caps, effectiveOwn, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
20718
|
-
if (!plan.update)
|
|
20747
|
+
if (!plan.update) {
|
|
20748
|
+
if (/disabled|no CLI release/.test(plan.reason) && !reportedStandingSkip.has(base2)) {
|
|
20749
|
+
reportedStandingSkip.add(base2);
|
|
20750
|
+
try {
|
|
20751
|
+
const { reportClientEvent: reportClientEvent2 } = await Promise.resolve().then(() => (init_client_events(), client_events_exports));
|
|
20752
|
+
reportClientEvent2("auto_update_skipped", { message: `${plan.reason} (server offers ${caps.cli?.version ?? "?"}, this device runs ${effectiveOwn})` });
|
|
20753
|
+
} catch {
|
|
20754
|
+
}
|
|
20755
|
+
}
|
|
20756
|
+
return { updated: false, reason: plan.reason };
|
|
20757
|
+
}
|
|
20719
20758
|
const result = await executeAutoUpdate(plan, {
|
|
20720
20759
|
download: deps?.download ?? realDownload,
|
|
20721
20760
|
install: deps?.install ?? realInstall,
|
|
@@ -20731,12 +20770,13 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
|
|
|
20731
20770
|
}
|
|
20732
20771
|
return result;
|
|
20733
20772
|
}
|
|
20734
|
-
var tarballUrl;
|
|
20773
|
+
var tarballUrl, reportedStandingSkip;
|
|
20735
20774
|
var init_auto_update = __esm({
|
|
20736
20775
|
"src/auto-update.ts"() {
|
|
20737
20776
|
"use strict";
|
|
20738
20777
|
init_http();
|
|
20739
20778
|
tarballUrl = (base2) => `${base2.replace(/\/+$/, "")}/install/chat-recall.tgz`;
|
|
20779
|
+
reportedStandingSkip = /* @__PURE__ */ new Set();
|
|
20740
20780
|
}
|
|
20741
20781
|
});
|
|
20742
20782
|
|
|
@@ -20752,6 +20792,9 @@ __export(sync_client_exports, {
|
|
|
20752
20792
|
batchScanExternal: () => batchScanExternal,
|
|
20753
20793
|
buildConversationSync: () => buildConversationSync,
|
|
20754
20794
|
buildConversationTail: () => buildConversationTail,
|
|
20795
|
+
classedError: () => classedError,
|
|
20796
|
+
classifyError: () => classifyError,
|
|
20797
|
+
isStandingFailure: () => isStandingFailure,
|
|
20755
20798
|
isSyncSkip: () => isSyncSkip,
|
|
20756
20799
|
loadAllCredentials: () => loadAllCredentials,
|
|
20757
20800
|
loadCredentials: () => loadCredentials,
|
|
@@ -20767,22 +20810,37 @@ __export(sync_client_exports, {
|
|
|
20767
20810
|
syncSessions: () => syncSessions
|
|
20768
20811
|
});
|
|
20769
20812
|
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
|
|
20813
|
+
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
20814
|
import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
|
|
20772
20815
|
import { createHash as createHash13 } from "node:crypto";
|
|
20773
20816
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
20774
20817
|
import { gzipSync as gzipSync3 } from "zlib";
|
|
20775
|
-
function
|
|
20818
|
+
function classedError(errorClass, message) {
|
|
20819
|
+
const e = new Error(message);
|
|
20820
|
+
Object.defineProperty(e, ERROR_CLASS, { value: errorClass, enumerable: false });
|
|
20821
|
+
return e;
|
|
20822
|
+
}
|
|
20823
|
+
function taggedClass(err) {
|
|
20824
|
+
const v = err?.[ERROR_CLASS];
|
|
20825
|
+
return typeof v === "string" && v ? v : null;
|
|
20826
|
+
}
|
|
20827
|
+
function isStandingFailure(err) {
|
|
20828
|
+
const c = taggedClass(err);
|
|
20829
|
+
return c !== null && STANDING_FAILURE.has(c);
|
|
20830
|
+
}
|
|
20831
|
+
function classifyError(msg, err) {
|
|
20832
|
+
const tagged = taggedClass(err);
|
|
20833
|
+
if (tagged) return tagged;
|
|
20776
20834
|
const m = msg.toLowerCase();
|
|
20777
20835
|
if (m.includes("429")) return "rate_limited";
|
|
20778
20836
|
if (m.includes("402")) return "payment_required";
|
|
20779
20837
|
if (m.includes("401") || m.includes("403") || m.includes("token")) return "auth";
|
|
20780
|
-
if (m.includes("timed out") || m.includes("etimedout")) return "timeout";
|
|
20838
|
+
if (m.includes("timed out") || m.includes("etimedout") || m.includes("aborted due to timeout")) return "timeout";
|
|
20781
20839
|
if (m.includes("econnrefused")) return "refused";
|
|
20782
20840
|
if (m.includes("enotfound") || m.includes("dns")) return "dns";
|
|
20783
20841
|
if (m.includes("fetch failed") || m.includes("econnreset")) return "network";
|
|
20784
|
-
if (m.includes("plain http") || m.includes("
|
|
20785
|
-
if (
|
|
20842
|
+
if (m.includes("over plain http") || m.includes("unsupported scheme")) return "insecure_transport";
|
|
20843
|
+
if (/\b5\d\d\b/.test(m)) return "server_error";
|
|
20786
20844
|
return "other";
|
|
20787
20845
|
}
|
|
20788
20846
|
function saveCredentials(c) {
|
|
@@ -21000,17 +21058,23 @@ async function syncSessions(opts = {}) {
|
|
|
21000
21058
|
let agg = null;
|
|
21001
21059
|
const errors = [];
|
|
21002
21060
|
const skippedTargets = [];
|
|
21061
|
+
const perTarget = {};
|
|
21003
21062
|
for (const cred of targets) {
|
|
21004
21063
|
const verdict = breakerState(cred.serverUrl);
|
|
21005
21064
|
if (verdict.open) {
|
|
21006
21065
|
const notice = breakerNotice(cred.serverUrl, verdict);
|
|
21007
21066
|
if (notice) console.error(`[sync] ${notice}`);
|
|
21008
21067
|
skippedTargets.push(cred.serverUrl);
|
|
21068
|
+
perTarget[cred.serverUrl] = {
|
|
21069
|
+
ok: false,
|
|
21070
|
+
error: notice ?? `in failure cooldown for ~${Math.max(1, Math.round(verdict.retryInMs / 6e4))}m`
|
|
21071
|
+
};
|
|
21009
21072
|
continue;
|
|
21010
21073
|
}
|
|
21011
21074
|
try {
|
|
21012
21075
|
const r = await syncToTarget(cred, opts);
|
|
21013
21076
|
noteTargetSuccess(cred.serverUrl);
|
|
21077
|
+
perTarget[cred.serverUrl] = { ok: true };
|
|
21014
21078
|
agg = agg ? {
|
|
21015
21079
|
uploaded: agg.uploaded + r.uploaded,
|
|
21016
21080
|
skipped: agg.skipped + r.skipped,
|
|
@@ -21027,14 +21091,20 @@ async function syncSessions(opts = {}) {
|
|
|
21027
21091
|
} catch (e) {
|
|
21028
21092
|
const msg = e instanceof Error ? e.message : String(e);
|
|
21029
21093
|
errors.push(`${cred.serverUrl}: ${msg}`);
|
|
21094
|
+
perTarget[cred.serverUrl] = { ok: false, error: msg };
|
|
21030
21095
|
const next = noteTargetFailure(cred.serverUrl, msg);
|
|
21031
21096
|
const notice = breakerNotice(cred.serverUrl, next);
|
|
21032
21097
|
if (notice) console.error(`[sync] ${notice}`);
|
|
21033
21098
|
record({
|
|
21034
|
-
|
|
21099
|
+
// `tripped`, not `open`: an open breaker re-opens on every attempt until
|
|
21100
|
+
// a success clears it, so `open` counted elapsed time. Every failure is
|
|
21101
|
+
// still reported — as `target_failure`, carrying the streak in
|
|
21102
|
+
// `failures` — so nothing becomes invisible, it just stops being counted
|
|
21103
|
+
// as a fresh incident.
|
|
21104
|
+
kind: next.tripped ? "breaker_trip" : "target_failure",
|
|
21035
21105
|
failures: next.failures,
|
|
21036
21106
|
retryInMs: next.retryInMs,
|
|
21037
|
-
errorClass: classifyError(msg),
|
|
21107
|
+
errorClass: classifyError(msg, e),
|
|
21038
21108
|
local: isLocalHost(cred.serverUrl) ? 1 : 0
|
|
21039
21109
|
});
|
|
21040
21110
|
}
|
|
@@ -21065,7 +21135,7 @@ async function syncSessions(opts = {}) {
|
|
|
21065
21135
|
${errors.join("\n ")}`);
|
|
21066
21136
|
if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
|
|
21067
21137
|
${errors.join("\n ")}`);
|
|
21068
|
-
return agg;
|
|
21138
|
+
return { ...agg, perTarget };
|
|
21069
21139
|
}
|
|
21070
21140
|
async function postFieldBatch(base2, token, rows) {
|
|
21071
21141
|
const headers = { "content-type": "application/json" };
|
|
@@ -21139,7 +21209,7 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21139
21209
|
let reaped = 0;
|
|
21140
21210
|
let names;
|
|
21141
21211
|
try {
|
|
21142
|
-
names =
|
|
21212
|
+
names = readdirSync33(dir);
|
|
21143
21213
|
} catch {
|
|
21144
21214
|
return 0;
|
|
21145
21215
|
}
|
|
@@ -21147,8 +21217,8 @@ function reapStaleScanDirs(dir = tmpdir3(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21147
21217
|
if (!name.startsWith("cr-batchscan-")) continue;
|
|
21148
21218
|
const p = join44(dir, name);
|
|
21149
21219
|
try {
|
|
21150
|
-
if (now -
|
|
21151
|
-
|
|
21220
|
+
if (now - statSync39(p).mtimeMs < maxAgeMs) continue;
|
|
21221
|
+
rmSync6(p, { recursive: true, force: true });
|
|
21152
21222
|
reaped++;
|
|
21153
21223
|
} catch {
|
|
21154
21224
|
}
|
|
@@ -21180,7 +21250,7 @@ function batchScanExternal(items, opts = {}) {
|
|
|
21180
21250
|
slices++;
|
|
21181
21251
|
}
|
|
21182
21252
|
try {
|
|
21183
|
-
|
|
21253
|
+
rmSync6(scanDir, { recursive: true, force: true });
|
|
21184
21254
|
} catch {
|
|
21185
21255
|
}
|
|
21186
21256
|
safeToId = /* @__PURE__ */ new Map();
|
|
@@ -21205,7 +21275,7 @@ function batchScanExternal(items, opts = {}) {
|
|
|
21205
21275
|
flushSlice();
|
|
21206
21276
|
} finally {
|
|
21207
21277
|
try {
|
|
21208
|
-
|
|
21278
|
+
rmSync6(scanDir, { recursive: true, force: true });
|
|
21209
21279
|
} catch {
|
|
21210
21280
|
}
|
|
21211
21281
|
}
|
|
@@ -21252,7 +21322,7 @@ async function syncToTarget(cred, opts = {}) {
|
|
|
21252
21322
|
}
|
|
21253
21323
|
async function syncToTargetInScope(cred, opts = {}) {
|
|
21254
21324
|
const risk = transportRisk(cred.serverUrl);
|
|
21255
|
-
if (risk) throw
|
|
21325
|
+
if (risk) throw classedError("insecure_transport", risk);
|
|
21256
21326
|
const sync = loadSettings().sync;
|
|
21257
21327
|
const serverCfg = await fetchTenantSyncConfig(cred);
|
|
21258
21328
|
const excludeTools = /* @__PURE__ */ new Set([...sync?.excludeTools ?? [], ...serverCfg.excludeTools]);
|
|
@@ -21338,9 +21408,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21338
21408
|
setServerIngestConcurrency(base2, Math.floor(adv));
|
|
21339
21409
|
}
|
|
21340
21410
|
if (body2.cli && body2.cli.version) {
|
|
21341
|
-
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21411
|
+
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21342
21412
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21343
21413
|
const plan = planAutoUpdate2(base2, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21414
|
+
sweepStaleStaging2();
|
|
21344
21415
|
if (plan.update) {
|
|
21345
21416
|
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21346
21417
|
});
|
|
@@ -21351,7 +21422,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21351
21422
|
const retryable = res.status === 429 || res.status >= 500;
|
|
21352
21423
|
const text = await res.text().catch(() => "");
|
|
21353
21424
|
if (res.status === 401 || res.status === 403) {
|
|
21354
|
-
throw
|
|
21425
|
+
throw classedError("auth", `server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
|
|
21355
21426
|
}
|
|
21356
21427
|
if (res.status === 402) {
|
|
21357
21428
|
let detail = text;
|
|
@@ -21365,7 +21436,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21365
21436
|
${body2.upgradeUrl}`;
|
|
21366
21437
|
} catch {
|
|
21367
21438
|
}
|
|
21368
|
-
throw
|
|
21439
|
+
throw classedError("payment_required", `sync paused by the server: ${detail}`);
|
|
21369
21440
|
}
|
|
21370
21441
|
if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
|
|
21371
21442
|
if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
|
|
@@ -21375,7 +21446,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21375
21446
|
await sleep2(waitMs);
|
|
21376
21447
|
continue;
|
|
21377
21448
|
} catch (err) {
|
|
21378
|
-
const fatal = err instanceof Error && /HTTP 4\d\d|sync failed after/.test(err.message);
|
|
21449
|
+
const fatal = isStandingFailure(err) || err instanceof Error && /HTTP 4\d\d|sync failed after/.test(err.message);
|
|
21379
21450
|
if (fatal || attempt >= RETRY_DELAYS_MS.length) throw err;
|
|
21380
21451
|
console.error(`[sync] upload attempt ${attempt + 1} failed (${err instanceof Error ? err.message : err}) \u2014 retrying in ${RETRY_DELAYS_MS[attempt] / 1e3}s`);
|
|
21381
21452
|
await sleep2(RETRY_DELAYS_MS[attempt]);
|
|
@@ -21466,9 +21537,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21466
21537
|
if (!res.ok) throw new Error(`append sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
21467
21538
|
const body = await res.json().catch(() => ({}));
|
|
21468
21539
|
if (body.cli && body.cli.version) {
|
|
21469
|
-
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21540
|
+
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21470
21541
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21471
21542
|
const plan = planAutoUpdate2(base2, { cli: body.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21543
|
+
sweepStaleStaging2();
|
|
21472
21544
|
if (plan.update) {
|
|
21473
21545
|
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21474
21546
|
});
|
|
@@ -21913,7 +21985,12 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
21913
21985
|
const containerBytes = built ? built.files.reduce((n, f) => n + Buffer.byteLength(f.text), 0) : 0;
|
|
21914
21986
|
if (containerBytes > FULL_BUILD_MAX_BYTES) {
|
|
21915
21987
|
console.error(`[sync] ${ref.prefixedId} is ${(containerBytes / 1048576).toFixed(0)}MB (over the ${(FULL_BUILD_MAX_BYTES / 1048576).toFixed(0)}MB ceiling) \u2014 shipping without the raw archive and derived data to keep the walk in memory`);
|
|
21916
|
-
record({
|
|
21988
|
+
record({
|
|
21989
|
+
kind: "oversized_session",
|
|
21990
|
+
mb: Math.round(containerBytes / 1048576),
|
|
21991
|
+
tool: ref.toolId,
|
|
21992
|
+
dedupeKey: stableMark(ref.prefixedId)
|
|
21993
|
+
});
|
|
21917
21994
|
includeRaw = false;
|
|
21918
21995
|
includeMeta = false;
|
|
21919
21996
|
}
|
|
@@ -22224,7 +22301,7 @@ async function syncIncremental(opts = {}) {
|
|
|
22224
22301
|
lock.release();
|
|
22225
22302
|
}
|
|
22226
22303
|
}
|
|
22227
|
-
var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22304
|
+
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22228
22305
|
var init_sync_client = __esm({
|
|
22229
22306
|
"src/sync-client.ts"() {
|
|
22230
22307
|
"use strict";
|
|
@@ -22270,10 +22347,16 @@ var init_sync_client = __esm({
|
|
|
22270
22347
|
ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22271
22348
|
} catch {
|
|
22272
22349
|
try {
|
|
22273
|
-
if (true) ownVersion = "0.5.
|
|
22350
|
+
if (true) ownVersion = "0.5.31";
|
|
22274
22351
|
} catch {
|
|
22275
22352
|
}
|
|
22276
22353
|
}
|
|
22354
|
+
ERROR_CLASS = "__chatRecallErrorClass";
|
|
22355
|
+
STANDING_FAILURE = /* @__PURE__ */ new Set([
|
|
22356
|
+
"auth",
|
|
22357
|
+
"payment_required",
|
|
22358
|
+
"insecure_transport"
|
|
22359
|
+
]);
|
|
22277
22360
|
walkProgressForUpload = null;
|
|
22278
22361
|
lastServerPackSpec = null;
|
|
22279
22362
|
credPath = () => join44(getDataDir(), "credentials.json");
|
|
@@ -22384,11 +22467,11 @@ var init_update_notice = __esm({
|
|
|
22384
22467
|
});
|
|
22385
22468
|
|
|
22386
22469
|
// ../engine/dist/core/which.js
|
|
22387
|
-
import { existsSync as existsSync43, statSync as
|
|
22470
|
+
import { existsSync as existsSync43, statSync as statSync40, accessSync, constants } from "node:fs";
|
|
22388
22471
|
import { join as join46, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep3 } from "node:path";
|
|
22389
22472
|
function executable(p) {
|
|
22390
22473
|
try {
|
|
22391
|
-
if (!
|
|
22474
|
+
if (!statSync40(p).isFile())
|
|
22392
22475
|
return false;
|
|
22393
22476
|
} catch {
|
|
22394
22477
|
return false;
|
|
@@ -22524,7 +22607,7 @@ function registerMcpServersJson(target, path, spec) {
|
|
|
22524
22607
|
entry.alwaysAllow = existing?.alwaysAllow ?? spec.alwaysAllow;
|
|
22525
22608
|
if (spec.env)
|
|
22526
22609
|
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);
|
|
22610
|
+
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
22611
|
if (isCurrent)
|
|
22529
22612
|
return { id: target.id, label: target.label, path, state: "current" };
|
|
22530
22613
|
servers[MCP_SERVER_NAME] = entry;
|
|
@@ -22551,7 +22634,7 @@ function registerOpencodeJson(path, spec) {
|
|
|
22551
22634
|
const entry = { type: "local", command: argv, enabled: true };
|
|
22552
22635
|
if (spec.env)
|
|
22553
22636
|
entry.environment = { ...existing?.environment, ...spec.env };
|
|
22554
|
-
const isCurrent = !!existing && JSON.stringify(existing.command ?? null) === JSON.stringify(argv) &&
|
|
22637
|
+
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
22638
|
if (isCurrent)
|
|
22556
22639
|
return { id, label, path, state: "current" };
|
|
22557
22640
|
mcp[MCP_SERVER_NAME] = entry;
|
|
@@ -22730,7 +22813,7 @@ __export(companions_exports, {
|
|
|
22730
22813
|
uninstallCodeindex: () => uninstallCodeindex,
|
|
22731
22814
|
unregisterCodeindexMcp: () => unregisterCodeindexMcp
|
|
22732
22815
|
});
|
|
22733
|
-
import { existsSync as existsSync45, mkdirSync as mkdirSync11, statSync as
|
|
22816
|
+
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
22817
|
import { homedir as homedir16 } from "node:os";
|
|
22735
22818
|
import { join as join48 } from "node:path";
|
|
22736
22819
|
import { execSync as execSync3 } from "node:child_process";
|
|
@@ -22781,7 +22864,7 @@ function checkCodeindexStatus() {
|
|
|
22781
22864
|
status.installed = true;
|
|
22782
22865
|
status.path = foundPath;
|
|
22783
22866
|
try {
|
|
22784
|
-
status.size =
|
|
22867
|
+
status.size = statSync41(foundPath).size;
|
|
22785
22868
|
} catch {
|
|
22786
22869
|
}
|
|
22787
22870
|
try {
|
|
@@ -22931,7 +23014,7 @@ __export(install_skills_exports, {
|
|
|
22931
23014
|
skillsSourceDir: () => skillsSourceDir,
|
|
22932
23015
|
uninstallSkills: () => uninstallSkills
|
|
22933
23016
|
});
|
|
22934
|
-
import { readdirSync as
|
|
23017
|
+
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
23018
|
import { createHash as createHash15 } from "crypto";
|
|
22936
23019
|
import { join as join49, basename as basename23 } from "path";
|
|
22937
23020
|
function skillsSourceDir() {
|
|
@@ -22957,7 +23040,7 @@ function skillsFingerprint() {
|
|
|
22957
23040
|
}
|
|
22958
23041
|
function filesUnder(dir, prefix = "") {
|
|
22959
23042
|
const out = [];
|
|
22960
|
-
for (const e of
|
|
23043
|
+
for (const e of readdirSync34(dir, { withFileTypes: true })) {
|
|
22961
23044
|
if (e.name === MARKER) continue;
|
|
22962
23045
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
22963
23046
|
if (e.isDirectory()) out.push(...filesUnder(join49(dir, e.name), rel));
|
|
@@ -22971,7 +23054,7 @@ function skillStamp() {
|
|
|
22971
23054
|
function bundledSkillNames() {
|
|
22972
23055
|
const src = skillsSourceDir();
|
|
22973
23056
|
if (!existsSync46(src)) return [];
|
|
22974
|
-
return
|
|
23057
|
+
return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
22975
23058
|
}
|
|
22976
23059
|
function realPathOrSelf(p) {
|
|
22977
23060
|
try {
|
|
@@ -23044,7 +23127,7 @@ function installSkills(opts = {}) {
|
|
|
23044
23127
|
continue;
|
|
23045
23128
|
}
|
|
23046
23129
|
mkdirSync12(t.dir, { recursive: true });
|
|
23047
|
-
|
|
23130
|
+
rmSync7(dest, { recursive: true, force: true });
|
|
23048
23131
|
cpSync(join49(src, name), dest, { recursive: true });
|
|
23049
23132
|
writeFileSync13(join49(dest, MARKER), `${stamp}
|
|
23050
23133
|
`);
|
|
@@ -23063,11 +23146,11 @@ function uninstallSkills() {
|
|
|
23063
23146
|
perTarget.push({ id: t.id, label: t.label, removed });
|
|
23064
23147
|
continue;
|
|
23065
23148
|
}
|
|
23066
|
-
for (const entry of
|
|
23149
|
+
for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
|
|
23067
23150
|
if (!entry.isDirectory()) continue;
|
|
23068
23151
|
const dir = join49(t.dir, entry.name);
|
|
23069
23152
|
if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
|
|
23070
|
-
|
|
23153
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
23071
23154
|
removed.push(entry.name);
|
|
23072
23155
|
}
|
|
23073
23156
|
}
|
|
@@ -23748,11 +23831,11 @@ __export(toolkit_pull_exports, {
|
|
|
23748
23831
|
planPull: () => planPull,
|
|
23749
23832
|
portableCommand: () => portableCommand
|
|
23750
23833
|
});
|
|
23751
|
-
import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as
|
|
23834
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync14, statSync as statSync43, writeFileSync as writeFileSync15 } from "node:fs";
|
|
23752
23835
|
import { dirname as dirname22, join as join52, basename as basename26, isAbsolute as isAbsolute3 } from "node:path";
|
|
23753
23836
|
function isExecutableFile(p) {
|
|
23754
23837
|
try {
|
|
23755
|
-
return
|
|
23838
|
+
return statSync43(p).isFile();
|
|
23756
23839
|
} catch {
|
|
23757
23840
|
return false;
|
|
23758
23841
|
}
|
|
@@ -25673,7 +25756,7 @@ __export(project_tasks_exports, {
|
|
|
25673
25756
|
pushProjectTaskStatuses: () => pushProjectTaskStatuses,
|
|
25674
25757
|
renderTeamTasksMd: () => renderTeamTasksMd
|
|
25675
25758
|
});
|
|
25676
|
-
import { existsSync as existsSync50, readFileSync as readFileSync45, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, statSync as
|
|
25759
|
+
import { existsSync as existsSync50, readFileSync as readFileSync45, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, statSync as statSync44 } from "node:fs";
|
|
25677
25760
|
import { join as join54, dirname as dirname23 } from "node:path";
|
|
25678
25761
|
import { homedir as homedir19 } from "node:os";
|
|
25679
25762
|
function isTeamTaskStatus(v) {
|
|
@@ -25762,7 +25845,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
|
|
|
25762
25845
|
if (!existsSync50(file)) continue;
|
|
25763
25846
|
let mtime;
|
|
25764
25847
|
try {
|
|
25765
|
-
mtime =
|
|
25848
|
+
mtime = statSync44(file).mtimeMs;
|
|
25766
25849
|
} catch {
|
|
25767
25850
|
continue;
|
|
25768
25851
|
}
|
|
@@ -25972,8 +26055,9 @@ async function drainSyncIntents(opts = {}) {
|
|
|
25972
26055
|
const data = await res.json();
|
|
25973
26056
|
pending = data.intents || [];
|
|
25974
26057
|
if (data.cli && data.cli.version) {
|
|
25975
|
-
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
26058
|
+
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
25976
26059
|
const plan = planAutoUpdate2(base2, { cli: data.cli }, ownVersion2, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
26060
|
+
sweepStaleStaging2();
|
|
25977
26061
|
if (plan.update) {
|
|
25978
26062
|
void runAutoUpdate2(base2, authHeaders, ownVersion2).catch(() => {
|
|
25979
26063
|
});
|
|
@@ -26027,7 +26111,7 @@ var init_intent_drain = __esm({
|
|
|
26027
26111
|
ownVersion2 = JSON.parse(readFileSync46(join55(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
26028
26112
|
} catch {
|
|
26029
26113
|
try {
|
|
26030
|
-
if (true) ownVersion2 = "0.5.
|
|
26114
|
+
if (true) ownVersion2 = "0.5.31";
|
|
26031
26115
|
} catch {
|
|
26032
26116
|
}
|
|
26033
26117
|
}
|
|
@@ -26228,7 +26312,7 @@ var team_merge_exports = {};
|
|
|
26228
26312
|
__export(team_merge_exports, {
|
|
26229
26313
|
mergePullResult: () => mergePullResult
|
|
26230
26314
|
});
|
|
26231
|
-
import { existsSync as existsSync52, mkdirSync as mkdirSync17, readFileSync as readFileSync47, rmSync as
|
|
26315
|
+
import { existsSync as existsSync52, mkdirSync as mkdirSync17, readFileSync as readFileSync47, rmSync as rmSync8, writeFileSync as writeFileSync18, statSync as statSync45 } from "fs";
|
|
26232
26316
|
import { dirname as dirname25, join as join56 } from "path";
|
|
26233
26317
|
import { createHash as createHash18 } from "crypto";
|
|
26234
26318
|
function installPathFor(art, tool) {
|
|
@@ -26345,7 +26429,7 @@ async function mergePullResult(opts) {
|
|
|
26345
26429
|
continue;
|
|
26346
26430
|
try {
|
|
26347
26431
|
if (existsSync52(path))
|
|
26348
|
-
|
|
26432
|
+
rmSync8(path, { force: true });
|
|
26349
26433
|
await teamInstallsForget(id, row.tool);
|
|
26350
26434
|
report.removed.push({ artifactId: id, path });
|
|
26351
26435
|
} catch (err) {
|
|
@@ -26364,7 +26448,7 @@ function readFileIfExists(path) {
|
|
|
26364
26448
|
try {
|
|
26365
26449
|
if (!existsSync52(path))
|
|
26366
26450
|
return null;
|
|
26367
|
-
const s =
|
|
26451
|
+
const s = statSync45(path);
|
|
26368
26452
|
if (!s.isFile())
|
|
26369
26453
|
return null;
|
|
26370
26454
|
return readFileSync47(path);
|
|
@@ -26449,7 +26533,7 @@ var init_vault_crypto = __esm({
|
|
|
26449
26533
|
});
|
|
26450
26534
|
|
|
26451
26535
|
// ../engine/dist/core/vault-source.js
|
|
26452
|
-
import { existsSync as existsSync53, readdirSync as
|
|
26536
|
+
import { existsSync as existsSync53, readdirSync as readdirSync35, statSync as statSync46 } from "fs";
|
|
26453
26537
|
import { extname, join as join57 } from "path";
|
|
26454
26538
|
function* walkVaultSources() {
|
|
26455
26539
|
const t = loadSettings().team.vault;
|
|
@@ -26467,7 +26551,7 @@ function* walkVaultSources() {
|
|
|
26467
26551
|
}
|
|
26468
26552
|
function safeStat(path) {
|
|
26469
26553
|
try {
|
|
26470
|
-
const s =
|
|
26554
|
+
const s = statSync46(path);
|
|
26471
26555
|
if (!s.isFile())
|
|
26472
26556
|
return null;
|
|
26473
26557
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
@@ -26481,7 +26565,7 @@ function* walkClaude() {
|
|
|
26481
26565
|
return;
|
|
26482
26566
|
let projectDirs = [];
|
|
26483
26567
|
try {
|
|
26484
|
-
projectDirs =
|
|
26568
|
+
projectDirs = readdirSync35(root);
|
|
26485
26569
|
} catch {
|
|
26486
26570
|
return;
|
|
26487
26571
|
}
|
|
@@ -26492,7 +26576,7 @@ function* walkClaude() {
|
|
|
26492
26576
|
const projDir = join57(root, proj);
|
|
26493
26577
|
let entries = [];
|
|
26494
26578
|
try {
|
|
26495
|
-
entries =
|
|
26579
|
+
entries = readdirSync35(projDir);
|
|
26496
26580
|
} catch {
|
|
26497
26581
|
continue;
|
|
26498
26582
|
}
|
|
@@ -26514,7 +26598,7 @@ function* walkGemini() {
|
|
|
26514
26598
|
return;
|
|
26515
26599
|
let hashDirs = [];
|
|
26516
26600
|
try {
|
|
26517
|
-
hashDirs =
|
|
26601
|
+
hashDirs = readdirSync35(tmp);
|
|
26518
26602
|
} catch {
|
|
26519
26603
|
return;
|
|
26520
26604
|
}
|
|
@@ -26524,7 +26608,7 @@ function* walkGemini() {
|
|
|
26524
26608
|
continue;
|
|
26525
26609
|
let entries = [];
|
|
26526
26610
|
try {
|
|
26527
|
-
entries =
|
|
26611
|
+
entries = readdirSync35(chatsDir);
|
|
26528
26612
|
} catch {
|
|
26529
26613
|
continue;
|
|
26530
26614
|
}
|
|
@@ -26570,7 +26654,7 @@ function* walkCodex() {
|
|
|
26570
26654
|
const dir = stack.pop();
|
|
26571
26655
|
let entries = [];
|
|
26572
26656
|
try {
|
|
26573
|
-
entries =
|
|
26657
|
+
entries = readdirSync35(dir, { withFileTypes: true });
|
|
26574
26658
|
} catch {
|
|
26575
26659
|
continue;
|
|
26576
26660
|
}
|
|
@@ -26596,14 +26680,14 @@ function* walkAgy() {
|
|
|
26596
26680
|
return;
|
|
26597
26681
|
let sessionDirs = [];
|
|
26598
26682
|
try {
|
|
26599
|
-
sessionDirs =
|
|
26683
|
+
sessionDirs = readdirSync35(brainDir);
|
|
26600
26684
|
} catch {
|
|
26601
26685
|
return;
|
|
26602
26686
|
}
|
|
26603
26687
|
for (const rawId of sessionDirs) {
|
|
26604
26688
|
const sessionPath = join57(brainDir, rawId);
|
|
26605
26689
|
try {
|
|
26606
|
-
if (!
|
|
26690
|
+
if (!statSync46(sessionPath).isDirectory())
|
|
26607
26691
|
continue;
|
|
26608
26692
|
} catch {
|
|
26609
26693
|
continue;
|
|
@@ -27084,7 +27168,7 @@ __export(service_installer_exports, {
|
|
|
27084
27168
|
uninstallService: () => uninstallService
|
|
27085
27169
|
});
|
|
27086
27170
|
import { execSync as execSync5 } from "child_process";
|
|
27087
|
-
import { existsSync as existsSync55, mkdirSync as mkdirSync19, rmSync as
|
|
27171
|
+
import { existsSync as existsSync55, mkdirSync as mkdirSync19, rmSync as rmSync9, writeFileSync as writeFileSync20 } from "fs";
|
|
27088
27172
|
import { homedir as homedir21 } from "os";
|
|
27089
27173
|
import { join as join59 } from "path";
|
|
27090
27174
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -27240,7 +27324,7 @@ function uninstallService() {
|
|
|
27240
27324
|
}
|
|
27241
27325
|
const unitPath = join59(homedir21(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
27242
27326
|
if (existsSync55(unitPath)) {
|
|
27243
|
-
|
|
27327
|
+
rmSync9(unitPath, { force: true });
|
|
27244
27328
|
removed = true;
|
|
27245
27329
|
}
|
|
27246
27330
|
try {
|
|
@@ -27257,7 +27341,7 @@ function uninstallService() {
|
|
|
27257
27341
|
}
|
|
27258
27342
|
let removed = false;
|
|
27259
27343
|
if (existsSync55(plistPath)) {
|
|
27260
|
-
|
|
27344
|
+
rmSync9(plistPath, { force: true });
|
|
27261
27345
|
removed = true;
|
|
27262
27346
|
}
|
|
27263
27347
|
return removed;
|
|
@@ -27534,7 +27618,7 @@ init_tool_paths();
|
|
|
27534
27618
|
init_project_resolver();
|
|
27535
27619
|
import { config } from "dotenv";
|
|
27536
27620
|
import { Command } from "commander";
|
|
27537
|
-
import
|
|
27621
|
+
import chalk2 from "chalk";
|
|
27538
27622
|
import { existsSync as existsSync56, readFileSync as readFileSync49, writeFileSync as writeFileSync21, realpathSync as realpathSync5 } from "fs";
|
|
27539
27623
|
import { homedir as homedir22 } from "os";
|
|
27540
27624
|
import { join as join60, resolve as resolve2, dirname as dirname27, basename as basename29 } from "path";
|
|
@@ -27595,16 +27679,64 @@ function openBrowser(url) {
|
|
|
27595
27679
|
init_which();
|
|
27596
27680
|
init_collector_health();
|
|
27597
27681
|
init_telemetry_consent();
|
|
27682
|
+
|
|
27683
|
+
// src/env-needed.ts
|
|
27684
|
+
import chalk from "chalk";
|
|
27685
|
+
function isSecret(name) {
|
|
27686
|
+
return /(TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL|API_?KEY|PAT)\b/i.test(name);
|
|
27687
|
+
}
|
|
27688
|
+
function unusableHere(server) {
|
|
27689
|
+
const mac = /xcode|swift|apple|ios|macos/i.test(server);
|
|
27690
|
+
if (mac && process.platform !== "darwin") return "macOS only \u2014 nothing to do on this machine";
|
|
27691
|
+
return null;
|
|
27692
|
+
}
|
|
27693
|
+
function collectEnvNeeds(outcomes) {
|
|
27694
|
+
const byServer = /* @__PURE__ */ new Map();
|
|
27695
|
+
for (const o of outcomes) {
|
|
27696
|
+
if (!o.needsEnv?.length) continue;
|
|
27697
|
+
const set = byServer.get(o.name) ?? /* @__PURE__ */ new Set();
|
|
27698
|
+
for (const v of o.needsEnv) set.add(v);
|
|
27699
|
+
byServer.set(o.name, set);
|
|
27700
|
+
}
|
|
27701
|
+
return [...byServer.entries()].map(([server, vars]) => ({ server, vars: [...vars].sort() })).sort((a, b) => a.server.localeCompare(b.server));
|
|
27702
|
+
}
|
|
27703
|
+
function renderEnvNeeds(needs, installedCount) {
|
|
27704
|
+
if (!needs.length) return [];
|
|
27705
|
+
const lines = [];
|
|
27706
|
+
const plural = installedCount === 1 ? "server" : "servers";
|
|
27707
|
+
lines.push(
|
|
27708
|
+
chalk.yellow(`Installed ${installedCount} MCP ${plural} from your account. `) + chalk.yellow(`${needs.length === 1 ? "One needs" : `${needs.length} need`} values you must set here:`)
|
|
27709
|
+
);
|
|
27710
|
+
lines.push("");
|
|
27711
|
+
const width = Math.max(...needs.map((n) => n.server.length));
|
|
27712
|
+
let anySecret = false;
|
|
27713
|
+
for (const n of needs) {
|
|
27714
|
+
const note = unusableHere(n.server);
|
|
27715
|
+
anySecret ||= n.vars.some(isSecret);
|
|
27716
|
+
const name = chalk.bold(n.server.padEnd(width));
|
|
27717
|
+
const vars = n.vars.join(", ");
|
|
27718
|
+
lines.push(` ${name} ${vars}${note ? chalk.dim(` (${note})`) : ""}`);
|
|
27719
|
+
}
|
|
27720
|
+
lines.push("");
|
|
27721
|
+
lines.push(chalk.dim(" Add them to your shell profile, then restart your AI tool."));
|
|
27722
|
+
lines.push(chalk.dim(" Until then these servers will fail to start."));
|
|
27723
|
+
if (anySecret) {
|
|
27724
|
+
lines.push(chalk.dim(" chat-recall never uploads secret values \u2014 only the variable names."));
|
|
27725
|
+
}
|
|
27726
|
+
return lines;
|
|
27727
|
+
}
|
|
27728
|
+
|
|
27729
|
+
// src/cli.ts
|
|
27598
27730
|
function matchLabel(tier) {
|
|
27599
27731
|
switch (tier) {
|
|
27600
27732
|
case "strong":
|
|
27601
|
-
return
|
|
27733
|
+
return chalk2.green("strong");
|
|
27602
27734
|
case "good":
|
|
27603
|
-
return
|
|
27735
|
+
return chalk2.yellow("good");
|
|
27604
27736
|
case "weak":
|
|
27605
|
-
return
|
|
27737
|
+
return chalk2.dim("weak");
|
|
27606
27738
|
default:
|
|
27607
|
-
return
|
|
27739
|
+
return chalk2.dim("unranked");
|
|
27608
27740
|
}
|
|
27609
27741
|
}
|
|
27610
27742
|
config({ quiet: true });
|
|
@@ -27639,13 +27771,13 @@ function describeProbeError(err) {
|
|
|
27639
27771
|
}
|
|
27640
27772
|
function failLogin(opts, message) {
|
|
27641
27773
|
if (opts.fromInit) throw new Error(message);
|
|
27642
|
-
console.error(
|
|
27774
|
+
console.error(chalk2.red("login failed:"), message);
|
|
27643
27775
|
process.exit(1);
|
|
27644
27776
|
}
|
|
27645
27777
|
function requireTarget() {
|
|
27646
27778
|
const t = firstTarget();
|
|
27647
27779
|
if (!t) {
|
|
27648
|
-
console.error(
|
|
27780
|
+
console.error(chalk2.red("Not logged in."), "Run", chalk2.bold("chat-recall login <server-url>"), "first.");
|
|
27649
27781
|
process.exit(1);
|
|
27650
27782
|
}
|
|
27651
27783
|
return t;
|
|
@@ -27803,7 +27935,7 @@ var program = new Command();
|
|
|
27803
27935
|
program.name("chat-recall").description("Semantic search for Claude Code sessions - recall and resume past conversations").version(pkgVersion);
|
|
27804
27936
|
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
27937
|
try {
|
|
27806
|
-
console.log(
|
|
27938
|
+
console.log(chalk2.bold("chat-recall init") + chalk2.dim(` v${pkgVersion}`));
|
|
27807
27939
|
const { createInitReporter: createInitReporter2 } = await Promise.resolve().then(() => (init_init_report(), init_report_exports));
|
|
27808
27940
|
const rep = createInitReporter2({
|
|
27809
27941
|
tty: process.stdout.isTTY === true,
|
|
@@ -27827,8 +27959,8 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27827
27959
|
}
|
|
27828
27960
|
}
|
|
27829
27961
|
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(`${
|
|
27962
|
+
if (!foundTools.length) rep.warn(chalk2.yellow("No AI tools detected \u2014 chat-recall indexes their transcripts, so there is nothing to ship yet."));
|
|
27963
|
+
const serverBad = (text) => rep.warn(`${chalk2.yellow("Not connected")} \u2014 ${text}`);
|
|
27832
27964
|
let target = firstTarget();
|
|
27833
27965
|
let deadTarget = false;
|
|
27834
27966
|
if (options.server) {
|
|
@@ -27852,24 +27984,24 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27852
27984
|
if (probeOk2(probe)) {
|
|
27853
27985
|
} else if (probe.kind === "moved" || probe.kind === "not_api") {
|
|
27854
27986
|
const retired = target.base;
|
|
27855
|
-
console.log(` ${
|
|
27987
|
+
console.log(` ${chalk2.yellow("The saved server no longer serves chat-recall")} \u2192 ${retired}`);
|
|
27856
27988
|
console.log(` ${probeAdvice2(probe, retired)}`);
|
|
27857
|
-
console.log(` Switching to ${
|
|
27989
|
+
console.log(` Switching to ${chalk2.bold(DEFAULT_SERVER)} \u2014 sign in to approve this machine.`);
|
|
27858
27990
|
try {
|
|
27859
27991
|
await runLogin(DEFAULT_SERVER, { token: options.token, fromInit: true, promptJson: options.promptJson });
|
|
27860
27992
|
const { removeCredentials: removeCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
27861
27993
|
if (removeCredentials2(retired)) {
|
|
27862
|
-
console.log(` ${
|
|
27994
|
+
console.log(` ${chalk2.dim(`Removed the retired target ${retired} (log in there again any time).`)}`);
|
|
27863
27995
|
}
|
|
27864
27996
|
target = firstTarget();
|
|
27865
|
-
if (target) console.log(` ${
|
|
27997
|
+
if (target) console.log(` ${chalk2.green("Connected")} \u2192 ${target.base}`);
|
|
27866
27998
|
} catch (e) {
|
|
27867
27999
|
serverBad(describeProbeError(e));
|
|
27868
|
-
console.log(` ${
|
|
28000
|
+
console.log(` ${chalk2.dim(`Run ${chalk2.bold(`chat-recall login ${DEFAULT_SERVER}`)} when ready, then ${chalk2.bold(`chat-recall logout ${retired}`)}.`)}`);
|
|
27869
28001
|
deadTarget = true;
|
|
27870
28002
|
}
|
|
27871
28003
|
} else {
|
|
27872
|
-
console.log(` ${
|
|
28004
|
+
console.log(` ${chalk2.yellow("Saved server did not answer")} \u2192 ${target.base}`);
|
|
27873
28005
|
console.log(` ${probeAdvice2(probe, target.base)}`);
|
|
27874
28006
|
}
|
|
27875
28007
|
} else if (target) {
|
|
@@ -27899,8 +28031,8 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27899
28031
|
}
|
|
27900
28032
|
for (const r of bad) {
|
|
27901
28033
|
const why = (r.error ?? "").replace(/^SyntaxError:\s*/, "").split("\n")[0];
|
|
27902
|
-
rep.warn(`${
|
|
27903
|
-
rep.warn(`${
|
|
28034
|
+
rep.warn(`${chalk2.yellow(`${r.label} config left alone \u2014 ${why || "the file does not parse"}`)} ${chalk2.dim(r.path)}`);
|
|
28035
|
+
rep.warn(`${chalk2.dim(` fix that file, then: chat-recall init (nothing else needs redoing)`)}`);
|
|
27904
28036
|
}
|
|
27905
28037
|
if (process.env.CHAT_RECALL_VERBOSE) {
|
|
27906
28038
|
}
|
|
@@ -27932,14 +28064,14 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27932
28064
|
const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
|
|
27933
28065
|
}
|
|
27934
28066
|
} else if (!result.prebuiltAvailable) {
|
|
27935
|
-
rep.warn(`${
|
|
28067
|
+
rep.warn(`${chalk2.yellow("codeindex")}: ${result.unsupportedReason}`);
|
|
27936
28068
|
}
|
|
27937
28069
|
} catch (err) {
|
|
27938
28070
|
const msg = String(err);
|
|
27939
|
-
rep.warn(`${
|
|
28071
|
+
rep.warn(`${chalk2.yellow("codeindex install failed")}: ${msg}`);
|
|
27940
28072
|
if (msg.includes("404")) {
|
|
27941
|
-
console.log(
|
|
27942
|
-
console.log(
|
|
28073
|
+
console.log(chalk2.dim(" The codeindex release returned 404. If the repo is private,"));
|
|
28074
|
+
console.log(chalk2.dim(" authenticate `gh` to it, or build from source."));
|
|
27943
28075
|
}
|
|
27944
28076
|
}
|
|
27945
28077
|
} else {
|
|
@@ -27955,7 +28087,7 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27955
28087
|
}
|
|
27956
28088
|
const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
|
|
27957
28089
|
} catch (err) {
|
|
27958
|
-
rep.warn(`${
|
|
28090
|
+
rep.warn(`${chalk2.yellow("Skill install failed")} \u2014 ${err instanceof Error ? err.message : err}`);
|
|
27959
28091
|
}
|
|
27960
28092
|
if (!options.skipMcp && firstTarget()) {
|
|
27961
28093
|
try {
|
|
@@ -27993,28 +28125,30 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
27993
28125
|
const written = report.outcomes.filter((o) => o.status === "written");
|
|
27994
28126
|
const present = report.outcomes.filter((o) => o.status === "present").length;
|
|
27995
28127
|
if (answered === 0) {
|
|
27996
|
-
rep.warn(
|
|
27997
|
-
for (const f of askFailed) console.log(` ${
|
|
27998
|
-
console.log(` ${
|
|
28128
|
+
rep.warn(chalk2.yellow("Could not ask your server what MCP servers are on your account"));
|
|
28129
|
+
for (const f of askFailed) console.log(` ${chalk2.dim(f)}`);
|
|
28130
|
+
console.log(` ${chalk2.dim("Re-run `chat-recall toolkit pull` once the server answers.")}`);
|
|
27999
28131
|
} else if (written.length === 0 && present === 0) {
|
|
28000
28132
|
} else {
|
|
28001
|
-
const
|
|
28002
|
-
if (
|
|
28003
|
-
|
|
28133
|
+
const needs = collectEnvNeeds(written);
|
|
28134
|
+
if (needs.length) {
|
|
28135
|
+
const [head, ...rest] = renderEnvNeeds(needs, written.length);
|
|
28136
|
+
rep.warn(head);
|
|
28137
|
+
for (const line of rest) console.log(line);
|
|
28004
28138
|
}
|
|
28005
28139
|
}
|
|
28006
28140
|
} catch (err) {
|
|
28007
|
-
rep.warn(`${
|
|
28008
|
-
console.log(` ${
|
|
28141
|
+
rep.warn(`${chalk2.yellow("Could not install account MCPs")} \u2014 ${err instanceof Error ? err.message : err}`);
|
|
28142
|
+
console.log(` ${chalk2.dim("Re-run `chat-recall toolkit pull` later.")}`);
|
|
28009
28143
|
}
|
|
28010
28144
|
}
|
|
28011
28145
|
if (deadTarget) {
|
|
28012
|
-
console.log(
|
|
28013
|
-
console.log(` ${
|
|
28146
|
+
console.log(chalk2.bold("Skipping first sync \u2014 the saved server is not serving chat-recall"));
|
|
28147
|
+
console.log(` ${chalk2.dim(`Re-point with the two commands above, then run ${chalk2.bold("chat-recall sync")}.`)}`);
|
|
28014
28148
|
} else if (!options.skipSync && firstTarget()) {
|
|
28015
28149
|
const { watchFirstSync: watchFirstSync2, spawnDetachedSync: spawnDetachedSync2, handOffLine: handOffLine2, progressLine: renderProgress } = await Promise.resolve().then(() => (init_init_progress(), init_progress_exports));
|
|
28016
28150
|
const { readCollectorHealth: readCollectorHealth2 } = await Promise.resolve().then(() => (init_collector_health(), collector_health_exports));
|
|
28017
|
-
const label = (text) => `${
|
|
28151
|
+
const label = (text) => `${chalk2.bold("Indexing")} ${text}`;
|
|
28018
28152
|
const tty = process.stdout.isTTY === true;
|
|
28019
28153
|
const outcome = await watchFirstSync2({
|
|
28020
28154
|
startWorker: () => spawnDetachedSync2(),
|
|
@@ -28032,22 +28166,22 @@ program.command("init").description("Set up chat-recall: connect to your server,
|
|
|
28032
28166
|
});
|
|
28033
28167
|
if (tty) process.stdout.write("\r");
|
|
28034
28168
|
if (outcome.finished) {
|
|
28035
|
-
console.log(label(
|
|
28169
|
+
console.log(label(chalk2.green(renderProgress(outcome.snapshot))));
|
|
28036
28170
|
} else {
|
|
28037
|
-
console.log(label(
|
|
28038
|
-
console.log(` ${
|
|
28171
|
+
console.log(label(chalk2.dim(handOffLine2(outcome.snapshot))));
|
|
28172
|
+
console.log(` ${chalk2.dim("watch it with: " + chalk2.bold("chat-recall status"))}`);
|
|
28039
28173
|
}
|
|
28040
28174
|
const failed = Object.entries(readCollectorHealth2()?.targets ?? {}).filter(([, t]) => (t.failures ?? 0) > 0 && t.lastError);
|
|
28041
28175
|
for (const [server, t] of failed) {
|
|
28042
|
-
console.log(` ${
|
|
28176
|
+
console.log(` ${chalk2.yellow("sync error")} ${chalk2.dim(server)}: ${t.lastError}`);
|
|
28043
28177
|
}
|
|
28044
28178
|
} else if (!options.skipSync) {
|
|
28045
28179
|
} else {
|
|
28046
28180
|
}
|
|
28047
28181
|
void options.skipService;
|
|
28048
|
-
rep.done(
|
|
28182
|
+
rep.done(chalk2.green.bold("\u2713 Done"));
|
|
28049
28183
|
} catch (err) {
|
|
28050
|
-
console.error(
|
|
28184
|
+
console.error(chalk2.red("Error:"), err);
|
|
28051
28185
|
process.exit(1);
|
|
28052
28186
|
}
|
|
28053
28187
|
});
|
|
@@ -28057,57 +28191,57 @@ program.command("install-skills").description("Install the chat-recall skills in
|
|
|
28057
28191
|
const r2 = mod.uninstallSkills();
|
|
28058
28192
|
const hits2 = r2.perTarget.filter((t) => t.removed.length > 0);
|
|
28059
28193
|
if (!hits2.length) {
|
|
28060
|
-
console.log(
|
|
28194
|
+
console.log(chalk2.dim("No chat-recall skills were installed."));
|
|
28061
28195
|
return;
|
|
28062
28196
|
}
|
|
28063
|
-
for (const t of hits2) console.log(`${
|
|
28197
|
+
for (const t of hits2) console.log(`${chalk2.green("\u2713")} ${t.label}: removed ${t.removed.length} skill(s)`);
|
|
28064
28198
|
return;
|
|
28065
28199
|
}
|
|
28066
28200
|
const r = mod.installSkills({ onlyAvailable: !opts.allTools });
|
|
28067
28201
|
const names = mod.bundledSkillNames();
|
|
28068
|
-
console.log(
|
|
28202
|
+
console.log(chalk2.bold(`Installing ${names.length} chat-recall skill(s) (v${r.version}):`), chalk2.dim(names.join(", ")));
|
|
28069
28203
|
const hits = r.perTarget.filter((t) => t.installed.length > 0);
|
|
28070
28204
|
if (!hits.length) {
|
|
28071
|
-
console.log(
|
|
28205
|
+
console.log(chalk2.yellow("No supported AI tools detected. Use --all-tools to install anyway."));
|
|
28072
28206
|
return;
|
|
28073
28207
|
}
|
|
28074
|
-
for (const t of hits) console.log(`${
|
|
28208
|
+
for (const t of hits) console.log(`${chalk2.green("\u2713")} ${t.label}: ${t.installed.length} \u2192 ${chalk2.dim(t.dir)}`);
|
|
28075
28209
|
const userOwned = [...new Set(r.perTarget.flatMap((t) => t.skippedUserOwned))];
|
|
28076
|
-
if (userOwned.length) console.log(
|
|
28210
|
+
if (userOwned.length) console.log(chalk2.yellow(`Kept your existing skill(s) of the same name: ${userOwned.join(", ")}`));
|
|
28077
28211
|
});
|
|
28078
28212
|
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
28213
|
try {
|
|
28080
28214
|
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28081
28215
|
const r = await syncSessions2(options.force ? { useLedger: false } : {});
|
|
28082
|
-
console.log(
|
|
28216
|
+
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
28217
|
} catch (err) {
|
|
28084
|
-
console.error(
|
|
28218
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28085
28219
|
process.exit(1);
|
|
28086
28220
|
}
|
|
28087
28221
|
});
|
|
28088
28222
|
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
28223
|
try {
|
|
28090
|
-
const mode = options.apply ?
|
|
28224
|
+
const mode = options.apply ? chalk2.red("APPLY (writing to server)") : chalk2.cyan("DRY-RUN (read-only)");
|
|
28091
28225
|
const render = (r) => {
|
|
28092
28226
|
const head = `${r.sessionId.slice(0, 8)}\u2026`;
|
|
28093
28227
|
if (r.status === "no-archive") {
|
|
28094
|
-
console.log(
|
|
28228
|
+
console.log(chalk2.yellow(` ? ${head} no archive found`));
|
|
28095
28229
|
return;
|
|
28096
28230
|
}
|
|
28097
28231
|
if (r.status === "already-full") {
|
|
28098
|
-
console.log(
|
|
28232
|
+
console.log(chalk2.dim(` = ${head} already full (${r.fullestMessages} msgs)`));
|
|
28099
28233
|
return;
|
|
28100
28234
|
}
|
|
28101
28235
|
if (r.status === "error") {
|
|
28102
|
-
console.log(
|
|
28236
|
+
console.log(chalk2.red(` \u2717 ${head} ${r.note}`));
|
|
28103
28237
|
return;
|
|
28104
28238
|
}
|
|
28105
28239
|
const verb = r.status === "would-repair" ? "would recover" : "recovered";
|
|
28106
|
-
console.log(
|
|
28107
|
-
for (const p of r.pushed) console.log(
|
|
28240
|
+
console.log(chalk2.green(` ${r.status === "would-repair" ? "\xB7" : "+"} ${head} ${verb} ${r.fullestMessages} msgs`) + chalk2.dim(` from ${r.fullestSource}`));
|
|
28241
|
+
for (const p of r.pushed) console.log(chalk2.dim(` ${p.server}: ${p.before} \u2192 ${p.after}`));
|
|
28108
28242
|
};
|
|
28109
28243
|
if (options.all) {
|
|
28110
|
-
console.log(
|
|
28244
|
+
console.log(chalk2.bold(`repair --all \xB7 ${mode} \xB7 window ${options.sinceHours}h`));
|
|
28111
28245
|
const { repairAll: repairAll2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
|
|
28112
28246
|
const report = await repairAll2({
|
|
28113
28247
|
sinceHours: Number(options.sinceHours) || 72,
|
|
@@ -28118,24 +28252,24 @@ program.command("repair [sessionIds...]").description("Rebuild sessions an upstr
|
|
|
28118
28252
|
report.candidates.forEach(render);
|
|
28119
28253
|
const fixed2 = report.candidates.filter((r) => r.status === "repaired").length;
|
|
28120
28254
|
const would2 = report.candidates.filter((r) => r.status === "would-repair").length;
|
|
28121
|
-
console.log(
|
|
28255
|
+
console.log(chalk2.bold(
|
|
28122
28256
|
`Scanned ${report.scanned} session(s) on ${report.discoveryServer}; ` + (options.apply ? `repaired ${fixed2}.` : `${would2} damaged, would repair. Re-run with --apply to write.`)
|
|
28123
28257
|
));
|
|
28124
28258
|
return;
|
|
28125
28259
|
}
|
|
28126
28260
|
if (!sessionIds || sessionIds.length === 0) {
|
|
28127
|
-
console.error(
|
|
28261
|
+
console.error(chalk2.red("Give session ids, or use --all. e.g. `chat-recall repair <uuid>` or `chat-recall repair --all`."));
|
|
28128
28262
|
process.exit(1);
|
|
28129
28263
|
}
|
|
28130
|
-
console.log(
|
|
28264
|
+
console.log(chalk2.bold(`repair \xB7 ${mode}`));
|
|
28131
28265
|
const { repairSessions: repairSessions2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
|
|
28132
28266
|
const results = await repairSessions2(sessionIds, { dryRun: !options.apply, force: options.force, verbose: options.verbose, server: options.server });
|
|
28133
28267
|
results.forEach(render);
|
|
28134
28268
|
const fixed = results.filter((r) => r.status === "repaired").length;
|
|
28135
28269
|
const would = results.filter((r) => r.status === "would-repair").length;
|
|
28136
|
-
console.log(
|
|
28270
|
+
console.log(chalk2.bold(options.apply ? `Repaired ${fixed} session(s).` : `${would} session(s) would be repaired. Re-run with --apply to write.`));
|
|
28137
28271
|
} catch (err) {
|
|
28138
|
-
console.error(
|
|
28272
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28139
28273
|
process.exit(1);
|
|
28140
28274
|
}
|
|
28141
28275
|
});
|
|
@@ -28145,11 +28279,11 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28145
28279
|
const remote = await serverPost("/api/search", { query, topK, projectFilter: options.project });
|
|
28146
28280
|
const results = remote.results;
|
|
28147
28281
|
if (results.length === 0) {
|
|
28148
|
-
console.log(
|
|
28282
|
+
console.log(chalk2.yellow("No matching sessions found."));
|
|
28149
28283
|
process.exit(0);
|
|
28150
28284
|
}
|
|
28151
28285
|
console.log();
|
|
28152
|
-
console.log(`${
|
|
28286
|
+
console.log(`${chalk2.bold("Results for:")} "${query}"`);
|
|
28153
28287
|
console.log();
|
|
28154
28288
|
const tiers = tierAll(results);
|
|
28155
28289
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -28158,13 +28292,13 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28158
28292
|
if (projectPath.length > 50) projectPath = "..." + projectPath.slice(-47);
|
|
28159
28293
|
let title = (result.firstPrompt || "").replace(/\n/g, " ").trim();
|
|
28160
28294
|
if (title.length > 80) title = title.slice(0, 80) + "...";
|
|
28161
|
-
console.log(`${
|
|
28162
|
-
if (projectPath) console.log(` ${
|
|
28163
|
-
console.log(` ${
|
|
28295
|
+
console.log(`${chalk2.bold.cyan(`#${i + 1}`)} ${title}`);
|
|
28296
|
+
if (projectPath) console.log(` ${chalk2.dim("Project:")} ${projectPath}`);
|
|
28297
|
+
console.log(` ${chalk2.dim("Match:")} ${matchLabel(tiers[i])}`);
|
|
28164
28298
|
if (result.summary) {
|
|
28165
28299
|
let summary = result.summary.replace(/\n/g, " ").trim();
|
|
28166
28300
|
if (summary.length > 200) summary = summary.slice(0, 200) + "...";
|
|
28167
|
-
console.log(` ${
|
|
28301
|
+
console.log(` ${chalk2.yellow("Summary:")} ${summary}`);
|
|
28168
28302
|
}
|
|
28169
28303
|
const chunk = (result.matchedChunks || []).find(
|
|
28170
28304
|
(c) => c.chunkType !== "summary" && c.chunkType !== "first_prompt"
|
|
@@ -28173,16 +28307,16 @@ program.command("search <query>").description("Search your server for relevant s
|
|
|
28173
28307
|
const label = chunk.chunkType === "assistant" ? "Discussed" : chunk.chunkType === "user_context" ? "Asked about" : chunk.chunkType === "tool_result" ? "Tool result" : "Context";
|
|
28174
28308
|
let text = chunk.text.replace(/\n/g, " ").trim();
|
|
28175
28309
|
if (text.length > 150) text = text.slice(0, 150) + "...";
|
|
28176
|
-
console.log(` ${
|
|
28310
|
+
console.log(` ${chalk2.magenta(label + ":")} ${text}`);
|
|
28177
28311
|
}
|
|
28178
28312
|
{
|
|
28179
28313
|
const rc = resumeCommandFor(result.sessionId);
|
|
28180
|
-
if (rc) console.log(` ${
|
|
28314
|
+
if (rc) console.log(` ${chalk2.green("Resume:")} ${rc}`);
|
|
28181
28315
|
}
|
|
28182
28316
|
console.log();
|
|
28183
28317
|
}
|
|
28184
28318
|
} catch (err) {
|
|
28185
|
-
console.error(
|
|
28319
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28186
28320
|
process.exit(1);
|
|
28187
28321
|
}
|
|
28188
28322
|
});
|
|
@@ -28190,7 +28324,7 @@ program.command("status").description("Show your server's sync status and statis
|
|
|
28190
28324
|
try {
|
|
28191
28325
|
const status = await serverGet("/api/status");
|
|
28192
28326
|
const sync = await serverGet("/api/status/sync");
|
|
28193
|
-
console.log(
|
|
28327
|
+
console.log(chalk2.bold("Chat-Recall Server Status"));
|
|
28194
28328
|
console.log();
|
|
28195
28329
|
console.log(`Synced sessions: ${sync.sessions}`);
|
|
28196
28330
|
console.log(`FTS5 chunks: ${status.totalChunks}`);
|
|
@@ -28202,34 +28336,34 @@ program.command("status").description("Show your server's sync status and statis
|
|
|
28202
28336
|
const types = Object.entries(sync.sourceTypes).filter(([, n]) => Number(n) > 0);
|
|
28203
28337
|
if (types.length > 0) {
|
|
28204
28338
|
console.log();
|
|
28205
|
-
console.log(
|
|
28339
|
+
console.log(chalk2.bold("By source type:"));
|
|
28206
28340
|
for (const [type, n] of types) console.log(` ${type}: ${n} items`);
|
|
28207
28341
|
}
|
|
28208
28342
|
const projects = Object.entries(status.projects || {}).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
28209
28343
|
if (projects.length > 0) {
|
|
28210
28344
|
console.log();
|
|
28211
|
-
console.log(
|
|
28345
|
+
console.log(chalk2.bold("Top projects:"));
|
|
28212
28346
|
for (const [proj, n] of projects) console.log(` ${proj}: ${n} sessions`);
|
|
28213
28347
|
}
|
|
28214
28348
|
} catch (err) {
|
|
28215
|
-
console.error(
|
|
28349
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28216
28350
|
process.exit(1);
|
|
28217
28351
|
}
|
|
28218
28352
|
});
|
|
28219
28353
|
program.command("optimize").description("Deprecated: storage is server-side now, nothing to optimize locally").action(() => {
|
|
28220
|
-
console.log(
|
|
28221
|
-
console.log(
|
|
28354
|
+
console.log(chalk2.dim("Storage is server-side now; there is no local index to optimize."));
|
|
28355
|
+
console.log(chalk2.dim("Run `chat-recall sync` to ship sessions; the server manages its own storage."));
|
|
28222
28356
|
process.exit(0);
|
|
28223
28357
|
});
|
|
28224
28358
|
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
28359
|
try {
|
|
28226
28360
|
const soft = await serverGetSoft(`/api/conversations/${encodeURIComponent(sessionId)}?limit=0`);
|
|
28227
28361
|
if (!soft.data || soft.data.messages.length === 0) {
|
|
28228
|
-
console.log(
|
|
28362
|
+
console.log(chalk2.yellow(soft.message || `Session not found: ${sessionId}`));
|
|
28229
28363
|
process.exit(soft.status === 404 ? 1 : 0);
|
|
28230
28364
|
}
|
|
28231
28365
|
const messagesList = soft.data.messages;
|
|
28232
|
-
console.log(
|
|
28366
|
+
console.log(chalk2.bold("Session:"), sessionId);
|
|
28233
28367
|
console.log();
|
|
28234
28368
|
const maxMessages = parseInt(options.messages, 10);
|
|
28235
28369
|
const displayMessages = options.full ? messagesList : messagesList.slice(0, maxMessages);
|
|
@@ -28237,22 +28371,22 @@ program.command("show <session_id>").description("Show conversation content from
|
|
|
28237
28371
|
let text = msg.content;
|
|
28238
28372
|
if (!options.full && text.length > 1e3) text = text.slice(0, 1e3) + "...";
|
|
28239
28373
|
if (msg.role === "user") {
|
|
28240
|
-
console.log(`${
|
|
28374
|
+
console.log(`${chalk2.bold.blue("User")} ${chalk2.dim(`(line ${msg.line})`)}`);
|
|
28241
28375
|
} else if (msg.role === "assistant") {
|
|
28242
|
-
console.log(`${
|
|
28376
|
+
console.log(`${chalk2.bold.green("Assistant")} ${chalk2.dim(`(line ${msg.line})`)}`);
|
|
28243
28377
|
} else {
|
|
28244
|
-
console.log(
|
|
28378
|
+
console.log(chalk2.bold.yellow(msg.role));
|
|
28245
28379
|
}
|
|
28246
28380
|
console.log(text);
|
|
28247
28381
|
console.log();
|
|
28248
28382
|
}
|
|
28249
|
-
console.log(
|
|
28383
|
+
console.log(chalk2.dim(`Showing ${displayMessages.length} of ${messagesList.length} messages. Use --full for the complete conversation.`));
|
|
28250
28384
|
{
|
|
28251
28385
|
const rc = resumeCommandFor(sessionId);
|
|
28252
|
-
if (rc) console.log(
|
|
28386
|
+
if (rc) console.log(chalk2.dim(`Resume: ${rc}`));
|
|
28253
28387
|
}
|
|
28254
28388
|
} catch (err) {
|
|
28255
|
-
console.error(
|
|
28389
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28256
28390
|
process.exit(1);
|
|
28257
28391
|
}
|
|
28258
28392
|
});
|
|
@@ -28261,9 +28395,9 @@ memory.command("index").description("Collect local sessions and ship them to you
|
|
|
28261
28395
|
try {
|
|
28262
28396
|
const { syncSessions: syncSessions2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28263
28397
|
const r = await syncSessions2(options.force ? { useLedger: false } : {});
|
|
28264
|
-
console.log(
|
|
28398
|
+
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
28399
|
} catch (err) {
|
|
28266
|
-
console.error(
|
|
28400
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28267
28401
|
process.exit(1);
|
|
28268
28402
|
}
|
|
28269
28403
|
});
|
|
@@ -28274,55 +28408,55 @@ memory.command("search <query>").description("Search across all memory types on
|
|
|
28274
28408
|
const remote = await serverPost("/api/memory/search", { query, topK, sourceTypes });
|
|
28275
28409
|
const results = remote.results;
|
|
28276
28410
|
if (results.length === 0) {
|
|
28277
|
-
console.log(
|
|
28411
|
+
console.log(chalk2.yellow("No matching results found."));
|
|
28278
28412
|
process.exit(0);
|
|
28279
28413
|
}
|
|
28280
28414
|
console.log();
|
|
28281
|
-
console.log(`${
|
|
28415
|
+
console.log(`${chalk2.bold("Memory search:")} "${query}"`);
|
|
28282
28416
|
console.log();
|
|
28283
28417
|
const tiers = tierAll(results);
|
|
28284
28418
|
for (let i = 0; i < results.length; i++) {
|
|
28285
28419
|
const r = results[i];
|
|
28286
28420
|
const typeColor = {
|
|
28287
|
-
session:
|
|
28288
|
-
plan:
|
|
28289
|
-
task:
|
|
28290
|
-
claude_md:
|
|
28291
|
-
history:
|
|
28292
|
-
paste:
|
|
28293
|
-
diary:
|
|
28294
|
-
}[r.sourceType] ||
|
|
28421
|
+
session: chalk2.blue,
|
|
28422
|
+
plan: chalk2.green,
|
|
28423
|
+
task: chalk2.yellow,
|
|
28424
|
+
claude_md: chalk2.magenta,
|
|
28425
|
+
history: chalk2.cyan,
|
|
28426
|
+
paste: chalk2.gray,
|
|
28427
|
+
diary: chalk2.redBright
|
|
28428
|
+
}[r.sourceType] || chalk2.white;
|
|
28295
28429
|
const typeBadge = typeColor(`[${r.sourceType}]`);
|
|
28296
28430
|
let title = (r.title || "").replace(/\n/g, " ").trim();
|
|
28297
28431
|
if (title.length > 70) title = title.slice(0, 67) + "...";
|
|
28298
|
-
console.log(`${
|
|
28432
|
+
console.log(`${chalk2.bold.cyan(`#${i + 1}`)} ${typeBadge} ${title}`);
|
|
28299
28433
|
if (r.projectPath) {
|
|
28300
28434
|
let pp = r.projectPath;
|
|
28301
28435
|
if (pp.length > 50) pp = "..." + pp.slice(-47);
|
|
28302
|
-
console.log(` ${
|
|
28436
|
+
console.log(` ${chalk2.dim("Project:")} ${pp}`);
|
|
28303
28437
|
}
|
|
28304
|
-
const typeSuffix = r.chunkType ? ` ${
|
|
28305
|
-
console.log(` ${
|
|
28438
|
+
const typeSuffix = r.chunkType ? ` ${chalk2.dim("Type:")} ${r.chunkType}` : "";
|
|
28439
|
+
console.log(` ${chalk2.dim("Match:")} ${matchLabel(tiers[i])}${typeSuffix}`);
|
|
28306
28440
|
let preview = (r.text || "").replace(/\n/g, " ").trim();
|
|
28307
28441
|
if (preview.length > 150) preview = preview.slice(0, 147) + "...";
|
|
28308
28442
|
if (preview) console.log(` ${preview}`);
|
|
28309
28443
|
if (r.sourceType === "session") {
|
|
28310
28444
|
{
|
|
28311
28445
|
const rc = resumeCommandFor(r.itemId);
|
|
28312
|
-
if (rc) console.log(` ${
|
|
28446
|
+
if (rc) console.log(` ${chalk2.green("Resume:")} ${rc}`);
|
|
28313
28447
|
}
|
|
28314
28448
|
}
|
|
28315
28449
|
console.log();
|
|
28316
28450
|
}
|
|
28317
28451
|
} catch (err) {
|
|
28318
|
-
console.error(
|
|
28452
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28319
28453
|
process.exit(1);
|
|
28320
28454
|
}
|
|
28321
28455
|
});
|
|
28322
28456
|
memory.command("status").description("Show your server's memory statistics across all source types (requires login)").action(async () => {
|
|
28323
28457
|
try {
|
|
28324
28458
|
const status = await serverGet("/api/memory/status");
|
|
28325
|
-
console.log(
|
|
28459
|
+
console.log(chalk2.bold("Memory System Status"));
|
|
28326
28460
|
console.log();
|
|
28327
28461
|
console.log(`Total items: ${status.totalItems}`);
|
|
28328
28462
|
console.log(`FTS5 chunks: ${status.totalChunks}`);
|
|
@@ -28330,7 +28464,7 @@ memory.command("status").description("Show your server's memory statistics acros
|
|
|
28330
28464
|
const bySource = Object.entries(status.bySourceType || {});
|
|
28331
28465
|
if (bySource.length > 0) {
|
|
28332
28466
|
console.log();
|
|
28333
|
-
console.log(
|
|
28467
|
+
console.log(chalk2.bold("By source type:"));
|
|
28334
28468
|
for (const [type, data] of bySource) {
|
|
28335
28469
|
console.log(` ${type}: ${data.items} items, ${data.chunks} chunks`);
|
|
28336
28470
|
}
|
|
@@ -28338,14 +28472,14 @@ memory.command("status").description("Show your server's memory statistics acros
|
|
|
28338
28472
|
const byTool = Object.entries(status.bySourceAndTool || {});
|
|
28339
28473
|
if (byTool.length > 0) {
|
|
28340
28474
|
console.log();
|
|
28341
|
-
console.log(
|
|
28475
|
+
console.log(chalk2.bold("By source type and tool:"));
|
|
28342
28476
|
for (const [type, tools] of byTool) {
|
|
28343
28477
|
const parts = Object.entries(tools).map(([t, n]) => `${t}: ${n}`).join(", ");
|
|
28344
28478
|
console.log(` ${type}: ${parts}`);
|
|
28345
28479
|
}
|
|
28346
28480
|
}
|
|
28347
28481
|
} catch (err) {
|
|
28348
|
-
console.error(
|
|
28482
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28349
28483
|
process.exit(1);
|
|
28350
28484
|
}
|
|
28351
28485
|
});
|
|
@@ -28353,20 +28487,20 @@ memory.command("links <source_type> <item_id>").description("Show relationships
|
|
|
28353
28487
|
try {
|
|
28354
28488
|
const { links } = await serverGet(`/api/memory/links/${encodeURIComponent(sourceType)}/${encodeURIComponent(itemId)}`);
|
|
28355
28489
|
if (links.length === 0) {
|
|
28356
|
-
console.log(
|
|
28490
|
+
console.log(chalk2.yellow("No links found."));
|
|
28357
28491
|
return;
|
|
28358
28492
|
}
|
|
28359
|
-
console.log(
|
|
28493
|
+
console.log(chalk2.bold(`Links for ${sourceType}:${itemId}`));
|
|
28360
28494
|
console.log();
|
|
28361
28495
|
for (const link of links) {
|
|
28362
28496
|
const direction = link.source_id === itemId ? "->" : "<-";
|
|
28363
28497
|
const otherType = link.source_id === itemId ? link.target_type : link.source_type;
|
|
28364
28498
|
const otherId = link.source_id === itemId ? link.target_id : link.source_id;
|
|
28365
28499
|
const confidence = Math.round(link.confidence * 100);
|
|
28366
|
-
console.log(` ${direction} ${
|
|
28500
|
+
console.log(` ${direction} ${chalk2.bold(otherType)}:${otherId.slice(0, 20)} [${link.link_type}] (${confidence}%)`);
|
|
28367
28501
|
}
|
|
28368
28502
|
} catch (err) {
|
|
28369
|
-
console.error(
|
|
28503
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28370
28504
|
process.exit(1);
|
|
28371
28505
|
}
|
|
28372
28506
|
});
|
|
@@ -28378,27 +28512,27 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
|
|
|
28378
28512
|
identity = readFileSync49(identityFile, "utf-8").trim();
|
|
28379
28513
|
}
|
|
28380
28514
|
const lines = [
|
|
28381
|
-
|
|
28382
|
-
|
|
28515
|
+
chalk2.bold("# Wake-Up Context"),
|
|
28516
|
+
chalk2.bold("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
28383
28517
|
"",
|
|
28384
|
-
|
|
28518
|
+
chalk2.bold("## Identity"),
|
|
28385
28519
|
identity,
|
|
28386
28520
|
""
|
|
28387
28521
|
];
|
|
28388
28522
|
const qs = options.project ? `?project_filter=${encodeURIComponent(options.project)}` : "";
|
|
28389
28523
|
const wake = await serverGet(`/api/memory/wake-up${qs}`);
|
|
28390
28524
|
if (wake.highFacts.length > 0) {
|
|
28391
|
-
lines.push(
|
|
28525
|
+
lines.push(chalk2.bold("## High-Importance Facts"));
|
|
28392
28526
|
for (const fact of wake.highFacts) {
|
|
28393
28527
|
lines.push(` [${fact.type}] ${fact.text}`);
|
|
28394
28528
|
}
|
|
28395
28529
|
lines.push("");
|
|
28396
28530
|
}
|
|
28397
28531
|
if (wake.kg.facts.length > 0) {
|
|
28398
|
-
lines.push(
|
|
28532
|
+
lines.push(chalk2.bold("## Knowledge Graph"));
|
|
28399
28533
|
const { entities, current_facts } = wake.kg.stats;
|
|
28400
28534
|
if (entities !== void 0 || current_facts !== void 0) {
|
|
28401
|
-
lines.push(
|
|
28535
|
+
lines.push(chalk2.dim(`${entities ?? 0} entities, ${current_facts ?? 0} current facts`));
|
|
28402
28536
|
}
|
|
28403
28537
|
for (const fact of wake.kg.facts) {
|
|
28404
28538
|
lines.push(` ${fact.subject} \u2192 ${fact.predicate} \u2192 ${fact.object}`);
|
|
@@ -28407,32 +28541,75 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
|
|
|
28407
28541
|
}
|
|
28408
28542
|
console.log(lines.join("\n"));
|
|
28409
28543
|
} catch (err) {
|
|
28410
|
-
console.error(
|
|
28544
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
28411
28545
|
process.exit(1);
|
|
28412
28546
|
}
|
|
28413
28547
|
});
|
|
28414
28548
|
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
28549
|
if (!sessionId && !opts.latest) {
|
|
28416
|
-
console.error(
|
|
28550
|
+
console.error(chalk2.red("Pass a session id or --latest."));
|
|
28417
28551
|
process.exit(1);
|
|
28418
28552
|
}
|
|
28419
28553
|
const target = firstTarget();
|
|
28420
28554
|
if (!target) {
|
|
28421
|
-
console.log(
|
|
28555
|
+
console.log(chalk2.dim("escalate: not logged in \u2014 nothing to do. Run `chat-recall login <server-url>` to enable learnings escalation."));
|
|
28422
28556
|
return;
|
|
28423
28557
|
}
|
|
28424
28558
|
try {
|
|
28425
28559
|
const { runEscalate: runEscalate2 } = await Promise.resolve().then(() => (init_escalate(), escalate_exports));
|
|
28426
28560
|
await runEscalate2(
|
|
28427
|
-
{ get: serverGet, getSoft: serverGetSoft, post: serverPost, log: (line) => console.log(
|
|
28561
|
+
{ get: serverGet, getSoft: serverGetSoft, post: serverPost, log: (line) => console.log(chalk2.dim(line)) },
|
|
28428
28562
|
{ sessionId, latest: opts.latest, project: opts.project, dryRun: opts.dryRun, cwd: process.cwd() }
|
|
28429
28563
|
);
|
|
28430
28564
|
} catch (err) {
|
|
28431
|
-
console.log(
|
|
28565
|
+
console.log(chalk2.dim(`escalate: skipped \u2014 ${err instanceof Error ? err.message : err}`));
|
|
28566
|
+
}
|
|
28567
|
+
});
|
|
28568
|
+
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) => {
|
|
28569
|
+
const message = words.join(" ").trim();
|
|
28570
|
+
if (message.length < 3) {
|
|
28571
|
+
console.error(chalk2.red("Say a little more than that."));
|
|
28572
|
+
process.exitCode = 1;
|
|
28573
|
+
return;
|
|
28574
|
+
}
|
|
28575
|
+
const target = firstTarget();
|
|
28576
|
+
const base2 = target?.base || (process.env.CHAT_RECALL_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
|
|
28577
|
+
try {
|
|
28578
|
+
const res = await fetch(`${base2}/api/contact/feedback`, {
|
|
28579
|
+
method: "POST",
|
|
28580
|
+
headers: {
|
|
28581
|
+
"content-type": "application/json",
|
|
28582
|
+
// Attached when logged in so a reply can find the account. Absent is
|
|
28583
|
+
// fine and expected.
|
|
28584
|
+
...target ? { authorization: `Bearer ${target.token}` } : {}
|
|
28585
|
+
},
|
|
28586
|
+
body: JSON.stringify({
|
|
28587
|
+
message,
|
|
28588
|
+
email: opts.email ?? null,
|
|
28589
|
+
cliVersion: pkgVersion,
|
|
28590
|
+
os: `${process.platform}-${process.arch}`
|
|
28591
|
+
}),
|
|
28592
|
+
signal: AbortSignal.timeout(1e4)
|
|
28593
|
+
});
|
|
28594
|
+
if (!res.ok) {
|
|
28595
|
+
const body = await res.json().catch(() => ({}));
|
|
28596
|
+
console.error(chalk2.red(`Could not send: ${body.error || res.status}`));
|
|
28597
|
+
process.exitCode = 1;
|
|
28598
|
+
return;
|
|
28599
|
+
}
|
|
28600
|
+
console.log(chalk2.green("Sent. Thank you \u2014 this genuinely gets read."));
|
|
28601
|
+
if (!opts.email) {
|
|
28602
|
+
console.log(chalk2.dim(" Add --email <address> next time if you want a reply."));
|
|
28603
|
+
}
|
|
28604
|
+
} catch (err) {
|
|
28605
|
+
console.error(chalk2.red(`Could not reach ${base2}.`));
|
|
28606
|
+
console.error(chalk2.dim(" Your message, so it is not lost:"));
|
|
28607
|
+
console.error(chalk2.dim(` ${message}`));
|
|
28608
|
+
process.exitCode = 1;
|
|
28432
28609
|
}
|
|
28433
28610
|
});
|
|
28434
28611
|
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:
|
|
28612
|
+
const { existsSync: existsSync57, readFileSync: readFileSync50, statSync: statSync47 } = await import("fs");
|
|
28436
28613
|
const { execSync: execSync6 } = await import("child_process");
|
|
28437
28614
|
const rows = [];
|
|
28438
28615
|
const note = (ok, label, detail) => rows.push({ ok, label, detail });
|
|
@@ -28500,7 +28677,7 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28500
28677
|
note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
|
|
28501
28678
|
} else {
|
|
28502
28679
|
try {
|
|
28503
|
-
const mode =
|
|
28680
|
+
const mode = statSync47(credFile).mode & 511;
|
|
28504
28681
|
const isWin = process.platform === "win32";
|
|
28505
28682
|
const safe = isWin || (mode & 63) === 0;
|
|
28506
28683
|
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 +28815,11 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28638
28815
|
} else if (legacyPresent.length === 0) {
|
|
28639
28816
|
note(true, "Purge legacy index", "nothing to remove");
|
|
28640
28817
|
} else {
|
|
28641
|
-
const { rmSync:
|
|
28818
|
+
const { rmSync: rmSync10 } = await import("fs");
|
|
28642
28819
|
let removed = 0;
|
|
28643
28820
|
for (const p of legacyPresent) {
|
|
28644
28821
|
try {
|
|
28645
|
-
|
|
28822
|
+
rmSync10(p, { recursive: true, force: true });
|
|
28646
28823
|
removed++;
|
|
28647
28824
|
} catch {
|
|
28648
28825
|
}
|
|
@@ -28654,67 +28831,67 @@ program.command("doctor").description("Quick health check across login, server,
|
|
|
28654
28831
|
} else {
|
|
28655
28832
|
note(true, "Legacy local index", "none (clean)");
|
|
28656
28833
|
}
|
|
28657
|
-
console.log(
|
|
28834
|
+
console.log(chalk2.bold("\nchat-recall doctor\n"));
|
|
28658
28835
|
for (const r of rows) {
|
|
28659
|
-
const icon = r.ok ?
|
|
28660
|
-
console.log(` ${icon} ${r.label.padEnd(28)} ${
|
|
28836
|
+
const icon = r.ok ? chalk2.green("\u2713") : chalk2.yellow("\u2022");
|
|
28837
|
+
console.log(` ${icon} ${r.label.padEnd(28)} ${chalk2.dim(r.detail || "")}`);
|
|
28661
28838
|
}
|
|
28662
28839
|
const failures = rows.filter((r) => !r.ok).length;
|
|
28663
28840
|
console.log();
|
|
28664
28841
|
if (failures === 0) {
|
|
28665
|
-
console.log(
|
|
28842
|
+
console.log(chalk2.green("All systems green."));
|
|
28666
28843
|
} else {
|
|
28667
|
-
console.log(
|
|
28844
|
+
console.log(chalk2.dim(`${failures} item${failures === 1 ? "" : "s"} need attention.`));
|
|
28668
28845
|
}
|
|
28669
28846
|
});
|
|
28670
28847
|
var companions = program.command("companions").description("Manage companion tools (codeindex)");
|
|
28671
28848
|
companions.command("status").description("Show whether codeindex is installed and what it provides").action(async () => {
|
|
28672
28849
|
const { checkCodeindexStatus: checkCodeindexStatus2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28673
28850
|
const s = checkCodeindexStatus2();
|
|
28674
|
-
console.log(
|
|
28851
|
+
console.log(chalk2.bold("codeindex status"));
|
|
28675
28852
|
if (s.installed) {
|
|
28676
|
-
console.log(` ${
|
|
28853
|
+
console.log(` ${chalk2.green("\u2713 installed")}`);
|
|
28677
28854
|
if (s.path) console.log(` path: ${s.path}`);
|
|
28678
28855
|
if (s.size) console.log(` size: ${(s.size / 1024 / 1024).toFixed(1)} MB`);
|
|
28679
28856
|
if (s.version) console.log(` version: ${s.version}`);
|
|
28680
28857
|
} else {
|
|
28681
|
-
console.log(` ${
|
|
28858
|
+
console.log(` ${chalk2.yellow("not installed")}`);
|
|
28682
28859
|
if (!s.prebuiltAvailable) {
|
|
28683
|
-
console.log(` ${
|
|
28860
|
+
console.log(` ${chalk2.dim(s.unsupportedReason || "no prebuilt binary for this platform")}`);
|
|
28684
28861
|
} else {
|
|
28685
|
-
console.log(` ${
|
|
28862
|
+
console.log(` ${chalk2.dim(`run \`chat-recall companions install\` to fetch ${s.artifactName}`)}`);
|
|
28686
28863
|
}
|
|
28687
28864
|
}
|
|
28688
28865
|
console.log();
|
|
28689
|
-
console.log(
|
|
28690
|
-
console.log(
|
|
28691
|
-
console.log(
|
|
28866
|
+
console.log(chalk2.dim("codeindex provides code-level lookup as a separate MCP server:"));
|
|
28867
|
+
console.log(chalk2.dim(" find_symbol, find_callers, get_imports, plan_change,"));
|
|
28868
|
+
console.log(chalk2.dim(" get_change_impact, analyze (security/dead_code/coupling/cycles/...)"));
|
|
28692
28869
|
});
|
|
28693
28870
|
companions.command("install").description("Install codeindex (downloads from munhq/codeindex GitHub release)").option("--force", "Re-download even if already installed").action(async (opts) => {
|
|
28694
28871
|
const { installCodeindex: installCodeindex2, registerCodeindexMcp: registerCodeindexMcp2, CODEINDEX_BIN_PATH: CODEINDEX_BIN_PATH2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28695
28872
|
try {
|
|
28696
28873
|
const result = await installCodeindex2({ force: opts.force });
|
|
28697
28874
|
if (!result.prebuiltAvailable && !result.installed) {
|
|
28698
|
-
console.log(
|
|
28699
|
-
console.log(
|
|
28875
|
+
console.log(chalk2.yellow(`codeindex: ${result.unsupportedReason}`));
|
|
28876
|
+
console.log(chalk2.dim(" Build from source: https://github.com/munhq/codeindex#install"));
|
|
28700
28877
|
return;
|
|
28701
28878
|
}
|
|
28702
28879
|
if (result.installed) {
|
|
28703
28880
|
const sizeMb = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : "?";
|
|
28704
|
-
console.log(
|
|
28881
|
+
console.log(chalk2.green(`\u2713 codeindex installed (${sizeMb}) \u2192 ${result.path}`));
|
|
28705
28882
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
28706
28883
|
const reg = registerCodeindexMcp2(mcpJsonPath, CODEINDEX_BIN_PATH2);
|
|
28707
|
-
if (reg.added) console.log(
|
|
28708
|
-
else console.log(
|
|
28884
|
+
if (reg.added) console.log(chalk2.dim(` Registered as MCP server in ${mcpJsonPath}`));
|
|
28885
|
+
else console.log(chalk2.dim(` Already registered as MCP server`));
|
|
28709
28886
|
}
|
|
28710
28887
|
} catch (err) {
|
|
28711
28888
|
const msg = String(err);
|
|
28712
|
-
console.error(
|
|
28889
|
+
console.error(chalk2.red(`codeindex install failed: ${msg}`));
|
|
28713
28890
|
if (msg.includes("404")) {
|
|
28714
|
-
console.error(
|
|
28715
|
-
console.error(
|
|
28891
|
+
console.error(chalk2.dim(" The codeindex release is currently private. Either authenticate `gh`"));
|
|
28892
|
+
console.error(chalk2.dim(" to munhq/codeindex, or build from source: https://github.com/munhq/codeindex"));
|
|
28716
28893
|
} else {
|
|
28717
|
-
console.error(
|
|
28894
|
+
console.error(chalk2.dim(" This does not block chat-recall \u2014 it just disables code-level lookup."));
|
|
28718
28895
|
}
|
|
28719
28896
|
process.exit(1);
|
|
28720
28897
|
}
|
|
@@ -28722,15 +28899,15 @@ companions.command("install").description("Install codeindex (downloads from mun
|
|
|
28722
28899
|
companions.command("uninstall").description("Remove the codeindex binary chat-recall installed (does not touch your other tools)").action(async () => {
|
|
28723
28900
|
const { uninstallCodeindex: uninstallCodeindex2, unregisterCodeindexMcp: unregisterCodeindexMcp2 } = await Promise.resolve().then(() => (init_companions(), companions_exports));
|
|
28724
28901
|
const r = uninstallCodeindex2();
|
|
28725
|
-
if (r.removed) console.log(
|
|
28726
|
-
else console.log(
|
|
28902
|
+
if (r.removed) console.log(chalk2.green(`\u2713 Removed ${r.path}`));
|
|
28903
|
+
else console.log(chalk2.dim(` Nothing to remove at ${r.path}`));
|
|
28727
28904
|
const mcpJsonPath = join60(homedir22(), ".mcp.json");
|
|
28728
28905
|
const u = unregisterCodeindexMcp2(mcpJsonPath);
|
|
28729
|
-
if (u.removed) console.log(
|
|
28906
|
+
if (u.removed) console.log(chalk2.dim(` Unregistered MCP server from ${mcpJsonPath}`));
|
|
28730
28907
|
});
|
|
28731
28908
|
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
28909
|
if (!opts.deep) {
|
|
28733
|
-
console.log(
|
|
28910
|
+
console.log(chalk2.yellow("verify currently implements only --deep; re-run with --deep."));
|
|
28734
28911
|
return;
|
|
28735
28912
|
}
|
|
28736
28913
|
const { verifyAgainstServer: verifyAgainstServer2, verifyTargets: verifyTargets2 } = await Promise.resolve().then(() => (init_verify_deep(), verify_deep_exports));
|
|
@@ -28741,7 +28918,7 @@ program.command("verify").description("Check that the server holds everything th
|
|
|
28741
28918
|
const { forceFullResync: forceFullResync2 } = await Promise.resolve().then(() => (init_verify_repair(), verify_repair_exports));
|
|
28742
28919
|
const targets = verifyTargets2();
|
|
28743
28920
|
if (targets.length === 0) {
|
|
28744
|
-
console.error(
|
|
28921
|
+
console.error(chalk2.red("No server configured \u2014 run `chat-recall login` first."));
|
|
28745
28922
|
process.exit(1);
|
|
28746
28923
|
}
|
|
28747
28924
|
const sinceMs = opts.sinceHours ? Date.now() - Number(opts.sinceHours) * 36e5 : 0;
|
|
@@ -28806,37 +28983,37 @@ program.command("verify").description("Check that the server holds everything th
|
|
|
28806
28983
|
try {
|
|
28807
28984
|
report = await verifyAgainstServer2(t.serverUrl, t.token, sinceMs, deps);
|
|
28808
28985
|
} catch (e) {
|
|
28809
|
-
console.error(
|
|
28986
|
+
console.error(chalk2.red(`${t.serverUrl}: ${e instanceof Error ? e.message : e}`));
|
|
28810
28987
|
exitCode = 1;
|
|
28811
28988
|
continue;
|
|
28812
28989
|
}
|
|
28813
28990
|
reports.push(report);
|
|
28814
28991
|
if (opts.json) continue;
|
|
28815
|
-
console.log(
|
|
28992
|
+
console.log(chalk2.bold(`
|
|
28816
28993
|
${t.serverUrl}`));
|
|
28817
28994
|
console.log(` checked ${report.checked} \xB7 complete ${report.complete} \xB7 no archive ${report.missingArchive}`);
|
|
28818
28995
|
if (report.pending.length > 0) {
|
|
28819
|
-
console.log(
|
|
28996
|
+
console.log(chalk2.dim(` ${report.pending.length} pending (the ledger knows there is more to send \u2014 these go on their own)`));
|
|
28820
28997
|
}
|
|
28821
28998
|
if (report.stranded.length === 0) {
|
|
28822
|
-
console.log(
|
|
28999
|
+
console.log(chalk2.green(" \u2713 nothing stranded"));
|
|
28823
29000
|
} else {
|
|
28824
29001
|
exitCode = 2;
|
|
28825
|
-
console.log(
|
|
29002
|
+
console.log(chalk2.red(` \u2717 ${report.stranded.length} STRANDED \u2014 the ledger claims complete while the server holds less:`));
|
|
28826
29003
|
for (const f of report.stranded.slice(0, 25)) {
|
|
28827
29004
|
console.log(` ${f.sessionId.slice(0, 8)} server short by ${String(f.deficit).padStart(9)} B ${(f.projectPath || "").slice(-44)}`);
|
|
28828
29005
|
}
|
|
28829
|
-
if (report.stranded.length > 25) console.log(
|
|
29006
|
+
if (report.stranded.length > 25) console.log(chalk2.dim(` \u2026and ${report.stranded.length - 25} more`));
|
|
28830
29007
|
if (!opts.repair) {
|
|
28831
|
-
console.log(
|
|
29008
|
+
console.log(chalk2.dim(" Re-run with --repair to clear their cursors and re-ship."));
|
|
28832
29009
|
}
|
|
28833
29010
|
}
|
|
28834
29011
|
if (opts.records) {
|
|
28835
29012
|
const suspects = [...report.stranded, ...report.pending];
|
|
28836
29013
|
if (suspects.length === 0) {
|
|
28837
|
-
console.log(
|
|
29014
|
+
console.log(chalk2.dim(" --records: nothing to deep-check (size pass found no gaps)"));
|
|
28838
29015
|
} else {
|
|
28839
|
-
console.log(
|
|
29016
|
+
console.log(chalk2.bold(`
|
|
28840
29017
|
record-level diff (${suspects.length} session(s)):`));
|
|
28841
29018
|
for (const f of suspects) {
|
|
28842
29019
|
let serverIds;
|
|
@@ -28861,9 +29038,9 @@ ${t.serverUrl}`));
|
|
|
28861
29038
|
}
|
|
28862
29039
|
const missing = [...localIds].filter((u) => !serverIds.has(u)).length;
|
|
28863
29040
|
const onlyServer = [...serverIds].filter((u) => !localIds.has(u)).length;
|
|
28864
|
-
const verdict = missing === 0 ?
|
|
29041
|
+
const verdict = missing === 0 ? chalk2.green("in sync") : chalk2.red(`${missing} MISSING server-side`);
|
|
28865
29042
|
console.log(
|
|
28866
|
-
` ${f.sessionId.slice(0, 8)} local ${String(localIds.size).padStart(6)} server ${String(serverIds.size).padStart(6)} ${verdict}` + (onlyServer > 0 ?
|
|
29043
|
+
` ${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
29044
|
);
|
|
28868
29045
|
if (missing > 0) exitCode = 2;
|
|
28869
29046
|
}
|
|
@@ -28871,7 +29048,7 @@ ${t.serverUrl}`));
|
|
|
28871
29048
|
}
|
|
28872
29049
|
if (opts.repair && report.stranded.length > 0) {
|
|
28873
29050
|
const n = forceFullResync2(t.serverUrl, report.stranded.map((f) => f.sessionId));
|
|
28874
|
-
console.log(
|
|
29051
|
+
console.log(chalk2.green(` \u21BB cleared ${n} ledger cursor(s) \u2014 run \`chat-recall sync\` to re-ship`));
|
|
28875
29052
|
}
|
|
28876
29053
|
}
|
|
28877
29054
|
if (opts.json) console.log(JSON.stringify({ reports }, null, 2));
|
|
@@ -28879,19 +29056,19 @@ ${t.serverUrl}`));
|
|
|
28879
29056
|
});
|
|
28880
29057
|
var sources2 = program.command("sources").description("See and choose which AI-tool transcript folders get synced");
|
|
28881
29058
|
function decisionBadge(d) {
|
|
28882
|
-
if (d === "primary") return
|
|
28883
|
-
if (d === "approved") return
|
|
28884
|
-
if (d === "declined") return
|
|
28885
|
-
return
|
|
29059
|
+
if (d === "primary") return chalk2.green("syncing") + chalk2.dim(" (main)");
|
|
29060
|
+
if (d === "approved") return chalk2.green("syncing");
|
|
29061
|
+
if (d === "declined") return chalk2.dim("not syncing");
|
|
29062
|
+
return chalk2.yellow("NEEDS A DECISION");
|
|
28886
29063
|
}
|
|
28887
29064
|
async function listSources() {
|
|
28888
29065
|
const { discoverHomes: discoverHomes2 } = await Promise.resolve().then(() => (init_home_discovery(), home_discovery_exports));
|
|
28889
29066
|
const { homeDecision: homeDecision2, grandfatherLegacyHomes: grandfatherLegacyHomes3 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28890
29067
|
grandfatherLegacyHomes3();
|
|
28891
29068
|
const homes = discoverHomes2();
|
|
28892
|
-
console.log(
|
|
29069
|
+
console.log(chalk2.bold("Transcript folders found on this machine"));
|
|
28893
29070
|
if (homes.length === 0) {
|
|
28894
|
-
console.log(
|
|
29071
|
+
console.log(chalk2.dim(" none \u2014 no AI tool transcripts detected"));
|
|
28895
29072
|
return { pending: 0 };
|
|
28896
29073
|
}
|
|
28897
29074
|
let pending = 0;
|
|
@@ -28900,14 +29077,14 @@ async function listSources() {
|
|
|
28900
29077
|
if (d === "pending") pending++;
|
|
28901
29078
|
const count = h.sessions > 0 ? `${h.sessions} session${h.sessions === 1 ? "" : "s"}` : "\u2014";
|
|
28902
29079
|
console.log(
|
|
28903
|
-
` ${h.tool.padEnd(9)} ${count.padStart(14)} ${decisionBadge(d).padEnd(30)} ${h.path}` + (h.via === "declared" ?
|
|
29080
|
+
` ${h.tool.padEnd(9)} ${count.padStart(14)} ${decisionBadge(d).padEnd(30)} ${h.path}` + (h.via === "declared" ? chalk2.dim(" (you configured this)") : "")
|
|
28904
29081
|
);
|
|
28905
29082
|
}
|
|
28906
29083
|
if (pending > 0) {
|
|
28907
29084
|
console.log();
|
|
28908
|
-
console.log(
|
|
28909
|
-
console.log(
|
|
28910
|
-
console.log(
|
|
29085
|
+
console.log(chalk2.yellow(` ${pending} folder(s) need a decision \u2014 they are NOT being synced yet.`));
|
|
29086
|
+
console.log(chalk2.dim(" chat-recall sources approve <path> start syncing it"));
|
|
29087
|
+
console.log(chalk2.dim(" chat-recall sources decline <path> keep it out (e.g. a work account)"));
|
|
28911
29088
|
}
|
|
28912
29089
|
return { pending };
|
|
28913
29090
|
}
|
|
@@ -28920,74 +29097,74 @@ sources2.command("add <path>").description("Sync a transcript folder we did not
|
|
|
28920
29097
|
const abs = normalizeHomePath2(path);
|
|
28921
29098
|
const tool = identifyHome2(abs);
|
|
28922
29099
|
if (!tool) {
|
|
28923
|
-
console.error(
|
|
28924
|
-
console.error(
|
|
28925
|
-
console.error(
|
|
28926
|
-
console.error(
|
|
28927
|
-
console.error(
|
|
28928
|
-
console.error(
|
|
28929
|
-
console.error(
|
|
29100
|
+
console.error(chalk2.red(`Not a transcript folder: ${abs}`));
|
|
29101
|
+
console.error(chalk2.dim(" Expected one of:"));
|
|
29102
|
+
console.error(chalk2.dim(" <dir>/projects/<project>/<uuid>.jsonl (Claude Code)"));
|
|
29103
|
+
console.error(chalk2.dim(" <dir>/sessions/YYYY/MM/DD/rollout-*.jsonl (Codex)"));
|
|
29104
|
+
console.error(chalk2.dim(" <dir>/tmp/<project>/chats/session-*.json (Gemini)"));
|
|
29105
|
+
console.error(chalk2.dim(" <dir>/brain/<id>/.system_generated/logs/* (Antigravity)"));
|
|
29106
|
+
console.error(chalk2.dim(" <dir>/opencode.db (OpenCode)"));
|
|
28930
29107
|
process.exit(1);
|
|
28931
29108
|
}
|
|
28932
29109
|
if (!approveHome2(abs)) {
|
|
28933
|
-
console.error(
|
|
29110
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28934
29111
|
process.exit(1);
|
|
28935
29112
|
}
|
|
28936
|
-
console.log(
|
|
28937
|
-
console.log(
|
|
29113
|
+
console.log(chalk2.green(`\u2713 syncing ${tool} transcripts from ${abs}`));
|
|
29114
|
+
console.log(chalk2.dim(" Takes effect on the next sync; run `chat-recall sync` to do it now."));
|
|
28938
29115
|
});
|
|
28939
29116
|
sources2.command("approve <path>").description("Start syncing a folder that was waiting for a decision").action(async (path) => {
|
|
28940
29117
|
const { approveHome: approveHome2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28941
29118
|
const abs = normalizeHomePath2(path);
|
|
28942
29119
|
if (!approveHome2(abs)) {
|
|
28943
|
-
console.error(
|
|
29120
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28944
29121
|
process.exit(1);
|
|
28945
29122
|
}
|
|
28946
|
-
console.log(
|
|
29123
|
+
console.log(chalk2.green(`\u2713 now syncing ${abs}`));
|
|
28947
29124
|
});
|
|
28948
29125
|
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
29126
|
const { declineHome: declineHome2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
28950
29127
|
const { identifyHome: identifyHome2, sessionIdsInHome: sessionIdsInHome2 } = await Promise.resolve().then(() => (init_home_discovery(), home_discovery_exports));
|
|
28951
29128
|
const abs = normalizeHomePath2(path);
|
|
28952
29129
|
if (!declineHome2(abs)) {
|
|
28953
|
-
console.error(
|
|
29130
|
+
console.error(chalk2.red("Could not write settings."));
|
|
28954
29131
|
process.exit(1);
|
|
28955
29132
|
}
|
|
28956
|
-
console.log(
|
|
29133
|
+
console.log(chalk2.green(`\u2713 not syncing ${abs}`));
|
|
28957
29134
|
if (!opts.deleteRemote) {
|
|
28958
|
-
console.log(
|
|
28959
|
-
console.log(
|
|
29135
|
+
console.log(chalk2.dim(" Sessions already uploaded from it stay on the server."));
|
|
29136
|
+
console.log(chalk2.dim(` To remove those too: chat-recall sources decline ${path} --delete-remote`));
|
|
28960
29137
|
return;
|
|
28961
29138
|
}
|
|
28962
29139
|
const tool = identifyHome2(abs);
|
|
28963
29140
|
if (!tool) {
|
|
28964
|
-
console.error(
|
|
29141
|
+
console.error(chalk2.red(`Not a transcript folder: ${abs}`));
|
|
28965
29142
|
process.exit(1);
|
|
28966
29143
|
}
|
|
28967
29144
|
const ids = sessionIdsInHome2(abs, tool);
|
|
28968
29145
|
if (ids.length === 0) {
|
|
28969
|
-
console.log(
|
|
29146
|
+
console.log(chalk2.dim(" Nothing to delete \u2014 no session files found in that folder."));
|
|
28970
29147
|
if (tool === "opencode") {
|
|
28971
|
-
console.log(
|
|
29148
|
+
console.log(chalk2.dim(" (OpenCode keys sessions by database row, so they cannot be enumerated from the path.)"));
|
|
28972
29149
|
}
|
|
28973
29150
|
return;
|
|
28974
29151
|
}
|
|
28975
29152
|
const targets = loadAllCredentials();
|
|
28976
29153
|
if (targets.length === 0) {
|
|
28977
|
-
console.error(
|
|
29154
|
+
console.error(chalk2.red("Not logged in \u2014 nothing to delete from."));
|
|
28978
29155
|
process.exit(1);
|
|
28979
29156
|
}
|
|
28980
29157
|
if (!opts.yes) {
|
|
28981
29158
|
console.log();
|
|
28982
|
-
console.log(
|
|
28983
|
-
console.log(
|
|
28984
|
-
console.log(
|
|
29159
|
+
console.log(chalk2.yellow(`About to DELETE ${ids.length} session(s) from ${targets.length} server(s).`));
|
|
29160
|
+
console.log(chalk2.yellow("This is irreversible \u2014 the transcripts stay on this machine, but the"));
|
|
29161
|
+
console.log(chalk2.yellow("server copies (search index, conversation view, archive) are removed."));
|
|
28985
29162
|
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
28986
29163
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
28987
29164
|
const answer = (await rl.question("Type the number of sessions to confirm: ")).trim();
|
|
28988
29165
|
rl.close();
|
|
28989
29166
|
if (answer !== String(ids.length)) {
|
|
28990
|
-
console.log(
|
|
29167
|
+
console.log(chalk2.dim("Cancelled \u2014 nothing deleted."));
|
|
28991
29168
|
return;
|
|
28992
29169
|
}
|
|
28993
29170
|
}
|
|
@@ -29007,8 +29184,8 @@ sources2.command("decline <path>").description("Keep a folder out of sync (e.g.
|
|
|
29007
29184
|
failed++;
|
|
29008
29185
|
}
|
|
29009
29186
|
}
|
|
29010
|
-
const line = ` ${base2}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ?
|
|
29011
|
-
console.log(failed ?
|
|
29187
|
+
const line = ` ${base2}: deleted ${deleted}` + (missing ? `, ${missing} were not there` : "") + (failed ? chalk2.red(`, ${failed} FAILED`) : "");
|
|
29188
|
+
console.log(failed ? chalk2.yellow(line) : chalk2.green(line));
|
|
29012
29189
|
if (failed) process.exitCode = 1;
|
|
29013
29190
|
}
|
|
29014
29191
|
});
|
|
@@ -29016,30 +29193,30 @@ sources2.command("forget <path>").description("Undo a decision, so the folder is
|
|
|
29016
29193
|
const { resetHomeDecision: resetHomeDecision2, normalizeHomePath: normalizeHomePath2 } = await Promise.resolve().then(() => (init_home_approval(), home_approval_exports));
|
|
29017
29194
|
const abs = normalizeHomePath2(path);
|
|
29018
29195
|
resetHomeDecision2(abs);
|
|
29019
|
-
console.log(
|
|
29196
|
+
console.log(chalk2.green(`\u2713 ${abs} is undecided again (not syncing until approved)`));
|
|
29020
29197
|
});
|
|
29021
29198
|
var detectors = program.command("detectors").description("Manage the optional external secret detectors (gitleaks, trufflehog)");
|
|
29022
29199
|
detectors.command("status").description("Show which detectors are installed, pinned and verified").action(async () => {
|
|
29023
29200
|
const { detectorStatus: detectorStatus2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
29024
29201
|
const { externalScannersEnabled: externalScannersEnabled2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
29025
|
-
console.log(
|
|
29202
|
+
console.log(chalk2.bold("external secret detectors"));
|
|
29026
29203
|
console.log(
|
|
29027
|
-
externalScannersEnabled2() ? ` gate: ${
|
|
29204
|
+
externalScannersEnabled2() ? ` gate: ${chalk2.green("enabled")} (CHAT_RECALL_EXTERNAL_SCANNERS)` : ` gate: ${chalk2.yellow("disabled")} \u2014 set CHAT_RECALL_EXTERNAL_SCANNERS=1 to use them`
|
|
29028
29205
|
);
|
|
29029
29206
|
if (process.env.CHAT_RECALL_DETECTOR_DIR) {
|
|
29030
|
-
console.log(` dir: ${
|
|
29207
|
+
console.log(` dir: ${chalk2.dim(process.env.CHAT_RECALL_DETECTOR_DIR)} ${chalk2.dim("(operator-supplied, unverified)")}`);
|
|
29031
29208
|
}
|
|
29032
29209
|
console.log();
|
|
29033
29210
|
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(` ${
|
|
29211
|
+
const label = `${d.name} ${chalk2.dim(d.pinnedVersion)} ${chalk2.dim(`[${d.license}]`)}`;
|
|
29212
|
+
if (d.state === "ready") console.log(` ${chalk2.green("\u2713")} ${label} \u2192 ${chalk2.dim(d.path)}`);
|
|
29213
|
+
else if (d.state === "configured") console.log(` ${chalk2.green("\u2713")} ${label} \u2192 ${chalk2.dim(d.path)} ${chalk2.dim("(operator-supplied)")}`);
|
|
29214
|
+
else if (d.state === "not-installed") console.log(` ${chalk2.dim("\xB7")} ${label} \u2014 not installed`);
|
|
29215
|
+
else if (!d.supported) console.log(` ${chalk2.dim("\xB7")} ${label} \u2014 no build for this platform`);
|
|
29216
|
+
else console.log(` ${chalk2.red("\u2717")} ${label} \u2014 ${d.state}, reinstall with \`chat-recall detectors install ${d.name}\``);
|
|
29040
29217
|
}
|
|
29041
29218
|
console.log();
|
|
29042
|
-
console.log(
|
|
29219
|
+
console.log(chalk2.dim("Builtin in-process detection runs for every user and needs none of this."));
|
|
29043
29220
|
});
|
|
29044
29221
|
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
29222
|
const { installDetector: installDetector2, DETECTOR_MANIFEST: DETECTOR_MANIFEST2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
@@ -29048,33 +29225,33 @@ detectors.command("install [name]").description("Download + verify a pinned dete
|
|
|
29048
29225
|
let failed = false;
|
|
29049
29226
|
for (const n of names) {
|
|
29050
29227
|
if (!valid.includes(n)) {
|
|
29051
|
-
console.error(
|
|
29228
|
+
console.error(chalk2.red(`unknown detector "${n}" \u2014 expected one of: ${valid.join(", ")}`));
|
|
29052
29229
|
failed = true;
|
|
29053
29230
|
continue;
|
|
29054
29231
|
}
|
|
29055
29232
|
try {
|
|
29056
29233
|
const r = await installDetector2(n, { force: opts.force });
|
|
29057
|
-
console.log(r.installed ?
|
|
29058
|
-
if (r.installed) console.log(
|
|
29234
|
+
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}`));
|
|
29235
|
+
if (r.installed) console.log(chalk2.dim(` sha256 ${r.binarySha256}`));
|
|
29059
29236
|
} catch (err) {
|
|
29060
29237
|
failed = true;
|
|
29061
|
-
console.error(
|
|
29238
|
+
console.error(chalk2.red(`${n} install failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
29062
29239
|
}
|
|
29063
29240
|
}
|
|
29064
29241
|
if (failed) process.exit(1);
|
|
29065
29242
|
console.log();
|
|
29066
|
-
console.log(
|
|
29067
|
-
console.log(
|
|
29243
|
+
console.log(chalk2.dim("Enable them with CHAT_RECALL_EXTERNAL_SCANNERS=1. They enrich the findings"));
|
|
29244
|
+
console.log(chalk2.dim("dashboard; they are not what redacts your data before it syncs."));
|
|
29068
29245
|
});
|
|
29069
29246
|
detectors.command("remove [name]").description("Remove detectors chat-recall installed (never touches your own binaries)").action(async (name) => {
|
|
29070
29247
|
const { removeDetector: removeDetector2, DETECTOR_MANIFEST: DETECTOR_MANIFEST2, managedDetectorPath: managedDetectorPath2 } = await Promise.resolve().then(() => (init_detector_install(), detector_install_exports));
|
|
29071
29248
|
for (const n of name ? [name] : Object.keys(DETECTOR_MANIFEST2)) {
|
|
29072
29249
|
if (!(n in DETECTOR_MANIFEST2)) {
|
|
29073
|
-
console.error(
|
|
29250
|
+
console.error(chalk2.red(`unknown detector "${n}"`));
|
|
29074
29251
|
continue;
|
|
29075
29252
|
}
|
|
29076
29253
|
const removed = removeDetector2(n);
|
|
29077
|
-
console.log(removed ?
|
|
29254
|
+
console.log(removed ? chalk2.green(`\u2713 removed ${n}`) : chalk2.dim(`\xB7 nothing to remove at ${managedDetectorPath2(n)}`));
|
|
29078
29255
|
}
|
|
29079
29256
|
});
|
|
29080
29257
|
var code = program.command("code").description("Index a codebase with codeindex and ship findings/hotspots/actions to your server");
|
|
@@ -29083,22 +29260,22 @@ code.command("index [path]").description("Run codeindex on a repo (default: cwd)
|
|
|
29083
29260
|
const target = requireTarget();
|
|
29084
29261
|
const { collectCode: collectCode2 } = await Promise.resolve().then(() => (init_collector(), collector_exports));
|
|
29085
29262
|
const workspace = resolve2(path || process.cwd());
|
|
29086
|
-
console.log(
|
|
29263
|
+
console.log(chalk2.bold(`code index ${workspace}`));
|
|
29087
29264
|
const result = await collectCode2({
|
|
29088
29265
|
workspace,
|
|
29089
29266
|
autoInstall: opts.install !== false,
|
|
29090
|
-
log: (m) => console.log(
|
|
29267
|
+
log: (m) => console.log(chalk2.dim(" \xB7 " + m))
|
|
29091
29268
|
});
|
|
29092
29269
|
const resp = await serverPost(
|
|
29093
29270
|
"/api/code/index",
|
|
29094
29271
|
result
|
|
29095
29272
|
);
|
|
29096
|
-
console.log(
|
|
29273
|
+
console.log(chalk2.green("\u2713 synced"), chalk2.dim(`\u2192 ${target.base}`));
|
|
29097
29274
|
console.log(` project: ${resp.projectId}`);
|
|
29098
29275
|
console.log(` health: ${result.project.health.score}/100`);
|
|
29099
29276
|
console.log(` findings: ${resp.findings} hotspots: ${resp.hotspots} actions: ${resp.actions}`);
|
|
29100
29277
|
} catch (err) {
|
|
29101
|
-
console.error(
|
|
29278
|
+
console.error(chalk2.red("code index failed:"), err instanceof Error ? err.message : err);
|
|
29102
29279
|
process.exit(1);
|
|
29103
29280
|
}
|
|
29104
29281
|
});
|
|
@@ -29106,17 +29283,17 @@ code.command("status").description("List code-indexed projects on your server").
|
|
|
29106
29283
|
try {
|
|
29107
29284
|
const { projects } = await serverGet("/api/code/projects");
|
|
29108
29285
|
if (!projects.length) {
|
|
29109
|
-
console.log(
|
|
29286
|
+
console.log(chalk2.dim("No code-indexed projects yet. Run `chat-recall code index` in a repo."));
|
|
29110
29287
|
return;
|
|
29111
29288
|
}
|
|
29112
|
-
console.log(
|
|
29289
|
+
console.log(chalk2.bold(`${projects.length} code project(s)`));
|
|
29113
29290
|
for (const p of projects) {
|
|
29114
29291
|
const when = new Date(p.lastIndexedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
29115
|
-
console.log(` ${
|
|
29116
|
-
console.log(
|
|
29292
|
+
console.log(` ${chalk2.bold(p.projectId)}${p.label ? chalk2.cyan(` [${p.label}]`) : ""}`);
|
|
29293
|
+
console.log(chalk2.dim(` health ${p.health.score}/100 \xB7 ${p.health.findings} findings \xB7 ${when}`));
|
|
29117
29294
|
}
|
|
29118
29295
|
} catch (err) {
|
|
29119
|
-
console.error(
|
|
29296
|
+
console.error(chalk2.red("code status failed:"), err instanceof Error ? err.message : err);
|
|
29120
29297
|
process.exit(1);
|
|
29121
29298
|
}
|
|
29122
29299
|
});
|
|
@@ -29125,12 +29302,12 @@ code.command("show <project>").description("Show a project: health, findings sum
|
|
|
29125
29302
|
const enc = encodeURIComponent(project);
|
|
29126
29303
|
const p = await serverGet(`/api/code/projects/${enc}`);
|
|
29127
29304
|
const { actions } = await serverGet(`/api/code/actions?project=${enc}&limit=12`);
|
|
29128
|
-
console.log(
|
|
29305
|
+
console.log(chalk2.bold(p.projectId), chalk2.dim(p.rootPath));
|
|
29129
29306
|
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(` ${
|
|
29307
|
+
console.log(chalk2.bold("\n Top actions:"));
|
|
29308
|
+
for (const a of actions) console.log(` ${chalk2.yellow("P" + a.pri)} [${a.category}] ${a.title}`);
|
|
29132
29309
|
} catch (err) {
|
|
29133
|
-
console.error(
|
|
29310
|
+
console.error(chalk2.red("code show failed:"), err instanceof Error ? err.message : err);
|
|
29134
29311
|
process.exit(1);
|
|
29135
29312
|
}
|
|
29136
29313
|
});
|
|
@@ -29139,18 +29316,18 @@ async function pushToolkitToServer() {
|
|
|
29139
29316
|
try {
|
|
29140
29317
|
const { syncIncremental: syncIncremental2, isSyncSkip: isSyncSkip2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
29141
29318
|
const res = await syncIncremental2();
|
|
29142
|
-
if (!isSyncSkip2(res) && res.items > 0) console.log(
|
|
29319
|
+
if (!isSyncSkip2(res) && res.items > 0) console.log(chalk2.dim(` synced ${res.items} toolkit item(s) to the server.`));
|
|
29143
29320
|
} catch (err) {
|
|
29144
|
-
console.error(
|
|
29321
|
+
console.error(chalk2.dim(` (could not sync to server: ${err instanceof Error ? err.message : err})`));
|
|
29145
29322
|
}
|
|
29146
29323
|
}
|
|
29147
29324
|
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
29325
|
const { executeSyncAll: executeSyncAll2 } = await Promise.resolve().then(() => (init_toolkit_sync(), toolkit_sync_exports));
|
|
29149
29326
|
const types = options.types ? options.types.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
29150
29327
|
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(
|
|
29328
|
+
for (const c of r.copied) console.log(chalk2.green(` + ${c.type} ${c.name} \u2192 ${c.toTool}`) + chalk2.dim(` ${c.path || ""}`));
|
|
29329
|
+
for (const f of r.failed) console.log(chalk2.red(` \u2717 ${f.type} ${f.name} \u2192 ${f.toTool}: ${f.error}`));
|
|
29330
|
+
console.log(chalk2.bold(`Synced: ${r.copied.length} copied, ${r.skipped.length} already present, ${r.failed.length} failed.`));
|
|
29154
29331
|
if (r.copied.length > 0) await pushToolkitToServer();
|
|
29155
29332
|
});
|
|
29156
29333
|
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 +29336,7 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29159
29336
|
const { hostname: hostname3 } = await import("node:os");
|
|
29160
29337
|
const targets = loadAllCredentials2();
|
|
29161
29338
|
if (targets.length === 0) {
|
|
29162
|
-
console.error(
|
|
29339
|
+
console.error(chalk2.red("Not logged in."), "Run `chat-recall login <server-url>` first.");
|
|
29163
29340
|
process.exit(1);
|
|
29164
29341
|
}
|
|
29165
29342
|
const rows = [];
|
|
@@ -29173,7 +29350,7 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29173
29350
|
const body = await res.json();
|
|
29174
29351
|
for (const it of body.items || []) rows.push(it);
|
|
29175
29352
|
} catch (err) {
|
|
29176
|
-
console.log(
|
|
29353
|
+
console.log(chalk2.dim(` (${t.serverUrl} ${type}: ${err instanceof Error ? err.message : err})`));
|
|
29177
29354
|
}
|
|
29178
29355
|
}
|
|
29179
29356
|
}
|
|
@@ -29188,41 +29365,40 @@ toolkit.command("pull").description("Install artifacts this device is missing, f
|
|
|
29188
29365
|
const failed = report.outcomes.filter((o) => o.status === "failed");
|
|
29189
29366
|
const skipped = report.outcomes.filter((o) => o.status === "skipped");
|
|
29190
29367
|
for (const o of written) {
|
|
29191
|
-
const note = o.rewrittenCommand ?
|
|
29192
|
-
console.log(
|
|
29368
|
+
const note = o.rewrittenCommand ? chalk2.cyan(` [using this machine's ${o.rewrittenCommand}]`) : "";
|
|
29369
|
+
console.log(chalk2.green(` + ${o.type} ${o.name} \u2192 ${o.tool}`) + chalk2.dim(` ${o.path || "(dry run)"}`) + note);
|
|
29193
29370
|
}
|
|
29194
|
-
for (const o of failed) console.log(
|
|
29195
|
-
for (const o of skipped) console.log(
|
|
29196
|
-
console.log(
|
|
29371
|
+
for (const o of failed) console.log(chalk2.red(` \u2717 ${o.type} ${o.name} \u2192 ${o.tool}: ${o.reason}`));
|
|
29372
|
+
for (const o of skipped) console.log(chalk2.yellow(` ~ ${o.type} ${o.name} \u2192 ${o.tool}: ${o.reason}`));
|
|
29373
|
+
console.log(chalk2.bold(
|
|
29197
29374
|
`${options.dryRun ? "Would install" : "Installed"}: ${written.length} \xB7 already present: ${present.length} \xB7 failed: ${failed.length} \xB7 skipped: ${skipped.length}`
|
|
29198
29375
|
));
|
|
29199
|
-
const
|
|
29200
|
-
if (
|
|
29376
|
+
const needs = collectEnvNeeds(written);
|
|
29377
|
+
if (needs.length) {
|
|
29201
29378
|
console.log("");
|
|
29202
|
-
|
|
29203
|
-
for (const v of envVars) console.log(` ${v}`);
|
|
29379
|
+
for (const line of renderEnvNeeds(needs, written.length)) console.log(line);
|
|
29204
29380
|
}
|
|
29205
29381
|
if (report.unsupported.length) {
|
|
29206
29382
|
console.log("");
|
|
29207
|
-
console.log(
|
|
29383
|
+
console.log(chalk2.dim("Not installable from the server yet:"));
|
|
29208
29384
|
for (const u of report.unsupported) {
|
|
29209
|
-
console.log(
|
|
29385
|
+
console.log(chalk2.dim(` ${u.type} (${u.rows} on the server) \u2014 ${u.reason}`));
|
|
29210
29386
|
}
|
|
29211
|
-
console.log(
|
|
29387
|
+
console.log(chalk2.dim(" Those need artifact content upload; the server holds an inventory only."));
|
|
29212
29388
|
}
|
|
29213
29389
|
});
|
|
29214
29390
|
toolkit.command("drain").description("Process cross-tool sync intents queued from the web UI, now").action(async () => {
|
|
29215
29391
|
const { drainSyncIntents: drainSyncIntents2 } = await Promise.resolve().then(() => (init_intent_drain(), intent_drain_exports));
|
|
29216
29392
|
const r = await drainSyncIntents2({ verbose: true });
|
|
29217
|
-
console.log(
|
|
29393
|
+
console.log(chalk2.bold(`Processed ${r.processed} intent(s): ${r.done} done, ${r.errored} errored.`));
|
|
29218
29394
|
if (r.done > 0) await pushToolkitToServer();
|
|
29219
29395
|
});
|
|
29220
29396
|
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
29397
|
const { executeCopy: executeCopy2 } = await Promise.resolve().then(() => (init_toolkit_sync(), toolkit_sync_exports));
|
|
29222
29398
|
const r = await executeCopy2(type, name, fromTool, toTool);
|
|
29223
|
-
if (r.ok) console.log(
|
|
29399
|
+
if (r.ok) console.log(chalk2.green(`Copied to ${r.targetPath}`));
|
|
29224
29400
|
else {
|
|
29225
|
-
console.error(
|
|
29401
|
+
console.error(chalk2.red(r.error || "failed"));
|
|
29226
29402
|
process.exit(1);
|
|
29227
29403
|
}
|
|
29228
29404
|
});
|
|
@@ -29230,31 +29406,31 @@ program.command("shares").description("List the projects you share with your tea
|
|
|
29230
29406
|
try {
|
|
29231
29407
|
const { shares } = await serverGet("/api/shares");
|
|
29232
29408
|
if (!shares.length) {
|
|
29233
|
-
console.log(
|
|
29409
|
+
console.log(chalk2.gray("You are not sharing any projects. Share one with"), chalk2.bold("chat-recall share <project_id>"));
|
|
29234
29410
|
return;
|
|
29235
29411
|
}
|
|
29236
|
-
console.log(
|
|
29237
|
-
for (const s of shares) console.log(` ${
|
|
29412
|
+
console.log(chalk2.bold(`Shared projects (${shares.length}):`));
|
|
29413
|
+
for (const s of shares) console.log(` ${chalk2.cyan(s.projectId)} ${chalk2.gray(s.scope)}`);
|
|
29238
29414
|
} catch (e) {
|
|
29239
|
-
console.error(
|
|
29415
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29240
29416
|
process.exit(1);
|
|
29241
29417
|
}
|
|
29242
29418
|
});
|
|
29243
29419
|
program.command("share <project_id>").description("Share a project with your team (teammates can then see your work on it)").action(async (projectId) => {
|
|
29244
29420
|
try {
|
|
29245
29421
|
await serverPost("/api/shares", { project_id: projectId });
|
|
29246
|
-
console.log(
|
|
29422
|
+
console.log(chalk2.green("\u2713 shared"), chalk2.cyan(projectId), chalk2.gray("with your team"));
|
|
29247
29423
|
} catch (e) {
|
|
29248
|
-
console.error(
|
|
29424
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29249
29425
|
process.exit(1);
|
|
29250
29426
|
}
|
|
29251
29427
|
});
|
|
29252
29428
|
program.command("unshare <project_id>").description("Stop sharing a project with your team").action(async (projectId) => {
|
|
29253
29429
|
try {
|
|
29254
29430
|
const r = await serverDelete("/api/shares", { project_id: projectId });
|
|
29255
|
-
console.log(r.removed ?
|
|
29431
|
+
console.log(r.removed ? chalk2.green("\u2713 unshared") : chalk2.yellow("was not shared"), chalk2.cyan(projectId));
|
|
29256
29432
|
} catch (e) {
|
|
29257
|
-
console.error(
|
|
29433
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29258
29434
|
process.exit(1);
|
|
29259
29435
|
}
|
|
29260
29436
|
});
|
|
@@ -29263,12 +29439,12 @@ tasks.command("list").description("List team tasks assigned to you").action(asyn
|
|
|
29263
29439
|
try {
|
|
29264
29440
|
const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
|
|
29265
29441
|
if (!tasks2.length) {
|
|
29266
|
-
console.log(
|
|
29442
|
+
console.log(chalk2.gray("No tasks assigned to you."));
|
|
29267
29443
|
return;
|
|
29268
29444
|
}
|
|
29269
|
-
for (const t of tasks2) console.log(` [${t.status}] ${t.title} ${
|
|
29445
|
+
for (const t of tasks2) console.log(` [${t.status}] ${t.title} ${chalk2.gray(t.id)}${t.projectId ? chalk2.gray(" \xB7 " + t.projectId) : ""}`);
|
|
29270
29446
|
} catch (e) {
|
|
29271
|
-
console.error(
|
|
29447
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29272
29448
|
process.exit(1);
|
|
29273
29449
|
}
|
|
29274
29450
|
});
|
|
@@ -29281,7 +29457,7 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
|
|
|
29281
29457
|
const { join: join61 } = await import("node:path");
|
|
29282
29458
|
const file = join61(cwd, "TEAM_TASKS.md");
|
|
29283
29459
|
if (!opts.force && existsSync57(file) && parseTeamTasksFile2(readFileSync50(file, "utf8")).length > 0) {
|
|
29284
|
-
console.error(
|
|
29460
|
+
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
29461
|
process.exit(1);
|
|
29286
29462
|
}
|
|
29287
29463
|
const resolved = resolveProjectId2(cwd);
|
|
@@ -29289,9 +29465,9 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
|
|
|
29289
29465
|
const { tasks: tasks2 } = await serverGet("/api/tasks?assignee=@me");
|
|
29290
29466
|
const mine = tasks2.filter((t) => !pid || !t.projectId || t.projectId === pid);
|
|
29291
29467
|
writeFileSync22(file, renderTeamTasksMd2(mine));
|
|
29292
|
-
console.log(
|
|
29468
|
+
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
29469
|
} catch (e) {
|
|
29294
|
-
console.error(
|
|
29470
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29295
29471
|
process.exit(1);
|
|
29296
29472
|
}
|
|
29297
29473
|
});
|
|
@@ -29303,7 +29479,7 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
|
|
|
29303
29479
|
const { join: join61 } = await import("node:path");
|
|
29304
29480
|
const file = join61(cwd, "TEAM_TASKS.md");
|
|
29305
29481
|
if (!existsSync57(file)) {
|
|
29306
|
-
console.error(
|
|
29482
|
+
console.error(chalk2.red("no TEAM_TASKS.md here \u2014"), "run", chalk2.bold("chat-recall tasks pull"), "first");
|
|
29307
29483
|
process.exit(1);
|
|
29308
29484
|
}
|
|
29309
29485
|
const edits = parseTeamTasksFile2(readFileSync50(file, "utf8"));
|
|
@@ -29312,9 +29488,9 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
|
|
|
29312
29488
|
await serverPatch(`/api/tasks/${encodeURIComponent(e.id)}`, { status: e.intent });
|
|
29313
29489
|
n++;
|
|
29314
29490
|
}
|
|
29315
|
-
console.log(n ?
|
|
29491
|
+
console.log(n ? chalk2.green(`\u2713 pushed ${n} status change(s)`) : chalk2.gray("no changes to push"));
|
|
29316
29492
|
} catch (e) {
|
|
29317
|
-
console.error(
|
|
29493
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
29318
29494
|
process.exit(1);
|
|
29319
29495
|
}
|
|
29320
29496
|
});
|
|
@@ -29325,12 +29501,12 @@ async function runTeam(label, fn) {
|
|
|
29325
29501
|
await fn();
|
|
29326
29502
|
} catch (err) {
|
|
29327
29503
|
if (err instanceof TeamConfigError2) {
|
|
29328
|
-
console.error(
|
|
29504
|
+
console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29329
29505
|
} else if (err instanceof TeamAuthError2) {
|
|
29330
|
-
console.error(
|
|
29331
|
-
console.error(
|
|
29506
|
+
console.error(chalk2.red(`${label}: auth failed \u2014 ${err.message}`));
|
|
29507
|
+
console.error(chalk2.dim(" Check the env var named in settings.team.tokenRef holds a valid Keycloak access token."));
|
|
29332
29508
|
} else if (err instanceof TeamHttpError2) {
|
|
29333
|
-
console.error(
|
|
29509
|
+
console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29334
29510
|
} else {
|
|
29335
29511
|
throw err;
|
|
29336
29512
|
}
|
|
@@ -29340,76 +29516,76 @@ async function runTeam(label, fn) {
|
|
|
29340
29516
|
team.command("whoami").description("Show the user + team memberships the server sees for your token").action(async () => runTeam("team whoami", async () => {
|
|
29341
29517
|
const { teamMe: teamMe2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29342
29518
|
const me = await teamMe2();
|
|
29343
|
-
console.log(
|
|
29519
|
+
console.log(chalk2.bold(me.user.email) + chalk2.dim(` (${me.user.id})`));
|
|
29344
29520
|
if (me.memberships.length === 0) {
|
|
29345
|
-
console.log(
|
|
29521
|
+
console.log(chalk2.dim(" no team memberships yet \u2014 run `chat-recall team create <name>` or `team join <token>`"));
|
|
29346
29522
|
return;
|
|
29347
29523
|
}
|
|
29348
29524
|
for (const m of me.memberships) {
|
|
29349
|
-
const role = m.role === "owner" ?
|
|
29350
|
-
console.log(` ${
|
|
29525
|
+
const role = m.role === "owner" ? chalk2.yellow(m.role) : chalk2.dim(m.role);
|
|
29526
|
+
console.log(` ${chalk2.cyan(m.teamName)} ${role} ${chalk2.dim(m.plan)} ${chalk2.dim(m.teamId)}`);
|
|
29351
29527
|
}
|
|
29352
29528
|
}));
|
|
29353
29529
|
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
29530
|
const { teamCreate: teamCreate2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29355
29531
|
const t = await teamCreate2(name);
|
|
29356
|
-
console.log(
|
|
29357
|
-
console.log(
|
|
29358
|
-
console.log(
|
|
29532
|
+
console.log(chalk2.green(`\u2713 Team created: ${chalk2.bold(t.name)}`));
|
|
29533
|
+
console.log(chalk2.dim(` id: ${t.id}`));
|
|
29534
|
+
console.log(chalk2.dim(` Now generate an invite: chat-recall team invite`));
|
|
29359
29535
|
}));
|
|
29360
29536
|
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
29537
|
const { teamInvite: teamInvite2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29362
29538
|
const r = await teamInvite2(opts.email);
|
|
29363
|
-
console.log(
|
|
29364
|
-
console.log(" " +
|
|
29365
|
-
console.log(
|
|
29539
|
+
console.log(chalk2.bold("Invite token (copy now \u2014 shown once):"));
|
|
29540
|
+
console.log(" " + chalk2.cyan(r.inviteToken));
|
|
29541
|
+
console.log(chalk2.dim(` expires: ${r.expiresAt}`));
|
|
29366
29542
|
console.log();
|
|
29367
|
-
console.log(
|
|
29543
|
+
console.log(chalk2.dim("Recipient runs: chat-recall team join <token>"));
|
|
29368
29544
|
}));
|
|
29369
29545
|
team.command("join <invite-token>").description("Accept a team invite. Stores team id locally.").action(async (inviteToken) => runTeam("team join", async () => {
|
|
29370
29546
|
const { teamJoin: teamJoin2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29371
29547
|
const t = await teamJoin2(inviteToken);
|
|
29372
|
-
console.log(
|
|
29373
|
-
console.log(
|
|
29374
|
-
console.log(
|
|
29548
|
+
console.log(chalk2.green(`\u2713 Joined team: ${chalk2.bold(t.name)}`));
|
|
29549
|
+
console.log(chalk2.dim(` id: ${t.id}`));
|
|
29550
|
+
console.log(chalk2.dim(" Run `chat-recall team pull` to fetch the team library."));
|
|
29375
29551
|
}));
|
|
29376
29552
|
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
29553
|
const { teamPull: teamPull2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29378
29554
|
const r = await teamPull2();
|
|
29379
29555
|
if (r.pulled.length === 0 && r.removed.length === 0) {
|
|
29380
|
-
console.log(
|
|
29556
|
+
console.log(chalk2.dim("Up to date."));
|
|
29381
29557
|
return;
|
|
29382
29558
|
}
|
|
29383
29559
|
for (const a of r.pulled) {
|
|
29384
|
-
console.log(` ${
|
|
29560
|
+
console.log(` ${chalk2.green("+")} ${chalk2.cyan(a.type + "/" + a.name)} v${a.version} ${chalk2.dim("(" + a.tool + ")")}`);
|
|
29385
29561
|
}
|
|
29386
29562
|
for (const id of r.removed) {
|
|
29387
|
-
console.log(` ${
|
|
29563
|
+
console.log(` ${chalk2.red("-")} ${chalk2.dim(id + " (revoked)")}`);
|
|
29388
29564
|
}
|
|
29389
29565
|
if (opts.dryRun) {
|
|
29390
|
-
console.log(
|
|
29566
|
+
console.log(chalk2.dim(`Watermark: ${r.serverNow} (dry-run \u2014 nothing written)`));
|
|
29391
29567
|
return;
|
|
29392
29568
|
}
|
|
29393
29569
|
const { mergePullResult: mergePullResult2 } = await Promise.resolve().then(() => (init_team_merge(), team_merge_exports));
|
|
29394
29570
|
const m = await mergePullResult2({ pulled: r.pulled, removed: r.removed });
|
|
29395
29571
|
console.log();
|
|
29396
|
-
if (m.written.length) console.log(
|
|
29397
|
-
if (m.skipped.length) console.log(
|
|
29398
|
-
if (m.removed.length) console.log(
|
|
29572
|
+
if (m.written.length) console.log(chalk2.green(`\u2713 Wrote ${m.written.length} file${m.written.length === 1 ? "" : "s"}`));
|
|
29573
|
+
if (m.skipped.length) console.log(chalk2.dim(` Skipped ${m.skipped.length} (unchanged or no target tool)`));
|
|
29574
|
+
if (m.removed.length) console.log(chalk2.yellow(` Removed ${m.removed.length} revoked file${m.removed.length === 1 ? "" : "s"}`));
|
|
29399
29575
|
for (const f of m.failures) {
|
|
29400
|
-
console.log(
|
|
29576
|
+
console.log(chalk2.red(` ! ${f.path}: ${f.error}`));
|
|
29401
29577
|
}
|
|
29402
|
-
console.log(
|
|
29578
|
+
console.log(chalk2.dim(`Watermark: ${r.serverNow}`));
|
|
29403
29579
|
}));
|
|
29404
29580
|
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
29581
|
const validTypes = ["skill", "command", "agent", "mcp", "plan", "plugin", "instructions", "hook"];
|
|
29406
29582
|
if (!validTypes.includes(type)) {
|
|
29407
|
-
console.error(
|
|
29583
|
+
console.error(chalk2.red(`Invalid type: ${type}. Must be one of: ${validTypes.join(", ")}`));
|
|
29408
29584
|
process.exit(1);
|
|
29409
29585
|
}
|
|
29410
29586
|
const validTools = ["claude", "gemini", "opencode", "codex", "agy", "cursor", "cross_tool"];
|
|
29411
29587
|
if (!validTools.includes(opts.tool)) {
|
|
29412
|
-
console.error(
|
|
29588
|
+
console.error(chalk2.red(`Invalid tool: ${opts.tool}. Must be one of: ${validTools.join(", ")}`));
|
|
29413
29589
|
process.exit(1);
|
|
29414
29590
|
}
|
|
29415
29591
|
const { readFileSync: readFileSync50 } = await import("fs");
|
|
@@ -29422,32 +29598,32 @@ team.command("publish <type> <name> <file>").description("Publish a local file a
|
|
|
29422
29598
|
body,
|
|
29423
29599
|
pinnedTo: opts.pinnedTo
|
|
29424
29600
|
});
|
|
29425
|
-
console.log(
|
|
29426
|
-
console.log(
|
|
29427
|
-
console.log(
|
|
29601
|
+
console.log(chalk2.green(`\u2713 Published ${chalk2.bold(type + "/" + name)} v${a.version}`));
|
|
29602
|
+
console.log(chalk2.dim(` id: ${a.id}`));
|
|
29603
|
+
console.log(chalk2.dim(` sha256: ${a.sha256}`));
|
|
29428
29604
|
}));
|
|
29429
29605
|
team.command("list").description("List artifacts in the team library (metadata only, no bodies)").action(async () => runTeam("team list", async () => {
|
|
29430
29606
|
const { teamList: teamList2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29431
29607
|
const items = await teamList2();
|
|
29432
29608
|
if (items.length === 0) {
|
|
29433
|
-
console.log(
|
|
29609
|
+
console.log(chalk2.dim("Library is empty. Try `chat-recall team publish <type> <name> <file>`."));
|
|
29434
29610
|
return;
|
|
29435
29611
|
}
|
|
29436
29612
|
for (const a of items) {
|
|
29437
|
-
const pinned = a.pinnedTo ?
|
|
29438
|
-
console.log(` ${
|
|
29613
|
+
const pinned = a.pinnedTo ? chalk2.dim(` pinned=${a.pinnedTo}`) : "";
|
|
29614
|
+
console.log(` ${chalk2.cyan(a.type + "/" + a.name)} v${a.version} ${chalk2.dim("(" + a.tool + ", " + a.bytes + " bytes)")}${pinned}`);
|
|
29439
29615
|
}
|
|
29440
29616
|
}));
|
|
29441
29617
|
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
29618
|
const { teamRevoke: teamRevoke2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29443
29619
|
const r = await teamRevoke2(artifactId);
|
|
29444
|
-
console.log(
|
|
29620
|
+
console.log(chalk2.green(`\u2713 Revoked ${chalk2.bold(r.type + "/" + r.name)} (${r.id})`));
|
|
29445
29621
|
}));
|
|
29446
29622
|
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
29623
|
const { teamLeave: teamLeave2 } = await Promise.resolve().then(() => (init_team_client(), team_client_exports));
|
|
29448
29624
|
await teamLeave2();
|
|
29449
|
-
console.log(
|
|
29450
|
-
console.log(
|
|
29625
|
+
console.log(chalk2.green("\u2713 Local team config cleared."));
|
|
29626
|
+
console.log(chalk2.dim(" Server-side membership unchanged. Owner must remove you to fully leave."));
|
|
29451
29627
|
}));
|
|
29452
29628
|
var vault = program.command("vault").description("Encrypted multi-device chat backup (E2EE \u2014 server cannot decrypt)");
|
|
29453
29629
|
async function runVault(label, fn) {
|
|
@@ -29455,9 +29631,9 @@ async function runVault(label, fn) {
|
|
|
29455
29631
|
try {
|
|
29456
29632
|
await fn();
|
|
29457
29633
|
} 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(
|
|
29634
|
+
if (err instanceof VaultConfigError2) console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29635
|
+
else if (err instanceof VaultAuthError2) console.error(chalk2.red(`${label}: auth failed \u2014 ${err.message}`));
|
|
29636
|
+
else if (err instanceof VaultHttpError2) console.error(chalk2.red(`${label}: ${err.message}`));
|
|
29461
29637
|
else throw err;
|
|
29462
29638
|
process.exit(1);
|
|
29463
29639
|
}
|
|
@@ -29467,7 +29643,7 @@ async function resolvePassphrase(opts) {
|
|
|
29467
29643
|
if (opts.envVar) {
|
|
29468
29644
|
const v = process.env[opts.envVar];
|
|
29469
29645
|
if (v) return v;
|
|
29470
|
-
console.error(
|
|
29646
|
+
console.error(chalk2.red(`Env var ${opts.envVar} is not set.`));
|
|
29471
29647
|
process.exit(1);
|
|
29472
29648
|
}
|
|
29473
29649
|
if (!opts.noKeyring) {
|
|
@@ -29504,49 +29680,49 @@ vault.command("enable").description("Set up the Vault on this device. Generates
|
|
|
29504
29680
|
r = await vaultEnableWithRemote2(passphrase, { baseUrl: cred.serverUrl, token: cred.token });
|
|
29505
29681
|
} catch (err) {
|
|
29506
29682
|
if (err instanceof VaultPassphraseMismatchError2) {
|
|
29507
|
-
console.error(
|
|
29683
|
+
console.error(chalk2.red(`vault enable: ${err.message}`));
|
|
29508
29684
|
process.exit(1);
|
|
29509
29685
|
}
|
|
29510
29686
|
throw err;
|
|
29511
29687
|
}
|
|
29512
29688
|
}
|
|
29513
|
-
console.log(
|
|
29514
|
-
console.log(
|
|
29515
|
-
console.log(
|
|
29689
|
+
console.log(chalk2.green("\u2713 Vault enabled on this device."));
|
|
29690
|
+
console.log(chalk2.dim(` keyId: ${r.keyId}`));
|
|
29691
|
+
console.log(chalk2.dim(` salt: ${r.saltHex}`) + (r.saltSource === "server" ? chalk2.dim(" (from your workspace)") : ""));
|
|
29516
29692
|
if (opts.keyring === false) {
|
|
29517
|
-
console.log(
|
|
29693
|
+
console.log(chalk2.yellow(" keyring: skipped (--no-keyring) \u2014 the background daemon will not be able to decrypt."));
|
|
29518
29694
|
} else {
|
|
29519
29695
|
const { keyringSet: keyringSet2, probeKeyring: probeKeyring2 } = await Promise.resolve().then(() => (init_keyring(), keyring_exports));
|
|
29520
29696
|
try {
|
|
29521
29697
|
keyringSet2(passphrase);
|
|
29522
|
-
console.log(
|
|
29698
|
+
console.log(chalk2.dim(` keyring: stored (${probeKeyring2().backend}) \u2014 the daemon can decrypt unattended.`));
|
|
29523
29699
|
} catch (err) {
|
|
29524
|
-
console.log(
|
|
29525
|
-
console.log(
|
|
29700
|
+
console.log(chalk2.yellow(` keyring: unavailable \u2014 ${err instanceof Error ? err.message : err}`));
|
|
29701
|
+
console.log(chalk2.yellow(" The vault still works for foreground commands; cross-device artifacts will not apply on this machine."));
|
|
29526
29702
|
}
|
|
29527
29703
|
}
|
|
29528
29704
|
console.log();
|
|
29529
29705
|
if (r.saltSource === "server") {
|
|
29530
|
-
console.log(
|
|
29706
|
+
console.log(chalk2.dim("This device joined your existing vault \u2014 same passphrase, same key."));
|
|
29531
29707
|
} else {
|
|
29532
|
-
console.log(
|
|
29708
|
+
console.log(chalk2.yellow("To set up another device, run there:"));
|
|
29533
29709
|
console.log(" chat-recall vault enable");
|
|
29534
|
-
console.log(
|
|
29710
|
+
console.log(chalk2.dim(" (it picks up this workspace's salt automatically \u2014 only the passphrase is needed)"));
|
|
29535
29711
|
}
|
|
29536
|
-
console.log(
|
|
29712
|
+
console.log(chalk2.yellow("Lose the passphrase = lose encrypted history. There is no recovery."));
|
|
29537
29713
|
}));
|
|
29538
29714
|
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
29715
|
const passphrase = await resolvePassphrase({ passphrase: opts.passphrase, envVar: opts.passphraseEnv });
|
|
29540
29716
|
const { vaultSync: vaultSync2 } = await Promise.resolve().then(() => (init_vault_client(), vault_client_exports));
|
|
29541
29717
|
const r = await vaultSync2(passphrase);
|
|
29542
29718
|
if (r.uploaded.length === 0 && r.skipped.length === 0 && r.failures.length === 0) {
|
|
29543
|
-
console.log(
|
|
29719
|
+
console.log(chalk2.dim("No chat sources found."));
|
|
29544
29720
|
return;
|
|
29545
29721
|
}
|
|
29546
|
-
if (r.uploaded.length) console.log(
|
|
29547
|
-
if (r.skipped.length) console.log(
|
|
29722
|
+
if (r.uploaded.length) console.log(chalk2.green(`\u2713 Uploaded ${r.uploaded.length} session${r.uploaded.length === 1 ? "" : "s"}`));
|
|
29723
|
+
if (r.skipped.length) console.log(chalk2.dim(` Skipped ${r.skipped.length} (unchanged or denylisted)`));
|
|
29548
29724
|
for (const f of r.failures) {
|
|
29549
|
-
console.log(
|
|
29725
|
+
console.log(chalk2.red(` ! ${f.tool}/${f.sessionId}: ${f.error}`));
|
|
29550
29726
|
}
|
|
29551
29727
|
}));
|
|
29552
29728
|
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 +29730,13 @@ vault.command("restore").description("Pull encrypted blobs from the server, decr
|
|
|
29554
29730
|
const { vaultRestore: vaultRestore2 } = await Promise.resolve().then(() => (init_vault_client(), vault_client_exports));
|
|
29555
29731
|
const r = await vaultRestore2(passphrase, { since: opts.since ? Number(opts.since) : void 0 });
|
|
29556
29732
|
if (r.restored.length === 0 && r.skipped.length === 0 && r.failures.length === 0) {
|
|
29557
|
-
console.log(
|
|
29733
|
+
console.log(chalk2.dim("No blobs to restore."));
|
|
29558
29734
|
return;
|
|
29559
29735
|
}
|
|
29560
|
-
if (r.restored.length) console.log(
|
|
29561
|
-
if (r.skipped.length) console.log(
|
|
29736
|
+
if (r.restored.length) console.log(chalk2.green(`\u2713 Restored ${r.restored.length} session${r.restored.length === 1 ? "" : "s"}`));
|
|
29737
|
+
if (r.skipped.length) console.log(chalk2.dim(` Skipped ${r.skipped.length} (unchanged or different key)`));
|
|
29562
29738
|
for (const f of r.failures) {
|
|
29563
|
-
console.log(
|
|
29739
|
+
console.log(chalk2.red(` ! ${f.tool}/${f.sessionId}: ${f.error}`));
|
|
29564
29740
|
}
|
|
29565
29741
|
}));
|
|
29566
29742
|
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 +29746,8 @@ vault.command("status").description("Show whether the Vault is enabled, salt fin
|
|
|
29570
29746
|
const probe = probeKeyring2();
|
|
29571
29747
|
const reportKeyring = () => {
|
|
29572
29748
|
if (!probe.available) {
|
|
29573
|
-
console.log(
|
|
29574
|
-
if (probe.hint) console.log(
|
|
29749
|
+
console.log(chalk2.yellow(` keyring: unavailable (${probe.backend}) \u2014 cross-device artifacts will not apply on this machine`));
|
|
29750
|
+
if (probe.hint) console.log(chalk2.dim(` ${probe.hint}`));
|
|
29575
29751
|
return;
|
|
29576
29752
|
}
|
|
29577
29753
|
let stored = false;
|
|
@@ -29580,27 +29756,27 @@ vault.command("status").description("Show whether the Vault is enabled, salt fin
|
|
|
29580
29756
|
} catch {
|
|
29581
29757
|
stored = false;
|
|
29582
29758
|
}
|
|
29583
|
-
console.log(stored ?
|
|
29759
|
+
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
29760
|
};
|
|
29585
29761
|
if (!s.enabled) {
|
|
29586
|
-
console.log(
|
|
29762
|
+
console.log(chalk2.dim("Vault: disabled. Run `chat-recall vault enable`."));
|
|
29587
29763
|
reportKeyring();
|
|
29588
29764
|
return;
|
|
29589
29765
|
}
|
|
29590
|
-
console.log(
|
|
29591
|
-
console.log(
|
|
29592
|
-
console.log(
|
|
29593
|
-
console.log(
|
|
29594
|
-
console.log(
|
|
29595
|
-
console.log(
|
|
29766
|
+
console.log(chalk2.green("Vault: enabled"));
|
|
29767
|
+
console.log(chalk2.dim(` keyId: ${s.keyId ?? "(unknown \u2014 re-enable)"}`));
|
|
29768
|
+
console.log(chalk2.dim(` salt: ${s.saltHex ? s.saltHex.slice(0, 16) + "\u2026" : "(missing)"}`));
|
|
29769
|
+
console.log(chalk2.dim(` lastSyncAt: ${s.lastSyncAt ? new Date(s.lastSyncAt).toISOString() : "never"}`));
|
|
29770
|
+
console.log(chalk2.dim(` syncTools: ${s.syncTools.join(", ")}`));
|
|
29771
|
+
console.log(chalk2.dim(` excludeProj: ${s.excludeProjects.length ? s.excludeProjects.join(", ") : "(none)"}`));
|
|
29596
29772
|
reportKeyring();
|
|
29597
29773
|
}));
|
|
29598
29774
|
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
29775
|
const { keyringDelete: keyringDelete2 } = await Promise.resolve().then(() => (init_keyring(), keyring_exports));
|
|
29600
|
-
console.log(keyringDelete2() ?
|
|
29776
|
+
console.log(keyringDelete2() ? chalk2.green("\u2713 Passphrase removed from the OS keyring.") : chalk2.dim("Nothing stored in the OS keyring."));
|
|
29601
29777
|
}));
|
|
29602
29778
|
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:
|
|
29779
|
+
const { mkdirSync: mkdirSync20, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync57, readFileSync: readFileSync50, writeFileSync: writeFileSync22, statSync: statSync47 } = await import("fs");
|
|
29604
29780
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
29605
29781
|
const here = fileURLToPath6(new URL(".", import.meta.url));
|
|
29606
29782
|
const findHook = (name) => {
|
|
@@ -29615,7 +29791,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29615
29791
|
const sourceEscalateHook = findHook("chat_recall_escalate_hook.sh");
|
|
29616
29792
|
const sourceWakeupHook = findHook("chat_recall_wakeup_hook.sh");
|
|
29617
29793
|
if (!sourceSaveHook) {
|
|
29618
|
-
console.error(
|
|
29794
|
+
console.error(chalk2.red("Could not locate chat_recall_save_hook.sh in the package."));
|
|
29619
29795
|
process.exit(1);
|
|
29620
29796
|
}
|
|
29621
29797
|
const hooksDir = getHooksDir();
|
|
@@ -29642,8 +29818,8 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29642
29818
|
if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
|
|
29643
29819
|
return c;
|
|
29644
29820
|
} catch (err) {
|
|
29645
|
-
console.error(
|
|
29646
|
-
console.error(
|
|
29821
|
+
console.error(chalk2.red(`Could not parse ${file}: ${err}`));
|
|
29822
|
+
console.error(chalk2.dim("Fix the file or move it aside before re-running."));
|
|
29647
29823
|
process.exit(1);
|
|
29648
29824
|
}
|
|
29649
29825
|
};
|
|
@@ -29667,23 +29843,23 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29667
29843
|
if (removed === 0 && !existsSync57(hooksJson)) continue;
|
|
29668
29844
|
writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
|
|
29669
29845
|
total += removed;
|
|
29670
|
-
console.log(
|
|
29846
|
+
console.log(chalk2.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
|
|
29671
29847
|
}
|
|
29672
|
-
if (total === 0) console.log(
|
|
29673
|
-
console.log(
|
|
29848
|
+
if (total === 0) console.log(chalk2.dim("No chat-recall hook entries were registered."));
|
|
29849
|
+
console.log(chalk2.dim(` (Hook scripts left in ${hooksDir} \u2014 delete manually if you want.)`));
|
|
29674
29850
|
return;
|
|
29675
29851
|
}
|
|
29676
29852
|
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(
|
|
29853
|
+
console.log(chalk2.yellow("Hooks are not installed on Windows."));
|
|
29854
|
+
console.log(chalk2.dim(" They are POSIX shell scripts, and Claude Code runs hook commands through"));
|
|
29855
|
+
console.log(chalk2.dim(" cmd.exe, which cannot execute them. Registering them would put an error on"));
|
|
29856
|
+
console.log(chalk2.dim(" every turn instead of saving anything."));
|
|
29857
|
+
console.log(chalk2.dim(""));
|
|
29858
|
+
console.log(chalk2.dim(" Everything else works: indexing, sync, search and the MCP server are"));
|
|
29859
|
+
console.log(chalk2.dim(" unaffected. Hooks only add automatic fact-saving and the wake-up bundle,"));
|
|
29860
|
+
console.log(chalk2.dim(" and you can get the same context by calling recall_wake_up from the agent."));
|
|
29861
|
+
console.log(chalk2.dim(""));
|
|
29862
|
+
console.log(chalk2.dim(" Under WSL, run this from inside the WSL shell and it installs normally."));
|
|
29687
29863
|
return;
|
|
29688
29864
|
}
|
|
29689
29865
|
mkdirSync20(hooksDir, { recursive: true });
|
|
@@ -29733,35 +29909,35 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
|
|
|
29733
29909
|
mkdirSync20(dirname27(hooksJson), { recursive: true });
|
|
29734
29910
|
writeFileSync22(hooksJson, JSON.stringify(config2, null, 2) + "\n");
|
|
29735
29911
|
}
|
|
29736
|
-
const saveSz =
|
|
29737
|
-
console.log(
|
|
29912
|
+
const saveSz = statSync47(installedSaveHook).size;
|
|
29913
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall save hook (${saveSz} bytes)`));
|
|
29738
29914
|
if (installResume) {
|
|
29739
|
-
const resumeSz =
|
|
29740
|
-
console.log(
|
|
29915
|
+
const resumeSz = statSync47(installedResumeHook).size;
|
|
29916
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall resume-hint hook (${resumeSz} bytes)`));
|
|
29741
29917
|
}
|
|
29742
29918
|
if (installEscalate) {
|
|
29743
|
-
const escalateSz =
|
|
29744
|
-
console.log(
|
|
29919
|
+
const escalateSz = statSync47(installedEscalateHook).size;
|
|
29920
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall learnings-escalation hook (${escalateSz} bytes)`));
|
|
29745
29921
|
}
|
|
29746
29922
|
if (installWakeup) {
|
|
29747
|
-
const wakeupSz =
|
|
29748
|
-
console.log(
|
|
29923
|
+
const wakeupSz = statSync47(installedWakeupHook).size;
|
|
29924
|
+
console.log(chalk2.green(`\u2713 Installed chat-recall wake-up hook (${wakeupSz} bytes)`));
|
|
29749
29925
|
for (const f of wakeupSkipped) {
|
|
29750
|
-
console.log(
|
|
29926
|
+
console.log(chalk2.dim(` (SessionStart already wired in ${f} \u2014 left as is, not duplicated)`));
|
|
29751
29927
|
}
|
|
29752
29928
|
}
|
|
29753
|
-
console.log(
|
|
29929
|
+
console.log(chalk2.dim(` scripts: ${hooksDir}`));
|
|
29754
29930
|
for (const [i, f] of hookConfigFiles.entries()) {
|
|
29755
|
-
console.log(
|
|
29931
|
+
console.log(chalk2.dim(` ${i === 0 ? "config: " : " "} ${f}`));
|
|
29756
29932
|
}
|
|
29757
|
-
console.log(
|
|
29933
|
+
console.log(chalk2.dim(` events: Stop, PreCompact${installResume ? ", UserPromptSubmit" : ""}${installEscalate ? ", SessionEnd" : ""}${installWakeup ? ", SessionStart" : ""}`));
|
|
29758
29934
|
console.log();
|
|
29759
|
-
console.log(
|
|
29935
|
+
console.log(chalk2.dim("Run `chat-recall install-hooks --uninstall` to remove later."));
|
|
29760
29936
|
if (!installResume) {
|
|
29761
|
-
console.log(
|
|
29937
|
+
console.log(chalk2.dim("(--no-resume-hint passed \u2014 UserPromptSubmit hook skipped)"));
|
|
29762
29938
|
}
|
|
29763
29939
|
if (!installEscalate) {
|
|
29764
|
-
console.log(
|
|
29940
|
+
console.log(chalk2.dim("(--no-escalate passed \u2014 SessionEnd hook skipped)"));
|
|
29765
29941
|
}
|
|
29766
29942
|
});
|
|
29767
29943
|
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 +29953,12 @@ program.command("dossier <project>").description("Generate a project dossier (ov
|
|
|
29777
29953
|
const md = dossier.markdown;
|
|
29778
29954
|
if (options.out) {
|
|
29779
29955
|
writeFileSync21(options.out, md);
|
|
29780
|
-
console.log(
|
|
29956
|
+
console.log(chalk2.green(`Wrote dossier to ${options.out} (${md.length} chars)`));
|
|
29781
29957
|
} else {
|
|
29782
29958
|
console.log(md);
|
|
29783
29959
|
}
|
|
29784
29960
|
} catch (err) {
|
|
29785
|
-
console.error(
|
|
29961
|
+
console.error(chalk2.red("Error:"), err instanceof Error ? err.message : err);
|
|
29786
29962
|
process.exit(1);
|
|
29787
29963
|
}
|
|
29788
29964
|
});
|
|
@@ -29795,17 +29971,17 @@ program.command("watch").description("Run the live indexer daemon (watches all A
|
|
|
29795
29971
|
try {
|
|
29796
29972
|
const { installService: installService2, isServiceRunning: isServiceRunning2, platformName: platformName2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
|
|
29797
29973
|
if (isServiceRunning2()) {
|
|
29798
|
-
console.log(
|
|
29974
|
+
console.log(chalk2.green(`\u2713 chat-recall-watch service already running (${platformName2()}).`));
|
|
29799
29975
|
return;
|
|
29800
29976
|
}
|
|
29801
29977
|
const paths = installService2();
|
|
29802
|
-
console.log(
|
|
29803
|
-
if (paths.definitionPath) console.log(
|
|
29804
|
-
console.log(
|
|
29978
|
+
console.log(chalk2.green(`\u2713 chat-recall-watch service installed and started (${platformName2()}).`));
|
|
29979
|
+
if (paths.definitionPath) console.log(chalk2.dim(` unit: ${paths.definitionPath}`));
|
|
29980
|
+
console.log(chalk2.dim(` logs: ${paths.logFile} \xB7 manage: \`chat-recall service status\``));
|
|
29805
29981
|
} catch (err) {
|
|
29806
29982
|
const se = err;
|
|
29807
|
-
console.error(
|
|
29808
|
-
if (se.hint) console.error(
|
|
29983
|
+
console.error(chalk2.red(se.message || String(err)));
|
|
29984
|
+
if (se.hint) console.error(chalk2.dim(` ${se.hint}`));
|
|
29809
29985
|
process.exit(1);
|
|
29810
29986
|
}
|
|
29811
29987
|
return;
|
|
@@ -29823,7 +29999,7 @@ program.command("watch").description("Run the live indexer daemon (watches all A
|
|
|
29823
29999
|
lastErr = err;
|
|
29824
30000
|
}
|
|
29825
30001
|
}
|
|
29826
|
-
console.error(
|
|
30002
|
+
console.error(chalk2.red("Could not load the watch daemon:"), lastErr instanceof Error ? lastErr.message : lastErr);
|
|
29827
30003
|
process.exit(1);
|
|
29828
30004
|
});
|
|
29829
30005
|
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 +30008,25 @@ program.command("service <action>").description("Manage the per-user background
|
|
|
29832
30008
|
if (action === "install") {
|
|
29833
30009
|
const wasRunning = isServiceRunning2();
|
|
29834
30010
|
const paths = installService2();
|
|
29835
|
-
console.log(
|
|
29836
|
-
if (paths.definitionPath) console.log(
|
|
29837
|
-
console.log(
|
|
30011
|
+
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()}).`));
|
|
30012
|
+
if (paths.definitionPath) console.log(chalk2.dim(` unit: ${paths.definitionPath}`));
|
|
30013
|
+
console.log(chalk2.dim(` logs: ${paths.logFile}`));
|
|
29838
30014
|
} else if (action === "uninstall") {
|
|
29839
30015
|
const removed = uninstallService2();
|
|
29840
|
-
if (removed) console.log(
|
|
29841
|
-
else console.log(
|
|
30016
|
+
if (removed) console.log(chalk2.green(`\u2713 chat-recall-watch service removed (${platformName2()}).`));
|
|
30017
|
+
else console.log(chalk2.dim("Service was not installed \u2014 nothing to remove."));
|
|
29842
30018
|
} else if (action === "status") {
|
|
29843
30019
|
const running = isServiceRunning2();
|
|
29844
|
-
console.log(`chat-recall-watch service (${platformName2()}): ${running ?
|
|
29845
|
-
if (!running) console.log(
|
|
30020
|
+
console.log(`chat-recall-watch service (${platformName2()}): ${running ? chalk2.green("running") : chalk2.yellow("not running")}`);
|
|
30021
|
+
if (!running) console.log(chalk2.dim(" install: `chat-recall service install`"));
|
|
29846
30022
|
} else {
|
|
29847
|
-
console.error(
|
|
30023
|
+
console.error(chalk2.red(`Unknown action '${action}'.`) + chalk2.dim(" Use: install | uninstall | status"));
|
|
29848
30024
|
process.exit(1);
|
|
29849
30025
|
}
|
|
29850
30026
|
} catch (err) {
|
|
29851
30027
|
const se = err;
|
|
29852
|
-
console.error(
|
|
29853
|
-
if (se.hint) console.error(
|
|
30028
|
+
console.error(chalk2.red(se.message || String(err)));
|
|
30029
|
+
if (se.hint) console.error(chalk2.dim(` ${se.hint}`));
|
|
29854
30030
|
process.exit(1);
|
|
29855
30031
|
}
|
|
29856
30032
|
});
|
|
@@ -29871,7 +30047,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29871
30047
|
if (opts.token) {
|
|
29872
30048
|
const token = opts.token.trim();
|
|
29873
30049
|
if (/[^\x21-\x7e]/.test(token)) {
|
|
29874
|
-
console.error(
|
|
30050
|
+
console.error(chalk2.dim("Use the Copy button next to the token in the dashboard and paste the full line."));
|
|
29875
30051
|
return failLogin(opts, 'the token contains non-ASCII characters \u2014 this looks like a masked/truncated copy (e.g. "ct_12345678\u2026")');
|
|
29876
30052
|
}
|
|
29877
30053
|
const { probeServer: identifyServer, probeOk: identified, probeAdvice: identifyAdvice } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
@@ -29880,7 +30056,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29880
30056
|
const check = await verifyToken(token);
|
|
29881
30057
|
if (!check.ok) {
|
|
29882
30058
|
if (check.status === 401 || check.status === 403) {
|
|
29883
|
-
console.error(
|
|
30059
|
+
console.error(chalk2.dim("Tokens are shown once \u2014 mint a fresh one (Account \u2192 Connect your machine) and copy the whole line."));
|
|
29884
30060
|
return failLogin(opts, `${base2} rejected the token (HTTP ${check.status})`);
|
|
29885
30061
|
}
|
|
29886
30062
|
if (check.status !== void 0 && check.status >= 500) {
|
|
@@ -29892,12 +30068,12 @@ async function runLogin(serverUrl, opts) {
|
|
|
29892
30068
|
return failLogin(opts, `could not verify the token against ${base2}: ${check.error || `HTTP ${check.status}`}`);
|
|
29893
30069
|
}
|
|
29894
30070
|
saveCredentials2({ serverUrl, token });
|
|
29895
|
-
console.log(
|
|
30071
|
+
console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` server: ${serverUrl} (token verified)`));
|
|
29896
30072
|
return;
|
|
29897
30073
|
}
|
|
29898
30074
|
const existing = loadAllCredentials2().find((t) => t.serverUrl.replace(/\/+$/, "") === base2);
|
|
29899
30075
|
if (existing && (await verifyToken(existing.token)).ok) {
|
|
29900
|
-
console.log(
|
|
30076
|
+
console.log(chalk2.green("\u2713 Already connected.") + chalk2.dim(` server: ${serverUrl} (existing token verified)`));
|
|
29901
30077
|
return;
|
|
29902
30078
|
}
|
|
29903
30079
|
try {
|
|
@@ -29907,14 +30083,14 @@ async function runLogin(serverUrl, opts) {
|
|
|
29907
30083
|
const probe = await fetch(`${base2}/api/status`, { signal: AbortSignal.timeout(8e3) });
|
|
29908
30084
|
if (probe.status === 200) {
|
|
29909
30085
|
saveCredentials2({ serverUrl, token: "" });
|
|
29910
|
-
console.log(
|
|
30086
|
+
console.log(chalk2.green("\u2713 Logged in.") + chalk2.dim(` ${serverUrl} (local server \u2014 no auth, no token needed)`));
|
|
29911
30087
|
return;
|
|
29912
30088
|
}
|
|
29913
30089
|
}
|
|
29914
30090
|
} catch {
|
|
29915
30091
|
}
|
|
29916
30092
|
if (opts.check) {
|
|
29917
|
-
console.error(
|
|
30093
|
+
console.error(chalk2.dim(`Not connected to ${serverUrl}.`));
|
|
29918
30094
|
process.exit(1);
|
|
29919
30095
|
}
|
|
29920
30096
|
try {
|
|
@@ -29942,7 +30118,7 @@ async function runLogin(serverUrl, opts) {
|
|
|
29942
30118
|
if (!authProvider && !issuer) {
|
|
29943
30119
|
authProvider = "better-auth";
|
|
29944
30120
|
if (capsError) {
|
|
29945
|
-
console.log(` ${
|
|
30121
|
+
console.log(` ${chalk2.yellow("Could not read the server's capabilities")} (${capsError}) \u2014 assuming the standard sign-in flow.`);
|
|
29946
30122
|
void Promise.resolve().then(() => (init_install_report(), install_report_exports)).then(({ reportInstallFailure: reportInstallFailure2 }) => reportInstallFailure2(base2, {
|
|
29947
30123
|
step: "cli_login_probe",
|
|
29948
30124
|
reason: capsError?.slice(0, 40) ?? null,
|
|
@@ -29957,13 +30133,13 @@ async function runLogin(serverUrl, opts) {
|
|
|
29957
30133
|
return;
|
|
29958
30134
|
}
|
|
29959
30135
|
console.log();
|
|
29960
|
-
console.log(
|
|
29961
|
-
console.log(" " +
|
|
30136
|
+
console.log(chalk2.bold("Open this to approve the machine:"));
|
|
30137
|
+
console.log(" " + chalk2.cyan(p.url));
|
|
29962
30138
|
if (openBrowser(p.url)) {
|
|
29963
|
-
console.log(
|
|
30139
|
+
console.log(chalk2.dim(" (tried to open it for you \u2014 if no window appeared, use the link above)"));
|
|
29964
30140
|
}
|
|
29965
|
-
console.log(
|
|
29966
|
-
console.log(
|
|
30141
|
+
console.log(chalk2.dim(` If the page asks for a code: ${chalk2.bold(p.userCode)}`));
|
|
30142
|
+
console.log(chalk2.dim("Then come back here \u2014 this finishes on its own. Waiting\u2026"));
|
|
29967
30143
|
};
|
|
29968
30144
|
const tokens = authProvider === "better-auth" ? await betterAuthDeviceLogin2(base2, onPrompt) : await deviceLogin2({ issuer, clientId: opts.clientId }, onPrompt);
|
|
29969
30145
|
const authHdr = { authorization: `Bearer ${tokens.accessToken}`, "content-type": "application/json" };
|
|
@@ -29979,9 +30155,9 @@ async function runLogin(serverUrl, opts) {
|
|
|
29979
30155
|
const created = await fetch(`${base2}/api/teams`, { method: "POST", headers: authHdr, body: JSON.stringify({ name }) });
|
|
29980
30156
|
if (!created.ok) throw new Error(`workspace create failed: HTTP ${created.status} ${await created.text().catch(() => "")}`);
|
|
29981
30157
|
slug = (await readJsonBody(created, base2, "POST /api/teams")).slug;
|
|
29982
|
-
console.log(
|
|
30158
|
+
console.log(chalk2.dim(`Created workspace "${name}" (${slug}) \u2014 you're its owner.`));
|
|
29983
30159
|
} else {
|
|
29984
|
-
teams.forEach((t) => console.error(` ${t.team_slug} ${
|
|
30160
|
+
teams.forEach((t) => console.error(` ${t.team_slug} ${chalk2.dim(t.name)}`));
|
|
29985
30161
|
throw new Error("you belong to multiple teams \u2014 pass --team <slug> (listed above)");
|
|
29986
30162
|
}
|
|
29987
30163
|
}
|
|
@@ -29994,13 +30170,13 @@ async function runLogin(serverUrl, opts) {
|
|
|
29994
30170
|
if (!mint.ok) throw new Error(`token mint failed: HTTP ${mint.status} ${await mint.text().catch(() => "")}`);
|
|
29995
30171
|
const { token } = await readJsonBody(mint, base2, "POST /api/teams/:slug/tokens");
|
|
29996
30172
|
saveCredentials2({ serverUrl, token });
|
|
29997
|
-
console.log(
|
|
30173
|
+
console.log(chalk2.green(`\u2713 Logged in to ${chalk2.bold(slug)}`) + chalk2.dim(` (device: ${deviceId}) server: ${serverUrl}`));
|
|
29998
30174
|
if (!opts.fromInit) {
|
|
29999
|
-
console.log(
|
|
30175
|
+
console.log(chalk2.dim("Run `chat-recall sync` to push redacted conversations."));
|
|
30000
30176
|
}
|
|
30001
30177
|
} catch (err) {
|
|
30002
30178
|
if (opts.fromInit) throw err instanceof Error ? err : new Error(String(err));
|
|
30003
|
-
console.error(
|
|
30179
|
+
console.error(chalk2.red("login failed:"), err instanceof Error ? err.message : err);
|
|
30004
30180
|
process.exit(1);
|
|
30005
30181
|
}
|
|
30006
30182
|
}
|
|
@@ -30008,15 +30184,15 @@ var exclude = program.command("exclude").description("Control what never leaves
|
|
|
30008
30184
|
exclude.command("list", { isDefault: true }).description("Show current exclusions").action(async () => {
|
|
30009
30185
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30010
30186
|
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 ? "" :
|
|
30187
|
+
console.log(chalk2.bold("Sync exclusions") + chalk2.dim(" (~/.chat-recall/settings/settings.json)"));
|
|
30188
|
+
console.log(` tools: ${s.sync.excludeTools.length ? s.sync.excludeTools.join(", ") : chalk2.dim("none")}`);
|
|
30189
|
+
console.log(` projects: ${s.sync.excludeProjects.length ? "" : chalk2.dim("none")}`);
|
|
30014
30190
|
for (const p of s.sync.excludeProjects) console.log(` ${p}`);
|
|
30015
30191
|
const deny = s.privacy.projectDenylist || [];
|
|
30016
|
-
console.log(` index denylist (never even indexed): ${deny.length ? "" :
|
|
30192
|
+
console.log(` index denylist (never even indexed): ${deny.length ? "" : chalk2.dim("none")}`);
|
|
30017
30193
|
for (const p of deny) console.log(` ${p}`);
|
|
30018
|
-
console.log(
|
|
30019
|
-
console.log(
|
|
30194
|
+
console.log(chalk2.dim("\nPersonal folders (Pictures, Music, Documents, Desktop, Downloads, \u2026) are"));
|
|
30195
|
+
console.log(chalk2.dim("skipped from code indexing by default. Opt one back in: `chat-recall include project <path>`."));
|
|
30020
30196
|
const inc = s.sync.includeProjects || [];
|
|
30021
30197
|
if (inc.length) {
|
|
30022
30198
|
console.log(` personal paths opted back in:`);
|
|
@@ -30027,8 +30203,8 @@ var include = program.command("include").description("Opt a personal folder (Pic
|
|
|
30027
30203
|
include.command("list", { isDefault: true }).description("Show paths opted back into indexing").action(async () => {
|
|
30028
30204
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30029
30205
|
const inc = loadSettings2().sync.includeProjects || [];
|
|
30030
|
-
console.log(
|
|
30031
|
-
if (!inc.length) console.log(
|
|
30206
|
+
console.log(chalk2.bold("Personal paths opted back into code indexing"));
|
|
30207
|
+
if (!inc.length) console.log(chalk2.dim(" none \u2014 personal folders are all skipped"));
|
|
30032
30208
|
for (const p of inc) console.log(` ${p}`);
|
|
30033
30209
|
});
|
|
30034
30210
|
include.command("project <path>").description("Re-permit a path under a personal folder (substring match; e.g. ~/Documents/code)").action(async (path) => {
|
|
@@ -30039,12 +30215,12 @@ include.command("project <path>").description("Re-permit a path under a personal
|
|
|
30039
30215
|
const s = loadSettings2();
|
|
30040
30216
|
s.sync.includeProjects = s.sync.includeProjects || [];
|
|
30041
30217
|
if (s.sync.includeProjects.includes(abs)) {
|
|
30042
|
-
console.log(
|
|
30218
|
+
console.log(chalk2.dim(`Already included: ${abs}`));
|
|
30043
30219
|
return;
|
|
30044
30220
|
}
|
|
30045
30221
|
s.sync.includeProjects.push(abs);
|
|
30046
30222
|
saveSettings2(s);
|
|
30047
|
-
console.log(
|
|
30223
|
+
console.log(chalk2.green(`\u2713 Included ${abs}`) + chalk2.dim(" \u2014 code indexing may now walk it (takes effect next discovery tick)."));
|
|
30048
30224
|
});
|
|
30049
30225
|
include.command("remove <path>").description("Undo an opt-in (personal folder goes back to skipped)").action(async (path) => {
|
|
30050
30226
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30055,30 +30231,30 @@ include.command("remove <path>").description("Undo an opt-in (personal folder go
|
|
|
30055
30231
|
const before = (s.sync.includeProjects || []).length;
|
|
30056
30232
|
s.sync.includeProjects = (s.sync.includeProjects || []).filter((p) => p !== path && p !== abs);
|
|
30057
30233
|
if (s.sync.includeProjects.length === before) {
|
|
30058
|
-
console.error(
|
|
30234
|
+
console.error(chalk2.red(`Not in the include list: ${path}`));
|
|
30059
30235
|
process.exit(1);
|
|
30060
30236
|
}
|
|
30061
30237
|
saveSettings2(s);
|
|
30062
|
-
console.log(
|
|
30238
|
+
console.log(chalk2.green(`\u2713 Removed ${path}`) + chalk2.dim(" \u2014 back to skipped."));
|
|
30063
30239
|
});
|
|
30064
30240
|
var retention = program.command("retention").description("How long the server keeps your synced sessions. Bare `retention` shows the current window.");
|
|
30065
30241
|
retention.command("show", { isDefault: true }).description("Show the window and how many sessions it would delete").action(async () => {
|
|
30066
30242
|
const t = requireTarget();
|
|
30067
30243
|
const r = await serverGet("/api/data/retention");
|
|
30068
|
-
console.log(
|
|
30244
|
+
console.log(chalk2.bold("Retention") + chalk2.dim(` (server: ${t.base})`));
|
|
30069
30245
|
if (!r.days) {
|
|
30070
|
-
console.log(` window: ${
|
|
30246
|
+
console.log(` window: ${chalk2.green("none")} \u2014 the server keeps everything you sync`);
|
|
30071
30247
|
} else {
|
|
30072
|
-
console.log(` window: ${
|
|
30248
|
+
console.log(` window: ${chalk2.yellow(`${r.days} days`)}`);
|
|
30073
30249
|
console.log(` sessions currently outside it: ${r.wouldDelete}`);
|
|
30074
30250
|
}
|
|
30075
|
-
console.log(
|
|
30251
|
+
console.log(chalk2.dim(` Set one: chat-recall retention set <${r.min}-${r.max}> \xB7 clear it: chat-recall retention set 0`));
|
|
30076
30252
|
});
|
|
30077
30253
|
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
30254
|
requireTarget();
|
|
30079
30255
|
const days = Number(daysArg);
|
|
30080
30256
|
if (!Number.isInteger(days) || days < 0) {
|
|
30081
|
-
console.error(
|
|
30257
|
+
console.error(chalk2.red(`'${daysArg}' is not a whole number of days.`));
|
|
30082
30258
|
process.exit(1);
|
|
30083
30259
|
}
|
|
30084
30260
|
const preview = await serverGet(
|
|
@@ -30086,23 +30262,23 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30086
30262
|
);
|
|
30087
30263
|
if (days === 0) {
|
|
30088
30264
|
await serverPost("/api/data/retention", { days: 0 });
|
|
30089
|
-
console.log(
|
|
30265
|
+
console.log(chalk2.green("\u2713 Window cleared") + chalk2.dim(" \u2014 the server keeps everything you sync."));
|
|
30090
30266
|
return;
|
|
30091
30267
|
}
|
|
30092
30268
|
if (preview.wouldDelete > 0) {
|
|
30093
30269
|
console.log();
|
|
30094
|
-
console.log(
|
|
30095
|
-
console.log(
|
|
30270
|
+
console.log(chalk2.yellow(`This deletes ${preview.wouldDelete} session(s) from the server, and keeps deleting`));
|
|
30271
|
+
console.log(chalk2.yellow(`anything older than ${days} days from now on.`));
|
|
30096
30272
|
console.log();
|
|
30097
|
-
console.log(
|
|
30098
|
-
console.log(
|
|
30099
|
-
console.log(
|
|
30100
|
-
console.log(
|
|
30273
|
+
console.log(chalk2.dim(" Re-syncing can restore a session ONLY if its transcript is still on a"));
|
|
30274
|
+
console.log(chalk2.dim(" machine you have. For a laptop you no longer own, history your AI tool"));
|
|
30275
|
+
console.log(chalk2.dim(" has rotated, or files you deleted, our copy is the only copy."));
|
|
30276
|
+
console.log(chalk2.dim(" Take one first if you want it: chat-recall export"));
|
|
30101
30277
|
console.log();
|
|
30102
30278
|
if (!opts.yes) {
|
|
30103
30279
|
if (!process.stdin.isTTY) {
|
|
30104
|
-
console.error(
|
|
30105
|
-
console.error(
|
|
30280
|
+
console.error(chalk2.red("Refusing: this would delete data and there is no terminal to confirm at."));
|
|
30281
|
+
console.error(chalk2.dim("Pass --yes if you mean it."));
|
|
30106
30282
|
process.exit(1);
|
|
30107
30283
|
}
|
|
30108
30284
|
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
@@ -30110,7 +30286,7 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30110
30286
|
const answer = (await rl.question(`Type ${preview.wouldDelete} to confirm: `)).trim();
|
|
30111
30287
|
rl.close();
|
|
30112
30288
|
if (answer !== String(preview.wouldDelete)) {
|
|
30113
|
-
console.log(
|
|
30289
|
+
console.log(chalk2.dim("Cancelled \u2014 nothing changed."));
|
|
30114
30290
|
return;
|
|
30115
30291
|
}
|
|
30116
30292
|
}
|
|
@@ -30119,20 +30295,20 @@ retention.command("set <days>").description("Set the window in days, or 0 to kee
|
|
|
30119
30295
|
"/api/data/retention",
|
|
30120
30296
|
{ days, acknowledge: true }
|
|
30121
30297
|
);
|
|
30122
|
-
console.log(
|
|
30298
|
+
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
30299
|
});
|
|
30124
30300
|
var syncOnly = program.command("sync-only").description("Opt-in sync: ship ONLY chosen projects. Bare `sync-only` shows the mode + allowlist.");
|
|
30125
30301
|
syncOnly.command("list", { isDefault: true }).description("Show the sync mode and allowlisted projects").action(async () => {
|
|
30126
30302
|
const { loadSettings: loadSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30127
30303
|
const s = loadSettings2();
|
|
30128
30304
|
const mode = s.sync.syncMode ?? "all";
|
|
30129
|
-
console.log(
|
|
30130
|
-
console.log(` mode: ${mode === "only" ?
|
|
30305
|
+
console.log(chalk2.bold("Selective sync") + chalk2.dim(" (~/.chat-recall/settings/settings.json)"));
|
|
30306
|
+
console.log(` mode: ${mode === "only" ? chalk2.yellow("only (allowlist)") : chalk2.green("all (default)")}`);
|
|
30131
30307
|
const list = s.sync.syncOnlyProjects ?? [];
|
|
30132
|
-
console.log(` projects: ${list.length ? "" :
|
|
30308
|
+
console.log(` projects: ${list.length ? "" : chalk2.dim("none")}`);
|
|
30133
30309
|
for (const p of list) console.log(` ${p}`);
|
|
30134
|
-
if (mode === "only" && list.length === 0) console.log(
|
|
30135
|
-
console.log(
|
|
30310
|
+
if (mode === "only" && list.length === 0) console.log(chalk2.red(' \u26A0 mode is "only" but the allowlist is empty \u2192 NOTHING syncs.'));
|
|
30311
|
+
console.log(chalk2.dim("\nList ids with `chat-recall projects`, then `chat-recall sync-only add <id>`."));
|
|
30136
30312
|
});
|
|
30137
30313
|
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
30314
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30141,7 +30317,7 @@ syncOnly.command("add <project>").description('Add a project id (git:\u2026/ws:\
|
|
|
30141
30317
|
if (!s.sync.syncOnlyProjects.includes(project)) s.sync.syncOnlyProjects.push(project);
|
|
30142
30318
|
s.sync.syncMode = "only";
|
|
30143
30319
|
saveSettings2(s);
|
|
30144
|
-
console.log(
|
|
30320
|
+
console.log(chalk2.green(`\u2713 Added ${project}`) + chalk2.dim(' \u2014 mode is now "only"; next sync ships only allowlisted projects.'));
|
|
30145
30321
|
});
|
|
30146
30322
|
syncOnly.command("remove <project>").description("Remove a project from the allowlist").action(async (project) => {
|
|
30147
30323
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30149,18 +30325,18 @@ syncOnly.command("remove <project>").description("Remove a project from the allo
|
|
|
30149
30325
|
const before = (s.sync.syncOnlyProjects || []).length;
|
|
30150
30326
|
s.sync.syncOnlyProjects = (s.sync.syncOnlyProjects || []).filter((p) => p !== project);
|
|
30151
30327
|
if (s.sync.syncOnlyProjects.length === before) {
|
|
30152
|
-
console.error(
|
|
30328
|
+
console.error(chalk2.red(`Not in the allowlist: ${project}`));
|
|
30153
30329
|
process.exit(1);
|
|
30154
30330
|
}
|
|
30155
30331
|
saveSettings2(s);
|
|
30156
|
-
console.log(
|
|
30332
|
+
console.log(chalk2.green(`\u2713 Removed ${project}`));
|
|
30157
30333
|
});
|
|
30158
30334
|
syncOnly.command("all").description("Switch back to syncing ALL non-excluded projects (keeps the list, just disables opt-in mode)").action(async () => {
|
|
30159
30335
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30160
30336
|
const s = loadSettings2();
|
|
30161
30337
|
s.sync.syncMode = "all";
|
|
30162
30338
|
saveSettings2(s);
|
|
30163
|
-
console.log(
|
|
30339
|
+
console.log(chalk2.green("\u2713 Sync mode: all") + chalk2.dim(" \u2014 every non-excluded project syncs again."));
|
|
30164
30340
|
});
|
|
30165
30341
|
program.command("projects").description("List distinct project ids discovered on this machine (for `sync-only add`)").action(async () => {
|
|
30166
30342
|
const { listAvailableBackends: listAvailableBackends2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
|
|
@@ -30180,11 +30356,11 @@ program.command("projects").description("List distinct project ids discovered on
|
|
|
30180
30356
|
}
|
|
30181
30357
|
const rows = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
30182
30358
|
if (!rows.length) {
|
|
30183
|
-
console.log(
|
|
30359
|
+
console.log(chalk2.dim("No projects discovered."));
|
|
30184
30360
|
return;
|
|
30185
30361
|
}
|
|
30186
|
-
console.log(
|
|
30187
|
-
for (const [id, n] of rows) console.log(` ${
|
|
30362
|
+
console.log(chalk2.bold("Discovered projects") + chalk2.dim(" (id \xB7 sessions)"));
|
|
30363
|
+
for (const [id, n] of rows) console.log(` ${chalk2.cyan(id)} ${chalk2.dim("\xB7 " + n)}`);
|
|
30188
30364
|
});
|
|
30189
30365
|
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
30366
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30193,28 +30369,28 @@ exclude.command("project <path>").description("Exclude a project path from synci
|
|
|
30193
30369
|
const abs = resolve3(path.replace(/^~(?=\/|$)/, homedir23()));
|
|
30194
30370
|
const s = loadSettings2();
|
|
30195
30371
|
if (s.sync.excludeProjects.includes(abs)) {
|
|
30196
|
-
console.log(
|
|
30372
|
+
console.log(chalk2.dim(`Already excluded: ${abs}`));
|
|
30197
30373
|
return;
|
|
30198
30374
|
}
|
|
30199
30375
|
s.sync.excludeProjects.push(abs);
|
|
30200
30376
|
saveSettings2(s);
|
|
30201
|
-
console.log(
|
|
30377
|
+
console.log(chalk2.green(`\u2713 Excluded ${abs}`) + chalk2.dim(" \u2014 takes effect next sync; already-synced rows stay until deleted."));
|
|
30202
30378
|
});
|
|
30203
30379
|
exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude | gemini | codex | opencode | agy | cursor)").action(async (tool) => {
|
|
30204
30380
|
const valid = ["claude", "gemini", "codex", "opencode", "agy", "cursor"];
|
|
30205
30381
|
if (!valid.includes(tool)) {
|
|
30206
|
-
console.error(
|
|
30382
|
+
console.error(chalk2.red(`Unknown tool '${tool}'.`) + chalk2.dim(` Use one of: ${valid.join(" | ")}`));
|
|
30207
30383
|
process.exit(1);
|
|
30208
30384
|
}
|
|
30209
30385
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
30210
30386
|
const s = loadSettings2();
|
|
30211
30387
|
if (s.sync.excludeTools.includes(tool)) {
|
|
30212
|
-
console.log(
|
|
30388
|
+
console.log(chalk2.dim(`Already excluded: ${tool}`));
|
|
30213
30389
|
return;
|
|
30214
30390
|
}
|
|
30215
30391
|
s.sync.excludeTools.push(tool);
|
|
30216
30392
|
saveSettings2(s);
|
|
30217
|
-
console.log(
|
|
30393
|
+
console.log(chalk2.green(`\u2713 Excluded ${tool}`) + chalk2.dim(" \u2014 nothing from it leaves this machine from the next sync on."));
|
|
30218
30394
|
});
|
|
30219
30395
|
exclude.command("remove <value>").description("Remove an exclusion (tool name or project path)").action(async (value) => {
|
|
30220
30396
|
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
@@ -30227,20 +30403,20 @@ exclude.command("remove <value>").description("Remove an exclusion (tool name or
|
|
|
30227
30403
|
s.sync.excludeTools = s.sync.excludeTools.filter((t) => t !== value);
|
|
30228
30404
|
s.sync.excludeProjects = s.sync.excludeProjects.filter((p) => p !== value && p !== abs);
|
|
30229
30405
|
if (s.sync.excludeTools.length === beforeTools && s.sync.excludeProjects.length === beforeProjects) {
|
|
30230
|
-
console.error(
|
|
30406
|
+
console.error(chalk2.red(`No exclusion matches '${value}'.`) + chalk2.dim(" See: chat-recall exclude list"));
|
|
30231
30407
|
process.exit(1);
|
|
30232
30408
|
}
|
|
30233
30409
|
saveSettings2(s);
|
|
30234
|
-
console.log(
|
|
30410
|
+
console.log(chalk2.green(`\u2713 Removed ${value}`));
|
|
30235
30411
|
});
|
|
30236
30412
|
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
30413
|
program.command("logout <server-url>").description("Remove one sync target (other targets keep syncing)").action(async (serverUrl) => {
|
|
30238
30414
|
const { removeCredentials: removeCredentials2, loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30239
30415
|
if (removeCredentials2(serverUrl)) {
|
|
30240
30416
|
const left = loadAllCredentials2();
|
|
30241
|
-
console.log(
|
|
30417
|
+
console.log(chalk2.green(`\u2713 Removed ${serverUrl}`) + chalk2.dim(` \u2014 ${left.length} target(s) remain`));
|
|
30242
30418
|
} else {
|
|
30243
|
-
console.error(
|
|
30419
|
+
console.error(chalk2.red(`No such target: ${serverUrl}`));
|
|
30244
30420
|
process.exit(1);
|
|
30245
30421
|
}
|
|
30246
30422
|
});
|
|
@@ -30248,7 +30424,7 @@ program.command("delete <session-id>").description("Delete a session from chat-r
|
|
|
30248
30424
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30249
30425
|
const targets = loadAllCredentials2();
|
|
30250
30426
|
if (targets.length === 0) {
|
|
30251
|
-
console.error(
|
|
30427
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30252
30428
|
process.exit(1);
|
|
30253
30429
|
}
|
|
30254
30430
|
let ok = 0;
|
|
@@ -30260,20 +30436,20 @@ program.command("delete <session-id>").description("Delete a session from chat-r
|
|
|
30260
30436
|
});
|
|
30261
30437
|
if (res.ok) {
|
|
30262
30438
|
ok++;
|
|
30263
|
-
console.log(
|
|
30264
|
-
} else console.error(
|
|
30439
|
+
console.log(chalk2.green(`\u2713 Deleted on ${t.serverUrl}`));
|
|
30440
|
+
} else console.error(chalk2.red(`\u2717 ${t.serverUrl}: HTTP ${res.status}`));
|
|
30265
30441
|
} catch (e) {
|
|
30266
|
-
console.error(
|
|
30442
|
+
console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
|
|
30267
30443
|
}
|
|
30268
30444
|
}
|
|
30269
30445
|
if (ok === 0) process.exit(1);
|
|
30270
|
-
console.log(
|
|
30446
|
+
console.log(chalk2.dim(`Tombstoned on ${ok}/${targets.length} server(s) \u2014 re-sync cannot resurrect it.`));
|
|
30271
30447
|
});
|
|
30272
30448
|
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
30449
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
30274
30450
|
const targets = loadAllCredentials2();
|
|
30275
30451
|
if (targets.length === 0) {
|
|
30276
|
-
console.error(
|
|
30452
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30277
30453
|
process.exit(1);
|
|
30278
30454
|
}
|
|
30279
30455
|
let ok = 0;
|
|
@@ -30286,14 +30462,14 @@ program.command("rename <session-id> <name>").description('Give a session a memo
|
|
|
30286
30462
|
});
|
|
30287
30463
|
if (res.ok) {
|
|
30288
30464
|
ok++;
|
|
30289
|
-
console.log(
|
|
30290
|
-
} else console.error(
|
|
30465
|
+
console.log(chalk2.green(`\u2713 ${name.trim() ? "Named" : "Cleared name"} on ${t.serverUrl}`));
|
|
30466
|
+
} else console.error(chalk2.red(`\u2717 ${t.serverUrl}: HTTP ${res.status}`));
|
|
30291
30467
|
} catch (e) {
|
|
30292
|
-
console.error(
|
|
30468
|
+
console.error(chalk2.red(`\u2717 ${t.serverUrl}: ${e instanceof Error ? e.message : "failed"}`));
|
|
30293
30469
|
}
|
|
30294
30470
|
}
|
|
30295
30471
|
if (ok === 0) process.exit(1);
|
|
30296
|
-
console.log(
|
|
30472
|
+
console.log(chalk2.dim(name.trim() ? `Named "${name.trim()}" on ${ok}/${targets.length} server(s).` : `Cleared name on ${ok}/${targets.length} server(s).`));
|
|
30297
30473
|
});
|
|
30298
30474
|
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
30475
|
const { syncSessions: syncSessions2, syncIncremental: syncIncremental2, isSyncSkip: isSyncSkip2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
@@ -30302,17 +30478,17 @@ program.command("sync").description("Push redacted conversations to the configur
|
|
|
30302
30478
|
const r2 = await syncIncremental2();
|
|
30303
30479
|
if (isSyncSkip2(r2)) {
|
|
30304
30480
|
if (r2.skipped === "lock-held") {
|
|
30305
|
-
console.log(
|
|
30481
|
+
console.log(chalk2.dim("Another sync is already running (MCP tick or watch daemon) \u2014 your data is on its way."));
|
|
30306
30482
|
return;
|
|
30307
30483
|
}
|
|
30308
30484
|
const msg = {
|
|
30309
30485
|
"no-credentials": "Not logged in \u2014 run `chat-recall login <server-url>` first.",
|
|
30310
30486
|
"paused": "Sync is paused in settings \u2014 re-run `chat-recall login <server-url>` to resume."
|
|
30311
30487
|
}[r2.skipped];
|
|
30312
|
-
console.error(
|
|
30488
|
+
console.error(chalk2.red(msg));
|
|
30313
30489
|
process.exit(1);
|
|
30314
30490
|
}
|
|
30315
|
-
console.log(
|
|
30491
|
+
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
30492
|
return;
|
|
30317
30493
|
}
|
|
30318
30494
|
const sinceMs = opts.sinceHours ? Date.now() - Number(opts.sinceHours) * 36e5 : void 0;
|
|
@@ -30327,9 +30503,9 @@ program.command("sync").description("Push redacted conversations to the configur
|
|
|
30327
30503
|
throttleMs: opts.throttle !== void 0 ? Number(opts.throttle) : opts.full ? 3e3 : void 0,
|
|
30328
30504
|
prune: !!opts.prune
|
|
30329
30505
|
});
|
|
30330
|
-
console.log(
|
|
30506
|
+
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
30507
|
} catch (err) {
|
|
30332
|
-
console.error(
|
|
30508
|
+
console.error(chalk2.red("sync failed:"), err instanceof Error ? err.message : err);
|
|
30333
30509
|
process.exit(1);
|
|
30334
30510
|
}
|
|
30335
30511
|
});
|
|
@@ -30338,11 +30514,11 @@ program.command("reconcile").description("Reconcile DERIVED FIELDS (e.g. native
|
|
|
30338
30514
|
try {
|
|
30339
30515
|
const r = await reconcileFields2({ force: !!opts.force });
|
|
30340
30516
|
const per = Object.entries(r.perTarget).map(([s, v]) => `${s}: ${v.pushed} pushed`).join(", ");
|
|
30341
|
-
console.log(
|
|
30517
|
+
console.log(chalk2.green(`\u2713 Reconciled derived fields`) + chalk2.dim(
|
|
30342
30518
|
` \u2014 ${r.sessions} sessions, ${r.scanned} scanned, ${r.pushed} pushed, ${r.absent} absent${per ? ` (${per})` : ""}`
|
|
30343
30519
|
));
|
|
30344
30520
|
} catch (err) {
|
|
30345
|
-
console.error(
|
|
30521
|
+
console.error(chalk2.red("reconcile failed:"), err instanceof Error ? err.message : err);
|
|
30346
30522
|
process.exit(1);
|
|
30347
30523
|
}
|
|
30348
30524
|
});
|
|
@@ -30352,7 +30528,7 @@ program.command("update").description("Update this CLI now from the server you s
|
|
|
30352
30528
|
const { cliVersion: cliVersion3 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
30353
30529
|
const targets = loadAllCredentials();
|
|
30354
30530
|
if (!targets.length) {
|
|
30355
|
-
console.error(
|
|
30531
|
+
console.error(chalk2.red("Not logged in \u2014 run `chat-recall login <server-url>` first."));
|
|
30356
30532
|
process.exit(1);
|
|
30357
30533
|
}
|
|
30358
30534
|
let installFailed = false;
|
|
@@ -30361,17 +30537,17 @@ program.command("update").description("Update this CLI now from the server you s
|
|
|
30361
30537
|
const headers = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
30362
30538
|
const r = await runAutoUpdate2(base2, headers, cliVersion3());
|
|
30363
30539
|
if (r.updated) {
|
|
30364
|
-
console.log(
|
|
30540
|
+
console.log(chalk2.green(`\u2713 ${r.reason}`) + chalk2.dim(` (${base2})`));
|
|
30365
30541
|
continue;
|
|
30366
30542
|
}
|
|
30367
30543
|
const benign = /already current|no CLI release|disabled/.test(r.reason);
|
|
30368
30544
|
if (/install failed|download failed|checksum/.test(r.reason)) installFailed = true;
|
|
30369
|
-
console.log((benign ?
|
|
30545
|
+
console.log((benign ? chalk2.dim("\u2022 ") : chalk2.red("\u2717 ")) + r.reason + chalk2.dim(` (${base2})`));
|
|
30370
30546
|
}
|
|
30371
30547
|
await refreshUpdateCheck2(true);
|
|
30372
30548
|
if (installFailed) {
|
|
30373
|
-
console.log(
|
|
30374
|
-
console.log(
|
|
30549
|
+
console.log(chalk2.dim("\nThe update was available but did not install \u2014 usually npm cannot write the global prefix. Retry with:"));
|
|
30550
|
+
console.log(chalk2.dim(` npm install -g chat-recall (or re-run the installer: curl -fsSL ${targets[0].serverUrl.replace(/\/+$/, "")}/install | sh)`));
|
|
30375
30551
|
}
|
|
30376
30552
|
});
|
|
30377
30553
|
program.hook("preAction", () => {
|