@krishivpb60/aether-ai-cli 1.1.8 → 1.3.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/.agents/AGENTS.md +15 -0
- package/.agents/orchestrator/BRIEFING.md +76 -0
- package/.agents/orchestrator/ORIGINAL_REQUEST.md +13 -0
- package/.agents/orchestrator/context.md +27 -0
- package/.agents/orchestrator/handoff.md +28 -0
- package/.agents/orchestrator/plan.md +30 -0
- package/.agents/orchestrator/progress.md +30 -0
- package/.agents/sentinel/BRIEFING.md +30 -0
- package/.agents/sentinel/handoff.md +19 -0
- package/.agents/teamwork_preview_auditor/BRIEFING.md +51 -0
- package/.agents/teamwork_preview_auditor/ORIGINAL_REQUEST.md +22 -0
- package/.agents/teamwork_preview_auditor/android-cli_SKILL.md +203 -0
- package/.agents/teamwork_preview_auditor/github_SKILL.md +58 -0
- package/.agents/teamwork_preview_auditor/handoff.md +80 -0
- package/.agents/teamwork_preview_auditor/progress.md +12 -0
- package/.agents/teamwork_preview_explorer_git_ci_ux/BRIEFING.md +50 -0
- package/.agents/teamwork_preview_explorer_git_ci_ux/ORIGINAL_REQUEST.md +12 -0
- package/.agents/teamwork_preview_explorer_git_ci_ux/handoff.md +170 -0
- package/.agents/teamwork_preview_explorer_git_ci_ux/progress.md +9 -0
- package/.agents/teamwork_preview_worker_git_ci/BRIEFING.md +60 -0
- package/.agents/teamwork_preview_worker_git_ci/ORIGINAL_REQUEST.md +18 -0
- package/.agents/teamwork_preview_worker_git_ci/handoff.md +122 -0
- package/.agents/teamwork_preview_worker_git_ci/progress.md +14 -0
- package/.agents/teamwork_preview_worker_git_ci/skills/android-cli_SKILL.md +63 -0
- package/.agents/teamwork_preview_worker_git_ci/skills/github_SKILL.md +54 -0
- package/.agents/teamwork_preview_worker_git_commit/BRIEFING.md +43 -0
- package/.agents/teamwork_preview_worker_git_commit/ORIGINAL_REQUEST.md +15 -0
- package/.agents/teamwork_preview_worker_git_commit/handoff.md +89 -0
- package/.agents/teamwork_preview_worker_git_commit/progress.md +12 -0
- package/.agents/teamwork_preview_worker_test_suite/BRIEFING.md +57 -0
- package/.agents/teamwork_preview_worker_test_suite/ORIGINAL_REQUEST.md +21 -0
- package/.agents/teamwork_preview_worker_test_suite/handoff.md +62 -0
- package/.agents/teamwork_preview_worker_test_suite/progress.md +12 -0
- package/.agents/teamwork_preview_worker_ux_upgrades/BRIEFING.md +56 -0
- package/.agents/teamwork_preview_worker_ux_upgrades/ORIGINAL_REQUEST.md +35 -0
- package/.agents/teamwork_preview_worker_ux_upgrades/handoff.md +53 -0
- package/.agents/teamwork_preview_worker_ux_upgrades/progress.md +12 -0
- package/.agents/teamwork_preview_worker_verification/BRIEFING.md +49 -0
- package/.agents/teamwork_preview_worker_verification/ORIGINAL_REQUEST.md +18 -0
- package/.agents/teamwork_preview_worker_verification/handoff.md +187 -0
- package/.agents/teamwork_preview_worker_verification/progress.md +16 -0
- package/HIGHLIGHTS.md +12 -0
- package/aether_pip/cli.py +1 -0
- package/package.json +1 -1
- package/src/ai/router.js +18 -3
- package/src/ai/tokens.js +101 -0
- package/src/chat.js +64 -3
- package/src/config.js +6 -1
- package/src/updater.js +186 -0
- package/test/tokens.test.js +89 -0
- package/test/updater.test.js +90 -0
- package/test/ux.test.js +1 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Handoff Report — E2E Verification of Aether AI CLI
|
|
2
|
+
|
|
3
|
+
This report documents the E2E verification steps and findings for the Aether AI CLI codebase on Windows.
|
|
4
|
+
|
|
5
|
+
## 1. Observation
|
|
6
|
+
|
|
7
|
+
### Local Package Linking
|
|
8
|
+
Command run: `npm.cmd link` in project root `C:\Users\naina\.gemini\antigravity\scratch\aether-ai-cli`.
|
|
9
|
+
Output:
|
|
10
|
+
```
|
|
11
|
+
added 1 package, and audited 3 packages in 2s
|
|
12
|
+
|
|
13
|
+
found 0 vulnerabilities
|
|
14
|
+
```
|
|
15
|
+
Global links were successfully created in `C:\Users\naina\AppData\Roaming\npm` for the `aether` binary.
|
|
16
|
+
|
|
17
|
+
### Unit Tests
|
|
18
|
+
Command run: `npm.cmd test` in project root `C:\Users\naina\.gemini\antigravity\scratch\aether-ai-cli`.
|
|
19
|
+
Output:
|
|
20
|
+
```
|
|
21
|
+
> @krylo-60/aether-ai-cli@1.0.0 test
|
|
22
|
+
> node --test
|
|
23
|
+
|
|
24
|
+
▶ Configuration Loading Suite
|
|
25
|
+
✔ getConfigPath should return path inside temporary home (5.6146ms)
|
|
26
|
+
✔ loadConfig should return empty object if file does not exist (7.768ms)
|
|
27
|
+
✔ saveConfig and loadConfig should save and load config file (22.7313ms)
|
|
28
|
+
✔ getConfigValue and setConfigValue read and write specific keys (15.9586ms)
|
|
29
|
+
✔ listConfig should mask sensitive keys (6.8407ms)
|
|
30
|
+
✔ getAIConfig Priority: config file overrides process.env (6.4427ms)
|
|
31
|
+
✔ getAIConfig supports fallback for any custom key ending with _API_KEY from process.env (2.9901ms)
|
|
32
|
+
✔ isValidConfigKey checks key formats and known keys (1.7146ms)
|
|
33
|
+
✔ Configuration Loading Suite (81.3576ms)
|
|
34
|
+
▶ Offline Math Fallback & Krylo Suite
|
|
35
|
+
✔ detectMathExpression identifies valid mathematical expressions (1.4379ms)
|
|
36
|
+
✔ detectMathExpression rejects non-math and invalid expressions (0.3137ms)
|
|
37
|
+
✔ solveMath evaluates simple math expressions correctly (0.5413ms)
|
|
38
|
+
✔ solveMath converts ^ to ** correctly for exponentiation (1.483ms)
|
|
39
|
+
✔ solveMath handles floats and division (0.3561ms)
|
|
40
|
+
✔ solveMath returns null on syntax error or unsafe code (0.3782ms)
|
|
41
|
+
✔ generateKryloReply responds to help and shortcut keywords (0.4279ms)
|
|
42
|
+
✔ generateKryloReply responds to status and diagnostic keywords (0.2695ms)
|
|
43
|
+
✔ generateKryloReply responds to matrix/rain/color keywords (0.3643ms)
|
|
44
|
+
✔ generateKryloReply responds to who/name/creator keywords (0.35ms)
|
|
45
|
+
✔ generateKryloReply falls back to random terminal responses (0.2222ms)
|
|
46
|
+
✔ Offline Math Fallback & Krylo Suite (10.6275ms)
|
|
47
|
+
▶ File Parser & Context Suite
|
|
48
|
+
✔ parseFile parses a standard .txt file successfully (12.0737ms)
|
|
49
|
+
✔ parseFile parses a code .js file successfully (4.4388ms)
|
|
50
|
+
✔ parseFile parses a .json file successfully (7.8301ms)
|
|
51
|
+
✔ parseFile parses a .csv file successfully (4.0536ms)
|
|
52
|
+
✔ parseFile truncates content exceeding 30,000 characters (43.5665ms)
|
|
53
|
+
✔ parseFile throws error on unsupported extension (3.1508ms)
|
|
54
|
+
✔ parseFile throws error when file does not exist (1.3021ms)
|
|
55
|
+
✔ parseFile throws error on directory path without supported extension (0.5618ms)
|
|
56
|
+
✔ parseFile throws error on directory path with supported extension (2.5804ms)
|
|
57
|
+
✔ formatContext returns formatted template string (0.4367ms)
|
|
58
|
+
✔ formatContext formats KB/MB file sizes correctly (0.2397ms)
|
|
59
|
+
✔ File Parser & Context Suite (91.4076ms)
|
|
60
|
+
▶ Universal AI Router Suite
|
|
61
|
+
✔ routePrompt routes to local math solver when pure math expression (3.1517ms)
|
|
62
|
+
✔ routePrompt routes to active providers in order of priority (1.6104ms)
|
|
63
|
+
✔ routePrompt falls back to next provider if priority provider fails (1.3292ms)
|
|
64
|
+
✔ routePrompt handles Google extra key rotation and failover (2.8772ms)
|
|
65
|
+
✔ routePrompt falls back to Krylo companion when no providers are configured (1.3161ms)
|
|
66
|
+
✔ routePrompt falls back to Krylo companion when all providers fail (1.1711ms)
|
|
67
|
+
✔ Universal AI Router Suite (18.7375ms)
|
|
68
|
+
▶ Cyberpunk UX and Streaming Suite
|
|
69
|
+
✔ createSpinner should return custom frames and 80ms interval (3.5346ms)
|
|
70
|
+
✔ separator should adjust length dynamically based on terminal width (0.4046ms)
|
|
71
|
+
✔ routePrompt calls callOpenAICompatible and streams tokens (3.4326ms)
|
|
72
|
+
✔ Cyberpunk UX and Streaming Suite (9.0996ms)
|
|
73
|
+
ℹ tests 44
|
|
74
|
+
ℹ suites 0
|
|
75
|
+
ℹ pass 44
|
|
76
|
+
ℹ fail 0
|
|
77
|
+
ℹ cancelled 0
|
|
78
|
+
ℹ skipped 0
|
|
79
|
+
ℹ todo 0
|
|
80
|
+
ℹ duration_ms 335.5458
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Global Command Execution: aether --help
|
|
84
|
+
Command run: `& "$env:APPDATA\npm\aether.cmd" --help`
|
|
85
|
+
Output:
|
|
86
|
+
```
|
|
87
|
+
Usage: aether [options] [command]
|
|
88
|
+
|
|
89
|
+
Aether Core AI v110 — Universal AI Gateway CLI
|
|
90
|
+
Supports 13+ AI providers • Free & paid models • Local fallbacks
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
-v, --version output the version number
|
|
94
|
+
-h, --help display help for command
|
|
95
|
+
|
|
96
|
+
Commands:
|
|
97
|
+
chat [options] Start an interactive chat session
|
|
98
|
+
ask [options] <prompt...> Send a single prompt and get a response
|
|
99
|
+
config Manage API keys and settings
|
|
100
|
+
providers [options] List all supported AI providers and their status
|
|
101
|
+
models [provider] List available models for a provider
|
|
102
|
+
modes List all reasoning modes
|
|
103
|
+
status Show system status & configured providers
|
|
104
|
+
setup Interactive guided setup for API keys
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Global Command Execution: aether config path
|
|
108
|
+
Command run: `& "$env:APPDATA\npm\aether.cmd" config path`
|
|
109
|
+
Output:
|
|
110
|
+
```
|
|
111
|
+
CONFIG C:\Users\naina\.aether\config.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Global Command Execution: Math Fallback Query
|
|
115
|
+
Command run: `& "$env:APPDATA\npm\aether.cmd" ask "2 + 2 * (10 - 5)"`
|
|
116
|
+
Output:
|
|
117
|
+
```
|
|
118
|
+
MODE Titan Fusion v110 • Layer 110
|
|
119
|
+
- Routing through failover mesh...
|
|
120
|
+
|
|
121
|
+
AETHER via local • Node 0
|
|
122
|
+
────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
🤖 [LOCAL MATH SOLVER]
|
|
125
|
+
Expression: 2+2*(10-5)
|
|
126
|
+
Result: 12
|
|
127
|
+
────────────────────────────────────────────────────────────────────────────
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Git Status Check
|
|
131
|
+
Command run: `git status`
|
|
132
|
+
Output:
|
|
133
|
+
```
|
|
134
|
+
On branch main
|
|
135
|
+
Changes not staged for commit:
|
|
136
|
+
(use "git add <file>..." to update what will be committed)
|
|
137
|
+
(use "git restore <file>..." to discard changes in working directory)
|
|
138
|
+
modified: package.json
|
|
139
|
+
modified: src/ai/router.js
|
|
140
|
+
modified: src/ai/universal.js
|
|
141
|
+
modified: src/chat.js
|
|
142
|
+
modified: src/cli.js
|
|
143
|
+
modified: src/ui/banner.js
|
|
144
|
+
modified: src/ui/spinner.js
|
|
145
|
+
modified: src/ui/theme.js
|
|
146
|
+
|
|
147
|
+
Untracked files:
|
|
148
|
+
(use "git add <file>..." to include in what will be committed)
|
|
149
|
+
test/
|
|
150
|
+
|
|
151
|
+
no changes added to commit (use "git add" and/or "git commit -a")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 2. Logic Chain
|
|
157
|
+
|
|
158
|
+
1. **Local Link Establishment**: The output from `npm.cmd link` confirms that the package is successfully registered in Node's global path.
|
|
159
|
+
2. **Command Executability**: Since the terminal PATH of the current shell did not automatically refresh, invoking `aether` directly failed. However, calling the generated batch script `& "$env:APPDATA\npm\aether.cmd"` succeeded, demonstrating that Commander configuration and binary entrypoints are fully operational.
|
|
160
|
+
3. **Core Command Compliance**:
|
|
161
|
+
- `aether --help` successfully parsed parameters and returned the standard Commander help menu with all expected commands.
|
|
162
|
+
- `aether config path` output the expected Cyberpunk formatted config path `C:\Users\naina\.aether\config.json`.
|
|
163
|
+
4. **Math Query Offline Routing**: The output of `aether ask "2 + 2 * (10 - 5)"` verifies that the prompt was correctly classified as a pure mathematical expression, bypassed remote AI networks, and routed to node 0 (the local math solver), producing the correct result `12`.
|
|
164
|
+
5. **Unit Test Coverage**: The `npm.cmd test` execution successfully discovered 44 tests across 5 suites, and all 44 passed with 0 failures, verifying all unit levels of logic (configs, math evaluation, UI parsing, fallback routers, spinners).
|
|
165
|
+
6. **Repository Status**: `git status` shows modifications in `src/` and `package.json` and a new `test/` directory, which represents the files implemented by other worker agents during prior tasks in this milestone. No other untracked or dirty state from verification is present.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 3. Caveats
|
|
170
|
+
|
|
171
|
+
- Since this verification ran on Windows, PowerShell execution policies blocked direct command resolution for `npm` (forcing `npm.cmd`) and the path to global command required invoking `aether.cmd` via the full path `%APPDATA%\npm\aether.cmd` or PowerShell invocation notation.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 4. Conclusion
|
|
176
|
+
|
|
177
|
+
The Aether AI CLI package is fully operational. Commander configurations, E2E routing to Node 0 (local math solver), unit tests (all 44 pass), config path display, and package linkage are verified and working as expected.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 5. Verification Method
|
|
182
|
+
|
|
183
|
+
To re-verify locally:
|
|
184
|
+
1. Run `npm.cmd test` in the project root directory.
|
|
185
|
+
2. Run `& "$env:APPDATA\npm\aether.cmd" --help` to verify command routing.
|
|
186
|
+
3. Run `& "$env:APPDATA\npm\aether.cmd" config path`.
|
|
187
|
+
4. Run `& "$env:APPDATA\npm\aether.cmd" ask "2 + 2 * (10 - 5)"`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# progress.md
|
|
2
|
+
Last visited: 2026-06-25T09:47:50-04:00
|
|
3
|
+
|
|
4
|
+
## Current Status
|
|
5
|
+
- Linked package locally with `npm.cmd link`.
|
|
6
|
+
- Successfully ran all 44 unit tests with 100% pass rate.
|
|
7
|
+
- Tested global `aether --help` command successfully.
|
|
8
|
+
- Tested global `aether config path` command successfully.
|
|
9
|
+
- Tested global math query `aether ask "2 + 2 * (10 - 5)"` successfully (routes to Node 0, returns 12).
|
|
10
|
+
- Ran `git status` to verify repository files status.
|
|
11
|
+
- Ready to write handoff.md.
|
|
12
|
+
|
|
13
|
+
## Plan
|
|
14
|
+
1. Write handoff.md.
|
|
15
|
+
2. Update BRIEFING.md.
|
|
16
|
+
3. Send completion message to main agent.
|
package/HIGHLIGHTS.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Aether CLI v1.3.0 Highlights
|
|
2
|
+
- **Token Telemetry Tracker**: Real-time prompt and completion token statistics shown on every chat turn.
|
|
3
|
+
- **Session Telemetry `/tokens`**: A new slash command displaying detailed model-by-model session token breakdowns and total exchange stats.
|
|
4
|
+
- **Toggle Telemetry**: Enable or disable display using `aether config set SHOW_TOKENS false`.
|
|
5
|
+
|
|
6
|
+
# Aether CLI v1.1.9 Highlights
|
|
7
|
+
- **Node 18 Compatibility**: Resolves `ReadableStream` reference errors inside the Node 18 CI test runner.
|
|
8
|
+
- **Auto-Updater**: Checks for updates once every 24 hours on launch and updates the CLI automatically.
|
|
9
|
+
- **Release Highlights**: Prompts and renders version release notes on launch.
|
|
10
|
+
- **Customizable Control**: Toggle behavior using `aether config set AUTO_UPDATE false` and `aether config set SHOW_HIGHLIGHTS false`.
|
|
11
|
+
- **Autopilot Safety Levels**: Added autonomous capabilities controlled by `/autopilot` setting.
|
|
12
|
+
- **Chat History Selector**: View and switch past chat logs with `/history`.
|
package/aether_pip/cli.py
CHANGED
package/package.json
CHANGED
|
@@ -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": {
|
package/src/ai/router.js
CHANGED
|
@@ -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
|
}
|
package/src/ai/tokens.js
ADDED
|
@@ -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
|
+
}
|
package/src/chat.js
CHANGED
|
@@ -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
|
+
}
|
package/src/config.js
CHANGED
|
@@ -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
|