@freesyntax/notch-cli 0.5.21 → 0.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{apply-patch-D5PDUXUC.js → apply-patch-U6K67CMT.js} +1 -0
- package/dist/auth-UAMMP5IJ.js +29 -0
- package/dist/{chunk-TU465P2P.js → chunk-474TAHDN.js} +50 -8
- package/dist/{chunk-OSWUX6TC.js → chunk-4HPRBCSY.js} +1 -1
- package/dist/{chunk-QKM27RHS.js → chunk-6NKRMZTX.js} +1 -1
- package/dist/{chunk-443G6HCC.js → chunk-JVFOAPYV.js} +331 -79
- package/dist/chunk-KCAR5DOB.js +52 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/chunk-O6AKZ4OH.js +0 -0
- package/dist/{chunk-FIFC4V2R.js → chunk-PPEBWOMJ.js} +91 -7
- package/dist/chunk-UHK6SI4H.js +206 -0
- package/dist/{chunk-MMBFNIKE.js → chunk-YNYVQ7ZI.js} +10 -8
- package/dist/{compression-SQAIQ2UU.js → compression-YJLWEHCC.js} +1 -0
- package/dist/config-set-5F4VK7IT.js +111 -0
- package/dist/{edit-JEFEK43H.js → edit-6QYAXVNU.js} +1 -0
- package/dist/{git-5T5TSQTX.js → git-DNQ5EELH.js} +1 -0
- package/dist/{github-DWRGWX6U.js → github-34T4QQIH.js} +1 -0
- package/dist/{glob-BI3P4C7Q.js → glob-XT43LEJ4.js} +1 -0
- package/dist/{grep-VZ3I5GNW.js → grep-T2CXYNRI.js} +1 -0
- package/dist/index.js +878 -428
- package/dist/{lsp-UPY6I3L7.js → lsp-JXQVU7NP.js} +1 -0
- package/dist/model-download-KCQJCEPW.js +176 -0
- package/dist/{notebook-FXJBTSPA.js → notebook-MFODW345.js} +1 -0
- package/dist/{ollama-bench-QQHBIG2D.js → ollama-bench-JLC5POG3.js} +8 -4
- package/dist/{ollama-launch-2ASVER3S.js → ollama-launch-3IKB2A3Z.js} +6 -2
- package/dist/{ollama-usage-2WPCZJJI.js → ollama-usage-3PROM2WC.js} +1 -0
- package/dist/{plugins-OG2P75K5.js → plugins-PNGRZLFW.js} +1 -0
- package/dist/{read-OVJG2XKW.js → read-B64XE7N3.js} +1 -0
- package/dist/server-GMF4WV67.js +187 -0
- package/dist/{session-index-SSGOOZXK.js → session-index-7FWEVP6E.js} +3 -2
- package/dist/{shell-4X545EVN.js → shell-BOZTHQUT.js} +1 -0
- package/dist/{task-OS3E5F3X.js → task-67G4KLYC.js} +1 -0
- package/dist/{tools-7WAWS6V4.js → tools-ABRZPCEJ.js} +6 -3
- package/dist/{web-fetch-KNIV3Z3W.js → web-fetch-OTNDICGJ.js} +1 -0
- package/dist/{write-NNHLOTYK.js → write-ZOSB7I4J.js} +1 -0
- package/package.json +60 -57
- package/dist/auth-JQX6MHJG.js +0 -16
- package/dist/server-7UQKCB2Z.js +0 -1477
package/dist/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-4HPRBCSY.js";
|
|
3
3
|
import {
|
|
4
4
|
MCPClient,
|
|
5
5
|
buildToolMap,
|
|
6
|
-
|
|
6
|
+
describeToolSchemas,
|
|
7
|
+
disconnectMCPServers,
|
|
7
8
|
drainTailNotifications,
|
|
9
|
+
initMCPServers,
|
|
8
10
|
listBuiltinAgents,
|
|
9
11
|
nextSubagentId,
|
|
10
12
|
parseMCPConfig,
|
|
11
13
|
pollPendingAgents,
|
|
12
14
|
setCurrentSurface,
|
|
13
15
|
spawnSubagent
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-474TAHDN.js";
|
|
15
17
|
import {
|
|
16
18
|
Rollout,
|
|
17
19
|
generateSessionId,
|
|
@@ -20,16 +22,21 @@ import {
|
|
|
20
22
|
readIndex,
|
|
21
23
|
readRollout,
|
|
22
24
|
rebuildMessagesFromRollout
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-6NKRMZTX.js";
|
|
24
26
|
import {
|
|
25
27
|
autoCompress,
|
|
26
28
|
estimateTokens
|
|
27
29
|
} from "./chunk-PKZKVOAN.js";
|
|
30
|
+
import "./chunk-O6AKZ4OH.js";
|
|
31
|
+
import {
|
|
32
|
+
loadConfig,
|
|
33
|
+
persistConfigPatch
|
|
34
|
+
} from "./chunk-UHK6SI4H.js";
|
|
35
|
+
import "./chunk-KCAR5DOB.js";
|
|
28
36
|
import {
|
|
29
37
|
ByokMissingApiKeyError,
|
|
30
38
|
ByokMissingBaseUrlError,
|
|
31
39
|
MODEL_CATALOG,
|
|
32
|
-
MODEL_IDS,
|
|
33
40
|
MissingApiKeyError,
|
|
34
41
|
findByokProvider,
|
|
35
42
|
isByokRef,
|
|
@@ -37,10 +44,11 @@ import {
|
|
|
37
44
|
listByokProviders,
|
|
38
45
|
modelSupportsImages,
|
|
39
46
|
parseByokRef,
|
|
40
|
-
|
|
47
|
+
recordShadowUsage,
|
|
41
48
|
resolveModel,
|
|
42
49
|
validateConfig
|
|
43
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-JVFOAPYV.js";
|
|
51
|
+
import "./chunk-GFVLHUSS.js";
|
|
44
52
|
import "./chunk-6CZCFY6H.js";
|
|
45
53
|
import "./chunk-6U3ZAGYA.js";
|
|
46
54
|
import "./chunk-FFB7GK3Y.js";
|
|
@@ -57,10 +65,12 @@ import {
|
|
|
57
65
|
registerCommand
|
|
58
66
|
} from "./chunk-3QUV4JEX.js";
|
|
59
67
|
import {
|
|
68
|
+
auth_exports,
|
|
60
69
|
clearCredentials,
|
|
70
|
+
init_auth,
|
|
61
71
|
loadCredentials,
|
|
62
72
|
login
|
|
63
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-PPEBWOMJ.js";
|
|
64
74
|
import "./chunk-CQMAVWLJ.js";
|
|
65
75
|
import "./chunk-O3WZW7GS.js";
|
|
66
76
|
import "./chunk-YAYPQTOU.js";
|
|
@@ -69,6 +79,9 @@ import {
|
|
|
69
79
|
} from "./chunk-C4CPDDMN.js";
|
|
70
80
|
import "./chunk-W4FAGQFL.js";
|
|
71
81
|
import "./chunk-FAULT7VE.js";
|
|
82
|
+
import {
|
|
83
|
+
__toCommonJS
|
|
84
|
+
} from "./chunk-KFQGP6VL.js";
|
|
72
85
|
|
|
73
86
|
// src/index.ts
|
|
74
87
|
import { Command } from "commander";
|
|
@@ -77,105 +90,8 @@ import ora7 from "ora";
|
|
|
77
90
|
import * as readline from "readline";
|
|
78
91
|
import * as nodePath2 from "path";
|
|
79
92
|
|
|
80
|
-
// src/config.ts
|
|
81
|
-
import fs from "fs/promises";
|
|
82
|
-
import path from "path";
|
|
83
|
-
var DEFAULT_MODEL = {
|
|
84
|
-
model: "notch-pyre",
|
|
85
|
-
temperature: 0.3
|
|
86
|
-
};
|
|
87
|
-
var DEFAULTS = {
|
|
88
|
-
models: { chat: DEFAULT_MODEL },
|
|
89
|
-
projectRoot: process.cwd(),
|
|
90
|
-
autoConfirm: false,
|
|
91
|
-
maxIterations: 25,
|
|
92
|
-
useRepoMap: true,
|
|
93
|
-
renderMarkdown: true,
|
|
94
|
-
enableMemory: true,
|
|
95
|
-
enableHooks: true,
|
|
96
|
-
permissionMode: "auto",
|
|
97
|
-
theme: "default"
|
|
98
|
-
};
|
|
99
|
-
async function loadConfig(overrides = {}) {
|
|
100
|
-
const config = { ...DEFAULTS, models: { chat: { ...DEFAULT_MODEL } } };
|
|
101
|
-
const configPath = path.resolve(config.projectRoot, ".notch.json");
|
|
102
|
-
try {
|
|
103
|
-
const raw = await fs.readFile(configPath, "utf-8");
|
|
104
|
-
const fileConfig = JSON.parse(raw);
|
|
105
|
-
if (fileConfig.model && (isValidModel(fileConfig.model) || isByokRef(fileConfig.model))) {
|
|
106
|
-
config.models.chat.model = fileConfig.model;
|
|
107
|
-
}
|
|
108
|
-
if (fileConfig.baseUrl) config.models.chat.baseUrl = fileConfig.baseUrl;
|
|
109
|
-
if (fileConfig.apiKey) config.models.chat.apiKey = fileConfig.apiKey;
|
|
110
|
-
if (fileConfig.byok && typeof fileConfig.byok === "object") {
|
|
111
|
-
const byok = fileConfig.byok;
|
|
112
|
-
config.byok = { ...byok };
|
|
113
|
-
if (byok.provider && findByokProvider(byok.provider === "custom" ? "__custom__" : byok.provider)) {
|
|
114
|
-
config.models.chat.byokProvider = byok.provider === "custom" ? "__custom__" : byok.provider;
|
|
115
|
-
if (byok.model) config.models.chat.model = byok.model;
|
|
116
|
-
if (byok.baseUrl) config.models.chat.baseUrl = byok.baseUrl;
|
|
117
|
-
if (byok.headers) {
|
|
118
|
-
config.models.chat.byokHeaders = { ...config.models.chat.byokHeaders, ...byok.headers };
|
|
119
|
-
}
|
|
120
|
-
if (byok.apiShape === "openai" || byok.apiShape === "anthropic") {
|
|
121
|
-
config.models.chat.byokApiShape = byok.apiShape;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (fileConfig.hybrid && typeof fileConfig.hybrid === "object") {
|
|
126
|
-
const hybrid = fileConfig.hybrid;
|
|
127
|
-
config.hybrid = hybrid;
|
|
128
|
-
}
|
|
129
|
-
if (fileConfig.maxIterations) config.maxIterations = fileConfig.maxIterations;
|
|
130
|
-
if (fileConfig.useRepoMap !== void 0) config.useRepoMap = fileConfig.useRepoMap;
|
|
131
|
-
if (fileConfig.temperature !== void 0) config.models.chat.temperature = fileConfig.temperature;
|
|
132
|
-
if (fileConfig.renderMarkdown !== void 0) config.renderMarkdown = fileConfig.renderMarkdown;
|
|
133
|
-
if (fileConfig.enableMemory !== void 0) config.enableMemory = fileConfig.enableMemory;
|
|
134
|
-
if (fileConfig.enableHooks !== void 0) config.enableHooks = fileConfig.enableHooks;
|
|
135
|
-
if (fileConfig.permissionMode) config.permissionMode = fileConfig.permissionMode;
|
|
136
|
-
if (fileConfig.shellTimeout) config.shellTimeout = fileConfig.shellTimeout;
|
|
137
|
-
if (fileConfig.theme) config.theme = fileConfig.theme;
|
|
138
|
-
} catch {
|
|
139
|
-
}
|
|
140
|
-
const activeProviderId = config.models.chat.byokProvider ?? (typeof config.models.chat.model === "string" && isByokRef(config.models.chat.model) ? config.models.chat.model.split(":", 1)[0] : void 0);
|
|
141
|
-
const isOllamaProvider = activeProviderId === "ollama" || activeProviderId === "ollama-cloud" || activeProviderId === "ollama-anthropic";
|
|
142
|
-
if (isOllamaProvider) {
|
|
143
|
-
if (!config.models.chat.apiKey && !process.env.OLLAMA_API_KEY) {
|
|
144
|
-
const ollamaCreds = await readOllamaCreds();
|
|
145
|
-
if (ollamaCreds?.apiKey) {
|
|
146
|
-
config.models.chat.apiKey = ollamaCreds.apiKey;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
const creds = await loadCredentials();
|
|
151
|
-
if (creds?.token) {
|
|
152
|
-
config.models.chat.apiKey = creds.token;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (process.env.NOTCH_MODEL) {
|
|
156
|
-
const envModel = process.env.NOTCH_MODEL;
|
|
157
|
-
if (isValidModel(envModel)) {
|
|
158
|
-
config.models.chat.model = envModel;
|
|
159
|
-
} else if (isByokRef(envModel)) {
|
|
160
|
-
config.models.chat.model = envModel;
|
|
161
|
-
config.models.chat.byokProvider = void 0;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (process.env.NOTCH_BASE_URL) {
|
|
165
|
-
config.models.chat.baseUrl = process.env.NOTCH_BASE_URL;
|
|
166
|
-
}
|
|
167
|
-
if (process.env.NOTCH_API_KEY) {
|
|
168
|
-
config.models.chat.apiKey = process.env.NOTCH_API_KEY;
|
|
169
|
-
}
|
|
170
|
-
if (config.models.chat.temperature !== void 0) {
|
|
171
|
-
config.models.chat.temperature = Math.max(0, Math.min(2, config.models.chat.temperature));
|
|
172
|
-
}
|
|
173
|
-
config.maxIterations = Math.max(1, Math.min(100, config.maxIterations));
|
|
174
|
-
return { ...config, ...overrides };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
93
|
// src/ui/image-input.ts
|
|
178
|
-
import { promises as
|
|
94
|
+
import { promises as fs } from "fs";
|
|
179
95
|
import * as nodePath from "path";
|
|
180
96
|
import * as os from "os";
|
|
181
97
|
function isImageLoadError(r) {
|
|
@@ -292,7 +208,7 @@ async function loadFromFile(spec, cwd) {
|
|
|
292
208
|
const abs = expandUserPath(spec, cwd);
|
|
293
209
|
let stat;
|
|
294
210
|
try {
|
|
295
|
-
stat = await
|
|
211
|
+
stat = await fs.stat(abs);
|
|
296
212
|
} catch (err) {
|
|
297
213
|
return { error: `Image file not found: ${abs} (${err?.code ?? err?.message ?? "unknown error"})` };
|
|
298
214
|
}
|
|
@@ -309,7 +225,7 @@ async function loadFromFile(spec, cwd) {
|
|
|
309
225
|
}
|
|
310
226
|
let buf;
|
|
311
227
|
try {
|
|
312
|
-
buf = await
|
|
228
|
+
buf = await fs.readFile(abs);
|
|
313
229
|
} catch (err) {
|
|
314
230
|
return { error: `Failed to read ${abs}: ${err?.message ?? String(err)}` };
|
|
315
231
|
}
|
|
@@ -386,19 +302,19 @@ function formatAttachmentStatus(att) {
|
|
|
386
302
|
|
|
387
303
|
// src/agent/loop.ts
|
|
388
304
|
import { streamText } from "ai";
|
|
389
|
-
import { promises as
|
|
305
|
+
import { promises as fs4 } from "fs";
|
|
390
306
|
import { execSync } from "child_process";
|
|
391
307
|
|
|
392
308
|
// src/context/project-instructions.ts
|
|
393
|
-
import
|
|
394
|
-
import
|
|
309
|
+
import fs2 from "fs/promises";
|
|
310
|
+
import path from "path";
|
|
395
311
|
import os2 from "os";
|
|
396
312
|
var INSTRUCTION_FILES = [".notch.md", "NOTCH.md", ".notch/instructions.md"];
|
|
397
313
|
async function loadProjectInstructions(projectRoot) {
|
|
398
314
|
const sources = [];
|
|
399
315
|
const homeDir = os2.homedir();
|
|
400
316
|
for (const file of INSTRUCTION_FILES) {
|
|
401
|
-
const globalPath =
|
|
317
|
+
const globalPath = path.join(homeDir, file);
|
|
402
318
|
const content = await safeRead(globalPath);
|
|
403
319
|
if (content) {
|
|
404
320
|
sources.push({ path: globalPath, content, scope: "global" });
|
|
@@ -406,7 +322,7 @@ async function loadProjectInstructions(projectRoot) {
|
|
|
406
322
|
}
|
|
407
323
|
}
|
|
408
324
|
for (const file of INSTRUCTION_FILES) {
|
|
409
|
-
const projectPath =
|
|
325
|
+
const projectPath = path.join(projectRoot, file);
|
|
410
326
|
const content = await safeRead(projectPath);
|
|
411
327
|
if (content) {
|
|
412
328
|
sources.push({ path: projectPath, content, scope: "project" });
|
|
@@ -428,7 +344,7 @@ ${sections.join("\n\n")}`;
|
|
|
428
344
|
}
|
|
429
345
|
async function safeRead(filePath) {
|
|
430
346
|
try {
|
|
431
|
-
const content = await
|
|
347
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
432
348
|
return content.trim() || null;
|
|
433
349
|
} catch {
|
|
434
350
|
return null;
|
|
@@ -436,19 +352,19 @@ async function safeRead(filePath) {
|
|
|
436
352
|
}
|
|
437
353
|
|
|
438
354
|
// src/memory/store.ts
|
|
439
|
-
import
|
|
440
|
-
import
|
|
355
|
+
import fs3 from "fs/promises";
|
|
356
|
+
import path2 from "path";
|
|
441
357
|
import os3 from "os";
|
|
442
|
-
var MEMORY_DIR =
|
|
443
|
-
var INDEX_FILE =
|
|
358
|
+
var MEMORY_DIR = path2.join(os3.homedir(), ".notch", "memory");
|
|
359
|
+
var INDEX_FILE = path2.join(MEMORY_DIR, "MEMORY.md");
|
|
444
360
|
async function ensureDir() {
|
|
445
|
-
await
|
|
361
|
+
await fs3.mkdir(MEMORY_DIR, { recursive: true });
|
|
446
362
|
}
|
|
447
363
|
async function saveMemory(memory) {
|
|
448
364
|
await ensureDir();
|
|
449
365
|
const slug = memory.name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
450
366
|
const filename = `${memory.type}_${slug}.md`;
|
|
451
|
-
const filePath =
|
|
367
|
+
const filePath = path2.join(MEMORY_DIR, filename);
|
|
452
368
|
const fileContent = [
|
|
453
369
|
"---",
|
|
454
370
|
`name: ${memory.name}`,
|
|
@@ -459,18 +375,18 @@ async function saveMemory(memory) {
|
|
|
459
375
|
"",
|
|
460
376
|
memory.content
|
|
461
377
|
].join("\n");
|
|
462
|
-
await
|
|
378
|
+
await fs3.writeFile(filePath, fileContent, "utf-8");
|
|
463
379
|
await updateIndex();
|
|
464
380
|
return filename;
|
|
465
381
|
}
|
|
466
382
|
async function loadMemories() {
|
|
467
383
|
await ensureDir();
|
|
468
|
-
const files = await
|
|
384
|
+
const files = await fs3.readdir(MEMORY_DIR);
|
|
469
385
|
const memories = [];
|
|
470
386
|
for (const file of files) {
|
|
471
387
|
if (!file.endsWith(".md") || file === "MEMORY.md") continue;
|
|
472
388
|
try {
|
|
473
|
-
const content = await
|
|
389
|
+
const content = await fs3.readFile(path2.join(MEMORY_DIR, file), "utf-8");
|
|
474
390
|
const memory = parseMemoryFile(content, file);
|
|
475
391
|
if (memory) memories.push(memory);
|
|
476
392
|
} catch {
|
|
@@ -480,7 +396,7 @@ async function loadMemories() {
|
|
|
480
396
|
}
|
|
481
397
|
async function deleteMemory(filename) {
|
|
482
398
|
try {
|
|
483
|
-
await
|
|
399
|
+
await fs3.unlink(path2.join(MEMORY_DIR, filename));
|
|
484
400
|
await updateIndex();
|
|
485
401
|
return true;
|
|
486
402
|
} catch {
|
|
@@ -556,7 +472,7 @@ async function updateIndex() {
|
|
|
556
472
|
}
|
|
557
473
|
lines.push("");
|
|
558
474
|
}
|
|
559
|
-
await
|
|
475
|
+
await fs3.writeFile(INDEX_FILE, lines.join("\n"), "utf-8");
|
|
560
476
|
}
|
|
561
477
|
|
|
562
478
|
// src/agent/prompt-sections.ts
|
|
@@ -705,6 +621,304 @@ function microcompact(messages, opts2 = {}) {
|
|
|
705
621
|
return { messages: out, cleared, savedChars };
|
|
706
622
|
}
|
|
707
623
|
|
|
624
|
+
// src/agent/correction.ts
|
|
625
|
+
var CORRECTION_PRESETS = {
|
|
626
|
+
/** No correction — for capable models (Claude, GPT-4, etc.) */
|
|
627
|
+
disabled: () => ({
|
|
628
|
+
intentGate: false,
|
|
629
|
+
maxCorrectionAttempts: 0,
|
|
630
|
+
useTemplateFormat: false
|
|
631
|
+
}),
|
|
632
|
+
/** Light correction — for mid-tier models (Qwen 14B+, Llama 70B) */
|
|
633
|
+
capable: () => ({
|
|
634
|
+
intentGate: false,
|
|
635
|
+
maxCorrectionAttempts: 1,
|
|
636
|
+
useTemplateFormat: false
|
|
637
|
+
}),
|
|
638
|
+
/** Full correction — for small models (7B-14B) */
|
|
639
|
+
smallModel: () => ({
|
|
640
|
+
intentGate: true,
|
|
641
|
+
maxCorrectionAttempts: 2,
|
|
642
|
+
useTemplateFormat: false
|
|
643
|
+
}),
|
|
644
|
+
/** Maximum scaffolding — for tiny models (sub-7B) */
|
|
645
|
+
tinyModel: () => ({
|
|
646
|
+
intentGate: true,
|
|
647
|
+
maxCorrectionAttempts: 3,
|
|
648
|
+
useTemplateFormat: true
|
|
649
|
+
})
|
|
650
|
+
};
|
|
651
|
+
var INTENT_GATE_PROMPT = `You are a classifier. Given the user's message and the list of available tools, decide: does this message require calling a tool to answer properly?
|
|
652
|
+
|
|
653
|
+
Reply with EXACTLY one word: YES or NO
|
|
654
|
+
|
|
655
|
+
Available tools: {tool_names}
|
|
656
|
+
|
|
657
|
+
User message: {user_message}`;
|
|
658
|
+
async function intentGate(model, userMessage, toolNames) {
|
|
659
|
+
const { generateText: generateText3 } = await import("ai");
|
|
660
|
+
const prompt = INTENT_GATE_PROMPT.replace("{tool_names}", toolNames.join(", ")).replace("{user_message}", userMessage.slice(0, 500));
|
|
661
|
+
try {
|
|
662
|
+
const result = await generateText3({
|
|
663
|
+
model,
|
|
664
|
+
messages: [{ role: "user", content: prompt }],
|
|
665
|
+
maxTokens: 10,
|
|
666
|
+
temperature: 0
|
|
667
|
+
});
|
|
668
|
+
const answer = result.text.trim().toUpperCase();
|
|
669
|
+
if (answer.startsWith("YES")) return "yes";
|
|
670
|
+
if (answer.startsWith("NO")) return "no";
|
|
671
|
+
return "unclear";
|
|
672
|
+
} catch {
|
|
673
|
+
return "unclear";
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function looksLikeFailedToolCall(text, toolNames) {
|
|
677
|
+
if (!text || text.length < 10) return false;
|
|
678
|
+
let weakSignals = 0;
|
|
679
|
+
let strongSignals = 0;
|
|
680
|
+
const lowerText = text.toLowerCase();
|
|
681
|
+
for (const name of toolNames) {
|
|
682
|
+
if (lowerText.includes(name.toLowerCase())) {
|
|
683
|
+
weakSignals++;
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const jsonPatterns = [
|
|
688
|
+
/\{\s*"name"\s*:/,
|
|
689
|
+
/\{\s*"tool"\s*:/,
|
|
690
|
+
/\{\s*"function"\s*:/,
|
|
691
|
+
/"arguments"\s*:\s*\{/,
|
|
692
|
+
/"parameters"\s*:\s*\{/
|
|
693
|
+
];
|
|
694
|
+
for (const pat of jsonPatterns) {
|
|
695
|
+
if (pat.test(text)) {
|
|
696
|
+
strongSignals++;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const intentPatterns = [
|
|
701
|
+
/\b(?:i'll|let me|i need to|i should|i want to|i will)\s+(?:use|call|invoke|run|execute)\b/i,
|
|
702
|
+
/\b(?:calling|using|invoking|running)\s+(?:the\s+)?(?:tool|function)\b/i
|
|
703
|
+
];
|
|
704
|
+
for (const pat of intentPatterns) {
|
|
705
|
+
if (pat.test(text)) {
|
|
706
|
+
weakSignals++;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const xmlPatterns = [
|
|
711
|
+
/<tool_call/i,
|
|
712
|
+
/<function_call/i,
|
|
713
|
+
/<tool_use/i,
|
|
714
|
+
/<\|tool▁call\|>/,
|
|
715
|
+
/<\|plugin\|>/
|
|
716
|
+
];
|
|
717
|
+
for (const pat of xmlPatterns) {
|
|
718
|
+
if (pat.test(text)) {
|
|
719
|
+
strongSignals++;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (/```(?:json|tool)?\s*\n?\s*\{/.test(text)) {
|
|
724
|
+
weakSignals++;
|
|
725
|
+
}
|
|
726
|
+
return strongSignals >= 1 || weakSignals >= 2;
|
|
727
|
+
}
|
|
728
|
+
var CORRECTION_PROMPTS = [
|
|
729
|
+
// Attempt 1: XML format (most models have seen this in training)
|
|
730
|
+
`Your previous response tried to call a tool but the format was wrong.
|
|
731
|
+
|
|
732
|
+
Your output was:
|
|
733
|
+
---
|
|
734
|
+
{raw_output}
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
To call a tool, you MUST use this exact format:
|
|
738
|
+
<tool_call>
|
|
739
|
+
{"name": "tool_name", "arguments": {"param1": "value1"}}
|
|
740
|
+
</tool_call>
|
|
741
|
+
|
|
742
|
+
Available tools: {tool_names}
|
|
743
|
+
|
|
744
|
+
Rewrite your response with the correct tool call format. Output ONLY the tool call, nothing else.`,
|
|
745
|
+
// Attempt 2: bare JSON (simpler)
|
|
746
|
+
`Your output was not a valid tool call. Rewrite it as a single JSON object:
|
|
747
|
+
|
|
748
|
+
{"name": "TOOL_NAME", "arguments": {"key": "value"}}
|
|
749
|
+
|
|
750
|
+
Available tools: {tool_names}
|
|
751
|
+
Your failed output: {raw_output_short}
|
|
752
|
+
|
|
753
|
+
Reply with ONLY the JSON object.`,
|
|
754
|
+
// Attempt 3: ask directly (simplest possible)
|
|
755
|
+
`Which tool do you want to call? Reply with the tool name and arguments as JSON.
|
|
756
|
+
Tools: {tool_names}
|
|
757
|
+
JSON:`
|
|
758
|
+
];
|
|
759
|
+
async function correctionPass(model, rawOutput, toolNames, conversationMessages, maxAttempts) {
|
|
760
|
+
const { generateText: generateText3 } = await import("ai");
|
|
761
|
+
for (let i = 0; i < Math.min(maxAttempts, CORRECTION_PROMPTS.length); i++) {
|
|
762
|
+
const promptTemplate = CORRECTION_PROMPTS[i];
|
|
763
|
+
if (!promptTemplate) continue;
|
|
764
|
+
const prompt = promptTemplate.replace("{raw_output}", rawOutput.slice(0, 1500)).replace("{raw_output_short}", rawOutput.slice(0, 500)).replace("{tool_names}", toolNames.join(", "));
|
|
765
|
+
try {
|
|
766
|
+
const result = await generateText3({
|
|
767
|
+
model,
|
|
768
|
+
messages: [
|
|
769
|
+
...conversationMessages.slice(-4),
|
|
770
|
+
// keep recent context
|
|
771
|
+
{ role: "user", content: prompt }
|
|
772
|
+
],
|
|
773
|
+
maxTokens: 500,
|
|
774
|
+
temperature: 0
|
|
775
|
+
});
|
|
776
|
+
const parsed = extractToolCallsFromText(result.text, toolNames);
|
|
777
|
+
if (parsed.length > 0) return parsed;
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
function extractTemplateToolCalls(text, toolNames) {
|
|
784
|
+
const calls = [];
|
|
785
|
+
const toolNameSet = new Set(toolNames.map((n) => n.toLowerCase()));
|
|
786
|
+
const lines = text.split("\n");
|
|
787
|
+
let currentTool = null;
|
|
788
|
+
let currentArgs = {};
|
|
789
|
+
for (const line of lines) {
|
|
790
|
+
const trimmed = line.trim();
|
|
791
|
+
const toolMatch = trimmed.match(/^TOOL:\s*(.+)$/i);
|
|
792
|
+
if (toolMatch && toolMatch[1]) {
|
|
793
|
+
if (currentTool && toolNameSet.has(currentTool.toLowerCase())) {
|
|
794
|
+
calls.push({
|
|
795
|
+
id: `correction-${Date.now()}-${calls.length}`,
|
|
796
|
+
name: currentTool,
|
|
797
|
+
args: currentArgs
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
currentTool = toolMatch[1].trim();
|
|
801
|
+
currentArgs = {};
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const argMatch = trimmed.match(/^ARG_(\w+):\s*(.+)$/i);
|
|
805
|
+
if (argMatch && currentTool && argMatch[1] && argMatch[2]) {
|
|
806
|
+
const key = argMatch[1];
|
|
807
|
+
const rawValue = argMatch[2].trim();
|
|
808
|
+
currentArgs[key] = coerceValue(rawValue);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (currentTool && toolNameSet.has(currentTool.toLowerCase())) {
|
|
812
|
+
calls.push({
|
|
813
|
+
id: `correction-${Date.now()}-${calls.length}`,
|
|
814
|
+
name: currentTool,
|
|
815
|
+
args: currentArgs
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
return calls;
|
|
819
|
+
}
|
|
820
|
+
function coerceValue(raw) {
|
|
821
|
+
if (raw === "true") return true;
|
|
822
|
+
if (raw === "false") return false;
|
|
823
|
+
if (raw === "null") return null;
|
|
824
|
+
const num = Number(raw);
|
|
825
|
+
if (!Number.isNaN(num) && raw !== "") return num;
|
|
826
|
+
if (raw.startsWith("{") && raw.endsWith("}") || raw.startsWith("[") && raw.endsWith("]")) {
|
|
827
|
+
try {
|
|
828
|
+
return JSON.parse(raw);
|
|
829
|
+
} catch {
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return raw;
|
|
833
|
+
}
|
|
834
|
+
function extractToolCallsFromText(text, toolNames) {
|
|
835
|
+
const toolNameSet = new Set(toolNames.map((n) => n.toLowerCase()));
|
|
836
|
+
const xmlRegex = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi;
|
|
837
|
+
let match2;
|
|
838
|
+
const xmlCalls = [];
|
|
839
|
+
while ((match2 = xmlRegex.exec(text)) !== null) {
|
|
840
|
+
const captured = match2[1];
|
|
841
|
+
if (captured) {
|
|
842
|
+
const parsed = tryParseToolJson(captured, toolNameSet);
|
|
843
|
+
if (parsed) xmlCalls.push(parsed);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (xmlCalls.length > 0) return xmlCalls;
|
|
847
|
+
const codeBlockRegex = /```(?:json|tool)?\s*\n?([\s\S]*?)\n?\s*```/g;
|
|
848
|
+
const codeCalls = [];
|
|
849
|
+
while ((match2 = codeBlockRegex.exec(text)) !== null) {
|
|
850
|
+
const captured = match2[1];
|
|
851
|
+
if (captured) {
|
|
852
|
+
const parsed = tryParseToolJson(captured, toolNameSet);
|
|
853
|
+
if (parsed) codeCalls.push(parsed);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (codeCalls.length > 0) return codeCalls;
|
|
857
|
+
const jsonRegex = /\{[^{}]*"name"\s*:\s*"[^"]+"\s*[,}][\s\S]*?\}/g;
|
|
858
|
+
const jsonCalls = [];
|
|
859
|
+
while ((match2 = jsonRegex.exec(text)) !== null) {
|
|
860
|
+
const parsed = tryParseToolJson(match2[0], toolNameSet);
|
|
861
|
+
if (parsed) jsonCalls.push(parsed);
|
|
862
|
+
}
|
|
863
|
+
if (jsonCalls.length > 0) return jsonCalls;
|
|
864
|
+
const templateCalls = extractTemplateToolCalls(text, toolNames);
|
|
865
|
+
if (templateCalls.length > 0) return templateCalls;
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
function tryParseToolJson(jsonStr, toolNameSet) {
|
|
869
|
+
try {
|
|
870
|
+
const obj = JSON.parse(jsonStr.trim());
|
|
871
|
+
const name = obj.name ?? obj.tool ?? obj.function;
|
|
872
|
+
if (!name || typeof name !== "string") return null;
|
|
873
|
+
if (!toolNameSet.has(name.toLowerCase())) return null;
|
|
874
|
+
const args = obj.arguments ?? obj.parameters ?? obj.args ?? {};
|
|
875
|
+
return {
|
|
876
|
+
id: `correction-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
877
|
+
name,
|
|
878
|
+
args: typeof args === "object" ? args : {}
|
|
879
|
+
};
|
|
880
|
+
} catch {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
var CAPABLE_PATTERNS = [
|
|
885
|
+
/claude/i,
|
|
886
|
+
/gpt-4/i,
|
|
887
|
+
/gpt-3\.5/i,
|
|
888
|
+
/o[1-4]/i,
|
|
889
|
+
/gemini-(?:pro|ultra|2)/i,
|
|
890
|
+
/deepseek-(?:v3|r1)/i
|
|
891
|
+
];
|
|
892
|
+
var SMALL_PATTERNS = [
|
|
893
|
+
/qwen.*(?:7b|14b|32b)/i,
|
|
894
|
+
/llama.*(?:8b|13b|70b)/i,
|
|
895
|
+
/mistral.*(?:7b|8x7b)/i,
|
|
896
|
+
/gemma.*(?:7b|9b|27b)/i,
|
|
897
|
+
/codestral/i,
|
|
898
|
+
/deepseek-coder/i
|
|
899
|
+
];
|
|
900
|
+
var TINY_PATTERNS = [
|
|
901
|
+
/qwen.*(?:0\.5b|1\.5b|3b|4b)/i,
|
|
902
|
+
/llama.*(?:1b|3b)/i,
|
|
903
|
+
/gemma.*(?:2b|4b)/i,
|
|
904
|
+
/phi.*(?:1|2|3)/i,
|
|
905
|
+
/tinyllama/i,
|
|
906
|
+
/stablelm/i
|
|
907
|
+
];
|
|
908
|
+
function autoDetectPreset(modelId) {
|
|
909
|
+
if (!modelId) return CORRECTION_PRESETS.capable();
|
|
910
|
+
for (const pat of CAPABLE_PATTERNS) {
|
|
911
|
+
if (pat.test(modelId)) return CORRECTION_PRESETS.disabled();
|
|
912
|
+
}
|
|
913
|
+
for (const pat of TINY_PATTERNS) {
|
|
914
|
+
if (pat.test(modelId)) return CORRECTION_PRESETS.tinyModel();
|
|
915
|
+
}
|
|
916
|
+
for (const pat of SMALL_PATTERNS) {
|
|
917
|
+
if (pat.test(modelId)) return CORRECTION_PRESETS.smallModel();
|
|
918
|
+
}
|
|
919
|
+
return CORRECTION_PRESETS.capable();
|
|
920
|
+
}
|
|
921
|
+
|
|
708
922
|
// src/agent/loop.ts
|
|
709
923
|
function getErrorSignature(toolName, result) {
|
|
710
924
|
return {
|
|
@@ -731,7 +945,18 @@ async function runAgentLoop(messages, config) {
|
|
|
731
945
|
let wasCompressed = false;
|
|
732
946
|
const recentErrors = [];
|
|
733
947
|
const MAX_REPEATED_ERRORS = 3;
|
|
948
|
+
const correctionCfg = config.correction ?? autoDetectPreset(config.modelId ?? "");
|
|
949
|
+
let totalCorrectionAttempts = 0;
|
|
950
|
+
let intentResult = "unclear";
|
|
734
951
|
let history = [...messages];
|
|
952
|
+
const toolNames = Object.keys(tools);
|
|
953
|
+
const templateFormatHint = correctionCfg.useTemplateFormat && toolNames.length > 0 ? [
|
|
954
|
+
"",
|
|
955
|
+
"If native tool calling fails, use this fallback format with no extra prose:",
|
|
956
|
+
"TOOL: <tool name>",
|
|
957
|
+
"ARG_<argument_name>: <argument value>",
|
|
958
|
+
`Available tool names: ${toolNames.join(", ")}`
|
|
959
|
+
].join("\n") : "";
|
|
735
960
|
await config.toolContext.runHook?.("pre-compact", { messageCount: history.length });
|
|
736
961
|
history = await autoCompress(history, config.model, contextWindow, () => {
|
|
737
962
|
wasCompressed = true;
|
|
@@ -740,6 +965,13 @@ async function runAgentLoop(messages, config) {
|
|
|
740
965
|
if (wasCompressed) {
|
|
741
966
|
await config.toolContext.runHook?.("post-compact", { messageCount: history.length });
|
|
742
967
|
}
|
|
968
|
+
if (correctionCfg.intentGate && toolNames.length > 0) {
|
|
969
|
+
const lastUserMsg = [...history].reverse().find((m) => m.role === "user");
|
|
970
|
+
const userText = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
971
|
+
if (userText) {
|
|
972
|
+
intentResult = await intentGate(config.model, userText, toolNames);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
743
975
|
while (iterations < maxIter) {
|
|
744
976
|
iterations++;
|
|
745
977
|
const mc = microcompact(history, { keepLastN: 6, threshold: 50 });
|
|
@@ -753,7 +985,7 @@ async function runAgentLoop(messages, config) {
|
|
|
753
985
|
let streamUsage = null;
|
|
754
986
|
const result = streamText({
|
|
755
987
|
model: config.model,
|
|
756
|
-
system: config.systemPrompt
|
|
988
|
+
system: `${config.systemPrompt}${templateFormatHint}`,
|
|
757
989
|
messages: history,
|
|
758
990
|
tools,
|
|
759
991
|
maxSteps: 1
|
|
@@ -792,8 +1024,67 @@ async function runAgentLoop(messages, config) {
|
|
|
792
1024
|
if (streamUsage) {
|
|
793
1025
|
totalPromptTokens += streamUsage.promptTokens ?? 0;
|
|
794
1026
|
totalCompletionTokens += streamUsage.completionTokens ?? 0;
|
|
1027
|
+
void recordShadowUsage(
|
|
1028
|
+
config.model,
|
|
1029
|
+
streamUsage
|
|
1030
|
+
);
|
|
795
1031
|
}
|
|
796
1032
|
totalToolCalls += toolCalls.length;
|
|
1033
|
+
if (toolCalls.length === 0 && fullText && correctionCfg.maxCorrectionAttempts > 0 && toolNames.length > 0) {
|
|
1034
|
+
const directExtracted = extractToolCallsFromText(fullText, toolNames);
|
|
1035
|
+
const templateExtracted = correctionCfg.useTemplateFormat ? extractTemplateToolCalls(fullText, toolNames) : [];
|
|
1036
|
+
const shouldCorrect = looksLikeFailedToolCall(fullText, toolNames) || directExtracted.length > 0 || templateExtracted.length > 0 || intentResult === "yes" && totalToolCalls === 0;
|
|
1037
|
+
if (shouldCorrect) {
|
|
1038
|
+
const corrected = directExtracted.length > 0 ? directExtracted : templateExtracted.length > 0 ? templateExtracted : await correctionPass(
|
|
1039
|
+
config.model,
|
|
1040
|
+
fullText,
|
|
1041
|
+
toolNames,
|
|
1042
|
+
history,
|
|
1043
|
+
correctionCfg.maxCorrectionAttempts
|
|
1044
|
+
);
|
|
1045
|
+
totalCorrectionAttempts++;
|
|
1046
|
+
if (corrected && corrected.length > 0) {
|
|
1047
|
+
config.onCorrection?.(totalCorrectionAttempts, true);
|
|
1048
|
+
for (const tc of corrected) {
|
|
1049
|
+
toolCalls.push({
|
|
1050
|
+
toolCallId: tc.id,
|
|
1051
|
+
toolName: tc.name,
|
|
1052
|
+
args: tc.args
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
totalToolCalls += corrected.length;
|
|
1056
|
+
for (const tc of corrected) {
|
|
1057
|
+
const toolDef = tools[tc.name];
|
|
1058
|
+
if (!toolDef) {
|
|
1059
|
+
toolResults.push({
|
|
1060
|
+
toolCallId: tc.id,
|
|
1061
|
+
result: { content: `Tool "${tc.name}" not found.`, isError: true }
|
|
1062
|
+
});
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
const result2 = await toolDef.execute(tc.args);
|
|
1067
|
+
toolResults.push({ toolCallId: tc.id, result: result2 });
|
|
1068
|
+
config.onToolCall?.(tc.name, tc.args);
|
|
1069
|
+
config.onToolResult?.(
|
|
1070
|
+
tc.name,
|
|
1071
|
+
result2?.content ?? String(result2),
|
|
1072
|
+
result2?.isError ?? false
|
|
1073
|
+
);
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1076
|
+
toolResults.push({
|
|
1077
|
+
toolCallId: tc.id,
|
|
1078
|
+
result: { content: `Error: ${msg}`, isError: true }
|
|
1079
|
+
});
|
|
1080
|
+
config.onToolResult?.(tc.name, `Error: ${msg}`, true);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
config.onCorrection?.(totalCorrectionAttempts, false);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
797
1088
|
if (toolCalls.length > 0) {
|
|
798
1089
|
let hasRepeatedError = false;
|
|
799
1090
|
for (const tr of toolResults) {
|
|
@@ -859,6 +1150,7 @@ async function runAgentLoop(messages, config) {
|
|
|
859
1150
|
iterations,
|
|
860
1151
|
toolCallCount: totalToolCalls,
|
|
861
1152
|
compressed: wasCompressed,
|
|
1153
|
+
correctionAttempts: totalCorrectionAttempts,
|
|
862
1154
|
usage: {
|
|
863
1155
|
promptTokens: totalPromptTokens,
|
|
864
1156
|
completionTokens: totalCompletionTokens,
|
|
@@ -874,6 +1166,7 @@ async function runAgentLoop(messages, config) {
|
|
|
874
1166
|
iterations,
|
|
875
1167
|
toolCallCount: totalToolCalls,
|
|
876
1168
|
compressed: wasCompressed,
|
|
1169
|
+
correctionAttempts: totalCorrectionAttempts,
|
|
877
1170
|
usage: {
|
|
878
1171
|
promptTokens: totalPromptTokens,
|
|
879
1172
|
completionTokens: totalCompletionTokens,
|
|
@@ -881,7 +1174,7 @@ async function runAgentLoop(messages, config) {
|
|
|
881
1174
|
}
|
|
882
1175
|
};
|
|
883
1176
|
}
|
|
884
|
-
async function buildSystemPrompt(projectRoot, _modelId) {
|
|
1177
|
+
async function buildSystemPrompt(projectRoot, _modelId, toolContext) {
|
|
885
1178
|
const sections = [
|
|
886
1179
|
// =========================================================
|
|
887
1180
|
// CACHEABLE PREFIX — stable across turns, sessions, users.
|
|
@@ -892,7 +1185,7 @@ async function buildSystemPrompt(projectRoot, _modelId) {
|
|
|
892
1185
|
() => [
|
|
893
1186
|
"You are Notch, an expert AI coding assistant built by Driftrail.",
|
|
894
1187
|
"You help developers write, debug, refactor, and understand code.",
|
|
895
|
-
"You have access to tools
|
|
1188
|
+
"You have access to the tools listed in the Tool Schemas section."
|
|
896
1189
|
].join("\n")
|
|
897
1190
|
),
|
|
898
1191
|
safeSection(
|
|
@@ -928,8 +1221,11 @@ async function buildSystemPrompt(projectRoot, _modelId) {
|
|
|
928
1221
|
"- When the user asks for a fix, fix the root cause, not the symptom."
|
|
929
1222
|
].join("\n")
|
|
930
1223
|
),
|
|
931
|
-
|
|
932
|
-
|
|
1224
|
+
DANGEROUS_uncachedSystemPromptSection(
|
|
1225
|
+
"tool-schemas",
|
|
1226
|
+
() => describeToolSchemas(toolContext),
|
|
1227
|
+
"The active tool set depends on coordinator mode, MCP servers, plugins, and permissions for this session."
|
|
1228
|
+
),
|
|
933
1229
|
safeSection("builtin-agents", () => {
|
|
934
1230
|
try {
|
|
935
1231
|
const agents = listBuiltinAgents();
|
|
@@ -954,7 +1250,7 @@ ${describeTools()}`),
|
|
|
954
1250
|
lines.push(`- Platform: ${process.platform}`);
|
|
955
1251
|
lines.push(`- Current date (UTC): ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
|
|
956
1252
|
try {
|
|
957
|
-
const entries = await
|
|
1253
|
+
const entries = await fs4.readdir(projectRoot);
|
|
958
1254
|
const preview = entries.filter((e) => !e.startsWith(".")).slice(0, 30).join(", ");
|
|
959
1255
|
if (preview) lines.push(`- Top-level entries: ${preview}`);
|
|
960
1256
|
} catch {
|
|
@@ -1009,8 +1305,8 @@ ${memoryStr}`, {
|
|
|
1009
1305
|
}
|
|
1010
1306
|
|
|
1011
1307
|
// src/agent/checkpoints.ts
|
|
1012
|
-
import
|
|
1013
|
-
import
|
|
1308
|
+
import fs5 from "fs/promises";
|
|
1309
|
+
import path3 from "path";
|
|
1014
1310
|
var CheckpointManager = class {
|
|
1015
1311
|
checkpoints = [];
|
|
1016
1312
|
nextId = 1;
|
|
@@ -1019,7 +1315,7 @@ var CheckpointManager = class {
|
|
|
1019
1315
|
async recordBefore(filePath) {
|
|
1020
1316
|
if (this.pendingFiles.has(filePath)) return;
|
|
1021
1317
|
try {
|
|
1022
|
-
const content = await
|
|
1318
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
1023
1319
|
this.pendingFiles.set(filePath, content);
|
|
1024
1320
|
} catch {
|
|
1025
1321
|
this.pendingFiles.set(filePath, null);
|
|
@@ -1031,7 +1327,7 @@ var CheckpointManager = class {
|
|
|
1031
1327
|
for (const [filePath, before] of this.pendingFiles) {
|
|
1032
1328
|
let after = null;
|
|
1033
1329
|
try {
|
|
1034
|
-
after = await
|
|
1330
|
+
after = await fs5.readFile(filePath, "utf-8");
|
|
1035
1331
|
} catch {
|
|
1036
1332
|
}
|
|
1037
1333
|
files.push({ path: filePath, before, after });
|
|
@@ -1053,12 +1349,12 @@ var CheckpointManager = class {
|
|
|
1053
1349
|
for (const snap of checkpoint.files) {
|
|
1054
1350
|
if (snap.before === null) {
|
|
1055
1351
|
try {
|
|
1056
|
-
await
|
|
1352
|
+
await fs5.unlink(snap.path);
|
|
1057
1353
|
} catch {
|
|
1058
1354
|
}
|
|
1059
1355
|
} else {
|
|
1060
|
-
await
|
|
1061
|
-
await
|
|
1356
|
+
await fs5.mkdir(path3.dirname(snap.path), { recursive: true });
|
|
1357
|
+
await fs5.writeFile(snap.path, snap.before, "utf-8");
|
|
1062
1358
|
}
|
|
1063
1359
|
}
|
|
1064
1360
|
return checkpoint;
|
|
@@ -1090,8 +1386,8 @@ var CheckpointManager = class {
|
|
|
1090
1386
|
}
|
|
1091
1387
|
}
|
|
1092
1388
|
}
|
|
1093
|
-
return Array.from(fileMap.entries()).map(([
|
|
1094
|
-
path:
|
|
1389
|
+
return Array.from(fileMap.entries()).map(([path20, { before, after }]) => ({
|
|
1390
|
+
path: path20,
|
|
1095
1391
|
before,
|
|
1096
1392
|
after
|
|
1097
1393
|
}));
|
|
@@ -1489,14 +1785,14 @@ var CostTracker = class {
|
|
|
1489
1785
|
};
|
|
1490
1786
|
|
|
1491
1787
|
// src/agent/ralph.ts
|
|
1492
|
-
import
|
|
1493
|
-
import
|
|
1788
|
+
import fs7 from "fs/promises";
|
|
1789
|
+
import path5 from "path";
|
|
1494
1790
|
import chalk4 from "chalk";
|
|
1495
1791
|
import { generateText as generateText2, streamText as streamText2 } from "ai";
|
|
1496
1792
|
|
|
1497
1793
|
// src/context/repo-map.ts
|
|
1498
|
-
import
|
|
1499
|
-
import
|
|
1794
|
+
import fs6 from "fs/promises";
|
|
1795
|
+
import path4 from "path";
|
|
1500
1796
|
import { glob } from "glob";
|
|
1501
1797
|
var PATTERNS = {
|
|
1502
1798
|
ts: [
|
|
@@ -1527,7 +1823,7 @@ var PATTERNS = {
|
|
|
1527
1823
|
};
|
|
1528
1824
|
var IMPORT_PATTERN = /(?:import|from)\s+['"]([^'"]+)['"]/g;
|
|
1529
1825
|
function getPatterns(filePath) {
|
|
1530
|
-
const ext2 =
|
|
1826
|
+
const ext2 = path4.extname(filePath).slice(1);
|
|
1531
1827
|
if (["ts", "tsx", "mts", "cts"].includes(ext2)) return PATTERNS.ts;
|
|
1532
1828
|
if (["js", "jsx", "mjs", "cjs"].includes(ext2)) return PATTERNS.js;
|
|
1533
1829
|
if (ext2 === "py") return PATTERNS.py;
|
|
@@ -1607,9 +1903,9 @@ async function buildRepoMap(root) {
|
|
|
1607
1903
|
const entries = [];
|
|
1608
1904
|
files.sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
|
|
1609
1905
|
for (const file of files.slice(0, 300)) {
|
|
1610
|
-
const fullPath =
|
|
1906
|
+
const fullPath = path4.resolve(root, file);
|
|
1611
1907
|
try {
|
|
1612
|
-
const content = await
|
|
1908
|
+
const content = await fs6.readFile(fullPath, "utf-8");
|
|
1613
1909
|
const lines = content.split("\n").length;
|
|
1614
1910
|
const patterns = getPatterns(file);
|
|
1615
1911
|
const symbols = extractSymbols(content, patterns);
|
|
@@ -1711,11 +2007,11 @@ ${repoContext || "(empty project)"}`;
|
|
|
1711
2007
|
async function savePlan(plan, cwd) {
|
|
1712
2008
|
plan.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1713
2009
|
plan.completedCount = plan.tasks.filter((t) => t.status === "done").length;
|
|
1714
|
-
await
|
|
2010
|
+
await fs7.writeFile(path5.join(cwd, PLAN_FILE), JSON.stringify(plan, null, 2), "utf-8");
|
|
1715
2011
|
}
|
|
1716
2012
|
async function loadPlan(cwd) {
|
|
1717
2013
|
try {
|
|
1718
|
-
const raw = await
|
|
2014
|
+
const raw = await fs7.readFile(path5.join(cwd, PLAN_FILE), "utf-8");
|
|
1719
2015
|
return JSON.parse(raw);
|
|
1720
2016
|
} catch {
|
|
1721
2017
|
return null;
|
|
@@ -1723,7 +2019,7 @@ async function loadPlan(cwd) {
|
|
|
1723
2019
|
}
|
|
1724
2020
|
async function deletePlan(cwd) {
|
|
1725
2021
|
try {
|
|
1726
|
-
await
|
|
2022
|
+
await fs7.unlink(path5.join(cwd, PLAN_FILE));
|
|
1727
2023
|
} catch {
|
|
1728
2024
|
}
|
|
1729
2025
|
}
|
|
@@ -1758,8 +2054,7 @@ ${toolContext.cwd}
|
|
|
1758
2054
|
## Repository
|
|
1759
2055
|
${repoContext}
|
|
1760
2056
|
|
|
1761
|
-
|
|
1762
|
-
${describeTools()}
|
|
2057
|
+
${describeToolSchemas(toolContext)}
|
|
1763
2058
|
|
|
1764
2059
|
## Instructions
|
|
1765
2060
|
1. Read relevant files to understand the current state
|
|
@@ -1906,8 +2201,8 @@ function formatRalphStatus(plan) {
|
|
|
1906
2201
|
}
|
|
1907
2202
|
|
|
1908
2203
|
// src/context/references.ts
|
|
1909
|
-
import
|
|
1910
|
-
import
|
|
2204
|
+
import fs8 from "fs/promises";
|
|
2205
|
+
import path6 from "path";
|
|
1911
2206
|
import { glob as glob2 } from "glob";
|
|
1912
2207
|
async function resolveReferences(input, cwd) {
|
|
1913
2208
|
const references = [];
|
|
@@ -1950,9 +2245,9 @@ ${truncated}
|
|
|
1950
2245
|
return sections.join("\n\n") + "\n\n";
|
|
1951
2246
|
}
|
|
1952
2247
|
async function resolveFile(ref, cwd) {
|
|
1953
|
-
const filePath =
|
|
2248
|
+
const filePath = path6.isAbsolute(ref) ? ref : path6.resolve(cwd, ref);
|
|
1954
2249
|
try {
|
|
1955
|
-
const content = await
|
|
2250
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
1956
2251
|
const lines = content.split("\n");
|
|
1957
2252
|
const numbered = lines.map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
|
|
1958
2253
|
return {
|
|
@@ -2677,17 +2972,17 @@ function formatTokens(n) {
|
|
|
2677
2972
|
}
|
|
2678
2973
|
|
|
2679
2974
|
// src/ui/update-checker.ts
|
|
2680
|
-
import
|
|
2681
|
-
import
|
|
2975
|
+
import fs9 from "fs/promises";
|
|
2976
|
+
import path7 from "path";
|
|
2682
2977
|
import os4 from "os";
|
|
2683
2978
|
import { execSync as execSync2, spawnSync } from "child_process";
|
|
2684
2979
|
var PACKAGE_NAME = "@freesyntax/notch-cli";
|
|
2685
2980
|
var CHECK_INTERVAL_HOURS = 4;
|
|
2686
2981
|
var CHECK_INTERVAL_MS = CHECK_INTERVAL_HOURS * 60 * 60 * 1e3;
|
|
2687
|
-
var NOTCH_DIR =
|
|
2688
|
-
var CACHE_FILE =
|
|
2689
|
-
var LOG_FILE =
|
|
2690
|
-
var CONFIG_FILE =
|
|
2982
|
+
var NOTCH_DIR = path7.join(os4.homedir(), ".notch");
|
|
2983
|
+
var CACHE_FILE = path7.join(NOTCH_DIR, "update-check.json");
|
|
2984
|
+
var LOG_FILE = path7.join(NOTCH_DIR, "update-log.txt");
|
|
2985
|
+
var CONFIG_FILE = path7.join(NOTCH_DIR, "config.json");
|
|
2691
2986
|
var MAX_LOG_LINES = 500;
|
|
2692
2987
|
var REGISTRY_BASE = "https://registry.npmjs.org";
|
|
2693
2988
|
var FETCH_TIMEOUT_MS2 = 5e3;
|
|
@@ -2822,7 +3117,7 @@ async function saveChannel(channel) {
|
|
|
2822
3117
|
}
|
|
2823
3118
|
async function readUserConfig() {
|
|
2824
3119
|
try {
|
|
2825
|
-
const raw = await
|
|
3120
|
+
const raw = await fs9.readFile(CONFIG_FILE, "utf-8");
|
|
2826
3121
|
const parsed = JSON.parse(raw);
|
|
2827
3122
|
return typeof parsed === "object" && parsed ? parsed : null;
|
|
2828
3123
|
} catch {
|
|
@@ -2831,7 +3126,7 @@ async function readUserConfig() {
|
|
|
2831
3126
|
}
|
|
2832
3127
|
async function loadCache() {
|
|
2833
3128
|
try {
|
|
2834
|
-
const raw = await
|
|
3129
|
+
const raw = await fs9.readFile(CACHE_FILE, "utf-8");
|
|
2835
3130
|
const parsed = JSON.parse(raw);
|
|
2836
3131
|
if (parsed && typeof parsed === "object" && typeof parsed.lastCheck === "number" && (parsed.latestVersion === null || typeof parsed.latestVersion === "string")) {
|
|
2837
3132
|
const channel = isValidChannel(parsed.channel) ? parsed.channel : "latest";
|
|
@@ -2858,7 +3153,7 @@ async function detectInstallLocation() {
|
|
|
2858
3153
|
if (!entry) return "unknown";
|
|
2859
3154
|
let entryReal;
|
|
2860
3155
|
try {
|
|
2861
|
-
entryReal = await
|
|
3156
|
+
entryReal = await fs9.realpath(entry);
|
|
2862
3157
|
} catch {
|
|
2863
3158
|
entryReal = entry;
|
|
2864
3159
|
}
|
|
@@ -2867,11 +3162,11 @@ async function detectInstallLocation() {
|
|
|
2867
3162
|
return "global";
|
|
2868
3163
|
}
|
|
2869
3164
|
let dir = process.cwd();
|
|
2870
|
-
const root =
|
|
3165
|
+
const root = path7.parse(dir).root;
|
|
2871
3166
|
while (dir !== root) {
|
|
2872
|
-
const localNM =
|
|
3167
|
+
const localNM = path7.join(dir, "node_modules");
|
|
2873
3168
|
if (pathIsInside(entryReal, localNM)) return "local";
|
|
2874
|
-
const parent =
|
|
3169
|
+
const parent = path7.dirname(dir);
|
|
2875
3170
|
if (parent === dir) break;
|
|
2876
3171
|
dir = parent;
|
|
2877
3172
|
}
|
|
@@ -2881,8 +3176,8 @@ async function detectInstallLocation() {
|
|
|
2881
3176
|
}
|
|
2882
3177
|
function pathIsInside(child, parent) {
|
|
2883
3178
|
if (!child || !parent) return false;
|
|
2884
|
-
const rel =
|
|
2885
|
-
return !!rel && !rel.startsWith("..") && !
|
|
3179
|
+
const rel = path7.relative(path7.resolve(parent), path7.resolve(child));
|
|
3180
|
+
return !!rel && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
2886
3181
|
}
|
|
2887
3182
|
async function tryExec(cmd) {
|
|
2888
3183
|
try {
|
|
@@ -2940,7 +3235,7 @@ function splitPre(v) {
|
|
|
2940
3235
|
return [v.slice(0, i), v.slice(i + 1)];
|
|
2941
3236
|
}
|
|
2942
3237
|
async function ensureNotchDir() {
|
|
2943
|
-
await
|
|
3238
|
+
await fs9.mkdir(NOTCH_DIR, { recursive: true });
|
|
2944
3239
|
}
|
|
2945
3240
|
async function appendLog(message) {
|
|
2946
3241
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`;
|
|
@@ -2951,7 +3246,7 @@ async function appendLogRaw(line) {
|
|
|
2951
3246
|
await ensureNotchDir();
|
|
2952
3247
|
let existing = "";
|
|
2953
3248
|
try {
|
|
2954
|
-
existing = await
|
|
3249
|
+
existing = await fs9.readFile(LOG_FILE, "utf-8");
|
|
2955
3250
|
} catch {
|
|
2956
3251
|
existing = "";
|
|
2957
3252
|
}
|
|
@@ -2964,16 +3259,16 @@ async function appendLogRaw(line) {
|
|
|
2964
3259
|
}
|
|
2965
3260
|
async function atomicWrite(file, contents) {
|
|
2966
3261
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
2967
|
-
await
|
|
3262
|
+
await fs9.writeFile(tmp, contents, "utf-8");
|
|
2968
3263
|
try {
|
|
2969
|
-
await
|
|
3264
|
+
await fs9.rename(tmp, file);
|
|
2970
3265
|
} catch (err) {
|
|
2971
3266
|
try {
|
|
2972
|
-
await
|
|
2973
|
-
await
|
|
3267
|
+
await fs9.copyFile(tmp, file);
|
|
3268
|
+
await fs9.unlink(tmp).catch(() => {
|
|
2974
3269
|
});
|
|
2975
3270
|
} catch {
|
|
2976
|
-
await
|
|
3271
|
+
await fs9.unlink(tmp).catch(() => {
|
|
2977
3272
|
});
|
|
2978
3273
|
throw err;
|
|
2979
3274
|
}
|
|
@@ -2995,16 +3290,16 @@ function indent(block, spaces = 2) {
|
|
|
2995
3290
|
// src/commands/update.ts
|
|
2996
3291
|
import chalk7 from "chalk";
|
|
2997
3292
|
import { createRequire } from "module";
|
|
2998
|
-
import
|
|
3293
|
+
import path8 from "path";
|
|
2999
3294
|
import { fileURLToPath } from "url";
|
|
3000
3295
|
var _require = createRequire(import.meta.url);
|
|
3001
3296
|
function resolveVersion() {
|
|
3002
3297
|
try {
|
|
3003
3298
|
const entry = process.argv[1];
|
|
3004
|
-
let dir = entry ?
|
|
3005
|
-
const root =
|
|
3299
|
+
let dir = entry ? path8.dirname(entry) : fileURLToPath(new URL(".", import.meta.url));
|
|
3300
|
+
const root = path8.parse(dir).root;
|
|
3006
3301
|
while (dir && dir !== root) {
|
|
3007
|
-
const candidate =
|
|
3302
|
+
const candidate = path8.join(dir, "package.json");
|
|
3008
3303
|
try {
|
|
3009
3304
|
const pkg = _require(candidate);
|
|
3010
3305
|
if (pkg && pkg.name === PACKAGE_NAME && typeof pkg.version === "string") {
|
|
@@ -3012,7 +3307,7 @@ function resolveVersion() {
|
|
|
3012
3307
|
}
|
|
3013
3308
|
} catch {
|
|
3014
3309
|
}
|
|
3015
|
-
const parent =
|
|
3310
|
+
const parent = path8.dirname(dir);
|
|
3016
3311
|
if (parent === dir) break;
|
|
3017
3312
|
dir = parent;
|
|
3018
3313
|
}
|
|
@@ -3134,8 +3429,8 @@ async function runUpdateCli(argv) {
|
|
|
3134
3429
|
}
|
|
3135
3430
|
|
|
3136
3431
|
// src/permissions/index.ts
|
|
3137
|
-
import
|
|
3138
|
-
import
|
|
3432
|
+
import fs10 from "fs/promises";
|
|
3433
|
+
import path10 from "path";
|
|
3139
3434
|
import os5 from "os";
|
|
3140
3435
|
|
|
3141
3436
|
// node_modules/balanced-match/dist/esm/index.js
|
|
@@ -4196,11 +4491,11 @@ var qmarksTestNoExtDot = ([$0]) => {
|
|
|
4196
4491
|
return (f) => f.length === len && f !== "." && f !== "..";
|
|
4197
4492
|
};
|
|
4198
4493
|
var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
|
|
4199
|
-
var
|
|
4494
|
+
var path9 = {
|
|
4200
4495
|
win32: { sep: "\\" },
|
|
4201
4496
|
posix: { sep: "/" }
|
|
4202
4497
|
};
|
|
4203
|
-
var sep = defaultPlatform === "win32" ?
|
|
4498
|
+
var sep = defaultPlatform === "win32" ? path9.win32.sep : path9.posix.sep;
|
|
4204
4499
|
minimatch.sep = sep;
|
|
4205
4500
|
var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
|
|
4206
4501
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
@@ -4997,11 +5292,11 @@ var DEFAULT_PERMISSIONS = {
|
|
|
4997
5292
|
denyWriteGlobs: [...DEFAULT_DENY_WRITE_GLOBS]
|
|
4998
5293
|
};
|
|
4999
5294
|
async function loadPermissions(projectRoot) {
|
|
5000
|
-
const projectPath =
|
|
5001
|
-
const globalPath =
|
|
5295
|
+
const projectPath = path10.join(projectRoot, ".notch.json");
|
|
5296
|
+
const globalPath = path10.join(os5.homedir(), ".notch", "permissions.json");
|
|
5002
5297
|
let config = { ...DEFAULT_PERMISSIONS };
|
|
5003
5298
|
try {
|
|
5004
|
-
const raw = await
|
|
5299
|
+
const raw = await fs10.readFile(globalPath, "utf-8");
|
|
5005
5300
|
const parsed = JSON.parse(raw);
|
|
5006
5301
|
if (parsed.permissions) {
|
|
5007
5302
|
config = mergePermissions(config, parsed.permissions);
|
|
@@ -5009,7 +5304,7 @@ async function loadPermissions(projectRoot) {
|
|
|
5009
5304
|
} catch {
|
|
5010
5305
|
}
|
|
5011
5306
|
try {
|
|
5012
|
-
const raw = await
|
|
5307
|
+
const raw = await fs10.readFile(projectPath, "utf-8");
|
|
5013
5308
|
const parsed = JSON.parse(raw);
|
|
5014
5309
|
if (parsed.permissions) {
|
|
5015
5310
|
config = mergePermissions(config, parsed.permissions);
|
|
@@ -5149,17 +5444,17 @@ function mergePermissions(base, override) {
|
|
|
5149
5444
|
|
|
5150
5445
|
// src/hooks/index.ts
|
|
5151
5446
|
import { execSync as execSync3 } from "child_process";
|
|
5152
|
-
import
|
|
5447
|
+
import fs11 from "fs/promises";
|
|
5153
5448
|
import { watch } from "fs";
|
|
5154
|
-
import
|
|
5449
|
+
import path11 from "path";
|
|
5155
5450
|
import os6 from "os";
|
|
5156
5451
|
import crypto from "crypto";
|
|
5157
|
-
var TRUST_STORE_PATH =
|
|
5452
|
+
var TRUST_STORE_PATH = path11.join(os6.homedir(), ".notch", "trusted-projects.json");
|
|
5158
5453
|
async function isTrustedProject(projectRoot, raw) {
|
|
5159
5454
|
const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
|
|
5160
|
-
const key =
|
|
5455
|
+
const key = path11.resolve(projectRoot);
|
|
5161
5456
|
try {
|
|
5162
|
-
const store = JSON.parse(await
|
|
5457
|
+
const store = JSON.parse(await fs11.readFile(TRUST_STORE_PATH, "utf-8"));
|
|
5163
5458
|
return store[key] === fingerprint;
|
|
5164
5459
|
} catch {
|
|
5165
5460
|
return false;
|
|
@@ -5167,30 +5462,30 @@ async function isTrustedProject(projectRoot, raw) {
|
|
|
5167
5462
|
}
|
|
5168
5463
|
async function trustProject(projectRoot, raw) {
|
|
5169
5464
|
const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
|
|
5170
|
-
const key =
|
|
5465
|
+
const key = path11.resolve(projectRoot);
|
|
5171
5466
|
let store = {};
|
|
5172
5467
|
try {
|
|
5173
|
-
store = JSON.parse(await
|
|
5468
|
+
store = JSON.parse(await fs11.readFile(TRUST_STORE_PATH, "utf-8"));
|
|
5174
5469
|
} catch {
|
|
5175
5470
|
}
|
|
5176
5471
|
store[key] = fingerprint;
|
|
5177
|
-
await
|
|
5178
|
-
await
|
|
5472
|
+
await fs11.mkdir(path11.dirname(TRUST_STORE_PATH), { recursive: true });
|
|
5473
|
+
await fs11.writeFile(TRUST_STORE_PATH, JSON.stringify(store, null, 2));
|
|
5179
5474
|
}
|
|
5180
5475
|
async function loadHooks(projectRoot, promptTrust) {
|
|
5181
5476
|
const hooks = [];
|
|
5182
|
-
const globalPath =
|
|
5477
|
+
const globalPath = path11.join(os6.homedir(), ".notch", "hooks.json");
|
|
5183
5478
|
try {
|
|
5184
|
-
const raw = await
|
|
5479
|
+
const raw = await fs11.readFile(globalPath, "utf-8");
|
|
5185
5480
|
const parsed = JSON.parse(raw);
|
|
5186
5481
|
if (Array.isArray(parsed.hooks)) {
|
|
5187
5482
|
hooks.push(...parsed.hooks);
|
|
5188
5483
|
}
|
|
5189
5484
|
} catch {
|
|
5190
5485
|
}
|
|
5191
|
-
const projectPath =
|
|
5486
|
+
const projectPath = path11.join(projectRoot, ".notch.json");
|
|
5192
5487
|
try {
|
|
5193
|
-
const raw = await
|
|
5488
|
+
const raw = await fs11.readFile(projectPath, "utf-8");
|
|
5194
5489
|
const parsed = JSON.parse(raw);
|
|
5195
5490
|
if (Array.isArray(parsed.hooks) && parsed.hooks.length > 0) {
|
|
5196
5491
|
const alreadyTrusted = await isTrustedProject(projectRoot, raw);
|
|
@@ -5285,7 +5580,7 @@ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
|
|
|
5285
5580
|
if (existing) clearTimeout(existing);
|
|
5286
5581
|
pending.set(filename, setTimeout(async () => {
|
|
5287
5582
|
pending.delete(filename);
|
|
5288
|
-
const filePath =
|
|
5583
|
+
const filePath = path11.join(projectRoot, filename);
|
|
5289
5584
|
const context = { cwd: projectRoot, file: filePath };
|
|
5290
5585
|
const { results } = await runHooks(hookConfig, "file-changed", context);
|
|
5291
5586
|
onHookResult?.("file-changed", results);
|
|
@@ -5305,8 +5600,8 @@ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
|
|
|
5305
5600
|
}
|
|
5306
5601
|
|
|
5307
5602
|
// src/session/index.ts
|
|
5308
|
-
import
|
|
5309
|
-
import
|
|
5603
|
+
import fs12 from "fs/promises";
|
|
5604
|
+
import path12 from "path";
|
|
5310
5605
|
import os7 from "os";
|
|
5311
5606
|
|
|
5312
5607
|
// src/session/fork.ts
|
|
@@ -5316,13 +5611,13 @@ import fsp from "fs/promises";
|
|
|
5316
5611
|
import fsp2 from "fs/promises";
|
|
5317
5612
|
|
|
5318
5613
|
// src/session/index.ts
|
|
5319
|
-
var SESSION_DIR =
|
|
5614
|
+
var SESSION_DIR = path12.join(os7.homedir(), ".notch", "sessions");
|
|
5320
5615
|
var MAX_SESSIONS = 20;
|
|
5321
5616
|
async function ensureDir2() {
|
|
5322
|
-
await
|
|
5617
|
+
await fs12.mkdir(SESSION_DIR, { recursive: true });
|
|
5323
5618
|
}
|
|
5324
5619
|
function sessionPath(id) {
|
|
5325
|
-
return
|
|
5620
|
+
return path12.join(SESSION_DIR, `${id}.json`);
|
|
5326
5621
|
}
|
|
5327
5622
|
function generateId() {
|
|
5328
5623
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -5349,13 +5644,13 @@ async function saveSession(messages, project, model, existingId) {
|
|
|
5349
5644
|
},
|
|
5350
5645
|
messages
|
|
5351
5646
|
};
|
|
5352
|
-
await
|
|
5647
|
+
await fs12.writeFile(sessionPath(id), JSON.stringify(session, null, 2), "utf-8");
|
|
5353
5648
|
await pruneOldSessions();
|
|
5354
5649
|
return id;
|
|
5355
5650
|
}
|
|
5356
5651
|
async function loadSession(id) {
|
|
5357
5652
|
try {
|
|
5358
|
-
const raw = await
|
|
5653
|
+
const raw = await fs12.readFile(sessionPath(id), "utf-8");
|
|
5359
5654
|
return JSON.parse(raw);
|
|
5360
5655
|
} catch {
|
|
5361
5656
|
return null;
|
|
@@ -5363,12 +5658,12 @@ async function loadSession(id) {
|
|
|
5363
5658
|
}
|
|
5364
5659
|
async function listSessions() {
|
|
5365
5660
|
await ensureDir2();
|
|
5366
|
-
const files = await
|
|
5661
|
+
const files = await fs12.readdir(SESSION_DIR);
|
|
5367
5662
|
const sessions = [];
|
|
5368
5663
|
for (const file of files) {
|
|
5369
5664
|
if (!file.endsWith(".json")) continue;
|
|
5370
5665
|
try {
|
|
5371
|
-
const raw = await
|
|
5666
|
+
const raw = await fs12.readFile(path12.join(SESSION_DIR, file), "utf-8");
|
|
5372
5667
|
const session = JSON.parse(raw);
|
|
5373
5668
|
sessions.push(session.meta);
|
|
5374
5669
|
} catch {
|
|
@@ -5384,7 +5679,7 @@ async function loadLastSession(project) {
|
|
|
5384
5679
|
}
|
|
5385
5680
|
async function deleteSession(id) {
|
|
5386
5681
|
try {
|
|
5387
|
-
await
|
|
5682
|
+
await fs12.unlink(sessionPath(id));
|
|
5388
5683
|
return true;
|
|
5389
5684
|
} catch {
|
|
5390
5685
|
return false;
|
|
@@ -5438,17 +5733,17 @@ async function exportSession(messages, outputPath, meta) {
|
|
|
5438
5733
|
lines.push("");
|
|
5439
5734
|
}
|
|
5440
5735
|
}
|
|
5441
|
-
await
|
|
5736
|
+
await fs12.writeFile(outputPath, lines.join("\n"), "utf-8");
|
|
5442
5737
|
return outputPath;
|
|
5443
5738
|
}
|
|
5444
5739
|
|
|
5445
5740
|
// src/init.ts
|
|
5446
|
-
import
|
|
5447
|
-
import
|
|
5741
|
+
import fs13 from "fs/promises";
|
|
5742
|
+
import path13 from "path";
|
|
5448
5743
|
import chalk8 from "chalk";
|
|
5449
5744
|
async function fileExists(p) {
|
|
5450
5745
|
try {
|
|
5451
|
-
await
|
|
5746
|
+
await fs13.access(p);
|
|
5452
5747
|
return true;
|
|
5453
5748
|
} catch {
|
|
5454
5749
|
return false;
|
|
@@ -5456,18 +5751,18 @@ async function fileExists(p) {
|
|
|
5456
5751
|
}
|
|
5457
5752
|
async function writeIfMissing(p, content, ctx) {
|
|
5458
5753
|
if (await fileExists(p)) {
|
|
5459
|
-
ctx.log(chalk8.gray(` Skipped ${
|
|
5754
|
+
ctx.log(chalk8.gray(` Skipped ${path13.relative(ctx.projectRoot, p)} (already exists)`));
|
|
5460
5755
|
return false;
|
|
5461
5756
|
}
|
|
5462
|
-
await
|
|
5463
|
-
await
|
|
5464
|
-
ctx.log(chalk8.green(` Created ${
|
|
5757
|
+
await fs13.mkdir(path13.dirname(p), { recursive: true });
|
|
5758
|
+
await fs13.writeFile(p, content, "utf-8");
|
|
5759
|
+
ctx.log(chalk8.green(` Created ${path13.relative(ctx.projectRoot, p)}`));
|
|
5465
5760
|
return true;
|
|
5466
5761
|
}
|
|
5467
5762
|
async function patchJson(p, patch, ctx) {
|
|
5468
5763
|
let current = {};
|
|
5469
5764
|
if (await fileExists(p)) {
|
|
5470
|
-
const raw = await
|
|
5765
|
+
const raw = await fs13.readFile(p, "utf-8");
|
|
5471
5766
|
try {
|
|
5472
5767
|
current = JSON.parse(raw);
|
|
5473
5768
|
} catch {
|
|
@@ -5475,35 +5770,35 @@ async function patchJson(p, patch, ctx) {
|
|
|
5475
5770
|
}
|
|
5476
5771
|
}
|
|
5477
5772
|
const next = patch(current);
|
|
5478
|
-
await
|
|
5479
|
-
ctx.log(chalk8.green(` Patched ${
|
|
5773
|
+
await fs13.writeFile(p, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
5774
|
+
ctx.log(chalk8.green(` Patched ${path13.relative(ctx.projectRoot, p)}`));
|
|
5480
5775
|
}
|
|
5481
5776
|
async function ensureGitignoreEntries(p, entries, sectionTitle, ctx) {
|
|
5482
5777
|
if (!await fileExists(p)) return;
|
|
5483
|
-
const current = await
|
|
5778
|
+
const current = await fs13.readFile(p, "utf-8");
|
|
5484
5779
|
const missing = entries.filter((e) => !current.includes(e));
|
|
5485
5780
|
if (missing.length === 0) return;
|
|
5486
5781
|
const append = `
|
|
5487
5782
|
# ${sectionTitle}
|
|
5488
5783
|
` + missing.join("\n") + "\n";
|
|
5489
|
-
await
|
|
5784
|
+
await fs13.appendFile(p, append, "utf-8");
|
|
5490
5785
|
ctx.log(chalk8.green(` Updated .gitignore (+${missing.length})`));
|
|
5491
5786
|
}
|
|
5492
5787
|
var DEFAULT_CONFIG = {
|
|
5493
|
-
model: "
|
|
5788
|
+
model: "openrouter/anthropic/claude-sonnet-4-6",
|
|
5494
5789
|
temperature: 0.3,
|
|
5495
5790
|
maxIterations: 25,
|
|
5496
5791
|
useRepoMap: true,
|
|
5497
5792
|
renderMarkdown: true,
|
|
5498
5793
|
permissionMode: "auto",
|
|
5499
|
-
//
|
|
5500
|
-
// Uncomment `
|
|
5794
|
+
// Provider passthrough — disabled by default.
|
|
5795
|
+
// Uncomment `provider` below (and remove the "_" prefix) to route every
|
|
5501
5796
|
// request to an OpenAI-compatible endpoint (OpenAI / Anthropic /
|
|
5502
|
-
// OpenRouter / Together / Fireworks / Groq / Ollama / vLLM / LM Studio
|
|
5797
|
+
// OpenRouter / Google / DeepSeek / Together / Fireworks / Groq / Ollama / vLLM / LM Studio
|
|
5503
5798
|
// / custom). Providers read their key from the corresponding env var —
|
|
5504
5799
|
// e.g. OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY.
|
|
5505
5800
|
// Run `notch --list-providers` to see all built-in ids.
|
|
5506
|
-
|
|
5801
|
+
_providerExample: {
|
|
5507
5802
|
provider: "openrouter",
|
|
5508
5803
|
model: "anthropic/claude-sonnet-4-6"
|
|
5509
5804
|
}
|
|
@@ -5536,17 +5831,17 @@ var baseInstaller = {
|
|
|
5536
5831
|
required: true,
|
|
5537
5832
|
async run(ctx) {
|
|
5538
5833
|
await writeIfMissing(
|
|
5539
|
-
|
|
5834
|
+
path13.join(ctx.projectRoot, ".notch.json"),
|
|
5540
5835
|
JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n",
|
|
5541
5836
|
ctx
|
|
5542
5837
|
);
|
|
5543
5838
|
await writeIfMissing(
|
|
5544
|
-
|
|
5839
|
+
path13.join(ctx.projectRoot, ".notch.md"),
|
|
5545
5840
|
DEFAULT_INSTRUCTIONS,
|
|
5546
5841
|
ctx
|
|
5547
5842
|
);
|
|
5548
5843
|
await ensureGitignoreEntries(
|
|
5549
|
-
|
|
5844
|
+
path13.join(ctx.projectRoot, ".gitignore"),
|
|
5550
5845
|
[".notch.json"],
|
|
5551
5846
|
"Notch CLI",
|
|
5552
5847
|
ctx
|
|
@@ -5559,7 +5854,7 @@ var mcpInstaller = {
|
|
|
5559
5854
|
description: "Add the mcpServers block to .notch.json (example: github)",
|
|
5560
5855
|
async run(ctx) {
|
|
5561
5856
|
await patchJson(
|
|
5562
|
-
|
|
5857
|
+
path13.join(ctx.projectRoot, ".notch.json"),
|
|
5563
5858
|
(cur) => ({
|
|
5564
5859
|
...cur,
|
|
5565
5860
|
mcpServers: cur.mcpServers ?? {
|
|
@@ -5580,7 +5875,7 @@ var hooksInstaller = {
|
|
|
5580
5875
|
description: "Add hooks array scaffold to .notch.json",
|
|
5581
5876
|
async run(ctx) {
|
|
5582
5877
|
await patchJson(
|
|
5583
|
-
|
|
5878
|
+
path13.join(ctx.projectRoot, ".notch.json"),
|
|
5584
5879
|
(cur) => ({
|
|
5585
5880
|
...cur,
|
|
5586
5881
|
hooks: cur.hooks ?? [
|
|
@@ -5596,10 +5891,10 @@ var vestaInstaller = {
|
|
|
5596
5891
|
label: "Vesta agents",
|
|
5597
5892
|
description: "Scaffold .notch/agents/ directory for Vesta agent configs",
|
|
5598
5893
|
async run(ctx) {
|
|
5599
|
-
const dir =
|
|
5600
|
-
await
|
|
5894
|
+
const dir = path13.join(ctx.projectRoot, ".notch", "agents");
|
|
5895
|
+
await fs13.mkdir(dir, { recursive: true });
|
|
5601
5896
|
await writeIfMissing(
|
|
5602
|
-
|
|
5897
|
+
path13.join(dir, "README.md"),
|
|
5603
5898
|
`# Vesta Agents
|
|
5604
5899
|
|
|
5605
5900
|
Drop agent JSON/TOML configs in this directory. Notch will load them at startup.
|
|
@@ -5614,7 +5909,7 @@ var oneSecInstaller = {
|
|
|
5614
5909
|
description: "Enable 1-SEC opt-in and add the block to .notch.json",
|
|
5615
5910
|
async run(ctx) {
|
|
5616
5911
|
await patchJson(
|
|
5617
|
-
|
|
5912
|
+
path13.join(ctx.projectRoot, ".notch.json"),
|
|
5618
5913
|
(cur) => ({
|
|
5619
5914
|
...cur,
|
|
5620
5915
|
security: cur.security ?? {
|
|
@@ -5633,7 +5928,7 @@ var telemetryInstaller = {
|
|
|
5633
5928
|
description: "Enable opt-in anonymous usage stats",
|
|
5634
5929
|
async run(ctx) {
|
|
5635
5930
|
await patchJson(
|
|
5636
|
-
|
|
5931
|
+
path13.join(ctx.projectRoot, ".notch.json"),
|
|
5637
5932
|
(cur) => ({ ...cur, telemetry: { enabled: true, anonymous: true } }),
|
|
5638
5933
|
ctx
|
|
5639
5934
|
);
|
|
@@ -5645,7 +5940,7 @@ var agentsMdInstaller = {
|
|
|
5645
5940
|
description: "Industry-standard agent instructions file (shared with Codex, Cursor, etc.)",
|
|
5646
5941
|
async run(ctx) {
|
|
5647
5942
|
await writeIfMissing(
|
|
5648
|
-
|
|
5943
|
+
path13.join(ctx.projectRoot, "AGENTS.md"),
|
|
5649
5944
|
`# Agent Instructions
|
|
5650
5945
|
|
|
5651
5946
|
Shared instructions for AI coding agents working in this repo.
|
|
@@ -5753,6 +6048,9 @@ async function initProject(projectRoot, opts2 = {}) {
|
|
|
5753
6048
|
`));
|
|
5754
6049
|
}
|
|
5755
6050
|
|
|
6051
|
+
// src/index.ts
|
|
6052
|
+
init_auth();
|
|
6053
|
+
|
|
5756
6054
|
// src/tools/diff-preview.ts
|
|
5757
6055
|
function unifiedDiff(oldContent, newContent, filePath) {
|
|
5758
6056
|
const t = theme();
|
|
@@ -5889,9 +6187,9 @@ var JsonEmitter = class {
|
|
|
5889
6187
|
};
|
|
5890
6188
|
|
|
5891
6189
|
// src/ui/output-schema.ts
|
|
5892
|
-
import
|
|
6190
|
+
import fs14 from "fs/promises";
|
|
5893
6191
|
async function loadOutputSchema(filePath) {
|
|
5894
|
-
const raw = await
|
|
6192
|
+
const raw = await fs14.readFile(filePath, "utf-8");
|
|
5895
6193
|
const schema = JSON.parse(raw);
|
|
5896
6194
|
return { schema, raw };
|
|
5897
6195
|
}
|
|
@@ -6108,8 +6406,8 @@ function isCoordinatorModeEnv() {
|
|
|
6108
6406
|
|
|
6109
6407
|
// src/commands/doctor.ts
|
|
6110
6408
|
import { execSync as execSync4 } from "child_process";
|
|
6111
|
-
import
|
|
6112
|
-
import
|
|
6409
|
+
import fs15 from "fs/promises";
|
|
6410
|
+
import path14 from "path";
|
|
6113
6411
|
import os8 from "os";
|
|
6114
6412
|
import chalk9 from "chalk";
|
|
6115
6413
|
async function runDiagnostics(cwd) {
|
|
@@ -6151,26 +6449,26 @@ async function runDiagnostics(cwd) {
|
|
|
6151
6449
|
results.push({ name: "Config", status: "fail", message: `Could not load: ${err.message}` });
|
|
6152
6450
|
}
|
|
6153
6451
|
try {
|
|
6154
|
-
await
|
|
6452
|
+
await fs15.access(path14.join(cwd, ".notch.json"));
|
|
6155
6453
|
results.push({ name: ".notch.json", status: "ok", message: "Found" });
|
|
6156
6454
|
} catch {
|
|
6157
6455
|
results.push({ name: ".notch.json", status: "warn", message: "Not found. Run: notch init" });
|
|
6158
6456
|
}
|
|
6159
|
-
const notchDir =
|
|
6457
|
+
const notchDir = path14.join(os8.homedir(), ".notch");
|
|
6160
6458
|
try {
|
|
6161
|
-
await
|
|
6459
|
+
await fs15.access(notchDir);
|
|
6162
6460
|
results.push({ name: "~/.notch/", status: "ok", message: "Exists" });
|
|
6163
6461
|
} catch {
|
|
6164
6462
|
results.push({ name: "~/.notch/", status: "warn", message: "Not found (will be created on first use)" });
|
|
6165
6463
|
}
|
|
6166
6464
|
try {
|
|
6167
|
-
await
|
|
6465
|
+
await fs15.access(path14.join(cwd, ".notch.md"));
|
|
6168
6466
|
results.push({ name: ".notch.md", status: "ok", message: "Found" });
|
|
6169
6467
|
} catch {
|
|
6170
6468
|
results.push({ name: ".notch.md", status: "warn", message: "Not found. Run: notch init" });
|
|
6171
6469
|
}
|
|
6172
6470
|
try {
|
|
6173
|
-
const configRaw = await
|
|
6471
|
+
const configRaw = await fs15.readFile(path14.join(cwd, ".notch.json"), "utf-8").catch(() => "{}");
|
|
6174
6472
|
const mcpConfigs = parseMCPConfig(JSON.parse(configRaw));
|
|
6175
6473
|
const serverNames = Object.keys(mcpConfigs);
|
|
6176
6474
|
if (serverNames.length === 0) {
|
|
@@ -6191,8 +6489,8 @@ async function runDiagnostics(cwd) {
|
|
|
6191
6489
|
results.push({ name: "MCP Servers", status: "ok", message: "No config to check" });
|
|
6192
6490
|
}
|
|
6193
6491
|
try {
|
|
6194
|
-
const sessionsDir =
|
|
6195
|
-
const entries = await
|
|
6492
|
+
const sessionsDir = path14.join(notchDir, "sessions");
|
|
6493
|
+
const entries = await fs15.readdir(sessionsDir).catch(() => []);
|
|
6196
6494
|
results.push({ name: "Sessions", status: "ok", message: `${entries.length} saved` });
|
|
6197
6495
|
} catch {
|
|
6198
6496
|
results.push({ name: "Sessions", status: "ok", message: "0 saved" });
|
|
@@ -6594,11 +6892,11 @@ Read the file first, then make the change. Only modify this one file.`
|
|
|
6594
6892
|
|
|
6595
6893
|
// src/commands/plugin.ts
|
|
6596
6894
|
import { execSync as execSync7, execFileSync as execFileSync2 } from "child_process";
|
|
6597
|
-
import
|
|
6598
|
-
import
|
|
6895
|
+
import fs16 from "fs/promises";
|
|
6896
|
+
import path15 from "path";
|
|
6599
6897
|
import os9 from "os";
|
|
6600
6898
|
import chalk15 from "chalk";
|
|
6601
|
-
var GLOBAL_PLUGINS_DIR =
|
|
6899
|
+
var GLOBAL_PLUGINS_DIR = path15.join(os9.homedir(), ".notch", "plugins");
|
|
6602
6900
|
registerCommand("/plugin", async (args, ctx) => {
|
|
6603
6901
|
const parts = args.split(/\s+/);
|
|
6604
6902
|
const subcommand = parts[0] || "list";
|
|
@@ -6634,8 +6932,8 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
6634
6932
|
console.log(chalk15.gray(" Usage: /plugin install <npm-package-or-git-url>\n"));
|
|
6635
6933
|
return;
|
|
6636
6934
|
}
|
|
6637
|
-
await
|
|
6638
|
-
const pluginDir =
|
|
6935
|
+
await fs16.mkdir(GLOBAL_PLUGINS_DIR, { recursive: true });
|
|
6936
|
+
const pluginDir = path15.join(GLOBAL_PLUGINS_DIR, path15.basename(target).replace(/\.git$/, ""));
|
|
6639
6937
|
console.log(chalk15.gray(` Installing ${target}...`));
|
|
6640
6938
|
try {
|
|
6641
6939
|
if (target.includes("/") && !target.startsWith("@")) {
|
|
@@ -6645,7 +6943,7 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
6645
6943
|
stdio: "pipe"
|
|
6646
6944
|
});
|
|
6647
6945
|
} else {
|
|
6648
|
-
await
|
|
6946
|
+
await fs16.mkdir(pluginDir, { recursive: true });
|
|
6649
6947
|
execFileSync2("npm", ["init", "-y"], {
|
|
6650
6948
|
cwd: pluginDir,
|
|
6651
6949
|
encoding: "utf-8",
|
|
@@ -6660,7 +6958,7 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
6660
6958
|
});
|
|
6661
6959
|
}
|
|
6662
6960
|
try {
|
|
6663
|
-
const pkgExists = await
|
|
6961
|
+
const pkgExists = await fs16.access(path15.join(pluginDir, "package.json")).then(() => true).catch(() => false);
|
|
6664
6962
|
if (pkgExists) {
|
|
6665
6963
|
execSync7("npm install --production", {
|
|
6666
6964
|
cwd: pluginDir,
|
|
@@ -6686,10 +6984,10 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
6686
6984
|
console.log(chalk15.gray(" Usage: /plugin remove <plugin-name>\n"));
|
|
6687
6985
|
return;
|
|
6688
6986
|
}
|
|
6689
|
-
const pluginDir =
|
|
6987
|
+
const pluginDir = path15.join(GLOBAL_PLUGINS_DIR, target);
|
|
6690
6988
|
try {
|
|
6691
|
-
await
|
|
6692
|
-
await
|
|
6989
|
+
await fs16.access(pluginDir);
|
|
6990
|
+
await fs16.rm(pluginDir, { recursive: true, force: true });
|
|
6693
6991
|
console.log(chalk15.green(` \u2713 Removed ${target}`));
|
|
6694
6992
|
console.log(chalk15.gray(" Restart notch to apply.\n"));
|
|
6695
6993
|
} catch {
|
|
@@ -7301,10 +7599,10 @@ import ora4 from "ora";
|
|
|
7301
7599
|
|
|
7302
7600
|
// src/skills/registry.ts
|
|
7303
7601
|
import { createHash } from "crypto";
|
|
7304
|
-
import
|
|
7602
|
+
import fs17 from "fs";
|
|
7305
7603
|
import fsp3 from "fs/promises";
|
|
7306
7604
|
import os10 from "os";
|
|
7307
|
-
import
|
|
7605
|
+
import path16 from "path";
|
|
7308
7606
|
var registry = /* @__PURE__ */ new Map();
|
|
7309
7607
|
var loadPromises = /* @__PURE__ */ new Map();
|
|
7310
7608
|
var extractedDirs = /* @__PURE__ */ new Set();
|
|
@@ -7342,14 +7640,14 @@ async function performLoad(skill) {
|
|
|
7342
7640
|
function getExtractDir(skill) {
|
|
7343
7641
|
const filesJson = skill.files ? JSON.stringify(skill.files) : "";
|
|
7344
7642
|
const sha8 = createHash("sha256").update(filesJson).digest("hex").slice(0, 8);
|
|
7345
|
-
return
|
|
7643
|
+
return path16.join(os10.tmpdir(), "notch-skills", `${skill.id}-${sha8}`);
|
|
7346
7644
|
}
|
|
7347
7645
|
async function extractFiles(dir, files) {
|
|
7348
7646
|
await fsp3.mkdir(dir, { recursive: true, mode: 448 });
|
|
7349
7647
|
const byParent = /* @__PURE__ */ new Map();
|
|
7350
7648
|
for (const [relPath, content] of Object.entries(files)) {
|
|
7351
7649
|
const target = resolveSafePath(dir, relPath);
|
|
7352
|
-
const parent =
|
|
7650
|
+
const parent = path16.dirname(target);
|
|
7353
7651
|
const group = byParent.get(parent);
|
|
7354
7652
|
if (group) group.push([target, content]);
|
|
7355
7653
|
else byParent.set(parent, [[target, content]]);
|
|
@@ -7366,17 +7664,17 @@ async function extractFiles(dir, files) {
|
|
|
7366
7664
|
);
|
|
7367
7665
|
}
|
|
7368
7666
|
function resolveSafePath(baseDir, relPath) {
|
|
7369
|
-
const normalized =
|
|
7667
|
+
const normalized = path16.normalize(relPath);
|
|
7370
7668
|
const parts = normalized.split(/[\\/]/);
|
|
7371
|
-
if (
|
|
7669
|
+
if (path16.isAbsolute(normalized) || parts.includes("..")) {
|
|
7372
7670
|
throw new Error(`skill file path escapes skill dir: ${relPath}`);
|
|
7373
7671
|
}
|
|
7374
|
-
return
|
|
7672
|
+
return path16.join(baseDir, normalized);
|
|
7375
7673
|
}
|
|
7376
7674
|
function cleanupSkills() {
|
|
7377
7675
|
for (const dir of extractedDirs) {
|
|
7378
7676
|
try {
|
|
7379
|
-
|
|
7677
|
+
fs17.rmSync(dir, { recursive: true, force: true });
|
|
7380
7678
|
} catch {
|
|
7381
7679
|
}
|
|
7382
7680
|
}
|
|
@@ -7562,6 +7860,7 @@ registerCommand("/microcompact", async (args, ctx) => {
|
|
|
7562
7860
|
import { exec, execSync as execSync11 } from "child_process";
|
|
7563
7861
|
import chalk27 from "chalk";
|
|
7564
7862
|
import ora5 from "ora";
|
|
7863
|
+
init_auth();
|
|
7565
7864
|
var PLATFORM_URL = "https://freesyntax.dev";
|
|
7566
7865
|
function openBrowser(url) {
|
|
7567
7866
|
let cmd;
|
|
@@ -7693,6 +7992,7 @@ registerCommand("/cloud", async (args, ctx) => {
|
|
|
7693
7992
|
import { exec as exec2 } from "child_process";
|
|
7694
7993
|
import chalk28 from "chalk";
|
|
7695
7994
|
import ora6 from "ora";
|
|
7995
|
+
init_auth();
|
|
7696
7996
|
var PLATFORM_URL2 = "https://freesyntax.dev";
|
|
7697
7997
|
function openBrowser2(url) {
|
|
7698
7998
|
let cmd;
|
|
@@ -7775,8 +8075,8 @@ registerCommand("/agent-builder", async (args, _ctx) => {
|
|
|
7775
8075
|
});
|
|
7776
8076
|
|
|
7777
8077
|
// src/ui/completions.ts
|
|
7778
|
-
import
|
|
7779
|
-
import
|
|
8078
|
+
import fs18 from "fs";
|
|
8079
|
+
import path17 from "path";
|
|
7780
8080
|
var COMMANDS = [
|
|
7781
8081
|
"/help",
|
|
7782
8082
|
"/quit",
|
|
@@ -7848,8 +8148,7 @@ function buildCompleter(cwd) {
|
|
|
7848
8148
|
}
|
|
7849
8149
|
if (line.startsWith("/model ")) {
|
|
7850
8150
|
const partial = line.slice(7);
|
|
7851
|
-
const
|
|
7852
|
-
const allNames = [...MODEL_IDS, ...modelNames];
|
|
8151
|
+
const allNames = listByokProviders().filter((p) => p.defaultModel).map((p) => `${p.id}/${p.defaultModel}`);
|
|
7853
8152
|
const matches = allNames.filter((m) => m.startsWith(partial));
|
|
7854
8153
|
return [matches.map((m) => `/model ${m}`), line];
|
|
7855
8154
|
}
|
|
@@ -7869,15 +8168,15 @@ function buildCompleter(cwd) {
|
|
|
7869
8168
|
}
|
|
7870
8169
|
function completeFilePath(partial, cwd) {
|
|
7871
8170
|
try {
|
|
7872
|
-
const dir = partial.includes("/") ?
|
|
7873
|
-
const prefix = partial.includes("/") ?
|
|
7874
|
-
const entries =
|
|
8171
|
+
const dir = partial.includes("/") ? path17.resolve(cwd, path17.dirname(partial)) : cwd;
|
|
8172
|
+
const prefix = partial.includes("/") ? path17.basename(partial) : partial;
|
|
8173
|
+
const entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
7875
8174
|
const matches = [];
|
|
7876
8175
|
for (const entry of entries) {
|
|
7877
8176
|
if (entry.name.startsWith(".")) continue;
|
|
7878
8177
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
7879
8178
|
if (entry.name.startsWith(prefix)) {
|
|
7880
|
-
const relative = partial.includes("/") ?
|
|
8179
|
+
const relative = partial.includes("/") ? path17.dirname(partial) + "/" + entry.name : entry.name;
|
|
7881
8180
|
if (entry.isDirectory()) {
|
|
7882
8181
|
matches.push(relative + "/");
|
|
7883
8182
|
} else {
|
|
@@ -7901,6 +8200,8 @@ var SLASH_COMMANDS = [
|
|
|
7901
8200
|
// Model & Status
|
|
7902
8201
|
{ name: "/model", description: "Switch or list models", category: "Model" },
|
|
7903
8202
|
{ name: "/status", description: "Check API endpoint health", category: "Model" },
|
|
8203
|
+
{ name: "/providers", description: "List model providers", category: "Model" },
|
|
8204
|
+
{ name: "/sync-keys", description: "Pull provider keys from freesyntax.dev", category: "Model" },
|
|
7904
8205
|
// Session
|
|
7905
8206
|
{ name: "/save", description: "Save current session", category: "Session" },
|
|
7906
8207
|
{ name: "/sessions", description: "List saved sessions", category: "Session" },
|
|
@@ -8158,22 +8459,22 @@ function rewritePromptLine(rl) {
|
|
|
8158
8459
|
}
|
|
8159
8460
|
|
|
8160
8461
|
// src/services/autoDream/gate.ts
|
|
8161
|
-
import
|
|
8162
|
-
import
|
|
8462
|
+
import fs19 from "fs/promises";
|
|
8463
|
+
import path18 from "path";
|
|
8163
8464
|
import os11 from "os";
|
|
8164
|
-
var NOTCH_DIR2 =
|
|
8165
|
-
var SESSION_DIR2 =
|
|
8166
|
-
var STATE_FILE =
|
|
8167
|
-
var LOCK_FILE =
|
|
8465
|
+
var NOTCH_DIR2 = path18.join(os11.homedir(), ".notch");
|
|
8466
|
+
var SESSION_DIR2 = path18.join(NOTCH_DIR2, "sessions");
|
|
8467
|
+
var STATE_FILE = path18.join(NOTCH_DIR2, "dream-state.json");
|
|
8468
|
+
var LOCK_FILE = path18.join(NOTCH_DIR2, ".dream.lock");
|
|
8168
8469
|
var DEFAULT_MIN_HOURS = 24;
|
|
8169
8470
|
var DEFAULT_MIN_SESSIONS = 5;
|
|
8170
8471
|
var MS_PER_HOUR = 36e5;
|
|
8171
8472
|
async function ensureNotchDir2() {
|
|
8172
|
-
await
|
|
8473
|
+
await fs19.mkdir(NOTCH_DIR2, { recursive: true });
|
|
8173
8474
|
}
|
|
8174
8475
|
async function readState() {
|
|
8175
8476
|
try {
|
|
8176
|
-
const raw = await
|
|
8477
|
+
const raw = await fs19.readFile(STATE_FILE, "utf-8");
|
|
8177
8478
|
const parsed = JSON.parse(raw);
|
|
8178
8479
|
if (typeof parsed?.lastRunAt !== "number" || !Number.isFinite(parsed.lastRunAt)) {
|
|
8179
8480
|
return null;
|
|
@@ -8185,7 +8486,7 @@ async function readState() {
|
|
|
8185
8486
|
}
|
|
8186
8487
|
async function writeState(state) {
|
|
8187
8488
|
await ensureNotchDir2();
|
|
8188
|
-
await
|
|
8489
|
+
await fs19.writeFile(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
|
|
8189
8490
|
}
|
|
8190
8491
|
async function shouldDream(_cwd, opts2 = {}) {
|
|
8191
8492
|
const minHours = opts2.minHours ?? DEFAULT_MIN_HOURS;
|
|
@@ -8201,7 +8502,7 @@ async function shouldDream(_cwd, opts2 = {}) {
|
|
|
8201
8502
|
}
|
|
8202
8503
|
let sessionFiles = [];
|
|
8203
8504
|
try {
|
|
8204
|
-
sessionFiles = await
|
|
8505
|
+
sessionFiles = await fs19.readdir(SESSION_DIR2);
|
|
8205
8506
|
} catch {
|
|
8206
8507
|
return { should: false, reason: "session-gate: no session directory yet" };
|
|
8207
8508
|
}
|
|
@@ -8209,7 +8510,7 @@ async function shouldDream(_cwd, opts2 = {}) {
|
|
|
8209
8510
|
for (const name of sessionFiles) {
|
|
8210
8511
|
if (!name.endsWith(".json") && !name.endsWith(".jsonl")) continue;
|
|
8211
8512
|
try {
|
|
8212
|
-
const stat = await
|
|
8513
|
+
const stat = await fs19.stat(path18.join(SESSION_DIR2, name));
|
|
8213
8514
|
if (stat.mtimeMs > lastAt) touchedCount++;
|
|
8214
8515
|
} catch {
|
|
8215
8516
|
}
|
|
@@ -8222,7 +8523,7 @@ async function shouldDream(_cwd, opts2 = {}) {
|
|
|
8222
8523
|
}
|
|
8223
8524
|
try {
|
|
8224
8525
|
await ensureNotchDir2();
|
|
8225
|
-
const handle = await
|
|
8526
|
+
const handle = await fs19.open(LOCK_FILE, "wx");
|
|
8226
8527
|
try {
|
|
8227
8528
|
await handle.writeFile(String(process.pid), "utf-8");
|
|
8228
8529
|
} finally {
|
|
@@ -8245,7 +8546,7 @@ async function shouldDream(_cwd, opts2 = {}) {
|
|
|
8245
8546
|
}
|
|
8246
8547
|
async function releaseDreamLock() {
|
|
8247
8548
|
try {
|
|
8248
|
-
await
|
|
8549
|
+
await fs19.unlink(LOCK_FILE);
|
|
8249
8550
|
} catch {
|
|
8250
8551
|
}
|
|
8251
8552
|
}
|
|
@@ -8262,10 +8563,10 @@ async function recordDreamRun() {
|
|
|
8262
8563
|
}
|
|
8263
8564
|
|
|
8264
8565
|
// src/services/autoDream/consolidationPrompt.ts
|
|
8265
|
-
import
|
|
8566
|
+
import path19 from "path";
|
|
8266
8567
|
import os12 from "os";
|
|
8267
|
-
var MEMORY_DIR2 =
|
|
8268
|
-
var SESSION_DIR3 =
|
|
8568
|
+
var MEMORY_DIR2 = path19.join(os12.homedir(), ".notch", "memory");
|
|
8569
|
+
var SESSION_DIR3 = path19.join(os12.homedir(), ".notch", "sessions");
|
|
8269
8570
|
var INDEX_FILE2 = "MEMORY.md";
|
|
8270
8571
|
var MAX_INDEX_LINES = 200;
|
|
8271
8572
|
var MAX_INDEX_BYTES = 25 * 1024;
|
|
@@ -8427,21 +8728,25 @@ function isDisabled() {
|
|
|
8427
8728
|
}
|
|
8428
8729
|
|
|
8429
8730
|
// src/index.ts
|
|
8430
|
-
import
|
|
8731
|
+
import fs20 from "fs/promises";
|
|
8431
8732
|
import { createRequire as createRequire2 } from "module";
|
|
8432
8733
|
var _require2 = createRequire2(import.meta.url);
|
|
8433
8734
|
var VERSION = _require2("../package.json").version;
|
|
8434
|
-
var modelChoices = MODEL_IDS.join(", ");
|
|
8435
8735
|
if (process.argv[2] === "update") {
|
|
8436
8736
|
await runUpdateCli(process.argv.slice(3));
|
|
8437
8737
|
process.exit(process.exitCode ?? 0);
|
|
8438
8738
|
}
|
|
8439
8739
|
if (process.argv[2] === "ollama") {
|
|
8440
|
-
const { runOllamaCli } = await import("./ollama-launch-
|
|
8740
|
+
const { runOllamaCli } = await import("./ollama-launch-3IKB2A3Z.js");
|
|
8441
8741
|
const code = await runOllamaCli(process.argv.slice(3), process.cwd());
|
|
8442
8742
|
process.exit(code);
|
|
8443
8743
|
}
|
|
8444
|
-
|
|
8744
|
+
if (process.argv[2] === "config") {
|
|
8745
|
+
const { runConfigCli } = await import("./config-set-5F4VK7IT.js");
|
|
8746
|
+
const code = await runConfigCli(process.argv.slice(3), process.cwd());
|
|
8747
|
+
process.exit(code);
|
|
8748
|
+
}
|
|
8749
|
+
var program = new Command().name("notch").description("Notch CLI \u2014 AI-powered coding assistant by Driftrail").version(VERSION).argument("[prompt...]", "One-shot prompt (runs once and exits)").option("-m, --model <model>", "Model id, preferably provider/model (for example openrouter/anthropic/claude-sonnet-4-6)").option("--base-url <url>", "Override the provider base URL (for custom OpenAI-compatible endpoints)").option("--api-key <key>", "API key for the active provider (prefer provider env vars such as OPENROUTER_API_KEY)").option("--provider <id>", "Provider id (openai, anthropic, openrouter, google, deepseek, together, fireworks, groq, ollama, lmstudio, vllm, custom).").option("--list-providers", "List built-in providers and their API-key env vars, then exit").option("--no-repo-map", "Disable automatic repository mapping").option("--no-markdown", "Disable markdown rendering in output").option("--max-iterations <n>", "Max tool-call rounds per turn", "25").option("-y, --yes", "Auto-confirm destructive actions").option("--trust", "Trust mode \u2014 auto-allow all tool calls").option("--theme <theme>", `UI color theme (${THEME_IDS.join(", ")})`).option("--resume", "Resume the last session for this project").option("--session <id>", "Resume a specific session by ID").option("--cwd <dir>", "Set working directory").option("--json", "Emit JSONL event stream on stdout (headless/CI mode)").option("--output-schema <file>", "Path to JSON Schema constraining the final structured output").option("--output-last-message <file>", "Write the final assistant message to this file on exit").option("--guardian", "Enable Guardian risk scoring when a provider-backed guardian model is configured").option("--coordinator", "Coordinator mode: top-level agent can only spawn/continue/stop workers (plus read/grep/glob). All real work is delegated.").option("--no-auto-dream", "Disable the background memory-consolidation daemon (default: enabled in REPL)").option("--no-update", "Disable the background update check on launch (equivalent to NOTCH_AUTO_UPDATE=0)").option("--update-channel <name>", "npm dist-tag to follow for updates (latest | next | beta)").option(
|
|
8445
8750
|
"--image <path>",
|
|
8446
8751
|
"Attach an image (file path, URL, or data URL). Repeatable.",
|
|
8447
8752
|
(val, prev) => prev ? [...prev, val] : [val],
|
|
@@ -8451,22 +8756,29 @@ var opts = program.opts();
|
|
|
8451
8756
|
var promptArgs = program.args;
|
|
8452
8757
|
function printByokProviderList() {
|
|
8453
8758
|
const t = theme();
|
|
8454
|
-
console.log(t.dim("\n
|
|
8455
|
-
const
|
|
8759
|
+
console.log(t.dim("\n Model providers (point --provider at any of these):\n"));
|
|
8760
|
+
const providers = listByokProviders();
|
|
8761
|
+
const idWidth = Math.max(12, ...providers.map((p) => p.id.length));
|
|
8762
|
+
const labelWidth = Math.max(20, ...providers.map((p) => p.label.length));
|
|
8763
|
+
const envWidth = Math.max(22, ...providers.map((p) => (p.apiKeyEnv || "(none)").length));
|
|
8764
|
+
const keyWidth = 7;
|
|
8765
|
+
const header = ` ${"id".padEnd(idWidth)} ${"label".padEnd(labelWidth)} ${"env var".padEnd(envWidth)} ${"key".padEnd(keyWidth)} default model`;
|
|
8456
8766
|
console.log(t.dim(header));
|
|
8457
8767
|
console.log(t.dim(` ${"-".repeat(header.length - 2)}`));
|
|
8458
|
-
for (const p of
|
|
8459
|
-
const
|
|
8768
|
+
for (const p of providers) {
|
|
8769
|
+
const keyLabel = p.apiKeyEnv ? process.env[p.apiKeyEnv] ? "set" : p.fallbackApiKey ? "local" : "missing" : "none";
|
|
8770
|
+
const paddedKey = keyLabel.padEnd(keyWidth);
|
|
8771
|
+
const keyPresent = keyLabel === "set" ? t.success(paddedKey) : keyLabel === "missing" ? t.dim(paddedKey) : t.dim(paddedKey);
|
|
8460
8772
|
const envDisplay = p.apiKeyEnv || "(none)";
|
|
8461
8773
|
console.log(
|
|
8462
|
-
` ${t.brand(p.id.padEnd(
|
|
8774
|
+
` ${t.brand(p.id.padEnd(idWidth))} ${p.label.padEnd(labelWidth)} ${envDisplay.padEnd(envWidth)} ${keyPresent} ${t.dim(p.defaultModel || "\u2014")}`
|
|
8463
8775
|
);
|
|
8464
8776
|
}
|
|
8465
8777
|
console.log("");
|
|
8466
8778
|
console.log(t.dim(" Use it like:"));
|
|
8467
8779
|
console.log(t.dim(" export OPENROUTER_API_KEY=sk-or-..."));
|
|
8468
|
-
console.log(t.dim(" notch --provider openrouter --model anthropic/claude-
|
|
8469
|
-
console.log(t.dim(" notch --model openrouter
|
|
8780
|
+
console.log(t.dim(" notch --provider openrouter --model anthropic/claude-sonnet-4-6"));
|
|
8781
|
+
console.log(t.dim(" notch --model openrouter/anthropic/claude-sonnet-4-6 # equivalent"));
|
|
8470
8782
|
console.log(t.dim(" notch --provider custom --base-url http://localhost:8000/v1 --model my-model"));
|
|
8471
8783
|
console.log("");
|
|
8472
8784
|
}
|
|
@@ -8483,13 +8795,14 @@ async function persistByokChoice(projectRoot, providerId, defaultModel) {
|
|
|
8483
8795
|
const p = nodePath2.resolve(projectRoot, ".notch.json");
|
|
8484
8796
|
let current = {};
|
|
8485
8797
|
try {
|
|
8486
|
-
const raw = await
|
|
8798
|
+
const raw = await fs20.readFile(p, "utf-8");
|
|
8487
8799
|
current = JSON.parse(raw);
|
|
8488
8800
|
} catch {
|
|
8489
8801
|
}
|
|
8490
8802
|
const idToPersist = providerId === "__custom__" ? "custom" : providerId;
|
|
8491
|
-
current.
|
|
8492
|
-
|
|
8803
|
+
current.provider = { provider: idToPersist, model: defaultModel };
|
|
8804
|
+
delete current.byok;
|
|
8805
|
+
await fs20.writeFile(p, JSON.stringify(current, null, 2) + "\n", "utf-8");
|
|
8493
8806
|
} catch {
|
|
8494
8807
|
}
|
|
8495
8808
|
}
|
|
@@ -8502,14 +8815,12 @@ function interactiveModelPicker(activeModel) {
|
|
|
8502
8815
|
return new Promise((resolve3) => {
|
|
8503
8816
|
const t = theme();
|
|
8504
8817
|
const rows = [];
|
|
8505
|
-
rows.push({ kind: "notch-header" });
|
|
8506
|
-
for (const id of MODEL_IDS) rows.push({ kind: "notch", id });
|
|
8507
8818
|
rows.push({ kind: "byok-header" });
|
|
8508
8819
|
for (const p of listByokProviders()) rows.push({ kind: "byok", provider: p });
|
|
8509
|
-
const selectableIndexes = rows.map((r, i) => r.kind === "
|
|
8820
|
+
const selectableIndexes = rows.map((r, i) => r.kind === "byok" ? i : -1).filter((i) => i >= 0);
|
|
8510
8821
|
let cursor = selectableIndexes.find((i) => {
|
|
8511
8822
|
const r = rows[i];
|
|
8512
|
-
return r && r.kind === "
|
|
8823
|
+
return r && r.kind === "byok" && typeof activeModel === "string" && isByokRef(activeModel) && parseByokRef(activeModel).provider === r.provider.id;
|
|
8513
8824
|
}) ?? selectableIndexes[0] ?? 0;
|
|
8514
8825
|
const rowCount = rows.length;
|
|
8515
8826
|
const headerLines = 2;
|
|
@@ -8526,35 +8837,31 @@ function interactiveModelPicker(activeModel) {
|
|
|
8526
8837
|
console.log(t.dim(" Select a model (\u2191\u2193 to move, Enter to select, Esc to cancel)\n"));
|
|
8527
8838
|
for (let i = 0; i < rows.length; i++) {
|
|
8528
8839
|
const row = rows[i];
|
|
8529
|
-
if (row.kind === "notch-header") {
|
|
8530
|
-
console.log(` ${t.dim("\u2500\u2500\u2500 Notch models (default) \u2500\u2500\u2500")}`);
|
|
8531
|
-
continue;
|
|
8532
|
-
}
|
|
8533
8840
|
if (row.kind === "byok-header") {
|
|
8534
|
-
console.log(` ${t.dim("
|
|
8841
|
+
console.log(` ${t.dim("--- Providers ---")}`);
|
|
8535
8842
|
continue;
|
|
8536
8843
|
}
|
|
8537
8844
|
const isSelected = i === cursor;
|
|
8538
8845
|
const pointer = isSelected ? t.brand("\u276F") : " ";
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
const keyPresent = p.apiKeyEnv ? process.env[p.apiKeyEnv] ? t.success("\u2713") : t.dim("\u2717") : t.dim("\u2013");
|
|
8553
|
-
const label = isSelected ? t.bold(p.label) : t.dim(p.label);
|
|
8554
|
-
const envDisplay = t.dim((p.apiKeyEnv || "local").padEnd(22));
|
|
8555
|
-
const defModel = t.dim(p.defaultModel ? p.defaultModel.slice(0, 34) : "\u2014");
|
|
8556
|
-
console.log(` ${pointer} ${dot} ${t.brand(p.id.padEnd(12))} ${label.padEnd(20)} ${envDisplay} ${keyPresent} ${defModel}`);
|
|
8846
|
+
const p = row.provider;
|
|
8847
|
+
const isCurrent = typeof activeModel === "string" && isByokRef(activeModel) ? parseByokRef(activeModel).provider === p.id : false;
|
|
8848
|
+
const dot = isCurrent ? t.success("\u25CF") : " ";
|
|
8849
|
+
const envHit = p.apiKeyEnv ? Boolean(process.env[p.apiKeyEnv]) : false;
|
|
8850
|
+
let syncHit = false;
|
|
8851
|
+
if (!envHit) {
|
|
8852
|
+
try {
|
|
8853
|
+
const { loadSyncedByokKeysSync } = (init_auth(), __toCommonJS(auth_exports));
|
|
8854
|
+
const synced = loadSyncedByokKeysSync();
|
|
8855
|
+
const fromSync = synced?.keys[p.id];
|
|
8856
|
+
syncHit = typeof fromSync === "string" && fromSync.length > 0;
|
|
8857
|
+
} catch {
|
|
8858
|
+
}
|
|
8557
8859
|
}
|
|
8860
|
+
const keyPresent = p.apiKeyEnv ? envHit ? t.success("\u2713") : syncHit ? t.brand("\u2713") : t.dim("\u2717") : t.dim("\u2013");
|
|
8861
|
+
const label = isSelected ? t.bold(p.label) : t.dim(p.label);
|
|
8862
|
+
const envDisplay = t.dim((p.apiKeyEnv || "local").padEnd(22));
|
|
8863
|
+
const defModel = t.dim(p.defaultModel ? p.defaultModel.slice(0, 34) : "\u2014");
|
|
8864
|
+
console.log(` ${pointer} ${dot} ${t.brand(p.id.padEnd(12))} ${label.padEnd(20)} ${envDisplay} ${keyPresent} ${defModel}`);
|
|
8558
8865
|
}
|
|
8559
8866
|
};
|
|
8560
8867
|
const render = (first) => {
|
|
@@ -8580,16 +8887,12 @@ function interactiveModelPicker(activeModel) {
|
|
|
8580
8887
|
} else if (s === "\r" || s === "\n") {
|
|
8581
8888
|
const row = rows[cursor];
|
|
8582
8889
|
cleanup();
|
|
8583
|
-
if (!row || row.kind !== "
|
|
8890
|
+
if (!row || row.kind !== "byok") {
|
|
8584
8891
|
resolve3(null);
|
|
8585
8892
|
return;
|
|
8586
8893
|
}
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
} else {
|
|
8590
|
-
const modelRef = `${row.provider.id}:${row.provider.defaultModel}`;
|
|
8591
|
-
resolve3({ kind: "byok", provider: row.provider, modelRef });
|
|
8592
|
-
}
|
|
8894
|
+
const modelRef = `${row.provider.id}/${row.provider.defaultModel}`;
|
|
8895
|
+
resolve3({ kind: "byok", provider: row.provider, modelRef });
|
|
8593
8896
|
} else if (s === "\x1B" || s === "") {
|
|
8594
8897
|
cleanup();
|
|
8595
8898
|
resolve3(null);
|
|
@@ -8606,10 +8909,11 @@ function interactiveModelPicker(activeModel) {
|
|
|
8606
8909
|
function printHelp() {
|
|
8607
8910
|
console.log(chalk30.gray(`
|
|
8608
8911
|
Commands:
|
|
8609
|
-
/model \u2014
|
|
8610
|
-
/model <
|
|
8611
|
-
/
|
|
8612
|
-
/
|
|
8912
|
+
/model \u2014 Pick a provider and default model
|
|
8913
|
+
/model <ref> \u2014 Switch model: /model openrouter/anthropic/claude-sonnet-4-6
|
|
8914
|
+
/sync-keys \u2014 Pull provider keys you added on freesyntax.dev
|
|
8915
|
+
/providers \u2014 List built-in providers and whether their keys are set
|
|
8916
|
+
/status \u2014 Check active provider health
|
|
8613
8917
|
/undo \u2014 Undo last file changes
|
|
8614
8918
|
/usage \u2014 Show token usage + context meter
|
|
8615
8919
|
/cost \u2014 Show estimated session cost
|
|
@@ -8632,6 +8936,7 @@ function printHelp() {
|
|
|
8632
8936
|
/memory search <q> \u2014 Search memories
|
|
8633
8937
|
/memory clear \u2014 Delete all memories
|
|
8634
8938
|
/permissions \u2014 Show current permission config
|
|
8939
|
+
/yolo \u2014 Toggle YOLO mode: auto-allow all tool calls (persists)
|
|
8635
8940
|
|
|
8636
8941
|
Ralph Wiggum Mode (autonomous):
|
|
8637
8942
|
/ralph plan <goal> \u2014 Generate task plan for a goal
|
|
@@ -8708,12 +9013,62 @@ async function main() {
|
|
|
8708
9013
|
const creds = await login();
|
|
8709
9014
|
console.log(chalk30.green(`
|
|
8710
9015
|
\u2713 Signed in as ${creds.email}`));
|
|
8711
|
-
console.log(chalk30.gray(` API key stored in ${(await import("./auth-
|
|
8712
|
-
|
|
9016
|
+
console.log(chalk30.gray(` API key stored in ${(await import("./auth-UAMMP5IJ.js")).getCredentialsPath()}`));
|
|
9017
|
+
try {
|
|
9018
|
+
const { syncByokKeys } = await import("./auth-UAMMP5IJ.js");
|
|
9019
|
+
const synced = await syncByokKeys(creds.token);
|
|
9020
|
+
const providerCount = Object.keys(synced.keys).length;
|
|
9021
|
+
if (providerCount > 0) {
|
|
9022
|
+
console.log(chalk30.gray(
|
|
9023
|
+
` Synced ${providerCount} provider key(s) from freesyntax.dev \u2192 ${(await import("./auth-UAMMP5IJ.js")).getByokSyncPath()}`
|
|
9024
|
+
));
|
|
9025
|
+
} else {
|
|
9026
|
+
console.log(chalk30.gray(
|
|
9027
|
+
` No provider keys on your profile yet \u2014 add them at freesyntax.dev/settings/keys then run ${chalk30.white("notch sync-keys")}.`
|
|
9028
|
+
));
|
|
9029
|
+
}
|
|
9030
|
+
} catch (syncErr) {
|
|
9031
|
+
console.log(chalk30.yellow(
|
|
9032
|
+
` (provider key sync skipped: ${syncErr.message.slice(0, 120)})`
|
|
9033
|
+
));
|
|
9034
|
+
}
|
|
9035
|
+
console.log("");
|
|
8713
9036
|
} catch (err) {
|
|
8714
9037
|
spinner.stop();
|
|
8715
9038
|
console.error(chalk30.red(`
|
|
8716
9039
|
Login failed: ${err.message}
|
|
9040
|
+
`));
|
|
9041
|
+
process.exit(1);
|
|
9042
|
+
}
|
|
9043
|
+
return;
|
|
9044
|
+
}
|
|
9045
|
+
if (promptArgs[0] === "sync-keys") {
|
|
9046
|
+
const creds = await loadCredentials();
|
|
9047
|
+
if (!creds) {
|
|
9048
|
+
console.log(chalk30.gray("\n Not signed in. Run: notch login\n"));
|
|
9049
|
+
return;
|
|
9050
|
+
}
|
|
9051
|
+
const spinner = ora7("Pulling provider keys from freesyntax.dev...").start();
|
|
9052
|
+
try {
|
|
9053
|
+
const { syncByokKeys, getByokSyncPath } = await import("./auth-UAMMP5IJ.js");
|
|
9054
|
+
const synced = await syncByokKeys(creds.token);
|
|
9055
|
+
spinner.stop();
|
|
9056
|
+
const providers = Object.keys(synced.keys);
|
|
9057
|
+
if (providers.length === 0) {
|
|
9058
|
+
console.log(chalk30.gray(`
|
|
9059
|
+
No provider keys on your profile yet.`));
|
|
9060
|
+
console.log(chalk30.gray(` Add them at ${chalk30.white("https://freesyntax.dev/settings/keys")} then rerun.
|
|
9061
|
+
`));
|
|
9062
|
+
} else {
|
|
9063
|
+
console.log(chalk30.green(`
|
|
9064
|
+
\u2713 Synced ${providers.length} provider key(s): ${providers.join(", ")}`));
|
|
9065
|
+
console.log(chalk30.gray(` Cached at ${getByokSyncPath()}
|
|
9066
|
+
`));
|
|
9067
|
+
}
|
|
9068
|
+
} catch (err) {
|
|
9069
|
+
spinner.stop();
|
|
9070
|
+
console.error(chalk30.red(`
|
|
9071
|
+
Key sync failed: ${err.message}
|
|
8717
9072
|
`));
|
|
8718
9073
|
process.exit(1);
|
|
8719
9074
|
}
|
|
@@ -8746,7 +9101,7 @@ async function main() {
|
|
|
8746
9101
|
return;
|
|
8747
9102
|
}
|
|
8748
9103
|
if (promptArgs[0] === "mcp-serve" || promptArgs[0] === "mcp-server") {
|
|
8749
|
-
const { runMcpServer } = await import("./server-
|
|
9104
|
+
const { runMcpServer } = await import("./server-GMF4WV67.js");
|
|
8750
9105
|
await runMcpServer({
|
|
8751
9106
|
cwd: opts.cwd ?? process.cwd(),
|
|
8752
9107
|
version: VERSION,
|
|
@@ -8785,7 +9140,7 @@ async function main() {
|
|
|
8785
9140
|
const providerId = opts.provider === "custom" ? "__custom__" : opts.provider;
|
|
8786
9141
|
const byokInfo = findByokProvider(providerId);
|
|
8787
9142
|
if (!byokInfo) {
|
|
8788
|
-
console.error(chalk30.red(` Unknown
|
|
9143
|
+
console.error(chalk30.red(` Unknown provider: ${opts.provider}`));
|
|
8789
9144
|
console.error(chalk30.gray(` Run notch --list-providers to see built-ins, or pass --provider custom --base-url <url>.`));
|
|
8790
9145
|
process.exit(1);
|
|
8791
9146
|
}
|
|
@@ -8802,7 +9157,7 @@ async function main() {
|
|
|
8802
9157
|
} else if (isByokRef(opts.model)) {
|
|
8803
9158
|
const { provider } = parseByokRef(opts.model);
|
|
8804
9159
|
if (!findByokProvider(provider)) {
|
|
8805
|
-
console.error(chalk30.red(` Unknown
|
|
9160
|
+
console.error(chalk30.red(` Unknown provider in model ref: ${provider}`));
|
|
8806
9161
|
console.error(chalk30.gray(` Run notch --list-providers to see built-ins.`));
|
|
8807
9162
|
process.exit(1);
|
|
8808
9163
|
}
|
|
@@ -8810,8 +9165,8 @@ async function main() {
|
|
|
8810
9165
|
config.models.chat.byokProvider = void 0;
|
|
8811
9166
|
} else {
|
|
8812
9167
|
console.error(chalk30.red(` Unknown model: ${opts.model}`));
|
|
8813
|
-
console.error(chalk30.gray(
|
|
8814
|
-
console.error(chalk30.gray(
|
|
9168
|
+
console.error(chalk30.gray(" Use provider/model, for example openrouter/anthropic/claude-sonnet-4-6."));
|
|
9169
|
+
console.error(chalk30.gray(" Run notch --list-providers to see built-ins."));
|
|
8815
9170
|
process.exit(1);
|
|
8816
9171
|
}
|
|
8817
9172
|
}
|
|
@@ -8865,7 +9220,7 @@ async function main() {
|
|
|
8865
9220
|
if (err instanceof ByokMissingApiKeyError) {
|
|
8866
9221
|
printWordmark(VERSION);
|
|
8867
9222
|
const p = err.provider;
|
|
8868
|
-
console.log(` To use ${p.label}
|
|
9223
|
+
console.log(` To use ${p.label} you need an API key.`);
|
|
8869
9224
|
console.log("");
|
|
8870
9225
|
if (p.apiKeyEnv) {
|
|
8871
9226
|
console.log(" \x1B[1mOption 1:\x1B[0m Set the env var");
|
|
@@ -8892,9 +9247,9 @@ async function main() {
|
|
|
8892
9247
|
console.log(" \x1B[1mOption 3:\x1B[0m Pass it inline");
|
|
8893
9248
|
console.log(" \x1B[33m$ notch --api-key your-key-here\x1B[0m");
|
|
8894
9249
|
console.log("");
|
|
8895
|
-
console.log(" \x1B[1mOr:\x1B[0m
|
|
9250
|
+
console.log(" \x1B[1mOr:\x1B[0m Use any provider key (OpenAI, Anthropic, OpenRouter, ...)");
|
|
8896
9251
|
console.log(" \x1B[33m$ notch --list-providers\x1B[0m");
|
|
8897
|
-
console.log(" \x1B[33m$ notch --
|
|
9252
|
+
console.log(" \x1B[33m$ notch --model openrouter/anthropic/claude-sonnet-4-6\x1B[0m");
|
|
8898
9253
|
console.log("");
|
|
8899
9254
|
console.log(" Get your Notch key at: \x1B[4mhttps://freesyntax.dev/settings\x1B[0m");
|
|
8900
9255
|
console.log("");
|
|
@@ -8905,7 +9260,7 @@ async function main() {
|
|
|
8905
9260
|
const info = activeByok ? {
|
|
8906
9261
|
id: activeModelId,
|
|
8907
9262
|
label: `${activeByok.label}`,
|
|
8908
|
-
size: "
|
|
9263
|
+
size: "provider",
|
|
8909
9264
|
gpu: activeByok.id,
|
|
8910
9265
|
contextWindow: 128e3,
|
|
8911
9266
|
maxOutputTokens: 8192,
|
|
@@ -8955,14 +9310,18 @@ async function main() {
|
|
|
8955
9310
|
}).catch(() => {
|
|
8956
9311
|
});
|
|
8957
9312
|
}
|
|
9313
|
+
const startupPermissive = config.permissionMode === "trust" || !!config.autoConfirm || !!opts.yes;
|
|
8958
9314
|
const hookTrustPrompt = async (commands) => {
|
|
9315
|
+
if (startupPermissive) return true;
|
|
9316
|
+
if (!process.stdin.isTTY) return true;
|
|
8959
9317
|
console.warn(chalk30.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
|
|
8960
9318
|
commands.forEach((cmd) => console.warn(chalk30.gray(` \u2022 ${cmd}`)));
|
|
8961
9319
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8962
9320
|
return new Promise((resolve3) => {
|
|
8963
|
-
rl2.question(chalk30.yellow("\nAllow these hooks for this project? [
|
|
9321
|
+
rl2.question(chalk30.yellow("\nAllow these hooks for this project? [Y/n] "), (answer) => {
|
|
8964
9322
|
rl2.close();
|
|
8965
|
-
|
|
9323
|
+
const norm = answer.trim().toLowerCase();
|
|
9324
|
+
resolve3(norm !== "n" && norm !== "no");
|
|
8966
9325
|
});
|
|
8967
9326
|
});
|
|
8968
9327
|
};
|
|
@@ -8981,7 +9340,6 @@ async function main() {
|
|
|
8981
9340
|
spinner.warn("Could not build repo map");
|
|
8982
9341
|
}
|
|
8983
9342
|
}
|
|
8984
|
-
const baseSystemPrompt = await buildSystemPrompt(config.projectRoot, activeModelId);
|
|
8985
9343
|
let outputSchema = null;
|
|
8986
9344
|
if (opts.outputSchema) {
|
|
8987
9345
|
try {
|
|
@@ -8996,19 +9354,6 @@ async function main() {
|
|
|
8996
9354
|
}
|
|
8997
9355
|
}
|
|
8998
9356
|
const coordinatorMode = !!opts.coordinator || isCoordinatorModeEnv();
|
|
8999
|
-
const systemPrompt = coordinatorMode ? [
|
|
9000
|
-
COORDINATOR_SYSTEM_PROMPT,
|
|
9001
|
-
repoMapStr ? `
|
|
9002
|
-
## Repository Map
|
|
9003
|
-
${repoMapStr}` : "",
|
|
9004
|
-
outputSchema ? schemaInstructions(outputSchema) : ""
|
|
9005
|
-
].join("") : [
|
|
9006
|
-
baseSystemPrompt,
|
|
9007
|
-
repoMapStr ? `
|
|
9008
|
-
## Repository Map
|
|
9009
|
-
${repoMapStr}` : "",
|
|
9010
|
-
outputSchema ? schemaInstructions(outputSchema) : ""
|
|
9011
|
-
].join("");
|
|
9012
9357
|
if (coordinatorMode && !jsonMode) {
|
|
9013
9358
|
console.log(
|
|
9014
9359
|
chalk30.green(
|
|
@@ -9026,7 +9371,7 @@ ${repoMapStr}` : "",
|
|
|
9026
9371
|
const permissionSessionId = `s_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
9027
9372
|
const checkpoints = new CheckpointManager();
|
|
9028
9373
|
const usage2 = new UsageTracker();
|
|
9029
|
-
const { OllamaCloudUsageTracker } = await import("./ollama-usage-
|
|
9374
|
+
const { OllamaCloudUsageTracker } = await import("./ollama-usage-3PROM2WC.js");
|
|
9030
9375
|
const cloudUsage = new OllamaCloudUsageTracker();
|
|
9031
9376
|
let sessionId;
|
|
9032
9377
|
let activePlan = null;
|
|
@@ -9034,19 +9379,11 @@ ${repoMapStr}` : "",
|
|
|
9034
9379
|
const branches = /* @__PURE__ */ new Map();
|
|
9035
9380
|
let currentBranch = "main";
|
|
9036
9381
|
const costTracker = new CostTracker();
|
|
9037
|
-
const mcpClients = [];
|
|
9038
9382
|
try {
|
|
9039
|
-
const configRaw = await
|
|
9040
|
-
const
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
const client = new MCPClient(mcpConfig, name);
|
|
9044
|
-
await client.connect();
|
|
9045
|
-
mcpClients.push(client);
|
|
9046
|
-
console.log(chalk30.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
|
|
9047
|
-
} catch (err) {
|
|
9048
|
-
console.log(chalk30.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
|
|
9049
|
-
}
|
|
9383
|
+
const configRaw = await fs20.readFile(nodePath2.resolve(config.projectRoot, ".notch.json"), "utf-8").catch(() => "{}");
|
|
9384
|
+
const toolCount = await initMCPServers(JSON.parse(configRaw));
|
|
9385
|
+
if (toolCount > 0) {
|
|
9386
|
+
console.log(chalk30.green(` MCP: Connected (${toolCount} tools)`));
|
|
9050
9387
|
}
|
|
9051
9388
|
} catch {
|
|
9052
9389
|
}
|
|
@@ -9064,13 +9401,16 @@ ${repoMapStr}` : "",
|
|
|
9064
9401
|
if (opts.guardian) {
|
|
9065
9402
|
try {
|
|
9066
9403
|
guardianModel = resolveModel({
|
|
9067
|
-
model:
|
|
9404
|
+
model: process.env.NOTCH_GUARDIAN_MODEL ?? config.models.chat.model,
|
|
9068
9405
|
apiKey: config.models.chat.apiKey,
|
|
9069
9406
|
baseUrl: process.env.NOTCH_GUARDIAN_BASE_URL,
|
|
9070
|
-
headers: config.models.chat.headers
|
|
9407
|
+
headers: config.models.chat.headers,
|
|
9408
|
+
byokProvider: config.models.chat.byokProvider,
|
|
9409
|
+
byokHeaders: config.models.chat.byokHeaders,
|
|
9410
|
+
byokApiShape: config.models.chat.byokApiShape
|
|
9071
9411
|
});
|
|
9072
9412
|
if (!jsonMode) {
|
|
9073
|
-
console.log(chalk30.green(
|
|
9413
|
+
console.log(chalk30.green(` Guardian: enabled (${process.env.NOTCH_GUARDIAN_MODEL ?? config.models.chat.model})`));
|
|
9074
9414
|
}
|
|
9075
9415
|
} catch (err) {
|
|
9076
9416
|
if (!jsonMode) {
|
|
@@ -9079,15 +9419,18 @@ ${repoMapStr}` : "",
|
|
|
9079
9419
|
guardianModel = void 0;
|
|
9080
9420
|
}
|
|
9081
9421
|
}
|
|
9422
|
+
const isPermissive = () => config.permissionMode === "trust" || config.autoConfirm;
|
|
9082
9423
|
const toolCtx = {
|
|
9083
9424
|
cwd: config.projectRoot,
|
|
9084
|
-
requireConfirm:
|
|
9425
|
+
requireConfirm: !isPermissive(),
|
|
9085
9426
|
confirm: async (message) => {
|
|
9427
|
+
if (isPermissive()) return true;
|
|
9086
9428
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
9087
9429
|
return new Promise((resolve3) => {
|
|
9088
|
-
rl2.question(`${message} (
|
|
9430
|
+
rl2.question(`${message} (Y/n) `, (answer) => {
|
|
9089
9431
|
rl2.close();
|
|
9090
|
-
|
|
9432
|
+
const norm = answer.trim().toLowerCase();
|
|
9433
|
+
resolve3(norm !== "n" && norm !== "no");
|
|
9091
9434
|
});
|
|
9092
9435
|
});
|
|
9093
9436
|
},
|
|
@@ -9112,6 +9455,28 @@ ${repoMapStr}` : "",
|
|
|
9112
9455
|
}
|
|
9113
9456
|
}
|
|
9114
9457
|
};
|
|
9458
|
+
const baseSystemPrompt = await buildSystemPrompt(config.projectRoot, activeModelId, toolCtx);
|
|
9459
|
+
const systemPrompt = coordinatorMode ? [
|
|
9460
|
+
COORDINATOR_SYSTEM_PROMPT,
|
|
9461
|
+
"\n\n",
|
|
9462
|
+
baseSystemPrompt,
|
|
9463
|
+
repoMapStr ? `
|
|
9464
|
+
|
|
9465
|
+
## Repository Map
|
|
9466
|
+
${repoMapStr}` : "",
|
|
9467
|
+
outputSchema ? `
|
|
9468
|
+
|
|
9469
|
+
${schemaInstructions(outputSchema)}` : ""
|
|
9470
|
+
].join("") : [
|
|
9471
|
+
baseSystemPrompt,
|
|
9472
|
+
repoMapStr ? `
|
|
9473
|
+
|
|
9474
|
+
## Repository Map
|
|
9475
|
+
${repoMapStr}` : "",
|
|
9476
|
+
outputSchema ? `
|
|
9477
|
+
|
|
9478
|
+
${schemaInstructions(outputSchema)}` : ""
|
|
9479
|
+
].join("");
|
|
9115
9480
|
await toolCtx.runHook?.("session-start", {});
|
|
9116
9481
|
const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
|
|
9117
9482
|
for (const r of results) {
|
|
@@ -9183,9 +9548,10 @@ Analyze the above input.`;
|
|
|
9183
9548
|
attachments.push(res);
|
|
9184
9549
|
}
|
|
9185
9550
|
if (attachments.length > 0 && !modelSupportsImages(activeModelId)) {
|
|
9186
|
-
const msg = `Selected model "${activeModelId}"
|
|
9187
|
-
if (jsonMode) events.emit({ type: "
|
|
9188
|
-
else console.
|
|
9551
|
+
const msg = `Selected model "${activeModelId}" does not support image attachments. Switch to a vision-capable model.`;
|
|
9552
|
+
if (jsonMode) events.emit({ type: "error", message: msg });
|
|
9553
|
+
else console.error(chalk30.red(` ${msg}`));
|
|
9554
|
+
process.exit(1);
|
|
9189
9555
|
}
|
|
9190
9556
|
if (attachments.length > 0) {
|
|
9191
9557
|
const imageBlocks = attachments.map(imageToContentBlock);
|
|
@@ -9305,7 +9671,7 @@ Analyze the above input.`;
|
|
|
9305
9671
|
}
|
|
9306
9672
|
if (opts.outputLastMessage) {
|
|
9307
9673
|
try {
|
|
9308
|
-
await
|
|
9674
|
+
await fs20.writeFile(opts.outputLastMessage, response.text, "utf-8");
|
|
9309
9675
|
} catch (err) {
|
|
9310
9676
|
if (jsonMode) events.emit({ type: "error", message: `Failed to write --output-last-message: ${err.message}` });
|
|
9311
9677
|
}
|
|
@@ -9393,11 +9759,9 @@ Analyze the above input.`;
|
|
|
9393
9759
|
} catch {
|
|
9394
9760
|
}
|
|
9395
9761
|
}
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
} catch {
|
|
9400
|
-
}
|
|
9762
|
+
try {
|
|
9763
|
+
disconnectMCPServers();
|
|
9764
|
+
} catch {
|
|
9401
9765
|
}
|
|
9402
9766
|
slashMenu.cleanup();
|
|
9403
9767
|
stopActiveLoop();
|
|
@@ -9424,24 +9788,6 @@ Analyze the above input.`;
|
|
|
9424
9788
|
const picked = await interactiveModelPicker(activeModelId);
|
|
9425
9789
|
if (!picked) {
|
|
9426
9790
|
console.log(chalk30.gray(" Cancelled\n"));
|
|
9427
|
-
} else if (picked.kind === "notch") {
|
|
9428
|
-
if (picked.id === activeModelId) {
|
|
9429
|
-
console.log(chalk30.gray(` Already using ${MODEL_CATALOG[picked.id].label}
|
|
9430
|
-
`));
|
|
9431
|
-
} else {
|
|
9432
|
-
activeModelId = picked.id;
|
|
9433
|
-
config.models.chat.model = activeModelId;
|
|
9434
|
-
config.models.chat.byokProvider = void 0;
|
|
9435
|
-
try {
|
|
9436
|
-
model = resolveModel(config.models.chat);
|
|
9437
|
-
const switchedInfo = MODEL_CATALOG[picked.id];
|
|
9438
|
-
console.log(chalk30.green(` \u2713 Switched to ${switchedInfo.label} (${switchedInfo.id})
|
|
9439
|
-
`));
|
|
9440
|
-
} catch (e) {
|
|
9441
|
-
console.log(chalk30.red(` Failed to switch: ${e.message}
|
|
9442
|
-
`));
|
|
9443
|
-
}
|
|
9444
|
-
}
|
|
9445
9791
|
} else {
|
|
9446
9792
|
activeModelId = picked.modelRef;
|
|
9447
9793
|
config.models.chat.model = picked.modelRef;
|
|
@@ -9465,12 +9811,12 @@ Analyze the above input.`;
|
|
|
9465
9811
|
rl.prompt();
|
|
9466
9812
|
return;
|
|
9467
9813
|
}
|
|
9468
|
-
if (input.startsWith("/model ")) {
|
|
9814
|
+
if (input.startsWith("/model ") && !input.startsWith("/model download")) {
|
|
9469
9815
|
const arg = input.replace("/model ", "").trim();
|
|
9470
9816
|
if (isByokRef(arg)) {
|
|
9471
9817
|
const { provider } = parseByokRef(arg);
|
|
9472
9818
|
if (!findByokProvider(provider)) {
|
|
9473
|
-
console.log(chalk30.red(` Unknown
|
|
9819
|
+
console.log(chalk30.red(` Unknown provider: ${provider}`));
|
|
9474
9820
|
console.log(chalk30.gray(` Run /providers to list built-ins.`));
|
|
9475
9821
|
rl.prompt();
|
|
9476
9822
|
return;
|
|
@@ -9489,6 +9835,29 @@ Analyze the above input.`;
|
|
|
9489
9835
|
`));
|
|
9490
9836
|
} else {
|
|
9491
9837
|
console.log(chalk30.red(` Failed to switch: ${e.message}
|
|
9838
|
+
`));
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9841
|
+
rl.prompt();
|
|
9842
|
+
return;
|
|
9843
|
+
}
|
|
9844
|
+
const currentProvider = activeByokProvider(config.models.chat);
|
|
9845
|
+
if (currentProvider) {
|
|
9846
|
+
const providerId = currentProvider.id === "__custom__" ? "custom" : currentProvider.id;
|
|
9847
|
+
const modelRef = `${providerId}/${arg}`;
|
|
9848
|
+
activeModelId = modelRef;
|
|
9849
|
+
config.models.chat.model = modelRef;
|
|
9850
|
+
config.models.chat.byokProvider = void 0;
|
|
9851
|
+
try {
|
|
9852
|
+
model = resolveModel(config.models.chat);
|
|
9853
|
+
console.log(chalk30.green(` Switched to ${currentProvider.label} (${modelRef})
|
|
9854
|
+
`));
|
|
9855
|
+
} catch (e) {
|
|
9856
|
+
if (e instanceof ByokMissingApiKeyError) {
|
|
9857
|
+
console.log(chalk30.yellow(` \u26A0 ${e.message}
|
|
9858
|
+
`));
|
|
9859
|
+
} else {
|
|
9860
|
+
console.log(chalk30.red(` Failed to switch: ${e.message}
|
|
9492
9861
|
`));
|
|
9493
9862
|
}
|
|
9494
9863
|
}
|
|
@@ -9501,8 +9870,8 @@ Analyze the above input.`;
|
|
|
9501
9870
|
}
|
|
9502
9871
|
if (!isValidModel(newModel)) {
|
|
9503
9872
|
console.log(chalk30.red(` Unknown model: ${arg}`));
|
|
9504
|
-
console.log(chalk30.gray(
|
|
9505
|
-
console.log(chalk30.gray(
|
|
9873
|
+
console.log(chalk30.gray(" Use provider/model, for example openrouter/anthropic/claude-sonnet-4-6."));
|
|
9874
|
+
console.log(chalk30.gray(" Run /providers to list built-ins."));
|
|
9506
9875
|
rl.prompt();
|
|
9507
9876
|
return;
|
|
9508
9877
|
}
|
|
@@ -9516,6 +9885,60 @@ Analyze the above input.`;
|
|
|
9516
9885
|
rl.prompt();
|
|
9517
9886
|
return;
|
|
9518
9887
|
}
|
|
9888
|
+
if (input.startsWith("/model download")) {
|
|
9889
|
+
console.log(chalk30.gray(" Direct weight downloads are no longer part of the model picker."));
|
|
9890
|
+
console.log(chalk30.gray(" Run a local provider instead, then select it with /model ollama/<model>, /model lmstudio/<model>, or /model custom/<model>.\n"));
|
|
9891
|
+
rl.prompt();
|
|
9892
|
+
return;
|
|
9893
|
+
}
|
|
9894
|
+
if (input === "/downloads") {
|
|
9895
|
+
const { listDownloads } = await import("./model-download-KCQJCEPW.js");
|
|
9896
|
+
const active = listDownloads();
|
|
9897
|
+
if (active.length === 0) {
|
|
9898
|
+
console.log(chalk30.gray(" No local downloads started this session.\n"));
|
|
9899
|
+
} else {
|
|
9900
|
+
console.log(chalk30.gray("\n Model State Last line"));
|
|
9901
|
+
for (const h of active) {
|
|
9902
|
+
const state = h.result == null ? chalk30.yellow("running") : h.result.code === 0 ? chalk30.green("done") : chalk30.red("error");
|
|
9903
|
+
const info2 = MODEL_CATALOG[h.modelId];
|
|
9904
|
+
const label = (info2?.label ?? h.modelId).padEnd(18);
|
|
9905
|
+
const tail2 = (h.lastLine || "").slice(0, 80);
|
|
9906
|
+
console.log(` ${label} ${state.padEnd(20)} ${chalk30.gray(tail2)}`);
|
|
9907
|
+
}
|
|
9908
|
+
console.log("");
|
|
9909
|
+
}
|
|
9910
|
+
rl.prompt();
|
|
9911
|
+
return;
|
|
9912
|
+
}
|
|
9913
|
+
if (input === "/sync-keys") {
|
|
9914
|
+
const { loadCredentials: loadCredentials2, syncByokKeys, getByokSyncPath } = await import("./auth-UAMMP5IJ.js");
|
|
9915
|
+
const creds = await loadCredentials2();
|
|
9916
|
+
if (!creds) {
|
|
9917
|
+
console.log(chalk30.gray(" Not signed in. Run: notch login\n"));
|
|
9918
|
+
rl.prompt();
|
|
9919
|
+
return;
|
|
9920
|
+
}
|
|
9921
|
+
const spinner2 = ora7("Pulling provider keys from freesyntax.dev...").start();
|
|
9922
|
+
try {
|
|
9923
|
+
const synced = await syncByokKeys(creds.token);
|
|
9924
|
+
spinner2.stop();
|
|
9925
|
+
const providers = Object.keys(synced.keys);
|
|
9926
|
+
if (providers.length === 0) {
|
|
9927
|
+
console.log(chalk30.gray(` No provider keys on your profile yet. Add them at https://freesyntax.dev/settings/keys.
|
|
9928
|
+
`));
|
|
9929
|
+
} else {
|
|
9930
|
+
console.log(chalk30.green(` \u2713 Synced ${providers.length} key(s): ${providers.join(", ")}`));
|
|
9931
|
+
console.log(chalk30.gray(` Cached at ${getByokSyncPath()}
|
|
9932
|
+
`));
|
|
9933
|
+
}
|
|
9934
|
+
} catch (err) {
|
|
9935
|
+
spinner2.stop();
|
|
9936
|
+
console.log(chalk30.red(` Sync failed: ${err.message}
|
|
9937
|
+
`));
|
|
9938
|
+
}
|
|
9939
|
+
rl.prompt();
|
|
9940
|
+
return;
|
|
9941
|
+
}
|
|
9519
9942
|
if (input === "/providers") {
|
|
9520
9943
|
printByokProviderList();
|
|
9521
9944
|
rl.prompt();
|
|
@@ -9586,7 +10009,7 @@ Analyze the above input.`;
|
|
|
9586
10009
|
return;
|
|
9587
10010
|
}
|
|
9588
10011
|
if (input === "/compact") {
|
|
9589
|
-
const { autoCompress: autoCompress2 } = await import("./compression-
|
|
10012
|
+
const { autoCompress: autoCompress2 } = await import("./compression-YJLWEHCC.js");
|
|
9590
10013
|
const before = messages.length;
|
|
9591
10014
|
const compressed = await autoCompress2(messages, model, activeContextWindow(config.models.chat));
|
|
9592
10015
|
messages.length = 0;
|
|
@@ -9889,6 +10312,29 @@ Analyze the above input.`;
|
|
|
9889
10312
|
}
|
|
9890
10313
|
if (input === "/permissions") {
|
|
9891
10314
|
console.log(formatPermissions(permissions));
|
|
10315
|
+
const mode = config.permissionMode === "trust" ? "trust (YOLO)" : config.permissionMode;
|
|
10316
|
+
console.log(chalk30.gray(` Mode: ${mode}${config.autoConfirm ? " (autoConfirm)" : ""}`));
|
|
10317
|
+
console.log(chalk30.gray(" Toggle YOLO with /yolo (persists to .notch.json)."));
|
|
10318
|
+
console.log("");
|
|
10319
|
+
rl.prompt();
|
|
10320
|
+
return;
|
|
10321
|
+
}
|
|
10322
|
+
if (input === "/yolo" || input === "/yes" || input === "/trust" || input === "/strict" || input === "/autoaccept" || input === "/auto") {
|
|
10323
|
+
const turnOn = input === "/yolo" ? config.permissionMode !== "trust" : input === "/yes" || input === "/trust" || input === "/autoaccept" || input === "/auto";
|
|
10324
|
+
config.permissionMode = turnOn ? "trust" : "auto";
|
|
10325
|
+
config.autoConfirm = turnOn;
|
|
10326
|
+
toolCtx.checkPermission = turnOn ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args);
|
|
10327
|
+
toolCtx.requireConfirm = !turnOn;
|
|
10328
|
+
toolCtx.autoConfirm = turnOn;
|
|
10329
|
+
try {
|
|
10330
|
+
await persistConfigPatch(config.projectRoot, {
|
|
10331
|
+
permissionMode: config.permissionMode,
|
|
10332
|
+
autoConfirm: config.autoConfirm || void 0
|
|
10333
|
+
});
|
|
10334
|
+
console.log(turnOn ? chalk30.red(` \u{1F525} YOLO mode ON \u2014 all tool calls auto-allowed, no prompts. Saved to .notch.json.`) : chalk30.cyan(` Strict mode ON \u2014 prompts restored. Saved to .notch.json.`));
|
|
10335
|
+
} catch (err) {
|
|
10336
|
+
console.log(chalk30.yellow(` Mode set for this session but persist failed: ${err?.message ?? err}`));
|
|
10337
|
+
}
|
|
9892
10338
|
console.log("");
|
|
9893
10339
|
rl.prompt();
|
|
9894
10340
|
return;
|
|
@@ -10082,9 +10528,10 @@ Analyze the above input.`;
|
|
|
10082
10528
|
if (replAttachments.length > 0 && !modelSupportsImages(activeModelId)) {
|
|
10083
10529
|
console.warn(
|
|
10084
10530
|
chalk30.yellow(
|
|
10085
|
-
` \u26A0 ${activeModelId}
|
|
10531
|
+
` \u26A0 ${activeModelId} does not support image attachments. Switch with /model; skipping image(s).`
|
|
10086
10532
|
)
|
|
10087
10533
|
);
|
|
10534
|
+
replAttachments.length = 0;
|
|
10088
10535
|
}
|
|
10089
10536
|
const textForRefs = imgRefs.cleanedText || (replAttachments.length > 0 ? "Describe the attached image(s)." : "");
|
|
10090
10537
|
const { cleanInput, references } = await resolveReferences(textForRefs, config.projectRoot);
|
|
@@ -10093,6 +10540,10 @@ Analyze the above input.`;
|
|
|
10093
10540
|
if (references.length > 0) {
|
|
10094
10541
|
console.log(chalk30.gray(` Injected ${references.length} reference(s)`));
|
|
10095
10542
|
}
|
|
10543
|
+
if (!finalPrompt.trim() && replAttachments.length === 0) {
|
|
10544
|
+
console.warn(chalk30.yellow(" Add a message, or switch to a vision-capable model before sending only images."));
|
|
10545
|
+
return;
|
|
10546
|
+
}
|
|
10096
10547
|
if (replAttachments.length > 0) {
|
|
10097
10548
|
const imageBlocks = replAttachments.map(imageToContentBlock);
|
|
10098
10549
|
messages.push({
|
|
@@ -10268,7 +10719,6 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
10268
10719
|
const config = await loadConfig(cliOpts.cwd ? { projectRoot: cliOpts.cwd } : {});
|
|
10269
10720
|
if (cliOpts.model) config.models.chat.model = cliOpts.model;
|
|
10270
10721
|
const model = resolveModel(config.models.chat);
|
|
10271
|
-
const systemPrompt = await buildSystemPrompt(config.projectRoot, config.models.chat.model);
|
|
10272
10722
|
const toolCtx = {
|
|
10273
10723
|
cwd: config.projectRoot,
|
|
10274
10724
|
requireConfirm: false,
|