agy-statusline 1.1.1
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/LICENSE +21 -0
- package/README.md +148 -0
- package/assets/logo.png +0 -0
- package/package.json +48 -0
- package/setup.js +161 -0
- package/statusline.js +218 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 radioman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Optimized Statusline for Antigravity CLI
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="assets/logo.png" alt="Optimized Statusline for Antigravity CLI Logo" width="250" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
An optimized custom statusline implementation for the Antigravity CLI, featuring token scaling, dynamic context warnings, and a 20-step progress bar.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **High Performance**: Native startup under Bun (`~3ms`) or Node.js (`~15ms`), fitting safely below the CLI's `150ms` execution timeout.
|
|
12
|
+
- **Token Scale & Rounding**: Displays cumulative input and output token counts in `k` scale, rounded to the nearest integer.
|
|
13
|
+
- **Dynamic Context Warnings**: Colorizes context usage text automatically based on total input token counts:
|
|
14
|
+
- **Cyan**: Normal ($<160\text{k}$ tokens)
|
|
15
|
+
- **Orange**: Warning ($\ge 160\text{k}$ tokens)
|
|
16
|
+
- **Red**: Alert ($\ge 200\text{k}$ tokens)
|
|
17
|
+
- **20-Step Progress Bar**: Visualizes context usage percentage in 5% increments (`[■■■■□□□□□□□□□□□□□□□□]`).
|
|
18
|
+
- **Compact & Split Layout**: Calibrates spaces dynamically according to your `terminal_width` to push the folder and model information cleanly to the right side.
|
|
19
|
+
|
|
20
|
+
## Layout Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
? for shortcuts • {progress_bar} {used_pct}% of {limit} • in {input}k | out {output}k {cwd} • {model_name}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
1. **Input**: The Antigravity CLI pipes a JSON metadata payload containing session details (active model, workspace, context token consumption, terminal size) into the script via standard input (`stdin`).
|
|
31
|
+
2. **Parsing**: The script parses the incoming JSON stream. If `stdin` is empty or times out (safety threshold: `150ms`), it falls back to defaults or reads from the environment variable (`process.env.ANTIGRAVITY_SOURCE_METADATA`).
|
|
32
|
+
3. **Calculations**:
|
|
33
|
+
- Calculates cumulative token usage (`total_input_tokens + total_output_tokens`) and rounds the values to integer `k` scales.
|
|
34
|
+
- Selects the statusline alert color (Cyan, Orange, or Red) based on total token limits.
|
|
35
|
+
- Strips ANSI escape codes from formatting templates to measure the true printable character width.
|
|
36
|
+
- Subtracts the printed width from the target `terminal_width` to determine the required padding spaces.
|
|
37
|
+
4. **Output**: Writes the aligned statusline directly to standard output (`stdout`) with no trailing newline, enabling the CLI shell wrapper to render it cleanly at the bottom of the prompt.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Installation & Setup
|
|
42
|
+
|
|
43
|
+
### Installation via NPM
|
|
44
|
+
|
|
45
|
+
You can install this statusline directly from the npm registry and configure it automatically with a single command:
|
|
46
|
+
|
|
47
|
+
1. **Install Globally**:
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g agy-statusline
|
|
50
|
+
```
|
|
51
|
+
*(or `bun install -g agy-statusline` if using Bun)*
|
|
52
|
+
|
|
53
|
+
2. **Run Automatic Configuration**:
|
|
54
|
+
```bash
|
|
55
|
+
agy-statusline-setup
|
|
56
|
+
```
|
|
57
|
+
*(This automatically locates your `settings.json` configuration file and updates your `statusLine` configuration block).*
|
|
58
|
+
|
|
59
|
+
3. **Verify Installation** (Optional):
|
|
60
|
+
Run the diagnostics doctor to verify everything is working perfectly:
|
|
61
|
+
```bash
|
|
62
|
+
agy-statusline-setup doctor
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Local Setup & Automatic Installation
|
|
66
|
+
|
|
67
|
+
If you cloned this repository locally, you can set it up using the included `setup.js` script:
|
|
68
|
+
|
|
69
|
+
Run the setup script using Bun (or Node.js):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
bun setup.js
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This will automatically:
|
|
76
|
+
1. Run `bun link` to register the `agy-statusline` command globally.
|
|
77
|
+
2. Locate your Antigravity CLI `settings.json` configuration file.
|
|
78
|
+
3. Add or update the `statusLine` configuration block automatically (handling the UTF-8 BOM check correctly):
|
|
79
|
+
```json
|
|
80
|
+
"statusLine": {
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "agy-statusline",
|
|
83
|
+
"enabled": true
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Manual Local Installation (Alternative)
|
|
88
|
+
|
|
89
|
+
If you prefer manual setup from the local directory:
|
|
90
|
+
1. **Link Globally**: Run `bun link` in the repository folder.
|
|
91
|
+
2. **Configure Settings**: Open your Antigravity CLI config file located at `~/.gemini/antigravity-cli/settings.json` (or `C:\Users\<username>\.gemini\antigravity-cli\settings.json` on Windows), making sure to write without a UTF-8 BOM, and set:
|
|
92
|
+
```json
|
|
93
|
+
"statusLine": {
|
|
94
|
+
"type": "command",
|
|
95
|
+
"command": "agy-statusline",
|
|
96
|
+
"enabled": true
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Local Testing
|
|
103
|
+
|
|
104
|
+
You can simulate how the statusline renders using the provided mock payload (`agy_statusline_stdin.json`).
|
|
105
|
+
|
|
106
|
+
### Windows (PowerShell)
|
|
107
|
+
```powershell
|
|
108
|
+
Get-Content .\agy_statusline_stdin.json -Raw | bun .\statusline.js
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### macOS/Linux
|
|
112
|
+
```bash
|
|
113
|
+
cat ./agy_statusline_stdin.json | bun ./statusline.js
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Diagnostic Doctor Check
|
|
119
|
+
|
|
120
|
+
You can run the built-in diagnostic tool to verify that the script is linked, the CLI settings are properly configured without a BOM, and the simulation executes correctly:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
bun setup.js doctor
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Testing & Coverage
|
|
129
|
+
|
|
130
|
+
This project features a comprehensive test suite written using Node's native test runner to maintain zero external runtime dependencies.
|
|
131
|
+
|
|
132
|
+
### Run Tests
|
|
133
|
+
|
|
134
|
+
You can run the full integration test suite (which validates rendering layout, token math, threshold coloring, path contraction, and error fallbacks):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm test
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
*Note: This command runs both the Node.js test runner and the Doctor diagnostics script, which also runs automatically as a Husky pre-commit hook.*
|
|
141
|
+
|
|
142
|
+
### Test Coverage Report
|
|
143
|
+
|
|
144
|
+
To run the tests with a code coverage report (requires Node.js v20+):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
node --test --experimental-test-coverage
|
|
148
|
+
```
|
package/assets/logo.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agy-statusline",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Optimized custom statusline implementation for the Antigravity CLI",
|
|
5
|
+
"main": "statusline.js",
|
|
6
|
+
"logo": "assets/logo.png",
|
|
7
|
+
"icon": "assets/logo.png",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pkradioman/agy-statusline.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pkradioman/agy-statusline/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/pkradioman/agy-statusline#readme",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"prepare": "husky",
|
|
21
|
+
"test": "node --test && node setup.js doctor",
|
|
22
|
+
"release": "commit-and-tag-version"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"agy-statusline": "statusline.js",
|
|
26
|
+
"agy-statusline-setup": "setup.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"statusline.js",
|
|
30
|
+
"setup.js",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"assets/logo.png"
|
|
34
|
+
],
|
|
35
|
+
"keywords": [
|
|
36
|
+
"antigravity",
|
|
37
|
+
"statusline",
|
|
38
|
+
"customizer"
|
|
39
|
+
],
|
|
40
|
+
"author": "radioman",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@commitlint/cli": "^21.0.2",
|
|
44
|
+
"@commitlint/config-conventional": "^21.0.2",
|
|
45
|
+
"commit-and-tag-version": "^12.7.3",
|
|
46
|
+
"husky": "^9.1.7"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/setup.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// 0. Handle doctor CLI parameter
|
|
8
|
+
if (process.argv.includes('doctor') || process.argv.includes('--doctor')) {
|
|
9
|
+
runDoctor();
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.log('🚀 Setting up Antigravity statusline...');
|
|
14
|
+
|
|
15
|
+
// 1. Run bun link to register agy-statusline globally (if in local repository dev environment)
|
|
16
|
+
let isLocalDev = fs.existsSync(path.join(__dirname, '.git'));
|
|
17
|
+
if (isLocalDev) {
|
|
18
|
+
try {
|
|
19
|
+
console.log('🔗 Running "bun link" to register the binary globally...');
|
|
20
|
+
execSync('bun link', { stdio: 'inherit' });
|
|
21
|
+
console.log('✅ Registered "agy-statusline" successfully.');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.warn('⚠️ Failed to run "bun link". Proceeding with settings configuration anyway...');
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
console.log('📦 Running from global package installation, skipping "bun link".');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. Locate settings.json
|
|
30
|
+
const homeDir = os.homedir();
|
|
31
|
+
const settingsPath = path.join(homeDir, '.gemini', 'antigravity-cli', 'settings.json');
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(settingsPath)) {
|
|
34
|
+
console.error(`❌ Antigravity settings file not found at: ${settingsPath}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Read and update settings.json
|
|
39
|
+
try {
|
|
40
|
+
const rawSettings = fs.readFileSync(settingsPath, 'utf8').trim();
|
|
41
|
+
// Strip UTF-8 BOM if present
|
|
42
|
+
const cleanSettings = rawSettings.replace(/^\uFEFF/, '');
|
|
43
|
+
const settings = JSON.parse(cleanSettings);
|
|
44
|
+
|
|
45
|
+
// Initialize statusLine block if it doesn't exist
|
|
46
|
+
if (!settings.statusLine) {
|
|
47
|
+
settings.statusLine = {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
settings.statusLine.type = 'command';
|
|
51
|
+
settings.statusLine.command = 'agy-statusline';
|
|
52
|
+
settings.statusLine.enabled = true;
|
|
53
|
+
|
|
54
|
+
// Convert back to string and write to file (default write has no BOM)
|
|
55
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
56
|
+
console.log(`✅ Successfully updated settings at: ${settingsPath}`);
|
|
57
|
+
console.log('🎉 Setup complete! Restart your shell or CLI to see changes.');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`❌ Failed to update settings.json: ${error.message}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function runDoctor() {
|
|
64
|
+
console.log('🩺 Running Antigravity Statusline Doctor...');
|
|
65
|
+
let health = true;
|
|
66
|
+
|
|
67
|
+
// 1. Check Runtime
|
|
68
|
+
console.log(`\n1. Checking JavaScript Runtime...`);
|
|
69
|
+
console.log(` Running under: ${process.release ? process.release.name : 'Bun'} (${process.version})`);
|
|
70
|
+
|
|
71
|
+
// 2. Check settings.json location and contents
|
|
72
|
+
console.log(`\n2. Checking settings.json...`);
|
|
73
|
+
const sPath = path.join(os.homedir(), '.gemini', 'antigravity-cli', 'settings.json');
|
|
74
|
+
if (!fs.existsSync(sPath)) {
|
|
75
|
+
console.error(` ❌ settings.json NOT found at: ${sPath}`);
|
|
76
|
+
health = false;
|
|
77
|
+
} else {
|
|
78
|
+
console.log(` Found settings.json at: ${sPath}`);
|
|
79
|
+
try {
|
|
80
|
+
const rawBytes = fs.readFileSync(sPath);
|
|
81
|
+
// Check for UTF-8 BOM (0xEF, 0xBB, 0xBF)
|
|
82
|
+
if (rawBytes.length >= 3 && rawBytes[0] === 0xEF && rawBytes[1] === 0xBB && rawBytes[2] === 0xBF) {
|
|
83
|
+
console.error(` ❌ WARNING: settings.json contains a UTF-8 BOM (Byte Order Mark). This will cause the CLI to crash or reset!`);
|
|
84
|
+
health = false;
|
|
85
|
+
} else {
|
|
86
|
+
console.log(` ✅ UTF-8 BOM Check: Passed (No BOM found)`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const settings = JSON.parse(rawBytes.toString('utf8').trim());
|
|
90
|
+
if (settings.statusLine) {
|
|
91
|
+
console.log(` ✅ statusLine block exists`);
|
|
92
|
+
console.log(` Enabled: ${settings.statusLine.enabled}`);
|
|
93
|
+
console.log(` Type: ${settings.statusLine.type}`);
|
|
94
|
+
console.log(` Command: ${settings.statusLine.command}`);
|
|
95
|
+
|
|
96
|
+
if (settings.statusLine.command !== 'agy-statusline') {
|
|
97
|
+
console.error(` ❌ WARNING: statusLine.command is set to "${settings.statusLine.command}" instead of "agy-statusline"`);
|
|
98
|
+
health = false;
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
console.error(` ❌ statusLine configuration is missing in settings.json`);
|
|
102
|
+
health = false;
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.error(` ❌ Failed to parse settings.json: ${e.message}`);
|
|
106
|
+
health = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. Check global executable in PATH
|
|
111
|
+
console.log(`\n3. Checking PATH for agy-statusline command...`);
|
|
112
|
+
const pathDirs = (process.env.PATH || '').split(path.delimiter);
|
|
113
|
+
let binaryFound = false;
|
|
114
|
+
const isWindows = process.platform === 'win32';
|
|
115
|
+
const binaryNames = isWindows ? ['agy-statusline.exe', 'agy-statusline.cmd', 'agy-statusline.ps1', 'agy-statusline'] : ['agy-statusline'];
|
|
116
|
+
|
|
117
|
+
for (const dir of pathDirs) {
|
|
118
|
+
for (const binName of binaryNames) {
|
|
119
|
+
const fullPath = path.join(dir, binName);
|
|
120
|
+
if (fs.existsSync(fullPath)) {
|
|
121
|
+
console.log(` ✅ Found executable shim at: ${fullPath}`);
|
|
122
|
+
binaryFound = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (binaryFound) break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!binaryFound) {
|
|
130
|
+
console.error(` ❌ "agy-statusline" was NOT found in your system PATH.`);
|
|
131
|
+
console.error(` Make sure your global Bun/NPM binary folder is in your PATH environment variable.`);
|
|
132
|
+
health = false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. Try running the statusline script with a mock input
|
|
136
|
+
console.log(`\n4. Simulating Statusline Run...`);
|
|
137
|
+
const mockPayloadPath = path.join(__dirname, 'agy_statusline_stdin.json');
|
|
138
|
+
if (!fs.existsSync(mockPayloadPath)) {
|
|
139
|
+
console.error(` ❌ Mock input payload NOT found at: ${mockPayloadPath}`);
|
|
140
|
+
health = false;
|
|
141
|
+
} else {
|
|
142
|
+
try {
|
|
143
|
+
const mockInput = fs.readFileSync(mockPayloadPath, 'utf8');
|
|
144
|
+
const runtime = process.release ? 'node' : 'bun';
|
|
145
|
+
const scriptPath = path.join(__dirname, 'statusline.js');
|
|
146
|
+
const output = execSync(`${runtime} "${scriptPath}"`, { input: mockInput, encoding: 'utf8' });
|
|
147
|
+
console.log(` ✅ Test Run Successful! Output:`);
|
|
148
|
+
console.log(` ${output}`);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
console.error(` ❌ Test Run Failed: ${e.message}`);
|
|
151
|
+
health = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('\n---');
|
|
156
|
+
if (health) {
|
|
157
|
+
console.log('🎉 Statusline Health: EXCELLENT (All checks passed)');
|
|
158
|
+
} else {
|
|
159
|
+
console.error('❌ Statusline Health: PROBLEMS DETECTED (See details above)');
|
|
160
|
+
}
|
|
161
|
+
}
|
package/statusline.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
let timeout;
|
|
6
|
+
|
|
7
|
+
if (require.main === module) {
|
|
8
|
+
let inputData = '';
|
|
9
|
+
process.stdin.setEncoding('utf8');
|
|
10
|
+
|
|
11
|
+
process.stdin.on('data', (chunk) => {
|
|
12
|
+
inputData += chunk;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
process.stdin.on('end', () => {
|
|
16
|
+
renderStatusline(inputData, 'end_event');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// To prevent hanging if stdin is not closed or redirected, set a safety timeout.
|
|
20
|
+
timeout = setTimeout(() => {
|
|
21
|
+
renderStatusline(inputData, 'timeout');
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}, 150);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderStatusline(jsonStr, triggerSource) {
|
|
27
|
+
clearTimeout(timeout);
|
|
28
|
+
|
|
29
|
+
// Default values
|
|
30
|
+
let used_pct = 0;
|
|
31
|
+
let size = 0;
|
|
32
|
+
let model_name = 'Antigravity';
|
|
33
|
+
let current_dir = process.cwd();
|
|
34
|
+
let terminal_width = 80;
|
|
35
|
+
let input_tokens = 0;
|
|
36
|
+
let output_tokens = 0;
|
|
37
|
+
let errorLog = '';
|
|
38
|
+
|
|
39
|
+
// Read from environment fallback if stdin is empty
|
|
40
|
+
if (process.env.ANTIGRAVITY_SOURCE_METADATA) {
|
|
41
|
+
try {
|
|
42
|
+
let envMeta = process.env.ANTIGRAVITY_SOURCE_METADATA.replace(/^\uFEFF/, '');
|
|
43
|
+
const meta = JSON.parse(envMeta);
|
|
44
|
+
if (meta.model && (meta.model.display_name || meta.model.id)) {
|
|
45
|
+
model_name = meta.model.display_name || meta.model.id;
|
|
46
|
+
}
|
|
47
|
+
if (meta.workspace && meta.workspace.current_dir) {
|
|
48
|
+
current_dir = meta.workspace.current_dir;
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
errorLog += `[Env Parse Err: ${e.message}] `;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (jsonStr) {
|
|
56
|
+
try {
|
|
57
|
+
jsonStr = jsonStr.replace(/^\uFEFF/, '').trim();
|
|
58
|
+
if (jsonStr) {
|
|
59
|
+
const data = JSON.parse(jsonStr);
|
|
60
|
+
|
|
61
|
+
if (data.model) {
|
|
62
|
+
model_name = data.model.display_name || data.model.id || model_name;
|
|
63
|
+
}
|
|
64
|
+
if (data.workspace && data.workspace.current_dir) {
|
|
65
|
+
current_dir = data.workspace.current_dir;
|
|
66
|
+
} else if (data.cwd) {
|
|
67
|
+
current_dir = data.cwd;
|
|
68
|
+
}
|
|
69
|
+
if (data.terminal_width !== undefined) {
|
|
70
|
+
terminal_width = Number(data.terminal_width) || 80;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (data.context_window) {
|
|
74
|
+
let cw = data.context_window;
|
|
75
|
+
used_pct = cw.used_percentage !== undefined ? Math.round(cw.used_percentage) : 0;
|
|
76
|
+
size = cw.context_window_size !== undefined ? cw.context_window_size : 0;
|
|
77
|
+
input_tokens = cw.total_input_tokens !== undefined ? cw.total_input_tokens : 0;
|
|
78
|
+
output_tokens = cw.total_output_tokens !== undefined ? cw.total_output_tokens : 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Save to file for debugging
|
|
82
|
+
const userProfile = process.env.USERPROFILE || process.env.HOME || '';
|
|
83
|
+
const targetDir = path.join(userProfile, 'temp');
|
|
84
|
+
if (fs.existsSync(targetDir)) {
|
|
85
|
+
fs.writeFileSync(path.join(targetDir, 'agy_statusline_stdin.txt'), jsonStr, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
errorLog += `[Stdin Parse Err: ${e.message}. Content: ${JSON.stringify(jsonStr)}] `;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Format size
|
|
94
|
+
let formatted_size = String(size);
|
|
95
|
+
if (size >= 1048576) {
|
|
96
|
+
formatted_size = `${Math.floor(size / 1048576)}M`;
|
|
97
|
+
} else if (size >= 1024) {
|
|
98
|
+
formatted_size = `${Math.floor(size / 1024)}k`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ANSI color codes
|
|
102
|
+
const RESET = '\x1b[0m';
|
|
103
|
+
const BOLD = '\x1b[1m';
|
|
104
|
+
const GREY = '\x1b[90m';
|
|
105
|
+
const CYAN = '\x1b[36m';
|
|
106
|
+
const BLUE = '\x1b[34m';
|
|
107
|
+
const GREEN = '\x1b[32m';
|
|
108
|
+
const RED = '\x1b[31m';
|
|
109
|
+
const ORANGE = '\x1b[38;5;208m';
|
|
110
|
+
|
|
111
|
+
// Determine color of context info
|
|
112
|
+
let CONTEXT_COLOR = CYAN;
|
|
113
|
+
if (input_tokens >= 200000) {
|
|
114
|
+
CONTEXT_COLOR = RED;
|
|
115
|
+
} else if (input_tokens >= 160000) {
|
|
116
|
+
CONTEXT_COLOR = ORANGE;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const formatK = (n) => {
|
|
120
|
+
return `${Math.round(n / 1000)}k`;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let filled = Math.min(20, Math.max(0, Math.ceil(used_pct / 5)));
|
|
124
|
+
let empty = 20 - filled;
|
|
125
|
+
let bar = `[${'■'.repeat(filled)}${'□'.repeat(empty)}]`;
|
|
126
|
+
|
|
127
|
+
// Strip ANSI codes to calculate actual printed length
|
|
128
|
+
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
129
|
+
|
|
130
|
+
// Form left part and calculate its clean length
|
|
131
|
+
let left_str = `${GREY}? for shortcuts${RESET}${GREY} • ${RESET}${CONTEXT_COLOR}${bar} ${used_pct}% of ${formatted_size}${RESET}${GREY} • ${RESET}${CONTEXT_COLOR}in ${formatK(input_tokens)}${RESET}${GREY} | ${RESET}${CONTEXT_COLOR}out ${formatK(output_tokens)}${RESET}`;
|
|
132
|
+
let plain_left_len = stripAnsi(left_str).length;
|
|
133
|
+
|
|
134
|
+
// Calculate clean length of the right part excluding the directory (bullet + model name)
|
|
135
|
+
let right_sans_dir = `${GREY} • ${RESET}${BOLD}${model_name}${RESET}`;
|
|
136
|
+
let plain_right_sans_dir_len = stripAnsi(right_sans_dir).length;
|
|
137
|
+
|
|
138
|
+
// Remaining width for the directory path (with a safety buffer of 2)
|
|
139
|
+
let max_dir_len = terminal_width - plain_left_len - plain_right_sans_dir_len - 2;
|
|
140
|
+
|
|
141
|
+
// Contract directory path dynamically to fit remaining space
|
|
142
|
+
let contracted_dir = contractPath(current_dir, max_dir_len);
|
|
143
|
+
|
|
144
|
+
// Form final right part using the contracted directory
|
|
145
|
+
let right_str = `${GREEN}${contracted_dir}${RESET}${right_sans_dir}`;
|
|
146
|
+
let plain_right_len = stripAnsi(right_str).length;
|
|
147
|
+
|
|
148
|
+
// Calculate padding size using clean lengths
|
|
149
|
+
let padding_size = terminal_width - plain_left_len - plain_right_len;
|
|
150
|
+
|
|
151
|
+
let status = '';
|
|
152
|
+
if (padding_size > 0) {
|
|
153
|
+
let padding = ' '.repeat(padding_size);
|
|
154
|
+
status = `${left_str}${padding}${right_str}`;
|
|
155
|
+
} else {
|
|
156
|
+
status = `${left_str}${GREY} • ${RESET}${right_str}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
process.stdout.write(status);
|
|
160
|
+
|
|
161
|
+
// Write debug log
|
|
162
|
+
const userProfile = process.env.USERPROFILE || process.env.HOME || '';
|
|
163
|
+
const logFile = path.join(userProfile, 'temp', 'statusline_debug.log');
|
|
164
|
+
const logEntry = `${new Date().toISOString()} - Executed via ${triggerSource}. Model: ${model_name}. Cwd: ${current_dir}. Errs: ${errorLog}\n`;
|
|
165
|
+
try {
|
|
166
|
+
fs.appendFileSync(logFile, logEntry, 'utf8');
|
|
167
|
+
} catch(e) {}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function contractPath(p, maxLen) {
|
|
171
|
+
if (p.length <= maxLen) return p;
|
|
172
|
+
|
|
173
|
+
// 1. Try substituting home directory with ~
|
|
174
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
175
|
+
if (home && p.startsWith(home)) {
|
|
176
|
+
p = p.replace(home, '~');
|
|
177
|
+
}
|
|
178
|
+
if (p.length <= maxLen) return p;
|
|
179
|
+
|
|
180
|
+
// 2. If still too long, truncate it in the middle or keep the last N characters with ...
|
|
181
|
+
if (maxLen <= 10) return p; // Don't truncate if allowed length is too small to be readable
|
|
182
|
+
|
|
183
|
+
// Split by path separator
|
|
184
|
+
const sep = p.includes('\\') ? '\\' : '/';
|
|
185
|
+
const segments = p.split(sep);
|
|
186
|
+
|
|
187
|
+
if (segments.length <= 2) {
|
|
188
|
+
return '...' + p.slice(-(maxLen - 3));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let first = segments[0];
|
|
192
|
+
let last = segments[segments.length - 1];
|
|
193
|
+
|
|
194
|
+
// Keep adding segments from the end as long as we stay under maxLen
|
|
195
|
+
let middle = '...';
|
|
196
|
+
let result = first + sep + middle + sep + last;
|
|
197
|
+
|
|
198
|
+
for (let i = segments.length - 2; i > 0; i--) {
|
|
199
|
+
let candidate = first + sep + middle + sep + segments[i] + sep + last;
|
|
200
|
+
if (candidate.length <= maxLen) {
|
|
201
|
+
last = segments[i] + sep + last;
|
|
202
|
+
} else {
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
result = first + sep + middle + sep + last;
|
|
208
|
+
if (result.length > maxLen) {
|
|
209
|
+
return '...' + p.slice(-(maxLen - 3));
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
215
|
+
module.exports = {
|
|
216
|
+
renderStatusline,
|
|
217
|
+
};
|
|
218
|
+
}
|