aether-ai-agent-cli 1.2.0__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.
Files changed (33) hide show
  1. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/PKG-INFO +1 -1
  2. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/PKG-INFO +1 -1
  3. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/SOURCES.txt +1 -0
  4. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/package.json +1 -1
  5. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/router.js +18 -3
  6. aether_ai_agent_cli-1.3.0/aether_pip/node_project/src/ai/tokens.js +101 -0
  7. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/chat.js +59 -3
  8. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/config.js +2 -1
  9. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/LICENSE +0 -0
  10. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/README.md +0 -0
  11. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/dependency_links.txt +0 -0
  12. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/entry_points.txt +0 -0
  13. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_ai_agent_cli.egg-info/top_level.txt +0 -0
  14. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/__init__.py +0 -0
  15. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/cli.py +0 -0
  16. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/bin/aether.js +0 -0
  17. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/package-lock.json +0 -0
  18. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/agent.js +0 -0
  19. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/fallback.js +0 -0
  20. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/google.js +0 -0
  21. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/providers.js +0 -0
  22. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/universal.js +0 -0
  23. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ai/xai.js +0 -0
  24. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/cli.js +0 -0
  25. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/file-parser.js +0 -0
  26. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/git.js +0 -0
  27. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/modes.js +0 -0
  28. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/banner.js +0 -0
  29. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/spinner.js +0 -0
  30. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/ui/theme.js +0 -0
  31. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/aether_pip/node_project/src/updater.js +0 -0
  32. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/setup.cfg +0 -0
  33. {aether_ai_agent_cli-1.2.0 → aether_ai_agent_cli-1.3.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-ai-agent-cli
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Aether Core AI v110 — Universal AI Gateway CLI (Python Wrapper)
5
5
  Home-page: https://github.com/Krylo-60/aether-ai-cli
6
6
  Author: Krishiv PB
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-ai-agent-cli
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Aether Core AI v110 — Universal AI Gateway CLI (Python Wrapper)
5
5
  Home-page: https://github.com/Krylo-60/aether-ai-cli
6
6
  Author: Krishiv PB
@@ -23,6 +23,7 @@ aether_pip/node_project/src/ai/fallback.js
23
23
  aether_pip/node_project/src/ai/google.js
24
24
  aether_pip/node_project/src/ai/providers.js
25
25
  aether_pip/node_project/src/ai/router.js
26
+ aether_pip/node_project/src/ai/tokens.js
26
27
  aether_pip/node_project/src/ai/universal.js
27
28
  aether_pip/node_project/src/ai/xai.js
28
29
  aether_pip/node_project/src/ui/banner.js
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krishivpb60/aether-ai-cli",
3
- "version": "1.2.0",
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": {
@@ -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
- return { ...mathResult, provider: "local", node: 0 };
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
- return { ...kryloReply, provider: "krylo-fallback", node: 0 };
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
- return { ...result, node: nodeIndex };
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
+ }
@@ -46,6 +46,8 @@ import { parseFile, formatContext } from "./file-parser.js";
46
46
  import { runMainframeHack } from "./ai/fallback.js";
47
47
  import { AGENT_INSTRUCTIONS } from "./agent.js";
48
48
  import { checkForUpdates } from "./updater.js";
49
+ import { getSessionTokenStats, getBreakdownByModel, resetSessionTokenStats } from "./ai/tokens.js";
50
+
49
51
 
50
52
 
51
53
  // Configure marked dynamically for terminal output
@@ -71,6 +73,10 @@ export async function startChat(options = {}) {
71
73
 
72
74
  // Run update check
73
75
  await checkForUpdates();
76
+
77
+ // Reset token stats for the new session
78
+ resetSessionTokenStats();
79
+
74
80
 
75
81
  // Set theme from configuration
76
82
  const theme = aiConfig.THEME || "cyberpunk";
@@ -130,7 +136,7 @@ export async function startChat(options = {}) {
130
136
  "/help", "/mode", "/modes", "/attach", "/files", "/clear",
131
137
  "/providers", "/export", "/status", "/copy", "/exit", "/quit",
132
138
  "/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/write",
133
- "/commit", "/run", "/history", "/autopilot"
139
+ "/commit", "/run", "/history", "/autopilot", "/tokens"
134
140
  ];
135
141
  const customCmds = aiConfig.CUSTOM_COMMANDS || {};
136
142
  const commands = [...builtIn, ...Object.keys(customCmds)];
@@ -303,11 +309,19 @@ export async function startChat(options = {}) {
303
309
  }
304
310
  }
