@oxecli/oxe 1.0.50 → 1.0.52
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/cli.js +67 -63
- package/dist/config.js +35 -35
- package/dist/engine.js +42 -42
- package/dist/skills.js +10 -4
- package/dist/system.js +2 -2
- package/dist/tools.js +103 -65
- package/dist/ui.js +242 -282
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { loadOrPrompt, default_reasoning_effort, default_model, max_action_chars, max_resume_history_items, } from "./config.js";
|
|
4
|
+
import { loadOrPrompt, default_reasoning_effort, default_model, max_action_chars, max_resume_history_items, runtimeOsSummary, } from "./config.js";
|
|
5
5
|
import { InferenceEngine, estimateTokens } from "./engine.js";
|
|
6
6
|
import { saveSession, loadSession, listSessions, nextSessionId, conversationLabel, COMMAND_HELP, toolOutputFailed, } from "./sessions.js";
|
|
7
7
|
import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, formatToolAction, truncateEllipsis, renderPanel, renderTableString, enableRawStdin, disableRawStdin, waitRawKey, } from "./ui.js";
|
|
@@ -39,10 +39,17 @@ export class CLI {
|
|
|
39
39
|
return centeredCell(value, `[bold ${color}]${value}[/bold ${color}]`);
|
|
40
40
|
};
|
|
41
41
|
const description = "A focused terminal workspace for building and shipping software.";
|
|
42
|
-
renderPanel("[bold white]Terminal
|
|
42
|
+
renderPanel("[bold white]Terminal Coding Agent[/bold white]\n" +
|
|
43
43
|
`[dim]${description}[/dim]\n\n` +
|
|
44
|
-
labelCell("Version") +
|
|
45
|
-
|
|
44
|
+
labelCell("Version") +
|
|
45
|
+
labelCell("Model") +
|
|
46
|
+
labelCell("Status") +
|
|
47
|
+
"\n" +
|
|
48
|
+
valueCell(version, "cyan") +
|
|
49
|
+
valueCell(model, "blue") +
|
|
50
|
+
valueCell("Ready", "green") +
|
|
51
|
+
"\n\n" +
|
|
52
|
+
`[dim]Environment: ${runtimeOsSummary()}[/dim]`, "Oxe", "Type /help for commands · Ctrl+C to interrupt", false, "90", "left");
|
|
46
53
|
}
|
|
47
54
|
saveSession(engine) {
|
|
48
55
|
if (!this.inputItems.length)
|
|
@@ -59,11 +66,9 @@ export class CLI {
|
|
|
59
66
|
});
|
|
60
67
|
}
|
|
61
68
|
printToolEntry(started, text, status) {
|
|
62
|
-
|
|
63
|
-
process.stdout.write(`\x1b[2m${truncateEllipsis(String(started), max_action_chars, "text")}\x1b[0m\n`);
|
|
64
|
-
}
|
|
69
|
+
const icon = status === "failed" ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
|
|
65
70
|
const style = status === "failed" ? "\x1b[31m" : "\x1b[32m";
|
|
66
|
-
process.stdout.write(`${style}${truncateEllipsis(String(text), max_action_chars, "text")}\x1b[0m\n`);
|
|
71
|
+
process.stdout.write(`${icon} ${style}${truncateEllipsis(String(text), max_action_chars, "text")}\x1b[0m\n`);
|
|
67
72
|
}
|
|
68
73
|
printAssistantBlock(text) {
|
|
69
74
|
const rendered = aiMarkdown(text);
|
|
@@ -80,25 +85,21 @@ export class CLI {
|
|
|
80
85
|
process.stdout.write("\n");
|
|
81
86
|
}
|
|
82
87
|
async showHelp() {
|
|
83
|
-
// Mirror Python's `_show_help`: a bordered table panel with the command
|
|
84
|
-
// column bold (with `<arg>` placeholders cyan) and descriptions dim, inside
|
|
85
|
-
// a panel titled "Commands · Enter / Ctrl+C closes". In a TTY it stays on
|
|
86
|
-
// screen until Enter/Ctrl+C/arrows/q closes it.
|
|
87
88
|
const rows = COMMAND_HELP.map(([cmd, desc]) => {
|
|
88
89
|
const styled = cmd.replace(/(<[^>]+>)/g, "\x1b[36m$1\x1b[0m");
|
|
89
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
cells: [`\x1b[1;36m${styled}\x1b[0m`, `\x1b[2m${desc}\x1b[0m`],
|
|
92
|
+
};
|
|
90
93
|
});
|
|
91
94
|
const panel = renderTableString(rows, {
|
|
92
|
-
title: "Commands · Enter
|
|
95
|
+
title: "Commands · Press Enter or Ctrl+C to close",
|
|
93
96
|
borderStyle: "90",
|
|
94
97
|
expand: false,
|
|
95
98
|
titleAlign: "left",
|
|
96
|
-
colGap:
|
|
99
|
+
colGap: 3,
|
|
97
100
|
});
|
|
98
|
-
// Keep one blank row between the previous response/footer and the panel.
|
|
99
101
|
process.stdout.write("\n" + panel + "\n");
|
|
100
102
|
if (process.stdin.isTTY) {
|
|
101
|
-
// Wait for a close key, then erase the panel (mirrors Python's Live).
|
|
102
103
|
enableRawStdin();
|
|
103
104
|
try {
|
|
104
105
|
for (;;) {
|
|
@@ -109,22 +110,29 @@ export class CLI {
|
|
|
109
110
|
finally {
|
|
110
111
|
disableRawStdin();
|
|
111
112
|
}
|
|
112
|
-
// Cursor sits one line below the panel; move up to its top border and
|
|
113
|
-
// clear downward so the whole panel disappears and the cursor lands back
|
|
114
|
-
// on the fresh line before the prompt.
|
|
115
113
|
const n = panel.split("\n").length;
|
|
116
|
-
// Include the leading separator row in the cleanup so the next prompt
|
|
117
|
-
// creates exactly one fresh separator instead of duplicating it.
|
|
118
114
|
process.stdout.write(`\x1b[${n + 1}A\r\x1b[J`);
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
117
|
async awaitRawCloseKey() {
|
|
122
118
|
const k = await waitRawKey();
|
|
123
|
-
const closeNames = [
|
|
119
|
+
const closeNames = [
|
|
120
|
+
"escape",
|
|
121
|
+
"return",
|
|
122
|
+
"enter",
|
|
123
|
+
"up",
|
|
124
|
+
"down",
|
|
125
|
+
"left",
|
|
126
|
+
"right",
|
|
127
|
+
];
|
|
124
128
|
if (k.ctrl && (k.name === "c" || k.name === "z"))
|
|
125
129
|
return true;
|
|
126
|
-
if (k.str === "q" ||
|
|
130
|
+
if (k.str === "q" ||
|
|
131
|
+
k.str === "Q" ||
|
|
132
|
+
k.str === "\x1b" ||
|
|
133
|
+
closeNames.includes(k.name)) {
|
|
127
134
|
return true;
|
|
135
|
+
}
|
|
128
136
|
return false;
|
|
129
137
|
}
|
|
130
138
|
renderHistory(items, maxItems = max_resume_history_items) {
|
|
@@ -145,13 +153,18 @@ export class CLI {
|
|
|
145
153
|
renders.push({ kind: "user", text: c, paste_spans: it["paste_spans"] });
|
|
146
154
|
}
|
|
147
155
|
else if (Array.isArray(c) && messageText(it).trim()) {
|
|
148
|
-
renders.push({
|
|
156
|
+
renders.push({
|
|
157
|
+
kind: "user",
|
|
158
|
+
text: messageText(it),
|
|
159
|
+
paste_spans: it["paste_spans"],
|
|
160
|
+
});
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
163
|
else if (typ === "message") {
|
|
152
164
|
const text = messageText(it);
|
|
153
|
-
if (text.trim())
|
|
165
|
+
if (text.trim()) {
|
|
154
166
|
renders.push({ kind: "assistant", text, footer: it["footer"] ?? "" });
|
|
167
|
+
}
|
|
155
168
|
}
|
|
156
169
|
else if (typ === "function_call") {
|
|
157
170
|
const name = it["name"] ?? "tool";
|
|
@@ -177,7 +190,7 @@ export class CLI {
|
|
|
177
190
|
const kind = r["kind"];
|
|
178
191
|
const payload = r["text"];
|
|
179
192
|
if (kind === "user") {
|
|
180
|
-
process.stdout.write(`\x1b[
|
|
193
|
+
process.stdout.write(`\x1b[1;36m❯\x1b[0m ${userDisplayText(payload, r["paste_spans"])}\n`);
|
|
181
194
|
}
|
|
182
195
|
else if (kind === "assistant") {
|
|
183
196
|
this.printAssistantBlock(payload);
|
|
@@ -208,7 +221,7 @@ export class CLI {
|
|
|
208
221
|
const typ = e["type"];
|
|
209
222
|
const text = e["text"] ?? "";
|
|
210
223
|
if (typ === "user") {
|
|
211
|
-
process.stdout.write(`\x1b[
|
|
224
|
+
process.stdout.write(`\x1b[1;36m❯\x1b[0m ${userDisplayText(text, e["paste_spans"])}\n`);
|
|
212
225
|
}
|
|
213
226
|
else if (typ === "assistant") {
|
|
214
227
|
this.printAssistantBlock(text);
|
|
@@ -217,8 +230,7 @@ export class CLI {
|
|
|
217
230
|
this.printToolEntry(e["started"], text, e["status"] ?? "");
|
|
218
231
|
}
|
|
219
232
|
else if (typ === "footer") {
|
|
220
|
-
|
|
221
|
-
process.stdout.write(aiMarkdown(text) + "\n");
|
|
233
|
+
process.stdout.write(mutedMarkdown(text) + "\n");
|
|
222
234
|
}
|
|
223
235
|
else {
|
|
224
236
|
process.stdout.write(mutedMarkdown(text) + "\n");
|
|
@@ -229,7 +241,9 @@ export class CLI {
|
|
|
229
241
|
}
|
|
230
242
|
conversationRow(rec) {
|
|
231
243
|
const sid = String(rec["id"] ?? "?");
|
|
232
|
-
const updated = String(rec["updated_at"] ?? "")
|
|
244
|
+
const updated = String(rec["updated_at"] ?? "")
|
|
245
|
+
.slice(0, 16)
|
|
246
|
+
.replace("T", " ");
|
|
233
247
|
const label = truncateLabel(rec["label"]);
|
|
234
248
|
const items = rec["input_items"] ?? [];
|
|
235
249
|
const toks = estimateTokens(items).toLocaleString();
|
|
@@ -242,7 +256,6 @@ export class CLI {
|
|
|
242
256
|
process.stdout.write(this.conversationRow(rec) + "\n");
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
|
-
/** Build the interactive picker panel (mirrors Python `_render_picker`). */
|
|
246
259
|
renderPickerPanel(recs, selected, visible = 8) {
|
|
247
260
|
const total = recs.length;
|
|
248
261
|
const half = Math.floor(visible / 2);
|
|
@@ -252,17 +265,19 @@ export class CLI {
|
|
|
252
265
|
for (let idx = lo; idx < hi; idx++) {
|
|
253
266
|
const rec = recs[idx];
|
|
254
267
|
const sid = String(rec["id"] ?? "?");
|
|
255
|
-
const updated = String(rec["updated_at"] ?? "")
|
|
268
|
+
const updated = String(rec["updated_at"] ?? "")
|
|
269
|
+
.slice(0, 16)
|
|
270
|
+
.replace("T", " ");
|
|
256
271
|
const label = truncateLabel(rec["label"]);
|
|
257
272
|
const items = rec["input_items"] ?? [];
|
|
258
273
|
const toks = estimateTokens(items).toLocaleString();
|
|
259
274
|
const selectedRow = idx === selected;
|
|
260
|
-
const content = `${selectedRow ? "\x1b[
|
|
261
|
-
`${selectedRow ? "\x1b[
|
|
262
|
-
`${selectedRow ? "\x1b[37m" : ""}
|
|
263
|
-
`${selectedRow ? "\x1b[
|
|
275
|
+
const content = `${selectedRow ? "\x1b[1;37m" : "\x1b[1m"}${sid.padStart(4)}\x1b[0m` +
|
|
276
|
+
`${selectedRow ? "\x1b[2;36m" : "\x1b[2m"} ${updated}\x1b[0m` +
|
|
277
|
+
`${selectedRow ? " \x1b[1;37m" : " "}${label}\x1b[0m` +
|
|
278
|
+
`${selectedRow ? "\x1b[2;36m" : "\x1b[2m"} ${items.length} items · ${toks} tok\x1b[0m`;
|
|
264
279
|
rows.push({
|
|
265
|
-
cells: [selectedRow ? "\x1b[
|
|
280
|
+
cells: [selectedRow ? "\x1b[1;36m❯\x1b[0m" : "", content],
|
|
266
281
|
});
|
|
267
282
|
}
|
|
268
283
|
const below = total - hi;
|
|
@@ -275,7 +290,7 @@ export class CLI {
|
|
|
275
290
|
footer = `\x1b[2m▼ ${below} more\x1b[0m`;
|
|
276
291
|
if (footer)
|
|
277
292
|
rows.push({ cells: ["", footer] });
|
|
278
|
-
const title = `Conversations \x1b[
|
|
293
|
+
const title = `Conversations \x1b[1;36m${lo + 1}-${hi}/${total}\x1b[0m (↑/↓ · Quit: q · Enter to select)`;
|
|
279
294
|
return renderTableString(rows, {
|
|
280
295
|
title,
|
|
281
296
|
borderStyle: "90",
|
|
@@ -285,7 +300,6 @@ export class CLI {
|
|
|
285
300
|
colWidths: [3, undefined],
|
|
286
301
|
});
|
|
287
302
|
}
|
|
288
|
-
/** Interactive arrow up/down project picker (mirrors Python `_pick_conversation`). */
|
|
289
303
|
async pickConversation() {
|
|
290
304
|
const recs = listSessions();
|
|
291
305
|
if (!recs.length) {
|
|
@@ -358,11 +372,9 @@ export class CLI {
|
|
|
358
372
|
clearScreen();
|
|
359
373
|
this.renderHeader(engine.modelName);
|
|
360
374
|
process.stdout.write("\n");
|
|
361
|
-
process.stdout.write(`\x1b[2mResumed:\x1b[0m ${truncateLabel(rec["label"])}\n`);
|
|
362
|
-
process.stdout.write(
|
|
363
|
-
|
|
364
|
-
process.stdout.write("\n");
|
|
365
|
-
renderPanel("Conversation history", "Restored");
|
|
375
|
+
process.stdout.write(`\x1b[2mResumed:\x1b[0m \x1b[1m${truncateLabel(rec["label"])}\x1b[0m\n`);
|
|
376
|
+
process.stdout.write(`\x1b[2m(${this.inputItems.length} items · ${estimateTokens(this.inputItems).toLocaleString()} tokens · Effort: \x1b[1;36m${engine.reasoningEffort}\x1b[0m\x1b[2m)\x1b[0m\n\n`);
|
|
377
|
+
renderPanel("Conversation history restored", "Restored");
|
|
366
378
|
process.stdout.write("\n");
|
|
367
379
|
if (this.story.length)
|
|
368
380
|
this.renderStory(this.story);
|
|
@@ -372,7 +384,6 @@ export class CLI {
|
|
|
372
384
|
async replLoop(engine) {
|
|
373
385
|
for (;;) {
|
|
374
386
|
engine.inQuery = false;
|
|
375
|
-
let rawPrompt;
|
|
376
387
|
let queryStarted = false;
|
|
377
388
|
try {
|
|
378
389
|
const [input, spans] = await askBottomPrompt("You", "❯", this.promptHistory);
|
|
@@ -419,7 +430,7 @@ export class CLI {
|
|
|
419
430
|
const effort = parts[1]?.toLowerCase() ?? "";
|
|
420
431
|
if (["none", "low", "high"].includes(effort)) {
|
|
421
432
|
engine.reasoningEffort = effort;
|
|
422
|
-
process.stdout.write(`\n\x1b[2mReasoning effort set to ${effort}.\x1b[0m\n`);
|
|
433
|
+
process.stdout.write(`\n\x1b[32m✓\x1b[0m \x1b[2mReasoning effort set to \x1b[1;36m${effort}\x1b[0m\x1b[2m.\x1b[0m\n`);
|
|
423
434
|
}
|
|
424
435
|
else {
|
|
425
436
|
process.stdout.write("\n");
|
|
@@ -427,7 +438,7 @@ export class CLI {
|
|
|
427
438
|
}
|
|
428
439
|
continue;
|
|
429
440
|
}
|
|
430
|
-
process.stdout.write(`\n\x1b[
|
|
441
|
+
process.stdout.write(`\n\x1b[1;36m❯\x1b[0m ${userDisplayText(input, spans)}\n\n`);
|
|
431
442
|
queryStarted = true;
|
|
432
443
|
await engine.executeQuery(input, this.inputItems, this.story, spans);
|
|
433
444
|
queryStarted = false;
|
|
@@ -437,20 +448,21 @@ export class CLI {
|
|
|
437
448
|
if (err?.message === "eof") {
|
|
438
449
|
this.saveSession(engine);
|
|
439
450
|
await engine.cleanupStoredResponses();
|
|
440
|
-
process.stdout.write("\nSession closing via exit interrupt hook.\n");
|
|
451
|
+
process.stdout.write("\nSession closing via exit interrupt hook.\n\n");
|
|
441
452
|
break;
|
|
442
453
|
}
|
|
443
454
|
if (err?.message === "interrupt") {
|
|
444
455
|
const wasActive = queryStarted || engine.inQuery;
|
|
445
456
|
if (wasActive && !engine.queryHasOutput) {
|
|
446
|
-
process.stdout.write(aiMarkdown("What else can I help with?") + "\n");
|
|
457
|
+
process.stdout.write(aiMarkdown("What else can I help you with?") + "\n");
|
|
447
458
|
}
|
|
448
459
|
engine.inQuery = false;
|
|
449
|
-
|
|
450
|
-
|
|
460
|
+
if (this.inputItems.length &&
|
|
461
|
+
this.inputItems[this.inputItems.length - 1]["role"] === "user") {
|
|
451
462
|
this.inputItems.pop();
|
|
452
463
|
}
|
|
453
|
-
if (this.story.length &&
|
|
464
|
+
if (this.story.length &&
|
|
465
|
+
this.story[this.story.length - 1]["type"] === "user") {
|
|
454
466
|
this.story.pop();
|
|
455
467
|
}
|
|
456
468
|
if (this.interruptPending) {
|
|
@@ -460,13 +472,10 @@ export class CLI {
|
|
|
460
472
|
break;
|
|
461
473
|
}
|
|
462
474
|
this.interruptPending = true;
|
|
463
|
-
// clearBox leaves the cursor on the leading blank row; the newline
|
|
464
|
-
// below places the message one row lower, giving one blank above it
|
|
465
|
-
// (mirrors Python's console.print()).
|
|
466
475
|
process.stdout.write("\n");
|
|
467
476
|
process.stdout.write(wasActive
|
|
468
|
-
? "\x1b[2mInterrupted agent. Press Ctrl+C again to
|
|
469
|
-
: "\x1b[2mNo active
|
|
477
|
+
? "\x1b[2mInterrupted agent. Press Ctrl+C again to exit, or type a new prompt.\x1b[0m\n"
|
|
478
|
+
: "\x1b[2mNo active query. Press Ctrl+C again to exit.\x1b[0m\n");
|
|
470
479
|
continue;
|
|
471
480
|
}
|
|
472
481
|
throw err;
|
|
@@ -483,9 +492,6 @@ export async function main() {
|
|
|
483
492
|
}
|
|
484
493
|
catch (err) {
|
|
485
494
|
if (err?.message === "interrupt" || err?.message === "eof") {
|
|
486
|
-
// cleanup() already moved to a fresh line; the leading newline below
|
|
487
|
-
// leaves that row as a blank (one under the prompt), and the trailing
|
|
488
|
-
// double-newline leaves a blank row under the message too.
|
|
489
495
|
process.stdout.write("\nClosing terminal session. Goodbye!\n\n");
|
|
490
496
|
process.exit(0);
|
|
491
497
|
return;
|
|
@@ -512,7 +518,5 @@ export async function main() {
|
|
|
512
518
|
if (typeof client?.close === "function")
|
|
513
519
|
await client.close();
|
|
514
520
|
}
|
|
515
|
-
// The prompt's readline keypress listener is never removed and stdin remains
|
|
516
|
-
// resumed, so Node won't exit on its own after the loop ends. Exit explicitly.
|
|
517
521
|
process.exit(0);
|
|
518
522
|
}
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,6 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { Spinner } from "./ui.js";
|
|
6
5
|
// ---------------------------------------------------------------------------
|
|
7
6
|
// Paths
|
|
8
7
|
// ---------------------------------------------------------------------------
|
|
@@ -20,7 +19,7 @@ export const max_output_chars = 8000;
|
|
|
20
19
|
export const max_read_lines = 1500;
|
|
21
20
|
export const max_read_line_chars = 500;
|
|
22
21
|
export const max_read_file_bytes = 50 * 1024 * 1024;
|
|
23
|
-
export const max_diff_lines =
|
|
22
|
+
export const max_diff_lines = 24;
|
|
24
23
|
export const max_diff_context_lines = 6;
|
|
25
24
|
export const max_diff_line_chars = 400;
|
|
26
25
|
export const max_agent_steps = 50;
|
|
@@ -40,7 +39,7 @@ export const max_bash_timeout_seconds = 300;
|
|
|
40
39
|
export const strict_max_properties = 2;
|
|
41
40
|
export const ai_style = "grey62";
|
|
42
41
|
// ---------------------------------------------------------------------------
|
|
43
|
-
// .env loading
|
|
42
|
+
// .env loading & cloud defaults
|
|
44
43
|
// ---------------------------------------------------------------------------
|
|
45
44
|
function loadEnvFile() {
|
|
46
45
|
const envPath = path.join(PROJECT_DIR, ".env");
|
|
@@ -69,17 +68,13 @@ function loadEnvFile() {
|
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
70
|
loadEnvFile();
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"Set it in your shell environment, or add it to a .env file next to this package.\n");
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
export const OXE_SUPABASE_URL_EXPORT = OXE_SUPABASE_URL;
|
|
80
|
-
export const OXE_SUPABASE_ANON_KEY_EXPORT = OXE_SUPABASE_ANON_KEY;
|
|
71
|
+
const DEFAULT_SUPABASE_URL = "https://ajmhaekffqzzrdfwsxfu.supabase.co";
|
|
72
|
+
const DEFAULT_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFqbWhhZWtmZnF6enJkZndzeGZ1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODczMzU4MzQsImV4cCI6MjEwMjkxMTgzNH0.17sukij-R3L0kecR6x2MILZBO4M1ZZ4mkyqCU5yrVyM";
|
|
73
|
+
export const OXE_SUPABASE_URL_EXPORT = process.env.OXE_SUPABASE_URL || DEFAULT_SUPABASE_URL;
|
|
74
|
+
export const OXE_SUPABASE_ANON_KEY_EXPORT = process.env.OXE_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
|
|
81
75
|
export const OXE_KEY_PREFIX = "oxe_live_";
|
|
82
|
-
export const default_base_url = process.env.OXE_BASE_URL ||
|
|
76
|
+
export const default_base_url = process.env.OXE_BASE_URL ||
|
|
77
|
+
`${OXE_SUPABASE_URL_EXPORT}/functions/v1/chat-proxy`;
|
|
83
78
|
export const default_model = process.env.OXE_MODEL_NAME || "deepseek-v4-flash";
|
|
84
79
|
export const default_reasoning_effort = "low";
|
|
85
80
|
// ---------------------------------------------------------------------------
|
|
@@ -90,17 +85,23 @@ export function runtimeOsSummary() {
|
|
|
90
85
|
}
|
|
91
86
|
export function osPrefix() {
|
|
92
87
|
const isWin = process.platform === "win32";
|
|
93
|
-
const shell = isWin ? "cmd.exe" : "POSIX sh";
|
|
88
|
+
const shell = isWin ? "PowerShell / cmd.exe" : "POSIX sh";
|
|
94
89
|
return (`Running environment: ${runtimeOsSummary()}. ` +
|
|
95
90
|
`Shell commands run via ${shell} ` +
|
|
96
91
|
"with `shell=True`; use commands, quoting, and path separators " +
|
|
97
92
|
"appropriate to this OS.\n\n");
|
|
98
93
|
}
|
|
99
94
|
export const IGNORED_DIRS_BY_CATEGORY = [
|
|
100
|
-
["Version control", [".git", ".svn"]],
|
|
101
|
-
[
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
["Version control", [".git", ".svn", ".hg"]],
|
|
96
|
+
[
|
|
97
|
+
"Dependencies & environments",
|
|
98
|
+
["node_modules", "__pycache__", ".venv", "venv", ".env"],
|
|
99
|
+
],
|
|
100
|
+
["Build output", ["dist", "build", "target", ".tox", "out", ".turbo"]],
|
|
101
|
+
[
|
|
102
|
+
"Tool caches & IDE files",
|
|
103
|
+
[".mypy_cache", ".pytest_cache", ".next", ".nuxt", ".idea", ".vscode"],
|
|
104
|
+
],
|
|
104
105
|
];
|
|
105
106
|
export const ignoredDirs = new Set(IGNORED_DIRS_BY_CATEGORY.flatMap(([, dirs]) => dirs));
|
|
106
107
|
export const IGNORED_DIRS_SYSTEM_TEXT = IGNORED_DIRS_BY_CATEGORY.map(([cat, dirs]) => `- ${dirs.map((d) => `\`${d}\``).join(", ")} — ${cat}`).join("\n");
|
|
@@ -126,7 +127,8 @@ export const SYSTEM_PROMPT_BODY = "You are a terminal-based coding agent named O
|
|
|
126
127
|
"- edit_file: make a small, precise change by replacing one unique snippet of text.\n" +
|
|
127
128
|
"- bash: run shell commands (tests, git, package managers, build tools, etc).\n" +
|
|
128
129
|
"- glob: find files by name pattern.\n" +
|
|
129
|
-
"- grep: find text/regex matches across files.\n
|
|
130
|
+
"- grep: find text/regex matches across files.\n" +
|
|
131
|
+
"- load_skill: load domain instructions from an available skill.\n\n" +
|
|
130
132
|
"Search scope:\n" +
|
|
131
133
|
"- `glob` and `grep` automatically skip the following directories, so their\n" +
|
|
132
134
|
" contents never appear in results and are never searched:\n" +
|
|
@@ -145,31 +147,28 @@ export const SYSTEM_PROMPT_BODY = "You are a terminal-based coding agent named O
|
|
|
145
147
|
"consequence in your response.\n";
|
|
146
148
|
export const API_KEY_MAX = 64;
|
|
147
149
|
import { validateOxeApiKey } from "./api.js";
|
|
148
|
-
import { renderPanel, markupToAnsi, hideCursor, showCursor } from "./ui.js";
|
|
150
|
+
import { renderPanel, markupToAnsi, hideCursor, showCursor, Spinner } from "./ui.js";
|
|
149
151
|
export async function loadOrPrompt() {
|
|
150
|
-
let api_key = "";
|
|
152
|
+
let api_key = (process.env.OXE_API_KEY || "").trim();
|
|
151
153
|
let key_data = {};
|
|
152
154
|
for (;;) {
|
|
153
155
|
if (!api_key) {
|
|
154
|
-
// The CLI banner ends with a trailing newline; write one more blank so
|
|
155
|
-
// there is exactly one empty line between the banner and this panel
|
|
156
|
-
// (mirrors Python's console.print() after the banner).
|
|
157
156
|
process.stdout.write("\n");
|
|
158
157
|
renderPanel("[bold white]Oxe Desktop Authentication[/bold white]\n\n" +
|
|
159
|
-
`[dim]Only valid Oxe API keys (starting with [bold]${OXE_KEY_PREFIX}[/bold])\n` +
|
|
160
|
-
"generated on your Oxe
|
|
161
|
-
"Get your key at: [bold underline]http://localhost:5173/dashboard/api-keys[/bold underline][/dim]", "Oxe Cloud Access", "", false);
|
|
158
|
+
`[dim]Only valid Oxe API keys (starting with [bold cyan]${OXE_KEY_PREFIX}[/bold cyan])\n` +
|
|
159
|
+
"generated on your Oxe dashboard are authorized to run this agent.\n\n" +
|
|
160
|
+
"Get your key at: [bold underline]http://localhost:5173/dashboard/api-keys[/bold underline][/dim]", "Oxe Cloud Access", "", false, "90", "left");
|
|
162
161
|
process.stdout.write("\n");
|
|
163
162
|
api_key = await promptApiKey("Enter Oxe API Key: ");
|
|
164
163
|
if (!api_key) {
|
|
165
|
-
process.stdout.write("\n" +
|
|
164
|
+
process.stdout.write("\n" +
|
|
165
|
+
markupToAnsi("[red]Error: Key cannot be blank. Exiting application.[/red]") +
|
|
166
|
+
"\n\n");
|
|
166
167
|
process.exit(1);
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
process.stdout.write("\n");
|
|
170
171
|
const authSpinner = new Spinner();
|
|
171
|
-
// promptApiKey echoes input with the cursor visible; hide it while the
|
|
172
|
-
// validating-key label runs so no stray block cursor sits beside it.
|
|
173
172
|
hideCursor();
|
|
174
173
|
authSpinner.start("Authenticating key with Oxe Cloud…");
|
|
175
174
|
const validation = await validateOxeApiKey(api_key);
|
|
@@ -182,8 +181,7 @@ export async function loadOrPrompt() {
|
|
|
182
181
|
break;
|
|
183
182
|
}
|
|
184
183
|
else {
|
|
185
|
-
process.stdout.write(`\x1b[31mAuthentication Error:\x1b[0m ${validation.error}\n`);
|
|
186
|
-
process.stdout.write("\n");
|
|
184
|
+
process.stdout.write(`\x1b[31mAuthentication Error:\x1b[0m ${validation.error}\n\n`);
|
|
187
185
|
api_key = "";
|
|
188
186
|
}
|
|
189
187
|
}
|
|
@@ -224,10 +222,12 @@ export async function loadOrPrompt() {
|
|
|
224
222
|
* Falls back to a plain readline read when stdin isn't a TTY (e.g. piped input).
|
|
225
223
|
*/
|
|
226
224
|
export async function promptApiKey(prompt) {
|
|
227
|
-
// Non-interactive (piped) input: just read a line, no masking possible.
|
|
228
225
|
if (!process.stdin.isTTY) {
|
|
229
226
|
const { default: readline } = await import("node:readline/promises");
|
|
230
|
-
const rl = readline.createInterface({
|
|
227
|
+
const rl = readline.createInterface({
|
|
228
|
+
input: process.stdin,
|
|
229
|
+
output: process.stdout,
|
|
230
|
+
});
|
|
231
231
|
try {
|
|
232
232
|
const answer = await rl.question(prompt);
|
|
233
233
|
return answer.replace(/\r?\n$/, "").trim();
|
|
@@ -244,7 +244,7 @@ export async function promptApiKey(prompt) {
|
|
|
244
244
|
const onData = (buf) => {
|
|
245
245
|
for (const b of buf) {
|
|
246
246
|
if (b === 3) {
|
|
247
|
-
// Ctrl+C
|
|
247
|
+
// Ctrl+C
|
|
248
248
|
cleanup();
|
|
249
249
|
reject(new Error("interrupt"));
|
|
250
250
|
return;
|