@freesyntax/notch-cli 0.4.2 → 0.4.4
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";
|
|
@@ -3749,7 +3991,7 @@ function printModelTable(activeModel) {
|
|
|
3749
3991
|
`));
|
|
3750
3992
|
}
|
|
3751
3993
|
function printHelp() {
|
|
3752
|
-
console.log(
|
|
3994
|
+
console.log(chalk9.gray(`
|
|
3753
3995
|
Commands:
|
|
3754
3996
|
/model \u2014 Show available models
|
|
3755
3997
|
/model <name> \u2014 Switch model (e.g., /model pyre)
|
|
@@ -3822,13 +4064,13 @@ async function main() {
|
|
|
3822
4064
|
try {
|
|
3823
4065
|
spinner.stop();
|
|
3824
4066
|
const creds = await login();
|
|
3825
|
-
console.log(
|
|
4067
|
+
console.log(chalk9.green(`
|
|
3826
4068
|
\u2713 Signed in as ${creds.email}`));
|
|
3827
|
-
console.log(
|
|
4069
|
+
console.log(chalk9.gray(` API key stored in ${(await import("./auth-JQX6MHJG.js")).getCredentialsPath()}
|
|
3828
4070
|
`));
|
|
3829
4071
|
} catch (err) {
|
|
3830
4072
|
spinner.stop();
|
|
3831
|
-
console.error(
|
|
4073
|
+
console.error(chalk9.red(`
|
|
3832
4074
|
Login failed: ${err.message}
|
|
3833
4075
|
`));
|
|
3834
4076
|
process.exit(1);
|
|
@@ -3838,10 +4080,10 @@ async function main() {
|
|
|
3838
4080
|
if (promptArgs[0] === "logout") {
|
|
3839
4081
|
const creds = await loadCredentials();
|
|
3840
4082
|
if (!creds) {
|
|
3841
|
-
console.log(
|
|
4083
|
+
console.log(chalk9.gray("\n Not signed in.\n"));
|
|
3842
4084
|
} else {
|
|
3843
4085
|
await clearCredentials();
|
|
3844
|
-
console.log(
|
|
4086
|
+
console.log(chalk9.green(`
|
|
3845
4087
|
\u2713 Signed out (${creds.email})
|
|
3846
4088
|
`));
|
|
3847
4089
|
}
|
|
@@ -3850,13 +4092,13 @@ async function main() {
|
|
|
3850
4092
|
if (promptArgs[0] === "whoami") {
|
|
3851
4093
|
const creds = await loadCredentials();
|
|
3852
4094
|
if (!creds) {
|
|
3853
|
-
console.log(
|
|
4095
|
+
console.log(chalk9.gray("\n Not signed in. Run: notch login\n"));
|
|
3854
4096
|
} else {
|
|
3855
4097
|
const keyPreview = `${creds.token.slice(0, 12)}...`;
|
|
3856
|
-
console.log(
|
|
3857
|
-
Signed in as ${
|
|
3858
|
-
console.log(
|
|
3859
|
-
console.log(
|
|
4098
|
+
console.log(chalk9.gray(`
|
|
4099
|
+
Signed in as ${chalk9.white(creds.email)}`));
|
|
4100
|
+
console.log(chalk9.gray(` Key: ${keyPreview}`));
|
|
4101
|
+
console.log(chalk9.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
|
|
3860
4102
|
`));
|
|
3861
4103
|
}
|
|
3862
4104
|
return;
|
|
@@ -3880,8 +4122,8 @@ async function main() {
|
|
|
3880
4122
|
const config = await loadConfig(configOverrides);
|
|
3881
4123
|
if (opts.model) {
|
|
3882
4124
|
if (!isValidModel(opts.model)) {
|
|
3883
|
-
console.error(
|
|
3884
|
-
console.error(
|
|
4125
|
+
console.error(chalk9.red(` Unknown model: ${opts.model}`));
|
|
4126
|
+
console.error(chalk9.gray(` Available: ${modelChoices}`));
|
|
3885
4127
|
process.exit(1);
|
|
3886
4128
|
}
|
|
3887
4129
|
config.models.chat.model = opts.model;
|
|
@@ -3897,9 +4139,7 @@ async function main() {
|
|
|
3897
4139
|
model = resolveModel(config.models.chat);
|
|
3898
4140
|
} catch (err) {
|
|
3899
4141
|
if (err instanceof MissingApiKeyError) {
|
|
3900
|
-
|
|
3901
|
-
console.log(" \x1B[1m\x1B[36m\u26A1 Welcome to Notch CLI\x1B[0m");
|
|
3902
|
-
console.log("");
|
|
4142
|
+
printWordmark(VERSION);
|
|
3903
4143
|
console.log(" To get started, you need a Notch API key.");
|
|
3904
4144
|
console.log("");
|
|
3905
4145
|
console.log(" \x1B[1mOption 1:\x1B[0m Log in via browser (recommended)");
|
|
@@ -3911,7 +4151,7 @@ async function main() {
|
|
|
3911
4151
|
console.log(" \x1B[1mOption 3:\x1B[0m Pass it inline");
|
|
3912
4152
|
console.log(" \x1B[33m$ notch --api-key your-key-here\x1B[0m");
|
|
3913
4153
|
console.log("");
|
|
3914
|
-
console.log(" Get your key at: \x1B[4mhttps://freesyntax.
|
|
4154
|
+
console.log(" Get your key at: \x1B[4mhttps://freesyntax.dev/settings\x1B[0m");
|
|
3915
4155
|
console.log("");
|
|
3916
4156
|
process.exit(0);
|
|
3917
4157
|
}
|
|
@@ -3923,11 +4163,11 @@ async function main() {
|
|
|
3923
4163
|
if (msg) console.log(msg);
|
|
3924
4164
|
});
|
|
3925
4165
|
const hookTrustPrompt = async (commands) => {
|
|
3926
|
-
console.warn(
|
|
3927
|
-
commands.forEach((cmd) => console.warn(
|
|
4166
|
+
console.warn(chalk9.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
|
|
4167
|
+
commands.forEach((cmd) => console.warn(chalk9.gray(` \u2022 ${cmd}`)));
|
|
3928
4168
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3929
4169
|
return new Promise((resolve2) => {
|
|
3930
|
-
rl2.question(
|
|
4170
|
+
rl2.question(chalk9.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
|
|
3931
4171
|
rl2.close();
|
|
3932
4172
|
resolve2(answer.trim().toLowerCase() === "y");
|
|
3933
4173
|
});
|
|
@@ -3973,9 +4213,9 @@ ${repoMapStr}` : ""
|
|
|
3973
4213
|
const client = new MCPClient(mcpConfig, name);
|
|
3974
4214
|
await client.connect();
|
|
3975
4215
|
mcpClients.push(client);
|
|
3976
|
-
console.log(
|
|
4216
|
+
console.log(chalk9.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
|
|
3977
4217
|
} catch (err) {
|
|
3978
|
-
console.log(
|
|
4218
|
+
console.log(chalk9.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
|
|
3979
4219
|
}
|
|
3980
4220
|
}
|
|
3981
4221
|
} catch {
|
|
@@ -3992,7 +4232,7 @@ ${repoMapStr}` : ""
|
|
|
3992
4232
|
});
|
|
3993
4233
|
});
|
|
3994
4234
|
},
|
|
3995
|
-
log: (msg) => console.log(
|
|
4235
|
+
log: (msg) => console.log(chalk9.gray(` ${msg}`)),
|
|
3996
4236
|
checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
|
|
3997
4237
|
runHook: async (event, ctx) => {
|
|
3998
4238
|
if (!config.enableHooks || hookConfig.hooks.length === 0) return;
|
|
@@ -4002,7 +4242,7 @@ ${repoMapStr}` : ""
|
|
|
4002
4242
|
});
|
|
4003
4243
|
for (const r of results) {
|
|
4004
4244
|
if (!r.ok) {
|
|
4005
|
-
console.log(
|
|
4245
|
+
console.log(chalk9.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
|
|
4006
4246
|
}
|
|
4007
4247
|
}
|
|
4008
4248
|
}
|
|
@@ -4014,10 +4254,10 @@ ${repoMapStr}` : ""
|
|
|
4014
4254
|
if (session) {
|
|
4015
4255
|
messages.push(...session.messages);
|
|
4016
4256
|
sessionId = session.meta.id;
|
|
4017
|
-
console.log(
|
|
4257
|
+
console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
4018
4258
|
`));
|
|
4019
4259
|
} else {
|
|
4020
|
-
console.log(
|
|
4260
|
+
console.log(chalk9.gray(" No session to resume.\n"));
|
|
4021
4261
|
}
|
|
4022
4262
|
}
|
|
4023
4263
|
const pipedInput = await readStdin();
|
|
@@ -4038,10 +4278,10 @@ Analyze the above input.`;
|
|
|
4038
4278
|
const refContext = formatReferences(references);
|
|
4039
4279
|
const finalPrompt = refContext + cleanInput;
|
|
4040
4280
|
messages.push({ role: "user", content: finalPrompt });
|
|
4041
|
-
console.log(
|
|
4281
|
+
console.log(chalk9.cyan(`> ${oneShot || "(piped input)"}
|
|
4042
4282
|
`));
|
|
4043
4283
|
if (references.length > 0) {
|
|
4044
|
-
console.log(
|
|
4284
|
+
console.log(chalk9.gray(` Injected ${references.length} reference(s)
|
|
4045
4285
|
`));
|
|
4046
4286
|
}
|
|
4047
4287
|
const spinner = ora("Thinking...").start();
|
|
@@ -4060,13 +4300,13 @@ Analyze the above input.`;
|
|
|
4060
4300
|
onToolCall: (name, args) => {
|
|
4061
4301
|
if (spinner.isSpinning) spinner.stop();
|
|
4062
4302
|
const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
|
|
4063
|
-
console.log(
|
|
4303
|
+
console.log(chalk9.gray(`
|
|
4064
4304
|
\u2192 ${name}(${argSummary})`));
|
|
4065
4305
|
},
|
|
4066
4306
|
onToolResult: (_name, result, isError) => {
|
|
4067
4307
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
4068
|
-
const icon = isError ?
|
|
4069
|
-
console.log(
|
|
4308
|
+
const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
|
|
4309
|
+
console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
4070
4310
|
}
|
|
4071
4311
|
})
|
|
4072
4312
|
);
|
|
@@ -4079,7 +4319,7 @@ Analyze the above input.`;
|
|
|
4079
4319
|
model: activeModelId
|
|
4080
4320
|
});
|
|
4081
4321
|
costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
|
|
4082
|
-
console.log(usage.formatLast());
|
|
4322
|
+
console.log(usage.formatLast() + " " + costTracker.formatLastCost());
|
|
4083
4323
|
}
|
|
4084
4324
|
} catch (err) {
|
|
4085
4325
|
spinner.fail(`Error: ${err.message}`);
|
|
@@ -4091,7 +4331,7 @@ Analyze the above input.`;
|
|
|
4091
4331
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
4092
4332
|
if (savedPlan) {
|
|
4093
4333
|
ralphPlan = savedPlan;
|
|
4094
|
-
console.log(
|
|
4334
|
+
console.log(chalk9.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
|
|
4095
4335
|
`));
|
|
4096
4336
|
}
|
|
4097
4337
|
} catch {
|
|
@@ -4103,7 +4343,7 @@ Analyze the above input.`;
|
|
|
4103
4343
|
prompt: theme().prompt("notch> "),
|
|
4104
4344
|
completer: (line) => completer(line)
|
|
4105
4345
|
});
|
|
4106
|
-
console.log(
|
|
4346
|
+
console.log(chalk9.gray(" Type your request, or /help for commands.\n"));
|
|
4107
4347
|
rl.prompt();
|
|
4108
4348
|
rl.on("line", async (line) => {
|
|
4109
4349
|
const input = line.trim();
|
|
@@ -4119,7 +4359,7 @@ Analyze the above input.`;
|
|
|
4119
4359
|
if (messages.length > 0) {
|
|
4120
4360
|
try {
|
|
4121
4361
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
4122
|
-
console.log(
|
|
4362
|
+
console.log(chalk9.gray(` Session saved: ${id}`));
|
|
4123
4363
|
} catch {
|
|
4124
4364
|
}
|
|
4125
4365
|
}
|
|
@@ -4130,12 +4370,12 @@ Analyze the above input.`;
|
|
|
4130
4370
|
}
|
|
4131
4371
|
}
|
|
4132
4372
|
await toolCtx.runHook?.("session-end", {});
|
|
4133
|
-
console.log(
|
|
4373
|
+
console.log(chalk9.gray("\n Goodbye!\n"));
|
|
4134
4374
|
process.exit(0);
|
|
4135
4375
|
}
|
|
4136
4376
|
if (input === "/clear") {
|
|
4137
4377
|
messages.length = 0;
|
|
4138
|
-
console.log(
|
|
4378
|
+
console.log(chalk9.gray(" Conversation cleared.\n"));
|
|
4139
4379
|
rl.prompt();
|
|
4140
4380
|
return;
|
|
4141
4381
|
}
|
|
@@ -4155,8 +4395,8 @@ Analyze the above input.`;
|
|
|
4155
4395
|
newModel = `notch-${newModel}`;
|
|
4156
4396
|
}
|
|
4157
4397
|
if (!isValidModel(newModel)) {
|
|
4158
|
-
console.log(
|
|
4159
|
-
console.log(
|
|
4398
|
+
console.log(chalk9.red(` Unknown model: ${newModel}`));
|
|
4399
|
+
console.log(chalk9.gray(` Available: ${modelChoices}`));
|
|
4160
4400
|
rl.prompt();
|
|
4161
4401
|
return;
|
|
4162
4402
|
}
|
|
@@ -4164,7 +4404,7 @@ Analyze the above input.`;
|
|
|
4164
4404
|
config.models.chat.model = activeModelId;
|
|
4165
4405
|
model = resolveModel(config.models.chat);
|
|
4166
4406
|
const switchedInfo = MODEL_CATALOG[activeModelId];
|
|
4167
|
-
console.log(
|
|
4407
|
+
console.log(chalk9.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
|
|
4168
4408
|
`));
|
|
4169
4409
|
rl.prompt();
|
|
4170
4410
|
return;
|
|
@@ -4177,22 +4417,22 @@ Analyze the above input.`;
|
|
|
4177
4417
|
statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
|
|
4178
4418
|
} else {
|
|
4179
4419
|
statusSpinner.fail(check.error ?? "API unreachable");
|
|
4180
|
-
console.log(
|
|
4420
|
+
console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
|
|
4181
4421
|
}
|
|
4182
4422
|
rl.prompt();
|
|
4183
4423
|
return;
|
|
4184
4424
|
}
|
|
4185
4425
|
if (input === "/undo") {
|
|
4186
4426
|
if (checkpoints.undoCount === 0) {
|
|
4187
|
-
console.log(
|
|
4427
|
+
console.log(chalk9.gray(" Nothing to undo.\n"));
|
|
4188
4428
|
rl.prompt();
|
|
4189
4429
|
return;
|
|
4190
4430
|
}
|
|
4191
4431
|
const undone = await checkpoints.undo();
|
|
4192
4432
|
if (undone) {
|
|
4193
4433
|
const fileCount = undone.files.length;
|
|
4194
|
-
console.log(
|
|
4195
|
-
console.log(
|
|
4434
|
+
console.log(chalk9.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
|
|
4435
|
+
console.log(chalk9.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
|
|
4196
4436
|
`));
|
|
4197
4437
|
}
|
|
4198
4438
|
rl.prompt();
|
|
@@ -4200,7 +4440,7 @@ Analyze the above input.`;
|
|
|
4200
4440
|
}
|
|
4201
4441
|
if (input === "/usage") {
|
|
4202
4442
|
if (usage.turnCount === 0) {
|
|
4203
|
-
console.log(
|
|
4443
|
+
console.log(chalk9.gray(" No usage yet.\n"));
|
|
4204
4444
|
} else {
|
|
4205
4445
|
console.log(usage.formatSession());
|
|
4206
4446
|
const currentTokens = estimateTokens(messages);
|
|
@@ -4213,7 +4453,7 @@ Analyze the above input.`;
|
|
|
4213
4453
|
}
|
|
4214
4454
|
if (input === "/cost") {
|
|
4215
4455
|
if (costTracker.totalCost === 0) {
|
|
4216
|
-
console.log(
|
|
4456
|
+
console.log(chalk9.gray(" No cost data yet.\n"));
|
|
4217
4457
|
} else {
|
|
4218
4458
|
console.log(costTracker.formatTotal());
|
|
4219
4459
|
console.log(costTracker.formatByModel());
|
|
@@ -4228,7 +4468,7 @@ Analyze the above input.`;
|
|
|
4228
4468
|
const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
|
|
4229
4469
|
messages.length = 0;
|
|
4230
4470
|
messages.push(...compressed);
|
|
4231
|
-
console.log(
|
|
4471
|
+
console.log(chalk9.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
|
|
4232
4472
|
console.log("");
|
|
4233
4473
|
rl.prompt();
|
|
4234
4474
|
return;
|
|
@@ -4236,10 +4476,10 @@ Analyze the above input.`;
|
|
|
4236
4476
|
if (input === "/diff") {
|
|
4237
4477
|
const diffs = checkpoints.allDiffs();
|
|
4238
4478
|
if (diffs.length === 0) {
|
|
4239
|
-
console.log(
|
|
4479
|
+
console.log(chalk9.gray(" No file changes this session.\n"));
|
|
4240
4480
|
} else {
|
|
4241
4481
|
for (const df of diffs) {
|
|
4242
|
-
console.log(
|
|
4482
|
+
console.log(chalk9.cyan(` ${df.path}:`));
|
|
4243
4483
|
console.log(unifiedDiff(df.before, df.after, df.path));
|
|
4244
4484
|
console.log("");
|
|
4245
4485
|
}
|
|
@@ -4255,10 +4495,10 @@ Analyze the above input.`;
|
|
|
4255
4495
|
projectRoot: config.projectRoot,
|
|
4256
4496
|
outputPath: exportPath
|
|
4257
4497
|
});
|
|
4258
|
-
console.log(
|
|
4498
|
+
console.log(chalk9.green(` Exported to ${ePath}
|
|
4259
4499
|
`));
|
|
4260
4500
|
} catch (err) {
|
|
4261
|
-
console.log(
|
|
4501
|
+
console.log(chalk9.red(` Export failed: ${err.message}
|
|
4262
4502
|
`));
|
|
4263
4503
|
}
|
|
4264
4504
|
rl.prompt();
|
|
@@ -4268,10 +4508,10 @@ Analyze the above input.`;
|
|
|
4268
4508
|
try {
|
|
4269
4509
|
const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
|
|
4270
4510
|
sessionId = id;
|
|
4271
|
-
console.log(
|
|
4511
|
+
console.log(chalk9.green(` Session saved: ${id}
|
|
4272
4512
|
`));
|
|
4273
4513
|
} catch (err) {
|
|
4274
|
-
console.log(
|
|
4514
|
+
console.log(chalk9.red(` Save failed: ${err.message}
|
|
4275
4515
|
`));
|
|
4276
4516
|
}
|
|
4277
4517
|
rl.prompt();
|
|
@@ -4281,16 +4521,16 @@ Analyze the above input.`;
|
|
|
4281
4521
|
try {
|
|
4282
4522
|
const sessions = await listSessions(config.projectRoot);
|
|
4283
4523
|
if (sessions.length === 0) {
|
|
4284
|
-
console.log(
|
|
4524
|
+
console.log(chalk9.gray(" No saved sessions.\n"));
|
|
4285
4525
|
} else {
|
|
4286
|
-
console.log(
|
|
4526
|
+
console.log(chalk9.gray("\n Saved sessions:\n"));
|
|
4287
4527
|
for (const s of sessions.slice(0, 10)) {
|
|
4288
|
-
console.log(
|
|
4528
|
+
console.log(chalk9.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
|
|
4289
4529
|
}
|
|
4290
4530
|
console.log("");
|
|
4291
4531
|
}
|
|
4292
4532
|
} catch (err) {
|
|
4293
|
-
console.log(
|
|
4533
|
+
console.log(chalk9.red(` Error listing sessions: ${err.message}
|
|
4294
4534
|
`));
|
|
4295
4535
|
}
|
|
4296
4536
|
rl.prompt();
|
|
@@ -4308,18 +4548,18 @@ Analyze the above input.`;
|
|
|
4308
4548
|
const savedPlan = await loadPlan(config.projectRoot);
|
|
4309
4549
|
if (savedPlan) {
|
|
4310
4550
|
ralphPlan = savedPlan;
|
|
4311
|
-
console.log(
|
|
4551
|
+
console.log(chalk9.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
|
|
4312
4552
|
`));
|
|
4313
4553
|
}
|
|
4314
4554
|
} catch {
|
|
4315
4555
|
}
|
|
4316
|
-
console.log(
|
|
4556
|
+
console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
|
|
4317
4557
|
`));
|
|
4318
4558
|
} else {
|
|
4319
|
-
console.log(
|
|
4559
|
+
console.log(chalk9.gray(" No session found.\n"));
|
|
4320
4560
|
}
|
|
4321
4561
|
} catch (err) {
|
|
4322
|
-
console.log(
|
|
4562
|
+
console.log(chalk9.red(` Resume failed: ${err.message}
|
|
4323
4563
|
`));
|
|
4324
4564
|
}
|
|
4325
4565
|
rl.prompt();
|
|
@@ -4328,7 +4568,7 @@ Analyze the above input.`;
|
|
|
4328
4568
|
if (input.startsWith("/search ")) {
|
|
4329
4569
|
const query = input.replace("/search ", "").trim().toLowerCase();
|
|
4330
4570
|
if (!query) {
|
|
4331
|
-
console.log(
|
|
4571
|
+
console.log(chalk9.gray(" Usage: /search <query>\n"));
|
|
4332
4572
|
rl.prompt();
|
|
4333
4573
|
return;
|
|
4334
4574
|
}
|
|
@@ -4345,14 +4585,14 @@ Analyze the above input.`;
|
|
|
4345
4585
|
}
|
|
4346
4586
|
}
|
|
4347
4587
|
if (matches.length === 0) {
|
|
4348
|
-
console.log(
|
|
4588
|
+
console.log(chalk9.gray(` No matches for "${query}"
|
|
4349
4589
|
`));
|
|
4350
4590
|
} else {
|
|
4351
|
-
console.log(
|
|
4591
|
+
console.log(chalk9.gray(`
|
|
4352
4592
|
${matches.length} match(es) for "${query}":
|
|
4353
4593
|
`));
|
|
4354
4594
|
for (const m of matches.slice(0, 10)) {
|
|
4355
|
-
console.log(
|
|
4595
|
+
console.log(chalk9.gray(` [${m.index}] ${m.role}: ${m.preview}`));
|
|
4356
4596
|
}
|
|
4357
4597
|
console.log("");
|
|
4358
4598
|
}
|
|
@@ -4366,7 +4606,7 @@ Analyze the above input.`;
|
|
|
4366
4606
|
activePlan = await generatePlan(task, model, systemPrompt);
|
|
4367
4607
|
planSpinner.succeed("Plan generated");
|
|
4368
4608
|
console.log(formatPlan(activePlan));
|
|
4369
|
-
console.log(
|
|
4609
|
+
console.log(chalk9.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
|
|
4370
4610
|
} catch (err) {
|
|
4371
4611
|
planSpinner.fail(`Plan failed: ${err.message}`);
|
|
4372
4612
|
}
|
|
@@ -4375,11 +4615,11 @@ Analyze the above input.`;
|
|
|
4375
4615
|
}
|
|
4376
4616
|
if (input === "/plan approve") {
|
|
4377
4617
|
if (!activePlan) {
|
|
4378
|
-
console.log(
|
|
4618
|
+
console.log(chalk9.gray(" No active plan. Use /plan <task> to create one.\n"));
|
|
4379
4619
|
rl.prompt();
|
|
4380
4620
|
return;
|
|
4381
4621
|
}
|
|
4382
|
-
console.log(
|
|
4622
|
+
console.log(chalk9.green(" Executing plan...\n"));
|
|
4383
4623
|
while (!isPlanComplete(activePlan)) {
|
|
4384
4624
|
const stepPrompt = currentStepPrompt(activePlan);
|
|
4385
4625
|
messages.push({ role: "user", content: stepPrompt });
|
|
@@ -4397,10 +4637,10 @@ Analyze the above input.`;
|
|
|
4397
4637
|
},
|
|
4398
4638
|
onToolCall: (name) => {
|
|
4399
4639
|
if (planStepSpinner.isSpinning) planStepSpinner.stop();
|
|
4400
|
-
console.log(
|
|
4640
|
+
console.log(chalk9.gray(` \u2192 ${name}`));
|
|
4401
4641
|
},
|
|
4402
4642
|
onToolResult: (_name, _result, isError) => {
|
|
4403
|
-
console.log(isError ?
|
|
4643
|
+
console.log(isError ? chalk9.red(" \u2717") : chalk9.green(" \u2713"));
|
|
4404
4644
|
}
|
|
4405
4645
|
});
|
|
4406
4646
|
console.log("\n");
|
|
@@ -4413,7 +4653,7 @@ Analyze the above input.`;
|
|
|
4413
4653
|
}
|
|
4414
4654
|
}
|
|
4415
4655
|
if (activePlan && isPlanComplete(activePlan)) {
|
|
4416
|
-
console.log(
|
|
4656
|
+
console.log(chalk9.green(" Plan completed!\n"));
|
|
4417
4657
|
}
|
|
4418
4658
|
activePlan = null;
|
|
4419
4659
|
rl.prompt();
|
|
@@ -4421,26 +4661,26 @@ Analyze the above input.`;
|
|
|
4421
4661
|
}
|
|
4422
4662
|
if (input === "/plan edit") {
|
|
4423
4663
|
if (!activePlan) {
|
|
4424
|
-
console.log(
|
|
4664
|
+
console.log(chalk9.gray(" No active plan to edit.\n"));
|
|
4425
4665
|
rl.prompt();
|
|
4426
4666
|
return;
|
|
4427
4667
|
}
|
|
4428
4668
|
console.log(formatPlan(activePlan));
|
|
4429
|
-
console.log(
|
|
4430
|
-
console.log(
|
|
4669
|
+
console.log(chalk9.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
|
|
4670
|
+
console.log(chalk9.gray(" or /plan approve to proceed with the current plan.\n"));
|
|
4431
4671
|
rl.prompt();
|
|
4432
4672
|
return;
|
|
4433
4673
|
}
|
|
4434
4674
|
if (input === "/plan cancel") {
|
|
4435
4675
|
activePlan = null;
|
|
4436
|
-
console.log(
|
|
4676
|
+
console.log(chalk9.gray(" Plan discarded.\n"));
|
|
4437
4677
|
rl.prompt();
|
|
4438
4678
|
return;
|
|
4439
4679
|
}
|
|
4440
4680
|
if (input.startsWith("/agent ")) {
|
|
4441
4681
|
const task = input.replace("/agent ", "").trim();
|
|
4442
4682
|
const agentId = nextSubagentId();
|
|
4443
|
-
console.log(
|
|
4683
|
+
console.log(chalk9.cyan(` Spawning subagent #${agentId}: ${task}
|
|
4444
4684
|
`));
|
|
4445
4685
|
spawnSubagent({
|
|
4446
4686
|
id: agentId,
|
|
@@ -4450,14 +4690,14 @@ Analyze the above input.`;
|
|
|
4450
4690
|
toolContext: toolCtx,
|
|
4451
4691
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
4452
4692
|
onComplete: (result) => {
|
|
4453
|
-
console.log(
|
|
4693
|
+
console.log(chalk9.green(`
|
|
4454
4694
|
Subagent #${agentId} finished:`));
|
|
4455
|
-
console.log(
|
|
4695
|
+
console.log(chalk9.gray(` ${result.slice(0, 200)}
|
|
4456
4696
|
`));
|
|
4457
4697
|
rl.prompt();
|
|
4458
4698
|
},
|
|
4459
4699
|
onError: (err) => {
|
|
4460
|
-
console.log(
|
|
4700
|
+
console.log(chalk9.red(`
|
|
4461
4701
|
Subagent #${agentId} failed: ${err}
|
|
4462
4702
|
`));
|
|
4463
4703
|
rl.prompt();
|
|
@@ -4470,18 +4710,18 @@ Analyze the above input.`;
|
|
|
4470
4710
|
try {
|
|
4471
4711
|
const memories = await loadMemories(config.projectRoot);
|
|
4472
4712
|
if (memories.length === 0) {
|
|
4473
|
-
console.log(
|
|
4713
|
+
console.log(chalk9.gray(" No saved memories.\n"));
|
|
4474
4714
|
} else {
|
|
4475
|
-
console.log(
|
|
4715
|
+
console.log(chalk9.gray(`
|
|
4476
4716
|
${memories.length} saved memories:
|
|
4477
4717
|
`));
|
|
4478
4718
|
for (const m of memories) {
|
|
4479
|
-
console.log(
|
|
4719
|
+
console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
|
|
4480
4720
|
}
|
|
4481
4721
|
console.log("");
|
|
4482
4722
|
}
|
|
4483
4723
|
} catch (err) {
|
|
4484
|
-
console.log(
|
|
4724
|
+
console.log(chalk9.red(` Error: ${err.message}
|
|
4485
4725
|
`));
|
|
4486
4726
|
}
|
|
4487
4727
|
rl.prompt();
|
|
@@ -4492,16 +4732,16 @@ Analyze the above input.`;
|
|
|
4492
4732
|
try {
|
|
4493
4733
|
const results = await searchMemories(config.projectRoot, query);
|
|
4494
4734
|
if (results.length === 0) {
|
|
4495
|
-
console.log(
|
|
4735
|
+
console.log(chalk9.gray(` No memories matching "${query}"
|
|
4496
4736
|
`));
|
|
4497
4737
|
} else {
|
|
4498
4738
|
for (const m of results) {
|
|
4499
|
-
console.log(
|
|
4739
|
+
console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
|
|
4500
4740
|
}
|
|
4501
4741
|
console.log("");
|
|
4502
4742
|
}
|
|
4503
4743
|
} catch (err) {
|
|
4504
|
-
console.log(
|
|
4744
|
+
console.log(chalk9.red(` Error: ${err.message}
|
|
4505
4745
|
`));
|
|
4506
4746
|
}
|
|
4507
4747
|
rl.prompt();
|
|
@@ -4513,10 +4753,10 @@ Analyze the above input.`;
|
|
|
4513
4753
|
for (const m of memories) {
|
|
4514
4754
|
await deleteMemory(config.projectRoot, m.id);
|
|
4515
4755
|
}
|
|
4516
|
-
console.log(
|
|
4756
|
+
console.log(chalk9.yellow(` Cleared ${memories.length} memories.
|
|
4517
4757
|
`));
|
|
4518
4758
|
} catch (err) {
|
|
4519
|
-
console.log(
|
|
4759
|
+
console.log(chalk9.red(` Error: ${err.message}
|
|
4520
4760
|
`));
|
|
4521
4761
|
}
|
|
4522
4762
|
rl.prompt();
|
|
@@ -4544,27 +4784,27 @@ Analyze the above input.`;
|
|
|
4544
4784
|
}
|
|
4545
4785
|
if (input === "/ralph run") {
|
|
4546
4786
|
if (!ralphPlan) {
|
|
4547
|
-
console.log(
|
|
4787
|
+
console.log(chalk9.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
|
|
4548
4788
|
rl.prompt();
|
|
4549
4789
|
return;
|
|
4550
4790
|
}
|
|
4551
|
-
console.log(
|
|
4791
|
+
console.log(chalk9.green(" Ralph is running...\n"));
|
|
4552
4792
|
try {
|
|
4553
4793
|
ralphPlan = await runRalphLoop(ralphPlan, {
|
|
4554
4794
|
model,
|
|
4555
4795
|
systemPrompt,
|
|
4556
4796
|
toolContext: toolCtx,
|
|
4557
4797
|
contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
|
|
4558
|
-
onTaskStart: (task) => console.log(
|
|
4559
|
-
onTaskComplete: (task) => console.log(
|
|
4798
|
+
onTaskStart: (task) => console.log(chalk9.cyan(` \u25B6 Task: ${task.description}`)),
|
|
4799
|
+
onTaskComplete: (task) => console.log(chalk9.green(` \u2713 Done: ${task.description}
|
|
4560
4800
|
`)),
|
|
4561
|
-
onTaskFail: (task, err) => console.log(
|
|
4801
|
+
onTaskFail: (task, err) => console.log(chalk9.red(` \u2717 Failed: ${task.description} (${err})
|
|
4562
4802
|
`))
|
|
4563
4803
|
});
|
|
4564
4804
|
await savePlan(config.projectRoot, ralphPlan);
|
|
4565
4805
|
console.log(formatRalphStatus(ralphPlan));
|
|
4566
4806
|
} catch (err) {
|
|
4567
|
-
console.log(
|
|
4807
|
+
console.log(chalk9.red(` Ralph error: ${err.message}
|
|
4568
4808
|
`));
|
|
4569
4809
|
}
|
|
4570
4810
|
rl.prompt();
|
|
@@ -4572,7 +4812,7 @@ Analyze the above input.`;
|
|
|
4572
4812
|
}
|
|
4573
4813
|
if (input === "/ralph status") {
|
|
4574
4814
|
if (!ralphPlan) {
|
|
4575
|
-
console.log(
|
|
4815
|
+
console.log(chalk9.gray(" No Ralph plan active.\n"));
|
|
4576
4816
|
} else {
|
|
4577
4817
|
console.log(formatRalphStatus(ralphPlan));
|
|
4578
4818
|
}
|
|
@@ -4583,26 +4823,26 @@ Analyze the above input.`;
|
|
|
4583
4823
|
ralphPlan = null;
|
|
4584
4824
|
await deletePlan(config.projectRoot).catch(() => {
|
|
4585
4825
|
});
|
|
4586
|
-
console.log(
|
|
4826
|
+
console.log(chalk9.gray(" Ralph plan cleared.\n"));
|
|
4587
4827
|
rl.prompt();
|
|
4588
4828
|
return;
|
|
4589
4829
|
}
|
|
4590
4830
|
if (input === "/branch") {
|
|
4591
4831
|
const branchId = `branch-${branches.size + 1}`;
|
|
4592
4832
|
branches.set(branchId, [...messages]);
|
|
4593
|
-
console.log(
|
|
4833
|
+
console.log(chalk9.green(` Forked conversation as "${branchId}" (${messages.length} messages)
|
|
4594
4834
|
`));
|
|
4595
4835
|
rl.prompt();
|
|
4596
4836
|
return;
|
|
4597
4837
|
}
|
|
4598
4838
|
if (input === "/branches") {
|
|
4599
4839
|
if (branches.size === 0) {
|
|
4600
|
-
console.log(
|
|
4840
|
+
console.log(chalk9.gray(" No conversation branches. Use /branch to fork.\n"));
|
|
4601
4841
|
} else {
|
|
4602
|
-
console.log(
|
|
4842
|
+
console.log(chalk9.gray("\n Branches:\n"));
|
|
4603
4843
|
for (const [name, msgs] of branches) {
|
|
4604
|
-
const marker = name === currentBranch ?
|
|
4605
|
-
console.log(
|
|
4844
|
+
const marker = name === currentBranch ? chalk9.green(" \u25CF") : " ";
|
|
4845
|
+
console.log(chalk9.gray(` ${marker} ${name} (${msgs.length} messages)`));
|
|
4606
4846
|
}
|
|
4607
4847
|
console.log("");
|
|
4608
4848
|
}
|
|
@@ -4639,8 +4879,8 @@ Analyze the above input.`;
|
|
|
4639
4879
|
return;
|
|
4640
4880
|
}
|
|
4641
4881
|
if (input.startsWith("/")) {
|
|
4642
|
-
console.log(
|
|
4643
|
-
console.log(
|
|
4882
|
+
console.log(chalk9.red(` Unknown command: ${input}`));
|
|
4883
|
+
console.log(chalk9.gray(" Type /help for available commands.\n"));
|
|
4644
4884
|
rl.prompt();
|
|
4645
4885
|
return;
|
|
4646
4886
|
}
|
|
@@ -4648,7 +4888,7 @@ Analyze the above input.`;
|
|
|
4648
4888
|
const refContext = formatReferences(references);
|
|
4649
4889
|
const finalPrompt = refContext + cleanInput;
|
|
4650
4890
|
if (references.length > 0) {
|
|
4651
|
-
console.log(
|
|
4891
|
+
console.log(chalk9.gray(` Injected ${references.length} reference(s)`));
|
|
4652
4892
|
}
|
|
4653
4893
|
messages.push({ role: "user", content: finalPrompt });
|
|
4654
4894
|
const spinner = ora("Thinking...").start();
|
|
@@ -4674,16 +4914,16 @@ Analyze the above input.`;
|
|
|
4674
4914
|
const val = String(v);
|
|
4675
4915
|
return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
|
|
4676
4916
|
}).join(", ");
|
|
4677
|
-
console.log(
|
|
4917
|
+
console.log(chalk9.gray(`
|
|
4678
4918
|
\u2192 ${name}(${argSummary})`));
|
|
4679
4919
|
},
|
|
4680
4920
|
onToolResult: (_name, result, isError) => {
|
|
4681
4921
|
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
4682
|
-
const icon = isError ?
|
|
4683
|
-
console.log(
|
|
4922
|
+
const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
|
|
4923
|
+
console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
|
|
4684
4924
|
},
|
|
4685
4925
|
onCompress: () => {
|
|
4686
|
-
console.log(
|
|
4926
|
+
console.log(chalk9.yellow("\n [Context compressed to fit window]\n"));
|
|
4687
4927
|
}
|
|
4688
4928
|
})
|
|
4689
4929
|
);
|
|
@@ -4697,7 +4937,7 @@ Analyze the above input.`;
|
|
|
4697
4937
|
model: activeModelId
|
|
4698
4938
|
});
|
|
4699
4939
|
costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
|
|
4700
|
-
console.log(usage.formatLast());
|
|
4940
|
+
console.log(usage.formatLast() + " " + costTracker.formatLastCost());
|
|
4701
4941
|
const currentTokens = estimateTokens(messages);
|
|
4702
4942
|
const ctxWindow = MODEL_CATALOG[activeModelId].contextWindow;
|
|
4703
4943
|
if (currentTokens > ctxWindow * 0.5) {
|
|
@@ -4716,7 +4956,7 @@ Analyze the above input.`;
|
|
|
4716
4956
|
type: "auto",
|
|
4717
4957
|
content: memMatch[1]
|
|
4718
4958
|
});
|
|
4719
|
-
console.log(
|
|
4959
|
+
console.log(chalk9.gray(" (Saved to memory)\n"));
|
|
4720
4960
|
} catch {
|
|
4721
4961
|
}
|
|
4722
4962
|
}
|
|
@@ -4726,13 +4966,13 @@ Analyze the above input.`;
|
|
|
4726
4966
|
checkpoints.discard();
|
|
4727
4967
|
const msg = err.message?.toLowerCase() ?? "";
|
|
4728
4968
|
if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
|
|
4729
|
-
console.log(
|
|
4969
|
+
console.log(chalk9.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
|
|
4730
4970
|
} else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
|
|
4731
|
-
console.log(
|
|
4971
|
+
console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
|
|
4732
4972
|
} else if (msg.includes("429") || msg.includes("rate limit")) {
|
|
4733
|
-
console.log(
|
|
4973
|
+
console.log(chalk9.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
|
|
4734
4974
|
} else {
|
|
4735
|
-
console.log(
|
|
4975
|
+
console.log(chalk9.gray(" (The conversation history is preserved. Try again.)\n"));
|
|
4736
4976
|
}
|
|
4737
4977
|
}
|
|
4738
4978
|
rl.prompt();
|
|
@@ -4750,13 +4990,13 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
4750
4990
|
cwd: config.projectRoot,
|
|
4751
4991
|
requireConfirm: false,
|
|
4752
4992
|
confirm: async () => true,
|
|
4753
|
-
log: (msg) => console.log(
|
|
4993
|
+
log: (msg) => console.log(chalk9.gray(` ${msg}`))
|
|
4754
4994
|
};
|
|
4755
4995
|
const subcommand = args[0];
|
|
4756
4996
|
if (subcommand === "plan") {
|
|
4757
4997
|
const goal = args.slice(1).join(" ");
|
|
4758
4998
|
if (!goal) {
|
|
4759
|
-
console.error(
|
|
4999
|
+
console.error(chalk9.red(" Usage: notch ralph plan <goal>"));
|
|
4760
5000
|
process.exit(1);
|
|
4761
5001
|
}
|
|
4762
5002
|
const spinner = ora("Ralph is planning...").start();
|
|
@@ -4767,7 +5007,7 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
4767
5007
|
} else if (subcommand === "run") {
|
|
4768
5008
|
let plan = await loadPlan(config.projectRoot);
|
|
4769
5009
|
if (!plan) {
|
|
4770
|
-
console.error(
|
|
5010
|
+
console.error(chalk9.red(" No plan found. Run: notch ralph plan <goal>"));
|
|
4771
5011
|
process.exit(1);
|
|
4772
5012
|
}
|
|
4773
5013
|
plan = await runRalphLoop(plan, {
|
|
@@ -4775,27 +5015,27 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
4775
5015
|
systemPrompt,
|
|
4776
5016
|
toolContext: toolCtx,
|
|
4777
5017
|
contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
|
|
4778
|
-
onTaskStart: (t) => console.log(
|
|
4779
|
-
onTaskComplete: (t) => console.log(
|
|
4780
|
-
onTaskFail: (t, e) => console.log(
|
|
5018
|
+
onTaskStart: (t) => console.log(chalk9.cyan(` \u25B6 ${t.description}`)),
|
|
5019
|
+
onTaskComplete: (t) => console.log(chalk9.green(` \u2713 ${t.description}`)),
|
|
5020
|
+
onTaskFail: (t, e) => console.log(chalk9.red(` \u2717 ${t.description}: ${e}`))
|
|
4781
5021
|
});
|
|
4782
5022
|
await savePlan(config.projectRoot, plan);
|
|
4783
5023
|
console.log(formatRalphStatus(plan));
|
|
4784
5024
|
} else if (subcommand === "status") {
|
|
4785
5025
|
const plan = await loadPlan(config.projectRoot);
|
|
4786
5026
|
if (!plan) {
|
|
4787
|
-
console.log(
|
|
5027
|
+
console.log(chalk9.gray(" No Ralph plan found."));
|
|
4788
5028
|
} else {
|
|
4789
5029
|
console.log(formatRalphStatus(plan));
|
|
4790
5030
|
}
|
|
4791
5031
|
} else {
|
|
4792
|
-
console.error(
|
|
4793
|
-
console.error(
|
|
5032
|
+
console.error(chalk9.red(` Unknown: notch ralph ${subcommand}`));
|
|
5033
|
+
console.error(chalk9.gray(" Usage: notch ralph <plan|run|status>"));
|
|
4794
5034
|
process.exit(1);
|
|
4795
5035
|
}
|
|
4796
5036
|
}
|
|
4797
5037
|
main().catch((err) => {
|
|
4798
|
-
console.error(
|
|
5038
|
+
console.error(chalk9.red(`
|
|
4799
5039
|
Fatal: ${err.message}
|
|
4800
5040
|
`));
|
|
4801
5041
|
process.exit(1);
|