agy-powerline 1.1.0 โ 1.2.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/CHANGELOG.md +25 -0
- package/README.ja.md +148 -0
- package/README.md +6 -3
- package/README.zh-CN.md +148 -0
- package/package.json +11 -3
- package/src/cli/bundle.js +82 -0
- package/src/cli/configure.js +896 -65
- package/src/cli/init.js +24 -9
- package/src/cli/tui-style.js +154 -31
- package/src/lib/catalog.js +73 -4
- package/src/lib/config.js +6 -1
- package/src/lib/fit.js +2 -0
- package/src/lib/git.js +70 -2
- package/src/lib/layouts/lines.js +6 -0
- package/src/lib/layouts/pills.js +42 -0
- package/src/lib/log.js +24 -1
- package/src/lib/mock-data.js +6 -1
- package/src/lib/paths.js +6 -2
- package/src/lib/session.js +44 -8
- package/src/lib/widget-style.js +2 -0
package/src/lib/git.js
CHANGED
|
@@ -4,6 +4,14 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { logWarning } = require("./log");
|
|
6
6
|
const { firstText } = require("./formatters");
|
|
7
|
+
const { writeFileAtomic, PRIVATE_FILE_MODE } = require("./paths");
|
|
8
|
+
|
|
9
|
+
// Ahead/behind counts depend only on the two commits compared, so they are
|
|
10
|
+
// kept per commit pair: git runs again only when the branch or its upstream
|
|
11
|
+
// moves. Starting git costs ~60 ms on Windows, on every refresh otherwise.
|
|
12
|
+
const SYNC_CACHE_NAME = ".git-sync-cache.json";
|
|
13
|
+
const SYNC_CACHE_MAX = 20;
|
|
14
|
+
const COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
7
15
|
|
|
8
16
|
// Only an absolute folder that exists can belong to the user's session. A
|
|
9
17
|
// relative path (a Windows path read on Linux, for one) resolves against
|
|
@@ -217,6 +225,54 @@ function upstreamRef(commonDir, branch) {
|
|
|
217
225
|
return remote === "." ? merge : `refs/remotes/${remote}/${tracked}`;
|
|
218
226
|
}
|
|
219
227
|
|
|
228
|
+
// Only next to a real install's config (like the exec cache); CLI previews
|
|
229
|
+
// and other copies don't write it.
|
|
230
|
+
function syncCachePath() {
|
|
231
|
+
try {
|
|
232
|
+
const { getConfigDir } = require("./config");
|
|
233
|
+
const dir = getConfigDir();
|
|
234
|
+
return dir && fs.existsSync(path.join(dir, "statusline.config.json"))
|
|
235
|
+
? path.join(dir, SYNC_CACHE_NAME)
|
|
236
|
+
: null;
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readSyncCache(file) {
|
|
243
|
+
try {
|
|
244
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
245
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
246
|
+
} catch {
|
|
247
|
+
return {};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function cachedSync(file, key) {
|
|
252
|
+
const value = file ? readSyncCache(file)[key] : null;
|
|
253
|
+
const valid =
|
|
254
|
+
Array.isArray(value) &&
|
|
255
|
+
value.length === 2 &&
|
|
256
|
+
value.every((n) => Number.isInteger(n) && n >= 0 && n <= 1e7);
|
|
257
|
+
return valid ? { ahead: value[0], behind: value[1] } : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function saveSync(file, key, sync) {
|
|
261
|
+
if (!file) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
// Newest last; the oldest pairs go once there are more than SYNC_CACHE_MAX.
|
|
266
|
+
const cache = readSyncCache(file);
|
|
267
|
+
delete cache[key];
|
|
268
|
+
cache[key] = [sync.ahead, sync.behind];
|
|
269
|
+
const kept = Object.fromEntries(Object.entries(cache).slice(-SYNC_CACHE_MAX));
|
|
270
|
+
writeFileAtomic(file, JSON.stringify(kept), { mode: PRIVATE_FILE_MODE });
|
|
271
|
+
} catch {
|
|
272
|
+
// Best effort: the next refresh runs git again.
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
220
276
|
function getGitSync(gitDir, branch) {
|
|
221
277
|
if (!gitDir || !isSafeBranchName(branch)) {
|
|
222
278
|
return { ahead: 0, behind: 0 };
|
|
@@ -232,10 +288,19 @@ function getGitSync(gitDir, branch) {
|
|
|
232
288
|
return { ahead: 0, behind: 0 };
|
|
233
289
|
}
|
|
234
290
|
|
|
235
|
-
|
|
291
|
+
const upstreamHash = readRef(commonDir, upstream);
|
|
292
|
+
if (upstreamHash === localHash) {
|
|
236
293
|
return { ahead: 0, behind: 0 };
|
|
237
294
|
}
|
|
238
295
|
|
|
296
|
+
const key =
|
|
297
|
+
COMMIT.test(localHash) && COMMIT.test(upstreamHash) ? `${localHash}..${upstreamHash}` : "";
|
|
298
|
+
const cacheFile = key ? syncCachePath() : null;
|
|
299
|
+
const cached = cachedSync(cacheFile, key);
|
|
300
|
+
if (cached) {
|
|
301
|
+
return cached;
|
|
302
|
+
}
|
|
303
|
+
|
|
239
304
|
const { execFileSync } = require("child_process");
|
|
240
305
|
const out = execFileSync(
|
|
241
306
|
"git",
|
|
@@ -253,10 +318,12 @@ function getGitSync(gitDir, branch) {
|
|
|
253
318
|
|
|
254
319
|
const parts = out.split(/\s+/);
|
|
255
320
|
if (parts.length >= 2) {
|
|
256
|
-
|
|
321
|
+
const sync = {
|
|
257
322
|
ahead: parseInt(parts[0], 10) || 0,
|
|
258
323
|
behind: parseInt(parts[1], 10) || 0,
|
|
259
324
|
};
|
|
325
|
+
saveSync(cacheFile, key, sync);
|
|
326
|
+
return sync;
|
|
260
327
|
}
|
|
261
328
|
} catch (err) {
|
|
262
329
|
// A timeout or missing git is worth knowing about when ahead/behind
|
|
@@ -327,4 +394,5 @@ module.exports = {
|
|
|
327
394
|
gitHasDirtyMarkers,
|
|
328
395
|
vcsFromPayload,
|
|
329
396
|
resolveGit,
|
|
397
|
+
SYNC_CACHE_NAME,
|
|
330
398
|
};
|
package/src/lib/layouts/lines.js
CHANGED
|
@@ -292,6 +292,12 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
292
292
|
case "thirdPartyWeekly":
|
|
293
293
|
return session.thirdPartyWeekly ? quotaMeter(session.thirdPartyWeekly) : null;
|
|
294
294
|
|
|
295
|
+
case "argonWeekly":
|
|
296
|
+
return session.argonWeekly ? quotaMeter(session.argonWeekly) : null;
|
|
297
|
+
|
|
298
|
+
case "argonReset":
|
|
299
|
+
return quotaReset(session.argonWeekly, true);
|
|
300
|
+
|
|
295
301
|
case "context": {
|
|
296
302
|
const { thresh, blinkSeq, color } = contextState();
|
|
297
303
|
return (
|
package/src/lib/layouts/pills.js
CHANGED
|
@@ -312,6 +312,48 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
312
312
|
);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
// Argon is a Gemini model, so its quota takes the Gemini 7d color; the
|
|
316
|
+
// ๐ฎ tag tells the two apart.
|
|
317
|
+
case "argonWeekly": {
|
|
318
|
+
if (!session.argonWeekly) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const thresh = resolveQuotaThresholds(widget, config);
|
|
322
|
+
const rem = session.argonWeekly.remaining;
|
|
323
|
+
const level = rem <= thresh.crit ? "crit" : rem <= thresh.warn ? "warn" : "normal";
|
|
324
|
+
const blink = !ascii && shouldBlink(level, thresh.blink);
|
|
325
|
+
const color = quotaColor(rem, colors.weekly, {
|
|
326
|
+
crit: thresh.crit,
|
|
327
|
+
warn: thresh.warn,
|
|
328
|
+
critColor: colors.warn,
|
|
329
|
+
warnColor: colors.caution,
|
|
330
|
+
});
|
|
331
|
+
return meterSegment(
|
|
332
|
+
color,
|
|
333
|
+
rem,
|
|
334
|
+
meterLabel(),
|
|
335
|
+
`${close} ${rem.toFixed(1)}% rem `,
|
|
336
|
+
true,
|
|
337
|
+
blink,
|
|
338
|
+
pace(session.argonWeekly)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
case "argonReset": {
|
|
343
|
+
const text = session.argonWeekly
|
|
344
|
+
? formatResetText(session.argonWeekly, {
|
|
345
|
+
format: widget.format,
|
|
346
|
+
fallback: "countdown",
|
|
347
|
+
weekly: true,
|
|
348
|
+
clock: config.clock,
|
|
349
|
+
})
|
|
350
|
+
: "";
|
|
351
|
+
if (!text) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return pill(colors.weekly, lbl(text));
|
|
355
|
+
}
|
|
356
|
+
|
|
315
357
|
case "context": {
|
|
316
358
|
const thresh = resolveContextThresholds(widget, config);
|
|
317
359
|
const level =
|
package/src/lib/log.js
CHANGED
|
@@ -28,6 +28,28 @@ const IN_REPO = path.basename(__dirname) === "lib";
|
|
|
28
28
|
let logDir = null;
|
|
29
29
|
let context = {};
|
|
30
30
|
|
|
31
|
+
// The installed statusline.js is the runtime files joined into one (see
|
|
32
|
+
// src/cli/bundle.js). [first line, file name] for each of them, so a stack
|
|
33
|
+
// frame can still name the original file and line.
|
|
34
|
+
let bundleLines = null;
|
|
35
|
+
|
|
36
|
+
function setBundleLines(lines) {
|
|
37
|
+
bundleLines = Array.isArray(lines) ? lines : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function originalFrame(rel, line) {
|
|
41
|
+
if (!bundleLines || rel !== "statusline.js") {
|
|
42
|
+
return `${rel}:${line}`;
|
|
43
|
+
}
|
|
44
|
+
let match = null;
|
|
45
|
+
for (const entry of bundleLines) {
|
|
46
|
+
if (entry[0] <= line) {
|
|
47
|
+
match = entry;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return match ? `${match[1]}:${line - match[0] + 1}` : `${rel}:${line}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
31
53
|
// The copied runtime has no package.json; init records the version in
|
|
32
54
|
// install.json instead.
|
|
33
55
|
function agyPowerlineVersion() {
|
|
@@ -139,7 +161,7 @@ function ourFrames(stack) {
|
|
|
139
161
|
continue;
|
|
140
162
|
}
|
|
141
163
|
const fn = match[1] && /^[\w$.<> ]{1,60}$/.test(match[1]) ? `${match[1]} ` : "";
|
|
142
|
-
frames.push(`${fn}${rel.split(path.sep).join("/")
|
|
164
|
+
frames.push(`${fn}${originalFrame(rel.split(path.sep).join("/"), Number(match[3]))}`);
|
|
143
165
|
if (frames.length >= 8) {
|
|
144
166
|
break;
|
|
145
167
|
}
|
|
@@ -283,4 +305,5 @@ module.exports = {
|
|
|
283
305
|
logError,
|
|
284
306
|
logWarning,
|
|
285
307
|
clearLog,
|
|
308
|
+
setBundleLines,
|
|
286
309
|
};
|
package/src/lib/mock-data.js
CHANGED
|
@@ -26,7 +26,7 @@ function sampleData() {
|
|
|
26
26
|
input_tokens: 63382,
|
|
27
27
|
output_tokens: 346,
|
|
28
28
|
cache_creation_input_tokens: 0,
|
|
29
|
-
cache_read_input_tokens:
|
|
29
|
+
cache_read_input_tokens: 85935,
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
32
|
exceeds_200k_tokens: false,
|
|
@@ -53,6 +53,11 @@ function sampleData() {
|
|
|
53
53
|
reset_time: weeklyReset.toISOString(),
|
|
54
54
|
reset_in_seconds: 86400 + 17 * 3600,
|
|
55
55
|
},
|
|
56
|
+
"gemini-argon-wtu-weekly": {
|
|
57
|
+
remaining_fraction: 0.64,
|
|
58
|
+
reset_time: new Date(now + 4 * 86400 * 1000 + 9 * 3600 * 1000).toISOString(),
|
|
59
|
+
reset_in_seconds: 4 * 86400 + 9 * 3600,
|
|
60
|
+
},
|
|
56
61
|
},
|
|
57
62
|
agent_state: "thinking",
|
|
58
63
|
vcs: {
|
package/src/lib/paths.js
CHANGED
|
@@ -8,7 +8,8 @@ const AGY_SETTINGS = path.join(os.homedir(), ".gemini", "antigravity-cli", "sett
|
|
|
8
8
|
|
|
9
9
|
const INSTALL_META_NAME = "install.json";
|
|
10
10
|
|
|
11
|
-
// Library files
|
|
11
|
+
// Library files the runtime needs (paths relative to src/lib). init joins them
|
|
12
|
+
// with src/runtime/statusline.js into the one installed statusline.js.
|
|
12
13
|
const RUNTIME_FILES = [
|
|
13
14
|
"shared.js",
|
|
14
15
|
"colors.js",
|
|
@@ -35,13 +36,16 @@ const PRIVATE_FILES = [
|
|
|
35
36
|
"statusline.config.json",
|
|
36
37
|
".exec-cache.json",
|
|
37
38
|
"last-payload.json",
|
|
39
|
+
// SYNC_CACHE_NAME in git.js: commit IDs of the repos you work in.
|
|
40
|
+
".git-sync-cache.json",
|
|
38
41
|
// LOG_NAME in log.js, spelled out because log.js requires this file.
|
|
39
42
|
"agy-powerline.log",
|
|
40
43
|
];
|
|
41
44
|
const PRIVATE_FILE_MODE = 0o600;
|
|
42
45
|
|
|
43
46
|
// Everything init or the runtime may create in the install dir. uninstall
|
|
44
|
-
// --delete removes only these, never the whole directory tree.
|
|
47
|
+
// --delete removes only these, never the whole directory tree. RUNTIME_FILES
|
|
48
|
+
// are listed for installs from 1.1.0 and earlier, which copied them separately.
|
|
45
49
|
const INSTALLED_FILES = [
|
|
46
50
|
"statusline.js",
|
|
47
51
|
"statusline.cmd",
|
package/src/lib/session.js
CHANGED
|
@@ -6,10 +6,15 @@ const { resolveGit } = require("./git");
|
|
|
6
6
|
|
|
7
7
|
const FIVE_HOURS = 5 * 3600;
|
|
8
8
|
const SEVEN_DAYS = 7 * 86400;
|
|
9
|
+
const ARGON_BUCKET = "gemini-argon-wtu-weekly";
|
|
9
10
|
// The buckets Antigravity sends, with the short names the bar shows.
|
|
10
11
|
const QUOTA_BUCKETS = {
|
|
11
12
|
"gemini-5h": { label: "5h", window: FIVE_HOURS },
|
|
12
13
|
"gemini-weekly": { label: "7d", window: SEVEN_DAYS },
|
|
14
|
+
// Gemini 4 Argon on Google AI Pro: a weekly quota of its own, with no 5-hour
|
|
15
|
+
// window, sent whatever model is in use. On Ultra Argon counts toward the
|
|
16
|
+
// Gemini buckets and this one isn't sent.
|
|
17
|
+
[ARGON_BUCKET]: { label: "Argon 7d", window: SEVEN_DAYS },
|
|
13
18
|
"3p-5h": { label: "3P 5h", window: FIVE_HOURS },
|
|
14
19
|
"3p-weekly": { label: "3P 7d", window: SEVEN_DAYS },
|
|
15
20
|
};
|
|
@@ -68,8 +73,21 @@ function readQuota(data, key, windowSeconds) {
|
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
//
|
|
72
|
-
|
|
76
|
+
// Which quota group a bucket belongs to.
|
|
77
|
+
function bucketGroup(key) {
|
|
78
|
+
if (key.startsWith("gemini-argon-")) {
|
|
79
|
+
return "argon";
|
|
80
|
+
}
|
|
81
|
+
if (key.startsWith("gemini-")) {
|
|
82
|
+
return "gemini";
|
|
83
|
+
}
|
|
84
|
+
return key.startsWith("3p-") ? "3p" : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The bucket closest to running out for the model in use: the Argon bucket
|
|
88
|
+
// for Argon models, the other gemini-* buckets for Gemini models, 3p-*
|
|
89
|
+
// (third-party) buckets for the others. Argon falls back to the Gemini
|
|
90
|
+
// buckets, where its usage counts when there is no Argon bucket. Without a
|
|
73
91
|
// matching bucket every bucket counts. Disabled buckets never do.
|
|
74
92
|
function tightestQuota(data) {
|
|
75
93
|
const buckets = data?.quota && typeof data.quota === "object" ? data.quota : {};
|
|
@@ -84,9 +102,17 @@ function tightestQuota(data) {
|
|
|
84
102
|
}
|
|
85
103
|
|
|
86
104
|
const model = firstText(data?.model?.display_name, data?.model?.id).toLowerCase();
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
105
|
+
const inGroup = (group) => usable.filter((quota) => bucketGroup(quota.key) === group);
|
|
106
|
+
let pool;
|
|
107
|
+
if (!model.startsWith("gemini")) {
|
|
108
|
+
pool = inGroup("3p");
|
|
109
|
+
} else if (model.includes("argon")) {
|
|
110
|
+
pool = inGroup("argon");
|
|
111
|
+
pool = pool.length ? pool : inGroup("gemini");
|
|
112
|
+
} else {
|
|
113
|
+
pool = inGroup("gemini");
|
|
114
|
+
}
|
|
115
|
+
pool = pool.length ? pool : usable;
|
|
90
116
|
|
|
91
117
|
return pool.reduce((low, quota) => (!low || quota.remaining < low.remaining ? quota : low), null);
|
|
92
118
|
}
|
|
@@ -121,9 +147,18 @@ function contextUsedPercent(data) {
|
|
|
121
147
|
function extractSession(data, config) {
|
|
122
148
|
const contextPct = percent(contextUsedPercent(data));
|
|
123
149
|
const contextSize = count(data.context_window?.context_window_size);
|
|
150
|
+
const current = data.context_window?.current_usage;
|
|
151
|
+
// Context tokens are the used share of the window, so they always agree with
|
|
152
|
+
// the percentage next to them: agy's percentage also counts cached tokens and
|
|
153
|
+
// its own system prompt and tools, which no sum of current_usage reproduces.
|
|
154
|
+
// Without a window size, count what current_usage lists as in the window.
|
|
124
155
|
const usedTokens =
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
contextSize > 0
|
|
157
|
+
? Math.round((contextPct * contextSize) / 100)
|
|
158
|
+
: count(current?.input_tokens) +
|
|
159
|
+
count(current?.cache_read_input_tokens) +
|
|
160
|
+
count(current?.cache_creation_input_tokens);
|
|
161
|
+
const requestTokens = count(current?.input_tokens) + count(current?.output_tokens);
|
|
127
162
|
|
|
128
163
|
// RAM is measured here; Antigravity doesn't send it.
|
|
129
164
|
let ramPct = 0;
|
|
@@ -139,7 +174,7 @@ function extractSession(data, config) {
|
|
|
139
174
|
const inputTokens = count(data.context_window?.total_input_tokens);
|
|
140
175
|
const outputTokens = count(data.context_window?.total_output_tokens);
|
|
141
176
|
const sessionTokens = inputTokens + outputTokens;
|
|
142
|
-
const totalTokens = sessionTokens > 0 ? sessionTokens :
|
|
177
|
+
const totalTokens = sessionTokens > 0 ? sessionTokens : requestTokens;
|
|
143
178
|
|
|
144
179
|
// Share of the prompt served from cache in the latest request.
|
|
145
180
|
const cacheReadTokens = count(usage?.cache_read_input_tokens);
|
|
@@ -169,6 +204,7 @@ function extractSession(data, config) {
|
|
|
169
204
|
weekly: readQuota(data, "gemini-weekly", SEVEN_DAYS),
|
|
170
205
|
thirdPartyFiveHour: readQuota(data, "3p-5h", FIVE_HOURS),
|
|
171
206
|
thirdPartyWeekly: readQuota(data, "3p-weekly", SEVEN_DAYS),
|
|
207
|
+
argonWeekly: readQuota(data, ARGON_BUCKET, SEVEN_DAYS),
|
|
172
208
|
tightestQuota: tightestQuota(data),
|
|
173
209
|
cwd: firstText(data.cwd, data.workspace?.current_dir),
|
|
174
210
|
project: firstText(data.workspace?.project_dir),
|
package/src/lib/widget-style.js
CHANGED
|
@@ -93,6 +93,8 @@ const WIDGET_TAGS = {
|
|
|
93
93
|
weeklyReset: { icon: "๐", name: "Reset" },
|
|
94
94
|
thirdPartyFiveHour: { icon: "๐งช", name: "3P 5h" },
|
|
95
95
|
thirdPartyWeekly: { icon: "๐งช", name: "3P 7d" },
|
|
96
|
+
argonWeekly: { icon: "๐ฎ", name: "Argon 7d" },
|
|
97
|
+
argonReset: { icon: "๐", name: "Argon reset" },
|
|
96
98
|
context: { icon: "๐พ", name: "Context", iconOnly: true },
|
|
97
99
|
contextPct: { icon: "๐พ", name: "Context" },
|
|
98
100
|
contextTokens: { icon: "๐พ", name: "Context" },
|