aether-ai-agent-cli 1.1.9__tar.gz → 1.3.0__tar.gz
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.9 → aether_ai_agent_cli-1.3.0}/PKG-INFO +1 -1
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/PKG-INFO +1 -1
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/SOURCES.txt +2 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/cli.py +1 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/package.json +1 -1
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/router.js +18 -3
- aether_ai_agent_cli-1.3.0/aether_pip/node_project/src/ai/tokens.js +101 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/chat.js +64 -3
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/config.js +6 -1
- aether_ai_agent_cli-1.3.0/aether_pip/node_project/src/updater.js +186 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/LICENSE +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/README.md +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/dependency_links.txt +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/entry_points.txt +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/top_level.txt +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/__init__.py +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/bin/aether.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/package-lock.json +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/agent.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/fallback.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/google.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/providers.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/universal.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/xai.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/cli.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/file-parser.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/git.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/modes.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/banner.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/spinner.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/theme.js +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/setup.cfg +0 -0
- {aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/setup.py +0 -0
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/SOURCES.txt
RENAMED
|
@@ -18,10 +18,12 @@ aether_pip/node_project/src/config.js
|
|
|
18
18
|
aether_pip/node_project/src/file-parser.js
|
|
19
19
|
aether_pip/node_project/src/git.js
|
|
20
20
|
aether_pip/node_project/src/modes.js
|
|
21
|
+
aether_pip/node_project/src/updater.js
|
|
21
22
|
aether_pip/node_project/src/ai/fallback.js
|
|
22
23
|
aether_pip/node_project/src/ai/google.js
|
|
23
24
|
aether_pip/node_project/src/ai/providers.js
|
|
24
25
|
aether_pip/node_project/src/ai/router.js
|
|
26
|
+
aether_pip/node_project/src/ai/tokens.js
|
|
25
27
|
aether_pip/node_project/src/ai/universal.js
|
|
26
28
|
aether_pip/node_project/src/ai/xai.js
|
|
27
29
|
aether_pip/node_project/src/ui/banner.js
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/package.json
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krishivpb60/aether-ai-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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": {
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/router.js
RENAMED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
callAnthropic,
|
|
12
12
|
callCohere,
|
|
13
13
|
} from "./universal.js";
|
|
14
|
+
import { estimateTokens, recordTokenUsage } from "./tokens.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Routes a prompt through the universal AI failover mesh.
|
|
@@ -32,7 +33,10 @@ export async function routePrompt(prompt, systemPrompt, config, onToken, history
|
|
|
32
33
|
if (mathExpr) {
|
|
33
34
|
const mathResult = solveMath(mathExpr);
|
|
34
35
|
if (mathResult) {
|
|
35
|
-
|
|
36
|
+
const pTokens = estimateTokens(systemPrompt + prompt);
|
|
37
|
+
const cTokens = estimateTokens(mathResult.text);
|
|
38
|
+
const usage = recordTokenUsage("local-math", pTokens, cTokens);
|
|
39
|
+
return { ...mathResult, provider: "local", node: 0, usage };
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -54,7 +58,10 @@ export async function routePrompt(prompt, systemPrompt, config, onToken, history
|
|
|
54
58
|
// ── No providers configured → Krylo ────────────────────
|
|
55
59
|
if (active.length === 0) {
|
|
56
60
|
const kryloReply = generateKryloReply(prompt);
|
|
57
|
-
|
|
61
|
+
const pTokens = estimateTokens(systemPrompt + prompt);
|
|
62
|
+
const cTokens = estimateTokens(kryloReply.text);
|
|
63
|
+
const usage = recordTokenUsage("krylo-local", pTokens, cTokens);
|
|
64
|
+
return { ...kryloReply, provider: "krylo-fallback", node: 0, usage };
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
// ── Try each provider in order ──────────────────────────
|
|
@@ -96,7 +103,11 @@ export async function routePrompt(prompt, systemPrompt, config, onToken, history
|
|
|
96
103
|
);
|
|
97
104
|
}
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
const pTokens = estimateTokens(systemPrompt + prompt + history.map(h => h.content).join(""));
|
|
107
|
+
const cTokens = estimateTokens(result.text);
|
|
108
|
+
const usage = recordTokenUsage(result.model, pTokens, cTokens);
|
|
109
|
+
|
|
110
|
+
return { ...result, node: nodeIndex, usage };
|
|
100
111
|
} catch (err) {
|
|
101
112
|
errors.push(`[Node ${nodeIndex} ${provider.name}] ${err.message}`);
|
|
102
113
|
nodeIndex++;
|
|
@@ -105,10 +116,14 @@ export async function routePrompt(prompt, systemPrompt, config, onToken, history
|
|
|
105
116
|
|
|
106
117
|
// ── Final Fallback: Krylo Companion ─────────────────────
|
|
107
118
|
const kryloReply = generateKryloReply(prompt);
|
|
119
|
+
const pTokens = estimateTokens(systemPrompt + prompt + history.map(h => h.content).join(""));
|
|
120
|
+
const cTokens = estimateTokens(kryloReply.text);
|
|
121
|
+
const usage = recordTokenUsage("krylo-local", pTokens, cTokens);
|
|
108
122
|
return {
|
|
109
123
|
...kryloReply,
|
|
110
124
|
provider: "krylo-fallback",
|
|
111
125
|
node: 0,
|
|
112
126
|
errors,
|
|
127
|
+
usage,
|
|
113
128
|
};
|
|
114
129
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Real-time Token & Telemetry Tracker
|
|
3
|
+
// Tracks input/output token counts and session usage metrics.
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
// Session token accumulator
|
|
7
|
+
let sessionTokens = {
|
|
8
|
+
prompt: 0,
|
|
9
|
+
completion: 0,
|
|
10
|
+
total: 0,
|
|
11
|
+
exchanges: 0,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Model-by-model breakdown
|
|
15
|
+
const modelBreakdown = {};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Heuristically estimates the token count of a string based on character length.
|
|
19
|
+
* Standard rule of thumb: 1 token ≈ 4 characters.
|
|
20
|
+
* @param {string} text
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
export function estimateTokens(text) {
|
|
24
|
+
if (!text) return 0;
|
|
25
|
+
return Math.ceil(text.length / 4);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns a copy of the current session token stats.
|
|
30
|
+
* @returns {{prompt: number, completion: number, total: number, exchanges: number}}
|
|
31
|
+
*/
|
|
32
|
+
export function getSessionTokenStats() {
|
|
33
|
+
return { ...sessionTokens };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resets all accumulated session token statistics and breakdowns.
|
|
38
|
+
*/
|
|
39
|
+
export function resetSessionTokenStats() {
|
|
40
|
+
sessionTokens = {
|
|
41
|
+
prompt: 0,
|
|
42
|
+
completion: 0,
|
|
43
|
+
total: 0,
|
|
44
|
+
exchanges: 0,
|
|
45
|
+
};
|
|
46
|
+
// Clear object properties safely without losing reference
|
|
47
|
+
for (const key of Object.keys(modelBreakdown)) {
|
|
48
|
+
delete modelBreakdown[key];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Records token usage for a query.
|
|
54
|
+
* @param {string} model - The model name
|
|
55
|
+
* @param {number} promptTokens - Input prompt tokens count
|
|
56
|
+
* @param {number} completionTokens - Output completion tokens count
|
|
57
|
+
* @returns {{promptTokens: number, completionTokens: number, totalTokens: number}}
|
|
58
|
+
*/
|
|
59
|
+
export function recordTokenUsage(model, promptTokens, completionTokens) {
|
|
60
|
+
const modelName = model || "unknown-model";
|
|
61
|
+
const totalTokens = promptTokens + completionTokens;
|
|
62
|
+
|
|
63
|
+
// Update session totals
|
|
64
|
+
sessionTokens.prompt += promptTokens;
|
|
65
|
+
sessionTokens.completion += completionTokens;
|
|
66
|
+
sessionTokens.total += totalTokens;
|
|
67
|
+
sessionTokens.exchanges += 1;
|
|
68
|
+
|
|
69
|
+
// Update model breakdown
|
|
70
|
+
if (!modelBreakdown[modelName]) {
|
|
71
|
+
modelBreakdown[modelName] = {
|
|
72
|
+
prompt: 0,
|
|
73
|
+
completion: 0,
|
|
74
|
+
total: 0,
|
|
75
|
+
exchanges: 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
modelBreakdown[modelName].prompt += promptTokens;
|
|
79
|
+
modelBreakdown[modelName].completion += completionTokens;
|
|
80
|
+
modelBreakdown[modelName].total += totalTokens;
|
|
81
|
+
modelBreakdown[modelName].exchanges += 1;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
promptTokens,
|
|
85
|
+
completionTokens,
|
|
86
|
+
totalTokens,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns the model-by-model token usage breakdown.
|
|
92
|
+
* @returns {object}
|
|
93
|
+
*/
|
|
94
|
+
export function getBreakdownByModel() {
|
|
95
|
+
// Deep clone modelBreakdown
|
|
96
|
+
const clone = {};
|
|
97
|
+
for (const [key, value] of Object.entries(modelBreakdown)) {
|
|
98
|
+
clone[key] = { ...value };
|
|
99
|
+
}
|
|
100
|
+
return clone;
|
|
101
|
+
}
|
|
@@ -45,6 +45,10 @@ import { MODES, DEFAULT_MODE, getModeByName } from "./modes.js";
|
|
|
45
45
|
import { parseFile, formatContext } from "./file-parser.js";
|
|
46
46
|
import { runMainframeHack } from "./ai/fallback.js";
|
|
47
47
|
import { AGENT_INSTRUCTIONS } from "./agent.js";
|
|
48
|
+
import { checkForUpdates } from "./updater.js";
|
|
49
|
+
import { getSessionTokenStats, getBreakdownByModel, resetSessionTokenStats } from "./ai/tokens.js";
|
|
50
|
+
|
|
51
|
+
|
|
48
52
|
|
|
49
53
|
// Configure marked dynamically for terminal output
|
|
50
54
|
const getMarked = () => new Marked(markedTerminal({
|
|
@@ -67,6 +71,13 @@ export async function startChat(options = {}) {
|
|
|
67
71
|
// Load AI config
|
|
68
72
|
const aiConfig = await getAIConfig();
|
|
69
73
|
|
|
74
|
+
// Run update check
|
|
75
|
+
await checkForUpdates();
|
|
76
|
+
|
|
77
|
+
// Reset token stats for the new session
|
|
78
|
+
resetSessionTokenStats();
|
|
79
|
+
|
|
80
|
+
|
|
70
81
|
// Set theme from configuration
|
|
71
82
|
const theme = aiConfig.THEME || "cyberpunk";
|
|
72
83
|
setTheme(theme);
|
|
@@ -125,7 +136,7 @@ export async function startChat(options = {}) {
|
|
|
125
136
|
"/help", "/mode", "/modes", "/attach", "/files", "/clear",
|
|
126
137
|
"/providers", "/export", "/status", "/copy", "/exit", "/quit",
|
|
127
138
|
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/write",
|
|
128
|
-
"/commit", "/run", "/history", "/autopilot"
|
|
139
|
+
"/commit", "/run", "/history", "/autopilot", "/tokens"
|
|
129
140
|
];
|
|
130
141
|
const customCmds = aiConfig.CUSTOM_COMMANDS || {};
|
|
131
142
|
const commands = [...builtIn, ...Object.keys(customCmds)];
|
|
@@ -298,11 +309,19 @@ export async function startChat(options = {}) {
|
|
|
298
309
|
}
|
|
299
310
|
}
|
|
300
311
|
|
|
312
|
+
const showTokens = aiConfig.SHOW_TOKENS !== "false";
|
|
313
|
+
let tokensText = "";
|
|
314
|
+
if (showTokens && result.usage) {
|
|
315
|
+
const { promptTokens, completionTokens } = result.usage;
|
|
316
|
+
tokensText = ` • ${promptTokens.toLocaleString()} in / ${completionTokens.toLocaleString()} out tokens`;
|
|
317
|
+
}
|
|
318
|
+
|
|
301
319
|
console.log(separator("─"));
|
|
302
320
|
console.log(
|
|
303
321
|
" " + colors.dim(`Node ${result.node} • ${result.provider}`) +
|
|
304
322
|
(result.model ? colors.dim(` • ${result.model}`) : "") +
|
|
305
323
|
colors.dim(` • ${elapsedSec}s${speedText}`) +
|
|
324
|
+
colors.dim(tokensText) +
|
|
306
325
|
colors.dim(` • ${Math.floor(history.length / 2)} exchanges`)
|
|
307
326
|
);
|
|
308
327
|
console.log("");
|
|
@@ -404,7 +423,7 @@ export async function startChat(options = {}) {
|
|
|
404
423
|
"/", "/help", "/mode", "/modes", "/attach", "/files", "/clear",
|
|
405
424
|
"/providers", "/export", "/status", "/copy", "/exit", "/quit",
|
|
406
425
|
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd",
|
|
407
|
-
"/guess", "/write", "/commit", "/run", "/history", "/autopilot"
|
|
426
|
+
"/guess", "/write", "/commit", "/run", "/history", "/autopilot", "/tokens"
|
|
408
427
|
];
|
|
409
428
|
|
|
410
429
|
const customCmds = aiConfig.CUSTOM_COMMANDS || {};
|
|
@@ -551,6 +570,10 @@ async function handleCommand(input, ctx) {
|
|
|
551
570
|
await handleAutopilotSwitch(args, ctx);
|
|
552
571
|
break;
|
|
553
572
|
|
|
573
|
+
case "/tokens":
|
|
574
|
+
await handleTokensDisplay(ctx);
|
|
575
|
+
break;
|
|
576
|
+
|
|
554
577
|
case "/exit":
|
|
555
578
|
case "/quit":
|
|
556
579
|
ctx.rl.close();
|
|
@@ -582,6 +605,7 @@ function showHelp(aiConfig) {
|
|
|
582
605
|
console.log(keyValue("/history", "List, switch, and resume past interactive chat sessions"));
|
|
583
606
|
console.log(keyValue("/history-clear", "Clear saved persistent chat history"));
|
|
584
607
|
console.log(keyValue("/autopilot <mode>", "View or switch agent autopilot level (off, safe, workspace, machine)"));
|
|
608
|
+
console.log(keyValue("/tokens", "View detailed session token usage and exchanges telemetry"));
|
|
585
609
|
console.log(keyValue("/game", "Start the local mainframe hacking mini-game"));
|
|
586
610
|
console.log(keyValue("/copy", "Copy the last assistant response to clipboard"));
|
|
587
611
|
console.log(keyValue("/cmd <list|add|remove>", "Manage custom command shortcuts"));
|
|
@@ -1121,7 +1145,7 @@ async function handleCustomCommands(args, ctx) {
|
|
|
1121
1145
|
const builtIn = [
|
|
1122
1146
|
"/help", "/mode", "/modes", "/attach", "/files", "/clear",
|
|
1123
1147
|
"/providers", "/export", "/status", "/copy", "/exit", "/quit",
|
|
1124
|
-
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/guess"
|
|
1148
|
+
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/guess", "/tokens"
|
|
1125
1149
|
];
|
|
1126
1150
|
|
|
1127
1151
|
if (builtIn.includes(name.toLowerCase())) {
|
|
@@ -1333,3 +1357,40 @@ async function handleRunCommand(args, ctx) {
|
|
|
1333
1357
|
});
|
|
1334
1358
|
});
|
|
1335
1359
|
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Interactive display of session token usage statistics.
|
|
1363
|
+
*/
|
|
1364
|
+
async function handleTokensDisplay(ctx) {
|
|
1365
|
+
const stats = getSessionTokenStats();
|
|
1366
|
+
const breakdown = getBreakdownByModel();
|
|
1367
|
+
|
|
1368
|
+
console.log("\n" + separator("━"));
|
|
1369
|
+
console.log(colors.accent.bold(" ★ AETHER SESSION TOKEN TELEMETRY ★"));
|
|
1370
|
+
console.log(separator("─"));
|
|
1371
|
+
|
|
1372
|
+
const models = Object.keys(breakdown);
|
|
1373
|
+
if (models.length === 0) {
|
|
1374
|
+
console.log(colors.muted(" No queries executed in this session yet."));
|
|
1375
|
+
} else {
|
|
1376
|
+
// Print header
|
|
1377
|
+
console.log(
|
|
1378
|
+
colors.brand(" " + "Model".padEnd(35) + "Prompt".padStart(10) + "Completion".padStart(12) + "Total".padStart(10))
|
|
1379
|
+
);
|
|
1380
|
+
console.log(colors.dim(" " + "─".repeat(67)));
|
|
1381
|
+
for (const [model, data] of Object.entries(breakdown)) {
|
|
1382
|
+
const truncatedModel = model.length > 33 ? model.slice(0, 30) + "..." : model;
|
|
1383
|
+
console.log(
|
|
1384
|
+
" " + colors.text(truncatedModel.padEnd(35)) +
|
|
1385
|
+
colors.brand(data.prompt.toLocaleString().padStart(10)) +
|
|
1386
|
+
colors.brand(data.completion.toLocaleString().padStart(12)) +
|
|
1387
|
+
colors.accent.bold(data.total.toLocaleString().padStart(10))
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
console.log(separator("─"));
|
|
1393
|
+
console.log(" " + colors.accent("Total Exchanges:") + colors.text(` ${stats.exchanges}`));
|
|
1394
|
+
console.log(" " + colors.accent("Total Tokens:") + colors.text(` Prompt: ${stats.prompt.toLocaleString()} | Completion: ${stats.completion.toLocaleString()} | Sum: `) + colors.brand.bold(stats.total.toLocaleString()));
|
|
1395
|
+
console.log(separator("━") + "\n");
|
|
1396
|
+
}
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/config.js
RENAMED
|
@@ -173,7 +173,12 @@ export async function configExists() {
|
|
|
173
173
|
export function isValidConfigKey(key) {
|
|
174
174
|
const upper = key.toUpperCase();
|
|
175
175
|
// Accept any API key or model override
|
|
176
|
-
|
|
176
|
+
const allowedSpecialKeys = [
|
|
177
|
+
"THEME", "CUSTOM_COMMANDS", "AUTOPILOT",
|
|
178
|
+
"AUTO_UPDATE", "SHOW_HIGHLIGHTS", "LAST_UPDATE_CHECK", "LAST_NOTIFIED_VERSION",
|
|
179
|
+
"SHOW_TOKENS"
|
|
180
|
+
];
|
|
181
|
+
if (upper.endsWith("_API_KEY") || upper.endsWith("_API_KEYS") || upper.endsWith("_MODEL") || allowedSpecialKeys.includes(upper)) {
|
|
177
182
|
return true;
|
|
178
183
|
}
|
|
179
184
|
// Accept known config keys
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Automated Update & Highlights Engine
|
|
3
|
+
// Checks NPM registry, updates packages, and renders release details.
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { exec } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { getConfigValue, setConfigValue } from "./config.js";
|
|
12
|
+
import { colors, label, separator } from "./ui/theme.js";
|
|
13
|
+
import { createSpinner } from "./ui/spinner.js";
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns true if version 'latest' is newer than version 'current'.
|
|
23
|
+
* Supports standard semver format x.y.z.
|
|
24
|
+
* @param {string} latest
|
|
25
|
+
* @param {string} current
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
export function isNewerVersion(latest, current) {
|
|
29
|
+
const l = latest.split(".").map((num) => parseInt(num, 10) || 0);
|
|
30
|
+
const c = current.split(".").map((num) => parseInt(num, 10) || 0);
|
|
31
|
+
for (let i = 0; i < 3; i++) {
|
|
32
|
+
if (l[i] > c[i]) return true;
|
|
33
|
+
if (l[i] < c[i]) return false;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetches and displays release highlights from the remote repository.
|
|
40
|
+
* @param {string} version
|
|
41
|
+
*/
|
|
42
|
+
export async function showReleaseHighlights(version) {
|
|
43
|
+
try {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
46
|
+
|
|
47
|
+
const res = await fetch("https://raw.githubusercontent.com/Krylo-60/aether-ai-cli/main/HIGHLIGHTS.md", {
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
});
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
|
|
52
|
+
if (!res.ok) return;
|
|
53
|
+
const text = await res.text();
|
|
54
|
+
|
|
55
|
+
console.log("\n" + separator("━"));
|
|
56
|
+
console.log(colors.accent.bold(` ★ AETHER AI CLI v${version} RELEASE HIGHLIGHTS ★`));
|
|
57
|
+
console.log(separator("─"));
|
|
58
|
+
|
|
59
|
+
const lines = text.split("\n");
|
|
60
|
+
let inReleaseHeader = false;
|
|
61
|
+
let printedAny = false;
|
|
62
|
+
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (trimmed.startsWith("#")) {
|
|
66
|
+
// Matches the version header like "# Aether CLI v1.1.9 Highlights" or similar
|
|
67
|
+
if (trimmed.toLowerCase().includes(`v${version}`)) {
|
|
68
|
+
inReleaseHeader = true;
|
|
69
|
+
} else {
|
|
70
|
+
inReleaseHeader = false;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (inReleaseHeader && trimmed) {
|
|
76
|
+
if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
|
|
77
|
+
console.log(colors.brand(" " + trimmed));
|
|
78
|
+
printedAny = true;
|
|
79
|
+
} else {
|
|
80
|
+
console.log(colors.text(" " + trimmed));
|
|
81
|
+
printedAny = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!printedAny) {
|
|
87
|
+
// Fallback if no specific version section was found in HIGHLIGHTS.md
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
const trimmed = line.trim();
|
|
90
|
+
if (trimmed.startsWith("#")) continue;
|
|
91
|
+
if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
|
|
92
|
+
console.log(colors.brand(" " + trimmed));
|
|
93
|
+
} else if (trimmed) {
|
|
94
|
+
console.log(colors.text(" " + trimmed));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(separator("━") + "\n");
|
|
100
|
+
} catch {
|
|
101
|
+
// Fail silently (offline or repo down)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks for updates and runs the automatic updater if configured.
|
|
107
|
+
*/
|
|
108
|
+
export async function checkForUpdates() {
|
|
109
|
+
const autoUpdate = (await getConfigValue("AUTO_UPDATE")) !== "false";
|
|
110
|
+
const showHighlights = (await getConfigValue("SHOW_HIGHLIGHTS")) !== "false";
|
|
111
|
+
const lastCheck = parseInt(await getConfigValue("LAST_UPDATE_CHECK") || "0", 10);
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const currentVersion = pkg.version;
|
|
114
|
+
|
|
115
|
+
// Run update check at most once every 24 hours (86,400,000 ms)
|
|
116
|
+
const checkInterval = 24 * 60 * 60 * 1000;
|
|
117
|
+
if (now - lastCheck < checkInterval) {
|
|
118
|
+
// Show highlights if we just updated and haven't shown highlights for this version
|
|
119
|
+
const lastNotified = await getConfigValue("LAST_NOTIFIED_VERSION") || "";
|
|
120
|
+
if (showHighlights && lastNotified !== currentVersion) {
|
|
121
|
+
await showReleaseHighlights(currentVersion);
|
|
122
|
+
await setConfigValue("LAST_NOTIFIED_VERSION", currentVersion);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Update check timestamp immediately
|
|
128
|
+
await setConfigValue("LAST_UPDATE_CHECK", now.toString());
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
133
|
+
|
|
134
|
+
const res = await fetch("https://registry.npmjs.org/@krishivpb60/aether-ai-cli/latest", {
|
|
135
|
+
signal: controller.signal,
|
|
136
|
+
});
|
|
137
|
+
clearTimeout(timeoutId);
|
|
138
|
+
|
|
139
|
+
if (!res.ok) return;
|
|
140
|
+
const data = await res.json();
|
|
141
|
+
const latestVersion = data.version;
|
|
142
|
+
|
|
143
|
+
if (isNewerVersion(latestVersion, currentVersion)) {
|
|
144
|
+
if (autoUpdate) {
|
|
145
|
+
console.log("\n" + label.system + " " + colors.brand(`⚡ New version detected! Auto-updating from v${currentVersion} to v${latestVersion}...`));
|
|
146
|
+
|
|
147
|
+
const isPip = process.env.AETHER_PACKAGER === "pip";
|
|
148
|
+
const updateCmd = isPip
|
|
149
|
+
? "pip install --upgrade aether-ai-agent-cli"
|
|
150
|
+
: "npm install -g @krishivpb60/aether-ai-cli";
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const spinner = createSpinner("Installing update").start();
|
|
154
|
+
await execAsync(updateCmd);
|
|
155
|
+
spinner.succeed("Update complete!");
|
|
156
|
+
|
|
157
|
+
console.log(label.system + " " + colors.success(`✓ Successfully updated to v${latestVersion}.`));
|
|
158
|
+
|
|
159
|
+
if (showHighlights) {
|
|
160
|
+
await showReleaseHighlights(latestVersion);
|
|
161
|
+
}
|
|
162
|
+
await setConfigValue("LAST_NOTIFIED_VERSION", latestVersion);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.log(label.system + " " + colors.warning(`⚠ Auto-update failed: ${err.message}`));
|
|
165
|
+
console.log(label.system + " " + colors.muted(`Please run manually: ${updateCmd}`));
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
console.log("\n" + label.system + " " + colors.warning(`⚡ A new version (v${latestVersion}) is available!`));
|
|
169
|
+
const isPip = process.env.AETHER_PACKAGER === "pip";
|
|
170
|
+
const updateCmd = isPip
|
|
171
|
+
? "pip install -U aether-ai-agent-cli"
|
|
172
|
+
: "npm install -g @krishivpb60/aether-ai-cli";
|
|
173
|
+
console.log(label.system + " " + colors.muted(`To update, run: ${updateCmd}`));
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
// Already on latest version, check if we need to show highlights
|
|
177
|
+
const lastNotified = await getConfigValue("LAST_NOTIFIED_VERSION") || "";
|
|
178
|
+
if (showHighlights && lastNotified !== currentVersion) {
|
|
179
|
+
await showReleaseHighlights(currentVersion);
|
|
180
|
+
await setConfigValue("LAST_NOTIFIED_VERSION", currentVersion);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// Fail silently (offline or registry down)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/bin/aether.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/package-lock.json
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/agent.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/fallback.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/google.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/providers.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/universal.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/xai.js
RENAMED
|
File without changes
|
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/file-parser.js
RENAMED
|
File without changes
|
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/modes.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/banner.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/spinner.js
RENAMED
|
File without changes
|
{aether_ai_agent_cli-1.1.9 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/theme.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|