@hemansubedi/aether-ai 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.
Files changed (90) hide show
  1. package/.gitattributes +3 -0
  2. package/.github/workflows/live-stats.yml +42 -0
  3. package/.github/workflows/publish.yml +34 -0
  4. package/.github/workflows/update-preview.yml +41 -0
  5. package/INSTALL.md +59 -0
  6. package/LICENSE +21 -0
  7. package/README.md +397 -0
  8. package/assets/aether-arena.svg +72 -0
  9. package/assets/aether-banner.svg +62 -0
  10. package/assets/aether-router.svg +129 -0
  11. package/dist/agent.js +125 -0
  12. package/dist/arena.js +486 -0
  13. package/dist/checkpoint.js +105 -0
  14. package/dist/client.js +95 -0
  15. package/dist/combos.js +176 -0
  16. package/dist/commands.js +483 -0
  17. package/dist/config.js +104 -0
  18. package/dist/cost.js +176 -0
  19. package/dist/git.js +52 -0
  20. package/dist/health.js +81 -0
  21. package/dist/index.js +272 -0
  22. package/dist/keys.js +128 -0
  23. package/dist/memory.js +98 -0
  24. package/dist/modes.js +68 -0
  25. package/dist/providers/index.js +32 -0
  26. package/dist/providers/ollama.js +206 -0
  27. package/dist/providers/openai-compat.js +181 -0
  28. package/dist/providers/openrouter.js +189 -0
  29. package/dist/providers/registry.js +211 -0
  30. package/dist/router-engine.js +200 -0
  31. package/dist/router.js +171 -0
  32. package/dist/server.js +210 -0
  33. package/dist/session.js +97 -0
  34. package/dist/settings.js +97 -0
  35. package/dist/skills.js +100 -0
  36. package/dist/tokensaver.js +50 -0
  37. package/dist/tools/filesystem.js +243 -0
  38. package/dist/tools/git.js +53 -0
  39. package/dist/tools/glob.js +175 -0
  40. package/dist/tools/grep.js +193 -0
  41. package/dist/tools/registry.js +39 -0
  42. package/dist/tools/vision.js +140 -0
  43. package/dist/tools/websearch.js +118 -0
  44. package/dist/tui.js +562 -0
  45. package/dist/types.js +8 -0
  46. package/docs/preview.txt +51 -0
  47. package/docs/screenshots.md +110 -0
  48. package/docs/stats.md +5 -0
  49. package/install.ps1 +170 -0
  50. package/install.sh +196 -0
  51. package/package.json +34 -0
  52. package/scripts/generate-stats-card.ts +62 -0
  53. package/scripts/patch_index.ps1 +17 -0
  54. package/scripts/release.sh +7 -0
  55. package/src/agent.ts +146 -0
  56. package/src/arena.ts +584 -0
  57. package/src/checkpoint.ts +111 -0
  58. package/src/client.ts +172 -0
  59. package/src/combos.ts +199 -0
  60. package/src/commands.ts +973 -0
  61. package/src/config.ts +122 -0
  62. package/src/cost.ts +206 -0
  63. package/src/git.ts +68 -0
  64. package/src/health.ts +90 -0
  65. package/src/index.ts +281 -0
  66. package/src/keys.ts +135 -0
  67. package/src/memory.ts +101 -0
  68. package/src/modes.ts +84 -0
  69. package/src/providers/index.ts +59 -0
  70. package/src/providers/ollama.ts +222 -0
  71. package/src/providers/openai-compat.ts +188 -0
  72. package/src/providers/openrouter.ts +198 -0
  73. package/src/providers/registry.ts +223 -0
  74. package/src/router-engine.ts +214 -0
  75. package/src/router.ts +195 -0
  76. package/src/server.ts +242 -0
  77. package/src/session.ts +111 -0
  78. package/src/settings.ts +125 -0
  79. package/src/skills.ts +106 -0
  80. package/src/tokensaver.ts +57 -0
  81. package/src/tools/filesystem.ts +258 -0
  82. package/src/tools/git.ts +53 -0
  83. package/src/tools/glob.ts +180 -0
  84. package/src/tools/grep.ts +192 -0
  85. package/src/tools/registry.ts +54 -0
  86. package/src/tools/vision.ts +152 -0
  87. package/src/tools/websearch.ts +130 -0
  88. package/src/tui.ts +664 -0
  89. package/src/types.ts +77 -0
  90. package/tsconfig.json +16 -0
