aether-ai-agent-cli 1.1.4__py3-none-any.whl
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.
- aether_ai_agent_cli-1.1.4.dist-info/METADATA +309 -0
- aether_ai_agent_cli-1.1.4.dist-info/RECORD +25 -0
- aether_ai_agent_cli-1.1.4.dist-info/WHEEL +5 -0
- aether_ai_agent_cli-1.1.4.dist-info/entry_points.txt +2 -0
- aether_ai_agent_cli-1.1.4.dist-info/licenses/LICENSE +21 -0
- aether_ai_agent_cli-1.1.4.dist-info/top_level.txt +1 -0
- aether_pip/__init__.py +1 -0
- aether_pip/cli.py +49 -0
- aether_pip/node_project/bin/aether.js +10 -0
- aether_pip/node_project/package-lock.json +794 -0
- aether_pip/node_project/package.json +46 -0
- aether_pip/node_project/src/ai/fallback.js +179 -0
- aether_pip/node_project/src/ai/google.js +87 -0
- aether_pip/node_project/src/ai/providers.js +203 -0
- aether_pip/node_project/src/ai/router.js +114 -0
- aether_pip/node_project/src/ai/universal.js +507 -0
- aether_pip/node_project/src/ai/xai.js +50 -0
- aether_pip/node_project/src/chat.js +1018 -0
- aether_pip/node_project/src/cli.js +679 -0
- aether_pip/node_project/src/config.js +214 -0
- aether_pip/node_project/src/file-parser.js +94 -0
- aether_pip/node_project/src/modes.js +121 -0
- aether_pip/node_project/src/ui/banner.js +60 -0
- aether_pip/node_project/src/ui/spinner.js +43 -0
- aether_pip/node_project/src/ui/theme.js +169 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Main CLI Logic & Command Routing
|
|
3
|
+
// Universal AI Gateway — Supports 13+ providers
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { Marked } from "marked";
|
|
12
|
+
import { markedTerminal } from "marked-terminal";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
colors,
|
|
16
|
+
label,
|
|
17
|
+
separator,
|
|
18
|
+
keyValue,
|
|
19
|
+
bullet,
|
|
20
|
+
clearStreamedText,
|
|
21
|
+
getActiveTheme,
|
|
22
|
+
setTheme,
|
|
23
|
+
getThemesList,
|
|
24
|
+
} from "./ui/theme.js";
|
|
25
|
+
import { createSpinner } from "./ui/spinner.js";
|
|
26
|
+
import { routePrompt } from "./ai/router.js";
|
|
27
|
+
import { PROVIDERS, getProvidersByTier, getActiveProviders } from "./ai/providers.js";
|
|
28
|
+
import { startChat } from "./chat.js";
|
|
29
|
+
import { parseFile, formatContext } from "./file-parser.js";
|
|
30
|
+
import { MODES, DEFAULT_MODE, getModeByName } from "./modes.js";
|
|
31
|
+
import {
|
|
32
|
+
getAIConfig,
|
|
33
|
+
setConfigValue,
|
|
34
|
+
getConfigValue,
|
|
35
|
+
listConfig,
|
|
36
|
+
resetConfig,
|
|
37
|
+
deleteConfigValue,
|
|
38
|
+
getConfigPath,
|
|
39
|
+
configExists,
|
|
40
|
+
isValidConfigKey,
|
|
41
|
+
} from "./config.js";
|
|
42
|
+
|
|
43
|
+
// Configure marked dynamically for terminal output
|
|
44
|
+
const getMarked = () => new Marked(markedTerminal({
|
|
45
|
+
reflowText: true,
|
|
46
|
+
width: process.stdout.columns ? Math.max(20, process.stdout.columns - 4) : 80,
|
|
47
|
+
showSectionPrefix: false,
|
|
48
|
+
code: (c) => colors.orange(c),
|
|
49
|
+
codespan: (c) => colors.accent3(c),
|
|
50
|
+
heading: (h) => colors.accent.bold(h),
|
|
51
|
+
strong: (s) => colors.magenta.bold(s),
|
|
52
|
+
em: chalk.italic,
|
|
53
|
+
hr: (h) => colors.dim(h),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
57
|
+
const __dirname = dirname(__filename);
|
|
58
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
59
|
+
const VERSION = pkg.version;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets up and runs the Aether CLI.
|
|
63
|
+
* @param {string[]} argv - Process arguments
|
|
64
|
+
*/
|
|
65
|
+
export function createCLI(argv) {
|
|
66
|
+
const program = new Command();
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.name("aether")
|
|
70
|
+
.description("Aether Core AI v110 — Universal AI Gateway CLI\n Supports 13+ AI providers • Free & paid models • Local fallbacks")
|
|
71
|
+
.version(VERSION, "-v, --version");
|
|
72
|
+
|
|
73
|
+
// ── Chat Command ────────────────────────────────────────
|
|
74
|
+
program
|
|
75
|
+
.command("chat")
|
|
76
|
+
.description("Start an interactive chat session")
|
|
77
|
+
.option("-m, --mode <mode>", `Reasoning mode (${Object.keys(MODES).filter(m => m !== "claude-code").join(", ")})`, DEFAULT_MODE)
|
|
78
|
+
.option("-p, --provider <provider>", "Preferred AI provider (openai, groq, google, etc.)")
|
|
79
|
+
.action(async (opts) => {
|
|
80
|
+
await startChat({ mode: opts.mode, preferredProvider: opts.provider });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ── Ask Command ─────────────────────────────────────────
|
|
84
|
+
program
|
|
85
|
+
.command("ask <prompt...>")
|
|
86
|
+
.description("Send a single prompt and get a response")
|
|
87
|
+
.option("-m, --mode <mode>", "Reasoning mode", DEFAULT_MODE)
|
|
88
|
+
.option("-f, --file <path>", "Attach a file for context")
|
|
89
|
+
.option("-p, --provider <provider>", "Preferred AI provider")
|
|
90
|
+
.option("--model <model>", "Override the AI model")
|
|
91
|
+
.option("--raw", "Output raw text without formatting")
|
|
92
|
+
.action(async (promptParts, opts) => {
|
|
93
|
+
const prompt = promptParts.join(" ");
|
|
94
|
+
await handleAsk(prompt, opts);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ── Config Command ──────────────────────────────────────
|
|
98
|
+
const configCmd = program
|
|
99
|
+
.command("config")
|
|
100
|
+
.description("Manage API keys and settings");
|
|
101
|
+
|
|
102
|
+
configCmd
|
|
103
|
+
.command("set <key> <value>")
|
|
104
|
+
.description("Set a config value (e.g., GROQ_API_KEY, OPENAI_API_KEY)")
|
|
105
|
+
.action(async (key, value) => {
|
|
106
|
+
await handleConfigSet(key, value);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
configCmd
|
|
110
|
+
.command("get <key>")
|
|
111
|
+
.description("Get a configuration value")
|
|
112
|
+
.action(async (key) => {
|
|
113
|
+
await handleConfigGet(key);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
configCmd
|
|
117
|
+
.command("list")
|
|
118
|
+
.description("List all configuration (keys masked)")
|
|
119
|
+
.action(async () => {
|
|
120
|
+
await handleConfigList();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
configCmd
|
|
124
|
+
.command("delete <key>")
|
|
125
|
+
.description("Delete a configuration key")
|
|
126
|
+
.action(async (key) => {
|
|
127
|
+
await handleConfigDelete(key);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
configCmd
|
|
131
|
+
.command("reset")
|
|
132
|
+
.description("Delete all configuration")
|
|
133
|
+
.action(async () => {
|
|
134
|
+
await handleConfigReset();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
configCmd
|
|
138
|
+
.command("path")
|
|
139
|
+
.description("Show config file location")
|
|
140
|
+
.action(() => {
|
|
141
|
+
console.log("\n" + label.config + " " + colors.text(getConfigPath()) + "\n");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ── Providers Command ───────────────────────────────────
|
|
145
|
+
program
|
|
146
|
+
.command("providers")
|
|
147
|
+
.description("List all supported AI providers and their status")
|
|
148
|
+
.option("--free", "Show only free-tier providers")
|
|
149
|
+
.action(async (opts) => {
|
|
150
|
+
await handleProviders(opts);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ── Models Command ──────────────────────────────────────
|
|
154
|
+
program
|
|
155
|
+
.command("models [provider]")
|
|
156
|
+
.description("List available models for a provider")
|
|
157
|
+
.action((provider) => {
|
|
158
|
+
handleModels(provider);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── Modes Command ───────────────────────────────────────
|
|
162
|
+
program
|
|
163
|
+
.command("modes")
|
|
164
|
+
.description("List all reasoning modes")
|
|
165
|
+
.action(() => {
|
|
166
|
+
handleModes();
|
|
167
|
+
});
|
|
168
|
+
// ── Theme Command ───────────────────────────────────────
|
|
169
|
+
program
|
|
170
|
+
.command("theme [name]")
|
|
171
|
+
.description("Show active visual theme or switch to a new theme")
|
|
172
|
+
.action(async (name) => {
|
|
173
|
+
if (!name) {
|
|
174
|
+
await handleThemeGet();
|
|
175
|
+
} else {
|
|
176
|
+
await handleThemeSet(name);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// ── Themes Command ──────────────────────────────────────
|
|
181
|
+
program
|
|
182
|
+
.command("themes")
|
|
183
|
+
.description("List all available color themes")
|
|
184
|
+
.action(() => {
|
|
185
|
+
handleThemesList();
|
|
186
|
+
});
|
|
187
|
+
// ── Status Command ──────────────────────────────────────
|
|
188
|
+
program
|
|
189
|
+
.command("status")
|
|
190
|
+
.description("Show system status & configured providers")
|
|
191
|
+
.action(async () => {
|
|
192
|
+
await handleStatus();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ── Setup Command ───────────────────────────────────────
|
|
196
|
+
program
|
|
197
|
+
.command("setup")
|
|
198
|
+
.description("Interactive guided setup for API keys")
|
|
199
|
+
.action(async () => {
|
|
200
|
+
await handleSetup();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ── Default: Show help ──────────────────────────────────
|
|
204
|
+
program.action(() => {
|
|
205
|
+
showMiniBanner();
|
|
206
|
+
program.help();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
program.parse(argv);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ═══════════════════════════════════════════════════════════
|
|
213
|
+
// COMMAND HANDLERS
|
|
214
|
+
// ═══════════════════════════════════════════════════════════
|
|
215
|
+
|
|
216
|
+
async function handleAsk(prompt, opts) {
|
|
217
|
+
const mode = getModeByName(opts.mode) || MODES[DEFAULT_MODE];
|
|
218
|
+
const aiConfig = await getAIConfig();
|
|
219
|
+
|
|
220
|
+
// Set theme from configuration
|
|
221
|
+
const theme = aiConfig.THEME || "cyberpunk";
|
|
222
|
+
setTheme(theme);
|
|
223
|
+
|
|
224
|
+
// Override model if specified
|
|
225
|
+
if (opts.model) {
|
|
226
|
+
// Set it for all providers
|
|
227
|
+
for (const p of Object.values(PROVIDERS)) {
|
|
228
|
+
aiConfig[`${p.key.replace("_API_KEY", "")}_MODEL`] = opts.model;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Attach file context if specified
|
|
233
|
+
let fullPrompt = prompt;
|
|
234
|
+
if (opts.file) {
|
|
235
|
+
try {
|
|
236
|
+
const fileData = await parseFile(opts.file);
|
|
237
|
+
fullPrompt = formatContext(fileData) + "\n\n" + prompt;
|
|
238
|
+
console.log(label.file + " " + colors.accent(`Attached: ${fileData.name}`) + colors.dim(` (${formatBytes(fileData.size)})`));
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.log(label.error + " " + colors.danger(err.message));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!opts.raw) {
|
|
246
|
+
console.log(label.mode + " " + colors.muted(`${mode.label} • ${mode.layer}`));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const queryStartTime = Date.now();
|
|
250
|
+
let firstTokenTime = 0;
|
|
251
|
+
const spinner = createSpinner(colors.muted("Routing through failover mesh..."));
|
|
252
|
+
spinner.start();
|
|
253
|
+
|
|
254
|
+
let hasStartedStreaming = false;
|
|
255
|
+
let streamedText = "";
|
|
256
|
+
const onToken = (token) => {
|
|
257
|
+
if (!hasStartedStreaming) {
|
|
258
|
+
hasStartedStreaming = true;
|
|
259
|
+
firstTokenTime = Date.now();
|
|
260
|
+
spinner.stop();
|
|
261
|
+
}
|
|
262
|
+
process.stdout.write(token);
|
|
263
|
+
streamedText += token;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const result = await routePrompt(fullPrompt, mode.systemPrompt, aiConfig, onToken);
|
|
268
|
+
spinner.stop();
|
|
269
|
+
|
|
270
|
+
if (opts.raw) {
|
|
271
|
+
if (!hasStartedStreaming) {
|
|
272
|
+
console.log(result.text);
|
|
273
|
+
} else {
|
|
274
|
+
if (!result.text.endsWith("\n")) {
|
|
275
|
+
console.log("");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
if (hasStartedStreaming) {
|
|
280
|
+
clearStreamedText(streamedText);
|
|
281
|
+
}
|
|
282
|
+
console.log("");
|
|
283
|
+
console.log(label.aether + " " + colors.dim(`via ${result.provider}${result.model ? ` (${result.model})` : ""} • Node ${result.node}`));
|
|
284
|
+
console.log(separator("─"));
|
|
285
|
+
console.log("");
|
|
286
|
+
|
|
287
|
+
if (result.provider === "local" || result.provider === "krylo-fallback") {
|
|
288
|
+
console.log(colors.text(" " + result.text.split("\n").join("\n ")));
|
|
289
|
+
} else {
|
|
290
|
+
let displayText = result.text;
|
|
291
|
+
const cleanedText = displayText.replace(/\[WRITE_FILE:\s*([^\n\]]+)\][\s\S]*?\[END_WRITE\]/g, (match, p1) => {
|
|
292
|
+
return `\n\n${colors.brand("⚡ [File creation request: " + p1 + "]")}\n\n`;
|
|
293
|
+
});
|
|
294
|
+
const rendered = getMarked().parse(cleanedText);
|
|
295
|
+
console.log(rendered);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const elapsedSec = ((Date.now() - queryStartTime) / 1000).toFixed(1);
|
|
299
|
+
let speedText = "";
|
|
300
|
+
if (firstTokenTime > 0) {
|
|
301
|
+
const streamElapsed = (Date.now() - firstTokenTime) / 1000;
|
|
302
|
+
if (streamElapsed > 0.05) {
|
|
303
|
+
const estimatedTokens = Math.max(1, Math.round(streamedText.length / 4));
|
|
304
|
+
const tps = (estimatedTokens / streamElapsed).toFixed(1);
|
|
305
|
+
speedText = ` • ${tps} tok/s`;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(separator("─"));
|
|
310
|
+
console.log(
|
|
311
|
+
" " + colors.dim(`Node ${result.node} • ${result.provider}`) +
|
|
312
|
+
(result.model ? colors.dim(` • ${result.model}`) : "") +
|
|
313
|
+
colors.dim(` • ${elapsedSec}s${speedText}`)
|
|
314
|
+
);
|
|
315
|
+
console.log("");
|
|
316
|
+
|
|
317
|
+
// Parse file write blocks
|
|
318
|
+
const writeRegex = /\[WRITE_FILE:\s*([^\n\]]+)\]\n([\s\S]*?)\n\[END_WRITE\]/g;
|
|
319
|
+
let match;
|
|
320
|
+
const fileWrites = [];
|
|
321
|
+
while ((match = writeRegex.exec(result.text)) !== null) {
|
|
322
|
+
fileWrites.push({ path: match[1].trim(), content: match[2] });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (fileWrites.length > 0) {
|
|
326
|
+
const { resolve, dirname } = await import("node:path");
|
|
327
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
328
|
+
|
|
329
|
+
for (const fileWrite of fileWrites) {
|
|
330
|
+
const finalPath = resolve(fileWrite.path);
|
|
331
|
+
console.log("");
|
|
332
|
+
console.log(label.system + " " + colors.warning(`Auto-Writing File: ${colors.accent(finalPath)} (${fileWrite.content.length} bytes)`));
|
|
333
|
+
try {
|
|
334
|
+
const dir = dirname(finalPath);
|
|
335
|
+
await mkdir(dir, { recursive: true });
|
|
336
|
+
await writeFile(finalPath, fileWrite.content, "utf-8");
|
|
337
|
+
console.log(" " + colors.success(`✓ File created successfully!\n`));
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.log(" " + colors.danger(`✗ Write failed: ${err.message}\n`));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch (err) {
|
|
345
|
+
spinner.fail("Request failed");
|
|
346
|
+
console.error(label.error + " " + colors.danger(err.message));
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function handleConfigSet(key, value) {
|
|
352
|
+
const normalizedKey = key.toUpperCase();
|
|
353
|
+
|
|
354
|
+
if (!isValidConfigKey(normalizedKey)) {
|
|
355
|
+
console.log("\n" + label.config + " " + colors.warning(`Unknown key format: "${normalizedKey}"`));
|
|
356
|
+
console.log(" " + colors.muted("Use format: PROVIDER_API_KEY or PROVIDER_MODEL"));
|
|
357
|
+
console.log(" " + colors.muted("Example: GROQ_API_KEY, OPENAI_API_KEY, GOOGLE_MODEL\n"));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
await setConfigValue(normalizedKey, value);
|
|
362
|
+
const maskedValue = normalizedKey.includes("KEY") && value.length > 8
|
|
363
|
+
? value.slice(0, 6) + "•••" + value.slice(-3)
|
|
364
|
+
: value;
|
|
365
|
+
console.log("\n" + label.config + " " + colors.success(`✓ Set ${normalizedKey} = ${maskedValue}`));
|
|
366
|
+
console.log(" " + colors.muted(`Saved to ${getConfigPath()}`) + "\n");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function handleConfigGet(key) {
|
|
370
|
+
const normalizedKey = key.toUpperCase();
|
|
371
|
+
const value = await getConfigValue(normalizedKey);
|
|
372
|
+
|
|
373
|
+
if (value === undefined) {
|
|
374
|
+
console.log("\n" + label.config + " " + colors.muted(`"${normalizedKey}" is not set.\n`));
|
|
375
|
+
} else {
|
|
376
|
+
const masked = normalizedKey.includes("KEY") && typeof value === "string" && value.length > 8
|
|
377
|
+
? value.slice(0, 6) + "•••" + value.slice(-3)
|
|
378
|
+
: value;
|
|
379
|
+
console.log("\n" + label.config + " " + keyValue(normalizedKey, masked) + "\n");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function handleConfigList() {
|
|
384
|
+
const exists = await configExists();
|
|
385
|
+
if (!exists) {
|
|
386
|
+
console.log("\n" + label.config + " " + colors.muted("No config file found."));
|
|
387
|
+
console.log(" " + colors.muted("Run ") + colors.accent("aether setup") + colors.muted(" for guided setup.\n"));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const config = await listConfig();
|
|
392
|
+
const keys = Object.keys(config);
|
|
393
|
+
|
|
394
|
+
if (keys.length === 0) {
|
|
395
|
+
console.log("\n" + label.config + " " + colors.muted("Config file is empty.\n"));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log("");
|
|
400
|
+
console.log(colors.brand(" ◈ CONFIGURATION"));
|
|
401
|
+
console.log(separator("─"));
|
|
402
|
+
for (const [k, v] of Object.entries(config)) {
|
|
403
|
+
console.log(keyValue(" " + k, v));
|
|
404
|
+
}
|
|
405
|
+
console.log("");
|
|
406
|
+
console.log(" " + colors.dim(`Location: ${getConfigPath()}`) + "\n");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function handleConfigDelete(key) {
|
|
410
|
+
const normalizedKey = key.toUpperCase();
|
|
411
|
+
await deleteConfigValue(normalizedKey);
|
|
412
|
+
console.log("\n" + label.config + " " + colors.success(`✓ Deleted "${normalizedKey}"`) + "\n");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function handleConfigReset() {
|
|
416
|
+
await resetConfig();
|
|
417
|
+
console.log("\n" + label.config + " " + colors.success("✓ All configuration cleared.") + "\n");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function handleProviders(opts) {
|
|
421
|
+
const aiConfig = await getAIConfig();
|
|
422
|
+
const active = getActiveProviders(aiConfig);
|
|
423
|
+
const activeIds = new Set(active.map((a) => a.id));
|
|
424
|
+
|
|
425
|
+
console.log("");
|
|
426
|
+
console.log(colors.brand(" ⚡ SUPPORTED AI PROVIDERS"));
|
|
427
|
+
console.log(separator("─"));
|
|
428
|
+
|
|
429
|
+
const tiers = getProvidersByTier();
|
|
430
|
+
const sections = [
|
|
431
|
+
{ label: "🆓 FREE TIER", providers: tiers.free, color: "#67ffb0" },
|
|
432
|
+
{ label: "🔓 FREE + PAID", providers: tiers["free+paid"], color: "#ffb900" },
|
|
433
|
+
{ label: "💎 PAID", providers: tiers.paid, color: "#6ce8ff" },
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
for (const section of sections) {
|
|
437
|
+
if (opts.free && section.label === "💎 PAID") continue;
|
|
438
|
+
|
|
439
|
+
console.log("");
|
|
440
|
+
console.log(" " + chalk.hex(section.color).bold(section.label));
|
|
441
|
+
console.log("");
|
|
442
|
+
|
|
443
|
+
for (const p of section.providers) {
|
|
444
|
+
const status = activeIds.has(p.id)
|
|
445
|
+
? colors.success(" ✓ ACTIVE")
|
|
446
|
+
: colors.dim(" ○ Not configured");
|
|
447
|
+
const name = chalk.hex(section.color).bold(p.name.padEnd(18));
|
|
448
|
+
console.log(` ${name} ${status}`);
|
|
449
|
+
console.log(` ${"".padEnd(18)} ${colors.muted(p.description)}`);
|
|
450
|
+
console.log(` ${"".padEnd(18)} ${colors.dim("Key: ")}${colors.accent(p.key)} ${colors.dim("Model: ")}${colors.text(p.defaultModel)}`);
|
|
451
|
+
console.log("");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
console.log(separator("─"));
|
|
456
|
+
console.log(" " + colors.muted("Configure: ") + colors.accent("aether config set <KEY_NAME> <your-key>"));
|
|
457
|
+
console.log(" " + colors.muted("Quick setup: ") + colors.accent("aether setup"));
|
|
458
|
+
console.log("");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function handleModels(providerName) {
|
|
462
|
+
if (!providerName) {
|
|
463
|
+
console.log("");
|
|
464
|
+
console.log(colors.brand(" ◈ MODELS BY PROVIDER"));
|
|
465
|
+
console.log(separator("─"));
|
|
466
|
+
|
|
467
|
+
for (const [id, p] of Object.entries(PROVIDERS)) {
|
|
468
|
+
console.log("");
|
|
469
|
+
console.log(" " + colors.accent(p.name));
|
|
470
|
+
for (const m of p.models) {
|
|
471
|
+
const isDefault = m === p.defaultModel;
|
|
472
|
+
console.log(" " + (isDefault ? colors.accent3(" ★ " + m) : colors.muted(" " + m)));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
console.log("");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const key = providerName.toLowerCase();
|
|
480
|
+
const provider = PROVIDERS[key];
|
|
481
|
+
|
|
482
|
+
if (!provider) {
|
|
483
|
+
console.log("\n" + label.error + " " + colors.danger(`Unknown provider: "${providerName}"`));
|
|
484
|
+
console.log(" " + colors.muted("Available: " + Object.keys(PROVIDERS).join(", ")) + "\n");
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
console.log("");
|
|
489
|
+
console.log(colors.brand(` ◈ ${provider.name} MODELS`));
|
|
490
|
+
console.log(separator("─"));
|
|
491
|
+
for (const m of provider.models) {
|
|
492
|
+
const isDefault = m === provider.defaultModel;
|
|
493
|
+
console.log(" " + (isDefault ? colors.accent3("★ " + m + " (default)") : colors.text(" " + m)));
|
|
494
|
+
}
|
|
495
|
+
console.log("");
|
|
496
|
+
console.log(" " + colors.muted("Override: ") + colors.accent(`aether ask --model ${provider.models[0]} "prompt"`) + "\n");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function handleModes() {
|
|
500
|
+
console.log("");
|
|
501
|
+
console.log(colors.brand(" ◈ AETHER REASONING MODES"));
|
|
502
|
+
console.log(separator("─"));
|
|
503
|
+
console.log("");
|
|
504
|
+
|
|
505
|
+
for (const mode of Object.values(MODES)) {
|
|
506
|
+
const badge = chalk.hex("#6ce8ff").bold(`[${mode.label}]`);
|
|
507
|
+
console.log(` ${badge} ${colors.dim(mode.layer)}`);
|
|
508
|
+
console.log(" " + colors.text(mode.description));
|
|
509
|
+
|
|
510
|
+
const sig = mode.signal;
|
|
511
|
+
const bar = (val) => {
|
|
512
|
+
const filled = Math.round(val / 10);
|
|
513
|
+
return chalk.hex("#6ce8ff")("█".repeat(filled)) + chalk.hex("#1a2a3a")("░".repeat(10 - filled)) + colors.dim(` ${val}%`);
|
|
514
|
+
};
|
|
515
|
+
console.log(
|
|
516
|
+
" " + colors.dim("RSN ") + bar(sig.reasoning) +
|
|
517
|
+
" " + colors.dim("CLR ") + bar(sig.clarity)
|
|
518
|
+
);
|
|
519
|
+
console.log(
|
|
520
|
+
" " + colors.dim("SIQ ") + bar(sig.systemIQ) +
|
|
521
|
+
" " + colors.dim("DLV ") + bar(sig.delivery)
|
|
522
|
+
);
|
|
523
|
+
console.log("");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function handleStatus() {
|
|
528
|
+
const aiConfig = await getAIConfig();
|
|
529
|
+
const exists = await configExists();
|
|
530
|
+
const active = getActiveProviders(aiConfig);
|
|
531
|
+
|
|
532
|
+
console.log("");
|
|
533
|
+
console.log(colors.brand(" ⚡ AETHER SYSTEM STATUS"));
|
|
534
|
+
console.log(separator("─"));
|
|
535
|
+
console.log(keyValue(" Version", `v${VERSION}`));
|
|
536
|
+
console.log(keyValue(" Config", exists ? colors.success("✓ Found") : colors.warning("✗ Not found")));
|
|
537
|
+
console.log(keyValue(" Location", getConfigPath()));
|
|
538
|
+
|
|
539
|
+
console.log("");
|
|
540
|
+
console.log(colors.accent(" ◈ Active Providers:"));
|
|
541
|
+
if (active.length === 0) {
|
|
542
|
+
console.log(" " + colors.warning(" No providers configured. Run `aether setup` to get started."));
|
|
543
|
+
} else {
|
|
544
|
+
for (const { id, provider } of active) {
|
|
545
|
+
console.log(" " + colors.success(" ✓ ") + colors.text(provider.name) + colors.dim(` (${provider.defaultModel})`));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
console.log("");
|
|
550
|
+
console.log(colors.accent(" ◈ Local Fallbacks:"));
|
|
551
|
+
console.log(keyValue(" Math Solver", colors.success("✓ Active")));
|
|
552
|
+
console.log(keyValue(" Krylo Companion", colors.success("✓ Standing By")));
|
|
553
|
+
|
|
554
|
+
console.log("");
|
|
555
|
+
console.log(colors.accent(" ◈ Failover Mesh:"));
|
|
556
|
+
const totalNodes = 1 + active.length; // +1 for Krylo
|
|
557
|
+
console.log(keyValue(" Active Nodes", `${totalNodes}`));
|
|
558
|
+
console.log(keyValue(" Mesh Status", active.length > 0 ? colors.success("✓ Online") : colors.warning("⚠ Local Only")));
|
|
559
|
+
console.log("");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function handleSetup() {
|
|
563
|
+
const { createInterface } = await import("node:readline");
|
|
564
|
+
|
|
565
|
+
console.log("");
|
|
566
|
+
console.log(colors.brand(" ⚡ AETHER SETUP WIZARD"));
|
|
567
|
+
console.log(separator("─"));
|
|
568
|
+
console.log("");
|
|
569
|
+
console.log(colors.text(" Configure your AI providers. Press Enter to skip any provider."));
|
|
570
|
+
console.log(colors.muted(" Keys are stored locally at: " + getConfigPath()));
|
|
571
|
+
console.log("");
|
|
572
|
+
|
|
573
|
+
const rl = createInterface({
|
|
574
|
+
input: process.stdin,
|
|
575
|
+
output: process.stdout,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const ask = (question) => new Promise((resolve) => {
|
|
579
|
+
rl.question(" " + colors.accent("? ") + colors.text(question) + " ", (answer) => {
|
|
580
|
+
resolve(answer.trim());
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Recommended free providers first
|
|
585
|
+
const setupOrder = [
|
|
586
|
+
{ id: "groq", hint: "Get free key at https://console.groq.com" },
|
|
587
|
+
{ id: "google", hint: "Get free key at https://aistudio.google.com/apikey" },
|
|
588
|
+
{ id: "openrouter", hint: "Free models at https://openrouter.ai/keys" },
|
|
589
|
+
{ id: "together", hint: "Free credits at https://api.together.xyz" },
|
|
590
|
+
{ id: "cerebras", hint: "Free tier at https://cloud.cerebras.ai" },
|
|
591
|
+
{ id: "cohere", hint: "Free dev key at https://dashboard.cohere.com" },
|
|
592
|
+
{ id: "openai", hint: "Paid — https://platform.openai.com/api-keys" },
|
|
593
|
+
{ id: "anthropic", hint: "Paid — https://console.anthropic.com" },
|
|
594
|
+
{ id: "xai", hint: "Paid — https://console.x.ai" },
|
|
595
|
+
{ id: "mistral", hint: "https://console.mistral.ai" },
|
|
596
|
+
{ id: "deepseek", hint: "https://platform.deepseek.com" },
|
|
597
|
+
{ id: "perplexity", hint: "https://www.perplexity.ai/settings/api" },
|
|
598
|
+
{ id: "fireworks", hint: "https://fireworks.ai/api-keys" },
|
|
599
|
+
];
|
|
600
|
+
|
|
601
|
+
let configured = 0;
|
|
602
|
+
|
|
603
|
+
for (const { id, hint } of setupOrder) {
|
|
604
|
+
const provider = PROVIDERS[id];
|
|
605
|
+
if (!provider) continue;
|
|
606
|
+
|
|
607
|
+
const tierBadge = provider.tier === "free"
|
|
608
|
+
? chalk.hex("#67ffb0")("[FREE]")
|
|
609
|
+
: provider.tier === "free+paid"
|
|
610
|
+
? chalk.hex("#ffb900")("[FREE+PAID]")
|
|
611
|
+
: chalk.hex("#6ce8ff")("[PAID]");
|
|
612
|
+
|
|
613
|
+
console.log(` ${tierBadge} ${colors.text.bold(provider.name)} — ${colors.dim(provider.description)}`);
|
|
614
|
+
console.log(" " + colors.dim(hint));
|
|
615
|
+
|
|
616
|
+
const key = await ask(`${provider.key}:`);
|
|
617
|
+
if (key) {
|
|
618
|
+
await setConfigValue(provider.key, key);
|
|
619
|
+
console.log(" " + colors.success("✓ Saved!") + "\n");
|
|
620
|
+
configured++;
|
|
621
|
+
} else {
|
|
622
|
+
console.log(" " + colors.dim("Skipped") + "\n");
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
rl.close();
|
|
627
|
+
|
|
628
|
+
console.log(separator("─", 62));
|
|
629
|
+
if (configured > 0) {
|
|
630
|
+
console.log("\n " + colors.success(`✓ Setup complete! ${configured} provider(s) configured.`));
|
|
631
|
+
console.log(" " + colors.muted("Start chatting: ") + colors.accent("aether chat"));
|
|
632
|
+
console.log(" " + colors.muted("Quick query: ") + colors.accent('aether ask "Hello!"'));
|
|
633
|
+
} else {
|
|
634
|
+
console.log("\n " + colors.warning("No providers configured. Aether will use Krylo fallback mode."));
|
|
635
|
+
console.log(" " + colors.muted("Run ") + colors.accent("aether setup") + colors.muted(" again anytime."));
|
|
636
|
+
}
|
|
637
|
+
console.log("");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function showMiniBanner() {
|
|
641
|
+
console.log("");
|
|
642
|
+
console.log(colors.brand(" ⚡ Aether Core AI v110") + colors.dim(" — Universal AI Gateway"));
|
|
643
|
+
console.log(separator("─"));
|
|
644
|
+
console.log("");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function formatBytes(bytes) {
|
|
648
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
649
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
650
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async function handleThemeGet() {
|
|
654
|
+
const aiConfig = await getAIConfig();
|
|
655
|
+
const theme = aiConfig.THEME || "cyberpunk";
|
|
656
|
+
console.log("\n" + label.config + " " + colors.muted("Active Theme: ") + colors.accent(theme.toUpperCase()) + "\n");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function handleThemeSet(name) {
|
|
660
|
+
const success = setTheme(name);
|
|
661
|
+
if (success) {
|
|
662
|
+
await setConfigValue("THEME", name.toLowerCase().trim());
|
|
663
|
+
console.log("\n" + label.config + " " + colors.success(`✓ Switched theme to ${name.toUpperCase()}`) + "\n");
|
|
664
|
+
} else {
|
|
665
|
+
console.log("\n" + label.error + " " + colors.danger(`Unknown theme: "${name}".`) + colors.muted(` Available: ${getThemesList().join(", ")}\n`));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function handleThemesList() {
|
|
670
|
+
console.log("");
|
|
671
|
+
console.log(colors.brand(" ◈ AVAILABLE COLOR THEMES"));
|
|
672
|
+
console.log(separator("─"));
|
|
673
|
+
const active = getActiveTheme();
|
|
674
|
+
for (const t of getThemesList()) {
|
|
675
|
+
const isAct = t === active ? colors.success("★ ACTIVE") : "";
|
|
676
|
+
console.log(bullet(t.toUpperCase().padEnd(14) + isAct));
|
|
677
|
+
}
|
|
678
|
+
console.log("");
|
|
679
|
+
}
|