@freesyntax/notch-cli 0.4.2 → 0.4.5
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
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
clearCredentials,
|
|
4
4
|
loadCredentials,
|
|
5
5
|
login
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FIFC4V2R.js";
|
|
7
7
|
import {
|
|
8
8
|
autoCompress,
|
|
9
9
|
estimateTokens
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { Command } from "commander";
|
|
14
|
-
import
|
|
14
|
+
import chalk9 from "chalk";
|
|
15
15
|
import ora from "ora";
|
|
16
16
|
import * as readline from "readline";
|
|
17
17
|
import * as nodePath from "path";
|
|
@@ -830,20 +830,373 @@ ${text}` };
|
|
|
830
830
|
}
|
|
831
831
|
};
|
|
832
832
|
|
|
833
|
+
// src/tools/github.ts
|
|
834
|
+
import { z as z9 } from "zod";
|
|
835
|
+
var GITHUB_API = "https://api.github.com";
|
|
836
|
+
function getToken() {
|
|
837
|
+
return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
|
|
838
|
+
}
|
|
839
|
+
async function ghFetch(path19, opts2 = {}) {
|
|
840
|
+
const token = getToken();
|
|
841
|
+
const headers = {
|
|
842
|
+
"Accept": "application/vnd.github+json",
|
|
843
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
844
|
+
...opts2.headers ?? {}
|
|
845
|
+
};
|
|
846
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
847
|
+
return fetch(`${GITHUB_API}${path19}`, { ...opts2, headers });
|
|
848
|
+
}
|
|
849
|
+
var parameters9 = z9.object({
|
|
850
|
+
action: z9.enum([
|
|
851
|
+
"create_pr",
|
|
852
|
+
"list_prs",
|
|
853
|
+
"list_issues",
|
|
854
|
+
"repo_info",
|
|
855
|
+
"create_issue",
|
|
856
|
+
"pr_status"
|
|
857
|
+
]).describe("The GitHub operation to perform"),
|
|
858
|
+
owner: z9.string().describe("Repository owner (user or org)"),
|
|
859
|
+
repo: z9.string().describe("Repository name"),
|
|
860
|
+
// PR fields
|
|
861
|
+
title: z9.string().optional().describe("PR or issue title"),
|
|
862
|
+
body: z9.string().optional().describe("PR or issue body/description"),
|
|
863
|
+
head: z9.string().optional().describe("PR source branch"),
|
|
864
|
+
base: z9.string().optional().describe("PR target branch (default: main)"),
|
|
865
|
+
// Issue fields
|
|
866
|
+
labels: z9.array(z9.string()).optional().describe("Labels for issue"),
|
|
867
|
+
// Filter
|
|
868
|
+
state: z9.enum(["open", "closed", "all"]).optional().describe("Filter by state"),
|
|
869
|
+
pr_number: z9.number().optional().describe("PR number for status check")
|
|
870
|
+
});
|
|
871
|
+
async function execute(args, _ctx) {
|
|
872
|
+
const token = getToken();
|
|
873
|
+
if (!token) {
|
|
874
|
+
return {
|
|
875
|
+
content: "No GitHub token found. Set GITHUB_TOKEN or GH_TOKEN environment variable.\nYou can create a token at https://github.com/settings/tokens",
|
|
876
|
+
isError: true
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
switch (args.action) {
|
|
881
|
+
case "repo_info": {
|
|
882
|
+
const res = await ghFetch(`/repos/${args.owner}/${args.repo}`);
|
|
883
|
+
if (!res.ok) return { content: `GitHub API error: ${res.status} ${await res.text()}`, isError: true };
|
|
884
|
+
const repo = await res.json();
|
|
885
|
+
return {
|
|
886
|
+
content: [
|
|
887
|
+
`Repository: ${repo.full_name}`,
|
|
888
|
+
`Description: ${repo.description || "None"}`,
|
|
889
|
+
`Stars: ${repo.stargazers_count} | Forks: ${repo.forks_count} | Open issues: ${repo.open_issues_count}`,
|
|
890
|
+
`Default branch: ${repo.default_branch}`,
|
|
891
|
+
`Language: ${repo.language || "Unknown"}`,
|
|
892
|
+
`Private: ${repo.private}`,
|
|
893
|
+
`URL: ${repo.html_url}`
|
|
894
|
+
].join("\n")
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
case "list_prs": {
|
|
898
|
+
const state = args.state ?? "open";
|
|
899
|
+
const res = await ghFetch(`/repos/${args.owner}/${args.repo}/pulls?state=${state}&per_page=15`);
|
|
900
|
+
if (!res.ok) return { content: `GitHub API error: ${res.status}`, isError: true };
|
|
901
|
+
const prs = await res.json();
|
|
902
|
+
if (prs.length === 0) return { content: `No ${state} pull requests.` };
|
|
903
|
+
const lines = prs.map(
|
|
904
|
+
(pr) => `#${pr.number} [${pr.state}] ${pr.title} (${pr.head.ref} \u2192 ${pr.base.ref}) by @${pr.user.login}`
|
|
905
|
+
);
|
|
906
|
+
return { content: lines.join("\n") };
|
|
907
|
+
}
|
|
908
|
+
case "list_issues": {
|
|
909
|
+
const state = args.state ?? "open";
|
|
910
|
+
const res = await ghFetch(`/repos/${args.owner}/${args.repo}/issues?state=${state}&per_page=15`);
|
|
911
|
+
if (!res.ok) return { content: `GitHub API error: ${res.status}`, isError: true };
|
|
912
|
+
const issues = await res.json();
|
|
913
|
+
const realIssues = issues.filter((i) => !i.pull_request);
|
|
914
|
+
if (realIssues.length === 0) return { content: `No ${state} issues.` };
|
|
915
|
+
const lines = realIssues.map((i) => {
|
|
916
|
+
const labels = i.labels.map((l) => l.name).join(", ");
|
|
917
|
+
return `#${i.number} [${i.state}] ${i.title}${labels ? ` [${labels}]` : ""} by @${i.user.login}`;
|
|
918
|
+
});
|
|
919
|
+
return { content: lines.join("\n") };
|
|
920
|
+
}
|
|
921
|
+
case "create_pr": {
|
|
922
|
+
if (!args.title || !args.head) {
|
|
923
|
+
return { content: "title and head branch are required for create_pr", isError: true };
|
|
924
|
+
}
|
|
925
|
+
const res = await ghFetch(`/repos/${args.owner}/${args.repo}/pulls`, {
|
|
926
|
+
method: "POST",
|
|
927
|
+
body: JSON.stringify({
|
|
928
|
+
title: args.title,
|
|
929
|
+
body: args.body ?? "",
|
|
930
|
+
head: args.head,
|
|
931
|
+
base: args.base ?? "main"
|
|
932
|
+
})
|
|
933
|
+
});
|
|
934
|
+
if (!res.ok) {
|
|
935
|
+
const err = await res.json();
|
|
936
|
+
return { content: `Failed to create PR: ${err.message || res.status}`, isError: true };
|
|
937
|
+
}
|
|
938
|
+
const pr = await res.json();
|
|
939
|
+
return { content: `PR #${pr.number} created: ${pr.html_url}` };
|
|
940
|
+
}
|
|
941
|
+
case "create_issue": {
|
|
942
|
+
if (!args.title) {
|
|
943
|
+
return { content: "title is required for create_issue", isError: true };
|
|
944
|
+
}
|
|
945
|
+
const res = await ghFetch(`/repos/${args.owner}/${args.repo}/issues`, {
|
|
946
|
+
method: "POST",
|
|
947
|
+
body: JSON.stringify({
|
|
948
|
+
title: args.title,
|
|
949
|
+
body: args.body ?? "",
|
|
950
|
+
labels: args.labels ?? []
|
|
951
|
+
})
|
|
952
|
+
});
|
|
953
|
+
if (!res.ok) {
|
|
954
|
+
const err = await res.json();
|
|
955
|
+
return { content: `Failed to create issue: ${err.message || res.status}`, isError: true };
|
|
956
|
+
}
|
|
957
|
+
const issue = await res.json();
|
|
958
|
+
return { content: `Issue #${issue.number} created: ${issue.html_url}` };
|
|
959
|
+
}
|
|
960
|
+
case "pr_status": {
|
|
961
|
+
if (!args.pr_number) {
|
|
962
|
+
return { content: "pr_number is required for pr_status", isError: true };
|
|
963
|
+
}
|
|
964
|
+
const res = await ghFetch(`/repos/${args.owner}/${args.repo}/pulls/${args.pr_number}`);
|
|
965
|
+
if (!res.ok) return { content: `GitHub API error: ${res.status}`, isError: true };
|
|
966
|
+
const pr = await res.json();
|
|
967
|
+
const checksRes = await ghFetch(`/repos/${args.owner}/${args.repo}/commits/${pr.head.sha}/check-runs`);
|
|
968
|
+
let checksInfo = "";
|
|
969
|
+
if (checksRes.ok) {
|
|
970
|
+
const checks = await checksRes.json();
|
|
971
|
+
if (checks.check_runs?.length) {
|
|
972
|
+
checksInfo = "\nChecks:\n" + checks.check_runs.map(
|
|
973
|
+
(c) => ` ${c.status === "completed" ? c.conclusion === "success" ? "\u2713" : "\u2717" : "\u25CC"} ${c.name}: ${c.conclusion ?? c.status}`
|
|
974
|
+
).join("\n");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
content: [
|
|
979
|
+
`PR #${pr.number}: ${pr.title}`,
|
|
980
|
+
`State: ${pr.state} | Mergeable: ${pr.mergeable ?? "checking..."}`,
|
|
981
|
+
`${pr.head.ref} \u2192 ${pr.base.ref}`,
|
|
982
|
+
`Author: @${pr.user.login}`,
|
|
983
|
+
`+${pr.additions} -${pr.deletions} (${pr.changed_files} files)`,
|
|
984
|
+
`Reviews: ${pr.requested_reviewers?.length ?? 0} requested`,
|
|
985
|
+
checksInfo,
|
|
986
|
+
`URL: ${pr.html_url}`
|
|
987
|
+
].join("\n")
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
default:
|
|
991
|
+
return { content: `Unknown action: ${args.action}`, isError: true };
|
|
992
|
+
}
|
|
993
|
+
} catch (err) {
|
|
994
|
+
return { content: `GitHub error: ${err.message}`, isError: true };
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
var githubTool = {
|
|
998
|
+
name: "github",
|
|
999
|
+
description: "GitHub operations \u2014 create PRs, list issues, check PR status, view repo info. Requires GITHUB_TOKEN env var.",
|
|
1000
|
+
parameters: parameters9,
|
|
1001
|
+
execute
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
// src/mcp/client.ts
|
|
1005
|
+
import { spawn } from "child_process";
|
|
1006
|
+
import { z as z10 } from "zod";
|
|
1007
|
+
var MCPClient = class {
|
|
1008
|
+
constructor(config, serverName) {
|
|
1009
|
+
this.config = config;
|
|
1010
|
+
this.serverName = serverName;
|
|
1011
|
+
}
|
|
1012
|
+
process = null;
|
|
1013
|
+
requestId = 0;
|
|
1014
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
1015
|
+
buffer = "";
|
|
1016
|
+
serverName;
|
|
1017
|
+
_tools = [];
|
|
1018
|
+
/**
|
|
1019
|
+
* Start the MCP server and initialize the connection.
|
|
1020
|
+
*/
|
|
1021
|
+
async connect() {
|
|
1022
|
+
this.process = spawn(this.config.command, this.config.args ?? [], {
|
|
1023
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1024
|
+
env: { ...process.env, ...this.config.env },
|
|
1025
|
+
cwd: this.config.cwd
|
|
1026
|
+
});
|
|
1027
|
+
this.process.stdout?.setEncoding("utf-8");
|
|
1028
|
+
this.process.stdout?.on("data", (data) => {
|
|
1029
|
+
this.buffer += data;
|
|
1030
|
+
this.processBuffer();
|
|
1031
|
+
});
|
|
1032
|
+
this.process.on("error", (err) => {
|
|
1033
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
1034
|
+
pending.reject(new Error(`MCP server ${this.serverName} error: ${err.message}`));
|
|
1035
|
+
this.pendingRequests.delete(id);
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
this.process.on("exit", (code) => {
|
|
1039
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
1040
|
+
pending.reject(new Error(`MCP server ${this.serverName} exited with code ${code}`));
|
|
1041
|
+
this.pendingRequests.delete(id);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
await this.sendRequest("initialize", {
|
|
1045
|
+
protocolVersion: "2024-11-05",
|
|
1046
|
+
capabilities: {},
|
|
1047
|
+
clientInfo: { name: "notch-cli", version: "0.3.0" }
|
|
1048
|
+
});
|
|
1049
|
+
this.sendNotification("notifications/initialized", {});
|
|
1050
|
+
const result = await this.sendRequest("tools/list", {});
|
|
1051
|
+
this._tools = result.tools ?? [];
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Get discovered tools from this server.
|
|
1055
|
+
*/
|
|
1056
|
+
get tools() {
|
|
1057
|
+
return this._tools;
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Check if the MCP server process is still alive.
|
|
1061
|
+
*/
|
|
1062
|
+
get isAlive() {
|
|
1063
|
+
return this.process !== null && this.process.exitCode === null && !this.process.killed;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Call a tool on the MCP server. Auto-reconnects if the server has crashed.
|
|
1067
|
+
*/
|
|
1068
|
+
async callTool(name, args) {
|
|
1069
|
+
if (!this.isAlive) {
|
|
1070
|
+
try {
|
|
1071
|
+
await this.connect();
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const result = await this.sendRequest("tools/call", { name, arguments: args });
|
|
1077
|
+
return result;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Disconnect from the MCP server.
|
|
1081
|
+
*/
|
|
1082
|
+
disconnect() {
|
|
1083
|
+
if (this.process) {
|
|
1084
|
+
this.process.stdin?.end();
|
|
1085
|
+
this.process.kill();
|
|
1086
|
+
this.process = null;
|
|
1087
|
+
}
|
|
1088
|
+
this.pendingRequests.clear();
|
|
1089
|
+
}
|
|
1090
|
+
sendRequest(method, params) {
|
|
1091
|
+
return new Promise((resolve2, reject) => {
|
|
1092
|
+
const id = ++this.requestId;
|
|
1093
|
+
const msg = {
|
|
1094
|
+
jsonrpc: "2.0",
|
|
1095
|
+
id,
|
|
1096
|
+
method,
|
|
1097
|
+
params
|
|
1098
|
+
};
|
|
1099
|
+
this.pendingRequests.set(id, { resolve: resolve2, reject });
|
|
1100
|
+
const data = JSON.stringify(msg);
|
|
1101
|
+
const header = `Content-Length: ${Buffer.byteLength(data)}\r
|
|
1102
|
+
\r
|
|
1103
|
+
`;
|
|
1104
|
+
this.process?.stdin?.write(header + data);
|
|
1105
|
+
setTimeout(() => {
|
|
1106
|
+
if (this.pendingRequests.has(id)) {
|
|
1107
|
+
this.pendingRequests.delete(id);
|
|
1108
|
+
reject(new Error(`MCP request ${method} timed out`));
|
|
1109
|
+
}
|
|
1110
|
+
}, 3e4);
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
sendNotification(method, params) {
|
|
1114
|
+
const msg = {
|
|
1115
|
+
jsonrpc: "2.0",
|
|
1116
|
+
method,
|
|
1117
|
+
params
|
|
1118
|
+
};
|
|
1119
|
+
const data = JSON.stringify(msg);
|
|
1120
|
+
const header = `Content-Length: ${Buffer.byteLength(data)}\r
|
|
1121
|
+
\r
|
|
1122
|
+
`;
|
|
1123
|
+
this.process?.stdin?.write(header + data);
|
|
1124
|
+
}
|
|
1125
|
+
processBuffer() {
|
|
1126
|
+
while (this.buffer.length > 0) {
|
|
1127
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
1128
|
+
if (headerEnd === -1) break;
|
|
1129
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
1130
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
1131
|
+
if (!lengthMatch) {
|
|
1132
|
+
const nlIdx = this.buffer.indexOf("\n");
|
|
1133
|
+
if (nlIdx === -1) break;
|
|
1134
|
+
const line = this.buffer.slice(0, nlIdx).trim();
|
|
1135
|
+
this.buffer = this.buffer.slice(nlIdx + 1);
|
|
1136
|
+
if (line) this.handleMessage(line);
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const contentLength = parseInt(lengthMatch[1], 10);
|
|
1140
|
+
const messageStart = headerEnd + 4;
|
|
1141
|
+
if (this.buffer.length < messageStart + contentLength) break;
|
|
1142
|
+
const body = this.buffer.slice(messageStart, messageStart + contentLength);
|
|
1143
|
+
this.buffer = this.buffer.slice(messageStart + contentLength);
|
|
1144
|
+
this.handleMessage(body);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
handleMessage(raw) {
|
|
1148
|
+
try {
|
|
1149
|
+
const msg = JSON.parse(raw);
|
|
1150
|
+
if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
|
|
1151
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
1152
|
+
this.pendingRequests.delete(msg.id);
|
|
1153
|
+
if (msg.error) {
|
|
1154
|
+
pending.reject(new Error(`MCP error: ${msg.error.message}`));
|
|
1155
|
+
} else {
|
|
1156
|
+
pending.resolve(msg.result);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
function parseMCPConfig(config) {
|
|
1164
|
+
const servers = config?.mcpServers;
|
|
1165
|
+
if (!servers || typeof servers !== "object") return {};
|
|
1166
|
+
const result = {};
|
|
1167
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
1168
|
+
const c = cfg;
|
|
1169
|
+
if (c?.command) {
|
|
1170
|
+
result[name] = {
|
|
1171
|
+
command: c.command,
|
|
1172
|
+
args: c.args,
|
|
1173
|
+
env: c.env,
|
|
1174
|
+
cwd: c.cwd
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
833
1181
|
// src/tools/index.ts
|
|
834
|
-
var
|
|
1182
|
+
var BUILTIN_TOOLS = [
|
|
835
1183
|
readTool,
|
|
836
1184
|
writeTool,
|
|
837
1185
|
editTool,
|
|
838
1186
|
shellTool,
|
|
839
1187
|
gitTool,
|
|
1188
|
+
githubTool,
|
|
840
1189
|
grepTool,
|
|
841
1190
|
globTool,
|
|
842
1191
|
webFetchTool
|
|
843
1192
|
];
|
|
1193
|
+
var mcpTools = [];
|
|
1194
|
+
function getAllTools() {
|
|
1195
|
+
return [...BUILTIN_TOOLS, ...mcpTools];
|
|
1196
|
+
}
|
|
844
1197
|
function buildToolMap(ctx) {
|
|
845
1198
|
const map = {};
|
|
846
|
-
for (const t of
|
|
1199
|
+
for (const t of getAllTools()) {
|
|
847
1200
|
map[t.name] = tool({
|
|
848
1201
|
description: t.description,
|
|
849
1202
|
parameters: t.parameters,
|
|
@@ -888,7 +1241,7 @@ ${paramSummary}`
|
|
|
888
1241
|
return map;
|
|
889
1242
|
}
|
|
890
1243
|
function describeTools() {
|
|
891
|
-
return
|
|
1244
|
+
return getAllTools().map(
|
|
892
1245
|
(t) => `- **${t.name}**: ${t.description}`
|
|
893
1246
|
).join("\n");
|
|
894
1247
|
}
|
|
@@ -2311,36 +2664,49 @@ async function resolveGlob(pattern, cwd) {
|
|
|
2311
2664
|
}
|
|
2312
2665
|
}
|
|
2313
2666
|
|
|
2667
|
+
// src/ui/banner.ts
|
|
2668
|
+
import chalk6 from "chalk";
|
|
2669
|
+
|
|
2314
2670
|
// src/ui/themes.ts
|
|
2315
2671
|
import chalk5 from "chalk";
|
|
2316
2672
|
var defaultTheme = {
|
|
2317
2673
|
name: "Default",
|
|
2318
|
-
description: "
|
|
2319
|
-
brand: chalk5.
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2674
|
+
description: "FreeSyntax \u2014 silver, white, monochrome",
|
|
2675
|
+
brand: chalk5.hex("#D4D4D4"),
|
|
2676
|
+
// silver (banner uses gradient override)
|
|
2677
|
+
mascot: chalk5.hex("#AAAAAA"),
|
|
2678
|
+
// medium gray mantis
|
|
2679
|
+
mascotAccent: chalk5.hex("#FFFFFF"),
|
|
2680
|
+
// white eyes
|
|
2681
|
+
tagline: chalk5.hex("#777777"),
|
|
2682
|
+
// muted gray
|
|
2683
|
+
prompt: chalk5.hex("#CCCCCC"),
|
|
2684
|
+
// silver prompt
|
|
2685
|
+
border: chalk5.hex("#444444"),
|
|
2686
|
+
// dark border
|
|
2687
|
+
dim: chalk5.hex("#666666"),
|
|
2688
|
+
// muted text
|
|
2689
|
+
text: chalk5.hex("#D4D4D4"),
|
|
2690
|
+
// silver body text
|
|
2691
|
+
bold: chalk5.hex("#FFFFFF").bold,
|
|
2692
|
+
// pure white emphasis
|
|
2328
2693
|
success: chalk5.green,
|
|
2329
2694
|
warning: chalk5.yellow,
|
|
2330
2695
|
error: chalk5.red,
|
|
2331
|
-
info: chalk5.
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2696
|
+
info: chalk5.hex("#BBBBBB"),
|
|
2697
|
+
// light gray info
|
|
2698
|
+
toolName: chalk5.hex("#AAAAAA"),
|
|
2699
|
+
toolArgs: chalk5.hex("#777777"),
|
|
2700
|
+
toolResult: chalk5.hex("#555555"),
|
|
2335
2701
|
diffAdd: chalk5.green,
|
|
2336
2702
|
diffRemove: chalk5.red,
|
|
2337
|
-
diffHeader: chalk5.
|
|
2338
|
-
mdH1: chalk5.
|
|
2339
|
-
mdH2: chalk5.
|
|
2340
|
-
mdH3: chalk5.
|
|
2341
|
-
mdCode: chalk5.
|
|
2342
|
-
mdInlineCode: chalk5.
|
|
2343
|
-
mdLink: chalk5.
|
|
2703
|
+
diffHeader: chalk5.hex("#CCCCCC"),
|
|
2704
|
+
mdH1: chalk5.hex("#FFFFFF").bold,
|
|
2705
|
+
mdH2: chalk5.hex("#D4D4D4"),
|
|
2706
|
+
mdH3: chalk5.hex("#AAAAAA"),
|
|
2707
|
+
mdCode: chalk5.hex("#BBBBBB"),
|
|
2708
|
+
mdInlineCode: chalk5.hex("#CCCCCC"),
|
|
2709
|
+
mdLink: chalk5.hex("#D4D4D4").underline,
|
|
2344
2710
|
meterLow: chalk5.green,
|
|
2345
2711
|
meterMid: chalk5.yellow,
|
|
2346
2712
|
meterHigh: chalk5.red
|
|
@@ -2790,6 +3156,39 @@ function formatThemeList(activeId) {
|
|
|
2790
3156
|
}
|
|
2791
3157
|
|
|
2792
3158
|
// src/ui/banner.ts
|
|
3159
|
+
var NOTCH_LARGE = [
|
|
3160
|
+
"\u2588\u2588\u2584 \u2588 \u2584\u2588\u2588\u2580\u2580\u2588\u2588\u2584 \u2580\u2588\u2588\u2580\u2580\u2580\u2580 \u2584\u2588\u2588\u2580\u2580\u2588\u2588\u2584 \u2588\u2588 \u2588\u2588",
|
|
3161
|
+
"\u2588\u2580\u2588\u2588 \u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2580\u2588\u2588\u2580\u2588",
|
|
3162
|
+
"\u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588 \u2580 \u2588",
|
|
3163
|
+
"\u2588 \u2580\u2588\u2584\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588 \u2588",
|
|
3164
|
+
"\u2588 \u2580\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588 \u2588",
|
|
3165
|
+
"\u2588 \u2588\u2588 \u2580\u2588\u2588\u2584\u2584\u2588\u2588\u2580 \u2588\u2588 \u2580\u2588\u2588\u2584\u2584\u2588\u2588\u2580 \u2588 \u2588",
|
|
3166
|
+
"\u2580 \u2580 \u2580\u2580\u2580\u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580 \u2580"
|
|
3167
|
+
];
|
|
3168
|
+
var ROW_COLORS = [
|
|
3169
|
+
"#666666",
|
|
3170
|
+
// dim silver
|
|
3171
|
+
"#888888",
|
|
3172
|
+
// medium gray
|
|
3173
|
+
"#AAAAAA",
|
|
3174
|
+
// silver
|
|
3175
|
+
"#CCCCCC",
|
|
3176
|
+
// light silver
|
|
3177
|
+
"#DDDDDD",
|
|
3178
|
+
// near white
|
|
3179
|
+
"#EEEEEE",
|
|
3180
|
+
// bright
|
|
3181
|
+
"#FFFFFF"
|
|
3182
|
+
// pure white
|
|
3183
|
+
];
|
|
3184
|
+
function colorBannerLine(line, rowIndex) {
|
|
3185
|
+
const t = theme();
|
|
3186
|
+
if (t.name !== "Default") {
|
|
3187
|
+
return t.brand(line);
|
|
3188
|
+
}
|
|
3189
|
+
const color = ROW_COLORS[rowIndex] ?? "#CCCCCC";
|
|
3190
|
+
return chalk6.hex(color)(line);
|
|
3191
|
+
}
|
|
2793
3192
|
var MANTIS = [
|
|
2794
3193
|
" \u2571\u25C9\u25C9\u2572",
|
|
2795
3194
|
// ╱◉◉╲ antennae + eyes
|
|
@@ -2804,11 +3203,6 @@ var MANTIS = [
|
|
|
2804
3203
|
" \u2580\u2580 \u2580\u2580"
|
|
2805
3204
|
// ▀▀ ▀▀ feet
|
|
2806
3205
|
];
|
|
2807
|
-
var LOGO_INLINE = [
|
|
2808
|
-
" \u2588\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2580\u2588\u2580 \u2584\u2580\u2580\u2584 \u2588 \u2588",
|
|
2809
|
-
" \u2588 \u2580\u2584 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2580\u2580\u2588",
|
|
2810
|
-
" \u2588 \u2580\u2588\u2588 \u2580\u2584\u2584\u2580 \u2588 \u2580\u2584\u2584\u2580 \u2588 \u2588"
|
|
2811
|
-
];
|
|
2812
3206
|
function colorMantis(line) {
|
|
2813
3207
|
const t = theme();
|
|
2814
3208
|
return line.replace(/[\u2571\u2572]/g, (ch) => t.mascot(ch)).replace(/\u25c9/g, t.mascotAccent("\u25C9")).replace(/[\u2588]/g, (ch) => t.mascot(ch)).replace(/[\u2584\u2580]/g, (ch) => t.mascot(ch)).replace(/\u2590/g, t.mascot("\u2590")).replace(/\u258c/g, t.mascot("\u258C"));
|
|
@@ -2817,24 +3211,49 @@ function printBanner(version, modelLabel, modelId, modelSize, project) {
|
|
|
2817
3211
|
const t = theme();
|
|
2818
3212
|
const termWidth = process.stdout.columns || 80;
|
|
2819
3213
|
console.log("");
|
|
2820
|
-
for (
|
|
2821
|
-
console.log(" " +
|
|
2822
|
-
}
|
|
2823
|
-
console.log("");
|
|
2824
|
-
for (const line of LOGO_INLINE) {
|
|
2825
|
-
console.log(" " + t.brand(line));
|
|
3214
|
+
for (let i = 0; i < NOTCH_LARGE.length; i++) {
|
|
3215
|
+
console.log(" " + colorBannerLine(NOTCH_LARGE[i], i));
|
|
2826
3216
|
}
|
|
2827
3217
|
console.log("");
|
|
2828
|
-
const
|
|
3218
|
+
const mantisStr = MANTIS.map((l) => colorMantis(l));
|
|
3219
|
+
const divWidth = Math.min(54, termWidth - 4);
|
|
2829
3220
|
const divider = t.border(" " + "\u2500".repeat(divWidth));
|
|
2830
3221
|
console.log(divider);
|
|
2831
|
-
|
|
2832
|
-
t.dim(" ") + t.bold(modelLabel) + t.dim(" \u2502 v") + t.text(version)
|
|
2833
|
-
)
|
|
2834
|
-
|
|
3222
|
+
const info = [
|
|
3223
|
+
t.dim(" ") + t.bold(modelLabel) + t.dim(" \u2502 v") + t.text(version),
|
|
3224
|
+
t.dim(" ") + t.dim(project),
|
|
3225
|
+
t.dim(" ") + t.dim("by ") + t.tagline("Driftrail")
|
|
3226
|
+
];
|
|
3227
|
+
for (let i = 0; i < Math.max(mantisStr.length, info.length); i++) {
|
|
3228
|
+
const left = i < mantisStr.length ? " " + mantisStr[i] : "";
|
|
3229
|
+
const right = i < info.length ? info[i] : "";
|
|
3230
|
+
if (left && right) {
|
|
3231
|
+
const rawLeft = " " + MANTIS[i];
|
|
3232
|
+
const pad = Math.max(0, 16 - rawLeft.length);
|
|
3233
|
+
console.log(left + " ".repeat(pad) + right);
|
|
3234
|
+
} else if (left) {
|
|
3235
|
+
console.log(left);
|
|
3236
|
+
} else {
|
|
3237
|
+
console.log(" " + " ".repeat(14) + right);
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
2835
3240
|
console.log(divider);
|
|
2836
3241
|
console.log("");
|
|
2837
3242
|
}
|
|
3243
|
+
function printWordmark(version) {
|
|
3244
|
+
const t = theme();
|
|
3245
|
+
const termWidth = process.stdout.columns || 80;
|
|
3246
|
+
console.log("");
|
|
3247
|
+
for (let i = 0; i < NOTCH_LARGE.length; i++) {
|
|
3248
|
+
console.log(" " + colorBannerLine(NOTCH_LARGE[i], i));
|
|
3249
|
+
}
|
|
3250
|
+
console.log("");
|
|
3251
|
+
const divWidth = Math.min(54, termWidth - 4);
|
|
3252
|
+
console.log(t.border(" " + "\u2500".repeat(divWidth)));
|
|
3253
|
+
console.log(t.dim(" v" + version) + t.dim(" \u2502 ") + t.tagline("by Driftrail"));
|
|
3254
|
+
console.log(t.border(" " + "\u2500".repeat(divWidth)));
|
|
3255
|
+
console.log("");
|
|
3256
|
+
}
|
|
2838
3257
|
function printMantis() {
|
|
2839
3258
|
const t = theme();
|
|
2840
3259
|
console.log("");
|
|
@@ -2877,7 +3296,7 @@ function formatTokens(n) {
|
|
|
2877
3296
|
import fs12 from "fs/promises";
|
|
2878
3297
|
import path13 from "path";
|
|
2879
3298
|
import os3 from "os";
|
|
2880
|
-
import
|
|
3299
|
+
import chalk7 from "chalk";
|
|
2881
3300
|
var CACHE_FILE = path13.join(os3.homedir(), ".notch", "update-check.json");
|
|
2882
3301
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
2883
3302
|
var PACKAGE_NAME = "notch-cli";
|
|
@@ -2921,7 +3340,7 @@ function isNewer(latest, current) {
|
|
|
2921
3340
|
return false;
|
|
2922
3341
|
}
|
|
2923
3342
|
function formatUpdateMessage(current, latest) {
|
|
2924
|
-
return
|
|
3343
|
+
return chalk7.yellow(` Update available: ${current} -> ${latest}. Run: npm update -g ${PACKAGE_NAME}
|
|
2925
3344
|
`);
|
|
2926
3345
|
}
|
|
2927
3346
|
async function loadCache() {
|
|
@@ -3275,7 +3694,7 @@ async function exportSession(messages, outputPath, meta) {
|
|
|
3275
3694
|
// src/init.ts
|
|
3276
3695
|
import fs16 from "fs/promises";
|
|
3277
3696
|
import path17 from "path";
|
|
3278
|
-
import
|
|
3697
|
+
import chalk8 from "chalk";
|
|
3279
3698
|
var DEFAULT_CONFIG = {
|
|
3280
3699
|
model: "notch-forge",
|
|
3281
3700
|
temperature: 0.3,
|
|
@@ -3322,15 +3741,15 @@ async function initProject(projectRoot) {
|
|
|
3322
3741
|
}
|
|
3323
3742
|
if (!configExists) {
|
|
3324
3743
|
await fs16.writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
|
|
3325
|
-
console.log(
|
|
3744
|
+
console.log(chalk8.green(` Created ${configPath}`));
|
|
3326
3745
|
} else {
|
|
3327
|
-
console.log(
|
|
3746
|
+
console.log(chalk8.gray(` Skipped ${configPath} (already exists)`));
|
|
3328
3747
|
}
|
|
3329
3748
|
if (!instructionsExist) {
|
|
3330
3749
|
await fs16.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS, "utf-8");
|
|
3331
|
-
console.log(
|
|
3750
|
+
console.log(chalk8.green(` Created ${instructionsPath}`));
|
|
3332
3751
|
} else {
|
|
3333
|
-
console.log(
|
|
3752
|
+
console.log(chalk8.gray(` Skipped ${instructionsPath} (already exists)`));
|
|
3334
3753
|
}
|
|
3335
3754
|
const gitignorePath = path17.join(projectRoot, ".gitignore");
|
|
3336
3755
|
try {
|
|
@@ -3340,13 +3759,13 @@ async function initProject(projectRoot) {
|
|
|
3340
3759
|
if (additions.length > 0) {
|
|
3341
3760
|
const append = "\n# Notch CLI\n" + additions.join("\n") + "\n";
|
|
3342
3761
|
await fs16.appendFile(gitignorePath, append, "utf-8");
|
|
3343
|
-
console.log(
|
|
3762
|
+
console.log(chalk8.green(` Updated .gitignore`));
|
|
3344
3763
|
}
|
|
3345
3764
|
} catch {
|
|
3346
3765
|
}
|
|
3347
3766
|
console.log("");
|
|
3348
|
-
console.log(
|
|
3349
|
-
console.log(
|
|
3767
|
+
console.log(chalk8.cyan(" Notch initialized! Edit .notch.md to customize behavior."));
|
|
3768
|
+
console.log(chalk8.gray(' Run "notch" to start.\n'));
|
|
3350
3769
|
}
|
|
3351
3770
|
|
|
3352
3771
|
// src/tools/diff-preview.ts
|
|
@@ -3465,183 +3884,6 @@ function findSync(oldLines, newLines, oi, ni, lookAhead) {
|
|
|
3465
3884
|
return null;
|
|
3466
3885
|
}
|
|
3467
3886
|
|
|
3468
|
-
// src/mcp/client.ts
|
|
3469
|
-
import { spawn } from "child_process";
|
|
3470
|
-
import { z as z9 } from "zod";
|
|
3471
|
-
var MCPClient = class {
|
|
3472
|
-
constructor(config, serverName) {
|
|
3473
|
-
this.config = config;
|
|
3474
|
-
this.serverName = serverName;
|
|
3475
|
-
}
|
|
3476
|
-
process = null;
|
|
3477
|
-
requestId = 0;
|
|
3478
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
3479
|
-
buffer = "";
|
|
3480
|
-
serverName;
|
|
3481
|
-
_tools = [];
|
|
3482
|
-
/**
|
|
3483
|
-
* Start the MCP server and initialize the connection.
|
|
3484
|
-
*/
|
|
3485
|
-
async connect() {
|
|
3486
|
-
this.process = spawn(this.config.command, this.config.args ?? [], {
|
|
3487
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
3488
|
-
env: { ...process.env, ...this.config.env },
|
|
3489
|
-
cwd: this.config.cwd
|
|
3490
|
-
});
|
|
3491
|
-
this.process.stdout?.setEncoding("utf-8");
|
|
3492
|
-
this.process.stdout?.on("data", (data) => {
|
|
3493
|
-
this.buffer += data;
|
|
3494
|
-
this.processBuffer();
|
|
3495
|
-
});
|
|
3496
|
-
this.process.on("error", (err) => {
|
|
3497
|
-
for (const [id, pending] of this.pendingRequests) {
|
|
3498
|
-
pending.reject(new Error(`MCP server ${this.serverName} error: ${err.message}`));
|
|
3499
|
-
this.pendingRequests.delete(id);
|
|
3500
|
-
}
|
|
3501
|
-
});
|
|
3502
|
-
this.process.on("exit", (code) => {
|
|
3503
|
-
for (const [id, pending] of this.pendingRequests) {
|
|
3504
|
-
pending.reject(new Error(`MCP server ${this.serverName} exited with code ${code}`));
|
|
3505
|
-
this.pendingRequests.delete(id);
|
|
3506
|
-
}
|
|
3507
|
-
});
|
|
3508
|
-
await this.sendRequest("initialize", {
|
|
3509
|
-
protocolVersion: "2024-11-05",
|
|
3510
|
-
capabilities: {},
|
|
3511
|
-
clientInfo: { name: "notch-cli", version: "0.3.0" }
|
|
3512
|
-
});
|
|
3513
|
-
this.sendNotification("notifications/initialized", {});
|
|
3514
|
-
const result = await this.sendRequest("tools/list", {});
|
|
3515
|
-
this._tools = result.tools ?? [];
|
|
3516
|
-
}
|
|
3517
|
-
/**
|
|
3518
|
-
* Get discovered tools from this server.
|
|
3519
|
-
*/
|
|
3520
|
-
get tools() {
|
|
3521
|
-
return this._tools;
|
|
3522
|
-
}
|
|
3523
|
-
/**
|
|
3524
|
-
* Check if the MCP server process is still alive.
|
|
3525
|
-
*/
|
|
3526
|
-
get isAlive() {
|
|
3527
|
-
return this.process !== null && this.process.exitCode === null && !this.process.killed;
|
|
3528
|
-
}
|
|
3529
|
-
/**
|
|
3530
|
-
* Call a tool on the MCP server. Auto-reconnects if the server has crashed.
|
|
3531
|
-
*/
|
|
3532
|
-
async callTool(name, args) {
|
|
3533
|
-
if (!this.isAlive) {
|
|
3534
|
-
try {
|
|
3535
|
-
await this.connect();
|
|
3536
|
-
} catch (err) {
|
|
3537
|
-
throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
const result = await this.sendRequest("tools/call", { name, arguments: args });
|
|
3541
|
-
return result;
|
|
3542
|
-
}
|
|
3543
|
-
/**
|
|
3544
|
-
* Disconnect from the MCP server.
|
|
3545
|
-
*/
|
|
3546
|
-
disconnect() {
|
|
3547
|
-
if (this.process) {
|
|
3548
|
-
this.process.stdin?.end();
|
|
3549
|
-
this.process.kill();
|
|
3550
|
-
this.process = null;
|
|
3551
|
-
}
|
|
3552
|
-
this.pendingRequests.clear();
|
|
3553
|
-
}
|
|
3554
|
-
sendRequest(method, params) {
|
|
3555
|
-
return new Promise((resolve2, reject) => {
|
|
3556
|
-
const id = ++this.requestId;
|
|
3557
|
-
const msg = {
|
|
3558
|
-
jsonrpc: "2.0",
|
|
3559
|
-
id,
|
|
3560
|
-
method,
|
|
3561
|
-
params
|
|
3562
|
-
};
|
|
3563
|
-
this.pendingRequests.set(id, { resolve: resolve2, reject });
|
|
3564
|
-
const data = JSON.stringify(msg);
|
|
3565
|
-
const header = `Content-Length: ${Buffer.byteLength(data)}\r
|
|
3566
|
-
\r
|
|
3567
|
-
`;
|
|
3568
|
-
this.process?.stdin?.write(header + data);
|
|
3569
|
-
setTimeout(() => {
|
|
3570
|
-
if (this.pendingRequests.has(id)) {
|
|
3571
|
-
this.pendingRequests.delete(id);
|
|
3572
|
-
reject(new Error(`MCP request ${method} timed out`));
|
|
3573
|
-
}
|
|
3574
|
-
}, 3e4);
|
|
3575
|
-
});
|
|
3576
|
-
}
|
|
3577
|
-
sendNotification(method, params) {
|
|
3578
|
-
const msg = {
|
|
3579
|
-
jsonrpc: "2.0",
|
|
3580
|
-
method,
|
|
3581
|
-
params
|
|
3582
|
-
};
|
|
3583
|
-
const data = JSON.stringify(msg);
|
|
3584
|
-
const header = `Content-Length: ${Buffer.byteLength(data)}\r
|
|
3585
|
-
\r
|
|
3586
|
-
`;
|
|
3587
|
-
this.process?.stdin?.write(header + data);
|
|
3588
|
-
}
|
|
3589
|
-
processBuffer() {
|
|
3590
|
-
while (this.buffer.length > 0) {
|
|
3591
|
-
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
3592
|
-
if (headerEnd === -1) break;
|
|
3593
|
-
const header = this.buffer.slice(0, headerEnd);
|
|
3594
|
-
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
3595
|
-
if (!lengthMatch) {
|
|
3596
|
-
const nlIdx = this.buffer.indexOf("\n");
|
|
3597
|
-
if (nlIdx === -1) break;
|
|
3598
|
-
const line = this.buffer.slice(0, nlIdx).trim();
|
|
3599
|
-
this.buffer = this.buffer.slice(nlIdx + 1);
|
|
3600
|
-
if (line) this.handleMessage(line);
|
|
3601
|
-
continue;
|
|
3602
|
-
}
|
|
3603
|
-
const contentLength = parseInt(lengthMatch[1], 10);
|
|
3604
|
-
const messageStart = headerEnd + 4;
|
|
3605
|
-
if (this.buffer.length < messageStart + contentLength) break;
|
|
3606
|
-
const body = this.buffer.slice(messageStart, messageStart + contentLength);
|
|
3607
|
-
this.buffer = this.buffer.slice(messageStart + contentLength);
|
|
3608
|
-
this.handleMessage(body);
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
handleMessage(raw) {
|
|
3612
|
-
try {
|
|
3613
|
-
const msg = JSON.parse(raw);
|
|
3614
|
-
if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
|
|
3615
|
-
const pending = this.pendingRequests.get(msg.id);
|
|
3616
|
-
this.pendingRequests.delete(msg.id);
|
|
3617
|
-
if (msg.error) {
|
|
3618
|
-
pending.reject(new Error(`MCP error: ${msg.error.message}`));
|
|
3619
|
-
} else {
|
|
3620
|
-
pending.resolve(msg.result);
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
} catch {
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
};
|
|
3627
|
-
function parseMCPConfig(config) {
|
|
3628
|
-
const servers = config?.mcpServers;
|
|
3629
|
-
if (!servers || typeof servers !== "object") return {};
|
|
3630
|
-
const result = {};
|
|
3631
|
-
for (const [name, cfg] of Object.entries(servers)) {
|
|
3632
|
-
const c = cfg;
|
|
3633
|
-
if (c?.command) {
|
|
3634
|
-
result[name] = {
|
|
3635
|
-
command: c.command,
|
|
3636
|
-
args: c.args,
|
|
3637
|
-
env: c.env,
|
|
3638
|
-
cwd: c.cwd
|
|
3639
|
-
};
|
|
3640
|
-
}
|
|
3641
|
-
}
|
|
3642
|
-
return result;
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
3887
|
// src/ui/completions.ts
|
|
3646
3888
|
import fs17 from "fs";
|
|
3647
3889
|
import path18 from "path";
|
|
@@ -3729,7 +3971,9 @@ function completeFilePath(partial, cwd) {
|
|
|
3729
3971
|
|
|
3730
3972
|
// src/index.ts
|
|
3731
3973
|
import fs18 from "fs/promises";
|
|
3732
|
-
|
|
3974
|
+
import { createRequire } from "module";
|
|
3975
|
+
var _require = createRequire(import.meta.url);
|
|
3976
|
+
var VERSION = _require("../package.json").version;
|
|
3733
3977
|
var modelChoices = MODEL_IDS.join(", ");
|
|
3734
3978
|
var program = new Command().name("notch").description("Notch CLI \u2014 AI-powered coding assistant by Driftrail").version(VERSION).argument("[prompt...]", "One-shot prompt (runs once and exits)").option(`-m, --model <model>`, `Notch model (${modelChoices})`).option("--base-url <url>", "Override Notch API base URL").option("--api-key <key>", "Notch API key (prefer NOTCH_API_KEY env var)").option("--no-repo-map", "Disable automatic repository mapping").option("--no-markdown", "Disable markdown rendering in output").option("--max-iterations <n>", "Max tool-call rounds per turn", "25").option("-y, --yes", "Auto-confirm destructive actions").option("--trust", "Trust mode \u2014 auto-allow all tool calls").option("--theme <theme>", `UI color theme (${THEME_IDS.join(", ")})`).option("--resume", "Resume the last session for this project").option("--session <id>", "Resume a specific session by ID").option("--cwd <dir>", "Set working directory").parse(process.argv);
|
|
3735
3979
|
var opts = program.opts();
|
|
@@ -3749,7 +3993,7 @@ function printModelTable(activeModel) {
|
|
|
3749
3993
|
`));
|
|
3750
3994
|
}
|
|
3751
3995
|
function printHelp() {
|
|
3752
|
-
console.log(
|
|
3996
|
+
console.log(chalk9.gray(`
|
|
3753
3997
|
Commands:
|
|
3754
3998
|
/model \u2014 Show available models
|
|
3755
3999
|
/model <name> \u2014 Switch model (e.g., /model pyre)
|
|
@@ -3822,13 +4066,13 @@ async function main() {
|
|
|
3822
4066
|
try {
|
|
3823
4067
|
spinner.stop();
|
|
3824
4068
|
const creds = await login();
|
|
3825
|
-
console.log(
|
|
4069
|
+
console.log(chalk9.green(`
|
|
3826
4070
|
\u2713 Signed in as ${creds.email}`));
|
|
3827
|
-
console.log(
|
|
4071
|
+
console.log(chalk9.gray(` API key stored in ${(await import("./auth-JQX6MHJG.js")).getCredentialsPath()}
|
|
3828
4072
|
`));
|
|
3829
4073
|
} catch (err) {
|
|
3830
4074
|
spinner.stop();
|
|
3831
|
-
console.error(
|
|
4075
|
+
console.error(chalk9.red(`
|
|
3832
4076
|
Login failed: ${err.message}
|
|
3833
4077
|
`));
|
|
3834
4078
|
process.exit(1);
|
|
@@ -3838,10 +4082,10 @@ async function main() {
|
|
|
3838
4082
|
if (promptArgs[0] === "logout") {
|
|
3839
4083
|
const creds = await loadCredentials();
|
|
3840
4084
|
if (!creds) {
|
|
3841
|
-
console.log(
|
|
4085
|
+
console.log(chalk9.gray("\n Not signed in.\n"));
|
|
3842
4086
|
} else {
|
|
3843
4087
|
await clearCredentials();
|
|
3844
|
-
console.log(
|
|
4088
|
+
console.log(chalk9.green(`
|
|
3845
4089
|
\u2713 Signed out (${creds.email})
|
|
3846
4090
|
`));
|
|
3847
4091
|
}
|
|
@@ -3850,13 +4094,13 @@ async function main() {
|
|
|
3850
4094
|
if (promptArgs[0] === "whoami") {
|
|
3851
4095
|
const creds = await loadCredentials();
|
|
3852
4096
|
if (!creds) {
|
|
3853
|
-
console.log(
|
|
4097
|
+
console.log(chalk9.gray("\n Not signed in. Run: notch login\n"));
|
|
3854
4098
|
} else {
|
|
3855
4099
|
const keyPreview = `${creds.token.slice(0, 12)}...`;
|
|
3856
|
-
console.log(
|
|
3857
|
-
Signed in as ${
|
|
3858
|
-
console.log(
|
|
3859
|
-
console.log(
|
|
4100
|
+
console.log(chalk9.gray(`
|
|
4101
|
+
Signed in as ${chalk9.white(creds.email)}`));
|
|
4102
|
+
console.log(chalk9.gray(` Key: ${keyPreview}`));
|
|
4103
|
+
console.log(chalk9.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
|
|
3860
4104
|
`));
|
|
3861
4105
|
}
|
|
3862
4106
|
return;
|
|
@@ -3880,8 +4124,8 @@ async function main() {
|
|
|
3880
4124
|
const config = await loadConfig(configOverrides);
|
|
3881
4125
|
if (opts.model) {
|
|
3882
4126
|
if (!isValidModel(opts.model)) {
|
|
3883
|
-
console.error(
|
|
3884
|
-
console.error(
|
|
4127
|
+
console.error(chalk9.red(` Unknown model: ${opts.model}`));
|
|
4128
|
+
console.error(chalk9.gray(` Available: ${modelChoices}`));
|
|
3885
4129
|
process.exit(1);
|
|
3886
4130
|
}
|
|
3887
4131
|
config.models.chat.model = opts.model;
|
|
@@ -3897,9 +4141,7 @@ async function main() {
|
|
|
3897
4141
|
model = resolveModel(config.models.chat);
|
|
3898
4142
|
} catch (err) {
|
|
3899
4143
|
if (err instanceof MissingApiKeyError) {
|
|
3900
|
-
|
|
3901
|
-
console.log(" \x1B[1m\x1B[36m\u26A1 Welcome to Notch CLI\x1B[0m");
|
|
3902
|
-
console.log("");
|
|
4144
|
+
printWordmark(VERSION);
|
|
3903
4145
|
console.log(" To get started, you need a Notch API key.");
|
|
3904
4146
|
console.log("");
|
|
3905
4147
|
console.log(" \x1B[1mOption 1:\x1B[0m Log in via browser (recommended)");
|
|
@@ -3911,7 +4153,7 @@ async function main() {
|
|
|
3911
4153
|
console.log(" \x1B[1mOption 3:\x1B[0m Pass it inline");
|
|
3912
4154
|
console.log(" \x1B[33m$ notch --api-key your-key-here\x1B[0m");
|
|
3913
4155
|
console.log("");
|
|
3914
|
-
console.log(" Get your key at: \x1B[4mhttps://freesyntax.
|
|
4156
|
+
console.log(" Get your key at: \x1B[4mhttps://freesyntax.dev/settings\x1B[0m");
|
|
3915
4157
|
console.log("");
|
|
3916
4158
|
process.exit(0);
|
|
3917
4159
|
}
|
|
@@ -3923,11 +4165,11 @@ async function main() {
|
|
|
3923
4165
|
if (msg) console.log(msg);
|
|
3924
4166
|
});
|
|
3925
4167
|
const hookTrustPrompt = async (commands) => {
|
|
3926
|
-
console.warn(
|
|
3927
|
-
commands.forEach((cmd) => console.warn(
|
|
4168
|
+
console.warn(chalk9.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
|
|
4169
|
+
commands.forEach((cmd) => console.warn(chalk9.gray(` \u2022 ${cmd}`)));
|
|
3928
4170
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3929
4171
|
return new Promise((resolve2) => {
|
|
3930
|
-
rl2.question(
|
|
4172
|
+
rl2.question(chalk9.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
|
|
3931
4173
|
rl2.close();
|
|
3932
4174
|
resolve2(answer.trim().toLowerCase() === "y");
|
|
3933
4175
|
});
|
|
@@ -3973,9 +4215,9 @@ ${repoMapStr}` : ""
|
|
|
3973
4215
|
const client = new MCPClient(mcpConfig, name);
|
|
3974
4216
|
await client.connect();
|
|
3975
4217
|
mcpClients.push(client);
|
|
3976
|
-
console.log(
|
|
4218
|
+
console.log(chalk9.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
|
|
3977
4219
|
} catch (err) {
|
|
3978
|
-
console.log(
|
|
4220
|
+
console.log(chalk9.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
|
|
3979
4221
|
}
|
|
3980
4222
|
}
|
|
3981
4223
|
} catch {
|
|
@@ -3992,7 +4234,7 @@ ${repoMapStr}` : ""
|
|
|
3992
4234
|
});
|
|
3993
4235
|
});
|
|
3994
4236
|
},
|
|
3995
|
-
log: (msg) => console.log(
|
|
4237
|
+
log: (msg) => console.log(chalk9.gray(` ${msg}`)),
|
|
3996
4238
|
checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
|
|
3997
4239
|
runHook: async (event, ctx) => {
|
|
3998
4240
|
if (!config.enableHooks || hookConfig.hooks.length === 0) return;
|
|
@@ -4002,7 +4244,7 @@ ${repoMapStr}` : ""
|
|
|
4002
4244
|
});
|
|
4003
4245
|
for (const r of results) {
|
|
4004
4246
|
if (!r.ok) {
|
|
4005
|
-
console.log(
|
|
4247
|
+
console.log(chalk9.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
|
|
4006
4248
|
}
|
|
4007
4249
|
}
|
|
4008
4250
|
}
|
|
@@ -4014,10 +4256,10 @@ ${repoMapStr}` : ""
|
|
|
4014
4256
|
if (session) {
|
|
4015
4257
|
messages.push(...session.messages);
|
|
4016
4258
|
sessionId = session.meta.id;
|
|
4017
|
-
console.log(
|
|
4259
|
+
console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
4018
4260
|
`));
|
|
4019
4261
|
} else {
|
|
4020
|
-
console.log(
|
|
4262
|
+
console.log(chalk9.gray(" No session to resume.\n"));
|
|
4021
4263
|
}
|
|
4022
4264
|
}
|
|
4023
4265
|
const pipedInput = await readStdin();
|
|
@@ -4038,10 +4280,10 @@ Analyze the above input.`;
|
|
|
4038
4280
|
const refContext = formatReferences(references);
|
|
4039
4281
|
const finalPrompt = refContext + cleanInput;
|
|
4040
4282
|
messages.push({ role: "user", content: finalPrompt });
|
|
4041
|
-
console.log(
|
|
4283
|
+
console.log(chalk9.cyan(`> ${oneShot || "(piped input)"}
|
|
4042
4284
|
`));
|
|
4043
4285
|
if (references.length > 0) {
|
|
4044
|
-
console.log(
|
|
4286
|
+
console.log(chalk9.gray(` Injected ${references.length} reference(s)
|
|
4045
4287
|
`));
|
|
4046
4288
|
}
|
|
4047
4289
|
const spinner = ora("Thinking...").start();
|
|
@@ -4060,13 +4302,13 @@ Analyze the above input.`;
|
|
|
4060
4302
|
onToolCall: (name, args) => {
|
|
4061
4303
|
if (spinner.isSpinning) spinner.stop();
|
|
4062
4304
|
const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
|
|
4063
|
-
console.log(
|
|
4305
|
+
console.log(chalk9.gray(`
|
|
4064
4306
|
\u2192 ${name}(${argSummary})`));
|
|
4065
4307
|
},
|
|
4066
4308
|
onToolResult: (_name, result, isError) => {
|
|
4067
4309
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
4068
|
-
const icon = isError ?
|
|
4069
|
-
console.log(
|
|
4310
|
+
const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
|
|
4311
|
+
console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
4070
4312
|
}
|
|
4071
4313
|
})
|
|
4072
4314
|
);
|
|
@@ -4079,7 +4321,7 @@ Analyze the above input.`;
|
|
|
4079
4321
|
model: activeModelId
|
|
4080
4322
|
});
|
|
4081
4323
|
costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
|
|
4082
|
-
console.log(usage.formatLast());
|
|
4324
|
+
console.log(usage.formatLast() + " " + costTracker.formatLastCost());
|
|
4083
4325
|
}
|
|
4084
4326
|
} catch (err) {
|
|
4085
4327
|
spinner.fail(`Error: ${err.message}`);
|
|
@@ -4091,7 +4333,7 @@ Analyze the above input.`;
|
|
|
4091
4333
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
4092
4334
|
if (savedPlan) {
|
|
4093
4335
|
ralphPlan = savedPlan;
|
|
4094
|
-
console.log(
|
|
4336
|
+
console.log(chalk9.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
|
|
4095
4337
|
`));
|
|
4096
4338
|
}
|
|
4097
4339
|
} catch {
|
|
@@ -4103,7 +4345,7 @@ Analyze the above input.`;
|
|
|
4103
4345
|
prompt: theme().prompt("notch> "),
|
|
4104
4346
|
completer: (line) => completer(line)
|
|
4105
4347
|
});
|
|
4106
|
-
console.log(
|
|
4348
|
+
console.log(chalk9.gray(" Type your request, or /help for commands.\n"));
|
|
4107
4349
|
rl.prompt();
|
|
4108
4350
|
rl.on("line", async (line) => {
|
|
4109
4351
|
const input = line.trim();
|
|
@@ -4119,7 +4361,7 @@ Analyze the above input.`;
|
|
|
4119
4361
|
if (messages.length > 0) {
|
|
4120
4362
|
try {
|
|
4121
4363
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
4122
|
-
console.log(
|
|
4364
|
+
console.log(chalk9.gray(` Session saved: ${id}`));
|
|
4123
4365
|
} catch {
|
|
4124
4366
|
}
|
|
4125
4367
|
}
|
|
@@ -4130,12 +4372,12 @@ Analyze the above input.`;
|
|
|
4130
4372
|
}
|
|
4131
4373
|
}
|
|
4132
4374
|
await toolCtx.runHook?.("session-end", {});
|
|
4133
|
-
console.log(
|
|
4375
|
+
console.log(chalk9.gray("\n Goodbye!\n"));
|
|
4134
4376
|
process.exit(0);
|
|
4135
4377
|
}
|
|
4136
4378
|
if (input === "/clear") {
|
|
4137
4379
|
messages.length = 0;
|
|
4138
|
-
console.log(
|
|
4380
|
+
console.log(chalk9.gray(" Conversation cleared.\n"));
|
|
4139
4381
|
rl.prompt();
|
|
4140
4382
|
return;
|
|
4141
4383
|
}
|
|
@@ -4155,8 +4397,8 @@ Analyze the above input.`;
|
|
|
4155
4397
|
newModel = `notch-${newModel}`;
|
|
4156
4398
|
}
|
|
4157
4399
|
if (!isValidModel(newModel)) {
|
|
4158
|
-
console.log(
|
|
4159
|
-
console.log(
|
|
4400
|
+
console.log(chalk9.red(` Unknown model: ${newModel}`));
|
|
4401
|
+
console.log(chalk9.gray(` Available: ${modelChoices}`));
|
|
4160
4402
|
rl.prompt();
|
|
4161
4403
|
return;
|
|
4162
4404
|
}
|
|
@@ -4164,7 +4406,7 @@ Analyze the above input.`;
|
|
|
4164
4406
|
config.models.chat.model = activeModelId;
|
|
4165
4407
|
model = resolveModel(config.models.chat);
|
|
4166
4408
|
const switchedInfo = MODEL_CATALOG[activeModelId];
|
|
4167
|
-
console.log(
|
|
4409
|
+
console.log(chalk9.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
|
|
4168
4410
|
`));
|
|
4169
4411
|
rl.prompt();
|
|
4170
4412
|
return;
|
|
@@ -4177,22 +4419,22 @@ Analyze the above input.`;
|
|
|
4177
4419
|
statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
|
|
4178
4420
|
} else {
|
|
4179
4421
|
statusSpinner.fail(check.error ?? "API unreachable");
|
|
4180
|
-
console.log(
|
|
4422
|
+
console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
|
|
4181
4423
|
}
|
|
4182
4424
|
rl.prompt();
|
|
4183
4425
|
return;
|
|
4184
4426
|
}
|
|
4185
4427
|
if (input === "/undo") {
|
|
4186
4428
|
if (checkpoints.undoCount === 0) {
|
|
4187
|
-
console.log(
|
|
4429
|
+
console.log(chalk9.gray(" Nothing to undo.\n"));
|
|
4188
4430
|
rl.prompt();
|
|
4189
4431
|
return;
|
|
4190
4432
|
}
|
|
4191
4433
|
const undone = await checkpoints.undo();
|
|
4192
4434
|
if (undone) {
|
|
4193
4435
|
const fileCount = undone.files.length;
|
|
4194
|
-
console.log(
|
|
4195
|
-
console.log(
|
|
4436
|
+
console.log(chalk9.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
|
|
4437
|
+
console.log(chalk9.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
|
|
4196
4438
|
`));
|
|
4197
4439
|
}
|
|
4198
4440
|
rl.prompt();
|
|
@@ -4200,7 +4442,7 @@ Analyze the above input.`;
|
|
|
4200
4442
|
}
|
|
4201
4443
|
if (input === "/usage") {
|
|
4202
4444
|
if (usage.turnCount === 0) {
|
|
4203
|
-
console.log(
|
|
4445
|
+
console.log(chalk9.gray(" No usage yet.\n"));
|
|
4204
4446
|
} else {
|
|
4205
4447
|
console.log(usage.formatSession());
|
|
4206
4448
|
const currentTokens = estimateTokens(messages);
|
|
@@ -4213,7 +4455,7 @@ Analyze the above input.`;
|
|
|
4213
4455
|
}
|
|
4214
4456
|
if (input === "/cost") {
|
|
4215
4457
|
if (costTracker.totalCost === 0) {
|
|
4216
|
-
console.log(
|
|
4458
|
+
console.log(chalk9.gray(" No cost data yet.\n"));
|
|
4217
4459
|
} else {
|
|
4218
4460
|
console.log(costTracker.formatTotal());
|
|
4219
4461
|
console.log(costTracker.formatByModel());
|
|
@@ -4228,7 +4470,7 @@ Analyze the above input.`;
|
|
|
4228
4470
|
const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
|
|
4229
4471
|
messages.length = 0;
|
|
4230
4472
|
messages.push(...compressed);
|
|
4231
|
-
console.log(
|
|
4473
|
+
console.log(chalk9.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
|
|
4232
4474
|
console.log("");
|
|
4233
4475
|
rl.prompt();
|
|
4234
4476
|
return;
|
|
@@ -4236,10 +4478,10 @@ Analyze the above input.`;
|
|
|
4236
4478
|
if (input === "/diff") {
|
|
4237
4479
|
const diffs = checkpoints.allDiffs();
|
|
4238
4480
|
if (diffs.length === 0) {
|
|
4239
|
-
console.log(
|
|
4481
|
+
console.log(chalk9.gray(" No file changes this session.\n"));
|
|
4240
4482
|
} else {
|
|
4241
4483
|
for (const df of diffs) {
|
|
4242
|
-
console.log(
|
|
4484
|
+
console.log(chalk9.cyan(` ${df.path}:`));
|
|
4243
4485
|
console.log(unifiedDiff(df.before, df.after, df.path));
|
|
4244
4486
|
console.log("");
|
|
4245
4487
|
}
|
|
@@ -4255,10 +4497,10 @@ Analyze the above input.`;
|
|
|
4255
4497
|
projectRoot: config.projectRoot,
|
|
4256
4498
|
outputPath: exportPath
|
|
4257
4499
|
});
|
|
4258
|
-
console.log(
|
|
4500
|
+
console.log(chalk9.green(` Exported to ${ePath}
|
|
4259
4501
|
`));
|
|
4260
4502
|
} catch (err) {
|
|
4261
|
-
console.log(
|
|
4503
|
+
console.log(chalk9.red(` Export failed: ${err.message}
|
|
4262
4504
|
`));
|
|
4263
4505
|
}
|
|
4264
4506
|
rl.prompt();
|
|
@@ -4268,10 +4510,10 @@ Analyze the above input.`;
|
|
|
4268
4510
|
try {
|
|
4269
4511
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
4270
4512
|
sessionId = id;
|
|
4271
|
-
console.log(
|
|
4513
|
+
console.log(chalk9.green(` Session saved: ${id}
|
|
4272
4514
|
`));
|
|
4273
4515
|
} catch (err) {
|
|
4274
|
-
console.log(
|
|
4516
|
+
console.log(chalk9.red(` Save failed: ${err.message}
|
|
4275
4517
|
`));
|
|
4276
4518
|
}
|
|
4277
4519
|
rl.prompt();
|
|
@@ -4281,16 +4523,16 @@ Analyze the above input.`;
|
|
|
4281
4523
|
try {
|
|
4282
4524
|
const sessions = await listSessions(config.projectRoot);
|
|
4283
4525
|
if (sessions.length === 0) {
|
|
4284
|
-
console.log(
|
|
4526
|
+
console.log(chalk9.gray(" No saved sessions.\n"));
|
|
4285
4527
|
} else {
|
|
4286
|
-
console.log(
|
|
4528
|
+
console.log(chalk9.gray("\n Saved sessions:\n"));
|
|
4287
4529
|
for (const s of sessions.slice(0, 10)) {
|
|
4288
|
-
console.log(
|
|
4530
|
+
console.log(chalk9.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
|
|
4289
4531
|
}
|
|
4290
4532
|
console.log("");
|
|
4291
4533
|
}
|
|
4292
4534
|
} catch (err) {
|
|
4293
|
-
console.log(
|
|
4535
|
+
console.log(chalk9.red(` Error listing sessions: ${err.message}
|
|
4294
4536
|
`));
|
|
4295
4537
|
}
|
|
4296
4538
|
rl.prompt();
|
|
@@ -4308,18 +4550,18 @@ Analyze the above input.`;
|
|
|
4308
4550
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
4309
4551
|
if (savedPlan) {
|
|
4310
4552
|
ralphPlan = savedPlan;
|
|
4311
|
-
console.log(
|
|
4553
|
+
console.log(chalk9.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
|
|
4312
4554
|
`));
|
|
4313
4555
|
}
|
|
4314
4556
|
} catch {
|
|
4315
4557
|
}
|
|
4316
|
-
console.log(
|
|
4558
|
+
console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
4317
4559
|
`));
|
|
4318
4560
|
} else {
|
|
4319
|
-
console.log(
|
|
4561
|
+
console.log(chalk9.gray(" No session found.\n"));
|
|
4320
4562
|
}
|
|
4321
4563
|
} catch (err) {
|
|
4322
|
-
console.log(
|
|
4564
|
+
console.log(chalk9.red(` Resume failed: ${err.message}
|
|
4323
4565
|
`));
|
|
4324
4566
|
}
|
|
4325
4567
|
rl.prompt();
|
|
@@ -4328,7 +4570,7 @@ Analyze the above input.`;
|
|
|
4328
4570
|
if (input.startsWith("/search ")) {
|
|
4329
4571
|
const query = input.replace("/search ", "").trim().toLowerCase();
|
|
4330
4572
|
if (!query) {
|
|
4331
|
-
console.log(
|
|
4573
|
+
console.log(chalk9.gray(" Usage: /search <query>\n"));
|
|
4332
4574
|
rl.prompt();
|
|
4333
4575
|
return;
|
|
4334
4576
|
}
|
|
@@ -4345,14 +4587,14 @@ Analyze the above input.`;
|
|
|
4345
4587
|
}
|
|
4346
4588
|
}
|
|
4347
4589
|
if (matches.length === 0) {
|
|
4348
|
-
console.log(
|
|
4590
|
+
console.log(chalk9.gray(` No matches for "${query}"
|
|
4349
4591
|
`));
|
|
4350
4592
|
} else {
|
|
4351
|
-
console.log(
|
|
4593
|
+
console.log(chalk9.gray(`
|
|
4352
4594
|
${matches.length} match(es) for "${query}":
|
|
4353
4595
|
`));
|
|
4354
4596
|
for (const m of matches.slice(0, 10)) {
|
|
4355
|
-
console.log(
|
|
4597
|
+
console.log(chalk9.gray(` [${m.index}] ${m.role}: ${m.preview}`));
|
|
4356
4598
|
}
|
|
4357
4599
|
console.log("");
|
|
4358
4600
|
}
|
|
@@ -4366,7 +4608,7 @@ Analyze the above input.`;
|
|
|
4366
4608
|
activePlan = await generatePlan(task, model, systemPrompt);
|
|
4367
4609
|
planSpinner.succeed("Plan generated");
|
|
4368
4610
|
console.log(formatPlan(activePlan));
|
|
4369
|
-
console.log(
|
|
4611
|
+
console.log(chalk9.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
|
|
4370
4612
|
} catch (err) {
|
|
4371
4613
|
planSpinner.fail(`Plan failed: ${err.message}`);
|
|
4372
4614
|
}
|
|
@@ -4375,11 +4617,11 @@ Analyze the above input.`;
|
|
|
4375
4617
|
}
|
|
4376
4618
|
if (input === "/plan approve") {
|
|
4377
4619
|
if (!activePlan) {
|
|
4378
|
-
console.log(
|
|
4620
|
+
console.log(chalk9.gray(" No active plan. Use /plan <task> to create one.\n"));
|
|
4379
4621
|
rl.prompt();
|
|
4380
4622
|
return;
|
|
4381
4623
|
}
|
|
4382
|
-
console.log(
|
|
4624
|
+
console.log(chalk9.green(" Executing plan...\n"));
|
|
4383
4625
|
while (!isPlanComplete(activePlan)) {
|
|
4384
4626
|
const stepPrompt = currentStepPrompt(activePlan);
|
|
4385
4627
|
messages.push({ role: "user", content: stepPrompt });
|
|
@@ -4397,10 +4639,10 @@ Analyze the above input.`;
|
|
|
4397
4639
|
},
|
|
4398
4640
|
onToolCall: (name) => {
|
|
4399
4641
|
if (planStepSpinner.isSpinning) planStepSpinner.stop();
|
|
4400
|
-
console.log(
|
|
4642
|
+
console.log(chalk9.gray(` \u2192 ${name}`));
|
|
4401
4643
|
},
|
|
4402
4644
|
onToolResult: (_name, _result, isError) => {
|
|
4403
|
-
console.log(isError ?
|
|
4645
|
+
console.log(isError ? chalk9.red(" \u2717") : chalk9.green(" \u2713"));
|
|
4404
4646
|
}
|
|
4405
4647
|
});
|
|
4406
4648
|
console.log("\n");
|
|
@@ -4413,7 +4655,7 @@ Analyze the above input.`;
|
|
|
4413
4655
|
}
|
|
4414
4656
|
}
|
|
4415
4657
|
if (activePlan && isPlanComplete(activePlan)) {
|
|
4416
|
-
console.log(
|
|
4658
|
+
console.log(chalk9.green(" Plan completed!\n"));
|
|
4417
4659
|
}
|
|
4418
4660
|
activePlan = null;
|
|
4419
4661
|
rl.prompt();
|
|
@@ -4421,26 +4663,26 @@ Analyze the above input.`;
|
|
|
4421
4663
|
}
|
|
4422
4664
|
if (input === "/plan edit") {
|
|
4423
4665
|
if (!activePlan) {
|
|
4424
|
-
console.log(
|
|
4666
|
+
console.log(chalk9.gray(" No active plan to edit.\n"));
|
|
4425
4667
|
rl.prompt();
|
|
4426
4668
|
return;
|
|
4427
4669
|
}
|
|
4428
4670
|
console.log(formatPlan(activePlan));
|
|
4429
|
-
console.log(
|
|
4430
|
-
console.log(
|
|
4671
|
+
console.log(chalk9.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
|
|
4672
|
+
console.log(chalk9.gray(" or /plan approve to proceed with the current plan.\n"));
|
|
4431
4673
|
rl.prompt();
|
|
4432
4674
|
return;
|
|
4433
4675
|
}
|
|
4434
4676
|
if (input === "/plan cancel") {
|
|
4435
4677
|
activePlan = null;
|
|
4436
|
-
console.log(
|
|
4678
|
+
console.log(chalk9.gray(" Plan discarded.\n"));
|
|
4437
4679
|
rl.prompt();
|
|
4438
4680
|
return;
|
|
4439
4681
|
}
|
|
4440
4682
|
if (input.startsWith("/agent ")) {
|
|
4441
4683
|
const task = input.replace("/agent ", "").trim();
|
|
4442
4684
|
const agentId = nextSubagentId();
|
|
4443
|
-
console.log(
|
|
4685
|
+
console.log(chalk9.cyan(` Spawning subagent #${agentId}: ${task}
|
|
4444
4686
|
`));
|
|
4445
4687
|
spawnSubagent({
|
|
4446
4688
|
id: agentId,
|
|
@@ -4450,14 +4692,14 @@ Analyze the above input.`;
|
|
|
4450
4692
|
toolContext: toolCtx,
|
|
4451
4693
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
4452
4694
|
onComplete: (result) => {
|
|
4453
|
-
console.log(
|
|
4695
|
+
console.log(chalk9.green(`
|
|
4454
4696
|
Subagent #${agentId} finished:`));
|
|
4455
|
-
console.log(
|
|
4697
|
+
console.log(chalk9.gray(` ${result.slice(0, 200)}
|
|
4456
4698
|
`));
|
|
4457
4699
|
rl.prompt();
|
|
4458
4700
|
},
|
|
4459
4701
|
onError: (err) => {
|
|
4460
|
-
console.log(
|
|
4702
|
+
console.log(chalk9.red(`
|
|
4461
4703
|
Subagent #${agentId} failed: ${err}
|
|
4462
4704
|
`));
|
|
4463
4705
|
rl.prompt();
|
|
@@ -4470,18 +4712,18 @@ Analyze the above input.`;
|
|
|
4470
4712
|
try {
|
|
4471
4713
|
const memories = await loadMemories(config.projectRoot);
|
|
4472
4714
|
if (memories.length === 0) {
|
|
4473
|
-
console.log(
|
|
4715
|
+
console.log(chalk9.gray(" No saved memories.\n"));
|
|
4474
4716
|
} else {
|
|
4475
|
-
console.log(
|
|
4717
|
+
console.log(chalk9.gray(`
|
|
4476
4718
|
${memories.length} saved memories:
|
|
4477
4719
|
`));
|
|
4478
4720
|
for (const m of memories) {
|
|
4479
|
-
console.log(
|
|
4721
|
+
console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
|
|
4480
4722
|
}
|
|
4481
4723
|
console.log("");
|
|
4482
4724
|
}
|
|
4483
4725
|
} catch (err) {
|
|
4484
|
-
console.log(
|
|
4726
|
+
console.log(chalk9.red(` Error: ${err.message}
|
|
4485
4727
|
`));
|
|
4486
4728
|
}
|
|
4487
4729
|
rl.prompt();
|
|
@@ -4492,16 +4734,16 @@ Analyze the above input.`;
|
|
|
4492
4734
|
try {
|
|
4493
4735
|
const results = await searchMemories(config.projectRoot, query);
|
|
4494
4736
|
if (results.length === 0) {
|
|
4495
|
-
console.log(
|
|
4737
|
+
console.log(chalk9.gray(` No memories matching "${query}"
|
|
4496
4738
|
`));
|
|
4497
4739
|
} else {
|
|
4498
4740
|
for (const m of results) {
|
|
4499
|
-
console.log(
|
|
4741
|
+
console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
|
|
4500
4742
|
}
|
|
4501
4743
|
console.log("");
|
|
4502
4744
|
}
|
|
4503
4745
|
} catch (err) {
|
|
4504
|
-
console.log(
|
|
4746
|
+
console.log(chalk9.red(` Error: ${err.message}
|
|
4505
4747
|
`));
|
|
4506
4748
|
}
|
|
4507
4749
|
rl.prompt();
|
|
@@ -4513,10 +4755,10 @@ Analyze the above input.`;
|
|
|
4513
4755
|
for (const m of memories) {
|
|
4514
4756
|
await deleteMemory(config.projectRoot, m.id);
|
|
4515
4757
|
}
|
|
4516
|
-
console.log(
|
|
4758
|
+
console.log(chalk9.yellow(` Cleared ${memories.length} memories.
|
|
4517
4759
|
`));
|
|
4518
4760
|
} catch (err) {
|
|
4519
|
-
console.log(
|
|
4761
|
+
console.log(chalk9.red(` Error: ${err.message}
|
|
4520
4762
|
`));
|
|
4521
4763
|
}
|
|
4522
4764
|
rl.prompt();
|
|
@@ -4544,27 +4786,27 @@ Analyze the above input.`;
|
|
|
4544
4786
|
}
|
|
4545
4787
|
if (input === "/ralph run") {
|
|
4546
4788
|
if (!ralphPlan) {
|
|
4547
|
-
console.log(
|
|
4789
|
+
console.log(chalk9.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
|
|
4548
4790
|
rl.prompt();
|
|
4549
4791
|
return;
|
|
4550
4792
|
}
|
|
4551
|
-
console.log(
|
|
4793
|
+
console.log(chalk9.green(" Ralph is running...\n"));
|
|
4552
4794
|
try {
|
|
4553
4795
|
ralphPlan = await runRalphLoop(ralphPlan, {
|
|
4554
4796
|
model,
|
|
4555
4797
|
systemPrompt,
|
|
4556
4798
|
toolContext: toolCtx,
|
|
4557
4799
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
4558
|
-
onTaskStart: (task) => console.log(
|
|
4559
|
-
onTaskComplete: (task) => console.log(
|
|
4800
|
+
onTaskStart: (task) => console.log(chalk9.cyan(` \u25B6 Task: ${task.description}`)),
|
|
4801
|
+
onTaskComplete: (task) => console.log(chalk9.green(` \u2713 Done: ${task.description}
|
|
4560
4802
|
`)),
|
|
4561
|
-
onTaskFail: (task, err) => console.log(
|
|
4803
|
+
onTaskFail: (task, err) => console.log(chalk9.red(` \u2717 Failed: ${task.description} (${err})
|
|
4562
4804
|
`))
|
|
4563
4805
|
});
|
|
4564
4806
|
await savePlan(config.projectRoot, ralphPlan);
|
|
4565
4807
|
console.log(formatRalphStatus(ralphPlan));
|
|
4566
4808
|
} catch (err) {
|
|
4567
|
-
console.log(
|
|
4809
|
+
console.log(chalk9.red(` Ralph error: ${err.message}
|
|
4568
4810
|
`));
|
|
4569
4811
|
}
|
|
4570
4812
|
rl.prompt();
|
|
@@ -4572,7 +4814,7 @@ Analyze the above input.`;
|
|
|
4572
4814
|
}
|
|
4573
4815
|
if (input === "/ralph status") {
|
|
4574
4816
|
if (!ralphPlan) {
|
|
4575
|
-
console.log(
|
|
4817
|
+
console.log(chalk9.gray(" No Ralph plan active.\n"));
|
|
4576
4818
|
} else {
|
|
4577
4819
|
console.log(formatRalphStatus(ralphPlan));
|
|
4578
4820
|
}
|
|
@@ -4583,26 +4825,26 @@ Analyze the above input.`;
|
|
|
4583
4825
|
ralphPlan = null;
|
|
4584
4826
|
await deletePlan(config.projectRoot).catch(() => {
|
|
4585
4827
|
});
|
|
4586
|
-
console.log(
|
|
4828
|
+
console.log(chalk9.gray(" Ralph plan cleared.\n"));
|
|
4587
4829
|
rl.prompt();
|
|
4588
4830
|
return;
|
|
4589
4831
|
}
|
|
4590
4832
|
if (input === "/branch") {
|
|
4591
4833
|
const branchId = `branch-${branches.size + 1}`;
|
|
4592
4834
|
branches.set(branchId, [...messages]);
|
|
4593
|
-
console.log(
|
|
4835
|
+
console.log(chalk9.green(` Forked conversation as "${branchId}" (${messages.length} messages)
|
|
4594
4836
|
`));
|
|
4595
4837
|
rl.prompt();
|
|
4596
4838
|
return;
|
|
4597
4839
|
}
|
|
4598
4840
|
if (input === "/branches") {
|
|
4599
4841
|
if (branches.size === 0) {
|
|
4600
|
-
console.log(
|
|
4842
|
+
console.log(chalk9.gray(" No conversation branches. Use /branch to fork.\n"));
|
|
4601
4843
|
} else {
|
|
4602
|
-
console.log(
|
|
4844
|
+
console.log(chalk9.gray("\n Branches:\n"));
|
|
4603
4845
|
for (const [name, msgs] of branches) {
|
|
4604
|
-
const marker = name === currentBranch ?
|
|
4605
|
-
console.log(
|
|
4846
|
+
const marker = name === currentBranch ? chalk9.green(" \u25CF") : " ";
|
|
4847
|
+
console.log(chalk9.gray(` ${marker} ${name} (${msgs.length} messages)`));
|
|
4606
4848
|
}
|
|
4607
4849
|
console.log("");
|
|
4608
4850
|
}
|
|
@@ -4639,8 +4881,8 @@ Analyze the above input.`;
|
|
|
4639
4881
|
return;
|
|
4640
4882
|
}
|
|
4641
4883
|
if (input.startsWith("/")) {
|
|
4642
|
-
console.log(
|
|
4643
|
-
console.log(
|
|
4884
|
+
console.log(chalk9.red(` Unknown command: ${input}`));
|
|
4885
|
+
console.log(chalk9.gray(" Type /help for available commands.\n"));
|
|
4644
4886
|
rl.prompt();
|
|
4645
4887
|
return;
|
|
4646
4888
|
}
|
|
@@ -4648,7 +4890,7 @@ Analyze the above input.`;
|
|
|
4648
4890
|
const refContext = formatReferences(references);
|
|
4649
4891
|
const finalPrompt = refContext + cleanInput;
|
|
4650
4892
|
if (references.length > 0) {
|
|
4651
|
-
console.log(
|
|
4893
|
+
console.log(chalk9.gray(` Injected ${references.length} reference(s)`));
|
|
4652
4894
|
}
|
|
4653
4895
|
messages.push({ role: "user", content: finalPrompt });
|
|
4654
4896
|
const spinner = ora("Thinking...").start();
|
|
@@ -4674,16 +4916,16 @@ Analyze the above input.`;
|
|
|
4674
4916
|
const val = String(v);
|
|
4675
4917
|
return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
|
|
4676
4918
|
}).join(", ");
|
|
4677
|
-
console.log(
|
|
4919
|
+
console.log(chalk9.gray(`
|
|
4678
4920
|
\u2192 ${name}(${argSummary})`));
|
|
4679
4921
|
},
|
|
4680
4922
|
onToolResult: (_name, result, isError) => {
|
|
4681
4923
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
4682
|
-
const icon = isError ?
|
|
4683
|
-
console.log(
|
|
4924
|
+
const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
|
|
4925
|
+
console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
4684
4926
|
},
|
|
4685
4927
|
onCompress: () => {
|
|
4686
|
-
console.log(
|
|
4928
|
+
console.log(chalk9.yellow("\n [Context compressed to fit window]\n"));
|
|
4687
4929
|
}
|
|
4688
4930
|
})
|
|
4689
4931
|
);
|
|
@@ -4697,7 +4939,7 @@ Analyze the above input.`;
|
|
|
4697
4939
|
model: activeModelId
|
|
4698
4940
|
});
|
|
4699
4941
|
costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
|
|
4700
|
-
console.log(usage.formatLast());
|
|
4942
|
+
console.log(usage.formatLast() + " " + costTracker.formatLastCost());
|
|
4701
4943
|
const currentTokens = estimateTokens(messages);
|
|
4702
4944
|
const ctxWindow = MODEL_CATALOG[activeModelId].contextWindow;
|
|
4703
4945
|
if (currentTokens > ctxWindow * 0.5) {
|
|
@@ -4716,7 +4958,7 @@ Analyze the above input.`;
|
|
|
4716
4958
|
type: "auto",
|
|
4717
4959
|
content: memMatch[1]
|
|
4718
4960
|
});
|
|
4719
|
-
console.log(
|
|
4961
|
+
console.log(chalk9.gray(" (Saved to memory)\n"));
|
|
4720
4962
|
} catch {
|
|
4721
4963
|
}
|
|
4722
4964
|
}
|
|
@@ -4726,13 +4968,13 @@ Analyze the above input.`;
|
|
|
4726
4968
|
checkpoints.discard();
|
|
4727
4969
|
const msg = err.message?.toLowerCase() ?? "";
|
|
4728
4970
|
if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
|
|
4729
|
-
console.log(
|
|
4971
|
+
console.log(chalk9.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
|
|
4730
4972
|
} else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
|
|
4731
|
-
console.log(
|
|
4973
|
+
console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
|
|
4732
4974
|
} else if (msg.includes("429") || msg.includes("rate limit")) {
|
|
4733
|
-
console.log(
|
|
4975
|
+
console.log(chalk9.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
|
|
4734
4976
|
} else {
|
|
4735
|
-
console.log(
|
|
4977
|
+
console.log(chalk9.gray(" (The conversation history is preserved. Try again.)\n"));
|
|
4736
4978
|
}
|
|
4737
4979
|
}
|
|
4738
4980
|
rl.prompt();
|
|
@@ -4750,13 +4992,13 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
4750
4992
|
cwd: config.projectRoot,
|
|
4751
4993
|
requireConfirm: false,
|
|
4752
4994
|
confirm: async () => true,
|
|
4753
|
-
log: (msg) => console.log(
|
|
4995
|
+
log: (msg) => console.log(chalk9.gray(` ${msg}`))
|
|
4754
4996
|
};
|
|
4755
4997
|
const subcommand = args[0];
|
|
4756
4998
|
if (subcommand === "plan") {
|
|
4757
4999
|
const goal = args.slice(1).join(" ");
|
|
4758
5000
|
if (!goal) {
|
|
4759
|
-
console.error(
|
|
5001
|
+
console.error(chalk9.red(" Usage: notch ralph plan <goal>"));
|
|
4760
5002
|
process.exit(1);
|
|
4761
5003
|
}
|
|
4762
5004
|
const spinner = ora("Ralph is planning...").start();
|
|
@@ -4767,7 +5009,7 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
4767
5009
|
} else if (subcommand === "run") {
|
|
4768
5010
|
let plan = await loadPlan(config.projectRoot);
|
|
4769
5011
|
if (!plan) {
|
|
4770
|
-
console.error(
|
|
5012
|
+
console.error(chalk9.red(" No plan found. Run: notch ralph plan <goal>"));
|
|
4771
5013
|
process.exit(1);
|
|
4772
5014
|
}
|
|
4773
5015
|
plan = await runRalphLoop(plan, {
|
|
@@ -4775,27 +5017,27 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
4775
5017
|
systemPrompt,
|
|
4776
5018
|
toolContext: toolCtx,
|
|
4777
5019
|
contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
|
|
4778
|
-
onTaskStart: (t) => console.log(
|
|
4779
|
-
onTaskComplete: (t) => console.log(
|
|
4780
|
-
onTaskFail: (t, e) => console.log(
|
|
5020
|
+
onTaskStart: (t) => console.log(chalk9.cyan(` \u25B6 ${t.description}`)),
|
|
5021
|
+
onTaskComplete: (t) => console.log(chalk9.green(` \u2713 ${t.description}`)),
|
|
5022
|
+
onTaskFail: (t, e) => console.log(chalk9.red(` \u2717 ${t.description}: ${e}`))
|
|
4781
5023
|
});
|
|
4782
5024
|
await savePlan(config.projectRoot, plan);
|
|
4783
5025
|
console.log(formatRalphStatus(plan));
|
|
4784
5026
|
} else if (subcommand === "status") {
|
|
4785
5027
|
const plan = await loadPlan(config.projectRoot);
|
|
4786
5028
|
if (!plan) {
|
|
4787
|
-
console.log(
|
|
5029
|
+
console.log(chalk9.gray(" No Ralph plan found."));
|
|
4788
5030
|
} else {
|
|
4789
5031
|
console.log(formatRalphStatus(plan));
|
|
4790
5032
|
}
|
|
4791
5033
|
} else {
|
|
4792
|
-
console.error(
|
|
4793
|
-
console.error(
|
|
5034
|
+
console.error(chalk9.red(` Unknown: notch ralph ${subcommand}`));
|
|
5035
|
+
console.error(chalk9.gray(" Usage: notch ralph <plan|run|status>"));
|
|
4794
5036
|
process.exit(1);
|
|
4795
5037
|
}
|
|
4796
5038
|
}
|
|
4797
5039
|
main().catch((err) => {
|
|
4798
|
-
console.error(
|
|
5040
|
+
console.error(chalk9.red(`
|
|
4799
5041
|
Fatal: ${err.message}
|
|
4800
5042
|
`));
|
|
4801
5043
|
process.exit(1);
|