@krishivpb60/aether-ai-cli 1.3.3 → 1.3.5
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 +13 -0
- package/package.json +1 -1
- package/src/ai/router.js +17 -0
- package/src/ai/search.js +94 -0
- package/src/ai/telemetry.js +125 -0
- package/src/chat.js +2184 -1617
- package/src/cli.js +31 -0
- package/src/dashboard.js +112 -0
- package/src/telemetry-server.js +855 -0
- package/src/ui/dashboard.html +834 -0
- package/test/autopilot-debug.test.js +91 -0
- package/test/git-tui.test.js +94 -0
- package/test/search.test.js +52 -0
- package/test/telemetry.test.js +104 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const tempHome = join(process.cwd(), "temp-test-home-autopilot");
|
|
7
|
+
process.env.USERPROFILE = tempHome;
|
|
8
|
+
process.env.HOME = tempHome;
|
|
9
|
+
process.env.NODE_ENV = "test";
|
|
10
|
+
|
|
11
|
+
const originalFetch = globalThis.fetch;
|
|
12
|
+
const { handleAutopilotDebug } = await import("../src/chat.js");
|
|
13
|
+
|
|
14
|
+
test("Autopilot Self-Correcting Debug Suite", async (t) => {
|
|
15
|
+
before(() => {
|
|
16
|
+
mkdirSync(tempHome, { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
after(() => {
|
|
20
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
21
|
+
globalThis.fetch = originalFetch;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await t.test("handleAutopilotDebug stops immediately if test command passes", async () => {
|
|
25
|
+
const ctx = {
|
|
26
|
+
aiConfig: { DIAGNOSE_CMD: "node -e \"process.exit(0)\"" },
|
|
27
|
+
rl: { pause: () => {}, resume: () => {} },
|
|
28
|
+
history: [],
|
|
29
|
+
currentMode: { name: "titan" }
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let logged = [];
|
|
33
|
+
const origLog = console.log;
|
|
34
|
+
console.log = (m) => logged.push(m);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await handleAutopilotDebug("node -e \"process.exit(0)\"", ctx);
|
|
38
|
+
|
|
39
|
+
const hasSuccess = logged.some(l => l && l.includes("passed successfully"));
|
|
40
|
+
assert.ok(hasSuccess);
|
|
41
|
+
} finally {
|
|
42
|
+
console.log = origLog;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await t.test("handleAutopilotDebug enters loop, applies write fix, and passes", async () => {
|
|
47
|
+
// We will run a command that checks for the existence of a file we write.
|
|
48
|
+
// If the file exists, it passes; if not, it fails.
|
|
49
|
+
const checkFile = join(tempHome, "fix.txt");
|
|
50
|
+
const testCmd = `node -e "const fs = require('fs'); if (fs.existsSync('${checkFile.replace(/\\/g, '\\\\')}')) { process.exit(0); } else { process.exit(1); }"`;
|
|
51
|
+
|
|
52
|
+
// Mock fetch to simulate AI response writing the file
|
|
53
|
+
globalThis.fetch = async (url, options) => {
|
|
54
|
+
// Simulate writing fix.txt using write block
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
json: async () => ({
|
|
58
|
+
choices: [{
|
|
59
|
+
message: {
|
|
60
|
+
content: `Let me fix this by writing the missing file.\n[WRITE_FILE: ${checkFile}]\nfixed content\n[END_WRITE]`
|
|
61
|
+
}
|
|
62
|
+
}]
|
|
63
|
+
})
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const ctx = {
|
|
68
|
+
aiConfig: {
|
|
69
|
+
GROQ_API_KEY: "mock-key",
|
|
70
|
+
AUTOPILOT: "machine"
|
|
71
|
+
},
|
|
72
|
+
rl: { pause: () => {}, resume: () => {} },
|
|
73
|
+
history: [],
|
|
74
|
+
currentMode: { name: "titan" }
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let logged = [];
|
|
78
|
+
const origLog = console.log;
|
|
79
|
+
console.log = (m) => logged.push(m);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await handleAutopilotDebug(testCmd, ctx);
|
|
83
|
+
|
|
84
|
+
// The test command should succeed on the second attempt
|
|
85
|
+
const hasCorrectedSuccess = logged.some(l => l && l.includes("Diagnostics passed successfully"));
|
|
86
|
+
assert.ok(hasCorrectedSuccess);
|
|
87
|
+
} finally {
|
|
88
|
+
console.log = origLog;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
|
|
5
|
+
// Stub only setRawMode globally for handleGitTUI to prevent crashes in non-TTY environments
|
|
6
|
+
const origSetRawMode = process.stdin.setRawMode;
|
|
7
|
+
process.stdin.setRawMode = () => {};
|
|
8
|
+
|
|
9
|
+
const originalCwd = process.cwd();
|
|
10
|
+
const nonGitDir = tmpdir();
|
|
11
|
+
|
|
12
|
+
// We import handleGitTUI
|
|
13
|
+
const { handleGitTUI } = await import("../src/chat.js");
|
|
14
|
+
|
|
15
|
+
test("Git TUI Suite", async (t) => {
|
|
16
|
+
after(() => {
|
|
17
|
+
// Restore Cwd and stdin setRawMode stub
|
|
18
|
+
process.chdir(originalCwd);
|
|
19
|
+
process.stdin.setRawMode = origSetRawMode;
|
|
20
|
+
if (typeof process.stdin.unref === "function") {
|
|
21
|
+
process.stdin.unref();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await t.test("handleGitTUI fails gracefully outside git repository", async () => {
|
|
26
|
+
let logged = [];
|
|
27
|
+
const origLog = console.log;
|
|
28
|
+
console.log = (m) => logged.push(m);
|
|
29
|
+
|
|
30
|
+
// Force git commands to fail by pointing GIT_DIR to a non-existent directory
|
|
31
|
+
const oldGitDir = process.env.GIT_DIR;
|
|
32
|
+
process.env.GIT_DIR = "C:\\non_existent_directory_xxx";
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await handleGitTUI({});
|
|
36
|
+
const hasErrorMsg = logged.some(l => l && l.includes("Not a git repository"));
|
|
37
|
+
assert.ok(hasErrorMsg);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error("SUBTEST 1 ERROR:", err);
|
|
40
|
+
throw err;
|
|
41
|
+
} finally {
|
|
42
|
+
console.log = origLog;
|
|
43
|
+
if (oldGitDir === undefined) {
|
|
44
|
+
delete process.env.GIT_DIR;
|
|
45
|
+
} else {
|
|
46
|
+
process.env.GIT_DIR = oldGitDir;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await t.test("handleGitTUI renders commit graph and parsed files status correctly", async () => {
|
|
52
|
+
let logged = [];
|
|
53
|
+
const origLog = console.log;
|
|
54
|
+
console.log = (m) => logged.push(m);
|
|
55
|
+
|
|
56
|
+
const origWrite = process.stdout.write;
|
|
57
|
+
let written = [];
|
|
58
|
+
process.stdout.write = (chunk) => {
|
|
59
|
+
written.push(chunk);
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const ctx = {
|
|
64
|
+
rl: { pause: () => {}, resume: () => {} }
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const initialCount = process.stdin.listeners("data").length;
|
|
69
|
+
const p = handleGitTUI(ctx);
|
|
70
|
+
|
|
71
|
+
// Poll until handleGitTUI registers its data listener on stdin (increases listener count)
|
|
72
|
+
while (process.stdin.listeners("data").length <= initialCount) {
|
|
73
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Trigger ESC/Q keypress on stdin to quit the TUI loop immediately
|
|
77
|
+
process.stdin.emit("data", "q");
|
|
78
|
+
|
|
79
|
+
await p;
|
|
80
|
+
|
|
81
|
+
const fullOutput = written.join("");
|
|
82
|
+
assert.ok(fullOutput.includes("🌿 AETHER INTERACTIVE GIT TUI"));
|
|
83
|
+
assert.ok(fullOutput.includes("Commit Graph & History:"));
|
|
84
|
+
assert.ok(fullOutput.includes("Modified Files:"));
|
|
85
|
+
assert.ok(fullOutput.includes("Hotkeys:"));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("SUBTEST 2 ERROR:", err);
|
|
88
|
+
throw err;
|
|
89
|
+
} finally {
|
|
90
|
+
console.log = origLog;
|
|
91
|
+
process.stdout.write = origWrite;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
5
|
+
import { workspaceSearch, crawlDirectory } from "../src/ai/search.js";
|
|
6
|
+
|
|
7
|
+
const testDir = join(process.cwd(), "temp-test-home-search");
|
|
8
|
+
|
|
9
|
+
test("Workspace Search Engine Suite", async (t) => {
|
|
10
|
+
before(async () => {
|
|
11
|
+
await mkdir(testDir, { recursive: true });
|
|
12
|
+
// Write test files
|
|
13
|
+
await writeFile(join(testDir, "file1.txt"), "hello world\nthis is a search test\nbye");
|
|
14
|
+
await writeFile(join(testDir, "file2.js"), "function test() {\n console.log('hello search');\n}");
|
|
15
|
+
await writeFile(join(testDir, "file3.png"), "binary data matches nothing");
|
|
16
|
+
|
|
17
|
+
const subDir = join(testDir, "subdir");
|
|
18
|
+
await mkdir(subDir, { recursive: true });
|
|
19
|
+
await writeFile(join(subDir, "file4.txt"), "nested matches here under search term");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
after(async () => {
|
|
23
|
+
await rm(testDir, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await t.test("crawlDirectory finds all text files and excludes binaries/ignored folders", () => {
|
|
27
|
+
const files = crawlDirectory(testDir, testDir);
|
|
28
|
+
const relativePaths = files.map((f) => relative(testDir, f).replace(/\\/g, "/"));
|
|
29
|
+
|
|
30
|
+
assert.ok(relativePaths.includes("file1.txt"));
|
|
31
|
+
assert.ok(relativePaths.includes("file2.js"));
|
|
32
|
+
assert.ok(relativePaths.includes("subdir/file4.txt"));
|
|
33
|
+
// Should exclude png
|
|
34
|
+
assert.strictEqual(relativePaths.includes("file3.png"), false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await t.test("workspaceSearch returns matching files, lines, and content", () => {
|
|
38
|
+
const results = workspaceSearch("search", testDir);
|
|
39
|
+
// Should match in file1.txt, file2.js, and subdir/file4.txt
|
|
40
|
+
assert.strictEqual(results.length, 3);
|
|
41
|
+
|
|
42
|
+
const file1Match = results.find((r) => r.relativePath.replace(/\\/g, "/") === "file1.txt");
|
|
43
|
+
assert.ok(file1Match);
|
|
44
|
+
assert.strictEqual(file1Match.lineNumber, 2);
|
|
45
|
+
assert.strictEqual(file1Match.lineContent, "this is a search test");
|
|
46
|
+
|
|
47
|
+
const jsMatch = results.find((r) => r.relativePath.replace(/\\/g, "/") === "file2.js");
|
|
48
|
+
assert.ok(jsMatch);
|
|
49
|
+
assert.strictEqual(jsMatch.lineNumber, 2);
|
|
50
|
+
assert.strictEqual(jsMatch.lineContent, "console.log('hello search');");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
// Redirect homedir for testing
|
|
7
|
+
const tempHome = join(process.cwd(), "temp-test-home-telemetry");
|
|
8
|
+
process.env.USERPROFILE = tempHome;
|
|
9
|
+
process.env.HOME = tempHome;
|
|
10
|
+
process.env.NODE_ENV = "test";
|
|
11
|
+
|
|
12
|
+
const { recordLatency, getLatencyLogs, clearTelemetryLogs, getTelemetryData } = await import("../src/ai/telemetry.js");
|
|
13
|
+
const { startTelemetryServer } = await import("../src/telemetry-server.js");
|
|
14
|
+
|
|
15
|
+
test("Visual Telemetry and Dashboard Suite", async (t) => {
|
|
16
|
+
before(async () => {
|
|
17
|
+
await mkdir(tempHome, { recursive: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
after(async () => {
|
|
21
|
+
await rm(tempHome, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await t.test("recordLatency saves metrics to memory and disk, getLatencyLogs returns them", () => {
|
|
25
|
+
clearTelemetryLogs();
|
|
26
|
+
|
|
27
|
+
assert.strictEqual(getLatencyLogs().length, 0);
|
|
28
|
+
|
|
29
|
+
recordLatency("openai", "gpt-4o", 150, 20, 15, true);
|
|
30
|
+
recordLatency("google", "gemini-1.5", 300, 30, 25, false);
|
|
31
|
+
|
|
32
|
+
const logs = getLatencyLogs();
|
|
33
|
+
assert.strictEqual(logs.length, 2);
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(logs[0].provider, "openai");
|
|
36
|
+
assert.strictEqual(logs[0].model, "gpt-4o");
|
|
37
|
+
assert.strictEqual(logs[0].latencyMs, 150);
|
|
38
|
+
assert.strictEqual(logs[0].promptTokens, 20);
|
|
39
|
+
assert.strictEqual(logs[0].completionTokens, 15);
|
|
40
|
+
assert.strictEqual(logs[0].success, true);
|
|
41
|
+
|
|
42
|
+
assert.strictEqual(logs[1].provider, "google");
|
|
43
|
+
assert.strictEqual(logs[1].success, false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await t.test("clearTelemetryLogs clears all saved logs", () => {
|
|
47
|
+
assert.ok(getLatencyLogs().length > 0);
|
|
48
|
+
clearTelemetryLogs();
|
|
49
|
+
assert.strictEqual(getLatencyLogs().length, 0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await t.test("getTelemetryData returns structured data for visual HUD", () => {
|
|
53
|
+
clearTelemetryLogs();
|
|
54
|
+
recordLatency("groq", "llama-3", 100, 10, 5, true);
|
|
55
|
+
|
|
56
|
+
const data = getTelemetryData({ GROQ_API_KEY: "fake-key" });
|
|
57
|
+
|
|
58
|
+
assert.ok(data.tokenStats);
|
|
59
|
+
assert.ok(data.modelBreakdown);
|
|
60
|
+
assert.strictEqual(data.latencyLogs.length, 1);
|
|
61
|
+
assert.strictEqual(data.latencyLogs[0].provider, "groq");
|
|
62
|
+
|
|
63
|
+
// Mesh structure verification
|
|
64
|
+
assert.ok(Array.isArray(data.meshStructure));
|
|
65
|
+
const groqItem = data.meshStructure.find(m => m.id === "groq");
|
|
66
|
+
assert.ok(groqItem);
|
|
67
|
+
assert.strictEqual(groqItem.configured, true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await t.test("startTelemetryServer serves HTTP endpoints correctly", async () => {
|
|
71
|
+
const { server, port } = await startTelemetryServer(5500);
|
|
72
|
+
const baseUrl = `http://localhost:${port}`;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// 1. Fetch dashboard HTML
|
|
76
|
+
const htmlRes = await fetch(`${baseUrl}/`);
|
|
77
|
+
assert.strictEqual(htmlRes.status, 200);
|
|
78
|
+
const htmlText = await htmlRes.text();
|
|
79
|
+
assert.ok(htmlText.includes("Telemetry HUD"));
|
|
80
|
+
|
|
81
|
+
// 2. Fetch telemetry JSON API
|
|
82
|
+
const apiRes = await fetch(`${baseUrl}/api/telemetry`);
|
|
83
|
+
assert.strictEqual(apiRes.status, 200);
|
|
84
|
+
const apiData = await apiRes.json();
|
|
85
|
+
assert.ok(apiData.latencyLogs);
|
|
86
|
+
assert.ok(apiData.meshStructure);
|
|
87
|
+
|
|
88
|
+
// 3. POST clear logs API
|
|
89
|
+
const clearRes = await fetch(`${baseUrl}/api/clear`, { method: "POST" });
|
|
90
|
+
assert.strictEqual(clearRes.status, 200);
|
|
91
|
+
const clearData = await clearRes.json();
|
|
92
|
+
assert.strictEqual(clearData.success, true);
|
|
93
|
+
assert.strictEqual(getLatencyLogs().length, 0);
|
|
94
|
+
|
|
95
|
+
// 4. POST shutdown server API
|
|
96
|
+
const shutdownRes = await fetch(`${baseUrl}/api/shutdown`, { method: "POST" });
|
|
97
|
+
assert.strictEqual(shutdownRes.status, 200);
|
|
98
|
+
const shutdownData = await shutdownRes.json();
|
|
99
|
+
assert.strictEqual(shutdownData.success, true);
|
|
100
|
+
} finally {
|
|
101
|
+
server.close();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|