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