chat-recall 0.5.16 → 0.5.17
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/README.md +29 -4
- package/dist/cli.js +387 -48
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +760 -136
- package/dist/mcp.js.map +1 -1
- package/dist/open-browser.d.ts +7 -0
- package/dist/open-browser.d.ts.map +1 -0
- package/dist/open-browser.js +54 -0
- package/dist/open-browser.js.map +1 -0
- package/dist/scan-worker.js +8 -0
- package/dist/sync-client.d.ts +12 -0
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +66 -4
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +181 -33
- package/package.json +1 -1
- package/skills/chat-recall/SKILL.md +12 -0
- package/skills/chat-recall-project/SKILL.md +11 -0
package/dist/mcp.js
CHANGED
|
@@ -4862,6 +4862,78 @@ var init_project_resolver = __esm({
|
|
|
4862
4862
|
}
|
|
4863
4863
|
});
|
|
4864
4864
|
|
|
4865
|
+
// ../engine/dist/mcp/login-prompt.js
|
|
4866
|
+
var login_prompt_exports = {};
|
|
4867
|
+
__export(login_prompt_exports, {
|
|
4868
|
+
_resetLoginPrompts: () => _resetLoginPrompts,
|
|
4869
|
+
defaultLoginServer: () => defaultLoginServer,
|
|
4870
|
+
hasLoginStarter: () => hasLoginStarter,
|
|
4871
|
+
loginInstruction: () => loginInstruction,
|
|
4872
|
+
setLoginStarter: () => setLoginStarter
|
|
4873
|
+
});
|
|
4874
|
+
function setLoginStarter(fn) {
|
|
4875
|
+
starter = fn;
|
|
4876
|
+
}
|
|
4877
|
+
function hasLoginStarter() {
|
|
4878
|
+
return starter !== null;
|
|
4879
|
+
}
|
|
4880
|
+
function _resetLoginPrompts() {
|
|
4881
|
+
pending.clear();
|
|
4882
|
+
}
|
|
4883
|
+
function defaultLoginServer() {
|
|
4884
|
+
return (process.env.CHAT_RECALL_SERVER || "https://chatrecall.dev").replace(/\/+$/, "");
|
|
4885
|
+
}
|
|
4886
|
+
function loginInstruction(server = defaultLoginServer()) {
|
|
4887
|
+
if (!starter) {
|
|
4888
|
+
return `chat-recall is not logged in, and this host cannot start a login. Install the CLI and log in: \`npm i -g chat-recall && chat-recall login ${server}\`. A free 7-day trial starts as soon as you sign up.`;
|
|
4889
|
+
}
|
|
4890
|
+
const known = pending.get(server);
|
|
4891
|
+
if (known && Date.now() - known.at < PROMPT_TTL_MS) {
|
|
4892
|
+
if (known.prompt)
|
|
4893
|
+
return signInText(known.prompt, server);
|
|
4894
|
+
if (known.error) {
|
|
4895
|
+
return `chat-recall could not start a login automatically (${known.error}). Sign up at ${server} and then run \`chat-recall login ${server}\`.`;
|
|
4896
|
+
}
|
|
4897
|
+
return "chat-recall is starting a sign-in \u2014 call this tool again in a moment and it will show you the link.";
|
|
4898
|
+
}
|
|
4899
|
+
pending.set(server, { at: Date.now(), prompt: null });
|
|
4900
|
+
void starter(server).then((prompt) => {
|
|
4901
|
+
pending.set(server, { at: Date.now(), prompt });
|
|
4902
|
+
}).catch((err) => {
|
|
4903
|
+
pending.set(server, { at: Date.now(), prompt: null, error: err instanceof Error ? err.message : String(err) });
|
|
4904
|
+
});
|
|
4905
|
+
return "chat-recall is not connected yet \u2014 starting a sign-in now. Call this tool again in a moment and it will show you the link to open.";
|
|
4906
|
+
}
|
|
4907
|
+
function signInText(p, server) {
|
|
4908
|
+
return [
|
|
4909
|
+
"**chat-recall needs one sign-in before it can recall anything.**",
|
|
4910
|
+
"",
|
|
4911
|
+
`Open: ${p.url}`,
|
|
4912
|
+
`Code: ${p.userCode}`,
|
|
4913
|
+
"",
|
|
4914
|
+
`(If the link does not carry the code, enter it at ${p.verificationUri}.)`,
|
|
4915
|
+
"",
|
|
4916
|
+
"Sign in with Google or GitHub and a free 7-day trial starts immediately \u2014 no card.",
|
|
4917
|
+
"",
|
|
4918
|
+
"Approving also finishes the setup for you: the chat-recall skills go into your AI",
|
|
4919
|
+
"tools, the MCP is registered with them, and a background service starts shipping new",
|
|
4920
|
+
"conversations. Your existing sessions then sync \u2014 the first pass reports progress, so",
|
|
4921
|
+
'a tool that says "still syncing" is working rather than broken.',
|
|
4922
|
+
"",
|
|
4923
|
+
"Nothing is read or uploaded until you approve. To keep a repo out afterwards:",
|
|
4924
|
+
"`recall_exclude_path`, or `chat-recall exclude project <path>`."
|
|
4925
|
+
].join("\n");
|
|
4926
|
+
}
|
|
4927
|
+
var starter, pending, PROMPT_TTL_MS;
|
|
4928
|
+
var init_login_prompt = __esm({
|
|
4929
|
+
"../engine/dist/mcp/login-prompt.js"() {
|
|
4930
|
+
"use strict";
|
|
4931
|
+
starter = null;
|
|
4932
|
+
pending = /* @__PURE__ */ new Map();
|
|
4933
|
+
PROMPT_TTL_MS = 9 * 60 * 1e3;
|
|
4934
|
+
}
|
|
4935
|
+
});
|
|
4936
|
+
|
|
4865
4937
|
// ../engine/dist/core/tool-backend.js
|
|
4866
4938
|
var tool_backend_exports = {};
|
|
4867
4939
|
__export(tool_backend_exports, {
|
|
@@ -4938,7 +5010,56 @@ var init_tool_backend = __esm({
|
|
|
4938
5010
|
}
|
|
4939
5011
|
});
|
|
4940
5012
|
|
|
5013
|
+
// ../engine/dist/core/project-path-match.js
|
|
5014
|
+
function canonicalProjectPath(p) {
|
|
5015
|
+
return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
5016
|
+
}
|
|
5017
|
+
function projectPathIncludes(candidate, rule) {
|
|
5018
|
+
if (!candidate || !rule)
|
|
5019
|
+
return false;
|
|
5020
|
+
if (candidate.includes(rule))
|
|
5021
|
+
return true;
|
|
5022
|
+
const c = canonicalProjectPath(candidate);
|
|
5023
|
+
const r = canonicalProjectPath(rule);
|
|
5024
|
+
return !!r && c.includes(r);
|
|
5025
|
+
}
|
|
5026
|
+
function projectPathAtOrUnder(candidate, rule) {
|
|
5027
|
+
if (!candidate || !rule)
|
|
5028
|
+
return false;
|
|
5029
|
+
const strip = (s) => s.replace(/\/+$/, "");
|
|
5030
|
+
const cRaw = strip(candidate);
|
|
5031
|
+
const rRaw = strip(rule);
|
|
5032
|
+
if (cRaw === rRaw || cRaw.startsWith(`${rRaw}/`))
|
|
5033
|
+
return true;
|
|
5034
|
+
const c = canonicalProjectPath(candidate);
|
|
5035
|
+
const r = canonicalProjectPath(rule);
|
|
5036
|
+
return !!r && (c === r || c.startsWith(`${r}/`));
|
|
5037
|
+
}
|
|
5038
|
+
var init_project_path_match = __esm({
|
|
5039
|
+
"../engine/dist/core/project-path-match.js"() {
|
|
5040
|
+
"use strict";
|
|
5041
|
+
}
|
|
5042
|
+
});
|
|
5043
|
+
|
|
4941
5044
|
// ../engine/dist/core/settings.js
|
|
5045
|
+
var settings_exports = {};
|
|
5046
|
+
__export(settings_exports, {
|
|
5047
|
+
PERSONAL_DIR_NAMES: () => PERSONAL_DIR_NAMES,
|
|
5048
|
+
PERSONAL_DIR_PATTERNS: () => PERSONAL_DIR_PATTERNS,
|
|
5049
|
+
_resetSecretsCacheForTests: () => _resetSecretsCacheForTests,
|
|
5050
|
+
_resetSettingsCacheForTests: () => _resetSettingsCacheForTests,
|
|
5051
|
+
_resetSourceSettingsCache: () => _resetSourceSettingsCache,
|
|
5052
|
+
applySettingsToEnv: () => applySettingsToEnv,
|
|
5053
|
+
isPersonalPath: () => isPersonalPath,
|
|
5054
|
+
isProjectSyncable: () => isProjectSyncable,
|
|
5055
|
+
isSourceEnabled: () => isSourceEnabled,
|
|
5056
|
+
loadSettings: () => loadSettings,
|
|
5057
|
+
loadSourceSettings: () => loadSourceSettings,
|
|
5058
|
+
mergeSettings: () => mergeSettings,
|
|
5059
|
+
redactSettings: () => redactSettings,
|
|
5060
|
+
saveSettings: () => saveSettings,
|
|
5061
|
+
settingsFilePath: () => settingsFilePath
|
|
5062
|
+
});
|
|
4942
5063
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, chmodSync, statSync as statSync3 } from "fs";
|
|
4943
5064
|
import { delimiter } from "path";
|
|
4944
5065
|
import { dirname as dirname3, join as join3 } from "path";
|
|
@@ -4973,7 +5094,7 @@ function isProjectSyncable(projectId, projectPath, cfg) {
|
|
|
4973
5094
|
if (projectId && cfg.syncOnly.has(projectId))
|
|
4974
5095
|
return true;
|
|
4975
5096
|
for (const x of cfg.syncOnly)
|
|
4976
|
-
if (
|
|
5097
|
+
if (projectPathIncludes(projectPath, x))
|
|
4977
5098
|
return true;
|
|
4978
5099
|
return false;
|
|
4979
5100
|
}
|
|
@@ -5043,6 +5164,12 @@ function defaultSync() {
|
|
|
5043
5164
|
includeProjects: []
|
|
5044
5165
|
};
|
|
5045
5166
|
}
|
|
5167
|
+
function isPersonalPath(projectPath, includeProjects = []) {
|
|
5168
|
+
if (includeProjects.some((inc) => inc && projectPath.includes(inc)))
|
|
5169
|
+
return false;
|
|
5170
|
+
const norm = (projectPath.replace(/\\/g, "/") + "/").toLowerCase();
|
|
5171
|
+
return PERSONAL_DIR_NAMES.some((n) => norm.includes(`/${n.toLowerCase()}/`));
|
|
5172
|
+
}
|
|
5046
5173
|
function defaultTeam() {
|
|
5047
5174
|
return {
|
|
5048
5175
|
enabled: false,
|
|
@@ -5158,9 +5285,15 @@ function isSourceEnabled(tool, source) {
|
|
|
5158
5285
|
return true;
|
|
5159
5286
|
return block[source] !== false;
|
|
5160
5287
|
}
|
|
5288
|
+
function _resetSourceSettingsCache() {
|
|
5289
|
+
_sourceSettingsCache = null;
|
|
5290
|
+
}
|
|
5161
5291
|
function secretsPath() {
|
|
5162
5292
|
return join3(getDataDir(), "secrets.env");
|
|
5163
5293
|
}
|
|
5294
|
+
function _resetSecretsCacheForTests() {
|
|
5295
|
+
_secretsCache = null;
|
|
5296
|
+
}
|
|
5164
5297
|
function loadSecrets() {
|
|
5165
5298
|
const out = {};
|
|
5166
5299
|
const p = secretsPath();
|
|
@@ -5218,6 +5351,9 @@ function applySecretOverlay(result) {
|
|
|
5218
5351
|
}
|
|
5219
5352
|
return result;
|
|
5220
5353
|
}
|
|
5354
|
+
function _resetSettingsCacheForTests() {
|
|
5355
|
+
_settingsCache = null;
|
|
5356
|
+
}
|
|
5221
5357
|
function loadSettings() {
|
|
5222
5358
|
const path = settingsPath();
|
|
5223
5359
|
if (!existsSync3(path))
|
|
@@ -5277,10 +5413,101 @@ function saveSettings(settings2) {
|
|
|
5277
5413
|
} catch {
|
|
5278
5414
|
}
|
|
5279
5415
|
}
|
|
5416
|
+
function applySettingsToEnv(s = loadSettings()) {
|
|
5417
|
+
if (s.embedding.provider && s.embedding.provider !== "none") {
|
|
5418
|
+
process.env.EMBEDDING_PROVIDER = s.embedding.provider;
|
|
5419
|
+
}
|
|
5420
|
+
if (s.embedding.ollamaHost)
|
|
5421
|
+
process.env.OLLAMA_HOST = s.embedding.ollamaHost;
|
|
5422
|
+
if (s.embedding.ollamaCloudApiKey)
|
|
5423
|
+
process.env.OLLAMA_API_KEY = s.embedding.ollamaCloudApiKey;
|
|
5424
|
+
if (s.embedding.geminiApiKey)
|
|
5425
|
+
process.env.GEMINI_API_KEY = s.embedding.geminiApiKey;
|
|
5426
|
+
if (s.embedding.openaiApiKey)
|
|
5427
|
+
process.env.OPENAI_API_KEY = s.embedding.openaiApiKey;
|
|
5428
|
+
if (s.embedding.nvidiaApiKey)
|
|
5429
|
+
process.env.NVIDIA_API_KEY = s.embedding.nvidiaApiKey;
|
|
5430
|
+
if (s.embedding.openaiCompatBaseUrl)
|
|
5431
|
+
process.env.OPENAI_COMPAT_BASE_URL = s.embedding.openaiCompatBaseUrl;
|
|
5432
|
+
if (s.embedding.openaiCompatModel)
|
|
5433
|
+
process.env.OPENAI_COMPAT_MODEL = s.embedding.openaiCompatModel;
|
|
5434
|
+
if (s.embedding.openaiCompatApiKey)
|
|
5435
|
+
process.env.OPENAI_COMPAT_API_KEY = s.embedding.openaiCompatApiKey;
|
|
5436
|
+
if (s.embedding.openaiCompatDimension)
|
|
5437
|
+
process.env.OPENAI_COMPAT_DIMENSION = String(s.embedding.openaiCompatDimension);
|
|
5438
|
+
if (s.summary.provider)
|
|
5439
|
+
process.env.SUMMARY_PROVIDER = s.summary.provider;
|
|
5440
|
+
if (s.summary.cliCommand)
|
|
5441
|
+
process.env.SUMMARY_CLI_CMD = s.summary.cliCommand;
|
|
5442
|
+
if (s.summary.cliPreset)
|
|
5443
|
+
process.env.SUMMARY_CLI_PRESET = s.summary.cliPreset;
|
|
5444
|
+
if (s.summary.cliTimeoutMs)
|
|
5445
|
+
process.env.SUMMARY_CLI_TIMEOUT_MS = String(s.summary.cliTimeoutMs);
|
|
5446
|
+
if (s.summary.anthropicApiKey)
|
|
5447
|
+
process.env.ANTHROPIC_API_KEY = s.summary.anthropicApiKey;
|
|
5448
|
+
if (s.summary.apiBaseUrl)
|
|
5449
|
+
process.env.SUMMARY_API_BASE_URL = s.summary.apiBaseUrl;
|
|
5450
|
+
if (s.summary.apiModel)
|
|
5451
|
+
process.env.SUMMARY_API_MODEL = s.summary.apiModel;
|
|
5452
|
+
if (s.summary.apiKey)
|
|
5453
|
+
process.env.SUMMARY_API_KEY = s.summary.apiKey;
|
|
5454
|
+
}
|
|
5455
|
+
function redactSettings(s) {
|
|
5456
|
+
const mask2 = (v) => v ? "\u2022\u2022\u2022\u2022" + v.slice(-4) : void 0;
|
|
5457
|
+
return {
|
|
5458
|
+
v: s.v,
|
|
5459
|
+
embedding: {
|
|
5460
|
+
...s.embedding,
|
|
5461
|
+
geminiApiKey: mask2(s.embedding.geminiApiKey),
|
|
5462
|
+
openaiApiKey: mask2(s.embedding.openaiApiKey),
|
|
5463
|
+
nvidiaApiKey: mask2(s.embedding.nvidiaApiKey),
|
|
5464
|
+
ollamaCloudApiKey: mask2(s.embedding.ollamaCloudApiKey),
|
|
5465
|
+
openaiCompatApiKey: mask2(s.embedding.openaiCompatApiKey)
|
|
5466
|
+
},
|
|
5467
|
+
summary: {
|
|
5468
|
+
...s.summary,
|
|
5469
|
+
anthropicApiKey: mask2(s.summary.anthropicApiKey),
|
|
5470
|
+
apiKey: mask2(s.summary.apiKey)
|
|
5471
|
+
},
|
|
5472
|
+
// sources/privacy/sync hold no secrets — the sync token is referenced
|
|
5473
|
+
// by env-var name only, never persisted as a value.
|
|
5474
|
+
sources: s.sources,
|
|
5475
|
+
privacy: s.privacy,
|
|
5476
|
+
sync: s.sync,
|
|
5477
|
+
team: s.team
|
|
5478
|
+
};
|
|
5479
|
+
}
|
|
5480
|
+
function mergeSettings(current, update) {
|
|
5481
|
+
const isMask = (v) => typeof v === "string" && v.startsWith("\u2022\u2022\u2022\u2022");
|
|
5482
|
+
const keepIfMasked = (currentVal, newVal) => {
|
|
5483
|
+
if (isMask(newVal))
|
|
5484
|
+
return currentVal;
|
|
5485
|
+
return newVal === void 0 ? currentVal : newVal;
|
|
5486
|
+
};
|
|
5487
|
+
const e = { ...current.embedding, ...update.embedding ?? {} };
|
|
5488
|
+
if (update.embedding) {
|
|
5489
|
+
e.geminiApiKey = keepIfMasked(current.embedding.geminiApiKey, update.embedding.geminiApiKey);
|
|
5490
|
+
e.openaiApiKey = keepIfMasked(current.embedding.openaiApiKey, update.embedding.openaiApiKey);
|
|
5491
|
+
e.nvidiaApiKey = keepIfMasked(current.embedding.nvidiaApiKey, update.embedding.nvidiaApiKey);
|
|
5492
|
+
e.ollamaCloudApiKey = keepIfMasked(current.embedding.ollamaCloudApiKey, update.embedding.ollamaCloudApiKey);
|
|
5493
|
+
e.openaiCompatApiKey = keepIfMasked(current.embedding.openaiCompatApiKey, update.embedding.openaiCompatApiKey);
|
|
5494
|
+
}
|
|
5495
|
+
const sm = { ...current.summary, ...update.summary ?? {} };
|
|
5496
|
+
if (update.summary) {
|
|
5497
|
+
sm.anthropicApiKey = keepIfMasked(current.summary.anthropicApiKey, update.summary.anthropicApiKey);
|
|
5498
|
+
sm.apiKey = keepIfMasked(current.summary.apiKey, update.summary.apiKey);
|
|
5499
|
+
}
|
|
5500
|
+
const sources2 = mergeSources(current.sources, update.sources);
|
|
5501
|
+
const privacy = mergePrivacy(current.privacy, update.privacy);
|
|
5502
|
+
const sync = mergeSync(current.sync, update.sync);
|
|
5503
|
+
const team = mergeTeam(current.team, update.team);
|
|
5504
|
+
return { v: SCHEMA_VERSION, embedding: e, summary: sm, sources: sources2, privacy, sync, team };
|
|
5505
|
+
}
|
|
5280
5506
|
var CLI_DETECTION_ORDER, SCHEMA_VERSION, PERSONAL_DIR_NAMES, PERSONAL_DIR_PATTERNS, _sourceSettingsCache, SECRET_FIELDS, _secretsCache, _settingsCache;
|
|
5281
5507
|
var init_settings = __esm({
|
|
5282
5508
|
"../engine/dist/core/settings.js"() {
|
|
5283
5509
|
"use strict";
|
|
5510
|
+
init_project_path_match();
|
|
5284
5511
|
init_paths();
|
|
5285
5512
|
CLI_DETECTION_ORDER = [
|
|
5286
5513
|
{ preset: "gemini", bin: "gemini", cmd: 'gemini -p " "' },
|
|
@@ -7209,13 +7436,13 @@ async function parseCodexSubagents(sessionPath) {
|
|
|
7209
7436
|
if (meta?.type !== "session_meta")
|
|
7210
7437
|
continue;
|
|
7211
7438
|
const payload = meta.payload || {};
|
|
7212
|
-
const
|
|
7213
|
-
if (!
|
|
7439
|
+
const spawn4 = payload.source?.subagent?.thread_spawn;
|
|
7440
|
+
if (!spawn4)
|
|
7214
7441
|
continue;
|
|
7215
|
-
if (
|
|
7442
|
+
if (spawn4.parent_thread_id !== parentId)
|
|
7216
7443
|
continue;
|
|
7217
7444
|
const id = fileName.replace(/\.jsonl$/i, "");
|
|
7218
|
-
const role = String(
|
|
7445
|
+
const role = String(spawn4.agent_role || payload.agent_role || "").toLowerCase();
|
|
7219
7446
|
const kind = role.includes("compact") ? "compact" : role.includes("aside") ? "aside" : role.includes("worker") || role.includes("explore") ? "explore" : "other";
|
|
7220
7447
|
let messages = [];
|
|
7221
7448
|
try {
|
|
@@ -7227,8 +7454,8 @@ async function parseCodexSubagents(sessionPath) {
|
|
|
7227
7454
|
subagents.push({
|
|
7228
7455
|
id,
|
|
7229
7456
|
kind,
|
|
7230
|
-
agentType:
|
|
7231
|
-
description:
|
|
7457
|
+
agentType: spawn4.agent_role || payload.agent_role,
|
|
7458
|
+
description: spawn4.agent_nickname || payload.agent_nickname,
|
|
7232
7459
|
filePath,
|
|
7233
7460
|
messageCount: messages.length,
|
|
7234
7461
|
toolUseCount,
|
|
@@ -11160,8 +11387,8 @@ var init_codex2 = __esm({
|
|
|
11160
11387
|
} catch {
|
|
11161
11388
|
continue;
|
|
11162
11389
|
}
|
|
11163
|
-
const
|
|
11164
|
-
if (!
|
|
11390
|
+
const spawn4 = meta?.payload?.source?.subagent?.thread_spawn;
|
|
11391
|
+
if (!spawn4 || spawn4.parent_thread_id !== parentUuid)
|
|
11165
11392
|
continue;
|
|
11166
11393
|
events.push(...this.parseRollout(path));
|
|
11167
11394
|
}
|
|
@@ -11354,10 +11581,10 @@ var init_codex2 = __esm({
|
|
|
11354
11581
|
const meta = JSON.parse(first);
|
|
11355
11582
|
const payload = meta?.payload || {};
|
|
11356
11583
|
projectPath = payload.cwd || "";
|
|
11357
|
-
const
|
|
11358
|
-
if (
|
|
11584
|
+
const spawn4 = payload.source?.subagent?.thread_spawn;
|
|
11585
|
+
if (spawn4 || payload.agent_role || payload.agent_nickname) {
|
|
11359
11586
|
isSubagent = true;
|
|
11360
|
-
parentId =
|
|
11587
|
+
parentId = spawn4?.parent_thread_id || null;
|
|
11361
11588
|
}
|
|
11362
11589
|
}
|
|
11363
11590
|
} catch {
|
|
@@ -13339,12 +13566,55 @@ var init_collector_health = __esm({
|
|
|
13339
13566
|
}
|
|
13340
13567
|
});
|
|
13341
13568
|
|
|
13569
|
+
// ../engine/dist/types/code-intel.js
|
|
13570
|
+
import { createHash as createHash6 } from "crypto";
|
|
13571
|
+
function shortHash(s) {
|
|
13572
|
+
return createHash6("sha256").update(s).digest("hex").slice(0, 16);
|
|
13573
|
+
}
|
|
13574
|
+
function snippetKey(snippet) {
|
|
13575
|
+
return (snippet ?? "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
13576
|
+
}
|
|
13577
|
+
function codeFindingIds(projectId, findings) {
|
|
13578
|
+
const groups = /* @__PURE__ */ new Map();
|
|
13579
|
+
findings.forEach((f, i) => {
|
|
13580
|
+
const key2 = [f.category, f.file, f.rule, identityTitle(f.title ?? ""), snippetKey(f.snippet)].join("|");
|
|
13581
|
+
const g = groups.get(key2);
|
|
13582
|
+
if (g)
|
|
13583
|
+
g.push(i);
|
|
13584
|
+
else
|
|
13585
|
+
groups.set(key2, [i]);
|
|
13586
|
+
});
|
|
13587
|
+
const lineOf = (i) => findings[i].line ?? -1;
|
|
13588
|
+
const ids = new Array(findings.length);
|
|
13589
|
+
for (const [key2, idxs] of groups) {
|
|
13590
|
+
idxs.sort((a, b) => lineOf(a) - lineOf(b));
|
|
13591
|
+
let ordinal = -1;
|
|
13592
|
+
let prevLine = Number.NaN;
|
|
13593
|
+
for (const idx of idxs) {
|
|
13594
|
+
if (lineOf(idx) !== prevLine) {
|
|
13595
|
+
ordinal++;
|
|
13596
|
+
prevLine = lineOf(idx);
|
|
13597
|
+
}
|
|
13598
|
+
ids[idx] = "cf_" + shortHash([projectId, key2, ordinal].join("|"));
|
|
13599
|
+
}
|
|
13600
|
+
}
|
|
13601
|
+
return ids;
|
|
13602
|
+
}
|
|
13603
|
+
function identityTitle(title) {
|
|
13604
|
+
return title.replace(/\d+/g, "#");
|
|
13605
|
+
}
|
|
13606
|
+
var init_code_intel = __esm({
|
|
13607
|
+
"../engine/dist/types/code-intel.js"() {
|
|
13608
|
+
"use strict";
|
|
13609
|
+
}
|
|
13610
|
+
});
|
|
13611
|
+
|
|
13342
13612
|
// ../engine/dist/core/companions.js
|
|
13343
13613
|
import { existsSync as existsSync20, mkdirSync as mkdirSync6, statSync as statSync17, chmodSync as chmodSync3, unlinkSync, renameSync as renameSync3, createWriteStream, readFileSync as readFileSync14, writeFileSync as writeFileSync4 } from "node:fs";
|
|
13344
13614
|
import { homedir as homedir5 } from "node:os";
|
|
13345
13615
|
import { join as join22 } from "node:path";
|
|
13346
13616
|
import { execSync as execSync2 } from "node:child_process";
|
|
13347
|
-
import { createHash as
|
|
13617
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
13348
13618
|
function expectedCodeindexSha256(artifact) {
|
|
13349
13619
|
return CODEINDEX_SHA256[artifact.replace(/^codeindex-/, "").replace(/\.exe$/, "")] ?? null;
|
|
13350
13620
|
}
|
|
@@ -13418,7 +13688,7 @@ async function downloadArtifact(artifact, dest) {
|
|
|
13418
13688
|
if (!res.body)
|
|
13419
13689
|
throw new Error("Download returned empty body");
|
|
13420
13690
|
const file = createWriteStream(dest);
|
|
13421
|
-
await new Promise((
|
|
13691
|
+
await new Promise((resolve3, reject) => {
|
|
13422
13692
|
const reader = res.body.getReader();
|
|
13423
13693
|
const pump = async () => {
|
|
13424
13694
|
try {
|
|
@@ -13429,7 +13699,7 @@ async function downloadArtifact(artifact, dest) {
|
|
|
13429
13699
|
file.write(value);
|
|
13430
13700
|
}
|
|
13431
13701
|
file.end();
|
|
13432
|
-
file.on("finish", () =>
|
|
13702
|
+
file.on("finish", () => resolve3());
|
|
13433
13703
|
file.on("error", reject);
|
|
13434
13704
|
} catch (e) {
|
|
13435
13705
|
reject(e);
|
|
@@ -13452,7 +13722,7 @@ async function installCodeindex(opts = {}) {
|
|
|
13452
13722
|
if (!expected) {
|
|
13453
13723
|
throw new Error(`No pinned checksum for ${status.artifactName} \u2014 refusing to install an unverified binary. Add its sha256 to CODEINDEX_SHA256 in companions.ts.`);
|
|
13454
13724
|
}
|
|
13455
|
-
const actual =
|
|
13725
|
+
const actual = createHash7("sha256").update(readFileSync14(tmpPath)).digest("hex");
|
|
13456
13726
|
if (actual !== expected) {
|
|
13457
13727
|
throw new Error(`Checksum mismatch for ${status.artifactName}.
|
|
13458
13728
|
expected ${expected}
|
|
@@ -13493,7 +13763,7 @@ var init_companions = __esm({
|
|
|
13493
13763
|
});
|
|
13494
13764
|
|
|
13495
13765
|
// ../engine/dist/core/detector-install.js
|
|
13496
|
-
import { createHash as
|
|
13766
|
+
import { createHash as createHash8 } from "crypto";
|
|
13497
13767
|
import { existsSync as existsSync21, mkdirSync as mkdirSync7, mkdtempSync, chmodSync as chmodSync4, renameSync as renameSync4, rmSync as rmSync2, writeFileSync as writeFileSync5, readFileSync as readFileSync15, statSync as statSync18, copyFileSync as copyFileSync2 } from "fs";
|
|
13498
13768
|
import { join as join23 } from "path";
|
|
13499
13769
|
function managedDetectorPath(name) {
|
|
@@ -13503,7 +13773,7 @@ function receiptPath(name) {
|
|
|
13503
13773
|
return join23(getDetectorBinDir(), `${name}.receipt.json`);
|
|
13504
13774
|
}
|
|
13505
13775
|
function sha256File(path) {
|
|
13506
|
-
return
|
|
13776
|
+
return createHash8("sha256").update(readFileSync15(path)).digest("hex");
|
|
13507
13777
|
}
|
|
13508
13778
|
function readReceipt(name) {
|
|
13509
13779
|
try {
|
|
@@ -13957,10 +14227,10 @@ async function resolveCodeindexBin(autoInstall = true) {
|
|
|
13957
14227
|
return st.path;
|
|
13958
14228
|
}
|
|
13959
14229
|
function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
|
|
13960
|
-
return new Promise((
|
|
14230
|
+
return new Promise((resolve3, reject) => {
|
|
13961
14231
|
const p = spawn(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
|
|
13962
14232
|
const res = /* @__PURE__ */ new Map();
|
|
13963
|
-
let
|
|
14233
|
+
let pending2 = "";
|
|
13964
14234
|
let failed = false;
|
|
13965
14235
|
const timer = setTimeout(() => {
|
|
13966
14236
|
try {
|
|
@@ -13987,16 +14257,16 @@ function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
|
|
|
13987
14257
|
p.stdout.on("data", (d) => {
|
|
13988
14258
|
if (failed)
|
|
13989
14259
|
return;
|
|
13990
|
-
|
|
13991
|
-
let nl =
|
|
14260
|
+
pending2 += d;
|
|
14261
|
+
let nl = pending2.indexOf("\n");
|
|
13992
14262
|
while (nl !== -1) {
|
|
13993
|
-
takeLine(
|
|
13994
|
-
|
|
13995
|
-
nl =
|
|
14263
|
+
takeLine(pending2.slice(0, nl));
|
|
14264
|
+
pending2 = pending2.slice(nl + 1);
|
|
14265
|
+
nl = pending2.indexOf("\n");
|
|
13996
14266
|
}
|
|
13997
|
-
if (
|
|
14267
|
+
if (pending2.length > MCP_OUT_MAX_BYTES) {
|
|
13998
14268
|
failed = true;
|
|
13999
|
-
|
|
14269
|
+
pending2 = "";
|
|
14000
14270
|
clearTimeout(timer);
|
|
14001
14271
|
try {
|
|
14002
14272
|
p.kill("SIGKILL");
|
|
@@ -14013,9 +14283,9 @@ function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
|
|
|
14013
14283
|
clearTimeout(timer);
|
|
14014
14284
|
if (failed)
|
|
14015
14285
|
return;
|
|
14016
|
-
if (
|
|
14017
|
-
takeLine(
|
|
14018
|
-
|
|
14286
|
+
if (pending2)
|
|
14287
|
+
takeLine(pending2);
|
|
14288
|
+
resolve3(res);
|
|
14019
14289
|
});
|
|
14020
14290
|
p.stdin.write(calls.map((c) => JSON.stringify(c) + "\n").join(""));
|
|
14021
14291
|
p.stdin.end();
|
|
@@ -14181,6 +14451,11 @@ async function collectCode(opts) {
|
|
|
14181
14451
|
}
|
|
14182
14452
|
}
|
|
14183
14453
|
const findings = [];
|
|
14454
|
+
const coveredBySecurityRule = /* @__PURE__ */ new Map();
|
|
14455
|
+
const coveredByCloneGroup = /* @__PURE__ */ new Map();
|
|
14456
|
+
const coveredByDupCluster = /* @__PURE__ */ new Map();
|
|
14457
|
+
const coveredByGodModule = /* @__PURE__ */ new Map();
|
|
14458
|
+
const coveredByCycle = /* @__PURE__ */ new Map();
|
|
14184
14459
|
const sevFromLiteral = (cat) => cat === "secret_suspect" ? "medium" : cat === "todo_marker" ? "info" : "low";
|
|
14185
14460
|
const securityRows = [];
|
|
14186
14461
|
const heuristicSec = [];
|
|
@@ -14230,7 +14505,7 @@ async function collectCode(opts) {
|
|
|
14230
14505
|
log6(`security: repo secret scan unavailable (${e instanceof Error ? e.message : e}) \u2014 install gitleaks/trufflehog for secret coverage`);
|
|
14231
14506
|
}
|
|
14232
14507
|
for (const f of [...securityRows, ...heuristicSec]) {
|
|
14233
|
-
|
|
14508
|
+
const finding = {
|
|
14234
14509
|
category: "security",
|
|
14235
14510
|
severity: f.severity,
|
|
14236
14511
|
file: f.file,
|
|
@@ -14240,7 +14515,13 @@ async function collectCode(opts) {
|
|
|
14240
14515
|
snippet: f.snippet,
|
|
14241
14516
|
why: FINDING_WHY.security,
|
|
14242
14517
|
agentPrompt: securityPrompt(f.rule, f.severity, f.file, f.line)
|
|
14243
|
-
}
|
|
14518
|
+
};
|
|
14519
|
+
findings.push(finding);
|
|
14520
|
+
const byRuleList = coveredBySecurityRule.get(f.rule);
|
|
14521
|
+
if (byRuleList)
|
|
14522
|
+
byRuleList.push(finding);
|
|
14523
|
+
else
|
|
14524
|
+
coveredBySecurityRule.set(f.rule, [finding]);
|
|
14244
14525
|
}
|
|
14245
14526
|
for (const f of (lit?.findings ?? []).slice(0, 150)) {
|
|
14246
14527
|
const file = rel(f.file);
|
|
@@ -14262,7 +14543,7 @@ async function collectCode(opts) {
|
|
|
14262
14543
|
const names = [...new Set(fns.map((x) => x.name))];
|
|
14263
14544
|
const fileList = fns.map((x) => rel(x.file)).slice(0, 6);
|
|
14264
14545
|
const heavy = (g.lines ?? 0) * (g.count ?? 0) >= 150;
|
|
14265
|
-
|
|
14546
|
+
const finding = {
|
|
14266
14547
|
category: "clone",
|
|
14267
14548
|
severity: heavy ? "high" : "medium",
|
|
14268
14549
|
file: fileList[0] ?? "",
|
|
@@ -14272,10 +14553,12 @@ async function collectCode(opts) {
|
|
|
14272
14553
|
snippet: `${g.lines} lines, ${g.count} copies`,
|
|
14273
14554
|
why: FINDING_WHY.clone,
|
|
14274
14555
|
agentPrompt: clonePrompt(names, g.lines ?? 0, g.count ?? 0, fileList)
|
|
14275
|
-
}
|
|
14556
|
+
};
|
|
14557
|
+
findings.push(finding);
|
|
14558
|
+
coveredByCloneGroup.set(g, finding);
|
|
14276
14559
|
}
|
|
14277
14560
|
for (const c of (dup?.clusters ?? []).slice(0, 40)) {
|
|
14278
|
-
|
|
14561
|
+
const finding = {
|
|
14279
14562
|
category: "duplication",
|
|
14280
14563
|
severity: "low",
|
|
14281
14564
|
file: c.name ?? "",
|
|
@@ -14285,7 +14568,9 @@ async function collectCode(opts) {
|
|
|
14285
14568
|
snippet: `${c.kind}, ${c.count} files`,
|
|
14286
14569
|
why: FINDING_WHY.duplication,
|
|
14287
14570
|
agentPrompt: duplicationPrompt(c.name, c.kind ?? "", c.count ?? 0)
|
|
14288
|
-
}
|
|
14571
|
+
};
|
|
14572
|
+
findings.push(finding);
|
|
14573
|
+
coveredByDupCluster.set(c, finding);
|
|
14289
14574
|
}
|
|
14290
14575
|
for (const s of (dead?.symbols ?? []).slice(0, 120)) {
|
|
14291
14576
|
const file = rel(s.file);
|
|
@@ -14303,7 +14588,7 @@ async function collectCode(opts) {
|
|
|
14303
14588
|
}
|
|
14304
14589
|
for (const m of (coup?.god_modules ?? []).slice(0, 15)) {
|
|
14305
14590
|
const file = rel(m.file);
|
|
14306
|
-
|
|
14591
|
+
const finding = {
|
|
14307
14592
|
category: "coupling",
|
|
14308
14593
|
severity: "medium",
|
|
14309
14594
|
file,
|
|
@@ -14313,11 +14598,13 @@ async function collectCode(opts) {
|
|
|
14313
14598
|
snippet: `fan-in ${m.fan_in}, fan-out ${m.fan_out}`,
|
|
14314
14599
|
why: FINDING_WHY.coupling,
|
|
14315
14600
|
agentPrompt: couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100)
|
|
14316
|
-
}
|
|
14601
|
+
};
|
|
14602
|
+
findings.push(finding);
|
|
14603
|
+
coveredByGodModule.set(file, finding);
|
|
14317
14604
|
}
|
|
14318
14605
|
for (const c of (cyc?.cycles ?? []).slice(0, 25)) {
|
|
14319
14606
|
const chain = (c.files ?? []).map(rel);
|
|
14320
|
-
|
|
14607
|
+
const finding = {
|
|
14321
14608
|
category: "cycle",
|
|
14322
14609
|
severity: "medium",
|
|
14323
14610
|
file: chain[0] ?? "",
|
|
@@ -14327,7 +14614,9 @@ async function collectCode(opts) {
|
|
|
14327
14614
|
snippet: chain.slice(0, 4).join(" \u2192 "),
|
|
14328
14615
|
why: FINDING_WHY.cycle,
|
|
14329
14616
|
agentPrompt: cyclePrompt(chain.slice(0, 6))
|
|
14330
|
-
}
|
|
14617
|
+
};
|
|
14618
|
+
findings.push(finding);
|
|
14619
|
+
coveredByCycle.set(c, finding);
|
|
14331
14620
|
}
|
|
14332
14621
|
for (const f of (unwrap?.findings ?? []).slice(0, 150)) {
|
|
14333
14622
|
const file = rel(f.file);
|
|
@@ -14556,7 +14845,13 @@ async function collectCode(opts) {
|
|
|
14556
14845
|
}
|
|
14557
14846
|
log6("synthesising action plan \u2026");
|
|
14558
14847
|
const actions = [];
|
|
14559
|
-
const
|
|
14848
|
+
const findingIdOf = /* @__PURE__ */ new Map();
|
|
14849
|
+
{
|
|
14850
|
+
const ids = codeFindingIds(projectId, findings);
|
|
14851
|
+
findings.forEach((f, i) => findingIdOf.set(f, ids[i]));
|
|
14852
|
+
}
|
|
14853
|
+
const coversOf = (fs) => [...new Set(fs.map((f) => f ? findingIdOf.get(f) : void 0).filter((id) => !!id))];
|
|
14854
|
+
const pushAction = (pri, category, title, fix, loc, agentPrompt, covers = []) => actions.push({ pri, category, title, fix, loc, agentPrompt, covers });
|
|
14560
14855
|
const byRule = /* @__PURE__ */ new Map();
|
|
14561
14856
|
for (const f of securityRows) {
|
|
14562
14857
|
if (isTestOrExampleFile(f.file))
|
|
@@ -14568,7 +14863,17 @@ async function collectCode(opts) {
|
|
|
14568
14863
|
for (const [rule, hits] of byRule) {
|
|
14569
14864
|
const sev = hits.some((h) => h.sev === "critical") ? "critical" : hits.some((h) => h.sev === "high") ? "high" : "medium";
|
|
14570
14865
|
const pri = sev === "critical" ? 0 : sev === "high" ? 1 : 2;
|
|
14571
|
-
pushAction(
|
|
14866
|
+
pushAction(
|
|
14867
|
+
pri,
|
|
14868
|
+
"security",
|
|
14869
|
+
`${rule} \u2014 ${hits.length} occurrence(s)`,
|
|
14870
|
+
secFix(rule),
|
|
14871
|
+
hits.slice(0, 4).map((h) => ({ file: h.file, line: h.line })),
|
|
14872
|
+
securityPrompt(rule, sev, hits[0].file, hits[0].line),
|
|
14873
|
+
// Every security finding on this rule. The card IS the roll-up, so the
|
|
14874
|
+
// members must not each file one of their own.
|
|
14875
|
+
coversOf(coveredBySecurityRule.get(rule) ?? [])
|
|
14876
|
+
);
|
|
14572
14877
|
}
|
|
14573
14878
|
for (const g of clones?.groups ?? []) {
|
|
14574
14879
|
if ((g.count ?? 0) >= 3 || (g.lines ?? 0) >= 12) {
|
|
@@ -14577,7 +14882,7 @@ async function collectCode(opts) {
|
|
|
14577
14882
|
continue;
|
|
14578
14883
|
const names = [...new Set(fns.map((x) => x.name))];
|
|
14579
14884
|
const locs = fns.map((x) => rel(x.file)).slice(0, 5);
|
|
14580
|
-
pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs));
|
|
14885
|
+
pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs), coversOf([coveredByCloneGroup.get(g)]));
|
|
14581
14886
|
}
|
|
14582
14887
|
}
|
|
14583
14888
|
for (const c of (dup?.clusters ?? []).slice(0, 12)) {
|
|
@@ -14589,19 +14894,19 @@ async function collectCode(opts) {
|
|
|
14589
14894
|
continue;
|
|
14590
14895
|
if (kind === "type_alias" || kind === "interface" || kind === "enum" || kind === "type")
|
|
14591
14896
|
continue;
|
|
14592
|
-
pushAction(2, "reuse", `${nm} (${c.kind}) reimplemented in ${c.count} files`, "Consolidate into a single shared utility/module.", [], duplicationPrompt(nm, c.kind ?? "", c.count));
|
|
14897
|
+
pushAction(2, "reuse", `${nm} (${c.kind}) reimplemented in ${c.count} files`, "Consolidate into a single shared utility/module.", [], duplicationPrompt(nm, c.kind ?? "", c.count), coversOf([coveredByDupCluster.get(c)]));
|
|
14593
14898
|
}
|
|
14594
14899
|
for (const m of (coup?.god_modules ?? []).slice(0, 5)) {
|
|
14595
14900
|
const file = rel(m.file);
|
|
14596
14901
|
if (isGenerated(file))
|
|
14597
14902
|
continue;
|
|
14598
|
-
pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100));
|
|
14903
|
+
pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100), coversOf([coveredByGodModule.get(file)]));
|
|
14599
14904
|
}
|
|
14600
14905
|
for (const c of (cyc?.cycles ?? []).slice(0, 5)) {
|
|
14601
14906
|
const chain = (c.files ?? []).map(rel).filter((f) => !isGenerated(f));
|
|
14602
14907
|
if (chain.length < 2)
|
|
14603
14908
|
continue;
|
|
14604
|
-
pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)));
|
|
14909
|
+
pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)), coversOf([coveredByCycle.get(c)]));
|
|
14605
14910
|
}
|
|
14606
14911
|
for (const h of hotspotsAll.slice(0, 5)) {
|
|
14607
14912
|
pushAction(2, "stability", `Hotspot ${h.file} (${h.churn}\xD7 changes, complexity ${h.complexity})`, "Add regression tests and refactor into smaller functions before the next change.", [{ file: h.file }], hotspotPrompt(h.file, h.churn, h.complexity, h.suggestion));
|
|
@@ -14714,11 +15019,12 @@ var init_collector = __esm({
|
|
|
14714
15019
|
"../engine/dist/core/code/collector.js"() {
|
|
14715
15020
|
"use strict";
|
|
14716
15021
|
init_project_resolver();
|
|
15022
|
+
init_code_intel();
|
|
14717
15023
|
init_companions();
|
|
14718
15024
|
init_secret_redactor();
|
|
14719
15025
|
init_secret_scanner();
|
|
14720
15026
|
init_prompts();
|
|
14721
|
-
COLLECTOR_VERSION =
|
|
15027
|
+
COLLECTOR_VERSION = 3;
|
|
14722
15028
|
ANALYSES = [
|
|
14723
15029
|
"health",
|
|
14724
15030
|
"security",
|
|
@@ -14840,11 +15146,11 @@ var init_collector = __esm({
|
|
|
14840
15146
|
});
|
|
14841
15147
|
|
|
14842
15148
|
// ../engine/dist/core/source-discovery.js
|
|
14843
|
-
import { createHash as
|
|
15149
|
+
import { createHash as createHash9 } from "crypto";
|
|
14844
15150
|
import { existsSync as existsSync24, readdirSync as readdirSync13, statSync as statSync20 } from "fs";
|
|
14845
15151
|
import { join as join27 } from "path";
|
|
14846
15152
|
function sourceId(path) {
|
|
14847
|
-
return "src_" +
|
|
15153
|
+
return "src_" + createHash9("sha256").update(path).digest("hex").slice(0, 12);
|
|
14848
15154
|
}
|
|
14849
15155
|
function installSourceExclusions(ids) {
|
|
14850
15156
|
excludedIds = new Set((ids || []).filter((s) => typeof s === "string" && SOURCE_ID_RE.test(s)));
|
|
@@ -14949,10 +15255,10 @@ var init_source_discovery = __esm({
|
|
|
14949
15255
|
// ../engine/dist/core/home-discovery.js
|
|
14950
15256
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
14951
15257
|
import { existsSync as existsSync25, readdirSync as readdirSync14, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync21 } from "fs";
|
|
14952
|
-
import { homedir as
|
|
15258
|
+
import { homedir as homedir7, platform as osPlatform } from "os";
|
|
14953
15259
|
import { join as join28 } from "path";
|
|
14954
15260
|
function candidateRoots(plat = osPlatform(), env = process.env) {
|
|
14955
|
-
const home =
|
|
15261
|
+
const home = homedir7();
|
|
14956
15262
|
const roots = [home];
|
|
14957
15263
|
const add = (p) => {
|
|
14958
15264
|
if (p && !roots.includes(p))
|
|
@@ -15070,7 +15376,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
|
|
|
15070
15376
|
}
|
|
15071
15377
|
return out;
|
|
15072
15378
|
}
|
|
15073
|
-
const home =
|
|
15379
|
+
const home = homedir7();
|
|
15074
15380
|
const rcs = [".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile"].map((f) => join28(home, f)).concat([join28(home, ".config", "fish", "config.fish")]);
|
|
15075
15381
|
for (const rc of rcs) {
|
|
15076
15382
|
let text;
|
|
@@ -16040,7 +16346,7 @@ function matchesProjectList(projectPath, patterns) {
|
|
|
16040
16346
|
const tail = projectPath.slice(parent.length + 1);
|
|
16041
16347
|
if (projectPath.startsWith(parent + "/") && !tail.includes("/"))
|
|
16042
16348
|
return true;
|
|
16043
|
-
} else if (projectPath
|
|
16349
|
+
} else if (projectPathAtOrUnder(projectPath, p)) {
|
|
16044
16350
|
return true;
|
|
16045
16351
|
}
|
|
16046
16352
|
}
|
|
@@ -16066,6 +16372,7 @@ var cache2, TOOLS;
|
|
|
16066
16372
|
var init_source_policy = __esm({
|
|
16067
16373
|
"../engine/dist/core/source-policy.js"() {
|
|
16068
16374
|
"use strict";
|
|
16375
|
+
init_project_path_match();
|
|
16069
16376
|
init_settings();
|
|
16070
16377
|
cache2 = null;
|
|
16071
16378
|
TOOLS = ["claude", "gemini", "opencode", "codex", "agy", "cursor"];
|
|
@@ -16269,7 +16576,7 @@ var init_session_source = __esm({
|
|
|
16269
16576
|
|
|
16270
16577
|
// ../engine/dist/core/utils.js
|
|
16271
16578
|
import { existsSync as existsSync26, readdirSync as readdirSync15 } from "fs";
|
|
16272
|
-
import { homedir as
|
|
16579
|
+
import { homedir as homedir8 } from "os";
|
|
16273
16580
|
import { join as join29 } from "path";
|
|
16274
16581
|
function splitByHeaders(markdown) {
|
|
16275
16582
|
const lines = markdown.split("\n");
|
|
@@ -16300,7 +16607,7 @@ function splitByHeaders(markdown) {
|
|
|
16300
16607
|
return sections;
|
|
16301
16608
|
}
|
|
16302
16609
|
function getClaudeDirs() {
|
|
16303
|
-
const home =
|
|
16610
|
+
const home = homedir8();
|
|
16304
16611
|
const dirs = [];
|
|
16305
16612
|
const seen = /* @__PURE__ */ new Set();
|
|
16306
16613
|
const add = (dir) => {
|
|
@@ -16340,7 +16647,7 @@ function discoverSubdirs(subdir) {
|
|
|
16340
16647
|
result.push(path);
|
|
16341
16648
|
}
|
|
16342
16649
|
}
|
|
16343
|
-
return result.length > 0 ? result : [join29(
|
|
16650
|
+
return result.length > 0 ? result : [join29(homedir8(), ".claude", subdir)];
|
|
16344
16651
|
}
|
|
16345
16652
|
var init_utils = __esm({
|
|
16346
16653
|
"../engine/dist/core/utils.js"() {
|
|
@@ -16350,9 +16657,9 @@ var init_utils = __esm({
|
|
|
16350
16657
|
|
|
16351
16658
|
// ../engine/dist/parsers/plan-source.js
|
|
16352
16659
|
import { existsSync as existsSync27, readdirSync as readdirSync16, readFileSync as readFileSync20, statSync as statSync24 } from "fs";
|
|
16353
|
-
import { homedir as
|
|
16660
|
+
import { homedir as homedir9 } from "os";
|
|
16354
16661
|
import { join as join30, basename as basename14 } from "path";
|
|
16355
|
-
import { createHash as
|
|
16662
|
+
import { createHash as createHash10 } from "crypto";
|
|
16356
16663
|
function parseFrontmatter(content) {
|
|
16357
16664
|
if (!content.startsWith("---"))
|
|
16358
16665
|
return { body: content };
|
|
@@ -16528,7 +16835,7 @@ var init_plan_source = __esm({
|
|
|
16528
16835
|
try {
|
|
16529
16836
|
const data = JSON.parse(readFileSync20(projectsPath, "utf-8"));
|
|
16530
16837
|
for (const path of Object.keys(data.projects || {})) {
|
|
16531
|
-
projMap.set(
|
|
16838
|
+
projMap.set(createHash10("sha256").update(path).digest("hex"), path);
|
|
16532
16839
|
}
|
|
16533
16840
|
} catch {
|
|
16534
16841
|
}
|
|
@@ -16592,7 +16899,7 @@ var init_plan_source = __esm({
|
|
|
16592
16899
|
for (const match of content.matchAll(fullPathPattern)) {
|
|
16593
16900
|
let path = (match[1] || "").trim();
|
|
16594
16901
|
if (path.startsWith("~/")) {
|
|
16595
|
-
path = join30(
|
|
16902
|
+
path = join30(homedir9(), path.slice(2));
|
|
16596
16903
|
}
|
|
16597
16904
|
for (const projectDir of knownProjects) {
|
|
16598
16905
|
if (path.startsWith(projectDir)) {
|
|
@@ -17051,9 +17358,9 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
|
|
|
17051
17358
|
|
|
17052
17359
|
// ../engine/dist/parsers/claude-md-source.js
|
|
17053
17360
|
import { existsSync as existsSync29, readFileSync as readFileSync22, readdirSync as readdirSync18, statSync as statSync26 } from "fs";
|
|
17054
|
-
import { homedir as
|
|
17361
|
+
import { homedir as homedir10 } from "os";
|
|
17055
17362
|
import { join as join32, basename as basename15, dirname as dirname15 } from "path";
|
|
17056
|
-
import { createHash as
|
|
17363
|
+
import { createHash as createHash11 } from "crypto";
|
|
17057
17364
|
function discoverProjectDirs(claudeDir) {
|
|
17058
17365
|
const dirs = [];
|
|
17059
17366
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -17081,7 +17388,7 @@ function discoverProjectDirs(claudeDir) {
|
|
|
17081
17388
|
scanProjectsDir(join32(claudeDir, "projects"));
|
|
17082
17389
|
return dirs;
|
|
17083
17390
|
}
|
|
17084
|
-
const home =
|
|
17391
|
+
const home = homedir10();
|
|
17085
17392
|
scanProjectsDir(claudeBackend.projectsDir());
|
|
17086
17393
|
try {
|
|
17087
17394
|
for (const entry of readdirSync18(home, { withFileTypes: true })) {
|
|
@@ -17115,7 +17422,7 @@ function discoverProjectDirs(claudeDir) {
|
|
|
17115
17422
|
} catch {
|
|
17116
17423
|
}
|
|
17117
17424
|
}
|
|
17118
|
-
void
|
|
17425
|
+
void createHash11;
|
|
17119
17426
|
return dirs;
|
|
17120
17427
|
}
|
|
17121
17428
|
var MAX_CHUNK_CHARS3, NOTE_FILES, ClaudeMdSource;
|
|
@@ -17150,7 +17457,7 @@ var init_claude_md_source = __esm({
|
|
|
17150
17457
|
const seen = /* @__PURE__ */ new Set();
|
|
17151
17458
|
const seenKey = (path, tool) => `${tool}:${path}`;
|
|
17152
17459
|
for (const note of NOTE_FILES) {
|
|
17153
|
-
const p = join32(
|
|
17460
|
+
const p = join32(homedir10(), note.filename);
|
|
17154
17461
|
const key2 = seenKey(p, note.tool);
|
|
17155
17462
|
if (existsSync29(p) && !seen.has(key2)) {
|
|
17156
17463
|
seen.add(key2);
|
|
@@ -17277,7 +17584,7 @@ var init_claude_md_source = __esm({
|
|
|
17277
17584
|
|
|
17278
17585
|
// ../engine/dist/parsers/agent-memory-source.js
|
|
17279
17586
|
import { existsSync as existsSync30, readFileSync as readFileSync23, readdirSync as readdirSync19, statSync as statSync27 } from "fs";
|
|
17280
|
-
import { homedir as
|
|
17587
|
+
import { homedir as homedir11 } from "os";
|
|
17281
17588
|
import { join as join33, basename as basename16 } from "path";
|
|
17282
17589
|
function parseFrontmatter2(content) {
|
|
17283
17590
|
const fm = {};
|
|
@@ -17315,7 +17622,7 @@ function discoverMemoryDirs() {
|
|
|
17315
17622
|
out.push({ memoryDir, projectHash });
|
|
17316
17623
|
}
|
|
17317
17624
|
};
|
|
17318
|
-
const home =
|
|
17625
|
+
const home = homedir11();
|
|
17319
17626
|
const claudeRoots = [claudeBackend.projectsDir()];
|
|
17320
17627
|
try {
|
|
17321
17628
|
for (const entry of readdirSync19(home, { withFileTypes: true })) {
|
|
@@ -17464,7 +17771,7 @@ var init_agent_memory_source = __esm({
|
|
|
17464
17771
|
import { existsSync as existsSync31, readdirSync as readdirSync20, statSync as statSync28 } from "fs";
|
|
17465
17772
|
import { createReadStream as createReadStream3 } from "fs";
|
|
17466
17773
|
import { createInterface as createInterface2 } from "readline";
|
|
17467
|
-
import { homedir as
|
|
17774
|
+
import { homedir as homedir12 } from "os";
|
|
17468
17775
|
import { join as join34 } from "path";
|
|
17469
17776
|
var MAX_CHUNK_CHARS5, HistorySource;
|
|
17470
17777
|
var init_history_source = __esm({
|
|
@@ -17480,7 +17787,7 @@ var init_history_source = __esm({
|
|
|
17480
17787
|
if (historyPath) {
|
|
17481
17788
|
this.historyPaths = [historyPath];
|
|
17482
17789
|
} else {
|
|
17483
|
-
const home =
|
|
17790
|
+
const home = homedir12();
|
|
17484
17791
|
const paths = [];
|
|
17485
17792
|
const tryAdd = (p) => {
|
|
17486
17793
|
if (existsSync31(p) && !paths.includes(p))
|
|
@@ -17646,7 +17953,7 @@ var init_history_source = __esm({
|
|
|
17646
17953
|
|
|
17647
17954
|
// ../engine/dist/parsers/paste-source.js
|
|
17648
17955
|
import { existsSync as existsSync32, readdirSync as readdirSync21, readFileSync as readFileSync24, statSync as statSync29 } from "fs";
|
|
17649
|
-
import { homedir as
|
|
17956
|
+
import { homedir as homedir13 } from "os";
|
|
17650
17957
|
import { join as join35, basename as basename17 } from "path";
|
|
17651
17958
|
var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
|
|
17652
17959
|
var init_paste_source = __esm({
|
|
@@ -17663,7 +17970,7 @@ var init_paste_source = __esm({
|
|
|
17663
17970
|
if (cacheDir) {
|
|
17664
17971
|
this.cacheDirs = [cacheDir];
|
|
17665
17972
|
} else {
|
|
17666
|
-
const home =
|
|
17973
|
+
const home = homedir13();
|
|
17667
17974
|
const dirs = [];
|
|
17668
17975
|
const tryAdd = (p) => {
|
|
17669
17976
|
if (existsSync32(p) && !dirs.includes(p))
|
|
@@ -17747,7 +18054,7 @@ var init_paste_source = __esm({
|
|
|
17747
18054
|
// ../engine/dist/parsers/gemini-source.js
|
|
17748
18055
|
import { existsSync as existsSync33, readdirSync as readdirSync22, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
|
|
17749
18056
|
import { join as join36, basename as basename18 } from "path";
|
|
17750
|
-
import { createHash as
|
|
18057
|
+
import { createHash as createHash12 } from "crypto";
|
|
17751
18058
|
function loadGeminiProjectMap2() {
|
|
17752
18059
|
const map = /* @__PURE__ */ new Map();
|
|
17753
18060
|
const projectsPath = geminiBackend.projectsJson();
|
|
@@ -17757,7 +18064,7 @@ function loadGeminiProjectMap2() {
|
|
|
17757
18064
|
const data = JSON.parse(readFileSync25(projectsPath, "utf-8"));
|
|
17758
18065
|
const projects = data.projects || {};
|
|
17759
18066
|
for (const [path] of Object.entries(projects)) {
|
|
17760
|
-
const hash =
|
|
18067
|
+
const hash = createHash12("sha256").update(path).digest("hex");
|
|
17761
18068
|
map.set(hash, path);
|
|
17762
18069
|
}
|
|
17763
18070
|
} catch {
|
|
@@ -18740,7 +19047,7 @@ var init_redact_inline = __esm({
|
|
|
18740
19047
|
|
|
18741
19048
|
// ../engine/dist/parsers/skills-source.js
|
|
18742
19049
|
import { existsSync as existsSync39, readdirSync as readdirSync26, readFileSync as readFileSync29, statSync as statSync34 } from "fs";
|
|
18743
|
-
import { homedir as
|
|
19050
|
+
import { homedir as homedir14 } from "os";
|
|
18744
19051
|
import { join as join40 } from "path";
|
|
18745
19052
|
function rebuildBody(content) {
|
|
18746
19053
|
const { text, redacted } = redactInlineSecrets(content);
|
|
@@ -18756,7 +19063,7 @@ function rebuildBody(content) {
|
|
|
18756
19063
|
return out;
|
|
18757
19064
|
}
|
|
18758
19065
|
function resolveRoots() {
|
|
18759
|
-
const home =
|
|
19066
|
+
const home = homedir14();
|
|
18760
19067
|
const all = [];
|
|
18761
19068
|
if (isSourceEnabled("shared", "skills")) {
|
|
18762
19069
|
all.push({ path: join40(home, ".agents", "skills"), tool: "shared" });
|
|
@@ -18939,7 +19246,7 @@ var init_skills_source = __esm({
|
|
|
18939
19246
|
|
|
18940
19247
|
// ../engine/dist/parsers/mcps-source.js
|
|
18941
19248
|
import { existsSync as existsSync40, readdirSync as readdirSync27, readFileSync as readFileSync30, statSync as statSync35 } from "fs";
|
|
18942
|
-
import { homedir as
|
|
19249
|
+
import { homedir as homedir15 } from "os";
|
|
18943
19250
|
import { join as join41 } from "path";
|
|
18944
19251
|
function readJson(path) {
|
|
18945
19252
|
if (!existsSync40(path))
|
|
@@ -19005,7 +19312,7 @@ var init_mcps_source = __esm({
|
|
|
19005
19312
|
async *discover() {
|
|
19006
19313
|
if (!isSourceEnabled("common", "mcps"))
|
|
19007
19314
|
return;
|
|
19008
|
-
const home =
|
|
19315
|
+
const home = homedir15();
|
|
19009
19316
|
yield* this.fromObject(readJson(join41(home, ".mcp.json"))?.mcpServers, {
|
|
19010
19317
|
tool: "claude",
|
|
19011
19318
|
filePath: join41(home, ".mcp.json"),
|
|
@@ -19678,7 +19985,7 @@ var init_hooks_source = __esm({
|
|
|
19678
19985
|
|
|
19679
19986
|
// ../engine/dist/parsers/plugins-source.js
|
|
19680
19987
|
import { existsSync as existsSync44, readFileSync as readFileSync34, readdirSync as readdirSync31, statSync as statSync39 } from "fs";
|
|
19681
|
-
import { homedir as
|
|
19988
|
+
import { homedir as homedir16 } from "os";
|
|
19682
19989
|
import { join as join45 } from "path";
|
|
19683
19990
|
function readJson3(path) {
|
|
19684
19991
|
if (!existsSync44(path))
|
|
@@ -19700,7 +20007,7 @@ var init_plugins_source = __esm({
|
|
|
19700
20007
|
PluginsSource = class {
|
|
19701
20008
|
sourceType = "plugin";
|
|
19702
20009
|
async *discover() {
|
|
19703
|
-
const home =
|
|
20010
|
+
const home = homedir16();
|
|
19704
20011
|
const claudePluginsManifest = claudeBackend.pluginsManifestPath();
|
|
19705
20012
|
const claudeJson = isSourceEnabled("claude", "plugins") ? readJson3(claudePluginsManifest) : null;
|
|
19706
20013
|
if (claudeJson?.plugins && typeof claudeJson.plugins === "object") {
|
|
@@ -20322,7 +20629,7 @@ var init_http = __esm({
|
|
|
20322
20629
|
CLI_VERSION = JSON.parse(readFileSync37(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
20323
20630
|
} catch {
|
|
20324
20631
|
try {
|
|
20325
|
-
if (true) CLI_VERSION = "0.5.
|
|
20632
|
+
if (true) CLI_VERSION = "0.5.17";
|
|
20326
20633
|
} catch {
|
|
20327
20634
|
}
|
|
20328
20635
|
}
|
|
@@ -20509,17 +20816,17 @@ var init_scan_pool = __esm({
|
|
|
20509
20816
|
});
|
|
20510
20817
|
const slot = { worker, pending: null };
|
|
20511
20818
|
worker.on("message", (r) => {
|
|
20512
|
-
const
|
|
20819
|
+
const resolve3 = slot.pending;
|
|
20513
20820
|
slot.pending = null;
|
|
20514
|
-
|
|
20821
|
+
resolve3?.(r);
|
|
20515
20822
|
this.pump();
|
|
20516
20823
|
});
|
|
20517
20824
|
const die = (err) => {
|
|
20518
|
-
const
|
|
20825
|
+
const resolve3 = slot.pending;
|
|
20519
20826
|
slot.pending = null;
|
|
20520
20827
|
this.slots = this.slots.filter((s) => s !== slot);
|
|
20521
20828
|
if (this.slots.length === 0) this.disabled = true;
|
|
20522
|
-
|
|
20829
|
+
resolve3?.({ id: -1, findings: [], redactions: 0, rows: null, error: `scan worker died: ${err}` });
|
|
20523
20830
|
this.pump();
|
|
20524
20831
|
};
|
|
20525
20832
|
worker.on("error", die);
|
|
@@ -20585,8 +20892,8 @@ var init_scan_pool = __esm({
|
|
|
20585
20892
|
this.start();
|
|
20586
20893
|
if (this.disabled) return null;
|
|
20587
20894
|
task.id = this.nextId++;
|
|
20588
|
-
const result = await new Promise((
|
|
20589
|
-
this.queue.push({ task, resolve:
|
|
20895
|
+
const result = await new Promise((resolve3) => {
|
|
20896
|
+
this.queue.push({ task, resolve: resolve3 });
|
|
20590
20897
|
this.pump();
|
|
20591
20898
|
});
|
|
20592
20899
|
if (result.error) return null;
|
|
@@ -20819,8 +21126,8 @@ async function flush() {
|
|
|
20819
21126
|
}
|
|
20820
21127
|
function hashHost() {
|
|
20821
21128
|
try {
|
|
20822
|
-
const { createHash:
|
|
20823
|
-
return
|
|
21129
|
+
const { createHash: createHash16 } = __require("node:crypto");
|
|
21130
|
+
return createHash16("sha256").update(hostname()).digest("hex").slice(0, 12);
|
|
20824
21131
|
} catch {
|
|
20825
21132
|
return "";
|
|
20826
21133
|
}
|
|
@@ -20834,7 +21141,7 @@ var init_telemetry = __esm({
|
|
|
20834
21141
|
init_sync_client();
|
|
20835
21142
|
cliVersion2 = () => {
|
|
20836
21143
|
try {
|
|
20837
|
-
return true ? "0.5.
|
|
21144
|
+
return true ? "0.5.17" : "dev";
|
|
20838
21145
|
} catch {
|
|
20839
21146
|
return "dev";
|
|
20840
21147
|
}
|
|
@@ -20858,7 +21165,7 @@ import { execSync as execSync4 } from "node:child_process";
|
|
|
20858
21165
|
import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
20859
21166
|
import { join as join48 } from "node:path";
|
|
20860
21167
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
20861
|
-
import { createHash as
|
|
21168
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
20862
21169
|
function compareVersions(a, b) {
|
|
20863
21170
|
const pa = String(a).split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
20864
21171
|
const pb = String(b).split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -20888,7 +21195,7 @@ async function executeAutoUpdate(plan, deps) {
|
|
|
20888
21195
|
} catch (e) {
|
|
20889
21196
|
return { updated: false, reason: `download failed: ${e instanceof Error ? e.message : e}` };
|
|
20890
21197
|
}
|
|
20891
|
-
const got =
|
|
21198
|
+
const got = createHash13("sha256").update(buf).digest("hex");
|
|
20892
21199
|
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` };
|
|
20893
21200
|
const dir = mkdtempSync3(join48(tmpdir2(), "cr-update-"));
|
|
20894
21201
|
const tgz = join48(dir, "chat-recall.tgz");
|
|
@@ -20961,6 +21268,7 @@ var sync_client_exports = {};
|
|
|
20961
21268
|
__export(sync_client_exports, {
|
|
20962
21269
|
BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
|
|
20963
21270
|
FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
|
|
21271
|
+
_fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
|
|
20964
21272
|
_resetCapsCacheForTests: () => _resetCapsCacheForTests,
|
|
20965
21273
|
_resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
|
|
20966
21274
|
_resetTenantSyncConfigCache: () => _resetTenantSyncConfigCache,
|
|
@@ -20984,7 +21292,7 @@ __export(sync_client_exports, {
|
|
|
20984
21292
|
import { join as join49, dirname as dirname18, basename as basename23 } from "node:path";
|
|
20985
21293
|
import { readFileSync as readFileSync38, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync5, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync41 } from "node:fs";
|
|
20986
21294
|
import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
|
|
20987
|
-
import { createHash as
|
|
21295
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
20988
21296
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20989
21297
|
import { gzipSync as gzipSync3 } from "zlib";
|
|
20990
21298
|
function classifyError(msg) {
|
|
@@ -21083,6 +21391,25 @@ async function fetchTenantSecurityConfig(cred) {
|
|
|
21083
21391
|
function _resetTenantSecurityConfigCache() {
|
|
21084
21392
|
_securityConfigCache.clear();
|
|
21085
21393
|
}
|
|
21394
|
+
function syncConfigCachePath() {
|
|
21395
|
+
return join49(getDataDir(), "sync-config-cache.json");
|
|
21396
|
+
}
|
|
21397
|
+
function readPersistedSyncConfigs() {
|
|
21398
|
+
try {
|
|
21399
|
+
return JSON.parse(readFileSync38(syncConfigCachePath(), "utf-8"));
|
|
21400
|
+
} catch {
|
|
21401
|
+
return {};
|
|
21402
|
+
}
|
|
21403
|
+
}
|
|
21404
|
+
function persistSyncConfig(base, config2) {
|
|
21405
|
+
try {
|
|
21406
|
+
const all = readPersistedSyncConfigs();
|
|
21407
|
+
all[base] = { fetchedAt: Date.now(), config: config2 };
|
|
21408
|
+
mkdirSync11(dirname18(syncConfigCachePath()), { recursive: true });
|
|
21409
|
+
writeFileSync10(syncConfigCachePath(), JSON.stringify(all, null, 2));
|
|
21410
|
+
} catch {
|
|
21411
|
+
}
|
|
21412
|
+
}
|
|
21086
21413
|
async function fetchTenantSyncConfig(cred) {
|
|
21087
21414
|
const base = cred.serverUrl.replace(/\/+$/, "");
|
|
21088
21415
|
const cached = _syncConfigCache.get(base);
|
|
@@ -21102,7 +21429,11 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
21102
21429
|
} catch {
|
|
21103
21430
|
}
|
|
21104
21431
|
const res = await fetchWithTimeout(`${base}/api/sync-config`, { headers });
|
|
21105
|
-
if (
|
|
21432
|
+
if (res.status === 404) {
|
|
21433
|
+
_syncConfigCache.set(base, { fetchedAt: Date.now(), config: EMPTY_SYNC_CONFIG });
|
|
21434
|
+
return EMPTY_SYNC_CONFIG;
|
|
21435
|
+
}
|
|
21436
|
+
if (!res.ok) return lastKnownSyncConfig(base, `HTTP ${res.status}`);
|
|
21106
21437
|
const body = await res.json().catch(() => ({}));
|
|
21107
21438
|
const config2 = {
|
|
21108
21439
|
excludeTools: Array.isArray(body.excludeTools) ? body.excludeTools.filter((t) => typeof t === "string") : [],
|
|
@@ -21140,11 +21471,22 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
21140
21471
|
} catch {
|
|
21141
21472
|
}
|
|
21142
21473
|
_syncConfigCache.set(base, { fetchedAt: Date.now(), config: config2 });
|
|
21474
|
+
persistSyncConfig(base, config2);
|
|
21143
21475
|
return config2;
|
|
21144
|
-
} catch {
|
|
21145
|
-
return
|
|
21476
|
+
} catch (err) {
|
|
21477
|
+
return lastKnownSyncConfig(base, err instanceof Error ? err.message : "request failed");
|
|
21146
21478
|
}
|
|
21147
21479
|
}
|
|
21480
|
+
function lastKnownSyncConfig(base, reason) {
|
|
21481
|
+
const remembered = _syncConfigCache.get(base) ?? readPersistedSyncConfigs()[base];
|
|
21482
|
+
if (remembered) {
|
|
21483
|
+
console.error(`[sync] could not read server-side sync rules (${reason}) \u2014 applying the last known set from ${new Date(remembered.fetchedAt).toISOString()}`);
|
|
21484
|
+
return remembered.config;
|
|
21485
|
+
}
|
|
21486
|
+
throw new Error(
|
|
21487
|
+
`Cannot read the server-side sync rules from ${base} (${reason}), and this machine has never successfully read them. Refusing to sync: uploading without knowing your exclusions could ship a project you told the dashboard to hold back. Fix the server or retry.`
|
|
21488
|
+
);
|
|
21489
|
+
}
|
|
21148
21490
|
function deviceLabel() {
|
|
21149
21491
|
try {
|
|
21150
21492
|
return hostname2();
|
|
@@ -21434,7 +21776,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21434
21776
|
};
|
|
21435
21777
|
const cleartext = opts.cleartextPaths ?? sync?.pathsCleartext === true;
|
|
21436
21778
|
const mapPath = (p) => cleartext ? p : hashPath(p);
|
|
21437
|
-
const excluded = (projectPath) => excludeProjects.some((x) =>
|
|
21779
|
+
const excluded = (projectPath) => excludeProjects.some((x) => projectPathIncludes(projectPath, x));
|
|
21438
21780
|
const syncOnly = /* @__PURE__ */ new Set([...sync?.syncOnlyProjects ?? [], ...serverCfg.syncOnlyProjects ?? []]);
|
|
21439
21781
|
const includedProject = (projectPath) => {
|
|
21440
21782
|
if ((sync?.syncMode ?? "all") !== "only") return true;
|
|
@@ -21690,7 +22032,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21690
22032
|
const kgSeen = /* @__PURE__ */ new Set();
|
|
21691
22033
|
const kgEntityType = /* @__PURE__ */ new Map();
|
|
21692
22034
|
const kgEntityNames = /* @__PURE__ */ new Set();
|
|
21693
|
-
const kgKey = (subject, predicate, object, validFrom) =>
|
|
22035
|
+
const kgKey = (subject, predicate, object, validFrom) => createHash14("sha1").update(`${subject}|${predicate}|${object}|${validFrom}`).digest("hex").slice(0, 16);
|
|
21694
22036
|
const accumulateKg = async (triples, sourceSession) => {
|
|
21695
22037
|
for (const t of triples) {
|
|
21696
22038
|
const key2 = kgKey(t.subject, t.predicate, t.object, t.validFrom ?? "");
|
|
@@ -21780,7 +22122,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21780
22122
|
let traceN = 0;
|
|
21781
22123
|
let considered = 0;
|
|
21782
22124
|
for (const ref of slice) {
|
|
21783
|
-
await new Promise((
|
|
22125
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
21784
22126
|
considered++;
|
|
21785
22127
|
const walkDone = considered >= slice.length;
|
|
21786
22128
|
reportWalkProgress({ done: considered, total: slice.length, startedAt: walkStartedAt, complete: walkDone });
|
|
@@ -22394,7 +22736,7 @@ async function syncIncremental(opts = {}) {
|
|
|
22394
22736
|
lock.release();
|
|
22395
22737
|
}
|
|
22396
22738
|
}
|
|
22397
|
-
var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22739
|
+
var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22398
22740
|
var init_sync_client = __esm({
|
|
22399
22741
|
"src/sync-client.ts"() {
|
|
22400
22742
|
"use strict";
|
|
@@ -22416,6 +22758,7 @@ var init_sync_client = __esm({
|
|
|
22416
22758
|
init_tool_backend();
|
|
22417
22759
|
init_session();
|
|
22418
22760
|
init_project_resolver();
|
|
22761
|
+
init_project_path_match();
|
|
22419
22762
|
init_entity_extractor();
|
|
22420
22763
|
init_all_sources();
|
|
22421
22764
|
init_sync_ledger();
|
|
@@ -22439,7 +22782,7 @@ var init_sync_client = __esm({
|
|
|
22439
22782
|
ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22440
22783
|
} catch {
|
|
22441
22784
|
try {
|
|
22442
|
-
if (true) ownVersion = "0.5.
|
|
22785
|
+
if (true) ownVersion = "0.5.17";
|
|
22443
22786
|
} catch {
|
|
22444
22787
|
}
|
|
22445
22788
|
}
|
|
@@ -22449,7 +22792,9 @@ var init_sync_client = __esm({
|
|
|
22449
22792
|
_securityConfigCache = /* @__PURE__ */ new Map();
|
|
22450
22793
|
SECURITY_CONFIG_TTL_MS = 5 * 60 * 1e3;
|
|
22451
22794
|
_syncConfigCache = /* @__PURE__ */ new Map();
|
|
22452
|
-
|
|
22795
|
+
EMPTY_SYNC_CONFIG = { excludeTools: [], excludeProjects: [], excludeSources: [], approveSources: [] };
|
|
22796
|
+
_fetchTenantSyncConfigForTests = fetchTenantSyncConfig;
|
|
22797
|
+
hashPath = (p) => p ? "p_" + createHash14("sha256").update(p).digest("hex").slice(0, 12) : "";
|
|
22453
22798
|
MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
|
|
22454
22799
|
isLocalHost = isPrivateHost;
|
|
22455
22800
|
CAPS_TTL_MS = 60 * 6e4;
|
|
@@ -22626,7 +22971,7 @@ __export(install_skills_exports, {
|
|
|
22626
22971
|
uninstallSkills: () => uninstallSkills
|
|
22627
22972
|
});
|
|
22628
22973
|
import { readdirSync as readdirSync33, existsSync as existsSync47, mkdirSync as mkdirSync12, rmSync as rmSync6, cpSync, writeFileSync as writeFileSync12, readFileSync as readFileSync41, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
|
|
22629
|
-
import { createHash as
|
|
22974
|
+
import { createHash as createHash15 } from "crypto";
|
|
22630
22975
|
import { join as join51, basename as basename24 } from "path";
|
|
22631
22976
|
function skillsSourceDir() {
|
|
22632
22977
|
return join51(import.meta.dirname, "..", "skills");
|
|
@@ -22640,7 +22985,7 @@ function version() {
|
|
|
22640
22985
|
}
|
|
22641
22986
|
function skillsFingerprint() {
|
|
22642
22987
|
const src = skillsSourceDir();
|
|
22643
|
-
const h =
|
|
22988
|
+
const h = createHash15("sha256");
|
|
22644
22989
|
for (const name of bundledSkillNames()) {
|
|
22645
22990
|
for (const rel of filesUnder(join51(src, name)).sort()) {
|
|
22646
22991
|
h.update(name + "/" + rel);
|
|
@@ -22836,13 +23181,13 @@ __export(service_installer_exports, {
|
|
|
22836
23181
|
});
|
|
22837
23182
|
import { execSync as execSync5 } from "child_process";
|
|
22838
23183
|
import { existsSync as existsSync48, mkdirSync as mkdirSync13, rmSync as rmSync7, writeFileSync as writeFileSync13 } from "fs";
|
|
22839
|
-
import { homedir as
|
|
23184
|
+
import { homedir as homedir17 } from "os";
|
|
22840
23185
|
import { join as join52 } from "path";
|
|
22841
23186
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
22842
23187
|
function resolveDaemonPaths() {
|
|
22843
23188
|
const watchJs = fileURLToPath3(new URL("./watch.js", import.meta.url));
|
|
22844
23189
|
const node = process.execPath;
|
|
22845
|
-
const dataDir = join52(
|
|
23190
|
+
const dataDir = join52(homedir17(), ".chat-recall");
|
|
22846
23191
|
const logFile = join52(dataDir, "watch.log");
|
|
22847
23192
|
return { watchJs, node, logFile, dataDir };
|
|
22848
23193
|
}
|
|
@@ -22884,7 +23229,7 @@ function installService() {
|
|
|
22884
23229
|
);
|
|
22885
23230
|
}
|
|
22886
23231
|
function installSystemd(watchJs, node, logFile) {
|
|
22887
|
-
const unitDir = join52(
|
|
23232
|
+
const unitDir = join52(homedir17(), ".config", "systemd", "user");
|
|
22888
23233
|
const unitPath = join52(unitDir, "chat-recall-watch.service");
|
|
22889
23234
|
const unit = renderSystemdUnit(watchJs, node, logFile);
|
|
22890
23235
|
mkdirSync13(unitDir, { recursive: true });
|
|
@@ -22932,7 +23277,7 @@ function renderSystemdUnit(watchJs, node, logFile) {
|
|
|
22932
23277
|
}
|
|
22933
23278
|
function installLaunchd(watchJs, node, logFile) {
|
|
22934
23279
|
const label = "com.chat-recall.watch";
|
|
22935
|
-
const plistDir = join52(
|
|
23280
|
+
const plistDir = join52(homedir17(), "Library", "LaunchAgents");
|
|
22936
23281
|
const plistPath = join52(plistDir, `${label}.plist`);
|
|
22937
23282
|
const plist = renderLaunchdPlist(watchJs, node, logFile, label);
|
|
22938
23283
|
mkdirSync13(plistDir, { recursive: true });
|
|
@@ -22989,7 +23334,7 @@ function uninstallService() {
|
|
|
22989
23334
|
removed = true;
|
|
22990
23335
|
} catch {
|
|
22991
23336
|
}
|
|
22992
|
-
const unitPath = join52(
|
|
23337
|
+
const unitPath = join52(homedir17(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
22993
23338
|
if (existsSync48(unitPath)) {
|
|
22994
23339
|
rmSync7(unitPath, { force: true });
|
|
22995
23340
|
removed = true;
|
|
@@ -23001,7 +23346,7 @@ function uninstallService() {
|
|
|
23001
23346
|
return removed;
|
|
23002
23347
|
}
|
|
23003
23348
|
if (process.platform === "darwin") {
|
|
23004
|
-
const plistPath = join52(
|
|
23349
|
+
const plistPath = join52(homedir17(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
|
|
23005
23350
|
try {
|
|
23006
23351
|
execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
|
|
23007
23352
|
} catch {
|
|
@@ -23045,9 +23390,13 @@ var init_service_installer = __esm({
|
|
|
23045
23390
|
|
|
23046
23391
|
// src/mcp.ts
|
|
23047
23392
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23048
|
-
import { spawn as
|
|
23393
|
+
import { spawn as spawn3 } from "child_process";
|
|
23049
23394
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
23050
23395
|
|
|
23396
|
+
// ../engine/dist/mcp/tools.js
|
|
23397
|
+
import { homedir as homedir6 } from "node:os";
|
|
23398
|
+
import { resolve as resolve2 } from "node:path";
|
|
23399
|
+
|
|
23051
23400
|
// ../engine/dist/core/resume-command.js
|
|
23052
23401
|
function toolOfSessionId(sessionId) {
|
|
23053
23402
|
if (sessionId.startsWith("codex_"))
|
|
@@ -23164,6 +23513,9 @@ function trialEndingBanner(state2, serverBase) {
|
|
|
23164
23513
|
].join(" ");
|
|
23165
23514
|
}
|
|
23166
23515
|
|
|
23516
|
+
// ../engine/dist/mcp/tools.js
|
|
23517
|
+
init_login_prompt();
|
|
23518
|
+
|
|
23167
23519
|
// ../engine/dist/mcp/credential-context.js
|
|
23168
23520
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23169
23521
|
var store = new AsyncLocalStorage();
|
|
@@ -23480,6 +23832,9 @@ function setIndexRunner(fn) {
|
|
|
23480
23832
|
function localOnlyRefusal(tool) {
|
|
23481
23833
|
return `${tool} runs on YOUR machine, not on the server \u2014 this connection has no access to your files. Install the chat-recall CLI and run it there; everything it collects syncs here and stays searchable from this connection.`;
|
|
23482
23834
|
}
|
|
23835
|
+
function emptyIndexHint() {
|
|
23836
|
+
return isMultiTenant() ? "No coding history in this account yet \u2014 normal for a new one, not an error. The account already exists and the free trial is running; there is nothing to sign up for. The transcripts live on the user's own machine, so they must be indexed there.\n\nIF YOU HAVE A SHELL, DO THIS NOW \u2014 it is the whole setup:\n 1. npm install -g chat-recall\n 2. chat-recall init\n Prints a URL and waits. Tell the user to open it and approve \u2014 one click, same account,\n not a second signup. Do not kill the command while it waits.\n 3. It then indexes this machine's Claude Code, Codex, Gemini CLI, OpenCode, Antigravity and\n Cursor sessions, redacts secrets locally, and syncs them.\n 4. Call recall_status to confirm, then retry what you were asked.\nIf you have no shell, give the user steps 1 and 2 verbatim.\n\nMeanwhile this connection already works for memory created here \u2014 facts, decisions, tasks and diary entries persist across conversations right now." : "Nothing has been indexed yet. Run `chat-recall init` (first time) or `chat-recall index` to collect this machine's sessions and sync them.";
|
|
23837
|
+
}
|
|
23483
23838
|
async function runIndexChild(force) {
|
|
23484
23839
|
if (!indexRunner) {
|
|
23485
23840
|
return "Indexing runs on YOUR machine, not on the server \u2014 this connection has no access to your transcripts. Install the CLI and run `chat-recall index` (or `chat-recall init` for first-time setup) on the machine whose history you want searchable here.";
|
|
@@ -23595,10 +23950,22 @@ async function remotePatch(path, body) {
|
|
|
23595
23950
|
throw await buildHttpError(path, res);
|
|
23596
23951
|
return res.json();
|
|
23597
23952
|
}
|
|
23953
|
+
async function remoteDelete(path) {
|
|
23954
|
+
const cred = remoteCredentials();
|
|
23955
|
+
if (!cred)
|
|
23956
|
+
throw new Error('scope "server" needs a login \u2014 run `chat-recall login <server-url>` first.');
|
|
23957
|
+
const res = await fetch(cred.base + path, {
|
|
23958
|
+
method: "DELETE",
|
|
23959
|
+
headers: { authorization: `Bearer ${cred.token}` }
|
|
23960
|
+
});
|
|
23961
|
+
if (!res.ok)
|
|
23962
|
+
throw await buildHttpError(path, res);
|
|
23963
|
+
return res.json();
|
|
23964
|
+
}
|
|
23598
23965
|
function requireRemote() {
|
|
23599
23966
|
const cred = remoteCredentials();
|
|
23600
23967
|
if (!cred) {
|
|
23601
|
-
throw new Error(
|
|
23968
|
+
throw new Error(loginInstruction());
|
|
23602
23969
|
}
|
|
23603
23970
|
return cred;
|
|
23604
23971
|
}
|
|
@@ -23670,6 +24037,14 @@ var RecallRenameSchema = z.object({
|
|
|
23670
24037
|
session_id: z.string().describe("Session ID to name (from recall_recent / recall_search)."),
|
|
23671
24038
|
name: z.string().describe("The name to give this conversation. Pass an empty string to clear it and revert to the auto title.")
|
|
23672
24039
|
});
|
|
24040
|
+
var RecallForgetSchema = z.object({
|
|
24041
|
+
session_id: z.string().describe("Session ID to delete everywhere (from recall_recent / recall_search)."),
|
|
24042
|
+
confirm: z.literal(true).describe("Must be true. Only pass it when the user has actually asked for this conversation to be deleted.")
|
|
24043
|
+
});
|
|
24044
|
+
var RecallExcludePathSchema = z.object({
|
|
24045
|
+
path: z.string().min(1).describe("Absolute path (or a distinctive part of one) to stop syncing."),
|
|
24046
|
+
confirm: z.literal(true).describe("Must be true. Only pass it when the user has actually asked to stop syncing this path.")
|
|
24047
|
+
});
|
|
23673
24048
|
var RecallEditsTimelineSchema = z.object({
|
|
23674
24049
|
since_hours: z.number().optional().default(24).describe("Time window in hours. Defaults to 24h."),
|
|
23675
24050
|
limit: z.number().optional().default(100).describe("Maximum edit rows to return"),
|
|
@@ -23713,7 +24088,13 @@ var RecallMemorySearchSchema = z.object({
|
|
|
23713
24088
|
semantic: z.boolean().optional().default(false).describe("Reserved and currently ignored: the semantic tier is switched off server-side, so every search runs keyword FTS.")
|
|
23714
24089
|
});
|
|
23715
24090
|
var RecallSmartResumeSchema = z.object({
|
|
23716
|
-
|
|
24091
|
+
// OPTIONAL, because the question this tool answers is "continue where we left
|
|
24092
|
+
// off" and whoever asks it does not have a session id. Requiring one made the
|
|
24093
|
+
// routing advice ("continue" -> recall_smart_resume) impossible to follow: the
|
|
24094
|
+
// agent had to call recall_recent first, and a bare call failed with a zod
|
|
24095
|
+
// error about a missing field rather than doing the obvious thing.
|
|
24096
|
+
session_id: z.string().optional().describe("Session to resume. Omit for the most recent one."),
|
|
24097
|
+
project_filter: z.string().optional().describe("When session_id is omitted, resume the latest session from this project (path or name substring).")
|
|
23717
24098
|
});
|
|
23718
24099
|
var RecallSubagentSearchSchema = z.object({
|
|
23719
24100
|
query: z.string().describe("Text to find inside subagent transcripts (explore/compact/aside sub-tasks)"),
|
|
@@ -23865,7 +24246,7 @@ var RecallUserPromptsSchema = z.object({
|
|
|
23865
24246
|
});
|
|
23866
24247
|
var RecallDecisionRecordSchema = z.object({
|
|
23867
24248
|
subject: z.string().describe('What the decision is about (e.g., "chat-recall", "auth strategy")'),
|
|
23868
|
-
decision: z.string().describe('The decision itself in plain words (e.g., "use
|
|
24249
|
+
decision: z.string().describe('The decision itself in plain words (e.g., "use Postgres full-text search as the default backend")'),
|
|
23869
24250
|
reason: z.string().optional().describe("Why this was decided \u2014 short rationale"),
|
|
23870
24251
|
importance: z.number().min(1).max(5).optional().default(4).describe("1\u20135; the classifier surfaces 4+ in wake-up context"),
|
|
23871
24252
|
session_id: z.string().optional().describe("Session this decision was made in (for traceability)"),
|
|
@@ -23984,12 +24365,28 @@ var LEAN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
23984
24365
|
// Health, and the one that pays out on day one.
|
|
23985
24366
|
"recall_status",
|
|
23986
24367
|
"recall_index",
|
|
23987
|
-
"recall_security_summary"
|
|
24368
|
+
"recall_security_summary",
|
|
24369
|
+
// The two REMOVE tools. They are here for the same reason as the rest — they
|
|
24370
|
+
// answer something a person says mid-conversation ("forget that", "stop
|
|
24371
|
+
// syncing this repo") — and a privacy control the agent cannot see is a
|
|
24372
|
+
// dashboard feature wearing an MCP badge. They shipped outside this set and
|
|
24373
|
+
// the e2e harness proved them working under CHAT_RECALL_MCP_PROFILE=full,
|
|
24374
|
+
// which is the one profile no user runs.
|
|
24375
|
+
//
|
|
24376
|
+
// Cost: two names on a 25-name list. Brake: `confirm: true` is a
|
|
24377
|
+
// z.literal(true), and both are kept out of the alwaysAllow list `init`
|
|
24378
|
+
// writes, so the host prompts every time. Neither depends on the listing.
|
|
24379
|
+
"recall_forget",
|
|
24380
|
+
"recall_exclude_path"
|
|
23988
24381
|
]);
|
|
23989
24382
|
function toolProfile() {
|
|
23990
24383
|
return (process.env.CHAT_RECALL_MCP_PROFILE || "").toLowerCase() === "full" ? "full" : "lean";
|
|
23991
24384
|
}
|
|
23992
24385
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
24386
|
+
"recall_forget",
|
|
24387
|
+
// deletes a session everywhere, with a tombstone
|
|
24388
|
+
"recall_exclude_path",
|
|
24389
|
+
// changes what syncs, here and on the account
|
|
23993
24390
|
"recall_index",
|
|
23994
24391
|
// indexes and ships new rows
|
|
23995
24392
|
"recall_code_index",
|
|
@@ -24017,6 +24414,10 @@ var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
24017
24414
|
// overwrites the session name
|
|
24018
24415
|
]);
|
|
24019
24416
|
var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
24417
|
+
// The two scope-narrowing tools. recall_forget has no undo at all; excluding a
|
|
24418
|
+
// path changes what leaves the machine until someone removes the rule by hand.
|
|
24419
|
+
"recall_forget",
|
|
24420
|
+
"recall_exclude_path",
|
|
24020
24421
|
"recall_kg_invalidate",
|
|
24021
24422
|
"recall_recommendation_dismiss",
|
|
24022
24423
|
// retires advice; undo exists but it is a decision
|
|
@@ -24129,7 +24530,7 @@ when the user is asking "what did we change?" and the diffs/SQL/commands matter.
|
|
|
24129
24530
|
},
|
|
24130
24531
|
{
|
|
24131
24532
|
name: "recall_recent",
|
|
24132
|
-
description: `List recent
|
|
24533
|
+
description: `List recent sessions across every AI tool chat-recall indexes.
|
|
24133
24534
|
|
|
24134
24535
|
Shows your most recent conversations across all projects or filtered by project.
|
|
24135
24536
|
Use this when the user says "continue our last conversation" or wants to see recent work.
|
|
@@ -24145,6 +24546,50 @@ Pass \`since_hours: N\` to restrict to sessions modified in the last N hours.`,
|
|
|
24145
24546
|
}
|
|
24146
24547
|
}
|
|
24147
24548
|
},
|
|
24549
|
+
{
|
|
24550
|
+
name: "recall_forget",
|
|
24551
|
+
description: `Delete one conversation from chat-recall, everywhere, permanently.
|
|
24552
|
+
|
|
24553
|
+
Use it when the user says "forget this conversation", "delete that session", "remove what we discussed
|
|
24554
|
+
about X" \u2014 anything that asks for a conversation to stop existing on the server.
|
|
24555
|
+
|
|
24556
|
+
WHAT IT DOES: purges the session on every server this machine is logged in to, and tombstones the id so
|
|
24557
|
+
no later sync can bring it back. The AI tool's own transcript file on the user's disk is NOT touched \u2014
|
|
24558
|
+
tell them that, because it is usually what they mean to keep.
|
|
24559
|
+
|
|
24560
|
+
IRREVERSIBLE. There is no undo and no tool that restores it. Only pass \`confirm: true\` when the user
|
|
24561
|
+
has actually asked for this; do not infer it from a complaint about a conversation.`,
|
|
24562
|
+
inputSchema: {
|
|
24563
|
+
type: "object",
|
|
24564
|
+
properties: {
|
|
24565
|
+
session_id: { type: "string", description: "Session ID to delete (from recall_recent / recall_search)." },
|
|
24566
|
+
confirm: { type: "boolean", description: "Must be true. Only when the user actually asked to delete it." }
|
|
24567
|
+
},
|
|
24568
|
+
required: ["session_id", "confirm"]
|
|
24569
|
+
}
|
|
24570
|
+
},
|
|
24571
|
+
{
|
|
24572
|
+
name: "recall_exclude_path",
|
|
24573
|
+
description: `Stop syncing a project path. Nothing under it leaves the machine from the next sync on.
|
|
24574
|
+
|
|
24575
|
+
Use it when the user says "stop syncing this repo", "don't upload anything from ~/work/client", "keep this
|
|
24576
|
+
project out of chat-recall".
|
|
24577
|
+
|
|
24578
|
+
WHAT IT DOES: adds the path to the exclusion rules \u2014 on this machine, and on the account so every device
|
|
24579
|
+
the user syncs from honours it. Sessions ALREADY uploaded from that path stay until they are deleted; say
|
|
24580
|
+
so, and offer recall_forget for the ones that matter.
|
|
24581
|
+
|
|
24582
|
+
There is deliberately no tool to REMOVE an exclusion: an agent may narrow what is synced, never widen it.
|
|
24583
|
+
Undo it with \`chat-recall exclude remove <path>\` or in the dashboard.`,
|
|
24584
|
+
inputSchema: {
|
|
24585
|
+
type: "object",
|
|
24586
|
+
properties: {
|
|
24587
|
+
path: { type: "string", description: "Absolute path (or a distinctive part of one) to stop syncing." },
|
|
24588
|
+
confirm: { type: "boolean", description: "Must be true. Only when the user actually asked to stop syncing it." }
|
|
24589
|
+
},
|
|
24590
|
+
required: ["path", "confirm"]
|
|
24591
|
+
}
|
|
24592
|
+
},
|
|
24148
24593
|
{
|
|
24149
24594
|
name: "recall_rename_session",
|
|
24150
24595
|
description: `Give a conversation a memorable name \u2014 or rename it \u2014 so you (and the user) can find and resume it later.
|
|
@@ -24166,7 +24611,7 @@ The name persists across re-syncs and summary regeneration (it's stored separate
|
|
|
24166
24611
|
{
|
|
24167
24612
|
name: "recall_edits_timeline",
|
|
24168
24613
|
description: `Chronological list of file edits across recent sessions, spanning every AI tool
|
|
24169
|
-
chat-recall indexes \u2014 Claude Code, Gemini CLI, OpenCode, Codex and
|
|
24614
|
+
chat-recall indexes \u2014 Claude Code, Gemini CLI, OpenCode, Codex, Antigravity and Cursor.
|
|
24170
24615
|
|
|
24171
24616
|
Returns rows shaped like (timestamp, tool, session_id, project, file, op) sorted newest
|
|
24172
24617
|
first. Pulls live from each tool's native session store \u2014 Claude JSONL, Gemini chat
|
|
@@ -24268,8 +24713,8 @@ by extension, with tools used) \u2014 "which files did session X actually touch?
|
|
|
24268
24713
|
name: "recall_commits",
|
|
24269
24714
|
description: `Find git commits that landed during a session's edit window, grouped by repo.
|
|
24270
24715
|
|
|
24271
|
-
Multi-repo aware: a session that touched ~/code/
|
|
24272
|
-
~/code/
|
|
24716
|
+
Multi-repo aware: a session that touched ~/code/example/infra and
|
|
24717
|
+
~/code/example/api returns commits from each repo, with overlap shown
|
|
24273
24718
|
between commit-files and session-touched-files.
|
|
24274
24719
|
|
|
24275
24720
|
Use this to verify "shipped" claims \u2014 if the session edited 18 files but no
|
|
@@ -24333,7 +24778,11 @@ with recall_show (pass the plan id).`,
|
|
|
24333
24778
|
},
|
|
24334
24779
|
{
|
|
24335
24780
|
name: "recall_smart_resume",
|
|
24336
|
-
description: `Get structured resume context for a session.
|
|
24781
|
+
description: `Get structured resume context for a session \u2014 by default, the most recent one.
|
|
24782
|
+
|
|
24783
|
+
Call it with NO arguments to resume the latest session. That is the common case:
|
|
24784
|
+
"continue", "pick up where we left off". Pass session_id only to resume a
|
|
24785
|
+
specific one, or project_filter to take the latest from one project.
|
|
24337
24786
|
|
|
24338
24787
|
Returns:
|
|
24339
24788
|
- What was done (completed work, decisions made)
|
|
@@ -24346,9 +24795,9 @@ Use this instead of recall_context for a more actionable summary when resuming w
|
|
|
24346
24795
|
inputSchema: {
|
|
24347
24796
|
type: "object",
|
|
24348
24797
|
properties: {
|
|
24349
|
-
session_id: { type: "string", description: "Session
|
|
24350
|
-
|
|
24351
|
-
|
|
24798
|
+
session_id: { type: "string", description: "Session to resume. OMIT to resume the most recent session." },
|
|
24799
|
+
project_filter: { type: "string", description: "With session_id omitted, resume the latest session from this project." }
|
|
24800
|
+
}
|
|
24352
24801
|
}
|
|
24353
24802
|
},
|
|
24354
24803
|
{
|
|
@@ -24360,9 +24809,9 @@ overview (from CLAUDE.md), tech stack (KG uses), architecture / deployment / sec
|
|
|
24360
24809
|
sections (from CLAUDE.md), decisions log (KG chose/rejected), recent session activity
|
|
24361
24810
|
with summaries, open tasks, plans, agent diary conclusions, and cost rollup.
|
|
24362
24811
|
|
|
24363
|
-
Accepts EITHER a project path / name substring (e.g. "
|
|
24364
|
-
"/home/user/code/
|
|
24365
|
-
- "git:<host>/<owner>/<repo>" (e.g. "git:github.com/
|
|
24812
|
+
Accepts EITHER a project path / name substring (e.g. "api", "example-app",
|
|
24813
|
+
"/home/user/code/example") OR a stable project_id:
|
|
24814
|
+
- "git:<host>/<owner>/<repo>" (e.g. "git:github.com/owner/repo")
|
|
24366
24815
|
- "ws:<name>" (workspace rollup)
|
|
24367
24816
|
- "git-local:<sha1>" (local-only git repo)
|
|
24368
24817
|
- "user:<custom>" (declared in ~/.chat-recall/projects.json)
|
|
@@ -24508,7 +24957,7 @@ without editing the task itself. Pass the task id (t_\u2026) from recall_tasks.`
|
|
|
24508
24957
|
name: "recall_kg_query",
|
|
24509
24958
|
description: `Query the knowledge graph for an entity's relationships.
|
|
24510
24959
|
|
|
24511
|
-
Returns typed facts with temporal validity. E.g. "
|
|
24960
|
+
Returns typed facts with temporal validity. E.g. "example-app" \u2192 uses TypeScript, deploys to Kubernetes.
|
|
24512
24961
|
Filter by date with as_of to see what was true at a specific point in time.
|
|
24513
24962
|
Use this to VERIFY facts before asserting them.`,
|
|
24514
24963
|
inputSchema: {
|
|
@@ -24525,7 +24974,7 @@ Use this to VERIFY facts before asserting them.`,
|
|
|
24525
24974
|
name: "recall_kg_add",
|
|
24526
24975
|
description: `Add a fact to the knowledge graph. Subject \u2192 predicate \u2192 object with optional time window.
|
|
24527
24976
|
|
|
24528
|
-
E.g. ("
|
|
24977
|
+
E.g. ("example-app", "uses", "Postgres", valid_from="2026-01-15")`,
|
|
24529
24978
|
inputSchema: {
|
|
24530
24979
|
type: "object",
|
|
24531
24980
|
properties: {
|
|
@@ -25098,7 +25547,12 @@ Complements recall_memory_search: search finds candidates, this reads the specif
|
|
|
25098
25547
|
if (hidden > 0) {
|
|
25099
25548
|
lean.push({
|
|
25100
25549
|
name: "recall_help",
|
|
25101
|
-
|
|
25550
|
+
// The env-var advice only applies to a LOCAL server the user launches.
|
|
25551
|
+
// Over the remote endpoint the process is ours, so telling a connector
|
|
25552
|
+
// user to set CHAT_RECALL_MCP_PROFILE names a knob they cannot reach —
|
|
25553
|
+
// and the useful half of the sentence (they still work by name) got lost
|
|
25554
|
+
// behind it.
|
|
25555
|
+
description: `List the ${hidden} additional chat-recall tools that are not registered in this profile (analytics, knowledge-graph navigation, code intelligence, sharing, security triage, maintenance). Every one of them still WORKS if you call it by name \u2014 this profile shortens the LIST, not the surface, because tool choice degrades as the list grows.` + (isMultiTenant() ? "" : " Set CHAT_RECALL_MCP_PROFILE=full to register them all up front."),
|
|
25102
25556
|
inputSchema: { type: "object", properties: {} }
|
|
25103
25557
|
});
|
|
25104
25558
|
}
|
|
@@ -25258,7 +25712,14 @@ async function dispatchTool(request) {
|
|
|
25258
25712
|
const searchQuery = sanitized.cleanQuery;
|
|
25259
25713
|
const remote = await remotePost("/api/search", { query: searchQuery, topK: params.top_k, projectFilter: params.project_filter });
|
|
25260
25714
|
if (!remote.results?.length) {
|
|
25261
|
-
|
|
25715
|
+
let indexed = 1;
|
|
25716
|
+
try {
|
|
25717
|
+
const st = await remoteGet("/api/status");
|
|
25718
|
+
indexed = st.totalChunks ?? 1;
|
|
25719
|
+
} catch {
|
|
25720
|
+
}
|
|
25721
|
+
const text = indexed === 0 ? emptyIndexHint() : `No matching sessions on the server for "${params.query}".`;
|
|
25722
|
+
return { content: [{ type: "text", text }] };
|
|
25262
25723
|
}
|
|
25263
25724
|
const lines = [`# Server results for: "${params.query}"`, "_(synced history across your devices)_", ""];
|
|
25264
25725
|
for (let i = 0; i < remote.results.length; i++) {
|
|
@@ -25302,7 +25763,7 @@ async function dispatchTool(request) {
|
|
|
25302
25763
|
const lines = [
|
|
25303
25764
|
"Chat-Recall Server Status",
|
|
25304
25765
|
`Synced sessions: ${sync.sessions}`,
|
|
25305
|
-
`
|
|
25766
|
+
`Indexed chunks: ${status.totalChunks}`,
|
|
25306
25767
|
`Raw archives: ${sync.rawArchived}`
|
|
25307
25768
|
];
|
|
25308
25769
|
{
|
|
@@ -25444,7 +25905,7 @@ ${plan.data.content}` }] };
|
|
|
25444
25905
|
qs.set("project", params.project_filter);
|
|
25445
25906
|
const remote = await remoteGet(`/api/conversations/recent?${qs.toString()}`);
|
|
25446
25907
|
if (!remote.sessions?.length) {
|
|
25447
|
-
const empty = params.project_filter || params.since_hours ? `No sessions match${params.project_filter ? ` project filter "${params.project_filter}"` : ""}${params.since_hours ? ` in the last ${params.since_hours}h` : ""}. The project filter is a case-insensitive substring of the project path \u2014 try a broader term, or drop it.` :
|
|
25908
|
+
const empty = params.project_filter || params.since_hours ? `No sessions match${params.project_filter ? ` project filter "${params.project_filter}"` : ""}${params.since_hours ? ` in the last ${params.since_hours}h` : ""}. The project filter is a case-insensitive substring of the project path \u2014 try a broader term, or drop it.` : emptyIndexHint();
|
|
25448
25909
|
return { content: [{ type: "text", text: empty }] };
|
|
25449
25910
|
}
|
|
25450
25911
|
const lines = [`# Recent sessions (server \u2014 ${remote.total} total synced)
|
|
@@ -25460,6 +25921,61 @@ ${plan.data.content}` }] };
|
|
|
25460
25921
|
}
|
|
25461
25922
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
25462
25923
|
}
|
|
25924
|
+
case "recall_forget": {
|
|
25925
|
+
const params = RecallForgetSchema.parse(args);
|
|
25926
|
+
getWAL().log("forget", { session_id: params.session_id });
|
|
25927
|
+
requireRemote();
|
|
25928
|
+
const r = await remoteDelete(`/api/conversations/${encodeURIComponent(params.session_id)}`);
|
|
25929
|
+
return {
|
|
25930
|
+
content: [{
|
|
25931
|
+
type: "text",
|
|
25932
|
+
text: `\u2713 Deleted session \`${r.deleted || params.session_id}\` from the server${r.tombstoned ? " and tombstoned it, so no later sync can bring it back" : ""}.
|
|
25933
|
+
The transcript on this machine is untouched \u2014 this only removed the copy chat-recall held.`
|
|
25934
|
+
}]
|
|
25935
|
+
};
|
|
25936
|
+
}
|
|
25937
|
+
case "recall_exclude_path": {
|
|
25938
|
+
const params = RecallExcludePathSchema.parse(args);
|
|
25939
|
+
getWAL().log("exclude_path", { path: params.path });
|
|
25940
|
+
const written = [];
|
|
25941
|
+
if (!isMultiTenant()) {
|
|
25942
|
+
const { loadSettings: loadSettings2, saveSettings: saveSettings2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
25943
|
+
const settings2 = loadSettings2();
|
|
25944
|
+
const abs = resolve2(params.path.replace(/^~(?=\/|$)/, homedir6()));
|
|
25945
|
+
if (!settings2.sync.excludeProjects.includes(abs)) {
|
|
25946
|
+
settings2.sync.excludeProjects.push(abs);
|
|
25947
|
+
saveSettings2(settings2);
|
|
25948
|
+
}
|
|
25949
|
+
written.push("this machine");
|
|
25950
|
+
}
|
|
25951
|
+
try {
|
|
25952
|
+
const current = await remoteGet("/api/sync-config");
|
|
25953
|
+
if (!current.excludeProjects.includes(params.path)) {
|
|
25954
|
+
await remotePost("/api/sync-config", {
|
|
25955
|
+
...current,
|
|
25956
|
+
excludeProjects: [...current.excludeProjects, params.path]
|
|
25957
|
+
});
|
|
25958
|
+
}
|
|
25959
|
+
written.push("your account (every device)");
|
|
25960
|
+
} catch (err) {
|
|
25961
|
+
if (!written.length)
|
|
25962
|
+
throw err;
|
|
25963
|
+
return {
|
|
25964
|
+
content: [{
|
|
25965
|
+
type: "text",
|
|
25966
|
+
text: `\u2713 Stopped syncing \`${params.path}\` from this machine.
|
|
25967
|
+
\u26A0 Could not write the rule to your account (${err instanceof Error ? err.message : "request failed"}), so your other devices are NOT covered yet. Retry, or add it in the dashboard.`
|
|
25968
|
+
}]
|
|
25969
|
+
};
|
|
25970
|
+
}
|
|
25971
|
+
return {
|
|
25972
|
+
content: [{
|
|
25973
|
+
type: "text",
|
|
25974
|
+
text: `\u2713 Stopped syncing \`${params.path}\` \u2014 rule written to ${written.join(" and ")}.
|
|
25975
|
+
Sessions already uploaded from that path stay until they are deleted; use recall_forget for the ones that matter.`
|
|
25976
|
+
}]
|
|
25977
|
+
};
|
|
25978
|
+
}
|
|
25463
25979
|
case "recall_rename_session": {
|
|
25464
25980
|
const params = RecallRenameSchema.parse(args);
|
|
25465
25981
|
requireRemote();
|
|
@@ -25802,7 +26318,18 @@ _Edits stayed local or window was off \u2014 try increasing buffer_minutes._` }]
|
|
|
25802
26318
|
}
|
|
25803
26319
|
case "recall_smart_resume": {
|
|
25804
26320
|
const params = RecallSmartResumeSchema.parse(args);
|
|
25805
|
-
|
|
26321
|
+
requireRemote();
|
|
26322
|
+
let sid = params.session_id;
|
|
26323
|
+
if (!sid) {
|
|
26324
|
+
const qs = new URLSearchParams({ limit: "1" });
|
|
26325
|
+
if (params.project_filter)
|
|
26326
|
+
qs.set("project", params.project_filter);
|
|
26327
|
+
const recent = await remoteGet(`/api/conversations/recent?${qs.toString()}`);
|
|
26328
|
+
sid = recent.sessions?.[0]?.sessionId;
|
|
26329
|
+
if (!sid) {
|
|
26330
|
+
return { content: [{ type: "text", text: params.project_filter ? `No sessions found for project \`${params.project_filter}\` \u2014 nothing to resume.` : "No sessions have synced yet, so there is nothing to resume. Run `chat-recall index` first." }] };
|
|
26331
|
+
}
|
|
26332
|
+
}
|
|
25806
26333
|
const enc = encodeURIComponent(sid);
|
|
25807
26334
|
const metaSoft = await remoteGetSoft(`/api/conversations/${enc}/metadata`);
|
|
25808
26335
|
if (!metaSoft.data) {
|
|
@@ -27307,6 +27834,34 @@ ${JSON.stringify(r, null, 2)}
|
|
|
27307
27834
|
}
|
|
27308
27835
|
}
|
|
27309
27836
|
|
|
27837
|
+
// src/mcp.ts
|
|
27838
|
+
init_login_prompt();
|
|
27839
|
+
|
|
27840
|
+
// src/open-browser.ts
|
|
27841
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
27842
|
+
function isSafeUrl(url) {
|
|
27843
|
+
try {
|
|
27844
|
+
const u = new URL(url);
|
|
27845
|
+
return u.protocol === "https:" || u.protocol === "http:";
|
|
27846
|
+
} catch {
|
|
27847
|
+
return false;
|
|
27848
|
+
}
|
|
27849
|
+
}
|
|
27850
|
+
function openBrowser(url) {
|
|
27851
|
+
if (!isSafeUrl(url)) return false;
|
|
27852
|
+
if (process.env.CHAT_RECALL_NO_BROWSER === "1" || process.env.CI) return false;
|
|
27853
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
27854
|
+
try {
|
|
27855
|
+
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
27856
|
+
child.on("error", () => {
|
|
27857
|
+
});
|
|
27858
|
+
child.unref();
|
|
27859
|
+
return true;
|
|
27860
|
+
} catch {
|
|
27861
|
+
return false;
|
|
27862
|
+
}
|
|
27863
|
+
}
|
|
27864
|
+
|
|
27310
27865
|
// src/mcp.ts
|
|
27311
27866
|
init_client_events();
|
|
27312
27867
|
init_update_notice();
|
|
@@ -27317,13 +27872,74 @@ setServerVersion(JSON.parse(
|
|
|
27317
27872
|
setEventReporter(reportClientEvent2);
|
|
27318
27873
|
setIndexRunner(runIndexChild2);
|
|
27319
27874
|
setUpdateNotice(updateNotice2);
|
|
27875
|
+
setLoginStarter(startLoginChild);
|
|
27876
|
+
function withVia(url) {
|
|
27877
|
+
try {
|
|
27878
|
+
const u = new URL(url);
|
|
27879
|
+
u.searchParams.set("via", "mcp");
|
|
27880
|
+
return u.toString();
|
|
27881
|
+
} catch {
|
|
27882
|
+
return url;
|
|
27883
|
+
}
|
|
27884
|
+
}
|
|
27885
|
+
function startLoginChild(server) {
|
|
27886
|
+
return new Promise((resolve3, reject) => {
|
|
27887
|
+
let cliPath;
|
|
27888
|
+
try {
|
|
27889
|
+
cliPath = fileURLToPath4(new URL("./cli.js", import.meta.url));
|
|
27890
|
+
} catch {
|
|
27891
|
+
reject(new Error("could not locate the CLI entry point"));
|
|
27892
|
+
return;
|
|
27893
|
+
}
|
|
27894
|
+
const child = spawn3(process.execPath, [cliPath, "init", "--server", server, "--prompt-json", "--yes"], {
|
|
27895
|
+
env: process.env,
|
|
27896
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
27897
|
+
// Outlives this call by design: it polls until the user approves. Detached
|
|
27898
|
+
// so a client that restarts the MCP does not abort a sign-in the user is
|
|
27899
|
+
// halfway through in their browser.
|
|
27900
|
+
detached: true
|
|
27901
|
+
});
|
|
27902
|
+
child.unref();
|
|
27903
|
+
let out = "";
|
|
27904
|
+
let settled = false;
|
|
27905
|
+
const fail = (msg) => {
|
|
27906
|
+
if (!settled) {
|
|
27907
|
+
settled = true;
|
|
27908
|
+
reject(new Error(msg));
|
|
27909
|
+
}
|
|
27910
|
+
};
|
|
27911
|
+
child.stdout?.on("data", (d) => {
|
|
27912
|
+
if (settled) return;
|
|
27913
|
+
out += d;
|
|
27914
|
+
for (const line of out.split("\n")) {
|
|
27915
|
+
const t = line.trim();
|
|
27916
|
+
if (!t.startsWith("{")) continue;
|
|
27917
|
+
try {
|
|
27918
|
+
const parsed = JSON.parse(t);
|
|
27919
|
+
if (parsed.prompt?.url) {
|
|
27920
|
+
settled = true;
|
|
27921
|
+
const tagged = { ...parsed.prompt, url: withVia(parsed.prompt.url) };
|
|
27922
|
+
openBrowser(tagged.url);
|
|
27923
|
+
resolve3(tagged);
|
|
27924
|
+
return;
|
|
27925
|
+
}
|
|
27926
|
+
} catch {
|
|
27927
|
+
}
|
|
27928
|
+
}
|
|
27929
|
+
});
|
|
27930
|
+
child.on("error", (e) => fail(e.message));
|
|
27931
|
+
child.on("exit", (code) => fail(`login helper exited (${code ?? "signal"}) before issuing a code`));
|
|
27932
|
+
const timer = setTimeout(() => fail("the server did not issue a device code in time"), 3e4);
|
|
27933
|
+
timer.unref?.();
|
|
27934
|
+
});
|
|
27935
|
+
}
|
|
27320
27936
|
function runIndexChild2(force) {
|
|
27321
|
-
return new Promise((
|
|
27937
|
+
return new Promise((resolve3) => {
|
|
27322
27938
|
let cliPath;
|
|
27323
27939
|
try {
|
|
27324
27940
|
cliPath = fileURLToPath4(new URL("./cli.js", import.meta.url));
|
|
27325
27941
|
} catch {
|
|
27326
|
-
|
|
27942
|
+
resolve3("Index unavailable: could not locate the CLI entry point.");
|
|
27327
27943
|
return;
|
|
27328
27944
|
}
|
|
27329
27945
|
const args = [cliPath, "index"];
|
|
@@ -27333,10 +27949,10 @@ function runIndexChild2(force) {
|
|
|
27333
27949
|
const finish = (msg) => {
|
|
27334
27950
|
if (!done) {
|
|
27335
27951
|
done = true;
|
|
27336
|
-
|
|
27952
|
+
resolve3(msg);
|
|
27337
27953
|
}
|
|
27338
27954
|
};
|
|
27339
|
-
const child =
|
|
27955
|
+
const child = spawn3(process.execPath, args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
27340
27956
|
child.stdout?.on("data", (d) => {
|
|
27341
27957
|
out += d;
|
|
27342
27958
|
});
|
|
@@ -27422,6 +28038,14 @@ async function main() {
|
|
|
27422
28038
|
} catch {
|
|
27423
28039
|
}
|
|
27424
28040
|
})();
|
|
28041
|
+
try {
|
|
28042
|
+
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
28043
|
+
if (loadAllCredentials2().length === 0 && !process.env.CHAT_RECALL_TOKEN) {
|
|
28044
|
+
const { loginInstruction: loginInstruction2 } = await Promise.resolve().then(() => (init_login_prompt(), login_prompt_exports));
|
|
28045
|
+
loginInstruction2();
|
|
28046
|
+
}
|
|
28047
|
+
} catch {
|
|
28048
|
+
}
|
|
27425
28049
|
let daemonRunning = false;
|
|
27426
28050
|
try {
|
|
27427
28051
|
const { isServiceRunning: isServiceRunning2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
|