package/dist/tui.js ADDED
@@ -0,0 +1,562 @@
1
+ import * as readline from "node:readline";
2
+ import { Agent } from "./agent.js";
3
+ import { Session } from "./session.js";
4
+ import { Router } from "./router.js";
5
+ import { Arena } from "./arena.js";
6
+ import { CommandHandler, parseCommand } from "./commands.js";
7
+ import { getConfig } from "./config.js";
8
+ import { messageText } from "./types.js";
9
+ import { HealthTracker } from "./health.js";
10
+ import { ToolRegistry } from "./tools/registry.js";
11
+ import { makeReadFileTool, makeWriteFileTool, makeEditFileTool, makeListDirTool, makeBashTool, } from "./tools/filesystem.js";
12
+ const ESC = "\u001b";
13
+ const CURSOR_HIDE = `${ESC}[?25l`;
14
+ const CURSOR_SHOW = `${ESC}[?25h`;
15
+ const ALT_SCREEN = `${ESC}[?1049h`;
16
+ const ALT_SCREEN_OFF = `${ESC}[?1049l`;
17
+ const CLEAR = `${ESC}[2J${ESC}[H`;
18
+ const RESET = `${ESC}[0m`;
19
+ const BOLD = `${ESC}[1m`;
20
+ const DIM = `${ESC}[2m`;
21
+ const ITALIC = `${ESC}[3m`;
22
+ const UNDERLINE = `${ESC}[4m`;
23
+ const FG = {
24
+ red: `${ESC}[31m`,
25
+ green: `${ESC}[32m`,
26
+ yellow: `${ESC}[33m`,
27
+ blue: `${ESC}[34m`,
28
+ magenta: `${ESC}[35m`,
29
+ cyan: `${ESC}[36m`,
30
+ white: `${ESC}[37m`,
31
+ gray: `${ESC}[90m`,
32
+ brightRed: `${ESC}[91m`,
33
+ brightGreen: `${ESC}[92m`,
34
+ brightYellow: `${ESC}[93m`,
35
+ brightCyan: `${ESC}[96m`,
36
+ };
37
+ const BG = {
38
+ blue: `\u001b[44m`,
39
+ darkGray: `\u001b[100m`,
40
+ };
41
+ const SPINNER = ['�', '?', '?', '?', '?', '?'];
42
+ export class TUI {
43
+ agent;
44
+ router;
45
+ session;
46
+ arena;
47
+ costTracker;
48
+ skills;
49
+ checkpoint;
50
+ settings;
51
+ rl;
52
+ isTty;
53
+ running = true;
54
+ messages = [];
55
+ inputLines = [""];
56
+ inputCursor = 0;
57
+ scrollOffset = 0;
58
+ spinnerIdx = 0;
59
+ spinnerTimer = null;
60
+ thinking = false;
61
+ arenaMode = false;
62
+ lastRenderHeight = 0;
63
+ resizeTimer = null;
64
+ constructor(opts) {
65
+ this.agent = opts.agent;
66
+ this.router = opts.router;
67
+ this.session = opts.session;
68
+ this.arena = opts.arena;
69
+ this.costTracker = opts.costTracker;
70
+ this.skills = opts.skills;
71
+ this.checkpoint = opts.checkpoint;
72
+ this.settings = opts.settings;
73
+ this.isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
74
+ this.rl = readline.createInterface({
75
+ input: process.stdin,
76
+ output: process.stdout,
77
+ terminal: false,
78
+ crlfDelay: Infinity,
79
+ });
80
+ for (const m of this.session.messages) {
81
+ if (m.role === "system")
82
+ continue;
83
+ this.messages.push({ role: m.role, content: messageText(m) });
84
+ }
85
+ }
86
+ showSystem(text) {
87
+ this.messages.push({ role: "system", content: text });
88
+ this.render();
89
+ if (!this.isTty)
90
+ process.stdout.write(text + "\n");
91
+ }
92
+ exit() {
93
+ this.running = false;
94
+ this.cleanup();
95
+ process.exit(0);
96
+ }
97
+ enterArena() {
98
+ this.arenaMode = true;
99
+ this.messages.push({
100
+ role: "system",
101
+ content: "Arena mode: type a prompt to compare models side-by-side. /arena exit to return to chat.",
102
+ });
103
+ this.render();
104
+ }
105
+ exitArena() {
106
+ this.arenaMode = false;
107
+ this.messages.push({ role: "system", content: "Returned to chat mode." });
108
+ this.render();
109
+ }
110
+ start() {
111
+ if (!this.isTty) {
112
+ this.runNonTty();
113
+ return;
114
+ }
115
+ this.initTty();
116
+ this.render();
117
+ this.bindKeys();
118
+ this.prompt();
119
+ }
120
+ initTty() {
121
+ process.stdin.setEncoding("utf8");
122
+ process.stdin.resume();
123
+ process.stdout.write(CURSOR_HIDE);
124
+ process.stdout.write(ALT_SCREEN);
125
+ process.stdin.on("resize", () => this.scheduleResize());
126
+ }
127
+ scheduleResize() {
128
+ if (this.resizeTimer)
129
+ clearTimeout(this.resizeTimer);
130
+ this.resizeTimer = setTimeout(() => {
131
+ this.scrollOffset = 0;
132
+ this.render();
133
+ }, 100);
134
+ }
135
+ cleanup() {
136
+ if (this.spinnerTimer) {
137
+ clearInterval(this.spinnerTimer);
138
+ this.spinnerTimer = null;
139
+ }
140
+ if (this.resizeTimer) {
141
+ clearTimeout(this.resizeTimer);
142
+ this.resizeTimer = null;
143
+ }
144
+ process.stdout.write(CURSOR_SHOW);
145
+ process.stdout.write(ALT_SCREEN_OFF);
146
+ process.stdout.write(RESET);
147
+ this.rl.close();
148
+ }
149
+ runNonTty() {
150
+ this.thinking = false;
151
+ this.rl.on("line", async (line) => {
152
+ await this.handleLine(line);
153
+ });
154
+ this.rl.on("close", () => {
155
+ this.running = false;
156
+ });
157
+ }
158
+ prompt() {
159
+ if (!this.isTty)
160
+ return;
161
+ this.render();
162
+ }
163
+ bindKeys() {
164
+ process.stdin.on("data", (data) => {
165
+ this.handleRawInput(data.toString("utf8"));
166
+ });
167
+ }
168
+ termWidth() {
169
+ return process.stdout.columns || 80;
170
+ }
171
+ termHeight() {
172
+ return process.stdout.rows || 24;
173
+ }
174
+ wrap(text, width) {
175
+ const lines = [];
176
+ for (const para of text.split("\n")) {
177
+ if (para.length === 0) {
178
+ lines.push("");
179
+ continue;
180
+ }
181
+ let start = 0;
182
+ while (start < para.length) {
183
+ const chunk = para.slice(start, start + width);
184
+ lines.push(chunk);
185
+ start += width;
186
+ }
187
+ }
188
+ return lines;
189
+ }
190
+ renderMessage(m, width) {
191
+ const color = m.role === "user"
192
+ ? FG.brightCyan
193
+ : m.role === "assistant"
194
+ ? FG.brightGreen
195
+ : m.role === "tool"
196
+ ? FG.gray
197
+ : FG.brightYellow;
198
+ const label = m.role === "user"
199
+ ? "You"
200
+ : m.role === "assistant"
201
+ ? "Assistant"
202
+ : m.role === "tool"
203
+ ? "Tool"
204
+ : "System";
205
+ const out = [];
206
+ out.push(`${color}${label}${RESET}`);
207
+ const body = this.wrap(m.content, width - 2);
208
+ for (const line of body) {
209
+ out.push(`${DIM}${line}${RESET}`);
210
+ }
211
+ return out;
212
+ }
213
+ banner() {
214
+ const provider = this.router.getActiveProvider() ?? "auto";
215
+ const model = this.router.getActiveModel() ?? "(default)";
216
+ const mode = this.arenaMode ? "ARENA" : "CHAT";
217
+ const line = `aether | ${mode} | ${provider} | ${model}`;
218
+ const pad = Math.max(0, this.termWidth() - line.length);
219
+ return `${BG.blue}${FG.white}${BOLD} ${line}${" ".repeat(pad)} ${RESET}`;
220
+ }
221
+ render() {
222
+ if (!this.isTty)
223
+ return;
224
+ const width = this.termWidth();
225
+ const height = this.termHeight();
226
+ const rendered = [];
227
+ rendered.push(this.banner());
228
+ rendered.push(`${FG.gray}${"-".repeat(width)}${RESET}`);
229
+ const bodyWidth = width - 2;
230
+ const allLines = [];
231
+ const start = Math.max(0, this.messages.length - 60);
232
+ for (let i = start; i < this.messages.length; i++) {
233
+ allLines.push(...this.renderMessage(this.messages[i], bodyWidth));
234
+ }
235
+ // Apply scroll offset (scroll down to newest by default).
236
+ const maxScroll = Math.max(0, allLines.length - (height - 10));
237
+ if (this.scrollOffset > maxScroll)
238
+ this.scrollOffset = maxScroll;
239
+ const visible = allLines.slice(this.scrollOffset, this.scrollOffset + height - 10);
240
+ for (const line of visible) {
241
+ rendered.push(` ${line}`);
242
+ }
243
+ while (rendered.length < height - 4) {
244
+ rendered.push("");
245
+ }
246
+ if (this.thinking) {
247
+ const sp = SPINNER[this.spinnerIdx % SPINNER.length];
248
+ rendered.push(`${FG.brightYellow}${sp} thinking...${RESET}`);
249
+ }
250
+ // Input box.
251
+ rendered.push(`${FG.gray}${"-".repeat(width)}${RESET}`);
252
+ const inputText = this.inputLines.join("\n");
253
+ const inputDisplay = this.wrap(inputText || "", width - 4);
254
+ if (inputDisplay.length === 0)
255
+ inputDisplay.push("");
256
+ for (const line of inputDisplay) {
257
+ rendered.push(` ${FG.brightCyan}${line}${RESET}`);
258
+ }
259
+ rendered.push(`${FG.brightCyan}> ${RESET}`);
260
+ process.stdout.write(CLEAR);
261
+ process.stdout.write(rendered.join("\n"));
262
+ this.lastRenderHeight = rendered.length;
263
+ }
264
+ scrollUp(n = 1) {
265
+ this.scrollOffset = Math.max(0, this.scrollOffset - n);
266
+ this.render();
267
+ }
268
+ scrollDown(n = 1) {
269
+ this.scrollOffset += n;
270
+ this.render();
271
+ }
272
+ startSpinner() {
273
+ if (this.spinnerTimer)
274
+ return;
275
+ this.thinking = true;
276
+ this.spinnerIdx = 0;
277
+ this.spinnerTimer = setInterval(() => {
278
+ this.spinnerIdx++;
279
+ this.render();
280
+ }, 120);
281
+ }
282
+ stopSpinner() {
283
+ if (this.spinnerTimer) {
284
+ clearInterval(this.spinnerTimer);
285
+ this.spinnerTimer = null;
286
+ }
287
+ this.thinking = false;
288
+ }
289
+ handleRawInput(s) {
290
+ if (s === "\u0003") {
291
+ // Ctrl+C: clear input
292
+ this.inputLines = [""];
293
+ this.inputCursor = 0;
294
+ this.render();
295
+ return;
296
+ }
297
+ if (s === "\u001b") {
298
+ // Esc: clear input line
299
+ this.inputLines = [""];
300
+ this.inputCursor = 0;
301
+ this.render();
302
+ return;
303
+ }
304
+ if (s === "\u001b[A") {
305
+ this.scrollUp(2);
306
+ return;
307
+ }
308
+ if (s === "\u001b[B") {
309
+ this.scrollDown(2);
310
+ return;
311
+ }
312
+ if (s === "\u001b[C") {
313
+ // right arrow: noop for now
314
+ return;
315
+ }
316
+ if (s === "\u001b[D") {
317
+ return;
318
+ }
319
+ if (s === "\u000c") {
320
+ // Ctrl+L: redraw
321
+ this.render();
322
+ return;
323
+ }
324
+ if (s === "\u0004") {
325
+ // Ctrl+D: exit
326
+ this.exit();
327
+ return;
328
+ }
329
+ if (s === "\r" || s === "\n") {
330
+ this.submitLine();
331
+ return;
332
+ }
333
+ if (s === "\u0008" || s === "\u007f") {
334
+ // backspace
335
+ this.backspace();
336
+ return;
337
+ }
338
+ if (s === "\t") {
339
+ this.cycleProvider();
340
+ return;
341
+ }
342
+ if (s === "\u001b\r" || s === "\u001b\n") {
343
+ // Alt+Enter: newline
344
+ this.insertText("\n");
345
+ return;
346
+ }
347
+ if (s.length === 1 && s >= " ") {
348
+ this.insertText(s);
349
+ return;
350
+ }
351
+ this.render();
352
+ }
353
+ insertText(s) {
354
+ const line = this.inputLines[this.inputLines.length - 1] ?? "";
355
+ this.inputLines[this.inputLines.length - 1] = line + s;
356
+ this.inputCursor++;
357
+ }
358
+ backspace() {
359
+ const last = this.inputLines.length - 1;
360
+ const line = this.inputLines[last] ?? "";
361
+ if (line.length > 0) {
362
+ this.inputLines[last] = line.slice(0, -1);
363
+ }
364
+ else if (this.inputLines.length > 1) {
365
+ this.inputLines.pop();
366
+ }
367
+ }
368
+ submitLine() {
369
+ const text = this.inputLines.join("\n").trim();
370
+ this.inputLines = [""];
371
+ this.inputCursor = 0;
372
+ if (!text)
373
+ return;
374
+ void this.handleInput(text);
375
+ }
376
+ cycleProvider() {
377
+ const names = this.router.getProviderNames();
378
+ if (names.length === 0) {
379
+ this.showSystem("No providers available.");
380
+ return;
381
+ }
382
+ const current = this.router.getActiveProvider();
383
+ const idx = current ? names.indexOf(current) : -1;
384
+ const next = names[(idx + 1) % names.length];
385
+ this.router.setActiveProvider(next);
386
+ this.showSystem(`Provider: ${next}`);
387
+ }
388
+ async handleInput(text) {
389
+ if (text.startsWith("/")) {
390
+ await this.handleCommand(text);
391
+ return;
392
+ }
393
+ if (this.arenaMode) {
394
+ await this.runArena(text);
395
+ return;
396
+ }
397
+ this.messages.push({ role: "user", content: text });
398
+ this.session.add({ role: "user", content: text });
399
+ this.scrollOffset = 0;
400
+ this.render();
401
+ this.startSpinner();
402
+ const history = this.session.messages.filter((m) => m.role !== "system");
403
+ let assistantText = "";
404
+ let toolCalls = [];
405
+ try {
406
+ for await (const chunk of this.agent.run(text, history)) {
407
+ this.handleStreamChunk(chunk, { assistantText, toolCalls });
408
+ }
409
+ }
410
+ catch (err) {
411
+ const msg = err.message;
412
+ this.messages.push({ role: "system", content: `[error] ${msg}` });
413
+ }
414
+ finally {
415
+ this.stopSpinner();
416
+ }
417
+ const finalText = assistantText;
418
+ if (finalText.trim()) {
419
+ this.messages.push({ role: "assistant", content: finalText });
420
+ this.session.add({ role: "assistant", content: finalText, tool_calls: toolCalls.length ? toolCalls : undefined });
421
+ }
422
+ this.scrollOffset = 0;
423
+ this.render();
424
+ }
425
+ handleStreamChunk(chunk, state) {
426
+ if (chunk.type === "text" && chunk.text) {
427
+ state.assistantText += chunk.text;
428
+ this.messages.push({ role: "assistant", content: chunk.text });
429
+ }
430
+ else if (chunk.type === "tool_call" && chunk.tool_call) {
431
+ state.toolCalls.push(chunk.tool_call);
432
+ const name = chunk.tool_call.function.name;
433
+ this.messages.push({
434
+ role: "tool",
435
+ content: `[Tool: ${name}]`,
436
+ });
437
+ }
438
+ else if (chunk.type === "error" && chunk.error) {
439
+ this.messages.push({ role: "system", content: `[error] ${chunk.error}` });
440
+ }
441
+ this.render();
442
+ }
443
+ async runArena(text) {
444
+ this.messages.push({ role: "user", content: text });
445
+ this.session.add({ role: "user", content: text });
446
+ this.scrollOffset = 0;
447
+ this.render();
448
+ this.startSpinner();
449
+ const history = this.session.messages.filter((m) => m.role !== "system");
450
+ const modelIds = this.allModelIds();
451
+ if (modelIds.length < 2) {
452
+ this.stopSpinner();
453
+ this.showSystem(`Arena needs at least 2 models. Found: ${modelIds.join(", ") || "(none)"}`);
454
+ return;
455
+ }
456
+ const results = await this.arena.compare(text, history, modelIds, (modelId, chunk) => {
457
+ if (chunk.type === "text" && chunk.text) {
458
+ this.messages.push({
459
+ role: "system",
460
+ content: `${FG.brightCyan}[${modelId}]${RESET} ${chunk.text}`,
461
+ });
462
+ this.render();
463
+ }
464
+ });
465
+ this.stopSpinner();
466
+ this.messages.push({ role: "system", content: this.arena.render(results) });
467
+ this.render();
468
+ }
469
+ allModelIds() {
470
+ const ids = new Set();
471
+ for (const cfg of this.router.configs) {
472
+ if (!cfg.enabled)
473
+ continue;
474
+ for (const m of cfg.models)
475
+ ids.add(m);
476
+ }
477
+ return Array.from(ids);
478
+ }
479
+ async handleCommand(text) {
480
+ const { command, args } = parseCommand(text);
481
+ if (!command)
482
+ return;
483
+ const ctx = {
484
+ tui: this,
485
+ agent: this.agent,
486
+ router: this.router,
487
+ session: this.session,
488
+ arena: this.arena,
489
+ settings: this.settings,
490
+ };
491
+ const handler = CommandHandler[command];
492
+ if (!handler) {
493
+ this.showSystem(`Unknown command: /${command}. Type /help for available commands.`);
494
+ return;
495
+ }
496
+ try {
497
+ const result = await handler.run(args, ctx);
498
+ if (typeof result === "string") {
499
+ this.showSystem(result);
500
+ }
501
+ }
502
+ catch (err) {
503
+ this.showSystem(`Command error: ${err.message}`);
504
+ }
505
+ }
506
+ async handleLine(line) {
507
+ const text = line.trim();
508
+ if (!text)
509
+ return;
510
+ if (text.startsWith("/")) {
511
+ await this.handleCommand(text);
512
+ return;
513
+ }
514
+ if (this.arenaMode) {
515
+ await this.runArena(text);
516
+ return;
517
+ }
518
+ this.messages.push({ role: "user", content: text });
519
+ this.session.add({ role: "user", content: text });
520
+ let assistantText = "";
521
+ const history = this.session.messages.filter((m) => m.role !== "system");
522
+ try {
523
+ for await (const chunk of this.agent.run(text, history)) {
524
+ if (chunk.type === "text" && chunk.text) {
525
+ assistantText += chunk.text;
526
+ process.stdout.write(chunk.text);
527
+ }
528
+ else if (chunk.type === "tool_call" && chunk.tool_call) {
529
+ process.stdout.write(`\n[Tool: ${chunk.tool_call.function.name}]\n`);
530
+ }
531
+ else if (chunk.type === "error" && chunk.error) {
532
+ process.stderr.write(`[error] ${chunk.error}\n`);
533
+ }
534
+ }
535
+ }
536
+ catch (err) {
537
+ process.stderr.write(`error: ${err.message}\n`);
538
+ }
539
+ if (assistantText.trim()) {
540
+ this.messages.push({ role: "assistant", content: assistantText });
541
+ this.session.add({ role: "assistant", content: assistantText });
542
+ }
543
+ process.stdout.write("\n");
544
+ }
545
+ }
546
+ export function createTUI(opts) {
547
+ return new TUI(opts);
548
+ }
549
+ export async function runTUI() {
550
+ const cfg = getConfig();
551
+ const router = new Router(cfg.providers, new HealthTracker());
552
+ const registry = new ToolRegistry();
553
+ for (const make of [makeReadFileTool, makeWriteFileTool, makeEditFileTool, makeListDirTool, makeBashTool]) {
554
+ const tool = make(process.cwd());
555
+ registry.register(tool.def, tool.execute);
556
+ }
557
+ const agent = new Agent(router, registry);
558
+ const session = new Session();
559
+ const arena = new Arena(router);
560
+ const tui = new TUI({ agent, router, session, arena });
561
+ tui.start();
562
+ }
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /** Extract a plain-text representation of a message's content. */
2
+ export function messageText(m) {
3
+ if (typeof m.content === "string")
4
+ return m.content;
5
+ return m.content
6
+ .map((p) => (p.type === "text" ? p.text : "[image]"))
7
+ .join("");
8
+ }
@@ -0,0 +1,51 @@
1
+ Commands:
2
+ /help - Show all available commands
3
+ /model <name> - Switch the active model
4
+ /provider <name> - Switch the active provider
5
+ /models - List all available models across providers
6
+ /providers - Show provider health status
7
+ /session save [name] | load <name> | list | clear - Manage sessions
8
+ /arena - Enter arena mode (compare models)
9
+ /exit - Exit the TUI
10
+ /cost - Show token usage and estimated cost summary
11
+ /reset-cost - Reset cost and token counters
12
+ /settings [set <key> <value> | reset] - View or modify settings
13
+ /stats - Show session stats, cost summary, and provider health
14
+ /combo list | create <name> [provider...] | select <name> | delete <name> - Manage provider/model combos
15
+ /quit - Exit the TUI
16
+ /reset-health - Reset health state for all providers
17
+ /skills - List available custom skills
18
+ /skill <name> [args] - Run a custom skill
19
+ /connect <provider> <api_key> - Connect an API key for a provider
20
+ /keys - List which providers have API keys configured
21
+ /disconnect <provider> - Remove an API key
22
+
23
+
24
+ === /stats ===
25
+ Session:
26
+ messages: 0 (user: 0, assistant: 0, tool: 0)
27
+
28
+ Cost:
29
+ Provider Model Requests Input Tok Output Tok Cost (USD)
30
+ -------- ----- -------- --------- ---------- ----------
31
+ - - - - - -
32
+ Total 0.000000
33
+
34
+ Provider health:
35
+ ollama-local: unhealthy (fetch failed)
36
+ openrouter-free: healthy
37
+ groq: unhealthy (HTTP 401)
38
+ mistral: unhealthy (fetch failed)
39
+ cohere: unhealthy (HTTP 401)
40
+ huggingface: unhealthy (fetch failed)
41
+ fireworks: unhealthy (HTTP 401)
42
+ together: unhealthy (HTTP 401)
43
+ deepseek: unhealthy (HTTP 401)
44
+ gemini: unhealthy (HTTP 404)
45
+ xai: unhealthy (HTTP 401)
46
+ perplexity: unhealthy (HTTP 404)
47
+ cerebras: unhealthy (HTTP 403)
48
+ nvidia: healthy
49
+ jina: healthy
50
+ parasail: unhealthy (fetch failed)
51
+ featherless: unhealthy (HTTP 404)