corex-cli 1.0.0
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/.env.example +1 -0
- package/.vscode/launch.json +15 -0
- package/Corex AI TERMINAL CLI +7 -0
- package/Corex AI TERMINAL CLI.pub +1 -0
- package/README.md +32 -0
- package/assets/COREX_SYSTEM_PROMPT.txt +155 -0
- package/assets/logo.txt +10 -0
- package/bin/corex.js +904 -0
- package/corex-ai-terminal-cli@1.0.0 +0 -0
- package/dist/index.js +742 -0
- package/install.sh +26 -0
- package/package.json +34 -0
- package/src/app.tsx +217 -0
- package/src/components/ApiKeyScreen.tsx +65 -0
- package/src/components/BootScreen.tsx +62 -0
- package/src/components/ChatHistory.tsx +45 -0
- package/src/components/Header.tsx +60 -0
- package/src/components/InputBar.tsx +43 -0
- package/src/components/StatusArea.tsx +23 -0
- package/src/components/StatusBar.tsx +27 -0
- package/src/components/ThinkingDots.tsx +22 -0
- package/src/components/TopBar.tsx +31 -0
- package/src/core/network/request.ts +211 -0
- package/src/core/providers/anthropic.ts +107 -0
- package/src/core/providers/gemini.ts +56 -0
- package/src/core/providers/index.ts +4 -0
- package/src/core/providers/openai.ts +64 -0
- package/src/index.ts +62 -0
- package/src/lib/ai.ts +167 -0
- package/src/lib/config.ts +250 -0
- package/src/lib/history.ts +43 -0
- package/src/lib/markdown.ts +3 -0
- package/src/themes/themes.ts +70 -0
- package/src/types/gradient-string.d.ts +12 -0
- package/src/types.ts +34 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +12 -0
- package/tsx +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_react7 = __toESM(require("react"));
|
|
28
|
+
var import_ink7 = require("ink");
|
|
29
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
30
|
+
|
|
31
|
+
// src/lib/config.ts
|
|
32
|
+
var import_conf = __toESM(require("conf"));
|
|
33
|
+
var readline = __toESM(require("readline"));
|
|
34
|
+
var fs = __toESM(require("fs"));
|
|
35
|
+
var path = __toESM(require("path"));
|
|
36
|
+
var FALLBACK_SYSTEM_PROMPT = `You are COREX, an elite AI assistant living inside a terminal.
|
|
37
|
+
You are direct, insightful, and technically brilliant.
|
|
38
|
+
Format your responses for terminal display using clean spacing.
|
|
39
|
+
When showing code, use markdown code blocks.
|
|
40
|
+
Keep responses focused and avoid unnecessary filler text.`;
|
|
41
|
+
function loadSystemPrompt() {
|
|
42
|
+
const filename = "COREX_SYSTEM_PROMPT.txt";
|
|
43
|
+
const possiblePaths = [
|
|
44
|
+
path.join(__dirname, "..", "assets", filename),
|
|
45
|
+
path.join(__dirname, "..", "..", "assets", filename),
|
|
46
|
+
path.join(process.cwd(), "assets", filename)
|
|
47
|
+
];
|
|
48
|
+
for (const p of possiblePaths) {
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(p)) {
|
|
51
|
+
return fs.readFileSync(p, "utf-8").trim();
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return FALLBACK_SYSTEM_PROMPT;
|
|
57
|
+
}
|
|
58
|
+
var DEFAULT_SYSTEM_PROMPT = loadSystemPrompt();
|
|
59
|
+
var defaults = {
|
|
60
|
+
apiKey: "",
|
|
61
|
+
model: "claude-3-5-sonnet-20241022",
|
|
62
|
+
theme: "dark",
|
|
63
|
+
systemPrompt: DEFAULT_SYSTEM_PROMPT,
|
|
64
|
+
maxTokens: 4096,
|
|
65
|
+
temperature: 0.7,
|
|
66
|
+
saveHistory: false,
|
|
67
|
+
userName: "You"
|
|
68
|
+
};
|
|
69
|
+
var config = new import_conf.default({
|
|
70
|
+
projectName: "corex",
|
|
71
|
+
defaults
|
|
72
|
+
});
|
|
73
|
+
function loadConfig() {
|
|
74
|
+
return {
|
|
75
|
+
apiKey: config.get("apiKey"),
|
|
76
|
+
model: config.get("model"),
|
|
77
|
+
theme: config.get("theme"),
|
|
78
|
+
systemPrompt: config.get("systemPrompt"),
|
|
79
|
+
maxTokens: config.get("maxTokens"),
|
|
80
|
+
temperature: config.get("temperature"),
|
|
81
|
+
saveHistory: config.get("saveHistory"),
|
|
82
|
+
userName: config.get("userName")
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function saveConfig(partial) {
|
|
86
|
+
for (const [key, value] of Object.entries(partial)) {
|
|
87
|
+
config.set(key, value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isFirstRun() {
|
|
91
|
+
const apiKey = config.get("apiKey");
|
|
92
|
+
return !apiKey || apiKey.trim() === "";
|
|
93
|
+
}
|
|
94
|
+
function prompt(rl, question) {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
rl.question(question, (answer) => {
|
|
97
|
+
resolve(answer.trim());
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function promptMasked(rl, question) {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
const stdout = process.stdout;
|
|
104
|
+
stdout.write(question);
|
|
105
|
+
const stdin = process.stdin;
|
|
106
|
+
const wasRaw = stdin.isRaw;
|
|
107
|
+
if (stdin.setRawMode) {
|
|
108
|
+
stdin.setRawMode(true);
|
|
109
|
+
}
|
|
110
|
+
stdin.resume();
|
|
111
|
+
let input = "";
|
|
112
|
+
const onData = (char) => {
|
|
113
|
+
const c = char.toString("utf8");
|
|
114
|
+
if (c === "\n" || c === "\r") {
|
|
115
|
+
stdin.removeListener("data", onData);
|
|
116
|
+
if (stdin.setRawMode) {
|
|
117
|
+
stdin.setRawMode(wasRaw || false);
|
|
118
|
+
}
|
|
119
|
+
stdout.write("\n");
|
|
120
|
+
resolve(input.trim());
|
|
121
|
+
} else if (c === "\x7F" || c === "\b") {
|
|
122
|
+
if (input.length > 0) {
|
|
123
|
+
input = input.slice(0, -1);
|
|
124
|
+
stdout.write("\b \b");
|
|
125
|
+
}
|
|
126
|
+
} else if (c === "") {
|
|
127
|
+
process.exit(0);
|
|
128
|
+
} else {
|
|
129
|
+
input += c;
|
|
130
|
+
stdout.write("*");
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
stdin.on("data", onData);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
var MODELS = [
|
|
137
|
+
{ id: "claude-3-5-sonnet-20241022", label: "claude-3-5-sonnet-20241022 Recommended" },
|
|
138
|
+
{ id: "claude-3-opus-20240229", label: "claude-3-opus-20240229 Most Powerful" },
|
|
139
|
+
{ id: "claude-3-haiku-20240307", label: "claude-3-haiku-20240307 Fastest" }
|
|
140
|
+
];
|
|
141
|
+
async function runFirstRunWizard() {
|
|
142
|
+
const rl = readline.createInterface({
|
|
143
|
+
input: process.stdin,
|
|
144
|
+
output: process.stdout
|
|
145
|
+
});
|
|
146
|
+
console.log("");
|
|
147
|
+
console.log("+------------------------------------------+");
|
|
148
|
+
console.log("| Welcome to COREX - First Setup |");
|
|
149
|
+
console.log("+------------------------------------------+");
|
|
150
|
+
console.log("");
|
|
151
|
+
rl.close();
|
|
152
|
+
let apiKey = "";
|
|
153
|
+
while (true) {
|
|
154
|
+
apiKey = await promptMasked(
|
|
155
|
+
readline.createInterface({ input: process.stdin, output: process.stdout }),
|
|
156
|
+
"Enter your Anthropic API key: "
|
|
157
|
+
);
|
|
158
|
+
if (apiKey.startsWith("sk-ant-")) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
console.log(' Error: API key must start with "sk-ant-". Please try again.');
|
|
162
|
+
}
|
|
163
|
+
const rl2 = readline.createInterface({
|
|
164
|
+
input: process.stdin,
|
|
165
|
+
output: process.stdout
|
|
166
|
+
});
|
|
167
|
+
console.log("");
|
|
168
|
+
console.log("Choose your default model:");
|
|
169
|
+
MODELS.forEach((m, i) => {
|
|
170
|
+
console.log(` ${i + 1}) ${m.label}`);
|
|
171
|
+
});
|
|
172
|
+
const modelChoice = await prompt(rl2, "Enter choice [1]: ");
|
|
173
|
+
const modelIndex = modelChoice ? parseInt(modelChoice, 10) - 1 : 0;
|
|
174
|
+
const model = MODELS[modelIndex >= 0 && modelIndex < MODELS.length ? modelIndex : 0].id;
|
|
175
|
+
console.log("");
|
|
176
|
+
const themeChoice = await prompt(rl2, "Choose your theme: dark / light / neon / retro [dark]: ");
|
|
177
|
+
const validThemes = ["dark", "light", "neon", "retro"];
|
|
178
|
+
const theme = validThemes.includes(themeChoice) ? themeChoice : "dark";
|
|
179
|
+
rl2.close();
|
|
180
|
+
saveConfig({ apiKey, model, theme });
|
|
181
|
+
console.log("");
|
|
182
|
+
console.log("Config saved to ~/.config/corex/config.json");
|
|
183
|
+
console.log("Launching COREX...");
|
|
184
|
+
console.log("");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/lib/ai.ts
|
|
188
|
+
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
189
|
+
var client = null;
|
|
190
|
+
function initAI(apiKey) {
|
|
191
|
+
client = new import_sdk.default({ apiKey });
|
|
192
|
+
}
|
|
193
|
+
async function sendMessage(history2, userMessage, config2, onToken, onComplete, onError) {
|
|
194
|
+
if (!client) {
|
|
195
|
+
onError(new Error("No API key found. Run corex to set it up."));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const messages = [
|
|
199
|
+
...history2.map((m) => ({
|
|
200
|
+
role: m.role,
|
|
201
|
+
content: m.content
|
|
202
|
+
})),
|
|
203
|
+
{ role: "user", content: userMessage }
|
|
204
|
+
];
|
|
205
|
+
try {
|
|
206
|
+
const stream = client.messages.stream({
|
|
207
|
+
model: config2.model,
|
|
208
|
+
max_tokens: config2.maxTokens,
|
|
209
|
+
temperature: config2.temperature,
|
|
210
|
+
system: config2.systemPrompt,
|
|
211
|
+
messages
|
|
212
|
+
});
|
|
213
|
+
let fullText = "";
|
|
214
|
+
stream.on("text", (text) => {
|
|
215
|
+
fullText += text;
|
|
216
|
+
onToken(text);
|
|
217
|
+
});
|
|
218
|
+
const finalMessage = await stream.finalMessage();
|
|
219
|
+
const usage = {
|
|
220
|
+
inputTokens: finalMessage.usage?.input_tokens || 0,
|
|
221
|
+
outputTokens: finalMessage.usage?.output_tokens || 0,
|
|
222
|
+
totalTokens: (finalMessage.usage?.input_tokens || 0) + (finalMessage.usage?.output_tokens || 0)
|
|
223
|
+
};
|
|
224
|
+
onComplete(fullText, usage);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
const statusCode = err?.status || err?.statusCode;
|
|
227
|
+
let message = "An unexpected error occurred.";
|
|
228
|
+
if (statusCode === 401) {
|
|
229
|
+
message = "Invalid API key. Use /key to update it.";
|
|
230
|
+
} else if (statusCode === 429) {
|
|
231
|
+
message = "Rate limit reached. Please wait a moment and try again.";
|
|
232
|
+
} else if (statusCode === 404 || err?.message && err.message.includes("model")) {
|
|
233
|
+
message = `Selected model unavailable. Try switching with /model.`;
|
|
234
|
+
} else if (err?.code === "ENOTFOUND" || err?.code === "ECONNREFUSED" || err?.code === "ETIMEDOUT" || err?.message?.includes("fetch failed") || err?.message?.includes("network")) {
|
|
235
|
+
message = "Connection failed. Check your internet connection.";
|
|
236
|
+
} else if (err?.message) {
|
|
237
|
+
message = err.message;
|
|
238
|
+
}
|
|
239
|
+
onError(new Error(message));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/app.tsx
|
|
244
|
+
var import_react6 = __toESM(require("react"));
|
|
245
|
+
var import_ink6 = require("ink");
|
|
246
|
+
|
|
247
|
+
// src/themes/themes.ts
|
|
248
|
+
var themes = {
|
|
249
|
+
dark: {
|
|
250
|
+
userText: "#00D9FF",
|
|
251
|
+
aiText: "#FFFFFF",
|
|
252
|
+
border: "#333333",
|
|
253
|
+
accent: "#00FF88",
|
|
254
|
+
statusBar: "#666666",
|
|
255
|
+
headerGradient: ["#00D9FF", "#007AFF"]
|
|
256
|
+
},
|
|
257
|
+
neon: {
|
|
258
|
+
userText: "#FF00FF",
|
|
259
|
+
aiText: "#00FFFF",
|
|
260
|
+
border: "#FF00FF",
|
|
261
|
+
accent: "#FFFF00",
|
|
262
|
+
statusBar: "#FF00FF",
|
|
263
|
+
headerGradient: ["#FF00FF", "#00FFFF"]
|
|
264
|
+
},
|
|
265
|
+
retro: {
|
|
266
|
+
userText: "#FFB000",
|
|
267
|
+
aiText: "#FF8C00",
|
|
268
|
+
border: "#FF8C00",
|
|
269
|
+
accent: "#FFB000",
|
|
270
|
+
statusBar: "#FF8C00",
|
|
271
|
+
headerGradient: ["#FFB000", "#FF4500"]
|
|
272
|
+
},
|
|
273
|
+
light: {
|
|
274
|
+
userText: "#0066CC",
|
|
275
|
+
aiText: "#222222",
|
|
276
|
+
border: "#CCCCCC",
|
|
277
|
+
accent: "#009900",
|
|
278
|
+
statusBar: "#888888",
|
|
279
|
+
headerGradient: ["#0066CC", "#0099FF"]
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
function getTheme(name) {
|
|
283
|
+
return themes[name] || themes.dark;
|
|
284
|
+
}
|
|
285
|
+
function getThemeNames() {
|
|
286
|
+
return Object.keys(themes);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/lib/history.ts
|
|
290
|
+
var fs2 = __toESM(require("fs"));
|
|
291
|
+
var path2 = __toESM(require("path"));
|
|
292
|
+
var os = __toESM(require("os"));
|
|
293
|
+
var history = [];
|
|
294
|
+
function addMessage(role, content) {
|
|
295
|
+
history.push({ role, content });
|
|
296
|
+
}
|
|
297
|
+
function getHistory() {
|
|
298
|
+
return [...history];
|
|
299
|
+
}
|
|
300
|
+
function clearHistory() {
|
|
301
|
+
history = [];
|
|
302
|
+
}
|
|
303
|
+
function saveSession() {
|
|
304
|
+
const sessionsDir = path2.join(os.homedir(), ".corex", "sessions");
|
|
305
|
+
if (!fs2.existsSync(sessionsDir)) {
|
|
306
|
+
fs2.mkdirSync(sessionsDir, { recursive: true });
|
|
307
|
+
}
|
|
308
|
+
const now = /* @__PURE__ */ new Date();
|
|
309
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
310
|
+
const filename = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}.txt`;
|
|
311
|
+
const filepath = path2.join(sessionsDir, filename);
|
|
312
|
+
let content = "=== COREX Chat Session ===\n";
|
|
313
|
+
content += `Date: ${now.toLocaleString()}
|
|
314
|
+
`;
|
|
315
|
+
content += `Messages: ${history.length}
|
|
316
|
+
`;
|
|
317
|
+
content += "=".repeat(40) + "\n\n";
|
|
318
|
+
for (const msg of history) {
|
|
319
|
+
const label = msg.role === "user" ? "You" : "COREX";
|
|
320
|
+
content += `[${label}]
|
|
321
|
+
${msg.content}
|
|
322
|
+
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
fs2.writeFileSync(filepath, content, "utf-8");
|
|
326
|
+
return filepath;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/components/Header.tsx
|
|
330
|
+
var import_react = __toESM(require("react"));
|
|
331
|
+
var import_ink = require("ink");
|
|
332
|
+
var fs3 = __toESM(require("fs"));
|
|
333
|
+
var path3 = __toESM(require("path"));
|
|
334
|
+
var import_gradient_string = __toESM(require("gradient-string"));
|
|
335
|
+
var Header = ({ theme }) => {
|
|
336
|
+
const [logoLines, setLogoLines] = (0, import_react.useState)([]);
|
|
337
|
+
(0, import_react.useEffect)(() => {
|
|
338
|
+
try {
|
|
339
|
+
const possiblePaths = [
|
|
340
|
+
path3.join(__dirname, "..", "assets", "logo.txt"),
|
|
341
|
+
path3.join(__dirname, "..", "..", "assets", "logo.txt"),
|
|
342
|
+
path3.join(process.cwd(), "assets", "logo.txt")
|
|
343
|
+
];
|
|
344
|
+
let logoText = "";
|
|
345
|
+
for (const p of possiblePaths) {
|
|
346
|
+
if (fs3.existsSync(p)) {
|
|
347
|
+
logoText = fs3.readFileSync(p, "utf-8");
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (!logoText) {
|
|
352
|
+
logoText = `
|
|
353
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557
|
|
354
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D
|
|
355
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D
|
|
356
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557
|
|
357
|
+
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557
|
|
358
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D`;
|
|
359
|
+
}
|
|
360
|
+
const grad = (0, import_gradient_string.default)(theme.headerGradient);
|
|
361
|
+
const lines = logoText.split("\n").map((line) => grad(line));
|
|
362
|
+
setLogoLines(lines);
|
|
363
|
+
} catch {
|
|
364
|
+
setLogoLines([" COREX"]);
|
|
365
|
+
}
|
|
366
|
+
}, [theme]);
|
|
367
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ink.Box, { flexDirection: "column", marginBottom: 1 }, logoLines.map((line, i) => /* @__PURE__ */ import_react.default.createElement(import_ink.Text, { key: i }, line)));
|
|
368
|
+
};
|
|
369
|
+
var Header_default = Header;
|
|
370
|
+
|
|
371
|
+
// src/components/ChatHistory.tsx
|
|
372
|
+
var import_react3 = __toESM(require("react"));
|
|
373
|
+
var import_ink3 = require("ink");
|
|
374
|
+
|
|
375
|
+
// src/lib/markdown.ts
|
|
376
|
+
var import_marked = require("marked");
|
|
377
|
+
var import_marked_terminal = __toESM(require("marked-terminal"));
|
|
378
|
+
import_marked.marked.setOptions({
|
|
379
|
+
renderer: new import_marked_terminal.default()
|
|
380
|
+
});
|
|
381
|
+
function renderMarkdown(text) {
|
|
382
|
+
try {
|
|
383
|
+
const rendered = import_marked.marked.parse(text);
|
|
384
|
+
if (typeof rendered === "string") {
|
|
385
|
+
return rendered.replace(/\n$/, "");
|
|
386
|
+
}
|
|
387
|
+
return text;
|
|
388
|
+
} catch {
|
|
389
|
+
return text;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/components/ThinkingDots.tsx
|
|
394
|
+
var import_react2 = __toESM(require("react"));
|
|
395
|
+
var import_ink2 = require("ink");
|
|
396
|
+
var import_ink_spinner = __toESM(require("ink-spinner"));
|
|
397
|
+
var ThinkingDots = ({ theme }) => {
|
|
398
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ink2.Box, { marginLeft: 2 }, /* @__PURE__ */ import_react2.default.createElement(import_ink2.Text, { color: theme.accent }, /* @__PURE__ */ import_react2.default.createElement(import_ink_spinner.default, { type: "dots" })), /* @__PURE__ */ import_react2.default.createElement(import_ink2.Text, { color: theme.statusBar }, " thinking..."));
|
|
399
|
+
};
|
|
400
|
+
var ThinkingDots_default = ThinkingDots;
|
|
401
|
+
|
|
402
|
+
// src/components/ChatHistory.tsx
|
|
403
|
+
var ChatHistory = ({
|
|
404
|
+
messages,
|
|
405
|
+
theme,
|
|
406
|
+
isThinking,
|
|
407
|
+
streamingText,
|
|
408
|
+
userName
|
|
409
|
+
}) => {
|
|
410
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_ink3.Box, { flexDirection: "column", flexGrow: 1 }, messages.map((msg, i) => /* @__PURE__ */ import_react3.default.createElement(import_ink3.Box, { key: i, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_ink3.Text, { bold: true, color: msg.role === "user" ? theme.userText : theme.accent }, msg.role === "user" ? `${userName}` : "COREX"), /* @__PURE__ */ import_react3.default.createElement(import_ink3.Box, { marginLeft: 2 }, /* @__PURE__ */ import_react3.default.createElement(import_ink3.Text, { color: msg.role === "user" ? theme.userText : theme.aiText }, msg.role === "assistant" ? renderMarkdown(msg.content) : msg.content)))), streamingText && /* @__PURE__ */ import_react3.default.createElement(import_ink3.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_ink3.Text, { bold: true, color: theme.accent }, "COREX"), /* @__PURE__ */ import_react3.default.createElement(import_ink3.Box, { marginLeft: 2 }, /* @__PURE__ */ import_react3.default.createElement(import_ink3.Text, { color: theme.aiText }, renderMarkdown(streamingText)))), isThinking && !streamingText && /* @__PURE__ */ import_react3.default.createElement(ThinkingDots_default, { theme }));
|
|
411
|
+
};
|
|
412
|
+
var ChatHistory_default = ChatHistory;
|
|
413
|
+
|
|
414
|
+
// src/components/InputBar.tsx
|
|
415
|
+
var import_react4 = __toESM(require("react"));
|
|
416
|
+
var import_ink4 = require("ink");
|
|
417
|
+
var import_ink_text_input = __toESM(require("ink-text-input"));
|
|
418
|
+
var InputBar = ({
|
|
419
|
+
value,
|
|
420
|
+
onChange,
|
|
421
|
+
onSubmit,
|
|
422
|
+
theme,
|
|
423
|
+
isDisabled
|
|
424
|
+
}) => {
|
|
425
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_ink4.Box, { flexDirection: "column" }, /* @__PURE__ */ import_react4.default.createElement(import_ink4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_ink4.Text, { color: theme.border }, "\u2500".repeat(process.stdout.columns || 80))), /* @__PURE__ */ import_react4.default.createElement(import_ink4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_ink4.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_ink4.Text, { color: theme.accent, bold: true }, "\u276F "), /* @__PURE__ */ import_react4.default.createElement(
|
|
426
|
+
import_ink_text_input.default,
|
|
427
|
+
{
|
|
428
|
+
value,
|
|
429
|
+
onChange,
|
|
430
|
+
onSubmit,
|
|
431
|
+
placeholder: isDisabled ? "Waiting for response..." : "Type a message..."
|
|
432
|
+
}
|
|
433
|
+
)), /* @__PURE__ */ import_react4.default.createElement(import_ink4.Box, { marginLeft: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_ink4.Text, { color: theme.statusBar, dimColor: true }, "[Enter to send]"))));
|
|
434
|
+
};
|
|
435
|
+
var InputBar_default = InputBar;
|
|
436
|
+
|
|
437
|
+
// src/components/StatusBar.tsx
|
|
438
|
+
var import_react5 = __toESM(require("react"));
|
|
439
|
+
var import_ink5 = require("ink");
|
|
440
|
+
var StatusBar = ({
|
|
441
|
+
model,
|
|
442
|
+
totalTokens,
|
|
443
|
+
themeName,
|
|
444
|
+
theme
|
|
445
|
+
}) => {
|
|
446
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_ink5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_ink5.Text, { color: theme.statusBar }, model, " \u2502 tokens: ", totalTokens, " \u2502 theme: ", themeName));
|
|
447
|
+
};
|
|
448
|
+
var StatusBar_default = StatusBar;
|
|
449
|
+
|
|
450
|
+
// src/app.tsx
|
|
451
|
+
var MODELS2 = [
|
|
452
|
+
{ id: "claude-3-5-sonnet-20241022", label: "claude-3-5-sonnet Recommended" },
|
|
453
|
+
{ id: "claude-3-opus-20240229", label: "claude-3-opus Most Powerful" },
|
|
454
|
+
{ id: "claude-3-haiku-20240307", label: "claude-3-haiku Fastest" }
|
|
455
|
+
];
|
|
456
|
+
var HELP_TEXT = `
|
|
457
|
+
\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E
|
|
458
|
+
\u2502 COREX Commands \u2502
|
|
459
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
460
|
+
\u2502 /clear Clear conversation \u2502
|
|
461
|
+
\u2502 /model Switch AI model \u2502
|
|
462
|
+
\u2502 /theme Change color theme \u2502
|
|
463
|
+
\u2502 /save Save chat to file \u2502
|
|
464
|
+
\u2502 /help Show this help \u2502
|
|
465
|
+
\u2502 /key Update API key \u2502
|
|
466
|
+
\u2502 /exit Quit COREX \u2502
|
|
467
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
468
|
+
\u2502 Ctrl+C Exit \u2502
|
|
469
|
+
\u2502 Ctrl+L Clear screen \u2502
|
|
470
|
+
\u2502 \u2191 / \u2193 Navigate input history \u2502
|
|
471
|
+
\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F`;
|
|
472
|
+
var App = ({ config: initialConfig }) => {
|
|
473
|
+
const { exit } = (0, import_ink6.useApp)();
|
|
474
|
+
const [config2, setConfig] = (0, import_react6.useState)(initialConfig);
|
|
475
|
+
const [messages, setMessages] = (0, import_react6.useState)([]);
|
|
476
|
+
const [inputValue, setInputValue] = (0, import_react6.useState)("");
|
|
477
|
+
const [isThinking, setIsThinking] = (0, import_react6.useState)(false);
|
|
478
|
+
const [streamingText, setStreamingText] = (0, import_react6.useState)("");
|
|
479
|
+
const [totalTokens, setTotalTokens] = (0, import_react6.useState)(0);
|
|
480
|
+
const [inputHistory, setInputHistory] = (0, import_react6.useState)([]);
|
|
481
|
+
const [historyIndex, setHistoryIndex] = (0, import_react6.useState)(-1);
|
|
482
|
+
const [welcomeShown, setWelcomeShown] = (0, import_react6.useState)(false);
|
|
483
|
+
const [systemMessages, setSystemMessages] = (0, import_react6.useState)([]);
|
|
484
|
+
const theme = getTheme(config2.theme);
|
|
485
|
+
(0, import_react6.useEffect)(() => {
|
|
486
|
+
if (!welcomeShown) {
|
|
487
|
+
setWelcomeShown(true);
|
|
488
|
+
const welcomeMsg = "Hello. I am COREX. Ask me anything.";
|
|
489
|
+
let i = 0;
|
|
490
|
+
const chars = [];
|
|
491
|
+
const interval = setInterval(() => {
|
|
492
|
+
if (i < welcomeMsg.length) {
|
|
493
|
+
chars.push(welcomeMsg[i]);
|
|
494
|
+
setMessages([{ role: "assistant", content: chars.join("") }]);
|
|
495
|
+
i++;
|
|
496
|
+
} else {
|
|
497
|
+
clearInterval(interval);
|
|
498
|
+
addMessage("assistant", welcomeMsg);
|
|
499
|
+
}
|
|
500
|
+
}, 30);
|
|
501
|
+
return () => clearInterval(interval);
|
|
502
|
+
}
|
|
503
|
+
}, []);
|
|
504
|
+
(0, import_ink6.useInput)((input, key) => {
|
|
505
|
+
if (key.ctrl && input === "l") {
|
|
506
|
+
setMessages([]);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (key.upArrow && inputHistory.length > 0) {
|
|
510
|
+
const newIndex = historyIndex < inputHistory.length - 1 ? historyIndex + 1 : historyIndex;
|
|
511
|
+
setHistoryIndex(newIndex);
|
|
512
|
+
setInputValue(inputHistory[inputHistory.length - 1 - newIndex]);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (key.downArrow) {
|
|
516
|
+
if (historyIndex > 0) {
|
|
517
|
+
const newIndex = historyIndex - 1;
|
|
518
|
+
setHistoryIndex(newIndex);
|
|
519
|
+
setInputValue(inputHistory[inputHistory.length - 1 - newIndex]);
|
|
520
|
+
} else {
|
|
521
|
+
setHistoryIndex(-1);
|
|
522
|
+
setInputValue("");
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
const addSystemMessage = (0, import_react6.useCallback)((text) => {
|
|
528
|
+
setSystemMessages((prev) => [...prev, text]);
|
|
529
|
+
}, []);
|
|
530
|
+
const handleSubmit = (0, import_react6.useCallback)(
|
|
531
|
+
async (value) => {
|
|
532
|
+
const trimmed = value.trim();
|
|
533
|
+
if (!trimmed || isThinking) return;
|
|
534
|
+
setInputValue("");
|
|
535
|
+
setHistoryIndex(-1);
|
|
536
|
+
setInputHistory((prev) => {
|
|
537
|
+
const next = [...prev, trimmed];
|
|
538
|
+
return next.slice(-50);
|
|
539
|
+
});
|
|
540
|
+
if (trimmed.startsWith("/")) {
|
|
541
|
+
const cmd = trimmed.toLowerCase();
|
|
542
|
+
if (cmd === "/exit") {
|
|
543
|
+
setMessages((prev) => [
|
|
544
|
+
...prev,
|
|
545
|
+
{ role: "assistant", content: "\u{1F44B} Goodbye! See you next time." }
|
|
546
|
+
]);
|
|
547
|
+
setTimeout(() => {
|
|
548
|
+
exit();
|
|
549
|
+
process.exit(0);
|
|
550
|
+
}, 500);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (cmd === "/clear") {
|
|
554
|
+
clearHistory();
|
|
555
|
+
setMessages([]);
|
|
556
|
+
setTotalTokens(0);
|
|
557
|
+
setSystemMessages([]);
|
|
558
|
+
addSystemMessage("Conversation cleared.");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (cmd === "/help") {
|
|
562
|
+
setMessages((prev) => [
|
|
563
|
+
...prev,
|
|
564
|
+
{ role: "assistant", content: HELP_TEXT }
|
|
565
|
+
]);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (cmd === "/save") {
|
|
569
|
+
try {
|
|
570
|
+
const filepath = saveSession();
|
|
571
|
+
addSystemMessage(`Session saved to ${filepath}`);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
addSystemMessage("Error: Failed to save session.");
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (cmd === "/model") {
|
|
578
|
+
const modelList = MODELS2.map(
|
|
579
|
+
(m, i) => ` ${i + 1}) ${m.label}${m.id === config2.model ? " \u2713" : ""}`
|
|
580
|
+
).join("\n");
|
|
581
|
+
setMessages((prev) => [
|
|
582
|
+
...prev,
|
|
583
|
+
{
|
|
584
|
+
role: "assistant",
|
|
585
|
+
content: `Switch model:
|
|
586
|
+
${modelList}
|
|
587
|
+
|
|
588
|
+
Type: /model 1, /model 2, or /model 3`
|
|
589
|
+
}
|
|
590
|
+
]);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (cmd.startsWith("/model ")) {
|
|
594
|
+
const choice = parseInt(cmd.split(" ")[1], 10);
|
|
595
|
+
if (choice >= 1 && choice <= MODELS2.length) {
|
|
596
|
+
const newModel = MODELS2[choice - 1].id;
|
|
597
|
+
const newConfig = { ...config2, model: newModel };
|
|
598
|
+
setConfig(newConfig);
|
|
599
|
+
saveConfig({ model: newModel });
|
|
600
|
+
addSystemMessage(`Model switched to ${newModel}`);
|
|
601
|
+
} else {
|
|
602
|
+
addSystemMessage("Invalid choice. Use /model 1, 2, or 3.");
|
|
603
|
+
}
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (cmd === "/theme") {
|
|
607
|
+
const themeNames = getThemeNames();
|
|
608
|
+
const themeList = themeNames.map((t) => ` ${t}${t === config2.theme ? " \u2713" : ""}`).join("\n");
|
|
609
|
+
setMessages((prev) => [
|
|
610
|
+
...prev,
|
|
611
|
+
{
|
|
612
|
+
role: "assistant",
|
|
613
|
+
content: `Available themes:
|
|
614
|
+
${themeList}
|
|
615
|
+
|
|
616
|
+
Type: /theme dark, /theme neon, /theme retro, or /theme light`
|
|
617
|
+
}
|
|
618
|
+
]);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (cmd.startsWith("/theme ")) {
|
|
622
|
+
const choice = cmd.split(" ")[1];
|
|
623
|
+
const validThemes = getThemeNames();
|
|
624
|
+
if (validThemes.includes(choice)) {
|
|
625
|
+
const newConfig = { ...config2, theme: choice };
|
|
626
|
+
setConfig(newConfig);
|
|
627
|
+
saveConfig({ theme: choice });
|
|
628
|
+
addSystemMessage(`Theme switched to ${choice}`);
|
|
629
|
+
} else {
|
|
630
|
+
addSystemMessage(`Invalid theme. Options: ${validThemes.join(", ")}`);
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (cmd === "/key") {
|
|
635
|
+
const maskedKey = config2.apiKey ? config2.apiKey.slice(0, 10) + "..." + config2.apiKey.slice(-4) : "Not set";
|
|
636
|
+
setMessages((prev) => [
|
|
637
|
+
...prev,
|
|
638
|
+
{
|
|
639
|
+
role: "assistant",
|
|
640
|
+
content: `Current API key: ${maskedKey}
|
|
641
|
+
|
|
642
|
+
To change your key, edit ~/.config/corex/config.json or delete it and restart COREX to re-run the setup wizard.`
|
|
643
|
+
}
|
|
644
|
+
]);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
addSystemMessage(`Unknown command: ${trimmed}. Type /help for available commands.`);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const userMsg = { role: "user", content: trimmed };
|
|
651
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
652
|
+
addMessage("user", trimmed);
|
|
653
|
+
setIsThinking(true);
|
|
654
|
+
setStreamingText("");
|
|
655
|
+
const history2 = getHistory().slice(0, -1);
|
|
656
|
+
await sendMessage(
|
|
657
|
+
history2,
|
|
658
|
+
trimmed,
|
|
659
|
+
config2,
|
|
660
|
+
// onToken
|
|
661
|
+
(token) => {
|
|
662
|
+
setIsThinking(false);
|
|
663
|
+
setStreamingText((prev) => prev + token);
|
|
664
|
+
},
|
|
665
|
+
// onComplete
|
|
666
|
+
(fullText, usage) => {
|
|
667
|
+
setIsThinking(false);
|
|
668
|
+
setStreamingText("");
|
|
669
|
+
const assistantMsg = { role: "assistant", content: fullText };
|
|
670
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
671
|
+
addMessage("assistant", fullText);
|
|
672
|
+
setTotalTokens((prev) => prev + usage.totalTokens);
|
|
673
|
+
},
|
|
674
|
+
// onError
|
|
675
|
+
(error) => {
|
|
676
|
+
setIsThinking(false);
|
|
677
|
+
setStreamingText("");
|
|
678
|
+
setMessages((prev) => [
|
|
679
|
+
...prev,
|
|
680
|
+
{ role: "assistant", content: `\u26A0 ${error.message}` }
|
|
681
|
+
]);
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
},
|
|
685
|
+
[config2, isThinking, inputHistory, exit]
|
|
686
|
+
);
|
|
687
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_ink6.Box, { flexDirection: "column", height: process.stdout.rows || 24 }, /* @__PURE__ */ import_react6.default.createElement(Header_default, { theme }), /* @__PURE__ */ import_react6.default.createElement(
|
|
688
|
+
ChatHistory_default,
|
|
689
|
+
{
|
|
690
|
+
messages,
|
|
691
|
+
theme,
|
|
692
|
+
isThinking,
|
|
693
|
+
streamingText,
|
|
694
|
+
userName: config2.userName
|
|
695
|
+
}
|
|
696
|
+
), systemMessages.length > 0 && /* @__PURE__ */ import_react6.default.createElement(import_ink6.Box, { flexDirection: "column", marginBottom: 0 }, systemMessages.slice(-3).map((msg, i) => /* @__PURE__ */ import_react6.default.createElement(import_ink6.Text, { key: i, color: theme.accent, dimColor: true }, "\u2139 ", msg))), /* @__PURE__ */ import_react6.default.createElement(
|
|
697
|
+
InputBar_default,
|
|
698
|
+
{
|
|
699
|
+
value: inputValue,
|
|
700
|
+
onChange: setInputValue,
|
|
701
|
+
onSubmit: handleSubmit,
|
|
702
|
+
theme,
|
|
703
|
+
isDisabled: isThinking
|
|
704
|
+
}
|
|
705
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
706
|
+
StatusBar_default,
|
|
707
|
+
{
|
|
708
|
+
model: config2.model,
|
|
709
|
+
totalTokens,
|
|
710
|
+
themeName: config2.theme,
|
|
711
|
+
theme
|
|
712
|
+
}
|
|
713
|
+
));
|
|
714
|
+
};
|
|
715
|
+
var app_default = App;
|
|
716
|
+
|
|
717
|
+
// src/index.ts
|
|
718
|
+
import_dotenv.default.config();
|
|
719
|
+
var nodeVersion = parseInt(process.version.slice(1).split(".")[0], 10);
|
|
720
|
+
if (nodeVersion < 18) {
|
|
721
|
+
console.error("COREX requires Node.js 18 or higher.");
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
async function main() {
|
|
725
|
+
try {
|
|
726
|
+
if (isFirstRun()) {
|
|
727
|
+
await runFirstRunWizard();
|
|
728
|
+
}
|
|
729
|
+
const config2 = loadConfig();
|
|
730
|
+
if (process.env.ANTHROPIC_API_KEY && !config2.apiKey) {
|
|
731
|
+
config2.apiKey = process.env.ANTHROPIC_API_KEY;
|
|
732
|
+
}
|
|
733
|
+
initAI(config2.apiKey);
|
|
734
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
735
|
+
const { waitUntilExit } = (0, import_ink7.render)(import_react7.default.createElement(app_default, { config: config2 }));
|
|
736
|
+
await waitUntilExit();
|
|
737
|
+
} catch (err) {
|
|
738
|
+
console.error("Failed to start COREX:", err.message);
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
main();
|