@freesyntax/notch-cli 0.5.0 → 0.5.1
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 +573 -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,258 @@ 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 items = state.filtered.slice(0, MAX_VISIBLE);
|
|
6341
|
+
const hasMore = state.filtered.length > MAX_VISIBLE;
|
|
6342
|
+
let currentCategory = "";
|
|
6343
|
+
const lines = [];
|
|
6344
|
+
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"));
|
|
6345
|
+
for (let i = 0; i < items.length; i++) {
|
|
6346
|
+
const cmd = items[i];
|
|
6347
|
+
const isSelected = i === state.selected;
|
|
6348
|
+
if (cmd.category !== currentCategory) {
|
|
6349
|
+
currentCategory = cmd.category;
|
|
6350
|
+
if (i > 0) {
|
|
6351
|
+
lines.push(chalk26.gray(" \u2502 \u2502"));
|
|
6352
|
+
}
|
|
6353
|
+
lines.push(chalk26.gray(` \u2502 `) + chalk26.gray.dim(cmd.category.toUpperCase().padEnd(47)) + chalk26.gray("\u2502"));
|
|
6354
|
+
}
|
|
6355
|
+
const name = cmd.name.padEnd(22);
|
|
6356
|
+
const desc = cmd.description.slice(0, 25).padEnd(25);
|
|
6357
|
+
if (isSelected) {
|
|
6358
|
+
lines.push(
|
|
6359
|
+
chalk26.gray(" \u2502 ") + chalk26.bgWhite.black(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
|
|
6360
|
+
);
|
|
6361
|
+
} else {
|
|
6362
|
+
lines.push(
|
|
6363
|
+
chalk26.gray(" \u2502 ") + chalk26.cyan(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
|
|
6364
|
+
);
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
if (hasMore) {
|
|
6368
|
+
const remaining = state.filtered.length - MAX_VISIBLE;
|
|
6369
|
+
lines.push(chalk26.gray(` \u2502 ${chalk26.dim(`+${remaining} more\u2026`)}${"".padEnd(41 - String(remaining).length)}\u2502`));
|
|
6370
|
+
}
|
|
6371
|
+
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"));
|
|
6372
|
+
state.renderedLines = lines.length;
|
|
6373
|
+
return lines.join("\n");
|
|
6374
|
+
}
|
|
6375
|
+
function clearMenu(state) {
|
|
6376
|
+
if (state.renderedLines === 0) return "";
|
|
6377
|
+
const lines = state.renderedLines;
|
|
6378
|
+
state.renderedLines = 0;
|
|
6379
|
+
let ansi = "";
|
|
6380
|
+
for (let i = 0; i < lines; i++) {
|
|
6381
|
+
ansi += "\x1B[1A\x1B[2K";
|
|
6382
|
+
}
|
|
6383
|
+
return ansi;
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
// src/ui/interactive-input.ts
|
|
6387
|
+
function attachSlashMenu(rl) {
|
|
6388
|
+
const state = createMenuState();
|
|
6389
|
+
let lastInput = "";
|
|
6390
|
+
const keypressHandler = (_ch, key) => {
|
|
6391
|
+
const currentLine = rl.line ?? "";
|
|
6392
|
+
if (state.visible && key) {
|
|
6393
|
+
if (key.name === "up") {
|
|
6394
|
+
process.stdout.write(clearMenu(state));
|
|
6395
|
+
menuUp(state);
|
|
6396
|
+
process.stdout.write(renderMenu(state) + "\n");
|
|
6397
|
+
rewritePromptLine(rl);
|
|
6398
|
+
return;
|
|
6399
|
+
}
|
|
6400
|
+
if (key.name === "down") {
|
|
6401
|
+
process.stdout.write(clearMenu(state));
|
|
6402
|
+
menuDown(state);
|
|
6403
|
+
process.stdout.write(renderMenu(state) + "\n");
|
|
6404
|
+
rewritePromptLine(rl);
|
|
6405
|
+
return;
|
|
6406
|
+
}
|
|
6407
|
+
if (key.name === "escape") {
|
|
6408
|
+
process.stdout.write(clearMenu(state));
|
|
6409
|
+
menuDismiss(state);
|
|
6410
|
+
return;
|
|
6411
|
+
}
|
|
6412
|
+
if (key.name === "return") {
|
|
6413
|
+
const selected = menuSelect(state);
|
|
6414
|
+
if (selected) {
|
|
6415
|
+
process.stdout.write(clearMenu(state));
|
|
6416
|
+
menuDismiss(state);
|
|
6417
|
+
rl.line = "";
|
|
6418
|
+
rl.cursor = 0;
|
|
6419
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
6420
|
+
rl.write(selected + " ");
|
|
6421
|
+
return;
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
if (key.name === "tab") {
|
|
6425
|
+
const selected = menuSelect(state);
|
|
6426
|
+
if (selected) {
|
|
6427
|
+
process.stdout.write(clearMenu(state));
|
|
6428
|
+
menuDismiss(state);
|
|
6429
|
+
rl.line = "";
|
|
6430
|
+
rl.cursor = 0;
|
|
6431
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
6432
|
+
rl.write(selected + " ");
|
|
6433
|
+
return;
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
setImmediate(() => {
|
|
6438
|
+
const line = rl.line ?? "";
|
|
6439
|
+
if (line !== lastInput) {
|
|
6440
|
+
lastInput = line;
|
|
6441
|
+
const wasVisible = state.visible;
|
|
6442
|
+
const shouldShow = updateMenu(state, line);
|
|
6443
|
+
if (wasVisible && !shouldShow) {
|
|
6444
|
+
process.stdout.write(clearMenu(state));
|
|
6445
|
+
} else if (shouldShow) {
|
|
6446
|
+
if (wasVisible) {
|
|
6447
|
+
process.stdout.write(clearMenu(state));
|
|
6448
|
+
}
|
|
6449
|
+
const menuStr = renderMenu(state);
|
|
6450
|
+
if (menuStr) {
|
|
6451
|
+
process.stdout.write("\x1B[s");
|
|
6452
|
+
process.stdout.write(menuStr + "\n");
|
|
6453
|
+
rewritePromptLine(rl);
|
|
6454
|
+
process.stdout.write("\x1B[u");
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
});
|
|
6459
|
+
};
|
|
6460
|
+
process.stdin.on("keypress", keypressHandler);
|
|
6461
|
+
const closeHandler = () => {
|
|
6462
|
+
if (state.visible) {
|
|
6463
|
+
process.stdout.write(clearMenu(state));
|
|
6464
|
+
}
|
|
6465
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
6466
|
+
};
|
|
6467
|
+
rl.on("close", closeHandler);
|
|
6468
|
+
const lineHandler = () => {
|
|
6469
|
+
if (state.visible) {
|
|
6470
|
+
process.stdout.write(clearMenu(state));
|
|
6471
|
+
menuDismiss(state);
|
|
6472
|
+
}
|
|
6473
|
+
lastInput = "";
|
|
6474
|
+
};
|
|
6475
|
+
rl.on("line", lineHandler);
|
|
6476
|
+
return {
|
|
6477
|
+
cleanup: () => {
|
|
6478
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
6479
|
+
rl.removeListener("close", closeHandler);
|
|
6480
|
+
rl.removeListener("line", lineHandler);
|
|
6481
|
+
}
|
|
6482
|
+
};
|
|
6483
|
+
}
|
|
6484
|
+
function rewritePromptLine(rl) {
|
|
6485
|
+
const prompt = rl._prompt ?? "notch> ";
|
|
6486
|
+
const line = rl.line ?? "";
|
|
6487
|
+
const cursor = rl.cursor ?? line.length;
|
|
6488
|
+
process.stdout.write("\x1B[2K\r");
|
|
6489
|
+
process.stdout.write(prompt + line);
|
|
6490
|
+
const cursorOffset = line.length - cursor;
|
|
6491
|
+
if (cursorOffset > 0) {
|
|
6492
|
+
process.stdout.write(`\x1B[${cursorOffset}D`);
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6198
6496
|
// src/index.ts
|
|
6199
6497
|
import fs23 from "fs/promises";
|
|
6200
6498
|
import { createRequire } from "module";
|
|
@@ -6219,7 +6517,7 @@ function printModelTable(activeModel) {
|
|
|
6219
6517
|
`));
|
|
6220
6518
|
}
|
|
6221
6519
|
function printHelp() {
|
|
6222
|
-
console.log(
|
|
6520
|
+
console.log(chalk27.gray(`
|
|
6223
6521
|
Commands:
|
|
6224
6522
|
/model \u2014 Show available models
|
|
6225
6523
|
/model <name> \u2014 Switch model (e.g., /model pyre)
|
|
@@ -6316,13 +6614,13 @@ async function main() {
|
|
|
6316
6614
|
try {
|
|
6317
6615
|
spinner.stop();
|
|
6318
6616
|
const creds = await login();
|
|
6319
|
-
console.log(
|
|
6617
|
+
console.log(chalk27.green(`
|
|
6320
6618
|
\u2713 Signed in as ${creds.email}`));
|
|
6321
|
-
console.log(
|
|
6619
|
+
console.log(chalk27.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
|
|
6322
6620
|
`));
|
|
6323
6621
|
} catch (err) {
|
|
6324
6622
|
spinner.stop();
|
|
6325
|
-
console.error(
|
|
6623
|
+
console.error(chalk27.red(`
|
|
6326
6624
|
Login failed: ${err.message}
|
|
6327
6625
|
`));
|
|
6328
6626
|
process.exit(1);
|
|
@@ -6332,10 +6630,10 @@ async function main() {
|
|
|
6332
6630
|
if (promptArgs[0] === "logout") {
|
|
6333
6631
|
const creds = await loadCredentials();
|
|
6334
6632
|
if (!creds) {
|
|
6335
|
-
console.log(
|
|
6633
|
+
console.log(chalk27.gray("\n Not signed in.\n"));
|
|
6336
6634
|
} else {
|
|
6337
6635
|
await clearCredentials();
|
|
6338
|
-
console.log(
|
|
6636
|
+
console.log(chalk27.green(`
|
|
6339
6637
|
\u2713 Signed out (${creds.email})
|
|
6340
6638
|
`));
|
|
6341
6639
|
}
|
|
@@ -6344,13 +6642,13 @@ async function main() {
|
|
|
6344
6642
|
if (promptArgs[0] === "whoami") {
|
|
6345
6643
|
const creds = await loadCredentials();
|
|
6346
6644
|
if (!creds) {
|
|
6347
|
-
console.log(
|
|
6645
|
+
console.log(chalk27.gray("\n Not signed in. Run: notch login\n"));
|
|
6348
6646
|
} else {
|
|
6349
6647
|
const keyPreview = `${creds.token.slice(0, 12)}...`;
|
|
6350
|
-
console.log(
|
|
6351
|
-
Signed in as ${
|
|
6352
|
-
console.log(
|
|
6353
|
-
console.log(
|
|
6648
|
+
console.log(chalk27.gray(`
|
|
6649
|
+
Signed in as ${chalk27.white(creds.email)}`));
|
|
6650
|
+
console.log(chalk27.gray(` Key: ${keyPreview}`));
|
|
6651
|
+
console.log(chalk27.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
|
|
6354
6652
|
`));
|
|
6355
6653
|
}
|
|
6356
6654
|
return;
|
|
@@ -6374,8 +6672,8 @@ async function main() {
|
|
|
6374
6672
|
const config = await loadConfig(configOverrides);
|
|
6375
6673
|
if (opts.model) {
|
|
6376
6674
|
if (!isValidModel(opts.model)) {
|
|
6377
|
-
console.error(
|
|
6378
|
-
console.error(
|
|
6675
|
+
console.error(chalk27.red(` Unknown model: ${opts.model}`));
|
|
6676
|
+
console.error(chalk27.gray(` Available: ${modelChoices}`));
|
|
6379
6677
|
process.exit(1);
|
|
6380
6678
|
}
|
|
6381
6679
|
config.models.chat.model = opts.model;
|
|
@@ -6414,11 +6712,11 @@ async function main() {
|
|
|
6414
6712
|
const updateMsg = await checkForUpdates(VERSION);
|
|
6415
6713
|
if (updateMsg) console.log(updateMsg);
|
|
6416
6714
|
const hookTrustPrompt = async (commands) => {
|
|
6417
|
-
console.warn(
|
|
6418
|
-
commands.forEach((cmd) => console.warn(
|
|
6715
|
+
console.warn(chalk27.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
|
|
6716
|
+
commands.forEach((cmd) => console.warn(chalk27.gray(` \u2022 ${cmd}`)));
|
|
6419
6717
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6420
6718
|
return new Promise((resolve2) => {
|
|
6421
|
-
rl2.question(
|
|
6719
|
+
rl2.question(chalk27.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
|
|
6422
6720
|
rl2.close();
|
|
6423
6721
|
resolve2(answer.trim().toLowerCase() === "y");
|
|
6424
6722
|
});
|
|
@@ -6464,22 +6762,22 @@ ${repoMapStr}` : ""
|
|
|
6464
6762
|
const client = new MCPClient(mcpConfig, name);
|
|
6465
6763
|
await client.connect();
|
|
6466
6764
|
mcpClients.push(client);
|
|
6467
|
-
console.log(
|
|
6765
|
+
console.log(chalk27.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
|
|
6468
6766
|
} catch (err) {
|
|
6469
|
-
console.log(
|
|
6767
|
+
console.log(chalk27.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
|
|
6470
6768
|
}
|
|
6471
6769
|
}
|
|
6472
6770
|
} catch {
|
|
6473
6771
|
}
|
|
6474
6772
|
try {
|
|
6475
6773
|
const pluginCount = await pluginManager.init(config.projectRoot, (msg) => {
|
|
6476
|
-
console.log(
|
|
6774
|
+
console.log(chalk27.gray(` ${msg}`));
|
|
6477
6775
|
});
|
|
6478
6776
|
if (pluginCount > 0) {
|
|
6479
|
-
console.log(
|
|
6777
|
+
console.log(chalk27.green(` Plugins: ${pluginCount} loaded`));
|
|
6480
6778
|
}
|
|
6481
6779
|
} catch (err) {
|
|
6482
|
-
console.log(
|
|
6780
|
+
console.log(chalk27.yellow(` Plugin init failed: ${err.message}`));
|
|
6483
6781
|
}
|
|
6484
6782
|
const toolCtx = {
|
|
6485
6783
|
cwd: config.projectRoot,
|
|
@@ -6493,7 +6791,7 @@ ${repoMapStr}` : ""
|
|
|
6493
6791
|
});
|
|
6494
6792
|
});
|
|
6495
6793
|
},
|
|
6496
|
-
log: (msg) => console.log(
|
|
6794
|
+
log: (msg) => console.log(chalk27.gray(` ${msg}`)),
|
|
6497
6795
|
checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
|
|
6498
6796
|
runHook: async (event, ctx) => {
|
|
6499
6797
|
if (!config.enableHooks || hookConfig.hooks.length === 0) return;
|
|
@@ -6503,7 +6801,7 @@ ${repoMapStr}` : ""
|
|
|
6503
6801
|
});
|
|
6504
6802
|
for (const r of results) {
|
|
6505
6803
|
if (!r.ok) {
|
|
6506
|
-
console.log(
|
|
6804
|
+
console.log(chalk27.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
|
|
6507
6805
|
}
|
|
6508
6806
|
}
|
|
6509
6807
|
}
|
|
@@ -6512,7 +6810,7 @@ ${repoMapStr}` : ""
|
|
|
6512
6810
|
const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
|
|
6513
6811
|
for (const r of results) {
|
|
6514
6812
|
if (!r.ok) {
|
|
6515
|
-
console.log(
|
|
6813
|
+
console.log(chalk27.yellow(` Hook failed (${event}): ${r.error}`));
|
|
6516
6814
|
}
|
|
6517
6815
|
}
|
|
6518
6816
|
});
|
|
@@ -6523,10 +6821,10 @@ ${repoMapStr}` : ""
|
|
|
6523
6821
|
if (session) {
|
|
6524
6822
|
messages.push(...session.messages);
|
|
6525
6823
|
sessionId = session.meta.id;
|
|
6526
|
-
console.log(
|
|
6824
|
+
console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
6527
6825
|
`));
|
|
6528
6826
|
} else {
|
|
6529
|
-
console.log(
|
|
6827
|
+
console.log(chalk27.gray(" No session to resume.\n"));
|
|
6530
6828
|
}
|
|
6531
6829
|
}
|
|
6532
6830
|
const pipedInput = await readStdin();
|
|
@@ -6547,10 +6845,10 @@ Analyze the above input.`;
|
|
|
6547
6845
|
const refContext = formatReferences(references);
|
|
6548
6846
|
const finalPrompt = refContext + cleanInput;
|
|
6549
6847
|
messages.push({ role: "user", content: finalPrompt });
|
|
6550
|
-
console.log(
|
|
6848
|
+
console.log(chalk27.cyan(`> ${oneShot || "(piped input)"}
|
|
6551
6849
|
`));
|
|
6552
6850
|
if (references.length > 0) {
|
|
6553
|
-
console.log(
|
|
6851
|
+
console.log(chalk27.gray(` Injected ${references.length} reference(s)
|
|
6554
6852
|
`));
|
|
6555
6853
|
}
|
|
6556
6854
|
const spinner = ora4("Thinking...").start();
|
|
@@ -6569,13 +6867,13 @@ Analyze the above input.`;
|
|
|
6569
6867
|
onToolCall: (name, args) => {
|
|
6570
6868
|
if (spinner.isSpinning) spinner.stop();
|
|
6571
6869
|
const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
|
|
6572
|
-
console.log(
|
|
6870
|
+
console.log(chalk27.gray(`
|
|
6573
6871
|
\u2192 ${name}(${argSummary})`));
|
|
6574
6872
|
},
|
|
6575
6873
|
onToolResult: (_name, result, isError) => {
|
|
6576
6874
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
6577
|
-
const icon = isError ?
|
|
6578
|
-
console.log(
|
|
6875
|
+
const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
|
|
6876
|
+
console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
6579
6877
|
}
|
|
6580
6878
|
})
|
|
6581
6879
|
);
|
|
@@ -6600,19 +6898,21 @@ Analyze the above input.`;
|
|
|
6600
6898
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
6601
6899
|
if (savedPlan) {
|
|
6602
6900
|
ralphPlan = savedPlan;
|
|
6603
|
-
console.log(
|
|
6901
|
+
console.log(chalk27.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
|
|
6604
6902
|
`));
|
|
6605
6903
|
}
|
|
6606
6904
|
} catch {
|
|
6607
6905
|
}
|
|
6608
6906
|
const completer = buildCompleter(config.projectRoot);
|
|
6907
|
+
readline.emitKeypressEvents(process.stdin);
|
|
6609
6908
|
const rl = readline.createInterface({
|
|
6610
6909
|
input: process.stdin,
|
|
6611
6910
|
output: process.stdout,
|
|
6612
6911
|
prompt: theme().prompt("notch> "),
|
|
6613
6912
|
completer: (line) => completer(line)
|
|
6614
6913
|
});
|
|
6615
|
-
|
|
6914
|
+
const slashMenu = attachSlashMenu(rl);
|
|
6915
|
+
console.log(chalk27.gray(" Type your request, or /help for commands.\n"));
|
|
6616
6916
|
rl.prompt();
|
|
6617
6917
|
rl.on("line", async (line) => {
|
|
6618
6918
|
const input = line.trim();
|
|
@@ -6629,7 +6929,7 @@ Analyze the above input.`;
|
|
|
6629
6929
|
try {
|
|
6630
6930
|
const name = getSessionName();
|
|
6631
6931
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
6632
|
-
console.log(
|
|
6932
|
+
console.log(chalk27.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
|
|
6633
6933
|
} catch {
|
|
6634
6934
|
}
|
|
6635
6935
|
}
|
|
@@ -6639,16 +6939,17 @@ Analyze the above input.`;
|
|
|
6639
6939
|
} catch {
|
|
6640
6940
|
}
|
|
6641
6941
|
}
|
|
6942
|
+
slashMenu.cleanup();
|
|
6642
6943
|
stopActiveLoop();
|
|
6643
6944
|
stopFileWatcher();
|
|
6644
6945
|
await pluginManager.shutdown();
|
|
6645
6946
|
await toolCtx.runHook?.("session-end", {});
|
|
6646
|
-
console.log(
|
|
6947
|
+
console.log(chalk27.gray("\n Goodbye!\n"));
|
|
6647
6948
|
process.exit(0);
|
|
6648
6949
|
}
|
|
6649
6950
|
if (input === "/clear") {
|
|
6650
6951
|
messages.length = 0;
|
|
6651
|
-
console.log(
|
|
6952
|
+
console.log(chalk27.gray(" Conversation cleared.\n"));
|
|
6652
6953
|
rl.prompt();
|
|
6653
6954
|
return;
|
|
6654
6955
|
}
|
|
@@ -6668,8 +6969,8 @@ Analyze the above input.`;
|
|
|
6668
6969
|
newModel = `notch-${newModel}`;
|
|
6669
6970
|
}
|
|
6670
6971
|
if (!isValidModel(newModel)) {
|
|
6671
|
-
console.log(
|
|
6672
|
-
console.log(
|
|
6972
|
+
console.log(chalk27.red(` Unknown model: ${newModel}`));
|
|
6973
|
+
console.log(chalk27.gray(` Available: ${modelChoices}`));
|
|
6673
6974
|
rl.prompt();
|
|
6674
6975
|
return;
|
|
6675
6976
|
}
|
|
@@ -6677,7 +6978,7 @@ Analyze the above input.`;
|
|
|
6677
6978
|
config.models.chat.model = activeModelId;
|
|
6678
6979
|
model = resolveModel(config.models.chat);
|
|
6679
6980
|
const switchedInfo = MODEL_CATALOG[activeModelId];
|
|
6680
|
-
console.log(
|
|
6981
|
+
console.log(chalk27.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
|
|
6681
6982
|
`));
|
|
6682
6983
|
rl.prompt();
|
|
6683
6984
|
return;
|
|
@@ -6690,22 +6991,22 @@ Analyze the above input.`;
|
|
|
6690
6991
|
statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
|
|
6691
6992
|
} else {
|
|
6692
6993
|
statusSpinner.fail(check.error ?? "API unreachable");
|
|
6693
|
-
console.log(
|
|
6994
|
+
console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
|
|
6694
6995
|
}
|
|
6695
6996
|
rl.prompt();
|
|
6696
6997
|
return;
|
|
6697
6998
|
}
|
|
6698
6999
|
if (input === "/undo") {
|
|
6699
7000
|
if (checkpoints.undoCount === 0) {
|
|
6700
|
-
console.log(
|
|
7001
|
+
console.log(chalk27.gray(" Nothing to undo.\n"));
|
|
6701
7002
|
rl.prompt();
|
|
6702
7003
|
return;
|
|
6703
7004
|
}
|
|
6704
7005
|
const undone = await checkpoints.undo();
|
|
6705
7006
|
if (undone) {
|
|
6706
7007
|
const fileCount = undone.files.length;
|
|
6707
|
-
console.log(
|
|
6708
|
-
console.log(
|
|
7008
|
+
console.log(chalk27.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
|
|
7009
|
+
console.log(chalk27.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
|
|
6709
7010
|
`));
|
|
6710
7011
|
}
|
|
6711
7012
|
rl.prompt();
|
|
@@ -6713,7 +7014,7 @@ Analyze the above input.`;
|
|
|
6713
7014
|
}
|
|
6714
7015
|
if (input === "/usage") {
|
|
6715
7016
|
if (usage.turnCount === 0) {
|
|
6716
|
-
console.log(
|
|
7017
|
+
console.log(chalk27.gray(" No usage yet.\n"));
|
|
6717
7018
|
} else {
|
|
6718
7019
|
console.log(usage.formatSession());
|
|
6719
7020
|
const currentTokens = estimateTokens(messages);
|
|
@@ -6726,7 +7027,7 @@ Analyze the above input.`;
|
|
|
6726
7027
|
}
|
|
6727
7028
|
if (input === "/cost") {
|
|
6728
7029
|
if (costTracker.totalCost === 0) {
|
|
6729
|
-
console.log(
|
|
7030
|
+
console.log(chalk27.gray(" No cost data yet.\n"));
|
|
6730
7031
|
} else {
|
|
6731
7032
|
console.log(costTracker.formatTotal());
|
|
6732
7033
|
console.log(costTracker.formatByModel());
|
|
@@ -6741,7 +7042,7 @@ Analyze the above input.`;
|
|
|
6741
7042
|
const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
|
|
6742
7043
|
messages.length = 0;
|
|
6743
7044
|
messages.push(...compressed);
|
|
6744
|
-
console.log(
|
|
7045
|
+
console.log(chalk27.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
|
|
6745
7046
|
console.log("");
|
|
6746
7047
|
rl.prompt();
|
|
6747
7048
|
return;
|
|
@@ -6749,10 +7050,10 @@ Analyze the above input.`;
|
|
|
6749
7050
|
if (input === "/diff") {
|
|
6750
7051
|
const diffs = checkpoints.allDiffs();
|
|
6751
7052
|
if (diffs.length === 0) {
|
|
6752
|
-
console.log(
|
|
7053
|
+
console.log(chalk27.gray(" No file changes this session.\n"));
|
|
6753
7054
|
} else {
|
|
6754
7055
|
for (const df of diffs) {
|
|
6755
|
-
console.log(
|
|
7056
|
+
console.log(chalk27.cyan(` ${df.path}:`));
|
|
6756
7057
|
console.log(unifiedDiff(df.before, df.after, df.path));
|
|
6757
7058
|
console.log("");
|
|
6758
7059
|
}
|
|
@@ -6768,10 +7069,10 @@ Analyze the above input.`;
|
|
|
6768
7069
|
projectRoot: config.projectRoot,
|
|
6769
7070
|
outputPath: exportPath
|
|
6770
7071
|
});
|
|
6771
|
-
console.log(
|
|
7072
|
+
console.log(chalk27.green(` Exported to ${ePath}
|
|
6772
7073
|
`));
|
|
6773
7074
|
} catch (err) {
|
|
6774
|
-
console.log(
|
|
7075
|
+
console.log(chalk27.red(` Export failed: ${err.message}
|
|
6775
7076
|
`));
|
|
6776
7077
|
}
|
|
6777
7078
|
rl.prompt();
|
|
@@ -6781,10 +7082,10 @@ Analyze the above input.`;
|
|
|
6781
7082
|
try {
|
|
6782
7083
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
6783
7084
|
sessionId = id;
|
|
6784
|
-
console.log(
|
|
7085
|
+
console.log(chalk27.green(` Session saved: ${id}
|
|
6785
7086
|
`));
|
|
6786
7087
|
} catch (err) {
|
|
6787
|
-
console.log(
|
|
7088
|
+
console.log(chalk27.red(` Save failed: ${err.message}
|
|
6788
7089
|
`));
|
|
6789
7090
|
}
|
|
6790
7091
|
rl.prompt();
|
|
@@ -6794,16 +7095,16 @@ Analyze the above input.`;
|
|
|
6794
7095
|
try {
|
|
6795
7096
|
const sessions = await listSessions(config.projectRoot);
|
|
6796
7097
|
if (sessions.length === 0) {
|
|
6797
|
-
console.log(
|
|
7098
|
+
console.log(chalk27.gray(" No saved sessions.\n"));
|
|
6798
7099
|
} else {
|
|
6799
|
-
console.log(
|
|
7100
|
+
console.log(chalk27.gray("\n Saved sessions:\n"));
|
|
6800
7101
|
for (const s of sessions.slice(0, 10)) {
|
|
6801
|
-
console.log(
|
|
7102
|
+
console.log(chalk27.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
|
|
6802
7103
|
}
|
|
6803
7104
|
console.log("");
|
|
6804
7105
|
}
|
|
6805
7106
|
} catch (err) {
|
|
6806
|
-
console.log(
|
|
7107
|
+
console.log(chalk27.red(` Error listing sessions: ${err.message}
|
|
6807
7108
|
`));
|
|
6808
7109
|
}
|
|
6809
7110
|
rl.prompt();
|
|
@@ -6821,18 +7122,18 @@ Analyze the above input.`;
|
|
|
6821
7122
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
6822
7123
|
if (savedPlan) {
|
|
6823
7124
|
ralphPlan = savedPlan;
|
|
6824
|
-
console.log(
|
|
7125
|
+
console.log(chalk27.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
|
|
6825
7126
|
`));
|
|
6826
7127
|
}
|
|
6827
7128
|
} catch {
|
|
6828
7129
|
}
|
|
6829
|
-
console.log(
|
|
7130
|
+
console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
6830
7131
|
`));
|
|
6831
7132
|
} else {
|
|
6832
|
-
console.log(
|
|
7133
|
+
console.log(chalk27.gray(" No session found.\n"));
|
|
6833
7134
|
}
|
|
6834
7135
|
} catch (err) {
|
|
6835
|
-
console.log(
|
|
7136
|
+
console.log(chalk27.red(` Resume failed: ${err.message}
|
|
6836
7137
|
`));
|
|
6837
7138
|
}
|
|
6838
7139
|
rl.prompt();
|
|
@@ -6841,7 +7142,7 @@ Analyze the above input.`;
|
|
|
6841
7142
|
if (input.startsWith("/search ")) {
|
|
6842
7143
|
const query = input.replace("/search ", "").trim().toLowerCase();
|
|
6843
7144
|
if (!query) {
|
|
6844
|
-
console.log(
|
|
7145
|
+
console.log(chalk27.gray(" Usage: /search <query>\n"));
|
|
6845
7146
|
rl.prompt();
|
|
6846
7147
|
return;
|
|
6847
7148
|
}
|
|
@@ -6858,14 +7159,14 @@ Analyze the above input.`;
|
|
|
6858
7159
|
}
|
|
6859
7160
|
}
|
|
6860
7161
|
if (matches.length === 0) {
|
|
6861
|
-
console.log(
|
|
7162
|
+
console.log(chalk27.gray(` No matches for "${query}"
|
|
6862
7163
|
`));
|
|
6863
7164
|
} else {
|
|
6864
|
-
console.log(
|
|
7165
|
+
console.log(chalk27.gray(`
|
|
6865
7166
|
${matches.length} match(es) for "${query}":
|
|
6866
7167
|
`));
|
|
6867
7168
|
for (const m of matches.slice(0, 10)) {
|
|
6868
|
-
console.log(
|
|
7169
|
+
console.log(chalk27.gray(` [${m.index}] ${m.role}: ${m.preview}`));
|
|
6869
7170
|
}
|
|
6870
7171
|
console.log("");
|
|
6871
7172
|
}
|
|
@@ -6879,7 +7180,7 @@ Analyze the above input.`;
|
|
|
6879
7180
|
activePlan = await generatePlan(task, model, systemPrompt);
|
|
6880
7181
|
planSpinner.succeed("Plan generated");
|
|
6881
7182
|
console.log(formatPlan(activePlan));
|
|
6882
|
-
console.log(
|
|
7183
|
+
console.log(chalk27.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
|
|
6883
7184
|
} catch (err) {
|
|
6884
7185
|
planSpinner.fail(`Plan failed: ${err.message}`);
|
|
6885
7186
|
}
|
|
@@ -6888,11 +7189,11 @@ Analyze the above input.`;
|
|
|
6888
7189
|
}
|
|
6889
7190
|
if (input === "/plan approve") {
|
|
6890
7191
|
if (!activePlan) {
|
|
6891
|
-
console.log(
|
|
7192
|
+
console.log(chalk27.gray(" No active plan. Use /plan <task> to create one.\n"));
|
|
6892
7193
|
rl.prompt();
|
|
6893
7194
|
return;
|
|
6894
7195
|
}
|
|
6895
|
-
console.log(
|
|
7196
|
+
console.log(chalk27.green(" Executing plan...\n"));
|
|
6896
7197
|
while (!isPlanComplete(activePlan)) {
|
|
6897
7198
|
const stepPrompt = currentStepPrompt(activePlan);
|
|
6898
7199
|
messages.push({ role: "user", content: stepPrompt });
|
|
@@ -6910,10 +7211,10 @@ Analyze the above input.`;
|
|
|
6910
7211
|
},
|
|
6911
7212
|
onToolCall: (name) => {
|
|
6912
7213
|
if (planStepSpinner.isSpinning) planStepSpinner.stop();
|
|
6913
|
-
console.log(
|
|
7214
|
+
console.log(chalk27.gray(` \u2192 ${name}`));
|
|
6914
7215
|
},
|
|
6915
7216
|
onToolResult: (_name, _result, isError) => {
|
|
6916
|
-
console.log(isError ?
|
|
7217
|
+
console.log(isError ? chalk27.red(" \u2717") : chalk27.green(" \u2713"));
|
|
6917
7218
|
}
|
|
6918
7219
|
});
|
|
6919
7220
|
console.log("\n");
|
|
@@ -6926,7 +7227,7 @@ Analyze the above input.`;
|
|
|
6926
7227
|
}
|
|
6927
7228
|
}
|
|
6928
7229
|
if (activePlan && isPlanComplete(activePlan)) {
|
|
6929
|
-
console.log(
|
|
7230
|
+
console.log(chalk27.green(" Plan completed!\n"));
|
|
6930
7231
|
}
|
|
6931
7232
|
activePlan = null;
|
|
6932
7233
|
rl.prompt();
|
|
@@ -6934,26 +7235,26 @@ Analyze the above input.`;
|
|
|
6934
7235
|
}
|
|
6935
7236
|
if (input === "/plan edit") {
|
|
6936
7237
|
if (!activePlan) {
|
|
6937
|
-
console.log(
|
|
7238
|
+
console.log(chalk27.gray(" No active plan to edit.\n"));
|
|
6938
7239
|
rl.prompt();
|
|
6939
7240
|
return;
|
|
6940
7241
|
}
|
|
6941
7242
|
console.log(formatPlan(activePlan));
|
|
6942
|
-
console.log(
|
|
6943
|
-
console.log(
|
|
7243
|
+
console.log(chalk27.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
|
|
7244
|
+
console.log(chalk27.gray(" or /plan approve to proceed with the current plan.\n"));
|
|
6944
7245
|
rl.prompt();
|
|
6945
7246
|
return;
|
|
6946
7247
|
}
|
|
6947
7248
|
if (input === "/plan cancel") {
|
|
6948
7249
|
activePlan = null;
|
|
6949
|
-
console.log(
|
|
7250
|
+
console.log(chalk27.gray(" Plan discarded.\n"));
|
|
6950
7251
|
rl.prompt();
|
|
6951
7252
|
return;
|
|
6952
7253
|
}
|
|
6953
7254
|
if (input.startsWith("/agent ")) {
|
|
6954
7255
|
const task = input.replace("/agent ", "").trim();
|
|
6955
7256
|
const agentId = nextSubagentId();
|
|
6956
|
-
console.log(
|
|
7257
|
+
console.log(chalk27.cyan(` Spawning subagent #${agentId}: ${task}
|
|
6957
7258
|
`));
|
|
6958
7259
|
spawnSubagent({
|
|
6959
7260
|
id: agentId,
|
|
@@ -6963,14 +7264,14 @@ Analyze the above input.`;
|
|
|
6963
7264
|
toolContext: toolCtx,
|
|
6964
7265
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
6965
7266
|
onComplete: (result) => {
|
|
6966
|
-
console.log(
|
|
7267
|
+
console.log(chalk27.green(`
|
|
6967
7268
|
Subagent #${agentId} finished:`));
|
|
6968
|
-
console.log(
|
|
7269
|
+
console.log(chalk27.gray(` ${result.slice(0, 200)}
|
|
6969
7270
|
`));
|
|
6970
7271
|
rl.prompt();
|
|
6971
7272
|
},
|
|
6972
7273
|
onError: (err) => {
|
|
6973
|
-
console.log(
|
|
7274
|
+
console.log(chalk27.red(`
|
|
6974
7275
|
Subagent #${agentId} failed: ${err}
|
|
6975
7276
|
`));
|
|
6976
7277
|
rl.prompt();
|
|
@@ -6983,18 +7284,18 @@ Analyze the above input.`;
|
|
|
6983
7284
|
try {
|
|
6984
7285
|
const memories = await loadMemories(config.projectRoot);
|
|
6985
7286
|
if (memories.length === 0) {
|
|
6986
|
-
console.log(
|
|
7287
|
+
console.log(chalk27.gray(" No saved memories.\n"));
|
|
6987
7288
|
} else {
|
|
6988
|
-
console.log(
|
|
7289
|
+
console.log(chalk27.gray(`
|
|
6989
7290
|
${memories.length} saved memories:
|
|
6990
7291
|
`));
|
|
6991
7292
|
for (const m of memories) {
|
|
6992
|
-
console.log(
|
|
7293
|
+
console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
|
|
6993
7294
|
}
|
|
6994
7295
|
console.log("");
|
|
6995
7296
|
}
|
|
6996
7297
|
} catch (err) {
|
|
6997
|
-
console.log(
|
|
7298
|
+
console.log(chalk27.red(` Error: ${err.message}
|
|
6998
7299
|
`));
|
|
6999
7300
|
}
|
|
7000
7301
|
rl.prompt();
|
|
@@ -7005,16 +7306,16 @@ Analyze the above input.`;
|
|
|
7005
7306
|
try {
|
|
7006
7307
|
const results = await searchMemories(config.projectRoot, query);
|
|
7007
7308
|
if (results.length === 0) {
|
|
7008
|
-
console.log(
|
|
7309
|
+
console.log(chalk27.gray(` No memories matching "${query}"
|
|
7009
7310
|
`));
|
|
7010
7311
|
} else {
|
|
7011
7312
|
for (const m of results) {
|
|
7012
|
-
console.log(
|
|
7313
|
+
console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
|
|
7013
7314
|
}
|
|
7014
7315
|
console.log("");
|
|
7015
7316
|
}
|
|
7016
7317
|
} catch (err) {
|
|
7017
|
-
console.log(
|
|
7318
|
+
console.log(chalk27.red(` Error: ${err.message}
|
|
7018
7319
|
`));
|
|
7019
7320
|
}
|
|
7020
7321
|
rl.prompt();
|
|
@@ -7026,10 +7327,10 @@ Analyze the above input.`;
|
|
|
7026
7327
|
for (const m of memories) {
|
|
7027
7328
|
await deleteMemory(config.projectRoot, m.id);
|
|
7028
7329
|
}
|
|
7029
|
-
console.log(
|
|
7330
|
+
console.log(chalk27.yellow(` Cleared ${memories.length} memories.
|
|
7030
7331
|
`));
|
|
7031
7332
|
} catch (err) {
|
|
7032
|
-
console.log(
|
|
7333
|
+
console.log(chalk27.red(` Error: ${err.message}
|
|
7033
7334
|
`));
|
|
7034
7335
|
}
|
|
7035
7336
|
rl.prompt();
|
|
@@ -7057,27 +7358,27 @@ Analyze the above input.`;
|
|
|
7057
7358
|
}
|
|
7058
7359
|
if (input === "/ralph run") {
|
|
7059
7360
|
if (!ralphPlan) {
|
|
7060
|
-
console.log(
|
|
7361
|
+
console.log(chalk27.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
|
|
7061
7362
|
rl.prompt();
|
|
7062
7363
|
return;
|
|
7063
7364
|
}
|
|
7064
|
-
console.log(
|
|
7365
|
+
console.log(chalk27.green(" Ralph is running...\n"));
|
|
7065
7366
|
try {
|
|
7066
7367
|
ralphPlan = await runRalphLoop(ralphPlan, {
|
|
7067
7368
|
model,
|
|
7068
7369
|
systemPrompt,
|
|
7069
7370
|
toolContext: toolCtx,
|
|
7070
7371
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
7071
|
-
onTaskStart: (task) => console.log(
|
|
7072
|
-
onTaskComplete: (task) => console.log(
|
|
7372
|
+
onTaskStart: (task) => console.log(chalk27.cyan(` \u25B6 Task: ${task.description}`)),
|
|
7373
|
+
onTaskComplete: (task) => console.log(chalk27.green(` \u2713 Done: ${task.description}
|
|
7073
7374
|
`)),
|
|
7074
|
-
onTaskFail: (task, err) => console.log(
|
|
7375
|
+
onTaskFail: (task, err) => console.log(chalk27.red(` \u2717 Failed: ${task.description} (${err})
|
|
7075
7376
|
`))
|
|
7076
7377
|
});
|
|
7077
7378
|
await savePlan(config.projectRoot, ralphPlan);
|
|
7078
7379
|
console.log(formatRalphStatus(ralphPlan));
|
|
7079
7380
|
} catch (err) {
|
|
7080
|
-
console.log(
|
|
7381
|
+
console.log(chalk27.red(` Ralph error: ${err.message}
|
|
7081
7382
|
`));
|
|
7082
7383
|
}
|
|
7083
7384
|
rl.prompt();
|
|
@@ -7085,7 +7386,7 @@ Analyze the above input.`;
|
|
|
7085
7386
|
}
|
|
7086
7387
|
if (input === "/ralph status") {
|
|
7087
7388
|
if (!ralphPlan) {
|
|
7088
|
-
console.log(
|
|
7389
|
+
console.log(chalk27.gray(" No Ralph plan active.\n"));
|
|
7089
7390
|
} else {
|
|
7090
7391
|
console.log(formatRalphStatus(ralphPlan));
|
|
7091
7392
|
}
|
|
@@ -7096,26 +7397,26 @@ Analyze the above input.`;
|
|
|
7096
7397
|
ralphPlan = null;
|
|
7097
7398
|
await deletePlan(config.projectRoot).catch(() => {
|
|
7098
7399
|
});
|
|
7099
|
-
console.log(
|
|
7400
|
+
console.log(chalk27.gray(" Ralph plan cleared.\n"));
|
|
7100
7401
|
rl.prompt();
|
|
7101
7402
|
return;
|
|
7102
7403
|
}
|
|
7103
7404
|
if (input === "/branch") {
|
|
7104
7405
|
const branchId = `branch-${branches.size + 1}`;
|
|
7105
7406
|
branches.set(branchId, [...messages]);
|
|
7106
|
-
console.log(
|
|
7407
|
+
console.log(chalk27.green(` Forked conversation as "${branchId}" (${messages.length} messages)
|
|
7107
7408
|
`));
|
|
7108
7409
|
rl.prompt();
|
|
7109
7410
|
return;
|
|
7110
7411
|
}
|
|
7111
7412
|
if (input === "/branches") {
|
|
7112
7413
|
if (branches.size === 0) {
|
|
7113
|
-
console.log(
|
|
7414
|
+
console.log(chalk27.gray(" No conversation branches. Use /branch to fork.\n"));
|
|
7114
7415
|
} else {
|
|
7115
|
-
console.log(
|
|
7416
|
+
console.log(chalk27.gray("\n Branches:\n"));
|
|
7116
7417
|
for (const [name, msgs] of branches) {
|
|
7117
|
-
const marker = name === currentBranch ?
|
|
7118
|
-
console.log(
|
|
7418
|
+
const marker = name === currentBranch ? chalk27.green(" \u25CF") : " ";
|
|
7419
|
+
console.log(chalk27.gray(` ${marker} ${name} (${msgs.length} messages)`));
|
|
7119
7420
|
}
|
|
7120
7421
|
console.log("");
|
|
7121
7422
|
}
|
|
@@ -7157,7 +7458,7 @@ Analyze the above input.`;
|
|
|
7157
7458
|
modelId: activeModelId,
|
|
7158
7459
|
messages,
|
|
7159
7460
|
lastResponse: lastAssistantResponse,
|
|
7160
|
-
log: (msg) => console.log(
|
|
7461
|
+
log: (msg) => console.log(chalk27.gray(` ${msg}`)),
|
|
7161
7462
|
runPrompt: async (prompt, msgs) => {
|
|
7162
7463
|
const spinner2 = ora4("Thinking...").start();
|
|
7163
7464
|
const response = await withRetry(
|
|
@@ -7174,13 +7475,13 @@ Analyze the above input.`;
|
|
|
7174
7475
|
onToolCall: (name, args) => {
|
|
7175
7476
|
if (spinner2.isSpinning) spinner2.stop();
|
|
7176
7477
|
const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
|
|
7177
|
-
console.log(
|
|
7478
|
+
console.log(chalk27.gray(`
|
|
7178
7479
|
\u2192 ${name}(${argSummary})`));
|
|
7179
7480
|
},
|
|
7180
7481
|
onToolResult: (_name, result, isError) => {
|
|
7181
7482
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
7182
|
-
const icon = isError ?
|
|
7183
|
-
console.log(
|
|
7483
|
+
const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
|
|
7484
|
+
console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
7184
7485
|
}
|
|
7185
7486
|
})
|
|
7186
7487
|
);
|
|
@@ -7194,8 +7495,8 @@ Analyze the above input.`;
|
|
|
7194
7495
|
rl.prompt();
|
|
7195
7496
|
return;
|
|
7196
7497
|
}
|
|
7197
|
-
console.log(
|
|
7198
|
-
console.log(
|
|
7498
|
+
console.log(chalk27.red(` Unknown command: ${input}`));
|
|
7499
|
+
console.log(chalk27.gray(" Type /help for available commands.\n"));
|
|
7199
7500
|
rl.prompt();
|
|
7200
7501
|
return;
|
|
7201
7502
|
}
|
|
@@ -7203,7 +7504,7 @@ Analyze the above input.`;
|
|
|
7203
7504
|
const refContext = formatReferences(references);
|
|
7204
7505
|
const finalPrompt = refContext + cleanInput;
|
|
7205
7506
|
if (references.length > 0) {
|
|
7206
|
-
console.log(
|
|
7507
|
+
console.log(chalk27.gray(` Injected ${references.length} reference(s)`));
|
|
7207
7508
|
}
|
|
7208
7509
|
messages.push({ role: "user", content: finalPrompt });
|
|
7209
7510
|
const spinner = ora4("Thinking...").start();
|
|
@@ -7229,16 +7530,16 @@ Analyze the above input.`;
|
|
|
7229
7530
|
const val = String(v);
|
|
7230
7531
|
return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
|
|
7231
7532
|
}).join(", ");
|
|
7232
|
-
console.log(
|
|
7533
|
+
console.log(chalk27.gray(`
|
|
7233
7534
|
\u2192 ${name}(${argSummary})`));
|
|
7234
7535
|
},
|
|
7235
7536
|
onToolResult: (_name, result, isError) => {
|
|
7236
7537
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
7237
|
-
const icon = isError ?
|
|
7238
|
-
console.log(
|
|
7538
|
+
const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
|
|
7539
|
+
console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
7239
7540
|
},
|
|
7240
7541
|
onCompress: () => {
|
|
7241
|
-
console.log(
|
|
7542
|
+
console.log(chalk27.yellow("\n [Context compressed to fit window]\n"));
|
|
7242
7543
|
}
|
|
7243
7544
|
})
|
|
7244
7545
|
);
|
|
@@ -7272,7 +7573,7 @@ Analyze the above input.`;
|
|
|
7272
7573
|
type: "auto",
|
|
7273
7574
|
content: memMatch[1]
|
|
7274
7575
|
});
|
|
7275
|
-
console.log(
|
|
7576
|
+
console.log(chalk27.gray(" (Saved to memory)\n"));
|
|
7276
7577
|
} catch {
|
|
7277
7578
|
}
|
|
7278
7579
|
}
|
|
@@ -7282,13 +7583,13 @@ Analyze the above input.`;
|
|
|
7282
7583
|
checkpoints.discard();
|
|
7283
7584
|
const msg = err.message?.toLowerCase() ?? "";
|
|
7284
7585
|
if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
|
|
7285
|
-
console.log(
|
|
7586
|
+
console.log(chalk27.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
|
|
7286
7587
|
} else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
|
|
7287
|
-
console.log(
|
|
7588
|
+
console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
|
|
7288
7589
|
} else if (msg.includes("429") || msg.includes("rate limit")) {
|
|
7289
|
-
console.log(
|
|
7590
|
+
console.log(chalk27.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
|
|
7290
7591
|
} else {
|
|
7291
|
-
console.log(
|
|
7592
|
+
console.log(chalk27.gray(" (The conversation history is preserved. Try again.)\n"));
|
|
7292
7593
|
}
|
|
7293
7594
|
}
|
|
7294
7595
|
rl.prompt();
|
|
@@ -7306,13 +7607,13 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
7306
7607
|
cwd: config.projectRoot,
|
|
7307
7608
|
requireConfirm: false,
|
|
7308
7609
|
confirm: async () => true,
|
|
7309
|
-
log: (msg) => console.log(
|
|
7610
|
+
log: (msg) => console.log(chalk27.gray(` ${msg}`))
|
|
7310
7611
|
};
|
|
7311
7612
|
const subcommand = args[0];
|
|
7312
7613
|
if (subcommand === "plan") {
|
|
7313
7614
|
const goal = args.slice(1).join(" ");
|
|
7314
7615
|
if (!goal) {
|
|
7315
|
-
console.error(
|
|
7616
|
+
console.error(chalk27.red(" Usage: notch ralph plan <goal>"));
|
|
7316
7617
|
process.exit(1);
|
|
7317
7618
|
}
|
|
7318
7619
|
const spinner = ora4("Ralph is planning...").start();
|
|
@@ -7323,7 +7624,7 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
7323
7624
|
} else if (subcommand === "run") {
|
|
7324
7625
|
let plan = await loadPlan(config.projectRoot);
|
|
7325
7626
|
if (!plan) {
|
|
7326
|
-
console.error(
|
|
7627
|
+
console.error(chalk27.red(" No plan found. Run: notch ralph plan <goal>"));
|
|
7327
7628
|
process.exit(1);
|
|
7328
7629
|
}
|
|
7329
7630
|
plan = await runRalphLoop(plan, {
|
|
@@ -7331,27 +7632,27 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
7331
7632
|
systemPrompt,
|
|
7332
7633
|
toolContext: toolCtx,
|
|
7333
7634
|
contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
|
|
7334
|
-
onTaskStart: (t) => console.log(
|
|
7335
|
-
onTaskComplete: (t) => console.log(
|
|
7336
|
-
onTaskFail: (t, e) => console.log(
|
|
7635
|
+
onTaskStart: (t) => console.log(chalk27.cyan(` \u25B6 ${t.description}`)),
|
|
7636
|
+
onTaskComplete: (t) => console.log(chalk27.green(` \u2713 ${t.description}`)),
|
|
7637
|
+
onTaskFail: (t, e) => console.log(chalk27.red(` \u2717 ${t.description}: ${e}`))
|
|
7337
7638
|
});
|
|
7338
7639
|
await savePlan(config.projectRoot, plan);
|
|
7339
7640
|
console.log(formatRalphStatus(plan));
|
|
7340
7641
|
} else if (subcommand === "status") {
|
|
7341
7642
|
const plan = await loadPlan(config.projectRoot);
|
|
7342
7643
|
if (!plan) {
|
|
7343
|
-
console.log(
|
|
7644
|
+
console.log(chalk27.gray(" No Ralph plan found."));
|
|
7344
7645
|
} else {
|
|
7345
7646
|
console.log(formatRalphStatus(plan));
|
|
7346
7647
|
}
|
|
7347
7648
|
} else {
|
|
7348
|
-
console.error(
|
|
7349
|
-
console.error(
|
|
7649
|
+
console.error(chalk27.red(` Unknown: notch ralph ${subcommand}`));
|
|
7650
|
+
console.error(chalk27.gray(" Usage: notch ralph <plan|run|status>"));
|
|
7350
7651
|
process.exit(1);
|
|
7351
7652
|
}
|
|
7352
7653
|
}
|
|
7353
7654
|
main().catch((err) => {
|
|
7354
|
-
console.error(
|
|
7655
|
+
console.error(chalk27.red(`
|
|
7355
7656
|
Fatal: ${err.message}
|
|
7356
7657
|
`));
|
|
7357
7658
|
process.exit(1);
|