@krishivpb60/aether-ai-cli 1.4.0 → 1.4.2
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/HIGHLIGHTS.md +11 -0
- package/package.json +1 -1
- package/src/ai/fallback.js +16 -68
- package/src/ai/router.js +16 -16
- package/src/chat.js +4 -4
- package/src/cli.js +4 -4
- package/src/telemetry-server.js +2 -2
- package/src/ui/dashboard.html +1 -1
- package/test/fallback.test.js +10 -32
- package/test/router.test.js +9 -6
package/HIGHLIGHTS.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
# Aether CLI v1.4.2 Highlights
|
|
2
|
+
- **Mesh Error Transparency**:
|
|
3
|
+
- Implements dynamic offline fallback error alerts containing the exact error messages encountered by all failing provider nodes.
|
|
4
|
+
- Ensures Aether only states "No active API keys configured" if no keys are setup at all, rather than outputting it incorrectly upon network or API limit node failures.
|
|
5
|
+
|
|
6
|
+
# Aether CLI v1.4.1 Highlights
|
|
7
|
+
- **Krylo Companion Bot Removal**:
|
|
8
|
+
- Removes the fictional Krylo companion terminal response lines entirely from local failbacks and mesh failures.
|
|
9
|
+
- Retains and isolates the fast local offline Math solver fallback.
|
|
10
|
+
- Implements clean, professional offline/configuration error alerts when no API keys are active or fail to respond.
|
|
11
|
+
|
|
1
12
|
# Aether CLI v1.4.0 Highlights
|
|
2
13
|
- **Microphone Audio Input & Dynamic Nerd Font Glyphs (`/mic`)**:
|
|
3
14
|
- Adds `/mic` voice command to record audio directly from your microphone inside the terminal session.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krishivpb60/aether-ai-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Aether Core AI — A cyberpunk command-line AI assistant with multi-mode reasoning, 12-node failover mesh, file context injection, and offline fallbacks.",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/ai/fallback.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════════════════════
|
|
2
2
|
// AETHER AI CLI — Local Fallback Engine
|
|
3
|
-
// Math Solver
|
|
3
|
+
// Math Solver & Offline Fallback
|
|
4
4
|
// ═══════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
|
-
const KRYLO_REPLIES = [
|
|
7
|
-
"Affirmative, commander. Systems are running at peak cybernetic capacity.",
|
|
8
|
-
"Neon grids initialized. Matrix color modulates are at nominal density.",
|
|
9
|
-
"Warning: Solar flare activity detected. Detuning audio synth harmonics by 18.4% to compensate.",
|
|
10
|
-
"Neural nodes synchronized. Analyzing the portfolio's glassmorphic boundaries.",
|
|
11
|
-
"I am Krylo, your holographic companion terminal. Ready to warp index nodes.",
|
|
12
|
-
"Ecosystem diagnostics complete. 0 memory leaks, 100% premium responsive UI."
|
|
13
|
-
];
|
|
14
|
-
|
|
15
6
|
/**
|
|
16
7
|
* Detects if a prompt is a pure mathematical expression.
|
|
17
8
|
* Supports basic operators, parentheses, standard math functions, and constants.
|
|
@@ -108,72 +99,29 @@ export function runMainframeHack() {
|
|
|
108
99
|
}
|
|
109
100
|
|
|
110
101
|
/**
|
|
111
|
-
* Generates a local
|
|
102
|
+
* Generates a local offline/error reply when no AI keys are configured or fail.
|
|
112
103
|
* @param {string} prompt - The user prompt
|
|
104
|
+
* @param {string[]} [errors] - Optional error messages from failed provider nodes
|
|
113
105
|
* @returns {{ text: string, type: string }}
|
|
114
106
|
*/
|
|
115
|
-
export function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (clean.includes("help") || clean.includes("shortcut") || clean.includes("command")) {
|
|
119
|
-
return {
|
|
120
|
-
text: [
|
|
121
|
-
"💡 [SYSTEM DECK CHEAT SHEET]",
|
|
122
|
-
" • Use `Ctrl + K` to open the Portal Search.",
|
|
123
|
-
" • Use `Ctrl + Shift + L` to open the Links Directory.",
|
|
124
|
-
" • Trigger Konami Code `↑↑↓↓←→←→BA` to launch Matrix mode!",
|
|
125
|
-
" • Type `/mode <name>` to switch reasoning modes.",
|
|
126
|
-
" • Type `/attach <file>` to inject file context.",
|
|
127
|
-
" • Type `/export` to save the conversation.",
|
|
128
|
-
].join("\n"),
|
|
129
|
-
type: "krylo-local",
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (clean.includes("status") || clean.includes("hud") || clean.includes("cpu") || clean.includes("ping") || clean.includes("diagnostics")) {
|
|
107
|
+
export function generateOfflineReply(prompt, errors = []) {
|
|
108
|
+
if (errors && errors.length > 0) {
|
|
134
109
|
return {
|
|
135
110
|
text: [
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
" • Failover Mesh: All 12 nodes standing by",
|
|
111
|
+
"⚠️ All configured AI provider nodes failed to respond.",
|
|
112
|
+
" Errors encountered:",
|
|
113
|
+
...errors.map((e) => ` • ${e}`),
|
|
114
|
+
"",
|
|
115
|
+
" Please check your API keys, network connection, or rate limits."
|
|
142
116
|
].join("\n"),
|
|
143
|
-
type: "
|
|
117
|
+
type: "offline-error"
|
|
144
118
|
};
|
|
145
119
|
}
|
|
146
|
-
|
|
147
|
-
if (clean.includes("matrix") || clean.includes("rain") || clean.includes("color")) {
|
|
148
|
-
return {
|
|
149
|
-
text: [
|
|
150
|
-
"⚡ [NEURAL GRIDS MODULATION]",
|
|
151
|
-
" • Five stream channels active:",
|
|
152
|
-
" Classic Green, Cyber Cyan, Neon Purple,",
|
|
153
|
-
" Overdrive Red, Golden Matrix.",
|
|
154
|
-
" • Detuned Web Audio frequency active.",
|
|
155
|
-
" • Matrix rain density: 94.2%",
|
|
156
|
-
].join("\n"),
|
|
157
|
-
type: "krylo-local",
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (clean.includes("who") || clean.includes("name") || clean.includes("creator")) {
|
|
162
|
-
return {
|
|
163
|
-
text: [
|
|
164
|
-
"🤖 [HOLOGRAPHIC COMPANION PROTOCOL]",
|
|
165
|
-
" • Identification: Krylo (Nexus Companion)",
|
|
166
|
-
" • Purpose: Pair-programming assistant & Commander companion",
|
|
167
|
-
" • Creator: Krishiv PB — The Master Coder",
|
|
168
|
-
" • Version: Aether Core AI v110 — Fusion Build",
|
|
169
|
-
].join("\n"),
|
|
170
|
-
type: "krylo-local",
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const index = Math.floor(Math.random() * KRYLO_REPLIES.length);
|
|
175
120
|
return {
|
|
176
|
-
text:
|
|
177
|
-
|
|
121
|
+
text: [
|
|
122
|
+
"⚠️ No active API keys configured. Please set GOOGLE_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY in your config to start chatting.",
|
|
123
|
+
" Example: aether config set GOOGLE_API_KEY <your-key>"
|
|
124
|
+
].join("\n"),
|
|
125
|
+
type: "offline-error"
|
|
178
126
|
};
|
|
179
127
|
}
|
package/src/ai/router.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Routes through ALL configured providers automatically
|
|
4
4
|
// ═══════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
|
-
import { detectMathExpression, solveMath,
|
|
6
|
+
import { detectMathExpression, solveMath, generateOfflineReply } from "./fallback.js";
|
|
7
7
|
import { PROVIDERS, getActiveProviders } from "./providers.js";
|
|
8
8
|
import {
|
|
9
9
|
callOpenAICompatible,
|
|
@@ -59,16 +59,16 @@ export async function routePrompt(prompt, systemPrompt, config, onToken, history
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// ── No providers configured →
|
|
62
|
+
// ── No providers configured → Offline ───────────────────
|
|
63
63
|
if (active.length === 0) {
|
|
64
64
|
const startTime = performance.now();
|
|
65
|
-
const
|
|
65
|
+
const offlineReply = generateOfflineReply(prompt);
|
|
66
66
|
const latencyMs = performance.now() - startTime;
|
|
67
67
|
const pTokens = estimateTokens(systemPrompt + prompt);
|
|
68
|
-
const cTokens = estimateTokens(
|
|
69
|
-
const usage = recordTokenUsage("
|
|
70
|
-
recordLatency("
|
|
71
|
-
return { ...
|
|
68
|
+
const cTokens = estimateTokens(offlineReply.text);
|
|
69
|
+
const usage = recordTokenUsage("offline-local", pTokens, cTokens);
|
|
70
|
+
recordLatency("offline-fallback", "local", latencyMs, pTokens, cTokens, true);
|
|
71
|
+
return { ...offlineReply, provider: "offline-fallback", node: 0, usage };
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// ── Try each provider in order ──────────────────────────
|
|
@@ -128,17 +128,17 @@ export async function routePrompt(prompt, systemPrompt, config, onToken, history
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
// ── Final Fallback:
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const
|
|
131
|
+
// ── Final Fallback: Offline Fallback ────────────────────
|
|
132
|
+
const startTimeOffline = performance.now();
|
|
133
|
+
const offlineReply = generateOfflineReply(prompt, errors);
|
|
134
|
+
const latencyMsOffline = performance.now() - startTimeOffline;
|
|
135
135
|
const pTokens = estimateTokens(systemPrompt + prompt + history.map(h => h.content).join(""));
|
|
136
|
-
const cTokens = estimateTokens(
|
|
137
|
-
const usage = recordTokenUsage("
|
|
138
|
-
recordLatency("
|
|
136
|
+
const cTokens = estimateTokens(offlineReply.text);
|
|
137
|
+
const usage = recordTokenUsage("offline-local", pTokens, cTokens);
|
|
138
|
+
recordLatency("offline-fallback", "local", latencyMsOffline, pTokens, cTokens, true);
|
|
139
139
|
return {
|
|
140
|
-
...
|
|
141
|
-
provider: "
|
|
140
|
+
...offlineReply,
|
|
141
|
+
provider: "offline-fallback",
|
|
142
142
|
node: 0,
|
|
143
143
|
errors,
|
|
144
144
|
usage,
|
package/src/chat.js
CHANGED
|
@@ -117,7 +117,7 @@ export async function startChat(options = {}) {
|
|
|
117
117
|
label.mesh + " " +
|
|
118
118
|
colors.accent("Failover mesh online: ") +
|
|
119
119
|
colors.text(unique.join(" → ")) +
|
|
120
|
-
colors.muted(" →
|
|
120
|
+
colors.muted(" → Offline fallback")
|
|
121
121
|
);
|
|
122
122
|
console.log(
|
|
123
123
|
" " + colors.dim(`${active.length} node(s) active across ${unique.length} provider(s)`) + "\n"
|
|
@@ -298,7 +298,7 @@ export async function startChat(options = {}) {
|
|
|
298
298
|
console.log(separator("─"));
|
|
299
299
|
console.log("");
|
|
300
300
|
|
|
301
|
-
if (result.provider === "local" || result.provider === "
|
|
301
|
+
if (result.provider === "local" || result.provider === "offline-fallback") {
|
|
302
302
|
console.log(colors.text(" " + result.text.split("\n").join("\n ")));
|
|
303
303
|
} else {
|
|
304
304
|
let displayText = result.text;
|
|
@@ -897,7 +897,7 @@ function showActiveProviders(aiConfig) {
|
|
|
897
897
|
for (const { provider } of active) {
|
|
898
898
|
console.log(" " + colors.success("✓ ") + colors.text(provider.name) + colors.dim(` • ${provider.defaultModel}`));
|
|
899
899
|
}
|
|
900
|
-
console.log(" " + colors.success("✓ ") + colors.text("
|
|
900
|
+
console.log(" " + colors.success("✓ ") + colors.text("Offline Fallback") + colors.dim(" • Local fallback"));
|
|
901
901
|
console.log(" " + colors.success("✓ ") + colors.text("Math Solver") + colors.dim(" • Local"));
|
|
902
902
|
console.log("");
|
|
903
903
|
}
|
|
@@ -1184,7 +1184,7 @@ function providerBadge(result) {
|
|
|
1184
1184
|
"perplexity": chalk.bgHex("#1a2a2a").hex("#6ce8ff")(" Perplexity "),
|
|
1185
1185
|
"fireworks ai": chalk.bgHex("#2a1a1a").hex("#ff6b8d")(" Fireworks "),
|
|
1186
1186
|
"local": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" Math Solver "),
|
|
1187
|
-
"
|
|
1187
|
+
"offline-fallback": chalk.bgHex("#2a0a14").hex("#ff6b6b")(" Offline "),
|
|
1188
1188
|
};
|
|
1189
1189
|
|
|
1190
1190
|
const badge = badges[result.provider] || colors.muted(` ${result.provider} `);
|
package/src/cli.js
CHANGED
|
@@ -312,7 +312,7 @@ async function handleAsk(prompt, opts) {
|
|
|
312
312
|
console.log(separator("─"));
|
|
313
313
|
console.log("");
|
|
314
314
|
|
|
315
|
-
if (result.provider === "local" || result.provider === "
|
|
315
|
+
if (result.provider === "local" || result.provider === "offline-fallback") {
|
|
316
316
|
console.log(colors.text(" " + result.text.split("\n").join("\n ")));
|
|
317
317
|
} else {
|
|
318
318
|
let displayText = result.text;
|
|
@@ -577,11 +577,11 @@ async function handleStatus() {
|
|
|
577
577
|
console.log("");
|
|
578
578
|
console.log(colors.accent(" ◈ Local Fallbacks:"));
|
|
579
579
|
console.log(keyValue(" Math Solver", colors.success("✓ Active")));
|
|
580
|
-
console.log(keyValue("
|
|
580
|
+
console.log(keyValue(" Offline Fallback", colors.success("✓ Standing By")));
|
|
581
581
|
|
|
582
582
|
console.log("");
|
|
583
583
|
console.log(colors.accent(" ◈ Failover Mesh:"));
|
|
584
|
-
const totalNodes = 1 + active.length; // +1 for
|
|
584
|
+
const totalNodes = 1 + active.length; // +1 for local offline fallback
|
|
585
585
|
console.log(keyValue(" Active Nodes", `${totalNodes}`));
|
|
586
586
|
console.log(keyValue(" Mesh Status", active.length > 0 ? colors.success("✓ Online") : colors.warning("⚠ Local Only")));
|
|
587
587
|
console.log("");
|
|
@@ -739,7 +739,7 @@ async function handleSetup() {
|
|
|
739
739
|
console.log(" " + colors.muted("Start chatting: ") + colors.accent("aether chat"));
|
|
740
740
|
console.log(" " + colors.muted("Quick query: ") + colors.accent('aether ask "Hello!"'));
|
|
741
741
|
} else {
|
|
742
|
-
console.log("\n " + colors.warning("No providers configured. Aether will use
|
|
742
|
+
console.log("\n " + colors.warning("No providers configured. Aether will use local offline fallback mode."));
|
|
743
743
|
console.log(" " + colors.muted("Run ") + colors.accent("aether setup") + colors.muted(" again anytime."));
|
|
744
744
|
}
|
|
745
745
|
console.log("");
|
package/src/telemetry-server.js
CHANGED
|
@@ -536,9 +536,9 @@ const HTML_CONTENT = `<!DOCTYPE html>
|
|
|
536
536
|
topoList.appendChild(createTopologyElement({
|
|
537
537
|
name: "Local Solver Node",
|
|
538
538
|
configured: true,
|
|
539
|
-
defaultModel: "Offline Math
|
|
539
|
+
defaultModel: "Offline Math & Logic",
|
|
540
540
|
tier: "free",
|
|
541
|
-
description: "Zero-latency mathematical reasoning & local
|
|
541
|
+
description: "Zero-latency mathematical reasoning & local offline fallbacks."
|
|
542
542
|
}, "Node 0 (Local)"));
|
|
543
543
|
|
|
544
544
|
mesh.forEach((provider, idx) => {
|
package/src/ui/dashboard.html
CHANGED
package/test/fallback.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import assert from "node:assert";
|
|
|
3
3
|
import {
|
|
4
4
|
detectMathExpression,
|
|
5
5
|
solveMath,
|
|
6
|
-
|
|
6
|
+
generateOfflineReply,
|
|
7
7
|
runMainframeHack,
|
|
8
8
|
} from "../src/ai/fallback.js";
|
|
9
9
|
|
|
@@ -50,38 +50,16 @@ test("Offline Math Fallback & Krylo Suite", async (t) => {
|
|
|
50
50
|
assert.strictEqual(solveMath("console.log(1)"), null);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
await t.test("
|
|
54
|
-
const reply =
|
|
55
|
-
assert.strictEqual(reply.type, "
|
|
56
|
-
assert.ok(reply.text.includes("
|
|
57
|
-
assert.ok(reply.text.includes("Ctrl + K"));
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
await t.test("generateKryloReply responds to status and diagnostic keywords", () => {
|
|
61
|
-
const reply = generateKryloReply("What is the CPU status?");
|
|
62
|
-
assert.strictEqual(reply.type, "krylo-local");
|
|
63
|
-
assert.ok(reply.text.includes("[LIVE DIAGNOSTIC READOUT]"));
|
|
64
|
-
assert.ok(reply.text.includes("Failover Mesh"));
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await t.test("generateKryloReply responds to matrix/rain/color keywords", () => {
|
|
68
|
-
const reply = generateKryloReply("change matrix color");
|
|
69
|
-
assert.strictEqual(reply.type, "krylo-local");
|
|
70
|
-
assert.ok(reply.text.includes("[NEURAL GRIDS MODULATION]"));
|
|
71
|
-
assert.ok(reply.text.includes("Classic Green"));
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
await t.test("generateKryloReply responds to who/name/creator keywords", () => {
|
|
75
|
-
const reply = generateKryloReply("who is your creator?");
|
|
76
|
-
assert.strictEqual(reply.type, "krylo-local");
|
|
77
|
-
assert.ok(reply.text.includes("[HOLOGRAPHIC COMPANION PROTOCOL]"));
|
|
78
|
-
assert.ok(reply.text.includes("Krishiv PB"));
|
|
79
|
-
});
|
|
53
|
+
await t.test("generateOfflineReply returns offline error formatting", () => {
|
|
54
|
+
const reply = generateOfflineReply("any query");
|
|
55
|
+
assert.strictEqual(reply.type, "offline-error");
|
|
56
|
+
assert.ok(reply.text.includes("No active API keys configured"));
|
|
80
57
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
assert.
|
|
84
|
-
assert.ok(
|
|
58
|
+
const replyWithErrors = generateOfflineReply("any query", ["Timeout error", "Quota exceeded"]);
|
|
59
|
+
assert.strictEqual(replyWithErrors.type, "offline-error");
|
|
60
|
+
assert.ok(replyWithErrors.text.includes("All configured AI provider nodes failed to respond"));
|
|
61
|
+
assert.ok(replyWithErrors.text.includes("Timeout error"));
|
|
62
|
+
assert.ok(replyWithErrors.text.includes("Quota exceeded"));
|
|
85
63
|
});
|
|
86
64
|
|
|
87
65
|
await t.test("detectMathExpression and solveMath support trig, logs, square root and constants", () => {
|
package/test/router.test.js
CHANGED
|
@@ -134,19 +134,19 @@ test("Universal AI Router Suite", async (t) => {
|
|
|
134
134
|
assert.ok(fetchCalls[1].url.includes("key=key-success"));
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
await t.test("routePrompt falls back to
|
|
137
|
+
await t.test("routePrompt falls back to Offline fallback when no providers are configured", async () => {
|
|
138
138
|
globalThis.fetch = async () => {
|
|
139
139
|
throw new Error("Fetch should not be called");
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
const result = await routePrompt("status", "Sys prompt", {});
|
|
143
|
-
assert.strictEqual(result.provider, "
|
|
143
|
+
assert.strictEqual(result.provider, "offline-fallback");
|
|
144
144
|
assert.strictEqual(result.node, 0);
|
|
145
|
-
assert.strictEqual(result.type, "
|
|
146
|
-
assert.ok(result.text.includes("
|
|
145
|
+
assert.strictEqual(result.type, "offline-error");
|
|
146
|
+
assert.ok(result.text.includes("No active API keys configured"));
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
await t.test("routePrompt falls back to
|
|
149
|
+
await t.test("routePrompt falls back to Offline fallback when all providers fail", async () => {
|
|
150
150
|
globalThis.fetch = async (url, options) => {
|
|
151
151
|
fetchCalls.push({ url, options });
|
|
152
152
|
return {
|
|
@@ -164,12 +164,15 @@ test("Universal AI Router Suite", async (t) => {
|
|
|
164
164
|
|
|
165
165
|
const result = await routePrompt("Hello", "Sys prompt", config);
|
|
166
166
|
|
|
167
|
-
assert.strictEqual(result.provider, "
|
|
167
|
+
assert.strictEqual(result.provider, "offline-fallback");
|
|
168
168
|
assert.strictEqual(result.node, 0);
|
|
169
169
|
assert.ok(result.errors);
|
|
170
170
|
assert.strictEqual(result.errors.length, 2);
|
|
171
171
|
assert.ok(result.errors[0].includes("Node 1 Groq"));
|
|
172
172
|
assert.ok(result.errors[1].includes("Node 2 OpenAI"));
|
|
173
|
+
assert.ok(result.text.includes("All configured AI provider nodes failed to respond"));
|
|
174
|
+
assert.ok(result.text.includes("Node 1 Groq"));
|
|
175
|
+
assert.ok(result.text.includes("Node 2 OpenAI"));
|
|
173
176
|
});
|
|
174
177
|
|
|
175
178
|
await t.test("routePrompt forwards chat history to OpenAI and Google payloads correctly", async () => {
|