305
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
+
306
319
  console.log(separator("─"));
307
320
  console.log(
308
321
  " " + colors.dim(`Node ${result.node} • ${result.provider}`) +
309
322
  (result.model ? colors.dim(` • ${result.model}`) : "") +
310
323
  colors.dim(` • ${elapsedSec}s${speedText}`) +
324
+ colors.dim(tokensText) +
311
325
  colors.dim(` • ${Math.floor(history.length / 2)} exchanges`)
312
326
  );
313
327
  console.log("");
@@ -409,7 +423,7 @@ export async function startChat(options = {}) {
409
423
  "/", "/help", "/mode", "/modes", "/attach", "/files", "/clear",
410
424
  "/providers", "/export", "/status", "/copy", "/exit", "/quit",
411
425
  "/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd",
412
- "/guess", "/write", "/commit", "/run", "/history", "/autopilot"
426
+ "/guess", "/write", "/commit", "/run", "/history", "/autopilot", "/tokens"
413
427
  ];
414
428
 
415
429
  const customCmds = aiConfig.CUSTOM_COMMANDS || {};
@@ -556,6 +570,10 @@ async function handleCommand(input, ctx) {
556
570
  await handleAutopilotSwitch(args, ctx);
557
571
  break;
558
572
 
573
+ case "/tokens":
574
+ await handleTokensDisplay(ctx);
575
+ break;
576
+
559
577
  case "/exit":
560
578
  case "/quit":
561
579
  ctx.rl.close();
@@ -587,6 +605,7 @@ function showHelp(aiConfig) {
587
605
  console.log(keyValue("/history", "List, switch, and resume past interactive chat sessions"));
588
606
  console.log(keyValue("/history-clear", "Clear saved persistent chat history"));
589
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"));
590
609
  console.log(keyValue("/game", "Start the local mainframe hacking mini-game"));
591
610
  console.log(keyValue("/copy", "Copy the last assistant response to clipboard"));
592
611
  console.log(keyValue("/cmd <list|add|remove>", "Manage custom command shortcuts"));
@@ -1126,7 +1145,7 @@ async function handleCustomCommands(args, ctx) {
1126
1145
  const builtIn = [
1127
1146
  "/help", "/mode", "/modes", "/attach", "/files", "/clear",
1128
1147
  "/providers", "/export", "/status", "/copy", "/exit", "/quit",
1129
- "/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/guess"
1148
+ "/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/guess", "/tokens"
1130
1149
  ];
1131
1150
 
1132
1151
  if (builtIn.includes(name.toLowerCase())) {
@@ -1338,3 +1357,40 @@ async function handleRunCommand(args, ctx) {
1338
1357
  });
1339
1358
  });
1340
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
+ }
@@ -175,7 +175,8 @@ export function isValidConfigKey(key) {
175
175
  // Accept any API key or model override
176
176
  const allowedSpecialKeys = [
177
177
  "THEME", "CUSTOM_COMMANDS", "AUTOPILOT",
178
- "AUTO_UPDATE", "SHOW_HIGHLIGHTS", "LAST_UPDATE_CHECK", "LAST_NOTIFIED_VERSION"
178
+ "AUTO_UPDATE", "SHOW_HIGHLIGHTS", "LAST_UPDATE_CHECK", "LAST_NOTIFIED_VERSION",
179
+ "SHOW_TOKENS"
179
180
  ];
180
181
  if (upper.endsWith("_API_KEY") || upper.endsWith("_API_KEYS") || upper.endsWith("_MODEL") || allowedSpecialKeys.includes(upper)) {
181
182
  return true;