@oxecli/oxe 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 +2 -0
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/bin/oxe.js +6 -0
- package/dist/api.js +64 -0
- package/dist/cli.js +284 -0
- package/dist/config.js +280 -0
- package/dist/engine.js +534 -0
- package/dist/oxe.js +6 -0
- package/dist/sessions.js +146 -0
- package/dist/skills.js +141 -0
- package/dist/system.js +19 -0
- package/dist/tools.js +856 -0
- package/dist/ui.js +569 -0
- package/package.json +49 -0
- package/skills/apple-design/SKILL.md +282 -0
- package/skills/react-native/SKILL.md +14 -0
- package/skills/react-native/references/structure.md +9 -0
- package/skills/react-native/scripts/scaffold.sh +3 -0
package/.env
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
OXE_SUPABASE_URL=https://ajmhaekffqzzrdfwsxfu.supabase.co
|
|
2
|
+
OXE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFqbWhhZWtmZnF6enJkZndzeGZ1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODczMzU4MzQsImV4cCI6MjEwMjkxMTgzNH0.17sukij-R3L0kecR6x2MILZBO4M1ZZ4mkyqCU5yrVyM
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oxe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Oxe — terminal AI coding agent
|
|
2
|
+
|
|
3
|
+
A terminal-based AI coding agent. This is a **native TypeScript** implementation
|
|
4
|
+
(pure Node.js, no Python required).
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g @oxecli/oxe
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The `oxe` command is provided by this package's `bin`.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
oxe
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
On first launch you'll be prompted for an **Oxe API key** (must start with
|
|
21
|
+
`oxe_live_`), generated on your Oxe dashboard.
|
|
22
|
+
|
|
23
|
+
## Interactive commands
|
|
24
|
+
|
|
25
|
+
| Command | Description |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `/help` | Show available commands |
|
|
28
|
+
| `/resume` | List saved conversations |
|
|
29
|
+
| `/resume <n>` | Resume conversation number `n` |
|
|
30
|
+
| `/effort <none\|low\|high>` | Set reasoning effort |
|
|
31
|
+
| `/clear` | Save current conversation and start a new one |
|
|
32
|
+
| `/exit`, `/quit` | Save and exit |
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Environment variables:
|
|
37
|
+
|
|
38
|
+
| Variable | Description | Default |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `OXE_DATA_DIR` | Where sessions are stored | `~/.oxe` |
|
|
41
|
+
| `OXE_BASE_URL` | Chat proxy base URL | bundled backend |
|
|
42
|
+
| `OXE_MODEL_NAME` | Model to use | `deepseek-v4-flash` |
|
|
43
|
+
| `OXE_REASONING_EFFORT` | Default reasoning effort | `low` |
|
|
44
|
+
| `OXE_TIMEOUT` / `OXE_MAX_RETRIES` / `OXE_TEMPERATURE` | Request tuning | |
|
|
45
|
+
|
|
46
|
+
Sessions and user data live under `~/.oxe/sessions`, never in `node_modules`.
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm run build # tsc -> dist/
|
|
53
|
+
npm install -g . # link as `oxe`
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
package/bin/oxe.js
ADDED
package/dist/api.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { OXE_SUPABASE_URL_EXPORT as OXE_SUPABASE_URL, OXE_SUPABASE_ANON_KEY_EXPORT as OXE_SUPABASE_ANON_KEY, OXE_KEY_PREFIX, } from "./config.js";
|
|
2
|
+
export async function validateOxeApiKey(apiKey) {
|
|
3
|
+
const key = apiKey.trim();
|
|
4
|
+
if (!key.startsWith(OXE_KEY_PREFIX)) {
|
|
5
|
+
return {
|
|
6
|
+
valid: false,
|
|
7
|
+
error: "Invalid API key format. Only authentic Oxe API keys (starting with 'oxe_live_') are permitted.\n" +
|
|
8
|
+
"Generate your key at: http://localhost:5173/dashboard/api-keys",
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const resp = await fetch(`${OXE_SUPABASE_URL}/functions/v1/validate-key`, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { "Content-Type": "application/json" },
|
|
15
|
+
body: JSON.stringify({ api_key: key }),
|
|
16
|
+
signal: AbortSignal.timeout(10000),
|
|
17
|
+
});
|
|
18
|
+
const result = (await resp.json());
|
|
19
|
+
if (result?.valid) {
|
|
20
|
+
return { valid: true, key_data: result["key_data"] || {} };
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
valid: false,
|
|
24
|
+
error: result?.error ||
|
|
25
|
+
"Oxe API key not found in cloud database. Please verify your key on the dashboard.",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const code = err?.name === "TimeoutError" ? "timeout" : "network";
|
|
30
|
+
return {
|
|
31
|
+
valid: false,
|
|
32
|
+
error: `Could not reach Oxe Cloud to verify key (${code}): ${err?.message ?? err}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function reportUsage(opts) {
|
|
37
|
+
const { user_id } = opts;
|
|
38
|
+
if (!user_id)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
const payload = {
|
|
42
|
+
user_id,
|
|
43
|
+
api_key_id: opts.api_key_id,
|
|
44
|
+
model: opts.model,
|
|
45
|
+
input_tokens: opts.input_tokens,
|
|
46
|
+
output_tokens: opts.output_tokens,
|
|
47
|
+
request_count: opts.request_count ?? 1,
|
|
48
|
+
};
|
|
49
|
+
await fetch(`${OXE_SUPABASE_URL}/rest/v1/usage`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
apikey: OXE_SUPABASE_ANON_KEY,
|
|
53
|
+
Authorization: `Bearer ${OXE_SUPABASE_ANON_KEY}`,
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
Prefer: "return=minimal",
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(payload),
|
|
58
|
+
signal: AbortSignal.timeout(4000),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* ignore */
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { loadOrPrompt, default_reasoning_effort, max_action_chars, max_resume_history_items, } from "./config.js";
|
|
4
|
+
import { InferenceEngine, estimateTokens } from "./engine.js";
|
|
5
|
+
import { saveSession, loadSession, listSessions, nextSessionId, conversationLabel, COMMAND_HELP, toolOutputFailed, } from "./sessions.js";
|
|
6
|
+
import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, formatToolAction, truncateEllipsis, renderPanel, } from "./ui.js";
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
function truncateLabel(s) {
|
|
9
|
+
const t = String(s || "(conversation)").trim();
|
|
10
|
+
return t.length > 60 ? t.slice(0, 57) + "..." : t;
|
|
11
|
+
}
|
|
12
|
+
export class CLI {
|
|
13
|
+
config;
|
|
14
|
+
inputItems = [];
|
|
15
|
+
story = [];
|
|
16
|
+
sessionId = null;
|
|
17
|
+
promptHistory = [];
|
|
18
|
+
constructor() {
|
|
19
|
+
clearScreen();
|
|
20
|
+
renderPanel("AI CODING AGENT", "Engine: Ready");
|
|
21
|
+
process.stdout.write("\n");
|
|
22
|
+
this.config = null;
|
|
23
|
+
}
|
|
24
|
+
saveSession(engine) {
|
|
25
|
+
if (!this.inputItems.length)
|
|
26
|
+
return;
|
|
27
|
+
if (this.sessionId === null)
|
|
28
|
+
this.sessionId = nextSessionId();
|
|
29
|
+
saveSession({
|
|
30
|
+
id: this.sessionId,
|
|
31
|
+
label: conversationLabel(this.inputItems),
|
|
32
|
+
updated_at: new Date().toISOString().replace(/\.\d{3}Z$/, ""),
|
|
33
|
+
input_items: this.inputItems,
|
|
34
|
+
story: this.story,
|
|
35
|
+
reasoning_effort: engine.reasoningEffort,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
printToolEntry(started, text, status) {
|
|
39
|
+
if (started) {
|
|
40
|
+
process.stdout.write(`\x1b[2m${truncateEllipsis(String(started), max_action_chars, "text")}\x1b[0m\n`);
|
|
41
|
+
}
|
|
42
|
+
const style = status === "failed" ? "\x1b[31m" : "\x1b[32m";
|
|
43
|
+
process.stdout.write(`${style}${truncateEllipsis(String(text), max_action_chars, "text")}\x1b[0m\n`);
|
|
44
|
+
}
|
|
45
|
+
showHelp() {
|
|
46
|
+
process.stdout.write("\n");
|
|
47
|
+
for (const [cmd, desc] of COMMAND_HELP) {
|
|
48
|
+
process.stdout.write(`\x1b[1m${cmd.padEnd(18)}\x1b[0m\x1b[2m${desc}\x1b[0m\n`);
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write("\n");
|
|
51
|
+
}
|
|
52
|
+
renderHistory(items, maxItems = max_resume_history_items) {
|
|
53
|
+
const outputsByCallId = new Map();
|
|
54
|
+
for (const it of items) {
|
|
55
|
+
if (it["type"] === "function_call_output" && it["call_id"]) {
|
|
56
|
+
outputsByCallId.set(it["call_id"], it["output"] ?? "");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const renders = [];
|
|
60
|
+
for (const it of items) {
|
|
61
|
+
const typ = it["type"] ?? it["role"];
|
|
62
|
+
if (typ === "user") {
|
|
63
|
+
if (it["compaction_summary"])
|
|
64
|
+
continue;
|
|
65
|
+
const c = it["content"];
|
|
66
|
+
if (typeof c === "string" && c.trim()) {
|
|
67
|
+
renders.push({ kind: "user", text: c, paste_spans: it["paste_spans"] });
|
|
68
|
+
}
|
|
69
|
+
else if (Array.isArray(c) && messageText(it).trim()) {
|
|
70
|
+
renders.push({ kind: "user", text: messageText(it), paste_spans: it["paste_spans"] });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (typ === "message") {
|
|
74
|
+
const text = messageText(it);
|
|
75
|
+
if (text.trim())
|
|
76
|
+
renders.push({ kind: "assistant", text, footer: it["footer"] ?? "" });
|
|
77
|
+
}
|
|
78
|
+
else if (typ === "function_call") {
|
|
79
|
+
const name = it["name"] ?? "tool";
|
|
80
|
+
const rawResult = outputsByCallId.get(it["call_id"] ?? "") ?? "";
|
|
81
|
+
const failed = toolOutputFailed(name, rawResult);
|
|
82
|
+
const argumentsJson = it["arguments"] ?? "";
|
|
83
|
+
renders.push({
|
|
84
|
+
kind: "tool",
|
|
85
|
+
started: formatToolAction(name, argumentsJson, "started"),
|
|
86
|
+
text: formatToolAction(name, argumentsJson, failed ? "failed" : "ok"),
|
|
87
|
+
status: failed ? "failed" : "ok",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (renders.length > maxItems) {
|
|
92
|
+
const skipped = renders.length - maxItems;
|
|
93
|
+
process.stdout.write(`\x1b[2m… ${skipped} earlier message(s) omitted …\x1b[0m\n\n`);
|
|
94
|
+
renders.splice(0, renders.length - maxItems);
|
|
95
|
+
}
|
|
96
|
+
const last = renders.length - 1;
|
|
97
|
+
for (let idx = 0; idx < renders.length; idx++) {
|
|
98
|
+
const r = renders[idx];
|
|
99
|
+
const kind = r["kind"];
|
|
100
|
+
const payload = r["text"];
|
|
101
|
+
if (kind === "user") {
|
|
102
|
+
process.stdout.write(`\x1b[1m❯\x1b[0m ${userDisplayText(payload, r["paste_spans"])}\n`);
|
|
103
|
+
}
|
|
104
|
+
else if (kind === "assistant") {
|
|
105
|
+
process.stdout.write(aiMarkdown(payload) + "\n");
|
|
106
|
+
let footer = String(r["footer"] ?? "").trim();
|
|
107
|
+
if (footer) {
|
|
108
|
+
if (!footer.startsWith("("))
|
|
109
|
+
footer = `(${footer})`;
|
|
110
|
+
process.stdout.write("\n" + mutedMarkdown(footer) + "\n");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.printToolEntry(r["started"], payload, r["status"] ?? "");
|
|
115
|
+
}
|
|
116
|
+
if (idx < last)
|
|
117
|
+
process.stdout.write("\n");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
renderStory(story, maxItems = max_resume_history_items) {
|
|
121
|
+
let entries = story.filter((e) => String(e["text"] ?? "").trim());
|
|
122
|
+
if (entries.length > maxItems) {
|
|
123
|
+
const skipped = entries.length - maxItems;
|
|
124
|
+
process.stdout.write(`\x1b[2m… ${skipped} earlier message(s) omitted …\x1b[0m\n\n`);
|
|
125
|
+
entries = entries.slice(-maxItems);
|
|
126
|
+
}
|
|
127
|
+
const last = entries.length - 1;
|
|
128
|
+
for (let idx = 0; idx < entries.length; idx++) {
|
|
129
|
+
const e = entries[idx];
|
|
130
|
+
const typ = e["type"];
|
|
131
|
+
const text = e["text"] ?? "";
|
|
132
|
+
if (typ === "user") {
|
|
133
|
+
process.stdout.write(`\x1b[1m❯\x1b[0m ${userDisplayText(text, e["paste_spans"])}\n`);
|
|
134
|
+
}
|
|
135
|
+
else if (typ === "assistant") {
|
|
136
|
+
process.stdout.write(aiMarkdown(text) + "\n");
|
|
137
|
+
}
|
|
138
|
+
else if (typ === "tool") {
|
|
139
|
+
this.printToolEntry(e["started"], text, e["status"] ?? "");
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
process.stdout.write(mutedMarkdown(text) + "\n");
|
|
143
|
+
}
|
|
144
|
+
if (idx < last)
|
|
145
|
+
process.stdout.write("\n");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
conversationRow(rec) {
|
|
149
|
+
const sid = String(rec["id"] ?? "?");
|
|
150
|
+
const updated = String(rec["updated_at"] ?? "").slice(0, 16).replace("T", " ");
|
|
151
|
+
const label = truncateLabel(rec["label"]);
|
|
152
|
+
const items = rec["input_items"] ?? [];
|
|
153
|
+
const toks = estimateTokens(items).toLocaleString();
|
|
154
|
+
return `${sid.padStart(4)}\x1b[2m ${updated}\x1b[0m ${label}\x1b[2m ${items.length} items · ${toks} tok\x1b[0m`;
|
|
155
|
+
}
|
|
156
|
+
showConversationsPlain(recs) {
|
|
157
|
+
process.stdout.write("\n");
|
|
158
|
+
renderPanel("Resume a conversation with: /resume <number>", "Conversations");
|
|
159
|
+
for (const rec of recs) {
|
|
160
|
+
process.stdout.write(this.conversationRow(rec) + "\n");
|
|
161
|
+
}
|
|
162
|
+
process.stdout.write("\n");
|
|
163
|
+
}
|
|
164
|
+
pickConversation() {
|
|
165
|
+
const recs = listSessions();
|
|
166
|
+
if (!recs.length) {
|
|
167
|
+
process.stdout.write("\n");
|
|
168
|
+
renderPanel("No saved conversations yet. Type a prompt to start one.", "Conversations");
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
this.showConversationsPlain(recs);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
async resumeConversation(engine, num) {
|
|
175
|
+
const sid = parseInt(num, 10);
|
|
176
|
+
if (Number.isNaN(sid)) {
|
|
177
|
+
renderPanel(`Invalid conversation number: ${num}`, "Error");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const rec = loadSession(sid);
|
|
181
|
+
if (!rec) {
|
|
182
|
+
renderPanel(`Conversation ${sid} not found.`, "Error");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.saveSession(engine);
|
|
186
|
+
await engine.cleanupStoredResponses();
|
|
187
|
+
this.inputItems = rec["input_items"] ?? [];
|
|
188
|
+
this.story = rec["story"] ?? [];
|
|
189
|
+
this.sessionId = sid;
|
|
190
|
+
engine.reasoningEffort = String(rec["reasoning_effort"] ?? default_reasoning_effort);
|
|
191
|
+
clearScreen();
|
|
192
|
+
renderPanel(`AI CODING AGENT (resumed ${sid})`, "Engine: Ready");
|
|
193
|
+
process.stdout.write(`\x1b[2mResumed:\x1b[0m ${truncateLabel(rec["label"])}\n`);
|
|
194
|
+
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`);
|
|
195
|
+
renderPanel("Conversation history", "Restored");
|
|
196
|
+
process.stdout.write("\n");
|
|
197
|
+
if (this.story.length)
|
|
198
|
+
this.renderStory(this.story);
|
|
199
|
+
else
|
|
200
|
+
this.renderHistory(this.inputItems);
|
|
201
|
+
}
|
|
202
|
+
async replLoop(engine) {
|
|
203
|
+
for (;;) {
|
|
204
|
+
engine.inQuery = false;
|
|
205
|
+
let rawPrompt;
|
|
206
|
+
try {
|
|
207
|
+
const [input, spans] = await askBottomPrompt("You", "❯", this.promptHistory);
|
|
208
|
+
if (!input.trim())
|
|
209
|
+
continue;
|
|
210
|
+
this.promptHistory.push(input);
|
|
211
|
+
const lower = input.trim().toLowerCase();
|
|
212
|
+
if (lower === "/exit" || lower === "/quit") {
|
|
213
|
+
this.saveSession(engine);
|
|
214
|
+
await engine.cleanupStoredResponses();
|
|
215
|
+
process.stdout.write("\nClosing terminal session. Goodbye!\n");
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
if (lower === "/help") {
|
|
219
|
+
this.showHelp();
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (lower === "/clear") {
|
|
223
|
+
this.saveSession(engine);
|
|
224
|
+
await engine.cleanupStoredResponses();
|
|
225
|
+
this.inputItems = [];
|
|
226
|
+
this.story = [];
|
|
227
|
+
this.sessionId = null;
|
|
228
|
+
clearScreen();
|
|
229
|
+
renderPanel("AI CODING AGENT", "Engine: Ready");
|
|
230
|
+
process.stdout.write("\n");
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (lower.startsWith("/resume")) {
|
|
234
|
+
const parts = input.trim().split(/\s+/);
|
|
235
|
+
if (parts.length === 1) {
|
|
236
|
+
this.pickConversation();
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
await this.resumeConversation(engine, parts[1]);
|
|
240
|
+
}
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (lower.startsWith("/effort")) {
|
|
244
|
+
const parts = input.trim().split(/\s+/);
|
|
245
|
+
const effort = parts[1]?.toLowerCase() ?? "";
|
|
246
|
+
if (["none", "low", "high"].includes(effort)) {
|
|
247
|
+
engine.reasoningEffort = effort;
|
|
248
|
+
process.stdout.write(`\n\x1b[2mReasoning effort set to ${effort}.\x1b[0m\n`);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
renderPanel("Usage: /effort none|low|high", "Error");
|
|
252
|
+
}
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
process.stdout.write(`\n\x1b[1m❯\x1b[0m ${userDisplayText(input, spans)}\n\n`);
|
|
256
|
+
await engine.executeQuery(input, this.inputItems, this.story, spans);
|
|
257
|
+
this.saveSession(engine);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
if (err?.message === "interrupt" || err?.message === "eof") {
|
|
261
|
+
this.saveSession(engine);
|
|
262
|
+
await engine.cleanupStoredResponses();
|
|
263
|
+
process.stdout.write("\nClosing terminal session. Goodbye!\n");
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
export async function main() {
|
|
272
|
+
enableAnsi();
|
|
273
|
+
const app = new CLI();
|
|
274
|
+
const config = await loadOrPrompt();
|
|
275
|
+
app.config = config;
|
|
276
|
+
const engine = new InferenceEngine(config);
|
|
277
|
+
try {
|
|
278
|
+
await app.replLoop(engine);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await engine.cleanupStoredResponses();
|
|
282
|
+
await engine.client.close();
|
|
283
|
+
}
|
|
284
|
+
}
|