@oxecli/oxe 1.0.51 → 1.0.53
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 +69 -65
- package/dist/config.js +35 -35
- package/dist/engine.js +56 -43
- package/dist/skills.js +10 -4
- package/dist/system.js +2 -2
- package/dist/tools.js +134 -75
- package/dist/ui.js +396 -297
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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
|
-
import { saveSession, loadSession, listSessions, nextSessionId, conversationLabel, COMMAND_HELP, toolOutputFailed, } from "./sessions.js";
|
|
6
|
+
import { saveSession, loadSession, listSessions, nextSessionId, conversationLabel, COMMAND_HELP, stripOrphanCalls, toolOutputFailed, } from "./sessions.js";
|
|
7
7
|
import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, formatToolAction, truncateEllipsis, renderPanel, renderTableString, enableRawStdin, disableRawStdin, waitRawKey, } from "./ui.js";
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
@@ -26,7 +26,7 @@ export class CLI {
|
|
|
26
26
|
}
|
|
27
27
|
renderHeader(model = default_model) {
|
|
28
28
|
const version = packageInfo.version ?? "unknown";
|
|
29
|
-
const cellWidth =
|
|
29
|
+
const cellWidth = 22;
|
|
30
30
|
const centeredCell = (value, render) => {
|
|
31
31
|
const left = Math.max(0, Math.floor((cellWidth - value.length) / 2));
|
|
32
32
|
const right = Math.max(0, cellWidth - value.length - left);
|
|
@@ -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,6 @@ export class CLI {
|
|
|
217
230
|
this.printToolEntry(e["started"], text, e["status"] ?? "");
|
|
218
231
|
}
|
|
219
232
|
else if (typ === "footer") {
|
|
220
|
-
// Footer metadata is muted, with dynamic duration and token values
|
|
221
|
-
// restored to white by the embedded ANSI value styles.
|
|
222
233
|
process.stdout.write(mutedMarkdown(text) + "\n");
|
|
223
234
|
}
|
|
224
235
|
else {
|
|
@@ -230,7 +241,9 @@ export class CLI {
|
|
|
230
241
|
}
|
|
231
242
|
conversationRow(rec) {
|
|
232
243
|
const sid = String(rec["id"] ?? "?");
|
|
233
|
-
const updated = String(rec["updated_at"] ?? "")
|
|
244
|
+
const updated = String(rec["updated_at"] ?? "")
|
|
245
|
+
.slice(0, 16)
|
|
246
|
+
.replace("T", " ");
|
|
234
247
|
const label = truncateLabel(rec["label"]);
|
|
235
248
|
const items = rec["input_items"] ?? [];
|
|
236
249
|
const toks = estimateTokens(items).toLocaleString();
|
|
@@ -243,7 +256,6 @@ export class CLI {
|
|
|
243
256
|
process.stdout.write(this.conversationRow(rec) + "\n");
|
|
244
257
|
}
|
|
245
258
|
}
|
|
246
|
-
/** Build the interactive picker panel (mirrors Python `_render_picker`). */
|
|
247
259
|
renderPickerPanel(recs, selected, visible = 8) {
|
|
248
260
|
const total = recs.length;
|
|
249
261
|
const half = Math.floor(visible / 2);
|
|
@@ -253,17 +265,19 @@ export class CLI {
|
|
|
253
265
|
for (let idx = lo; idx < hi; idx++) {
|
|
254
266
|
const rec = recs[idx];
|
|
255
267
|
const sid = String(rec["id"] ?? "?");
|
|
256
|
-
const updated = String(rec["updated_at"] ?? "")
|
|
268
|
+
const updated = String(rec["updated_at"] ?? "")
|
|
269
|
+
.slice(0, 16)
|
|
270
|
+
.replace("T", " ");
|
|
257
271
|
const label = truncateLabel(rec["label"]);
|
|
258
272
|
const items = rec["input_items"] ?? [];
|
|
259
273
|
const toks = estimateTokens(items).toLocaleString();
|
|
260
274
|
const selectedRow = idx === selected;
|
|
261
|
-
const content = `${selectedRow ? "\x1b[
|
|
262
|
-
`${selectedRow ? "\x1b[
|
|
263
|
-
`${selectedRow ? "\x1b[37m" : ""}
|
|
264
|
-
`${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`;
|
|
265
279
|
rows.push({
|
|
266
|
-
cells: [selectedRow ? "\x1b[
|
|
280
|
+
cells: [selectedRow ? "\x1b[1;36m❯\x1b[0m" : "", content],
|
|
267
281
|
});
|
|
268
282
|
}
|
|
269
283
|
const below = total - hi;
|
|
@@ -276,7 +290,7 @@ export class CLI {
|
|
|
276
290
|
footer = `\x1b[2m▼ ${below} more\x1b[0m`;
|
|
277
291
|
if (footer)
|
|
278
292
|
rows.push({ cells: ["", footer] });
|
|
279
|
-
const title = `Conversations \x1b[
|
|
293
|
+
const title = `Conversations \x1b[1;36m${lo + 1}-${hi}/${total}\x1b[0m (↑/↓ · Quit: q · Enter to select)`;
|
|
280
294
|
return renderTableString(rows, {
|
|
281
295
|
title,
|
|
282
296
|
borderStyle: "90",
|
|
@@ -286,7 +300,6 @@ export class CLI {
|
|
|
286
300
|
colWidths: [3, undefined],
|
|
287
301
|
});
|
|
288
302
|
}
|
|
289
|
-
/** Interactive arrow up/down project picker (mirrors Python `_pick_conversation`). */
|
|
290
303
|
async pickConversation() {
|
|
291
304
|
const recs = listSessions();
|
|
292
305
|
if (!recs.length) {
|
|
@@ -359,11 +372,9 @@ export class CLI {
|
|
|
359
372
|
clearScreen();
|
|
360
373
|
this.renderHeader(engine.modelName);
|
|
361
374
|
process.stdout.write("\n");
|
|
362
|
-
process.stdout.write(`\x1b[2mResumed:\x1b[0m ${truncateLabel(rec["label"])}\n`);
|
|
363
|
-
process.stdout.write(
|
|
364
|
-
|
|
365
|
-
process.stdout.write("\n");
|
|
366
|
-
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");
|
|
367
378
|
process.stdout.write("\n");
|
|
368
379
|
if (this.story.length)
|
|
369
380
|
this.renderStory(this.story);
|
|
@@ -373,7 +384,6 @@ export class CLI {
|
|
|
373
384
|
async replLoop(engine) {
|
|
374
385
|
for (;;) {
|
|
375
386
|
engine.inQuery = false;
|
|
376
|
-
let rawPrompt;
|
|
377
387
|
let queryStarted = false;
|
|
378
388
|
try {
|
|
379
389
|
const [input, spans] = await askBottomPrompt("You", "❯", this.promptHistory);
|
|
@@ -420,7 +430,7 @@ export class CLI {
|
|
|
420
430
|
const effort = parts[1]?.toLowerCase() ?? "";
|
|
421
431
|
if (["none", "low", "high"].includes(effort)) {
|
|
422
432
|
engine.reasoningEffort = effort;
|
|
423
|
-
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`);
|
|
424
434
|
}
|
|
425
435
|
else {
|
|
426
436
|
process.stdout.write("\n");
|
|
@@ -428,7 +438,7 @@ export class CLI {
|
|
|
428
438
|
}
|
|
429
439
|
continue;
|
|
430
440
|
}
|
|
431
|
-
process.stdout.write(`\n\x1b[
|
|
441
|
+
process.stdout.write(`\n\x1b[1;36m❯\x1b[0m ${userDisplayText(input, spans)}\n\n`);
|
|
432
442
|
queryStarted = true;
|
|
433
443
|
await engine.executeQuery(input, this.inputItems, this.story, spans);
|
|
434
444
|
queryStarted = false;
|
|
@@ -438,20 +448,22 @@ export class CLI {
|
|
|
438
448
|
if (err?.message === "eof") {
|
|
439
449
|
this.saveSession(engine);
|
|
440
450
|
await engine.cleanupStoredResponses();
|
|
441
|
-
process.stdout.write("\nSession closing via exit interrupt hook.\n");
|
|
451
|
+
process.stdout.write("\nSession closing via exit interrupt hook.\n\n");
|
|
442
452
|
break;
|
|
443
453
|
}
|
|
444
454
|
if (err?.message === "interrupt") {
|
|
445
455
|
const wasActive = queryStarted || engine.inQuery;
|
|
446
456
|
if (wasActive && !engine.queryHasOutput) {
|
|
447
|
-
process.stdout.write(aiMarkdown("What else can I help with?") + "\n");
|
|
457
|
+
process.stdout.write(aiMarkdown("What else can I help you with?") + "\n");
|
|
448
458
|
}
|
|
449
459
|
engine.inQuery = false;
|
|
450
|
-
|
|
451
|
-
if (this.inputItems.length &&
|
|
460
|
+
stripOrphanCalls(this.inputItems);
|
|
461
|
+
if (this.inputItems.length &&
|
|
462
|
+
this.inputItems[this.inputItems.length - 1]["role"] === "user") {
|
|
452
463
|
this.inputItems.pop();
|
|
453
464
|
}
|
|
454
|
-
if (this.story.length &&
|
|
465
|
+
if (this.story.length &&
|
|
466
|
+
this.story[this.story.length - 1]["type"] === "user") {
|
|
455
467
|
this.story.pop();
|
|
456
468
|
}
|
|
457
469
|
if (this.interruptPending) {
|
|
@@ -461,13 +473,10 @@ export class CLI {
|
|
|
461
473
|
break;
|
|
462
474
|
}
|
|
463
475
|
this.interruptPending = true;
|
|
464
|
-
// clearBox leaves the cursor on the leading blank row; the newline
|
|
465
|
-
// below places the message one row lower, giving one blank above it
|
|
466
|
-
// (mirrors Python's console.print()).
|
|
467
476
|
process.stdout.write("\n");
|
|
468
477
|
process.stdout.write(wasActive
|
|
469
|
-
? "\x1b[2mInterrupted agent. Press Ctrl+C again to
|
|
470
|
-
: "\x1b[2mNo active
|
|
478
|
+
? "\x1b[2mInterrupted agent. Press Ctrl+C again to exit, or type a new prompt.\x1b[0m\n"
|
|
479
|
+
: "\x1b[2mNo active query. Press Ctrl+C again to exit.\x1b[0m\n");
|
|
471
480
|
continue;
|
|
472
481
|
}
|
|
473
482
|
throw err;
|
|
@@ -484,9 +493,6 @@ export async function main() {
|
|
|
484
493
|
}
|
|
485
494
|
catch (err) {
|
|
486
495
|
if (err?.message === "interrupt" || err?.message === "eof") {
|
|
487
|
-
// cleanup() already moved to a fresh line; the leading newline below
|
|
488
|
-
// leaves that row as a blank (one under the prompt), and the trailing
|
|
489
|
-
// double-newline leaves a blank row under the message too.
|
|
490
496
|
process.stdout.write("\nClosing terminal session. Goodbye!\n\n");
|
|
491
497
|
process.exit(0);
|
|
492
498
|
return;
|
|
@@ -513,7 +519,5 @@ export async function main() {
|
|
|
513
519
|
if (typeof client?.close === "function")
|
|
514
520
|
await client.close();
|
|
515
521
|
}
|
|
516
|
-
// The prompt's readline keypress listener is never removed and stdin remains
|
|
517
|
-
// resumed, so Node won't exit on its own after the loop ends. Exit explicitly.
|
|
518
522
|
process.exit(0);
|
|
519
523
|
}
|
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;
|