@freesyntax/notch-cli 0.5.0 → 0.5.11
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/index.js +587 -272
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
17
|
-
import
|
|
17
|
+
import chalk27 from "chalk";
|
|
18
18
|
import ora4 from "ora";
|
|
19
19
|
import * as readline from "readline";
|
|
20
20
|
import * as nodePath from "path";
|
|
@@ -418,6 +418,7 @@ var editTool = {
|
|
|
418
418
|
|
|
419
419
|
// src/tools/shell.ts
|
|
420
420
|
import { execSync } from "child_process";
|
|
421
|
+
import path5 from "path";
|
|
421
422
|
import { z as z4 } from "zod";
|
|
422
423
|
var BLOCKED_PATTERNS = [
|
|
423
424
|
/rm\s+-rf\s+\/(?!\S)/,
|
|
@@ -446,14 +447,17 @@ var parameters4 = z4.object({
|
|
|
446
447
|
timeout: z4.number().optional().describe("Timeout in ms (default 30s, max configurable up to 10m)")
|
|
447
448
|
});
|
|
448
449
|
function validateCommand(command, cwd) {
|
|
449
|
-
const
|
|
450
|
+
const fileOpRegex = /(?:^|\s)(?:>|>>|cat|cp|mv|ln|tee|tar|zip|scp|rsync|chmod|chown|rm)\s+(\/(?!tmp\b|dev\/null\b)[^\s]+)/g;
|
|
450
451
|
let match;
|
|
451
|
-
while ((match =
|
|
452
|
-
const targetPath = match[1];
|
|
452
|
+
while ((match = fileOpRegex.exec(command)) !== null) {
|
|
453
|
+
const targetPath = path5.normalize(match[1]);
|
|
453
454
|
if (!targetPath.startsWith(cwd) && !targetPath.startsWith("/tmp")) {
|
|
454
455
|
return `Blocked: command targets path outside project root: ${targetPath}`;
|
|
455
456
|
}
|
|
456
457
|
}
|
|
458
|
+
if (/(?:^|\s)(?:\.\.\/){3,}/.test(command)) {
|
|
459
|
+
return "Blocked: deep path traversal detected";
|
|
460
|
+
}
|
|
457
461
|
return null;
|
|
458
462
|
}
|
|
459
463
|
var shellTool = {
|
|
@@ -664,7 +668,7 @@ Proceed?`
|
|
|
664
668
|
// src/tools/grep.ts
|
|
665
669
|
import { execSync as execSync2 } from "child_process";
|
|
666
670
|
import fs5 from "fs/promises";
|
|
667
|
-
import
|
|
671
|
+
import path6 from "path";
|
|
668
672
|
import { z as z6 } from "zod";
|
|
669
673
|
var parameters6 = z6.object({
|
|
670
674
|
pattern: z6.string().describe("Regex pattern to search for"),
|
|
@@ -678,7 +682,7 @@ var grepTool = {
|
|
|
678
682
|
description: "Search file contents by regex pattern. Returns matching lines with file paths and line numbers. Supports glob filtering and context lines.",
|
|
679
683
|
parameters: parameters6,
|
|
680
684
|
async execute(params, ctx) {
|
|
681
|
-
const searchPath = params.path ?
|
|
685
|
+
const searchPath = params.path ? path6.isAbsolute(params.path) ? params.path : path6.resolve(ctx.cwd, params.path) : ctx.cwd;
|
|
682
686
|
try {
|
|
683
687
|
const rgArgs = [
|
|
684
688
|
"rg",
|
|
@@ -710,7 +714,7 @@ var grepTool = {
|
|
|
710
714
|
for (const entry of entries) {
|
|
711
715
|
if (results.length >= (params.max_results ?? 50)) return;
|
|
712
716
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
713
|
-
const full =
|
|
717
|
+
const full = path6.join(dir, entry.name);
|
|
714
718
|
if (entry.isDirectory()) {
|
|
715
719
|
await searchDir(full);
|
|
716
720
|
} else if (entry.isFile()) {
|
|
@@ -723,7 +727,7 @@ var grepTool = {
|
|
|
723
727
|
const lines = content.split("\n");
|
|
724
728
|
for (let i = 0; i < lines.length; i++) {
|
|
725
729
|
if (regex.test(lines[i])) {
|
|
726
|
-
const rel =
|
|
730
|
+
const rel = path6.relative(ctx.cwd, full);
|
|
727
731
|
results.push(`${rel}:${i + 1}:${lines[i]}`);
|
|
728
732
|
regex.lastIndex = 0;
|
|
729
733
|
if (results.length >= (params.max_results ?? 50)) return;
|
|
@@ -738,7 +742,7 @@ var grepTool = {
|
|
|
738
742
|
if (stat.isFile()) {
|
|
739
743
|
const content = await fs5.readFile(searchPath, "utf-8");
|
|
740
744
|
const lines = content.split("\n");
|
|
741
|
-
const rel =
|
|
745
|
+
const rel = path6.relative(ctx.cwd, searchPath);
|
|
742
746
|
for (let i = 0; i < lines.length; i++) {
|
|
743
747
|
if (regex.test(lines[i])) {
|
|
744
748
|
results.push(`${rel}:${i + 1}:${lines[i]}`);
|
|
@@ -758,7 +762,7 @@ var grepTool = {
|
|
|
758
762
|
|
|
759
763
|
// src/tools/glob.ts
|
|
760
764
|
import { glob as globFn } from "glob";
|
|
761
|
-
import
|
|
765
|
+
import path7 from "path";
|
|
762
766
|
import { z as z7 } from "zod";
|
|
763
767
|
var parameters7 = z7.object({
|
|
764
768
|
pattern: z7.string().describe('Glob pattern, e.g. "**/*.ts" or "src/**/*.tsx"'),
|
|
@@ -769,7 +773,7 @@ var globTool = {
|
|
|
769
773
|
description: "Find files matching a glob pattern. Returns relative paths sorted by modification time. Ignores node_modules and .git by default.",
|
|
770
774
|
parameters: parameters7,
|
|
771
775
|
async execute(params, ctx) {
|
|
772
|
-
const searchDir = params.path ?
|
|
776
|
+
const searchDir = params.path ? path7.isAbsolute(params.path) ? params.path : path7.resolve(ctx.cwd, params.path) : ctx.cwd;
|
|
773
777
|
try {
|
|
774
778
|
const matches = await globFn(params.pattern, {
|
|
775
779
|
cwd: searchDir,
|
|
@@ -778,9 +782,9 @@ var globTool = {
|
|
|
778
782
|
dot: false
|
|
779
783
|
});
|
|
780
784
|
if (matches.length === 0) {
|
|
781
|
-
return { content: `No files matching "${params.pattern}" in ${
|
|
785
|
+
return { content: `No files matching "${params.pattern}" in ${path7.relative(ctx.cwd, searchDir) || "."}` };
|
|
782
786
|
}
|
|
783
|
-
const relative = matches.map((m) =>
|
|
787
|
+
const relative = matches.map((m) => path7.relative(ctx.cwd, path7.resolve(searchDir, m)));
|
|
784
788
|
return {
|
|
785
789
|
content: `Found ${relative.length} file(s):
|
|
786
790
|
${relative.join("\n")}`
|
|
@@ -866,7 +870,7 @@ var GITHUB_API = "https://api.github.com";
|
|
|
866
870
|
function getToken() {
|
|
867
871
|
return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
|
|
868
872
|
}
|
|
869
|
-
async function ghFetch(
|
|
873
|
+
async function ghFetch(path26, opts2 = {}) {
|
|
870
874
|
const token = getToken();
|
|
871
875
|
const headers = {
|
|
872
876
|
"Accept": "application/vnd.github+json",
|
|
@@ -874,7 +878,7 @@ async function ghFetch(path25, opts2 = {}) {
|
|
|
874
878
|
...opts2.headers ?? {}
|
|
875
879
|
};
|
|
876
880
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
877
|
-
return fetch(`${GITHUB_API}${
|
|
881
|
+
return fetch(`${GITHUB_API}${path26}`, { ...opts2, headers });
|
|
878
882
|
}
|
|
879
883
|
var parameters9 = z9.object({
|
|
880
884
|
action: z9.enum([
|
|
@@ -1032,9 +1036,9 @@ var githubTool = {
|
|
|
1032
1036
|
};
|
|
1033
1037
|
|
|
1034
1038
|
// src/tools/lsp.ts
|
|
1035
|
-
import {
|
|
1039
|
+
import { execFileSync } from "child_process";
|
|
1036
1040
|
import fs6 from "fs/promises";
|
|
1037
|
-
import
|
|
1041
|
+
import path8 from "path";
|
|
1038
1042
|
import { z as z10 } from "zod";
|
|
1039
1043
|
var parameters10 = z10.object({
|
|
1040
1044
|
action: z10.enum(["definition", "references", "diagnostics", "hover"]).describe("LSP action: definition (go-to-def), references (find usages), diagnostics (type errors), hover (type info)"),
|
|
@@ -1044,7 +1048,7 @@ var parameters10 = z10.object({
|
|
|
1044
1048
|
symbol: z10.string().optional().describe("Symbol name to search for (alternative to line/character)")
|
|
1045
1049
|
});
|
|
1046
1050
|
function detectLanguage(filePath) {
|
|
1047
|
-
const ext =
|
|
1051
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
1048
1052
|
switch (ext) {
|
|
1049
1053
|
case ".ts":
|
|
1050
1054
|
case ".tsx":
|
|
@@ -1074,10 +1078,11 @@ function isAvailable(cmd) {
|
|
|
1074
1078
|
}
|
|
1075
1079
|
async function tsDiagnostics(filePath, cwd) {
|
|
1076
1080
|
try {
|
|
1077
|
-
const result =
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
+
const result = execFileSync("npx", ["tsc", "--noEmit", "--pretty", "false", filePath], {
|
|
1082
|
+
cwd,
|
|
1083
|
+
encoding: "utf-8",
|
|
1084
|
+
timeout: 3e4
|
|
1085
|
+
});
|
|
1081
1086
|
return result.trim() || "No type errors found.";
|
|
1082
1087
|
} catch (err) {
|
|
1083
1088
|
const output = err.stdout || err.stderr || "";
|
|
@@ -1086,11 +1091,13 @@ async function tsDiagnostics(filePath, cwd) {
|
|
|
1086
1091
|
return lines.slice(0, 20).join("\n") || "No type errors found.";
|
|
1087
1092
|
}
|
|
1088
1093
|
try {
|
|
1089
|
-
const result =
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
+
const result = execFileSync("npx", ["tsc", "--noEmit", "--pretty", "false"], {
|
|
1095
|
+
cwd,
|
|
1096
|
+
encoding: "utf-8",
|
|
1097
|
+
timeout: 3e4
|
|
1098
|
+
});
|
|
1099
|
+
const allLines = result.trim().split("\n").slice(0, 30).join("\n");
|
|
1100
|
+
return allLines || "No type errors found.";
|
|
1094
1101
|
} catch (err2) {
|
|
1095
1102
|
const out = err2.stdout || err2.stderr || "";
|
|
1096
1103
|
const errLines = out.split("\n").filter((l) => l.includes("error TS"));
|
|
@@ -1099,6 +1106,9 @@ async function tsDiagnostics(filePath, cwd) {
|
|
|
1099
1106
|
}
|
|
1100
1107
|
}
|
|
1101
1108
|
async function findDefinition(symbol, cwd) {
|
|
1109
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(symbol)) {
|
|
1110
|
+
return `Invalid symbol name: "${symbol}"`;
|
|
1111
|
+
}
|
|
1102
1112
|
const patterns = [
|
|
1103
1113
|
`(function|const|let|var|class|interface|type|enum)\\s+${symbol}\\b`,
|
|
1104
1114
|
`export\\s+(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${symbol}\\b`,
|
|
@@ -1111,40 +1121,76 @@ async function findDefinition(symbol, cwd) {
|
|
|
1111
1121
|
];
|
|
1112
1122
|
const combinedPattern = patterns.join("|");
|
|
1113
1123
|
try {
|
|
1114
|
-
const result =
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1124
|
+
const result = execFileSync("rg", [
|
|
1125
|
+
"--no-heading",
|
|
1126
|
+
"--line-number",
|
|
1127
|
+
"-e",
|
|
1128
|
+
combinedPattern,
|
|
1129
|
+
"--type-add",
|
|
1130
|
+
"code:*.{ts,tsx,js,jsx,py,go,rs}",
|
|
1131
|
+
"--type",
|
|
1132
|
+
"code",
|
|
1133
|
+
"-m",
|
|
1134
|
+
"10"
|
|
1135
|
+
], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
|
|
1118
1136
|
return result.trim() || `No definition found for "${symbol}"`;
|
|
1119
1137
|
} catch {
|
|
1120
1138
|
try {
|
|
1121
|
-
const result =
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1139
|
+
const result = execFileSync("grep", [
|
|
1140
|
+
"-rn",
|
|
1141
|
+
"-E",
|
|
1142
|
+
combinedPattern,
|
|
1143
|
+
"--include=*.ts",
|
|
1144
|
+
"--include=*.tsx",
|
|
1145
|
+
"--include=*.js",
|
|
1146
|
+
"--include=*.py",
|
|
1147
|
+
"--include=*.go",
|
|
1148
|
+
"--include=*.rs",
|
|
1149
|
+
"."
|
|
1150
|
+
], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
|
|
1151
|
+
const lines = result.trim().split("\n").slice(0, 10).join("\n");
|
|
1152
|
+
return lines || `No definition found for "${symbol}"`;
|
|
1126
1153
|
} catch {
|
|
1127
1154
|
return `No definition found for "${symbol}"`;
|
|
1128
1155
|
}
|
|
1129
1156
|
}
|
|
1130
1157
|
}
|
|
1131
1158
|
async function findReferences(symbol, cwd) {
|
|
1159
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(symbol)) {
|
|
1160
|
+
return `Invalid symbol name: "${symbol}"`;
|
|
1161
|
+
}
|
|
1132
1162
|
try {
|
|
1133
|
-
const result =
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1163
|
+
const result = execFileSync("rg", [
|
|
1164
|
+
"--no-heading",
|
|
1165
|
+
"--line-number",
|
|
1166
|
+
"-w",
|
|
1167
|
+
symbol,
|
|
1168
|
+
"--type-add",
|
|
1169
|
+
"code:*.{ts,tsx,js,jsx,py,go,rs}",
|
|
1170
|
+
"--type",
|
|
1171
|
+
"code",
|
|
1172
|
+
"-m",
|
|
1173
|
+
"30"
|
|
1174
|
+
], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
|
|
1137
1175
|
const lines = result.trim().split("\n");
|
|
1138
1176
|
return `Found ${lines.length} reference(s) for "${symbol}":
|
|
1139
1177
|
|
|
1140
1178
|
${lines.join("\n")}`;
|
|
1141
1179
|
} catch {
|
|
1142
1180
|
try {
|
|
1143
|
-
const result =
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1181
|
+
const result = execFileSync("grep", [
|
|
1182
|
+
"-rn",
|
|
1183
|
+
"-w",
|
|
1184
|
+
symbol,
|
|
1185
|
+
"--include=*.ts",
|
|
1186
|
+
"--include=*.tsx",
|
|
1187
|
+
"--include=*.js",
|
|
1188
|
+
"--include=*.py",
|
|
1189
|
+
"--include=*.go",
|
|
1190
|
+
"--include=*.rs",
|
|
1191
|
+
"."
|
|
1192
|
+
], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
|
|
1193
|
+
const lines = result.trim().split("\n").slice(0, 30);
|
|
1148
1194
|
return `Found ${lines.length} reference(s) for "${symbol}":
|
|
1149
1195
|
|
|
1150
1196
|
${lines.join("\n")}`;
|
|
@@ -1155,7 +1201,7 @@ ${lines.join("\n")}`;
|
|
|
1155
1201
|
}
|
|
1156
1202
|
async function getHoverInfo(symbol, filePath, line, cwd) {
|
|
1157
1203
|
try {
|
|
1158
|
-
const absPath =
|
|
1204
|
+
const absPath = path8.isAbsolute(filePath) ? filePath : path8.resolve(cwd, filePath);
|
|
1159
1205
|
const content = await fs6.readFile(absPath, "utf-8");
|
|
1160
1206
|
const lines = content.split("\n");
|
|
1161
1207
|
const startLine = Math.max(0, line - 3);
|
|
@@ -1174,7 +1220,7 @@ ${def}`;
|
|
|
1174
1220
|
}
|
|
1175
1221
|
}
|
|
1176
1222
|
async function getDiagnostics(filePath, cwd) {
|
|
1177
|
-
const absPath =
|
|
1223
|
+
const absPath = path8.isAbsolute(filePath) ? filePath : path8.resolve(cwd, filePath);
|
|
1178
1224
|
const { lang } = detectLanguage(absPath);
|
|
1179
1225
|
switch (lang) {
|
|
1180
1226
|
case "typescript":
|
|
@@ -1217,13 +1263,13 @@ async function getDiagnostics(filePath, cwd) {
|
|
|
1217
1263
|
}
|
|
1218
1264
|
case "rust": {
|
|
1219
1265
|
try {
|
|
1220
|
-
const result =
|
|
1266
|
+
const result = execFileSync("cargo", ["check", "--message-format", "short"], {
|
|
1221
1267
|
cwd,
|
|
1222
1268
|
encoding: "utf-8",
|
|
1223
|
-
timeout: 6e4
|
|
1224
|
-
shell: true
|
|
1269
|
+
timeout: 6e4
|
|
1225
1270
|
});
|
|
1226
|
-
|
|
1271
|
+
const lines = result.trim().split("\n").slice(0, 30).join("\n");
|
|
1272
|
+
return lines || "No issues found.";
|
|
1227
1273
|
} catch (err) {
|
|
1228
1274
|
return err.stderr || err.stdout || "Cargo check failed.";
|
|
1229
1275
|
}
|
|
@@ -1238,7 +1284,7 @@ var lspTool = {
|
|
|
1238
1284
|
parameters: parameters10,
|
|
1239
1285
|
async execute(params, ctx) {
|
|
1240
1286
|
const filePath = params.file;
|
|
1241
|
-
const absPath =
|
|
1287
|
+
const absPath = path8.isAbsolute(filePath) ? filePath : path8.resolve(ctx.cwd, filePath);
|
|
1242
1288
|
try {
|
|
1243
1289
|
switch (params.action) {
|
|
1244
1290
|
case "definition": {
|
|
@@ -1303,7 +1349,7 @@ async function extractSymbol(filePath, line, character) {
|
|
|
1303
1349
|
|
|
1304
1350
|
// src/tools/notebook.ts
|
|
1305
1351
|
import fs7 from "fs/promises";
|
|
1306
|
-
import
|
|
1352
|
+
import path9 from "path";
|
|
1307
1353
|
import { z as z11 } from "zod";
|
|
1308
1354
|
var parameters11 = z11.object({
|
|
1309
1355
|
action: z11.enum(["read", "edit", "add", "remove"]).describe("Action: read (view cells), edit (modify cell source), add (insert new cell), remove (delete cell)"),
|
|
@@ -1315,7 +1361,7 @@ var parameters11 = z11.object({
|
|
|
1315
1361
|
});
|
|
1316
1362
|
function formatNotebook(nb, filePath) {
|
|
1317
1363
|
const parts = [
|
|
1318
|
-
`# ${
|
|
1364
|
+
`# ${path9.basename(filePath)}`,
|
|
1319
1365
|
`# ${nb.cells.length} cells | nbformat ${nb.nbformat}.${nb.nbformat_minor}`,
|
|
1320
1366
|
""
|
|
1321
1367
|
];
|
|
@@ -1346,7 +1392,7 @@ var notebookTool = {
|
|
|
1346
1392
|
description: "Read and edit Jupyter .ipynb notebook files. Actions: read (view all cells with outputs), edit (modify a cell), add (insert a new cell), remove (delete a cell).",
|
|
1347
1393
|
parameters: parameters11,
|
|
1348
1394
|
async execute(params, ctx) {
|
|
1349
|
-
const filePath =
|
|
1395
|
+
const filePath = path9.isAbsolute(params.path) ? params.path : path9.resolve(ctx.cwd, params.path);
|
|
1350
1396
|
try {
|
|
1351
1397
|
switch (params.action) {
|
|
1352
1398
|
case "read": {
|
|
@@ -1371,7 +1417,7 @@ var notebookTool = {
|
|
|
1371
1417
|
);
|
|
1372
1418
|
nb.cells[params.cell_index].source = lines;
|
|
1373
1419
|
await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
|
|
1374
|
-
return { content: `Cell [${params.cell_index}] updated in ${
|
|
1420
|
+
return { content: `Cell [${params.cell_index}] updated in ${path9.basename(filePath)}.` };
|
|
1375
1421
|
}
|
|
1376
1422
|
case "add": {
|
|
1377
1423
|
if (params.content === void 0) {
|
|
@@ -1392,7 +1438,7 @@ var notebookTool = {
|
|
|
1392
1438
|
const insertIdx = params.insert_after !== void 0 ? params.insert_after + 1 : nb.cells.length;
|
|
1393
1439
|
nb.cells.splice(insertIdx, 0, newCell);
|
|
1394
1440
|
await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
|
|
1395
|
-
return { content: `Added ${cellType} cell at index [${insertIdx}] in ${
|
|
1441
|
+
return { content: `Added ${cellType} cell at index [${insertIdx}] in ${path9.basename(filePath)}.` };
|
|
1396
1442
|
}
|
|
1397
1443
|
case "remove": {
|
|
1398
1444
|
if (params.cell_index === void 0) {
|
|
@@ -1405,7 +1451,7 @@ var notebookTool = {
|
|
|
1405
1451
|
}
|
|
1406
1452
|
const removed = nb.cells.splice(params.cell_index, 1)[0];
|
|
1407
1453
|
await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
|
|
1408
|
-
return { content: `Removed ${removed.cell_type} cell [${params.cell_index}] from ${
|
|
1454
|
+
return { content: `Removed ${removed.cell_type} cell [${params.cell_index}] from ${path9.basename(filePath)}. ${nb.cells.length} cells remaining.` };
|
|
1409
1455
|
}
|
|
1410
1456
|
default:
|
|
1411
1457
|
return { content: `Unknown action: ${params.action}`, isError: true };
|
|
@@ -1958,11 +2004,11 @@ function parseMCPConfig(config) {
|
|
|
1958
2004
|
|
|
1959
2005
|
// src/plugins/discovery.ts
|
|
1960
2006
|
import fs8 from "fs/promises";
|
|
1961
|
-
import
|
|
2007
|
+
import path10 from "path";
|
|
1962
2008
|
import os from "os";
|
|
1963
2009
|
async function discoverPlugins(projectRoot) {
|
|
1964
|
-
const globalDir =
|
|
1965
|
-
const projectDir =
|
|
2010
|
+
const globalDir = path10.join(os.homedir(), ".notch", "plugins");
|
|
2011
|
+
const projectDir = path10.join(projectRoot, ".notch", "plugins");
|
|
1966
2012
|
const plugins = /* @__PURE__ */ new Map();
|
|
1967
2013
|
for (const plugin of await scanDirectory(globalDir)) {
|
|
1968
2014
|
plugins.set(plugin.name, plugin);
|
|
@@ -1978,8 +2024,8 @@ async function scanDirectory(dir) {
|
|
|
1978
2024
|
const entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
1979
2025
|
for (const entry of entries) {
|
|
1980
2026
|
if (!entry.isDirectory()) continue;
|
|
1981
|
-
const pluginDir =
|
|
1982
|
-
const pkgPath =
|
|
2027
|
+
const pluginDir = path10.join(dir, entry.name);
|
|
2028
|
+
const pkgPath = path10.join(pluginDir, "package.json");
|
|
1983
2029
|
try {
|
|
1984
2030
|
const raw = await fs8.readFile(pkgPath, "utf-8");
|
|
1985
2031
|
const pkg = JSON.parse(raw);
|
|
@@ -2001,7 +2047,7 @@ async function scanDirectory(dir) {
|
|
|
2001
2047
|
}
|
|
2002
2048
|
|
|
2003
2049
|
// src/plugins/loader.ts
|
|
2004
|
-
import
|
|
2050
|
+
import path11 from "path";
|
|
2005
2051
|
import { pathToFileURL } from "url";
|
|
2006
2052
|
|
|
2007
2053
|
// src/commands/registry.ts
|
|
@@ -2022,7 +2068,7 @@ async function dispatchCommand(input, ctx) {
|
|
|
2022
2068
|
|
|
2023
2069
|
// src/plugins/loader.ts
|
|
2024
2070
|
async function loadPlugin(discovered, projectRoot, log) {
|
|
2025
|
-
const entryPath =
|
|
2071
|
+
const entryPath = path11.resolve(discovered.path, discovered.manifest.main);
|
|
2026
2072
|
const tools = [];
|
|
2027
2073
|
const hooks = [];
|
|
2028
2074
|
const ctx = {
|
|
@@ -2189,14 +2235,14 @@ function describeTools() {
|
|
|
2189
2235
|
|
|
2190
2236
|
// src/context/project-instructions.ts
|
|
2191
2237
|
import fs9 from "fs/promises";
|
|
2192
|
-
import
|
|
2238
|
+
import path12 from "path";
|
|
2193
2239
|
import os2 from "os";
|
|
2194
2240
|
var INSTRUCTION_FILES = [".notch.md", "NOTCH.md", ".notch/instructions.md"];
|
|
2195
2241
|
async function loadProjectInstructions(projectRoot) {
|
|
2196
2242
|
const sources = [];
|
|
2197
2243
|
const homeDir = os2.homedir();
|
|
2198
2244
|
for (const file of INSTRUCTION_FILES) {
|
|
2199
|
-
const globalPath =
|
|
2245
|
+
const globalPath = path12.join(homeDir, file);
|
|
2200
2246
|
const content = await safeRead(globalPath);
|
|
2201
2247
|
if (content) {
|
|
2202
2248
|
sources.push({ path: globalPath, content, scope: "global" });
|
|
@@ -2204,7 +2250,7 @@ async function loadProjectInstructions(projectRoot) {
|
|
|
2204
2250
|
}
|
|
2205
2251
|
}
|
|
2206
2252
|
for (const file of INSTRUCTION_FILES) {
|
|
2207
|
-
const projectPath =
|
|
2253
|
+
const projectPath = path12.join(projectRoot, file);
|
|
2208
2254
|
const content = await safeRead(projectPath);
|
|
2209
2255
|
if (content) {
|
|
2210
2256
|
sources.push({ path: projectPath, content, scope: "project" });
|
|
@@ -2235,10 +2281,10 @@ async function safeRead(filePath) {
|
|
|
2235
2281
|
|
|
2236
2282
|
// src/memory/store.ts
|
|
2237
2283
|
import fs10 from "fs/promises";
|
|
2238
|
-
import
|
|
2284
|
+
import path13 from "path";
|
|
2239
2285
|
import os3 from "os";
|
|
2240
|
-
var MEMORY_DIR =
|
|
2241
|
-
var INDEX_FILE =
|
|
2286
|
+
var MEMORY_DIR = path13.join(os3.homedir(), ".notch", "memory");
|
|
2287
|
+
var INDEX_FILE = path13.join(MEMORY_DIR, "MEMORY.md");
|
|
2242
2288
|
async function ensureDir() {
|
|
2243
2289
|
await fs10.mkdir(MEMORY_DIR, { recursive: true });
|
|
2244
2290
|
}
|
|
@@ -2246,7 +2292,7 @@ async function saveMemory(memory) {
|
|
|
2246
2292
|
await ensureDir();
|
|
2247
2293
|
const slug = memory.name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2248
2294
|
const filename = `${memory.type}_${slug}.md`;
|
|
2249
|
-
const filePath =
|
|
2295
|
+
const filePath = path13.join(MEMORY_DIR, filename);
|
|
2250
2296
|
const fileContent = [
|
|
2251
2297
|
"---",
|
|
2252
2298
|
`name: ${memory.name}`,
|
|
@@ -2268,7 +2314,7 @@ async function loadMemories() {
|
|
|
2268
2314
|
for (const file of files) {
|
|
2269
2315
|
if (!file.endsWith(".md") || file === "MEMORY.md") continue;
|
|
2270
2316
|
try {
|
|
2271
|
-
const content = await fs10.readFile(
|
|
2317
|
+
const content = await fs10.readFile(path13.join(MEMORY_DIR, file), "utf-8");
|
|
2272
2318
|
const memory = parseMemoryFile(content, file);
|
|
2273
2319
|
if (memory) memories.push(memory);
|
|
2274
2320
|
} catch {
|
|
@@ -2278,7 +2324,7 @@ async function loadMemories() {
|
|
|
2278
2324
|
}
|
|
2279
2325
|
async function deleteMemory(filename) {
|
|
2280
2326
|
try {
|
|
2281
|
-
await fs10.unlink(
|
|
2327
|
+
await fs10.unlink(path13.join(MEMORY_DIR, filename));
|
|
2282
2328
|
await updateIndex();
|
|
2283
2329
|
return true;
|
|
2284
2330
|
} catch {
|
|
@@ -2544,7 +2590,7 @@ async function buildSystemPrompt(projectRoot, modelId) {
|
|
|
2544
2590
|
|
|
2545
2591
|
// src/agent/checkpoints.ts
|
|
2546
2592
|
import fs11 from "fs/promises";
|
|
2547
|
-
import
|
|
2593
|
+
import path14 from "path";
|
|
2548
2594
|
var CheckpointManager = class {
|
|
2549
2595
|
checkpoints = [];
|
|
2550
2596
|
nextId = 1;
|
|
@@ -2591,7 +2637,7 @@ var CheckpointManager = class {
|
|
|
2591
2637
|
} catch {
|
|
2592
2638
|
}
|
|
2593
2639
|
} else {
|
|
2594
|
-
await fs11.mkdir(
|
|
2640
|
+
await fs11.mkdir(path14.dirname(snap.path), { recursive: true });
|
|
2595
2641
|
await fs11.writeFile(snap.path, snap.before, "utf-8");
|
|
2596
2642
|
}
|
|
2597
2643
|
}
|
|
@@ -2624,8 +2670,8 @@ var CheckpointManager = class {
|
|
|
2624
2670
|
}
|
|
2625
2671
|
}
|
|
2626
2672
|
}
|
|
2627
|
-
return Array.from(fileMap.entries()).map(([
|
|
2628
|
-
path:
|
|
2673
|
+
return Array.from(fileMap.entries()).map(([path26, { before, after }]) => ({
|
|
2674
|
+
path: path26,
|
|
2629
2675
|
before,
|
|
2630
2676
|
after
|
|
2631
2677
|
}));
|
|
@@ -3092,13 +3138,13 @@ var CostTracker = class {
|
|
|
3092
3138
|
|
|
3093
3139
|
// src/agent/ralph.ts
|
|
3094
3140
|
import fs13 from "fs/promises";
|
|
3095
|
-
import
|
|
3141
|
+
import path16 from "path";
|
|
3096
3142
|
import chalk5 from "chalk";
|
|
3097
3143
|
import { generateText as generateText3, streamText as streamText3 } from "ai";
|
|
3098
3144
|
|
|
3099
3145
|
// src/context/repo-map.ts
|
|
3100
3146
|
import fs12 from "fs/promises";
|
|
3101
|
-
import
|
|
3147
|
+
import path15 from "path";
|
|
3102
3148
|
import { glob } from "glob";
|
|
3103
3149
|
var PATTERNS = {
|
|
3104
3150
|
ts: [
|
|
@@ -3129,7 +3175,7 @@ var PATTERNS = {
|
|
|
3129
3175
|
};
|
|
3130
3176
|
var IMPORT_PATTERN = /(?:import|from)\s+['"]([^'"]+)['"]/g;
|
|
3131
3177
|
function getPatterns(filePath) {
|
|
3132
|
-
const ext =
|
|
3178
|
+
const ext = path15.extname(filePath).slice(1);
|
|
3133
3179
|
if (["ts", "tsx", "mts", "cts"].includes(ext)) return PATTERNS.ts;
|
|
3134
3180
|
if (["js", "jsx", "mjs", "cjs"].includes(ext)) return PATTERNS.js;
|
|
3135
3181
|
if (ext === "py") return PATTERNS.py;
|
|
@@ -3180,7 +3226,7 @@ async function buildRepoMap(root) {
|
|
|
3180
3226
|
});
|
|
3181
3227
|
const entries = [];
|
|
3182
3228
|
for (const file of files.slice(0, 500)) {
|
|
3183
|
-
const fullPath =
|
|
3229
|
+
const fullPath = path15.resolve(root, file);
|
|
3184
3230
|
try {
|
|
3185
3231
|
const content = await fs12.readFile(fullPath, "utf-8");
|
|
3186
3232
|
const lines = content.split("\n").length;
|
|
@@ -3284,11 +3330,11 @@ ${repoContext || "(empty project)"}`;
|
|
|
3284
3330
|
async function savePlan(plan, cwd) {
|
|
3285
3331
|
plan.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
3286
3332
|
plan.completedCount = plan.tasks.filter((t) => t.status === "done").length;
|
|
3287
|
-
await fs13.writeFile(
|
|
3333
|
+
await fs13.writeFile(path16.join(cwd, PLAN_FILE), JSON.stringify(plan, null, 2), "utf-8");
|
|
3288
3334
|
}
|
|
3289
3335
|
async function loadPlan(cwd) {
|
|
3290
3336
|
try {
|
|
3291
|
-
const raw = await fs13.readFile(
|
|
3337
|
+
const raw = await fs13.readFile(path16.join(cwd, PLAN_FILE), "utf-8");
|
|
3292
3338
|
return JSON.parse(raw);
|
|
3293
3339
|
} catch {
|
|
3294
3340
|
return null;
|
|
@@ -3296,7 +3342,7 @@ async function loadPlan(cwd) {
|
|
|
3296
3342
|
}
|
|
3297
3343
|
async function deletePlan(cwd) {
|
|
3298
3344
|
try {
|
|
3299
|
-
await fs13.unlink(
|
|
3345
|
+
await fs13.unlink(path16.join(cwd, PLAN_FILE));
|
|
3300
3346
|
} catch {
|
|
3301
3347
|
}
|
|
3302
3348
|
}
|
|
@@ -3480,7 +3526,7 @@ function formatRalphStatus(plan) {
|
|
|
3480
3526
|
|
|
3481
3527
|
// src/context/references.ts
|
|
3482
3528
|
import fs14 from "fs/promises";
|
|
3483
|
-
import
|
|
3529
|
+
import path17 from "path";
|
|
3484
3530
|
import { glob as glob2 } from "glob";
|
|
3485
3531
|
async function resolveReferences(input, cwd) {
|
|
3486
3532
|
const references = [];
|
|
@@ -3523,7 +3569,7 @@ ${truncated}
|
|
|
3523
3569
|
return sections.join("\n\n") + "\n\n";
|
|
3524
3570
|
}
|
|
3525
3571
|
async function resolveFile(ref, cwd) {
|
|
3526
|
-
const filePath =
|
|
3572
|
+
const filePath = path17.isAbsolute(ref) ? ref : path17.resolve(cwd, ref);
|
|
3527
3573
|
try {
|
|
3528
3574
|
const content = await fs14.readFile(filePath, "utf-8");
|
|
3529
3575
|
const lines = content.split("\n");
|
|
@@ -4250,11 +4296,11 @@ function formatTokens(n) {
|
|
|
4250
4296
|
|
|
4251
4297
|
// src/ui/update-checker.ts
|
|
4252
4298
|
import fs15 from "fs/promises";
|
|
4253
|
-
import
|
|
4299
|
+
import path18 from "path";
|
|
4254
4300
|
import os4 from "os";
|
|
4255
|
-
import { execSync as
|
|
4301
|
+
import { execSync as execSync3 } from "child_process";
|
|
4256
4302
|
import chalk8 from "chalk";
|
|
4257
|
-
var CACHE_FILE =
|
|
4303
|
+
var CACHE_FILE = path18.join(os4.homedir(), ".notch", "update-check.json");
|
|
4258
4304
|
var CHECK_INTERVAL = 60 * 60 * 1e3;
|
|
4259
4305
|
var PACKAGE_NAME = "@freesyntax/notch-cli";
|
|
4260
4306
|
var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
@@ -4291,7 +4337,7 @@ function autoUpdate(current, latest) {
|
|
|
4291
4337
|
\u2B06 Updating Notch CLI: ${current} \u2192 ${latest}...
|
|
4292
4338
|
`));
|
|
4293
4339
|
try {
|
|
4294
|
-
|
|
4340
|
+
execSync3(`npm install -g ${PACKAGE_NAME}@${latest}`, {
|
|
4295
4341
|
stdio: "inherit",
|
|
4296
4342
|
timeout: 6e4
|
|
4297
4343
|
});
|
|
@@ -4326,7 +4372,7 @@ async function loadCache() {
|
|
|
4326
4372
|
}
|
|
4327
4373
|
async function saveCache(cache) {
|
|
4328
4374
|
try {
|
|
4329
|
-
await fs15.mkdir(
|
|
4375
|
+
await fs15.mkdir(path18.dirname(CACHE_FILE), { recursive: true });
|
|
4330
4376
|
await fs15.writeFile(CACHE_FILE, JSON.stringify(cache), "utf-8");
|
|
4331
4377
|
} catch {
|
|
4332
4378
|
}
|
|
@@ -4334,7 +4380,7 @@ async function saveCache(cache) {
|
|
|
4334
4380
|
|
|
4335
4381
|
// src/permissions/index.ts
|
|
4336
4382
|
import fs16 from "fs/promises";
|
|
4337
|
-
import
|
|
4383
|
+
import path19 from "path";
|
|
4338
4384
|
import os5 from "os";
|
|
4339
4385
|
var DEFAULT_PERMISSIONS = {
|
|
4340
4386
|
default: "prompt",
|
|
@@ -4352,8 +4398,8 @@ var DEFAULT_PERMISSIONS = {
|
|
|
4352
4398
|
]
|
|
4353
4399
|
};
|
|
4354
4400
|
async function loadPermissions(projectRoot) {
|
|
4355
|
-
const projectPath =
|
|
4356
|
-
const globalPath =
|
|
4401
|
+
const projectPath = path19.join(projectRoot, ".notch.json");
|
|
4402
|
+
const globalPath = path19.join(os5.homedir(), ".notch", "permissions.json");
|
|
4357
4403
|
let config = { ...DEFAULT_PERMISSIONS };
|
|
4358
4404
|
try {
|
|
4359
4405
|
const raw = await fs16.readFile(globalPath, "utf-8");
|
|
@@ -4410,16 +4456,16 @@ function mergePermissions(base, override) {
|
|
|
4410
4456
|
}
|
|
4411
4457
|
|
|
4412
4458
|
// src/hooks/index.ts
|
|
4413
|
-
import { execSync as
|
|
4459
|
+
import { execSync as execSync4 } from "child_process";
|
|
4414
4460
|
import fs17 from "fs/promises";
|
|
4415
4461
|
import { watch } from "fs";
|
|
4416
|
-
import
|
|
4462
|
+
import path20 from "path";
|
|
4417
4463
|
import os6 from "os";
|
|
4418
4464
|
import crypto from "crypto";
|
|
4419
|
-
var TRUST_STORE_PATH =
|
|
4465
|
+
var TRUST_STORE_PATH = path20.join(os6.homedir(), ".notch", "trusted-projects.json");
|
|
4420
4466
|
async function isTrustedProject(projectRoot, raw) {
|
|
4421
4467
|
const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
|
|
4422
|
-
const key =
|
|
4468
|
+
const key = path20.resolve(projectRoot);
|
|
4423
4469
|
try {
|
|
4424
4470
|
const store = JSON.parse(await fs17.readFile(TRUST_STORE_PATH, "utf-8"));
|
|
4425
4471
|
return store[key] === fingerprint;
|
|
@@ -4429,19 +4475,19 @@ async function isTrustedProject(projectRoot, raw) {
|
|
|
4429
4475
|
}
|
|
4430
4476
|
async function trustProject(projectRoot, raw) {
|
|
4431
4477
|
const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
|
|
4432
|
-
const key =
|
|
4478
|
+
const key = path20.resolve(projectRoot);
|
|
4433
4479
|
let store = {};
|
|
4434
4480
|
try {
|
|
4435
4481
|
store = JSON.parse(await fs17.readFile(TRUST_STORE_PATH, "utf-8"));
|
|
4436
4482
|
} catch {
|
|
4437
4483
|
}
|
|
4438
4484
|
store[key] = fingerprint;
|
|
4439
|
-
await fs17.mkdir(
|
|
4485
|
+
await fs17.mkdir(path20.dirname(TRUST_STORE_PATH), { recursive: true });
|
|
4440
4486
|
await fs17.writeFile(TRUST_STORE_PATH, JSON.stringify(store, null, 2));
|
|
4441
4487
|
}
|
|
4442
4488
|
async function loadHooks(projectRoot, promptTrust) {
|
|
4443
4489
|
const hooks = [];
|
|
4444
|
-
const globalPath =
|
|
4490
|
+
const globalPath = path20.join(os6.homedir(), ".notch", "hooks.json");
|
|
4445
4491
|
try {
|
|
4446
4492
|
const raw = await fs17.readFile(globalPath, "utf-8");
|
|
4447
4493
|
const parsed = JSON.parse(raw);
|
|
@@ -4450,7 +4496,7 @@ async function loadHooks(projectRoot, promptTrust) {
|
|
|
4450
4496
|
}
|
|
4451
4497
|
} catch {
|
|
4452
4498
|
}
|
|
4453
|
-
const projectPath =
|
|
4499
|
+
const projectPath = path20.join(projectRoot, ".notch.json");
|
|
4454
4500
|
try {
|
|
4455
4501
|
const raw = await fs17.readFile(projectPath, "utf-8");
|
|
4456
4502
|
const parsed = JSON.parse(raw);
|
|
@@ -4518,7 +4564,7 @@ async function executeHook(hook, context) {
|
|
|
4518
4564
|
NOTCH_CWD: context.cwd
|
|
4519
4565
|
};
|
|
4520
4566
|
try {
|
|
4521
|
-
const output =
|
|
4567
|
+
const output = execSync4(hook.command, {
|
|
4522
4568
|
cwd: context.cwd,
|
|
4523
4569
|
encoding: "utf-8",
|
|
4524
4570
|
timeout: hook.timeout ?? 1e4,
|
|
@@ -4547,7 +4593,7 @@ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
|
|
|
4547
4593
|
if (existing) clearTimeout(existing);
|
|
4548
4594
|
pending.set(filename, setTimeout(async () => {
|
|
4549
4595
|
pending.delete(filename);
|
|
4550
|
-
const filePath =
|
|
4596
|
+
const filePath = path20.join(projectRoot, filename);
|
|
4551
4597
|
const context = { cwd: projectRoot, file: filePath };
|
|
4552
4598
|
const { results } = await runHooks(hookConfig, "file-changed", context);
|
|
4553
4599
|
onHookResult?.("file-changed", results);
|
|
@@ -4568,15 +4614,15 @@ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
|
|
|
4568
4614
|
|
|
4569
4615
|
// src/session/index.ts
|
|
4570
4616
|
import fs18 from "fs/promises";
|
|
4571
|
-
import
|
|
4617
|
+
import path21 from "path";
|
|
4572
4618
|
import os7 from "os";
|
|
4573
|
-
var SESSION_DIR =
|
|
4619
|
+
var SESSION_DIR = path21.join(os7.homedir(), ".notch", "sessions");
|
|
4574
4620
|
var MAX_SESSIONS = 20;
|
|
4575
4621
|
async function ensureDir2() {
|
|
4576
4622
|
await fs18.mkdir(SESSION_DIR, { recursive: true });
|
|
4577
4623
|
}
|
|
4578
4624
|
function sessionPath(id) {
|
|
4579
|
-
return
|
|
4625
|
+
return path21.join(SESSION_DIR, `${id}.json`);
|
|
4580
4626
|
}
|
|
4581
4627
|
function generateId() {
|
|
4582
4628
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -4622,7 +4668,7 @@ async function listSessions() {
|
|
|
4622
4668
|
for (const file of files) {
|
|
4623
4669
|
if (!file.endsWith(".json")) continue;
|
|
4624
4670
|
try {
|
|
4625
|
-
const raw = await fs18.readFile(
|
|
4671
|
+
const raw = await fs18.readFile(path21.join(SESSION_DIR, file), "utf-8");
|
|
4626
4672
|
const session = JSON.parse(raw);
|
|
4627
4673
|
sessions.push(session.meta);
|
|
4628
4674
|
} catch {
|
|
@@ -4698,7 +4744,7 @@ async function exportSession(messages, outputPath, meta) {
|
|
|
4698
4744
|
|
|
4699
4745
|
// src/init.ts
|
|
4700
4746
|
import fs19 from "fs/promises";
|
|
4701
|
-
import
|
|
4747
|
+
import path22 from "path";
|
|
4702
4748
|
import chalk9 from "chalk";
|
|
4703
4749
|
var DEFAULT_CONFIG = {
|
|
4704
4750
|
model: "notch-forge",
|
|
@@ -4730,8 +4776,8 @@ var DEFAULT_INSTRUCTIONS = `# Project Instructions for Notch
|
|
|
4730
4776
|
<!-- Files or areas Notch should NOT modify -->
|
|
4731
4777
|
`;
|
|
4732
4778
|
async function initProject(projectRoot) {
|
|
4733
|
-
const configPath =
|
|
4734
|
-
const instructionsPath =
|
|
4779
|
+
const configPath = path22.join(projectRoot, ".notch.json");
|
|
4780
|
+
const instructionsPath = path22.join(projectRoot, ".notch.md");
|
|
4735
4781
|
let configExists = false;
|
|
4736
4782
|
let instructionsExist = false;
|
|
4737
4783
|
try {
|
|
@@ -4756,7 +4802,7 @@ async function initProject(projectRoot) {
|
|
|
4756
4802
|
} else {
|
|
4757
4803
|
console.log(chalk9.gray(` Skipped ${instructionsPath} (already exists)`));
|
|
4758
4804
|
}
|
|
4759
|
-
const gitignorePath =
|
|
4805
|
+
const gitignorePath = path22.join(projectRoot, ".gitignore");
|
|
4760
4806
|
try {
|
|
4761
4807
|
const gitignore = await fs19.readFile(gitignorePath, "utf-8");
|
|
4762
4808
|
const additions = [];
|
|
@@ -4890,9 +4936,9 @@ function findSync(oldLines, newLines, oi, ni, lookAhead) {
|
|
|
4890
4936
|
}
|
|
4891
4937
|
|
|
4892
4938
|
// src/commands/doctor.ts
|
|
4893
|
-
import { execSync as
|
|
4939
|
+
import { execSync as execSync5 } from "child_process";
|
|
4894
4940
|
import fs20 from "fs/promises";
|
|
4895
|
-
import
|
|
4941
|
+
import path23 from "path";
|
|
4896
4942
|
import os8 from "os";
|
|
4897
4943
|
import chalk10 from "chalk";
|
|
4898
4944
|
async function runDiagnostics(cwd) {
|
|
@@ -4907,7 +4953,7 @@ async function runDiagnostics(cwd) {
|
|
|
4907
4953
|
results.push({ name: "Node.js", status: "fail", message: `v${nodeVersion} (requires >= 18)` });
|
|
4908
4954
|
}
|
|
4909
4955
|
try {
|
|
4910
|
-
const gitVersion =
|
|
4956
|
+
const gitVersion = execSync5("git --version", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
4911
4957
|
results.push({ name: "Git", status: "ok", message: gitVersion });
|
|
4912
4958
|
} catch {
|
|
4913
4959
|
results.push({ name: "Git", status: "fail", message: "Not found. Install git to use git tools." });
|
|
@@ -4934,12 +4980,12 @@ async function runDiagnostics(cwd) {
|
|
|
4934
4980
|
results.push({ name: "Config", status: "fail", message: `Could not load: ${err.message}` });
|
|
4935
4981
|
}
|
|
4936
4982
|
try {
|
|
4937
|
-
await fs20.access(
|
|
4983
|
+
await fs20.access(path23.join(cwd, ".notch.json"));
|
|
4938
4984
|
results.push({ name: ".notch.json", status: "ok", message: "Found" });
|
|
4939
4985
|
} catch {
|
|
4940
4986
|
results.push({ name: ".notch.json", status: "warn", message: "Not found. Run: notch init" });
|
|
4941
4987
|
}
|
|
4942
|
-
const notchDir =
|
|
4988
|
+
const notchDir = path23.join(os8.homedir(), ".notch");
|
|
4943
4989
|
try {
|
|
4944
4990
|
await fs20.access(notchDir);
|
|
4945
4991
|
results.push({ name: "~/.notch/", status: "ok", message: "Exists" });
|
|
@@ -4947,13 +4993,13 @@ async function runDiagnostics(cwd) {
|
|
|
4947
4993
|
results.push({ name: "~/.notch/", status: "warn", message: "Not found (will be created on first use)" });
|
|
4948
4994
|
}
|
|
4949
4995
|
try {
|
|
4950
|
-
await fs20.access(
|
|
4996
|
+
await fs20.access(path23.join(cwd, ".notch.md"));
|
|
4951
4997
|
results.push({ name: ".notch.md", status: "ok", message: "Found" });
|
|
4952
4998
|
} catch {
|
|
4953
4999
|
results.push({ name: ".notch.md", status: "warn", message: "Not found. Run: notch init" });
|
|
4954
5000
|
}
|
|
4955
5001
|
try {
|
|
4956
|
-
const configRaw = await fs20.readFile(
|
|
5002
|
+
const configRaw = await fs20.readFile(path23.join(cwd, ".notch.json"), "utf-8").catch(() => "{}");
|
|
4957
5003
|
const mcpConfigs = parseMCPConfig(JSON.parse(configRaw));
|
|
4958
5004
|
const serverNames = Object.keys(mcpConfigs);
|
|
4959
5005
|
if (serverNames.length === 0) {
|
|
@@ -4974,7 +5020,7 @@ async function runDiagnostics(cwd) {
|
|
|
4974
5020
|
results.push({ name: "MCP Servers", status: "ok", message: "No config to check" });
|
|
4975
5021
|
}
|
|
4976
5022
|
try {
|
|
4977
|
-
const sessionsDir =
|
|
5023
|
+
const sessionsDir = path23.join(notchDir, "sessions");
|
|
4978
5024
|
const entries = await fs20.readdir(sessionsDir).catch(() => []);
|
|
4979
5025
|
results.push({ name: "Sessions", status: "ok", message: `${entries.length} saved` });
|
|
4980
5026
|
} catch {
|
|
@@ -5009,23 +5055,23 @@ registerCommand("/doctor", async (_args, ctx) => {
|
|
|
5009
5055
|
});
|
|
5010
5056
|
|
|
5011
5057
|
// src/commands/copy.ts
|
|
5012
|
-
import { execSync as
|
|
5058
|
+
import { execSync as execSync6 } from "child_process";
|
|
5013
5059
|
import chalk11 from "chalk";
|
|
5014
5060
|
function copyToClipboard(text) {
|
|
5015
5061
|
try {
|
|
5016
5062
|
const platform = process.platform;
|
|
5017
5063
|
if (platform === "win32") {
|
|
5018
|
-
|
|
5064
|
+
execSync6("clip.exe", { input: text, timeout: 5e3 });
|
|
5019
5065
|
} else if (platform === "darwin") {
|
|
5020
|
-
|
|
5066
|
+
execSync6("pbcopy", { input: text, timeout: 5e3 });
|
|
5021
5067
|
} else {
|
|
5022
5068
|
try {
|
|
5023
|
-
|
|
5069
|
+
execSync6("xclip -selection clipboard", { input: text, timeout: 5e3 });
|
|
5024
5070
|
} catch {
|
|
5025
5071
|
try {
|
|
5026
|
-
|
|
5072
|
+
execSync6("xsel --clipboard --input", { input: text, timeout: 5e3 });
|
|
5027
5073
|
} catch {
|
|
5028
|
-
|
|
5074
|
+
execSync6("wl-copy", { input: text, timeout: 5e3 });
|
|
5029
5075
|
}
|
|
5030
5076
|
}
|
|
5031
5077
|
}
|
|
@@ -5071,7 +5117,7 @@ registerCommand("/btw", async (args, ctx) => {
|
|
|
5071
5117
|
});
|
|
5072
5118
|
|
|
5073
5119
|
// src/commands/security-review.ts
|
|
5074
|
-
import { execFileSync as execFileSync2, execSync as
|
|
5120
|
+
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5075
5121
|
import chalk13 from "chalk";
|
|
5076
5122
|
function isValidGitRange(range) {
|
|
5077
5123
|
return /^[a-zA-Z0-9._~^\/\-]+(\.\.[a-zA-Z0-9._~^\/\-]+)?$/.test(range);
|
|
@@ -5099,12 +5145,12 @@ registerCommand("/security-review", async (args, ctx) => {
|
|
|
5099
5145
|
}).trim();
|
|
5100
5146
|
} catch {
|
|
5101
5147
|
try {
|
|
5102
|
-
stat =
|
|
5148
|
+
stat = execSync7("git diff --stat", {
|
|
5103
5149
|
cwd: ctx.cwd,
|
|
5104
5150
|
encoding: "utf-8",
|
|
5105
5151
|
timeout: 1e4
|
|
5106
5152
|
}).trim();
|
|
5107
|
-
diff =
|
|
5153
|
+
diff = execSync7("git diff", {
|
|
5108
5154
|
cwd: ctx.cwd,
|
|
5109
5155
|
encoding: "utf-8",
|
|
5110
5156
|
timeout: 1e4,
|
|
@@ -5376,12 +5422,12 @@ Read the file first, then make the change. Only modify this one file.`
|
|
|
5376
5422
|
});
|
|
5377
5423
|
|
|
5378
5424
|
// src/commands/plugin.ts
|
|
5379
|
-
import { execSync as
|
|
5425
|
+
import { execSync as execSync8, execFileSync as execFileSync3 } from "child_process";
|
|
5380
5426
|
import fs21 from "fs/promises";
|
|
5381
|
-
import
|
|
5427
|
+
import path24 from "path";
|
|
5382
5428
|
import os9 from "os";
|
|
5383
5429
|
import chalk16 from "chalk";
|
|
5384
|
-
var GLOBAL_PLUGINS_DIR =
|
|
5430
|
+
var GLOBAL_PLUGINS_DIR = path24.join(os9.homedir(), ".notch", "plugins");
|
|
5385
5431
|
registerCommand("/plugin", async (args, ctx) => {
|
|
5386
5432
|
const parts = args.split(/\s+/);
|
|
5387
5433
|
const subcommand = parts[0] || "list";
|
|
@@ -5418,7 +5464,7 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
5418
5464
|
return;
|
|
5419
5465
|
}
|
|
5420
5466
|
await fs21.mkdir(GLOBAL_PLUGINS_DIR, { recursive: true });
|
|
5421
|
-
const pluginDir =
|
|
5467
|
+
const pluginDir = path24.join(GLOBAL_PLUGINS_DIR, path24.basename(target).replace(/\.git$/, ""));
|
|
5422
5468
|
console.log(chalk16.gray(` Installing ${target}...`));
|
|
5423
5469
|
try {
|
|
5424
5470
|
if (target.includes("/") && !target.startsWith("@")) {
|
|
@@ -5443,9 +5489,9 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
5443
5489
|
});
|
|
5444
5490
|
}
|
|
5445
5491
|
try {
|
|
5446
|
-
const pkgExists = await fs21.access(
|
|
5492
|
+
const pkgExists = await fs21.access(path24.join(pluginDir, "package.json")).then(() => true).catch(() => false);
|
|
5447
5493
|
if (pkgExists) {
|
|
5448
|
-
|
|
5494
|
+
execSync8("npm install --production", {
|
|
5449
5495
|
cwd: pluginDir,
|
|
5450
5496
|
encoding: "utf-8",
|
|
5451
5497
|
timeout: 12e4,
|
|
@@ -5469,7 +5515,7 @@ registerCommand("/plugin", async (args, ctx) => {
|
|
|
5469
5515
|
console.log(chalk16.gray(" Usage: /plugin remove <plugin-name>\n"));
|
|
5470
5516
|
return;
|
|
5471
5517
|
}
|
|
5472
|
-
const pluginDir =
|
|
5518
|
+
const pluginDir = path24.join(GLOBAL_PLUGINS_DIR, target);
|
|
5473
5519
|
try {
|
|
5474
5520
|
await fs21.access(pluginDir);
|
|
5475
5521
|
await fs21.rm(pluginDir, { recursive: true, force: true });
|
|
@@ -5596,12 +5642,12 @@ Reply with ONLY the commit message, nothing else. No markdown, no explanation.`;
|
|
|
5596
5642
|
});
|
|
5597
5643
|
|
|
5598
5644
|
// src/commands/pr.ts
|
|
5599
|
-
import { execSync as
|
|
5645
|
+
import { execSync as execSync10, execFileSync as execFileSync5 } from "child_process";
|
|
5600
5646
|
import chalk18 from "chalk";
|
|
5601
5647
|
import ora3 from "ora";
|
|
5602
5648
|
function tryExec(cmd, cwd) {
|
|
5603
5649
|
try {
|
|
5604
|
-
return
|
|
5650
|
+
return execSync10(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
|
|
5605
5651
|
} catch {
|
|
5606
5652
|
return null;
|
|
5607
5653
|
}
|
|
@@ -5732,11 +5778,11 @@ BODY:
|
|
|
5732
5778
|
});
|
|
5733
5779
|
|
|
5734
5780
|
// src/commands/worktree.ts
|
|
5735
|
-
import { execSync as
|
|
5781
|
+
import { execSync as execSync11, execFileSync as execFileSync6 } from "child_process";
|
|
5736
5782
|
import chalk19 from "chalk";
|
|
5737
5783
|
function tryExec2(cmd, cwd) {
|
|
5738
5784
|
try {
|
|
5739
|
-
return
|
|
5785
|
+
return execSync11(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
|
|
5740
5786
|
} catch {
|
|
5741
5787
|
return null;
|
|
5742
5788
|
}
|
|
@@ -5847,7 +5893,7 @@ registerCommand("/worktree", async (args, ctx) => {
|
|
|
5847
5893
|
}
|
|
5848
5894
|
case "prune": {
|
|
5849
5895
|
try {
|
|
5850
|
-
|
|
5896
|
+
execSync11("git worktree prune", { cwd: ctx.cwd, encoding: "utf-8" });
|
|
5851
5897
|
console.log(chalk19.green(" \u2713 Pruned stale worktrees.\n"));
|
|
5852
5898
|
} catch (err) {
|
|
5853
5899
|
console.log(chalk19.red(` Failed: ${err.message}
|
|
@@ -6080,7 +6126,7 @@ registerCommand("/sandbox", async (args, _ctx) => {
|
|
|
6080
6126
|
|
|
6081
6127
|
// src/ui/completions.ts
|
|
6082
6128
|
import fs22 from "fs";
|
|
6083
|
-
import
|
|
6129
|
+
import path25 from "path";
|
|
6084
6130
|
var COMMANDS = [
|
|
6085
6131
|
"/help",
|
|
6086
6132
|
"/quit",
|
|
@@ -6173,15 +6219,15 @@ function buildCompleter(cwd) {
|
|
|
6173
6219
|
}
|
|
6174
6220
|
function completeFilePath(partial, cwd) {
|
|
6175
6221
|
try {
|
|
6176
|
-
const dir = partial.includes("/") ?
|
|
6177
|
-
const prefix = partial.includes("/") ?
|
|
6222
|
+
const dir = partial.includes("/") ? path25.resolve(cwd, path25.dirname(partial)) : cwd;
|
|
6223
|
+
const prefix = partial.includes("/") ? path25.basename(partial) : partial;
|
|
6178
6224
|
const entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
6179
6225
|
const matches = [];
|
|
6180
6226
|
for (const entry of entries) {
|
|
6181
6227
|
if (entry.name.startsWith(".")) continue;
|
|
6182
6228
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
6183
6229
|
if (entry.name.startsWith(prefix)) {
|
|
6184
|
-
const relative = partial.includes("/") ?
|
|
6230
|
+
const relative = partial.includes("/") ? path25.dirname(partial) + "/" + entry.name : entry.name;
|
|
6185
6231
|
if (entry.isDirectory()) {
|
|
6186
6232
|
matches.push(relative + "/");
|
|
6187
6233
|
} else {
|
|
@@ -6195,6 +6241,272 @@ function completeFilePath(partial, cwd) {
|
|
|
6195
6241
|
}
|
|
6196
6242
|
}
|
|
6197
6243
|
|
|
6244
|
+
// src/ui/slash-menu.ts
|
|
6245
|
+
import chalk26 from "chalk";
|
|
6246
|
+
var SLASH_COMMANDS = [
|
|
6247
|
+
// Core
|
|
6248
|
+
{ name: "/help", description: "Show available commands", category: "Core" },
|
|
6249
|
+
{ name: "/clear", description: "Clear conversation history", category: "Core" },
|
|
6250
|
+
{ name: "/quit", description: "Exit Notch", category: "Core" },
|
|
6251
|
+
// Model & Status
|
|
6252
|
+
{ name: "/model", description: "Switch or list models", category: "Model" },
|
|
6253
|
+
{ name: "/status", description: "Check API endpoint health", category: "Model" },
|
|
6254
|
+
// Session
|
|
6255
|
+
{ name: "/save", description: "Save current session", category: "Session" },
|
|
6256
|
+
{ name: "/sessions", description: "List saved sessions", category: "Session" },
|
|
6257
|
+
{ name: "/resume", description: "Resume a saved session", category: "Session" },
|
|
6258
|
+
{ name: "/rename", description: "Name this session", category: "Session" },
|
|
6259
|
+
{ name: "/export", description: "Export conversation to markdown", category: "Session" },
|
|
6260
|
+
{ name: "/branch", description: "Fork conversation at this point", category: "Session" },
|
|
6261
|
+
// Context
|
|
6262
|
+
{ name: "/usage", description: "Show token usage + context meter", category: "Context" },
|
|
6263
|
+
{ name: "/cost", description: "Show estimated session cost", category: "Context" },
|
|
6264
|
+
{ name: "/compact", description: "Compress conversation history", category: "Context" },
|
|
6265
|
+
{ name: "/rewind", description: "Rewind to a previous turn", category: "Context" },
|
|
6266
|
+
{ name: "/insights", description: "Session analytics & tool usage", category: "Context" },
|
|
6267
|
+
// Tools
|
|
6268
|
+
{ name: "/undo", description: "Undo last file changes", category: "Tools" },
|
|
6269
|
+
{ name: "/diff", description: "Show all file changes this session", category: "Tools" },
|
|
6270
|
+
{ name: "/search", description: "Search conversation history", category: "Tools" },
|
|
6271
|
+
{ name: "/tasks", description: "Show agent task list", category: "Tools" },
|
|
6272
|
+
// Git
|
|
6273
|
+
{ name: "/commit", description: "Smart commit with AI message", category: "Git" },
|
|
6274
|
+
{ name: "/pr", description: "Create PR with AI summary", category: "Git" },
|
|
6275
|
+
{ name: "/worktree", description: "Manage git worktrees", category: "Git" },
|
|
6276
|
+
// Planning & Agents
|
|
6277
|
+
{ name: "/plan", description: "Generate a step-by-step plan", category: "Planning" },
|
|
6278
|
+
{ name: "/agent", description: "Spawn a background subagent", category: "Planning" },
|
|
6279
|
+
{ name: "/ralph plan", description: "Generate autonomous task plan", category: "Planning" },
|
|
6280
|
+
{ name: "/ralph run", description: "Execute autonomous tasks", category: "Planning" },
|
|
6281
|
+
// Utilities
|
|
6282
|
+
{ name: "/doctor", description: "Diagnose installation health", category: "Utility" },
|
|
6283
|
+
{ name: "/copy", description: "Copy last response to clipboard", category: "Utility" },
|
|
6284
|
+
{ name: "/btw", description: "Side question (isolated context)", category: "Utility" },
|
|
6285
|
+
{ name: "/security-review", description: "Review changes for vulnerabilities", category: "Utility" },
|
|
6286
|
+
{ name: "/loop", description: "Run command on a recurring interval", category: "Utility" },
|
|
6287
|
+
{ name: "/batch", description: "Apply task across files in parallel", category: "Utility" },
|
|
6288
|
+
{ name: "/sandbox", description: "Toggle read-only sandbox mode", category: "Utility" },
|
|
6289
|
+
{ name: "/debug", description: "Toggle debug logging", category: "Utility" },
|
|
6290
|
+
// Config
|
|
6291
|
+
{ name: "/memory", description: "List saved memories", category: "Config" },
|
|
6292
|
+
{ name: "/permissions", description: "Show permission config", category: "Config" },
|
|
6293
|
+
{ name: "/theme", description: "Switch color theme", category: "Config" },
|
|
6294
|
+
{ name: "/plugin", description: "Manage plugins", category: "Config" }
|
|
6295
|
+
];
|
|
6296
|
+
var MAX_VISIBLE = 10;
|
|
6297
|
+
function createMenuState() {
|
|
6298
|
+
return {
|
|
6299
|
+
visible: false,
|
|
6300
|
+
filter: "",
|
|
6301
|
+
selected: 0,
|
|
6302
|
+
filtered: [],
|
|
6303
|
+
renderedLines: 0
|
|
6304
|
+
};
|
|
6305
|
+
}
|
|
6306
|
+
function updateMenu(state, input) {
|
|
6307
|
+
if (!input.startsWith("/") || input.includes(" ") || input.length === 0) {
|
|
6308
|
+
state.visible = false;
|
|
6309
|
+
state.filtered = [];
|
|
6310
|
+
state.selected = 0;
|
|
6311
|
+
return false;
|
|
6312
|
+
}
|
|
6313
|
+
state.filter = input.toLowerCase();
|
|
6314
|
+
state.filtered = SLASH_COMMANDS.filter(
|
|
6315
|
+
(cmd) => cmd.name.toLowerCase().startsWith(state.filter) || cmd.name.toLowerCase().includes(state.filter.slice(1)) || cmd.description.toLowerCase().includes(state.filter.slice(1))
|
|
6316
|
+
);
|
|
6317
|
+
if (state.selected >= state.filtered.length) {
|
|
6318
|
+
state.selected = Math.max(0, state.filtered.length - 1);
|
|
6319
|
+
}
|
|
6320
|
+
state.visible = state.filtered.length > 0;
|
|
6321
|
+
return state.visible;
|
|
6322
|
+
}
|
|
6323
|
+
function menuUp(state) {
|
|
6324
|
+
if (state.selected > 0) state.selected--;
|
|
6325
|
+
}
|
|
6326
|
+
function menuDown(state) {
|
|
6327
|
+
if (state.selected < state.filtered.length - 1) state.selected++;
|
|
6328
|
+
}
|
|
6329
|
+
function menuSelect(state) {
|
|
6330
|
+
if (!state.visible || state.filtered.length === 0) return null;
|
|
6331
|
+
return state.filtered[state.selected]?.name ?? null;
|
|
6332
|
+
}
|
|
6333
|
+
function menuDismiss(state) {
|
|
6334
|
+
state.visible = false;
|
|
6335
|
+
state.filtered = [];
|
|
6336
|
+
state.selected = 0;
|
|
6337
|
+
}
|
|
6338
|
+
function renderMenu(state) {
|
|
6339
|
+
if (!state.visible || state.filtered.length === 0) return "";
|
|
6340
|
+
const total = state.filtered.length;
|
|
6341
|
+
let scrollTop = 0;
|
|
6342
|
+
if (state.selected >= MAX_VISIBLE) {
|
|
6343
|
+
scrollTop = state.selected - MAX_VISIBLE + 1;
|
|
6344
|
+
}
|
|
6345
|
+
if (scrollTop + MAX_VISIBLE > total) {
|
|
6346
|
+
scrollTop = Math.max(0, total - MAX_VISIBLE);
|
|
6347
|
+
}
|
|
6348
|
+
const items = state.filtered.slice(scrollTop, scrollTop + MAX_VISIBLE);
|
|
6349
|
+
const hasMore = scrollTop + MAX_VISIBLE < total;
|
|
6350
|
+
const hasAbove = scrollTop > 0;
|
|
6351
|
+
let currentCategory = "";
|
|
6352
|
+
const lines = [];
|
|
6353
|
+
lines.push(chalk26.gray(" \u250C\u2500 Commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
6354
|
+
if (hasAbove) {
|
|
6355
|
+
const above = scrollTop;
|
|
6356
|
+
lines.push(chalk26.gray(` \u2502 ${chalk26.dim(`\u2191 ${above} more\u2026`)}${"".padEnd(40 - String(above).length)}\u2502`));
|
|
6357
|
+
}
|
|
6358
|
+
for (let i = 0; i < items.length; i++) {
|
|
6359
|
+
const cmd = items[i];
|
|
6360
|
+
const absoluteIndex = scrollTop + i;
|
|
6361
|
+
const isSelected = absoluteIndex === state.selected;
|
|
6362
|
+
if (cmd.category !== currentCategory) {
|
|
6363
|
+
currentCategory = cmd.category;
|
|
6364
|
+
if (i > 0) {
|
|
6365
|
+
lines.push(chalk26.gray(" \u2502 \u2502"));
|
|
6366
|
+
}
|
|
6367
|
+
lines.push(chalk26.gray(` \u2502 `) + chalk26.gray.dim(cmd.category.toUpperCase().padEnd(47)) + chalk26.gray("\u2502"));
|
|
6368
|
+
}
|
|
6369
|
+
const name = cmd.name.padEnd(22);
|
|
6370
|
+
const desc = cmd.description.slice(0, 25).padEnd(25);
|
|
6371
|
+
if (isSelected) {
|
|
6372
|
+
lines.push(
|
|
6373
|
+
chalk26.gray(" \u2502 ") + chalk26.bgWhite.black(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
|
|
6374
|
+
);
|
|
6375
|
+
} else {
|
|
6376
|
+
lines.push(
|
|
6377
|
+
chalk26.gray(" \u2502 ") + chalk26.cyan(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
|
|
6378
|
+
);
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
if (hasMore) {
|
|
6382
|
+
const remaining = state.filtered.length - MAX_VISIBLE;
|
|
6383
|
+
lines.push(chalk26.gray(` \u2502 ${chalk26.dim(`+${remaining} more\u2026`)}${"".padEnd(41 - String(remaining).length)}\u2502`));
|
|
6384
|
+
}
|
|
6385
|
+
lines.push(chalk26.gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2191\u2193 navigate \xB7 enter select \u2500\u2500\u2518"));
|
|
6386
|
+
state.renderedLines = lines.length;
|
|
6387
|
+
return lines.join("\n");
|
|
6388
|
+
}
|
|
6389
|
+
function clearMenu(state) {
|
|
6390
|
+
if (state.renderedLines === 0) return "";
|
|
6391
|
+
const lines = state.renderedLines;
|
|
6392
|
+
state.renderedLines = 0;
|
|
6393
|
+
let ansi = "";
|
|
6394
|
+
for (let i = 0; i < lines; i++) {
|
|
6395
|
+
ansi += "\x1B[1A\x1B[2K";
|
|
6396
|
+
}
|
|
6397
|
+
return ansi;
|
|
6398
|
+
}
|
|
6399
|
+
|
|
6400
|
+
// src/ui/interactive-input.ts
|
|
6401
|
+
function attachSlashMenu(rl) {
|
|
6402
|
+
const state = createMenuState();
|
|
6403
|
+
let lastInput = "";
|
|
6404
|
+
const keypressHandler = (_ch, key) => {
|
|
6405
|
+
const currentLine = rl.line ?? "";
|
|
6406
|
+
if (state.visible && key) {
|
|
6407
|
+
if (key.name === "up") {
|
|
6408
|
+
process.stdout.write(clearMenu(state));
|
|
6409
|
+
menuUp(state);
|
|
6410
|
+
process.stdout.write(renderMenu(state) + "\n");
|
|
6411
|
+
rewritePromptLine(rl);
|
|
6412
|
+
return;
|
|
6413
|
+
}
|
|
6414
|
+
if (key.name === "down") {
|
|
6415
|
+
process.stdout.write(clearMenu(state));
|
|
6416
|
+
menuDown(state);
|
|
6417
|
+
process.stdout.write(renderMenu(state) + "\n");
|
|
6418
|
+
rewritePromptLine(rl);
|
|
6419
|
+
return;
|
|
6420
|
+
}
|
|
6421
|
+
if (key.name === "escape") {
|
|
6422
|
+
process.stdout.write(clearMenu(state));
|
|
6423
|
+
menuDismiss(state);
|
|
6424
|
+
return;
|
|
6425
|
+
}
|
|
6426
|
+
if (key.name === "return") {
|
|
6427
|
+
const selected = menuSelect(state);
|
|
6428
|
+
if (selected) {
|
|
6429
|
+
process.stdout.write(clearMenu(state));
|
|
6430
|
+
menuDismiss(state);
|
|
6431
|
+
rl.line = "";
|
|
6432
|
+
rl.cursor = 0;
|
|
6433
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
6434
|
+
rl.write(selected + " ");
|
|
6435
|
+
return;
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
if (key.name === "tab") {
|
|
6439
|
+
const selected = menuSelect(state);
|
|
6440
|
+
if (selected) {
|
|
6441
|
+
process.stdout.write(clearMenu(state));
|
|
6442
|
+
menuDismiss(state);
|
|
6443
|
+
rl.line = "";
|
|
6444
|
+
rl.cursor = 0;
|
|
6445
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
6446
|
+
rl.write(selected + " ");
|
|
6447
|
+
return;
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
setImmediate(() => {
|
|
6452
|
+
const line = rl.line ?? "";
|
|
6453
|
+
if (line !== lastInput) {
|
|
6454
|
+
lastInput = line;
|
|
6455
|
+
const wasVisible = state.visible;
|
|
6456
|
+
const shouldShow = updateMenu(state, line);
|
|
6457
|
+
if (wasVisible && !shouldShow) {
|
|
6458
|
+
process.stdout.write(clearMenu(state));
|
|
6459
|
+
} else if (shouldShow) {
|
|
6460
|
+
if (wasVisible) {
|
|
6461
|
+
process.stdout.write(clearMenu(state));
|
|
6462
|
+
}
|
|
6463
|
+
const menuStr = renderMenu(state);
|
|
6464
|
+
if (menuStr) {
|
|
6465
|
+
process.stdout.write("\x1B[s");
|
|
6466
|
+
process.stdout.write(menuStr + "\n");
|
|
6467
|
+
rewritePromptLine(rl);
|
|
6468
|
+
process.stdout.write("\x1B[u");
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
});
|
|
6473
|
+
};
|
|
6474
|
+
process.stdin.on("keypress", keypressHandler);
|
|
6475
|
+
const closeHandler = () => {
|
|
6476
|
+
if (state.visible) {
|
|
6477
|
+
process.stdout.write(clearMenu(state));
|
|
6478
|
+
}
|
|
6479
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
6480
|
+
};
|
|
6481
|
+
rl.on("close", closeHandler);
|
|
6482
|
+
const lineHandler = () => {
|
|
6483
|
+
if (state.visible) {
|
|
6484
|
+
process.stdout.write(clearMenu(state));
|
|
6485
|
+
menuDismiss(state);
|
|
6486
|
+
}
|
|
6487
|
+
lastInput = "";
|
|
6488
|
+
};
|
|
6489
|
+
rl.on("line", lineHandler);
|
|
6490
|
+
return {
|
|
6491
|
+
cleanup: () => {
|
|
6492
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
6493
|
+
rl.removeListener("close", closeHandler);
|
|
6494
|
+
rl.removeListener("line", lineHandler);
|
|
6495
|
+
}
|
|
6496
|
+
};
|
|
6497
|
+
}
|
|
6498
|
+
function rewritePromptLine(rl) {
|
|
6499
|
+
const prompt = rl._prompt ?? "notch> ";
|
|
6500
|
+
const line = rl.line ?? "";
|
|
6501
|
+
const cursor = rl.cursor ?? line.length;
|
|
6502
|
+
process.stdout.write("\x1B[2K\r");
|
|
6503
|
+
process.stdout.write(prompt + line);
|
|
6504
|
+
const cursorOffset = line.length - cursor;
|
|
6505
|
+
if (cursorOffset > 0) {
|
|
6506
|
+
process.stdout.write(`\x1B[${cursorOffset}D`);
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
|
|
6198
6510
|
// src/index.ts
|
|
6199
6511
|
import fs23 from "fs/promises";
|
|
6200
6512
|
import { createRequire } from "module";
|
|
@@ -6219,7 +6531,7 @@ function printModelTable(activeModel) {
|
|
|
6219
6531
|
`));
|
|
6220
6532
|
}
|
|
6221
6533
|
function printHelp() {
|
|
6222
|
-
console.log(
|
|
6534
|
+
console.log(chalk27.gray(`
|
|
6223
6535
|
Commands:
|
|
6224
6536
|
/model \u2014 Show available models
|
|
6225
6537
|
/model <name> \u2014 Switch model (e.g., /model pyre)
|
|
@@ -6316,13 +6628,13 @@ async function main() {
|
|
|
6316
6628
|
try {
|
|
6317
6629
|
spinner.stop();
|
|
6318
6630
|
const creds = await login();
|
|
6319
|
-
console.log(
|
|
6631
|
+
console.log(chalk27.green(`
|
|
6320
6632
|
\u2713 Signed in as ${creds.email}`));
|
|
6321
|
-
console.log(
|
|
6633
|
+
console.log(chalk27.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
|
|
6322
6634
|
`));
|
|
6323
6635
|
} catch (err) {
|
|
6324
6636
|
spinner.stop();
|
|
6325
|
-
console.error(
|
|
6637
|
+
console.error(chalk27.red(`
|
|
6326
6638
|
Login failed: ${err.message}
|
|
6327
6639
|
`));
|
|
6328
6640
|
process.exit(1);
|
|
@@ -6332,10 +6644,10 @@ async function main() {
|
|
|
6332
6644
|
if (promptArgs[0] === "logout") {
|
|
6333
6645
|
const creds = await loadCredentials();
|
|
6334
6646
|
if (!creds) {
|
|
6335
|
-
console.log(
|
|
6647
|
+
console.log(chalk27.gray("\n Not signed in.\n"));
|
|
6336
6648
|
} else {
|
|
6337
6649
|
await clearCredentials();
|
|
6338
|
-
console.log(
|
|
6650
|
+
console.log(chalk27.green(`
|
|
6339
6651
|
\u2713 Signed out (${creds.email})
|
|
6340
6652
|
`));
|
|
6341
6653
|
}
|
|
@@ -6344,13 +6656,13 @@ async function main() {
|
|
|
6344
6656
|
if (promptArgs[0] === "whoami") {
|
|
6345
6657
|
const creds = await loadCredentials();
|
|
6346
6658
|
if (!creds) {
|
|
6347
|
-
console.log(
|
|
6659
|
+
console.log(chalk27.gray("\n Not signed in. Run: notch login\n"));
|
|
6348
6660
|
} else {
|
|
6349
6661
|
const keyPreview = `${creds.token.slice(0, 12)}...`;
|
|
6350
|
-
console.log(
|
|
6351
|
-
Signed in as ${
|
|
6352
|
-
console.log(
|
|
6353
|
-
console.log(
|
|
6662
|
+
console.log(chalk27.gray(`
|
|
6663
|
+
Signed in as ${chalk27.white(creds.email)}`));
|
|
6664
|
+
console.log(chalk27.gray(` Key: ${keyPreview}`));
|
|
6665
|
+
console.log(chalk27.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
|
|
6354
6666
|
`));
|
|
6355
6667
|
}
|
|
6356
6668
|
return;
|
|
@@ -6374,8 +6686,8 @@ async function main() {
|
|
|
6374
6686
|
const config = await loadConfig(configOverrides);
|
|
6375
6687
|
if (opts.model) {
|
|
6376
6688
|
if (!isValidModel(opts.model)) {
|
|
6377
|
-
console.error(
|
|
6378
|
-
console.error(
|
|
6689
|
+
console.error(chalk27.red(` Unknown model: ${opts.model}`));
|
|
6690
|
+
console.error(chalk27.gray(` Available: ${modelChoices}`));
|
|
6379
6691
|
process.exit(1);
|
|
6380
6692
|
}
|
|
6381
6693
|
config.models.chat.model = opts.model;
|
|
@@ -6414,11 +6726,11 @@ async function main() {
|
|
|
6414
6726
|
const updateMsg = await checkForUpdates(VERSION);
|
|
6415
6727
|
if (updateMsg) console.log(updateMsg);
|
|
6416
6728
|
const hookTrustPrompt = async (commands) => {
|
|
6417
|
-
console.warn(
|
|
6418
|
-
commands.forEach((cmd) => console.warn(
|
|
6729
|
+
console.warn(chalk27.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
|
|
6730
|
+
commands.forEach((cmd) => console.warn(chalk27.gray(` \u2022 ${cmd}`)));
|
|
6419
6731
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6420
6732
|
return new Promise((resolve2) => {
|
|
6421
|
-
rl2.question(
|
|
6733
|
+
rl2.question(chalk27.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
|
|
6422
6734
|
rl2.close();
|
|
6423
6735
|
resolve2(answer.trim().toLowerCase() === "y");
|
|
6424
6736
|
});
|
|
@@ -6464,22 +6776,22 @@ ${repoMapStr}` : ""
|
|
|
6464
6776
|
const client = new MCPClient(mcpConfig, name);
|
|
6465
6777
|
await client.connect();
|
|
6466
6778
|
mcpClients.push(client);
|
|
6467
|
-
console.log(
|
|
6779
|
+
console.log(chalk27.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
|
|
6468
6780
|
} catch (err) {
|
|
6469
|
-
console.log(
|
|
6781
|
+
console.log(chalk27.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
|
|
6470
6782
|
}
|
|
6471
6783
|
}
|
|
6472
6784
|
} catch {
|
|
6473
6785
|
}
|
|
6474
6786
|
try {
|
|
6475
6787
|
const pluginCount = await pluginManager.init(config.projectRoot, (msg) => {
|
|
6476
|
-
console.log(
|
|
6788
|
+
console.log(chalk27.gray(` ${msg}`));
|
|
6477
6789
|
});
|
|
6478
6790
|
if (pluginCount > 0) {
|
|
6479
|
-
console.log(
|
|
6791
|
+
console.log(chalk27.green(` Plugins: ${pluginCount} loaded`));
|
|
6480
6792
|
}
|
|
6481
6793
|
} catch (err) {
|
|
6482
|
-
console.log(
|
|
6794
|
+
console.log(chalk27.yellow(` Plugin init failed: ${err.message}`));
|
|
6483
6795
|
}
|
|
6484
6796
|
const toolCtx = {
|
|
6485
6797
|
cwd: config.projectRoot,
|
|
@@ -6493,7 +6805,7 @@ ${repoMapStr}` : ""
|
|
|
6493
6805
|
});
|
|
6494
6806
|
});
|
|
6495
6807
|
},
|
|
6496
|
-
log: (msg) => console.log(
|
|
6808
|
+
log: (msg) => console.log(chalk27.gray(` ${msg}`)),
|
|
6497
6809
|
checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
|
|
6498
6810
|
runHook: async (event, ctx) => {
|
|
6499
6811
|
if (!config.enableHooks || hookConfig.hooks.length === 0) return;
|
|
@@ -6503,7 +6815,7 @@ ${repoMapStr}` : ""
|
|
|
6503
6815
|
});
|
|
6504
6816
|
for (const r of results) {
|
|
6505
6817
|
if (!r.ok) {
|
|
6506
|
-
console.log(
|
|
6818
|
+
console.log(chalk27.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
|
|
6507
6819
|
}
|
|
6508
6820
|
}
|
|
6509
6821
|
}
|
|
@@ -6512,7 +6824,7 @@ ${repoMapStr}` : ""
|
|
|
6512
6824
|
const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
|
|
6513
6825
|
for (const r of results) {
|
|
6514
6826
|
if (!r.ok) {
|
|
6515
|
-
console.log(
|
|
6827
|
+
console.log(chalk27.yellow(` Hook failed (${event}): ${r.error}`));
|
|
6516
6828
|
}
|
|
6517
6829
|
}
|
|
6518
6830
|
});
|
|
@@ -6523,10 +6835,10 @@ ${repoMapStr}` : ""
|
|
|
6523
6835
|
if (session) {
|
|
6524
6836
|
messages.push(...session.messages);
|
|
6525
6837
|
sessionId = session.meta.id;
|
|
6526
|
-
console.log(
|
|
6838
|
+
console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
6527
6839
|
`));
|
|
6528
6840
|
} else {
|
|
6529
|
-
console.log(
|
|
6841
|
+
console.log(chalk27.gray(" No session to resume.\n"));
|
|
6530
6842
|
}
|
|
6531
6843
|
}
|
|
6532
6844
|
const pipedInput = await readStdin();
|
|
@@ -6547,10 +6859,10 @@ Analyze the above input.`;
|
|
|
6547
6859
|
const refContext = formatReferences(references);
|
|
6548
6860
|
const finalPrompt = refContext + cleanInput;
|
|
6549
6861
|
messages.push({ role: "user", content: finalPrompt });
|
|
6550
|
-
console.log(
|
|
6862
|
+
console.log(chalk27.cyan(`> ${oneShot || "(piped input)"}
|
|
6551
6863
|
`));
|
|
6552
6864
|
if (references.length > 0) {
|
|
6553
|
-
console.log(
|
|
6865
|
+
console.log(chalk27.gray(` Injected ${references.length} reference(s)
|
|
6554
6866
|
`));
|
|
6555
6867
|
}
|
|
6556
6868
|
const spinner = ora4("Thinking...").start();
|
|
@@ -6569,13 +6881,13 @@ Analyze the above input.`;
|
|
|
6569
6881
|
onToolCall: (name, args) => {
|
|
6570
6882
|
if (spinner.isSpinning) spinner.stop();
|
|
6571
6883
|
const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
|
|
6572
|
-
console.log(
|
|
6884
|
+
console.log(chalk27.gray(`
|
|
6573
6885
|
\u2192 ${name}(${argSummary})`));
|
|
6574
6886
|
},
|
|
6575
6887
|
onToolResult: (_name, result, isError) => {
|
|
6576
6888
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
6577
|
-
const icon = isError ?
|
|
6578
|
-
console.log(
|
|
6889
|
+
const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
|
|
6890
|
+
console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
6579
6891
|
}
|
|
6580
6892
|
})
|
|
6581
6893
|
);
|
|
@@ -6600,19 +6912,21 @@ Analyze the above input.`;
|
|
|
6600
6912
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
6601
6913
|
if (savedPlan) {
|
|
6602
6914
|
ralphPlan = savedPlan;
|
|
6603
|
-
console.log(
|
|
6915
|
+
console.log(chalk27.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
|
|
6604
6916
|
`));
|
|
6605
6917
|
}
|
|
6606
6918
|
} catch {
|
|
6607
6919
|
}
|
|
6608
6920
|
const completer = buildCompleter(config.projectRoot);
|
|
6921
|
+
readline.emitKeypressEvents(process.stdin);
|
|
6609
6922
|
const rl = readline.createInterface({
|
|
6610
6923
|
input: process.stdin,
|
|
6611
6924
|
output: process.stdout,
|
|
6612
6925
|
prompt: theme().prompt("notch> "),
|
|
6613
6926
|
completer: (line) => completer(line)
|
|
6614
6927
|
});
|
|
6615
|
-
|
|
6928
|
+
const slashMenu = attachSlashMenu(rl);
|
|
6929
|
+
console.log(chalk27.gray(" Type your request, or /help for commands.\n"));
|
|
6616
6930
|
rl.prompt();
|
|
6617
6931
|
rl.on("line", async (line) => {
|
|
6618
6932
|
const input = line.trim();
|
|
@@ -6629,7 +6943,7 @@ Analyze the above input.`;
|
|
|
6629
6943
|
try {
|
|
6630
6944
|
const name = getSessionName();
|
|
6631
6945
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
6632
|
-
console.log(
|
|
6946
|
+
console.log(chalk27.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
|
|
6633
6947
|
} catch {
|
|
6634
6948
|
}
|
|
6635
6949
|
}
|
|
@@ -6639,16 +6953,17 @@ Analyze the above input.`;
|
|
|
6639
6953
|
} catch {
|
|
6640
6954
|
}
|
|
6641
6955
|
}
|
|
6956
|
+
slashMenu.cleanup();
|
|
6642
6957
|
stopActiveLoop();
|
|
6643
6958
|
stopFileWatcher();
|
|
6644
6959
|
await pluginManager.shutdown();
|
|
6645
6960
|
await toolCtx.runHook?.("session-end", {});
|
|
6646
|
-
console.log(
|
|
6961
|
+
console.log(chalk27.gray("\n Goodbye!\n"));
|
|
6647
6962
|
process.exit(0);
|
|
6648
6963
|
}
|
|
6649
6964
|
if (input === "/clear") {
|
|
6650
6965
|
messages.length = 0;
|
|
6651
|
-
console.log(
|
|
6966
|
+
console.log(chalk27.gray(" Conversation cleared.\n"));
|
|
6652
6967
|
rl.prompt();
|
|
6653
6968
|
return;
|
|
6654
6969
|
}
|
|
@@ -6668,8 +6983,8 @@ Analyze the above input.`;
|
|
|
6668
6983
|
newModel = `notch-${newModel}`;
|
|
6669
6984
|
}
|
|
6670
6985
|
if (!isValidModel(newModel)) {
|
|
6671
|
-
console.log(
|
|
6672
|
-
console.log(
|
|
6986
|
+
console.log(chalk27.red(` Unknown model: ${newModel}`));
|
|
6987
|
+
console.log(chalk27.gray(` Available: ${modelChoices}`));
|
|
6673
6988
|
rl.prompt();
|
|
6674
6989
|
return;
|
|
6675
6990
|
}
|
|
@@ -6677,7 +6992,7 @@ Analyze the above input.`;
|
|
|
6677
6992
|
config.models.chat.model = activeModelId;
|
|
6678
6993
|
model = resolveModel(config.models.chat);
|
|
6679
6994
|
const switchedInfo = MODEL_CATALOG[activeModelId];
|
|
6680
|
-
console.log(
|
|
6995
|
+
console.log(chalk27.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
|
|
6681
6996
|
`));
|
|
6682
6997
|
rl.prompt();
|
|
6683
6998
|
return;
|
|
@@ -6690,22 +7005,22 @@ Analyze the above input.`;
|
|
|
6690
7005
|
statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
|
|
6691
7006
|
} else {
|
|
6692
7007
|
statusSpinner.fail(check.error ?? "API unreachable");
|
|
6693
|
-
console.log(
|
|
7008
|
+
console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
|
|
6694
7009
|
}
|
|
6695
7010
|
rl.prompt();
|
|
6696
7011
|
return;
|
|
6697
7012
|
}
|
|
6698
7013
|
if (input === "/undo") {
|
|
6699
7014
|
if (checkpoints.undoCount === 0) {
|
|
6700
|
-
console.log(
|
|
7015
|
+
console.log(chalk27.gray(" Nothing to undo.\n"));
|
|
6701
7016
|
rl.prompt();
|
|
6702
7017
|
return;
|
|
6703
7018
|
}
|
|
6704
7019
|
const undone = await checkpoints.undo();
|
|
6705
7020
|
if (undone) {
|
|
6706
7021
|
const fileCount = undone.files.length;
|
|
6707
|
-
console.log(
|
|
6708
|
-
console.log(
|
|
7022
|
+
console.log(chalk27.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
|
|
7023
|
+
console.log(chalk27.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
|
|
6709
7024
|
`));
|
|
6710
7025
|
}
|
|
6711
7026
|
rl.prompt();
|
|
@@ -6713,7 +7028,7 @@ Analyze the above input.`;
|
|
|
6713
7028
|
}
|
|
6714
7029
|
if (input === "/usage") {
|
|
6715
7030
|
if (usage.turnCount === 0) {
|
|
6716
|
-
console.log(
|
|
7031
|
+
console.log(chalk27.gray(" No usage yet.\n"));
|
|
6717
7032
|
} else {
|
|
6718
7033
|
console.log(usage.formatSession());
|
|
6719
7034
|
const currentTokens = estimateTokens(messages);
|
|
@@ -6726,7 +7041,7 @@ Analyze the above input.`;
|
|
|
6726
7041
|
}
|
|
6727
7042
|
if (input === "/cost") {
|
|
6728
7043
|
if (costTracker.totalCost === 0) {
|
|
6729
|
-
console.log(
|
|
7044
|
+
console.log(chalk27.gray(" No cost data yet.\n"));
|
|
6730
7045
|
} else {
|
|
6731
7046
|
console.log(costTracker.formatTotal());
|
|
6732
7047
|
console.log(costTracker.formatByModel());
|
|
@@ -6741,7 +7056,7 @@ Analyze the above input.`;
|
|
|
6741
7056
|
const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
|
|
6742
7057
|
messages.length = 0;
|
|
6743
7058
|
messages.push(...compressed);
|
|
6744
|
-
console.log(
|
|
7059
|
+
console.log(chalk27.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
|
|
6745
7060
|
console.log("");
|
|
6746
7061
|
rl.prompt();
|
|
6747
7062
|
return;
|
|
@@ -6749,10 +7064,10 @@ Analyze the above input.`;
|
|
|
6749
7064
|
if (input === "/diff") {
|
|
6750
7065
|
const diffs = checkpoints.allDiffs();
|
|
6751
7066
|
if (diffs.length === 0) {
|
|
6752
|
-
console.log(
|
|
7067
|
+
console.log(chalk27.gray(" No file changes this session.\n"));
|
|
6753
7068
|
} else {
|
|
6754
7069
|
for (const df of diffs) {
|
|
6755
|
-
console.log(
|
|
7070
|
+
console.log(chalk27.cyan(` ${df.path}:`));
|
|
6756
7071
|
console.log(unifiedDiff(df.before, df.after, df.path));
|
|
6757
7072
|
console.log("");
|
|
6758
7073
|
}
|
|
@@ -6768,10 +7083,10 @@ Analyze the above input.`;
|
|
|
6768
7083
|
projectRoot: config.projectRoot,
|
|
6769
7084
|
outputPath: exportPath
|
|
6770
7085
|
});
|
|
6771
|
-
console.log(
|
|
7086
|
+
console.log(chalk27.green(` Exported to ${ePath}
|
|
6772
7087
|
`));
|
|
6773
7088
|
} catch (err) {
|
|
6774
|
-
console.log(
|
|
7089
|
+
console.log(chalk27.red(` Export failed: ${err.message}
|
|
6775
7090
|
`));
|
|
6776
7091
|
}
|
|
6777
7092
|
rl.prompt();
|
|
@@ -6781,10 +7096,10 @@ Analyze the above input.`;
|
|
|
6781
7096
|
try {
|
|
6782
7097
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
6783
7098
|
sessionId = id;
|
|
6784
|
-
console.log(
|
|
7099
|
+
console.log(chalk27.green(` Session saved: ${id}
|
|
6785
7100
|
`));
|
|
6786
7101
|
} catch (err) {
|
|
6787
|
-
console.log(
|
|
7102
|
+
console.log(chalk27.red(` Save failed: ${err.message}
|
|
6788
7103
|
`));
|
|
6789
7104
|
}
|
|
6790
7105
|
rl.prompt();
|
|
@@ -6794,16 +7109,16 @@ Analyze the above input.`;
|
|
|
6794
7109
|
try {
|
|
6795
7110
|
const sessions = await listSessions(config.projectRoot);
|
|
6796
7111
|
if (sessions.length === 0) {
|
|
6797
|
-
console.log(
|
|
7112
|
+
console.log(chalk27.gray(" No saved sessions.\n"));
|
|
6798
7113
|
} else {
|
|
6799
|
-
console.log(
|
|
7114
|
+
console.log(chalk27.gray("\n Saved sessions:\n"));
|
|
6800
7115
|
for (const s of sessions.slice(0, 10)) {
|
|
6801
|
-
console.log(
|
|
7116
|
+
console.log(chalk27.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
|
|
6802
7117
|
}
|
|
6803
7118
|
console.log("");
|
|
6804
7119
|
}
|
|
6805
7120
|
} catch (err) {
|
|
6806
|
-
console.log(
|
|
7121
|
+
console.log(chalk27.red(` Error listing sessions: ${err.message}
|
|
6807
7122
|
`));
|
|
6808
7123
|
}
|
|
6809
7124
|
rl.prompt();
|
|
@@ -6821,18 +7136,18 @@ Analyze the above input.`;
|
|
|
6821
7136
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
6822
7137
|
if (savedPlan) {
|
|
6823
7138
|
ralphPlan = savedPlan;
|
|
6824
|
-
console.log(
|
|
7139
|
+
console.log(chalk27.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
|
|
6825
7140
|
`));
|
|
6826
7141
|
}
|
|
6827
7142
|
} catch {
|
|
6828
7143
|
}
|
|
6829
|
-
console.log(
|
|
7144
|
+
console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
6830
7145
|
`));
|
|
6831
7146
|
} else {
|
|
6832
|
-
console.log(
|
|
7147
|
+
console.log(chalk27.gray(" No session found.\n"));
|
|
6833
7148
|
}
|
|
6834
7149
|
} catch (err) {
|
|
6835
|
-
console.log(
|
|
7150
|
+
console.log(chalk27.red(` Resume failed: ${err.message}
|
|
6836
7151
|
`));
|
|
6837
7152
|
}
|
|
6838
7153
|
rl.prompt();
|
|
@@ -6841,7 +7156,7 @@ Analyze the above input.`;
|
|
|
6841
7156
|
if (input.startsWith("/search ")) {
|
|
6842
7157
|
const query = input.replace("/search ", "").trim().toLowerCase();
|
|
6843
7158
|
if (!query) {
|
|
6844
|
-
console.log(
|
|
7159
|
+
console.log(chalk27.gray(" Usage: /search <query>\n"));
|
|
6845
7160
|
rl.prompt();
|
|
6846
7161
|
return;
|
|
6847
7162
|
}
|
|
@@ -6858,14 +7173,14 @@ Analyze the above input.`;
|
|
|
6858
7173
|
}
|
|
6859
7174
|
}
|
|
6860
7175
|
if (matches.length === 0) {
|
|
6861
|
-
console.log(
|
|
7176
|
+
console.log(chalk27.gray(` No matches for "${query}"
|
|
6862
7177
|
`));
|
|
6863
7178
|
} else {
|
|
6864
|
-
console.log(
|
|
7179
|
+
console.log(chalk27.gray(`
|
|
6865
7180
|
${matches.length} match(es) for "${query}":
|
|
6866
7181
|
`));
|
|
6867
7182
|
for (const m of matches.slice(0, 10)) {
|
|
6868
|
-
console.log(
|
|
7183
|
+
console.log(chalk27.gray(` [${m.index}] ${m.role}: ${m.preview}`));
|
|
6869
7184
|
}
|
|
6870
7185
|
console.log("");
|
|
6871
7186
|
}
|
|
@@ -6879,7 +7194,7 @@ Analyze the above input.`;
|
|
|
6879
7194
|
activePlan = await generatePlan(task, model, systemPrompt);
|
|
6880
7195
|
planSpinner.succeed("Plan generated");
|
|
6881
7196
|
console.log(formatPlan(activePlan));
|
|
6882
|
-
console.log(
|
|
7197
|
+
console.log(chalk27.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
|
|
6883
7198
|
} catch (err) {
|
|
6884
7199
|
planSpinner.fail(`Plan failed: ${err.message}`);
|
|
6885
7200
|
}
|
|
@@ -6888,11 +7203,11 @@ Analyze the above input.`;
|
|
|
6888
7203
|
}
|
|
6889
7204
|
if (input === "/plan approve") {
|
|
6890
7205
|
if (!activePlan) {
|
|
6891
|
-
console.log(
|
|
7206
|
+
console.log(chalk27.gray(" No active plan. Use /plan <task> to create one.\n"));
|
|
6892
7207
|
rl.prompt();
|
|
6893
7208
|
return;
|
|
6894
7209
|
}
|
|
6895
|
-
console.log(
|
|
7210
|
+
console.log(chalk27.green(" Executing plan...\n"));
|
|
6896
7211
|
while (!isPlanComplete(activePlan)) {
|
|
6897
7212
|
const stepPrompt = currentStepPrompt(activePlan);
|
|
6898
7213
|
messages.push({ role: "user", content: stepPrompt });
|
|
@@ -6910,10 +7225,10 @@ Analyze the above input.`;
|
|
|
6910
7225
|
},
|
|
6911
7226
|
onToolCall: (name) => {
|
|
6912
7227
|
if (planStepSpinner.isSpinning) planStepSpinner.stop();
|
|
6913
|
-
console.log(
|
|
7228
|
+
console.log(chalk27.gray(` \u2192 ${name}`));
|
|
6914
7229
|
},
|
|
6915
7230
|
onToolResult: (_name, _result, isError) => {
|
|
6916
|
-
console.log(isError ?
|
|
7231
|
+
console.log(isError ? chalk27.red(" \u2717") : chalk27.green(" \u2713"));
|
|
6917
7232
|
}
|
|
6918
7233
|
});
|
|
6919
7234
|
console.log("\n");
|
|
@@ -6926,7 +7241,7 @@ Analyze the above input.`;
|
|
|
6926
7241
|
}
|
|
6927
7242
|
}
|
|
6928
7243
|
if (activePlan && isPlanComplete(activePlan)) {
|
|
6929
|
-
console.log(
|
|
7244
|
+
console.log(chalk27.green(" Plan completed!\n"));
|
|
6930
7245
|
}
|
|
6931
7246
|
activePlan = null;
|
|
6932
7247
|
rl.prompt();
|
|
@@ -6934,26 +7249,26 @@ Analyze the above input.`;
|
|
|
6934
7249
|
}
|
|
6935
7250
|
if (input === "/plan edit") {
|
|
6936
7251
|
if (!activePlan) {
|
|
6937
|
-
console.log(
|
|
7252
|
+
console.log(chalk27.gray(" No active plan to edit.\n"));
|
|
6938
7253
|
rl.prompt();
|
|
6939
7254
|
return;
|
|
6940
7255
|
}
|
|
6941
7256
|
console.log(formatPlan(activePlan));
|
|
6942
|
-
console.log(
|
|
6943
|
-
console.log(
|
|
7257
|
+
console.log(chalk27.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
|
|
7258
|
+
console.log(chalk27.gray(" or /plan approve to proceed with the current plan.\n"));
|
|
6944
7259
|
rl.prompt();
|
|
6945
7260
|
return;
|
|
6946
7261
|
}
|
|
6947
7262
|
if (input === "/plan cancel") {
|
|
6948
7263
|
activePlan = null;
|
|
6949
|
-
console.log(
|
|
7264
|
+
console.log(chalk27.gray(" Plan discarded.\n"));
|
|
6950
7265
|
rl.prompt();
|
|
6951
7266
|
return;
|
|
6952
7267
|
}
|
|
6953
7268
|
if (input.startsWith("/agent ")) {
|
|
6954
7269
|
const task = input.replace("/agent ", "").trim();
|
|
6955
7270
|
const agentId = nextSubagentId();
|
|
6956
|
-
console.log(
|
|
7271
|
+
console.log(chalk27.cyan(` Spawning subagent #${agentId}: ${task}
|
|
6957
7272
|
`));
|
|
6958
7273
|
spawnSubagent({
|
|
6959
7274
|
id: agentId,
|
|
@@ -6963,14 +7278,14 @@ Analyze the above input.`;
|
|
|
6963
7278
|
toolContext: toolCtx,
|
|
6964
7279
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
6965
7280
|
onComplete: (result) => {
|
|
6966
|
-
console.log(
|
|
7281
|
+
console.log(chalk27.green(`
|
|
6967
7282
|
Subagent #${agentId} finished:`));
|
|
6968
|
-
console.log(
|
|
7283
|
+
console.log(chalk27.gray(` ${result.slice(0, 200)}
|
|
6969
7284
|
`));
|
|
6970
7285
|
rl.prompt();
|
|
6971
7286
|
},
|
|
6972
7287
|
onError: (err) => {
|
|
6973
|
-
console.log(
|
|
7288
|
+
console.log(chalk27.red(`
|
|
6974
7289
|
Subagent #${agentId} failed: ${err}
|
|
6975
7290
|
`));
|
|
6976
7291
|
rl.prompt();
|
|
@@ -6983,18 +7298,18 @@ Analyze the above input.`;
|
|
|
6983
7298
|
try {
|
|
6984
7299
|
const memories = await loadMemories(config.projectRoot);
|
|
6985
7300
|
if (memories.length === 0) {
|
|
6986
|
-
console.log(
|
|
7301
|
+
console.log(chalk27.gray(" No saved memories.\n"));
|
|
6987
7302
|
} else {
|
|
6988
|
-
console.log(
|
|
7303
|
+
console.log(chalk27.gray(`
|
|
6989
7304
|
${memories.length} saved memories:
|
|
6990
7305
|
`));
|
|
6991
7306
|
for (const m of memories) {
|
|
6992
|
-
console.log(
|
|
7307
|
+
console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
|
|
6993
7308
|
}
|
|
6994
7309
|
console.log("");
|
|
6995
7310
|
}
|
|
6996
7311
|
} catch (err) {
|
|
6997
|
-
console.log(
|
|
7312
|
+
console.log(chalk27.red(` Error: ${err.message}
|
|
6998
7313
|
`));
|
|
6999
7314
|
}
|
|
7000
7315
|
rl.prompt();
|
|
@@ -7005,16 +7320,16 @@ Analyze the above input.`;
|
|
|
7005
7320
|
try {
|
|
7006
7321
|
const results = await searchMemories(config.projectRoot, query);
|
|
7007
7322
|
if (results.length === 0) {
|
|
7008
|
-
console.log(
|
|
7323
|
+
console.log(chalk27.gray(` No memories matching "${query}"
|
|
7009
7324
|
`));
|
|
7010
7325
|
} else {
|
|
7011
7326
|
for (const m of results) {
|
|
7012
|
-
console.log(
|
|
7327
|
+
console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
|
|
7013
7328
|
}
|
|
7014
7329
|
console.log("");
|
|
7015
7330
|
}
|
|
7016
7331
|
} catch (err) {
|
|
7017
|
-
console.log(
|
|
7332
|
+
console.log(chalk27.red(` Error: ${err.message}
|
|
7018
7333
|
`));
|
|
7019
7334
|
}
|
|
7020
7335
|
rl.prompt();
|
|
@@ -7026,10 +7341,10 @@ Analyze the above input.`;
|
|
|
7026
7341
|
for (const m of memories) {
|
|
7027
7342
|
await deleteMemory(config.projectRoot, m.id);
|
|
7028
7343
|
}
|
|
7029
|
-
console.log(
|
|
7344
|
+
console.log(chalk27.yellow(` Cleared ${memories.length} memories.
|
|
7030
7345
|
`));
|
|
7031
7346
|
} catch (err) {
|
|
7032
|
-
console.log(
|
|
7347
|
+
console.log(chalk27.red(` Error: ${err.message}
|
|
7033
7348
|
`));
|
|
7034
7349
|
}
|
|
7035
7350
|
rl.prompt();
|
|
@@ -7057,27 +7372,27 @@ Analyze the above input.`;
|
|
|
7057
7372
|
}
|
|
7058
7373
|
if (input === "/ralph run") {
|
|
7059
7374
|
if (!ralphPlan) {
|
|
7060
|
-
console.log(
|
|
7375
|
+
console.log(chalk27.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
|
|
7061
7376
|
rl.prompt();
|
|
7062
7377
|
return;
|
|
7063
7378
|
}
|
|
7064
|
-
console.log(
|
|
7379
|
+
console.log(chalk27.green(" Ralph is running...\n"));
|
|
7065
7380
|
try {
|
|
7066
7381
|
ralphPlan = await runRalphLoop(ralphPlan, {
|
|
7067
7382
|
model,
|
|
7068
7383
|
systemPrompt,
|
|
7069
7384
|
toolContext: toolCtx,
|
|
7070
7385
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
7071
|
-
onTaskStart: (task) => console.log(
|
|
7072
|
-
onTaskComplete: (task) => console.log(
|
|
7386
|
+
onTaskStart: (task) => console.log(chalk27.cyan(` \u25B6 Task: ${task.description}`)),
|
|
7387
|
+
onTaskComplete: (task) => console.log(chalk27.green(` \u2713 Done: ${task.description}
|
|
7073
7388
|
`)),
|
|
7074
|
-
onTaskFail: (task, err) => console.log(
|
|
7389
|
+
onTaskFail: (task, err) => console.log(chalk27.red(` \u2717 Failed: ${task.description} (${err})
|
|
7075
7390
|
`))
|
|
7076
7391
|
});
|
|
7077
7392
|
await savePlan(config.projectRoot, ralphPlan);
|
|
7078
7393
|
console.log(formatRalphStatus(ralphPlan));
|
|
7079
7394
|
} catch (err) {
|
|
7080
|
-
console.log(
|
|
7395
|
+
console.log(chalk27.red(` Ralph error: ${err.message}
|
|
7081
7396
|
`));
|
|
7082
7397
|
}
|
|
7083
7398
|
rl.prompt();
|
|
@@ -7085,7 +7400,7 @@ Analyze the above input.`;
|
|
|
7085
7400
|
}
|
|
7086
7401
|
if (input === "/ralph status") {
|
|
7087
7402
|
if (!ralphPlan) {
|
|
7088
|
-
console.log(
|
|
7403
|
+
console.log(chalk27.gray(" No Ralph plan active.\n"));
|
|
7089
7404
|
} else {
|
|
7090
7405
|
console.log(formatRalphStatus(ralphPlan));
|
|
7091
7406
|
}
|
|
@@ -7096,26 +7411,26 @@ Analyze the above input.`;
|
|
|
7096
7411
|
ralphPlan = null;
|
|
7097
7412
|
await deletePlan(config.projectRoot).catch(() => {
|
|
7098
7413
|
});
|
|
7099
|
-
console.log(
|
|
7414
|
+
console.log(chalk27.gray(" Ralph plan cleared.\n"));
|
|
7100
7415
|
rl.prompt();
|
|
7101
7416
|
return;
|
|
7102
7417
|
}
|
|
7103
7418
|
if (input === "/branch") {
|
|
7104
7419
|
const branchId = `branch-${branches.size + 1}`;
|
|
7105
7420
|
branches.set(branchId, [...messages]);
|
|
7106
|
-
console.log(
|
|
7421
|
+
console.log(chalk27.green(` Forked conversation as "${branchId}" (${messages.length} messages)
|
|
7107
7422
|
`));
|
|
7108
7423
|
rl.prompt();
|
|
7109
7424
|
return;
|
|
7110
7425
|
}
|
|
7111
7426
|
if (input === "/branches") {
|
|
7112
7427
|
if (branches.size === 0) {
|
|
7113
|
-
console.log(
|
|
7428
|
+
console.log(chalk27.gray(" No conversation branches. Use /branch to fork.\n"));
|
|
7114
7429
|
} else {
|
|
7115
|
-
console.log(
|
|
7430
|
+
console.log(chalk27.gray("\n Branches:\n"));
|
|
7116
7431
|
for (const [name, msgs] of branches) {
|
|
7117
|
-
const marker = name === currentBranch ?
|
|
7118
|
-
console.log(
|
|
7432
|
+
const marker = name === currentBranch ? chalk27.green(" \u25CF") : " ";
|
|
7433
|
+
console.log(chalk27.gray(` ${marker} ${name} (${msgs.length} messages)`));
|
|
7119
7434
|
}
|
|
7120
7435
|
console.log("");
|
|
7121
7436
|
}
|
|
@@ -7157,7 +7472,7 @@ Analyze the above input.`;
|
|
|
7157
7472
|
modelId: activeModelId,
|
|
7158
7473
|
messages,
|
|
7159
7474
|
lastResponse: lastAssistantResponse,
|
|
7160
|
-
log: (msg) => console.log(
|
|
7475
|
+
log: (msg) => console.log(chalk27.gray(` ${msg}`)),
|
|
7161
7476
|
runPrompt: async (prompt, msgs) => {
|
|
7162
7477
|
const spinner2 = ora4("Thinking...").start();
|
|
7163
7478
|
const response = await withRetry(
|
|
@@ -7174,13 +7489,13 @@ Analyze the above input.`;
|
|
|
7174
7489
|
onToolCall: (name, args) => {
|
|
7175
7490
|
if (spinner2.isSpinning) spinner2.stop();
|
|
7176
7491
|
const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
|
|
7177
|
-
console.log(
|
|
7492
|
+
console.log(chalk27.gray(`
|
|
7178
7493
|
\u2192 ${name}(${argSummary})`));
|
|
7179
7494
|
},
|
|
7180
7495
|
onToolResult: (_name, result, isError) => {
|
|
7181
7496
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
7182
|
-
const icon = isError ?
|
|
7183
|
-
console.log(
|
|
7497
|
+
const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
|
|
7498
|
+
console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
7184
7499
|
}
|
|
7185
7500
|
})
|
|
7186
7501
|
);
|
|
@@ -7194,8 +7509,8 @@ Analyze the above input.`;
|
|
|
7194
7509
|
rl.prompt();
|
|
7195
7510
|
return;
|
|
7196
7511
|
}
|
|
7197
|
-
console.log(
|
|
7198
|
-
console.log(
|
|
7512
|
+
console.log(chalk27.red(` Unknown command: ${input}`));
|
|
7513
|
+
console.log(chalk27.gray(" Type /help for available commands.\n"));
|
|
7199
7514
|
rl.prompt();
|
|
7200
7515
|
return;
|
|
7201
7516
|
}
|
|
@@ -7203,7 +7518,7 @@ Analyze the above input.`;
|
|
|
7203
7518
|
const refContext = formatReferences(references);
|
|
7204
7519
|
const finalPrompt = refContext + cleanInput;
|
|
7205
7520
|
if (references.length > 0) {
|
|
7206
|
-
console.log(
|
|
7521
|
+
console.log(chalk27.gray(` Injected ${references.length} reference(s)`));
|
|
7207
7522
|
}
|
|
7208
7523
|
messages.push({ role: "user", content: finalPrompt });
|
|
7209
7524
|
const spinner = ora4("Thinking...").start();
|
|
@@ -7229,16 +7544,16 @@ Analyze the above input.`;
|
|
|
7229
7544
|
const val = String(v);
|
|
7230
7545
|
return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
|
|
7231
7546
|
}).join(", ");
|
|
7232
|
-
console.log(
|
|
7547
|
+
console.log(chalk27.gray(`
|
|
7233
7548
|
\u2192 ${name}(${argSummary})`));
|
|
7234
7549
|
},
|
|
7235
7550
|
onToolResult: (_name, result, isError) => {
|
|
7236
7551
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
7237
|
-
const icon = isError ?
|
|
7238
|
-
console.log(
|
|
7552
|
+
const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
|
|
7553
|
+
console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
7239
7554
|
},
|
|
7240
7555
|
onCompress: () => {
|
|
7241
|
-
console.log(
|
|
7556
|
+
console.log(chalk27.yellow("\n [Context compressed to fit window]\n"));
|
|
7242
7557
|
}
|
|
7243
7558
|
})
|
|
7244
7559
|
);
|
|
@@ -7272,7 +7587,7 @@ Analyze the above input.`;
|
|
|
7272
7587
|
type: "auto",
|
|
7273
7588
|
content: memMatch[1]
|
|
7274
7589
|
});
|
|
7275
|
-
console.log(
|
|
7590
|
+
console.log(chalk27.gray(" (Saved to memory)\n"));
|
|
7276
7591
|
} catch {
|
|
7277
7592
|
}
|
|
7278
7593
|
}
|
|
@@ -7282,13 +7597,13 @@ Analyze the above input.`;
|
|
|
7282
7597
|
checkpoints.discard();
|
|
7283
7598
|
const msg = err.message?.toLowerCase() ?? "";
|
|
7284
7599
|
if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
|
|
7285
|
-
console.log(
|
|
7600
|
+
console.log(chalk27.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
|
|
7286
7601
|
} else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
|
|
7287
|
-
console.log(
|
|
7602
|
+
console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
|
|
7288
7603
|
} else if (msg.includes("429") || msg.includes("rate limit")) {
|
|
7289
|
-
console.log(
|
|
7604
|
+
console.log(chalk27.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
|
|
7290
7605
|
} else {
|
|
7291
|
-
console.log(
|
|
7606
|
+
console.log(chalk27.gray(" (The conversation history is preserved. Try again.)\n"));
|
|
7292
7607
|
}
|
|
7293
7608
|
}
|
|
7294
7609
|
rl.prompt();
|
|
@@ -7306,13 +7621,13 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
7306
7621
|
cwd: config.projectRoot,
|
|
7307
7622
|
requireConfirm: false,
|
|
7308
7623
|
confirm: async () => true,
|
|
7309
|
-
log: (msg) => console.log(
|
|
7624
|
+
log: (msg) => console.log(chalk27.gray(` ${msg}`))
|
|
7310
7625
|
};
|
|
7311
7626
|
const subcommand = args[0];
|
|
7312
7627
|
if (subcommand === "plan") {
|
|
7313
7628
|
const goal = args.slice(1).join(" ");
|
|
7314
7629
|
if (!goal) {
|
|
7315
|
-
console.error(
|
|
7630
|
+
console.error(chalk27.red(" Usage: notch ralph plan <goal>"));
|
|
7316
7631
|
process.exit(1);
|
|
7317
7632
|
}
|
|
7318
7633
|
const spinner = ora4("Ralph is planning...").start();
|
|
@@ -7323,7 +7638,7 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
7323
7638
|
} else if (subcommand === "run") {
|
|
7324
7639
|
let plan = await loadPlan(config.projectRoot);
|
|
7325
7640
|
if (!plan) {
|
|
7326
|
-
console.error(
|
|
7641
|
+
console.error(chalk27.red(" No plan found. Run: notch ralph plan <goal>"));
|
|
7327
7642
|
process.exit(1);
|
|
7328
7643
|
}
|
|
7329
7644
|
plan = await runRalphLoop(plan, {
|
|
@@ -7331,27 +7646,27 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
7331
7646
|
systemPrompt,
|
|
7332
7647
|
toolContext: toolCtx,
|
|
7333
7648
|
contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
|
|
7334
|
-
onTaskStart: (t) => console.log(
|
|
7335
|
-
onTaskComplete: (t) => console.log(
|
|
7336
|
-
onTaskFail: (t, e) => console.log(
|
|
7649
|
+
onTaskStart: (t) => console.log(chalk27.cyan(` \u25B6 ${t.description}`)),
|
|
7650
|
+
onTaskComplete: (t) => console.log(chalk27.green(` \u2713 ${t.description}`)),
|
|
7651
|
+
onTaskFail: (t, e) => console.log(chalk27.red(` \u2717 ${t.description}: ${e}`))
|
|
7337
7652
|
});
|
|
7338
7653
|
await savePlan(config.projectRoot, plan);
|
|
7339
7654
|
console.log(formatRalphStatus(plan));
|
|
7340
7655
|
} else if (subcommand === "status") {
|
|
7341
7656
|
const plan = await loadPlan(config.projectRoot);
|
|
7342
7657
|
if (!plan) {
|
|
7343
|
-
console.log(
|
|
7658
|
+
console.log(chalk27.gray(" No Ralph plan found."));
|
|
7344
7659
|
} else {
|
|
7345
7660
|
console.log(formatRalphStatus(plan));
|
|
7346
7661
|
}
|
|
7347
7662
|
} else {
|
|
7348
|
-
console.error(
|
|
7349
|
-
console.error(
|
|
7663
|
+
console.error(chalk27.red(` Unknown: notch ralph ${subcommand}`));
|
|
7664
|
+
console.error(chalk27.gray(" Usage: notch ralph <plan|run|status>"));
|
|
7350
7665
|
process.exit(1);
|
|
7351
7666
|
}
|
|
7352
7667
|
}
|
|
7353
7668
|
main().catch((err) => {
|
|
7354
|
-
console.error(
|
|
7669
|
+
console.error(chalk27.red(`
|
|
7355
7670
|
Fatal: ${err.message}
|
|
7356
7671
|
`));
|
|
7357
7672
|
process.exit(1);
|