@krishivpb60/aether-ai-cli 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/.github/workflows/ci.yml +30 -0
- package/LICENSE +21 -0
- package/ORIGINAL_REQUEST.md +74 -0
- package/README.md +271 -0
- package/aether_pip/__init__.py +1 -0
- package/aether_pip/cli.py +49 -0
- package/bin/aether.js +10 -0
- package/package.json +46 -0
- package/setup.py +51 -0
- package/src/ai/fallback.js +179 -0
- package/src/ai/google.js +87 -0
- package/src/ai/providers.js +203 -0
- package/src/ai/router.js +114 -0
- package/src/ai/universal.js +465 -0
- package/src/ai/xai.js +50 -0
- package/src/chat.js +1034 -0
- package/src/cli.js +642 -0
- package/src/config.js +214 -0
- package/src/file-parser.js +94 -0
- package/src/modes.js +88 -0
- package/src/ui/banner.js +60 -0
- package/src/ui/spinner.js +43 -0
- package/src/ui/theme.js +169 -0
- package/test/config.test.js +182 -0
- package/test/fallback.test.js +105 -0
- package/test/file-parser.test.js +136 -0
- package/test/router.test.js +174 -0
- package/test/ux.test.js +128 -0
package/test/ux.test.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { test, beforeEach, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { separator, clearStreamedText, getActiveTheme, setTheme, getThemesList } from "../src/ui/theme.js";
|
|
4
|
+
import { createSpinner } from "../src/ui/spinner.js";
|
|
5
|
+
import { routePrompt } from "../src/ai/router.js";
|
|
6
|
+
|
|
7
|
+
const originalFetch = globalThis.fetch;
|
|
8
|
+
|
|
9
|
+
test("Cyberpunk UX and Streaming Suite", async (t) => {
|
|
10
|
+
let fetchCalls = [];
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
fetchCalls = [];
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
globalThis.fetch = originalFetch;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await t.test("createSpinner should return custom frames and 80ms interval", () => {
|
|
21
|
+
const spinner = createSpinner("Loading");
|
|
22
|
+
assert.strictEqual(spinner.color, "cyan");
|
|
23
|
+
assert.deepEqual(spinner.spinner, {
|
|
24
|
+
interval: 80,
|
|
25
|
+
frames: ["▖", "▘", "▝", "▗"]
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await t.test("separator should adjust length dynamically based on terminal width", () => {
|
|
30
|
+
const originalColumns = process.stdout.columns;
|
|
31
|
+
|
|
32
|
+
// Mock columns
|
|
33
|
+
Object.defineProperty(process.stdout, "columns", {
|
|
34
|
+
value: 100,
|
|
35
|
+
configurable: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const sep = separator("─");
|
|
39
|
+
// Should be process.stdout.columns - 4 = 96
|
|
40
|
+
assert.strictEqual(sep.length, separator("─", 96).length);
|
|
41
|
+
|
|
42
|
+
// Reset columns definition
|
|
43
|
+
if (originalColumns === undefined) {
|
|
44
|
+
delete process.stdout.columns;
|
|
45
|
+
} else {
|
|
46
|
+
Object.defineProperty(process.stdout, "columns", {
|
|
47
|
+
value: originalColumns,
|
|
48
|
+
configurable: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await t.test("routePrompt calls callOpenAICompatible and streams tokens", async () => {
|
|
54
|
+
globalThis.fetch = async (url, options) => {
|
|
55
|
+
fetchCalls.push({ url, options });
|
|
56
|
+
|
|
57
|
+
const encoder = new TextEncoder();
|
|
58
|
+
const chunks = [
|
|
59
|
+
'data: {"choices":[{"delta":{"content":"Hello"}}]}\n',
|
|
60
|
+
'data: {"choices":[{"delta":{"content":" Cyberpunk"}}]}\n',
|
|
61
|
+
'data: {"choices":[{"delta":{"content":" World"}}]}\n',
|
|
62
|
+
'data: [DONE]\n'
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const mockStream = new ReadableStream({
|
|
66
|
+
start(controller) {
|
|
67
|
+
for (const chunk of chunks) {
|
|
68
|
+
controller.enqueue(encoder.encode(chunk));
|
|
69
|
+
}
|
|
70
|
+
controller.close();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
body: mockStream,
|
|
77
|
+
json: async () => ({
|
|
78
|
+
choices: [{ message: { content: "Hello Cyberpunk World" } }],
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const config = {
|
|
84
|
+
GROQ_API_KEY: "groq-stream-key",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let receivedTokens = [];
|
|
88
|
+
const onToken = (token) => {
|
|
89
|
+
receivedTokens.push(token);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const result = await routePrompt("Hello", "System prompt", config, onToken);
|
|
93
|
+
|
|
94
|
+
assert.strictEqual(result.provider, "groq");
|
|
95
|
+
assert.strictEqual(result.text, "Hello Cyberpunk World");
|
|
96
|
+
assert.deepEqual(receivedTokens, ["Hello", " Cyberpunk", " World"]);
|
|
97
|
+
assert.strictEqual(fetchCalls.length, 1);
|
|
98
|
+
|
|
99
|
+
// Verify options body has stream: true
|
|
100
|
+
const sentBody = JSON.parse(fetchCalls[0].options.body);
|
|
101
|
+
assert.strictEqual(sentBody.stream, true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await t.test("Theme switching functions manage state correctly", () => {
|
|
105
|
+
// 1. Initial active theme should be cyberpunk
|
|
106
|
+
assert.strictEqual(getActiveTheme(), "cyberpunk");
|
|
107
|
+
|
|
108
|
+
// 2. Switch to matrix
|
|
109
|
+
const success = setTheme("matrix");
|
|
110
|
+
assert.strictEqual(success, true);
|
|
111
|
+
assert.strictEqual(getActiveTheme(), "matrix");
|
|
112
|
+
|
|
113
|
+
// 3. Switch to invalid theme should return false and not change theme
|
|
114
|
+
const fail = setTheme("nonexistent-theme");
|
|
115
|
+
assert.strictEqual(fail, false);
|
|
116
|
+
assert.strictEqual(getActiveTheme(), "matrix");
|
|
117
|
+
|
|
118
|
+
// 4. Get all themes list
|
|
119
|
+
const list = getThemesList();
|
|
120
|
+
assert.ok(list.includes("cyberpunk"));
|
|
121
|
+
assert.ok(list.includes("matrix"));
|
|
122
|
+
assert.ok(list.includes("synthwave"));
|
|
123
|
+
assert.ok(list.includes("crimson"));
|
|
124
|
+
|
|
125
|
+
// Reset back to cyberpunk
|
|
126
|
+
setTheme("cyberpunk");
|
|
127
|
+
});
|
|
128
|
+
});
|