agy-powerline 1.6.0 → 1.7.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 +12 -0
- package/README.fr.md +10 -0
- package/package.json +1 -1
- package/src/cli/configure.js +14 -0
- package/src/cli/doctor.js +13 -1
- package/src/cli/init.js +15 -1
- package/src/index.js +12 -0
- package/src/lib/config.js +14 -1
- package/src/lib/exec.js +3 -7
- package/src/lib/formatters.js +17 -14
- package/src/lib/i18n.js +140 -0
- package/src/lib/layouts/lines.js +17 -11
- package/src/lib/layouts/pills.js +20 -16
- package/src/lib/paths.js +1 -0
- package/src/lib/session.js +21 -17
- package/src/lib/widget-style.js +43 -38
- package/src/runtime/statusline.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.7.0] - 2026-10-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `"language"` setting for the bar's own words (labels, `rem`, units), set with `init --language` or tried with `render --language`; a code without messages shows English (#102)
|
|
13
|
+
- The bar in French with `"language": "fr"`, and agy-powerline.com/fr/ shows its bars in French (#148)
|
|
14
|
+
- agy-powerline.com/.well-known/security.txt sends security reports to GitHub's private vulnerability reporting
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- agy-powerline.com loads smaller images: each screenshot comes in smaller copies the browser picks from by the width it shows, the feature icons have a 56 px version, and the WebP images carry no color profile
|
|
19
|
+
|
|
8
20
|
## [1.6.0] - 2026-10-24
|
|
9
21
|
|
|
10
22
|
### Added
|
package/README.fr.md
CHANGED
|
@@ -135,6 +135,16 @@ Prévisualisez tous les thèmes avec `npx agy-powerline theme --gallery`.
|
|
|
135
135
|
|
|
136
136
|
</details>
|
|
137
137
|
|
|
138
|
+
### Langue de la barre
|
|
139
|
+
|
|
140
|
+
Les mots de la barre elle-même (`Réinit`, `rest.`, les unités de `2h 47m`) peuvent s’afficher en français :
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npx agy-powerline init --language fr
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
ou `"language": "fr"` dans `statusline.config.json` (touche `u` dans `configure`). Le nom du modèle, la branche et tout ce qu’envoie agy restent tels quels ; les commandes et `configure` restent en anglais. Détails dans la [documentation de configuration](docs/configuration.md#language).
|
|
147
|
+
|
|
138
148
|
## Widgets
|
|
139
149
|
|
|
140
150
|
| Widget | Informations affichées |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-powerline",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "agy statusline for Google Antigravity CLI: live Gemini, Claude/GPT and Argon quota pacing, git, themes and an interactive TUI configurator",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agy-powerline": "src/index.js"
|
package/src/cli/configure.js
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
} = require("../lib/shared");
|
|
25
25
|
const { resolveInstallDir } = require("../lib/paths");
|
|
26
26
|
const { WIDGET_TAGS } = require("../lib/widget-style");
|
|
27
|
+
const { SUPPORTED_LANGUAGES, resolveLanguage } = require("../lib/i18n");
|
|
27
28
|
const { editFields: getEditFields, stepNumberField } = require("../lib/widget-options");
|
|
28
29
|
const { loadEditableConfig } = require("./config-guard");
|
|
29
30
|
const {
|
|
@@ -427,6 +428,10 @@ function previewParts(width) {
|
|
|
427
428
|
if (preview.layout === "pills") {
|
|
428
429
|
chips.unshift(ui.chip("joins", preview.powerline.enabled, key("j")));
|
|
429
430
|
}
|
|
431
|
+
// Only worth a chip once there is a language to switch to.
|
|
432
|
+
if (SUPPORTED_LANGUAGES.length > 1) {
|
|
433
|
+
chips.push(ui.chip(`lang ${resolveLanguage(preview.language)}`, true, key("u")));
|
|
434
|
+
}
|
|
430
435
|
|
|
431
436
|
return { title, bar: String(rendered).split("\n"), chips: chips.join(" ") };
|
|
432
437
|
}
|
|
@@ -1629,6 +1634,15 @@ function onMain(key) {
|
|
|
1629
1634
|
if (persist()) {
|
|
1630
1635
|
flashMessage = `Pace marker: ${config.pacing ? "on" : "off"}`;
|
|
1631
1636
|
}
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (key.name === "u" && SUPPORTED_LANGUAGES.length > 1) {
|
|
1641
|
+
const next = SUPPORTED_LANGUAGES.indexOf(resolveLanguage(config.language)) + 1;
|
|
1642
|
+
config.language = SUPPORTED_LANGUAGES[next % SUPPORTED_LANGUAGES.length];
|
|
1643
|
+
if (persist()) {
|
|
1644
|
+
flashMessage = `Bar language: ${config.language}`;
|
|
1645
|
+
}
|
|
1632
1646
|
}
|
|
1633
1647
|
}
|
|
1634
1648
|
|
package/src/cli/doctor.js
CHANGED
|
@@ -28,6 +28,7 @@ const { runStatuslineCommand } = require("../lib/run");
|
|
|
28
28
|
const { detectColorDepth } = require("../lib/colors");
|
|
29
29
|
const { readLog, getLogPath } = require("../lib/log");
|
|
30
30
|
const { USAGE_PERIODS, usageAvailable } = require("../lib/usage");
|
|
31
|
+
const { matchLanguage, SUPPORTED_LANGUAGES } = require("../lib/i18n");
|
|
31
32
|
const { version } = require("../../package.json");
|
|
32
33
|
|
|
33
34
|
const DIM = "\x1b[2m";
|
|
@@ -78,7 +79,7 @@ function stripAnsi(text) {
|
|
|
78
79
|
function renderOverridesInCommand(command) {
|
|
79
80
|
const names = [];
|
|
80
81
|
const pattern =
|
|
81
|
-
/(?:^|\s)--(preset|layout|theme|caps|gradient)(?:(?:\s*=\s*|\s+)(?:"([^"]*)"|'([^']*)'|([^\s-][^\s]*)))?/g;
|
|
82
|
+
/(?:^|\s)--(preset|layout|theme|caps|gradient|language)(?:(?:\s*=\s*|\s+)(?:"([^"]*)"|'([^']*)'|([^\s-][^\s]*)))?/g;
|
|
82
83
|
|
|
83
84
|
for (const match of String(command).matchAll(pattern)) {
|
|
84
85
|
const value = match[2] ?? match[3] ?? match[4] ?? "(missing)";
|
|
@@ -193,6 +194,17 @@ async function doctorCommand(options = {}) {
|
|
|
193
194
|
console.log(" tokensToday and tokensWeek stay hidden until Node is updated.");
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
// Not a failure either: the bar shows English.
|
|
198
|
+
const language = configState.raw?.language;
|
|
199
|
+
if (language !== undefined && !matchLanguage(language)) {
|
|
200
|
+
const shown = String(language)
|
|
201
|
+
.replace(/[^A-Za-z0-9_-]/g, "?")
|
|
202
|
+
.slice(0, 32);
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(`${YELLOW} language "${shown}" isn't available${RESET}, so the bar shows English.`);
|
|
205
|
+
console.log(` Available: ${SUPPORTED_LANGUAGES.join(", ")}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
196
208
|
const breaking = cmdBreakingChars(installDir);
|
|
197
209
|
if (breaking.length) {
|
|
198
210
|
failed += 1;
|
package/src/cli/init.js
CHANGED
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
THEME_ORDER,
|
|
20
20
|
canonicalThemeId,
|
|
21
21
|
} = require("../lib/shared");
|
|
22
|
+
const { matchLanguage, SUPPORTED_LANGUAGES } = require("../lib/i18n");
|
|
22
23
|
const {
|
|
23
24
|
AGY_SETTINGS,
|
|
24
25
|
RUNTIME_FILES,
|
|
@@ -89,7 +90,7 @@ function unknownChoice(kind, value, ids) {
|
|
|
89
90
|
return null;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
// --preset, --layout and --
|
|
93
|
+
// --preset, --layout, --theme and --language, checked before anything is written. Returns
|
|
93
94
|
// the ids given ({} for none), or null after printing what's wrong.
|
|
94
95
|
function readChoiceFlags(options) {
|
|
95
96
|
const choices = {};
|
|
@@ -113,6 +114,16 @@ function readChoiceFlags(options) {
|
|
|
113
114
|
}
|
|
114
115
|
choices.theme = theme;
|
|
115
116
|
}
|
|
117
|
+
if (options.language !== undefined) {
|
|
118
|
+
const language = matchLanguage(options.language);
|
|
119
|
+
if (!language) {
|
|
120
|
+
console.error(`${RED}error${RESET} Unknown language "${options.language}".`);
|
|
121
|
+
console.error(` Available languages: ${SUPPORTED_LANGUAGES.join(", ")}`);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
choices.language = language;
|
|
126
|
+
}
|
|
116
127
|
return choices;
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -124,6 +135,9 @@ function applyChoices(config, choices) {
|
|
|
124
135
|
if (choices.theme) {
|
|
125
136
|
next.theme = choices.theme;
|
|
126
137
|
}
|
|
138
|
+
if (choices.language) {
|
|
139
|
+
next.language = choices.language;
|
|
140
|
+
}
|
|
127
141
|
return next;
|
|
128
142
|
}
|
|
129
143
|
|
package/src/index.js
CHANGED
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
} = require("./lib/shared");
|
|
29
29
|
const { STYLE_ORDER } = require("./lib/powerline");
|
|
30
30
|
const { GRADIENT_ORDER } = require("./lib/themes");
|
|
31
|
+
const { SUPPORTED_LANGUAGES } = require("./lib/i18n");
|
|
31
32
|
const { stripControl } = require("./lib/formatters");
|
|
32
33
|
const { resolveInstallDir, isTrustedInstallDir } = require("./lib/paths");
|
|
33
34
|
const { isLogEnabled, setLogDir, logError, jsonErrorMessage } = require("./lib/log");
|
|
@@ -75,6 +76,11 @@ const COMMANDS = [
|
|
|
75
76
|
value: "<id>",
|
|
76
77
|
help: "use this theme, or dark, light, colorblind (skips the question)",
|
|
77
78
|
},
|
|
79
|
+
language: {
|
|
80
|
+
type: "string",
|
|
81
|
+
value: "<code>",
|
|
82
|
+
help: `show the bar in this language (${SUPPORTED_LANGUAGES.join(", ")})`,
|
|
83
|
+
},
|
|
78
84
|
},
|
|
79
85
|
run: (_arg, opts) =>
|
|
80
86
|
initCommand({
|
|
@@ -232,6 +238,11 @@ const COMMANDS = [
|
|
|
232
238
|
value: "<name|colors>",
|
|
233
239
|
help: "use a gradient preset, hex stops, or off",
|
|
234
240
|
},
|
|
241
|
+
language: {
|
|
242
|
+
type: "string",
|
|
243
|
+
value: "<code>",
|
|
244
|
+
help: `use a language for this render (${SUPPORTED_LANGUAGES.join(", ")})`,
|
|
245
|
+
},
|
|
235
246
|
"dump-payload": {
|
|
236
247
|
type: "string",
|
|
237
248
|
value: "<path>",
|
|
@@ -384,6 +395,7 @@ function renderOverrideError(name, value) {
|
|
|
384
395
|
preset: ["preset", `Available presets: ${PRESET_ORDER.join(", ")}`],
|
|
385
396
|
caps: ["cap style", `Available styles: ${STYLE_ORDER.join(", ")}`],
|
|
386
397
|
gradient: ["gradient", `Available gradients: ${GRADIENT_ORDER.join(", ")}, off`],
|
|
398
|
+
language: ["language", `Available languages: ${SUPPORTED_LANGUAGES.join(", ")}`],
|
|
387
399
|
}[name];
|
|
388
400
|
console.error(`${RED}error${RESET} Unknown ${what} "${stripControl(String(value))}".`);
|
|
389
401
|
console.error(` ${list}`);
|
package/src/lib/config.js
CHANGED
|
@@ -8,6 +8,7 @@ const { stripControl, normalizeClock } = require("./formatters");
|
|
|
8
8
|
const { normalizePowerline, applyStyle, STYLES } = require("./powerline");
|
|
9
9
|
const { writeFileAtomic, PRIVATE_FILE_MODE } = require("./paths");
|
|
10
10
|
const { isMeterStyle } = require("./widget-style");
|
|
11
|
+
const { resolveLanguage, matchLanguage } = require("./i18n");
|
|
11
12
|
|
|
12
13
|
// Display text in widget configs is printed as-is, so strip terminal control
|
|
13
14
|
// from it. cmd/command are left alone: they are executed, not printed.
|
|
@@ -126,6 +127,7 @@ function defaultConfig() {
|
|
|
126
127
|
ascii: false,
|
|
127
128
|
color: "auto",
|
|
128
129
|
layout: "pills",
|
|
130
|
+
language: "en",
|
|
129
131
|
pacing: true,
|
|
130
132
|
minimalist: false,
|
|
131
133
|
rowGap: false,
|
|
@@ -185,6 +187,8 @@ function mergeConfig(raw) {
|
|
|
185
187
|
ascii: raw?.ascii === true,
|
|
186
188
|
color: resolveColorModeId(raw?.color ?? fallback.color),
|
|
187
189
|
layout: resolveLayoutId(raw?.layout ?? fallback.layout),
|
|
190
|
+
// A language without messages shows English (fr-FR uses fr when there is one).
|
|
191
|
+
language: resolveLanguage(raw?.language),
|
|
188
192
|
pacing: raw?.pacing !== false,
|
|
189
193
|
minimalist: raw?.minimalist === true,
|
|
190
194
|
rowGap: raw?.rowGap === true,
|
|
@@ -237,7 +241,7 @@ function readConfigFile() {
|
|
|
237
241
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
238
242
|
return { exists: true, config: defaultConfig(), error: "top-level value is not an object" };
|
|
239
243
|
}
|
|
240
|
-
return { exists: true, config: mergeConfig(raw) };
|
|
244
|
+
return { exists: true, config: mergeConfig(raw), raw };
|
|
241
245
|
} catch (err) {
|
|
242
246
|
// Newer Node quotes part of the file in the message; it may hold anything.
|
|
243
247
|
return { exists: true, config: defaultConfig(), error: stripControl(err.message) };
|
|
@@ -475,6 +479,15 @@ function applyRenderOverrides(baseConfig, overrides = {}) {
|
|
|
475
479
|
}
|
|
476
480
|
}
|
|
477
481
|
|
|
482
|
+
if (overrides.language !== undefined) {
|
|
483
|
+
const language = matchLanguage(overrides.language);
|
|
484
|
+
if (language) {
|
|
485
|
+
config = mergeConfig({ ...config, language });
|
|
486
|
+
} else {
|
|
487
|
+
invalid.push("language");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
478
491
|
return { config, invalid };
|
|
479
492
|
}
|
|
480
493
|
|
package/src/lib/exec.js
CHANGED
|
@@ -8,6 +8,7 @@ const { execSync } = require("child_process");
|
|
|
8
8
|
const { stripControl, sanitizeDeep } = require("./formatters");
|
|
9
9
|
const { RESET, getActiveDepth } = require("./colors");
|
|
10
10
|
const { logWarning } = require("./log");
|
|
11
|
+
const { t } = require("./i18n");
|
|
11
12
|
const { PRIVATE_FILE_MODE } = require("./paths");
|
|
12
13
|
|
|
13
14
|
const DEFAULT_TIMEOUT_MS = 500;
|
|
@@ -15,10 +16,6 @@ const DEFAULT_INTERVAL_SEC = 10;
|
|
|
15
16
|
const MAX_CACHE_AGE_SEC = 3600;
|
|
16
17
|
const MAX_OUTPUT_CHARS = 80;
|
|
17
18
|
|
|
18
|
-
// Placeholders shown instead of running the command.
|
|
19
|
-
const EXEC_PREVIEW_TEXT = "[cmd]";
|
|
20
|
-
const EXEC_BLOCKED_TEXT = "[blocked]";
|
|
21
|
-
|
|
22
19
|
const memoryCache = new Map();
|
|
23
20
|
|
|
24
21
|
function getCacheFilePath() {
|
|
@@ -233,7 +230,8 @@ function execWidgetOutput(widget, session = {}) {
|
|
|
233
230
|
return output;
|
|
234
231
|
}
|
|
235
232
|
|
|
236
|
-
|
|
233
|
+
// Placeholders shown instead of running the command.
|
|
234
|
+
return t(session.execMode === "blocked" ? "cmd_blocked" : "cmd_preview", session.lang);
|
|
237
235
|
}
|
|
238
236
|
|
|
239
237
|
function runExecWidget(widget, session = {}) {
|
|
@@ -349,6 +347,4 @@ module.exports = {
|
|
|
349
347
|
saveDiskCache,
|
|
350
348
|
DEFAULT_TIMEOUT_MS,
|
|
351
349
|
DEFAULT_INTERVAL_SEC,
|
|
352
|
-
EXEC_PREVIEW_TEXT,
|
|
353
|
-
EXEC_BLOCKED_TEXT,
|
|
354
350
|
};
|
package/src/lib/formatters.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
const os = require("os");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { rgb } = require("./colors");
|
|
6
|
+
const { t } = require("./i18n");
|
|
6
7
|
|
|
7
|
-
function formatReset(seconds) {
|
|
8
|
+
function formatReset(seconds, lang) {
|
|
8
9
|
seconds = Number(seconds);
|
|
9
10
|
|
|
10
11
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
11
|
-
return "now";
|
|
12
|
+
return t("now", lang);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const totalMinutes = Math.floor(seconds / 60);
|
|
@@ -17,23 +18,23 @@ function formatReset(seconds) {
|
|
|
17
18
|
const minutes = totalMinutes % 60;
|
|
18
19
|
|
|
19
20
|
if (days > 0) {
|
|
20
|
-
return `${days}
|
|
21
|
+
return `${days}${t("unit_d", lang)} ${hours}${t("unit_h", lang)}`;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
if (hours > 0) {
|
|
24
|
-
return `${hours}
|
|
25
|
+
return `${hours}${t("unit_hr", lang)} ${minutes}${t("unit_m", lang)}`;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
return `${Math.max(1, minutes)}
|
|
28
|
+
return `${Math.max(1, minutes)}${t("unit_m", lang)}`;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function formatSessionTime(startMs, nowMs = Date.now()) {
|
|
31
|
+
function formatSessionTime(startMs, nowMs = Date.now(), lang) {
|
|
31
32
|
const start = Number(startMs);
|
|
32
33
|
const now = Number(nowMs);
|
|
33
34
|
if (!Number.isFinite(start) || start <= 0 || !Number.isFinite(now) || now - start < 60000) {
|
|
34
35
|
return "";
|
|
35
36
|
}
|
|
36
|
-
return formatReset((now - start) / 1000);
|
|
37
|
+
return formatReset((now - start) / 1000, lang);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const MONTHS_SHORT = [
|
|
@@ -274,22 +275,22 @@ function shortModelName(name, ascii = false) {
|
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
// Session input and output tokens: "88.2K↑ 61.1K↓", or "88.2K in 61.1K out".
|
|
277
|
-
function formatTokenSplit(input, output, ascii = false) {
|
|
278
|
+
function formatTokenSplit(input, output, ascii = false, lang) {
|
|
278
279
|
return ascii
|
|
279
|
-
? `${formatTokens(input)}
|
|
280
|
+
? `${formatTokens(input)} ${t("tokens_in", lang)} ${formatTokens(output)} ${t("tokens_out", lang)}`
|
|
280
281
|
: `${formatTokens(input)}↑ ${formatTokens(output)}↓`;
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
// The usage widgets' text: "1.2M", or "1.0M↑ 245.3K↓" with "split", plus the
|
|
284
285
|
// share of a "budget" when one is set ("1.2M (60%)"). null with no tokens.
|
|
285
|
-
function formatUsage(tokens, widget = {}, ascii = false) {
|
|
286
|
+
function formatUsage(tokens, widget = {}, ascii = false, lang) {
|
|
286
287
|
const input = Number(tokens?.input) || 0;
|
|
287
288
|
const output = Number(tokens?.output) || 0;
|
|
288
289
|
const total = input + output;
|
|
289
290
|
if (!(total > 0)) {
|
|
290
291
|
return null;
|
|
291
292
|
}
|
|
292
|
-
const value = widget?.split ? formatTokenSplit(input, output, ascii) : formatTokens(total);
|
|
293
|
+
const value = widget?.split ? formatTokenSplit(input, output, ascii, lang) : formatTokens(total);
|
|
293
294
|
const budget = widget?.budget;
|
|
294
295
|
const pct =
|
|
295
296
|
typeof budget === "number" && Number.isFinite(budget) && budget > 0
|
|
@@ -620,9 +621,11 @@ function formatGitOperation(op) {
|
|
|
620
621
|
}
|
|
621
622
|
|
|
622
623
|
// "2 subagents, 1 task", or "" when nothing runs.
|
|
623
|
-
function formatTasks(subagents, tasks) {
|
|
624
|
-
const part = (n,
|
|
625
|
-
return [part(subagents, "subagent"), part(tasks, "task"
|
|
624
|
+
function formatTasks(subagents, tasks, lang) {
|
|
625
|
+
const part = (n, one, many) => (n > 0 ? `${n} ${t(n === 1 ? one : many, lang)}` : "");
|
|
626
|
+
return [part(subagents, "subagent", "subagents"), part(tasks, "task", "tasks")]
|
|
627
|
+
.filter(Boolean)
|
|
628
|
+
.join(", ");
|
|
626
629
|
}
|
|
627
630
|
|
|
628
631
|
// "accept-edits, NORMAL", or "" in the default mode with Vim editing off.
|
package/src/lib/i18n.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// The bar's own words: widget tags, captions and units. Payload text (model,
|
|
4
|
+
// branch, paths, agent state), git operation names, numbers and dates are
|
|
5
|
+
// never translated; dates follow clock.locale instead.
|
|
6
|
+
const MESSAGES = {
|
|
7
|
+
en: {
|
|
8
|
+
tag_model: "Model",
|
|
9
|
+
tag_quota: "Quota",
|
|
10
|
+
tag_5h: "5h",
|
|
11
|
+
tag_7d: "7d",
|
|
12
|
+
tag_reset: "Reset",
|
|
13
|
+
tag_3p_5h: "3P 5h",
|
|
14
|
+
tag_3p_7d: "3P 7d",
|
|
15
|
+
tag_argon_7d: "Argon 7d",
|
|
16
|
+
tag_argon_reset: "Argon reset",
|
|
17
|
+
tag_context: "Context",
|
|
18
|
+
tag_dir: "Dir",
|
|
19
|
+
tag_project: "Project",
|
|
20
|
+
tag_host: "Host",
|
|
21
|
+
tag_lines: "Lines",
|
|
22
|
+
tag_files: "Files",
|
|
23
|
+
tag_commit: "Commit",
|
|
24
|
+
tag_stash: "Stash",
|
|
25
|
+
tag_worktree: "Worktree",
|
|
26
|
+
tag_plan: "Plan",
|
|
27
|
+
tag_agent: "Agent",
|
|
28
|
+
tag_artifacts: "Artifacts",
|
|
29
|
+
tag_session: "Session",
|
|
30
|
+
tag_ram: "RAM",
|
|
31
|
+
tag_cache_read: "Cache read",
|
|
32
|
+
tag_cache_create: "Cache create",
|
|
33
|
+
tag_cache_hit: "Cache hit",
|
|
34
|
+
tag_tokens: "Tokens",
|
|
35
|
+
tag_clock: "Clock",
|
|
36
|
+
tag_today: "Today",
|
|
37
|
+
|
|
38
|
+
rem: "rem",
|
|
39
|
+
over_200k: ">200k",
|
|
40
|
+
now: "now",
|
|
41
|
+
unit_d: "d",
|
|
42
|
+
unit_h: "h",
|
|
43
|
+
unit_hr: "hr",
|
|
44
|
+
unit_m: "m",
|
|
45
|
+
subagent: "subagent",
|
|
46
|
+
subagents: "subagents",
|
|
47
|
+
task: "task",
|
|
48
|
+
tasks: "tasks",
|
|
49
|
+
tokens_in: "in",
|
|
50
|
+
tokens_out: "out",
|
|
51
|
+
sandbox_net: "sandbox (net)",
|
|
52
|
+
sandbox_isolated: "sandbox (isolated)",
|
|
53
|
+
cmd_preview: "[cmd]",
|
|
54
|
+
cmd_blocked: "[blocked]",
|
|
55
|
+
},
|
|
56
|
+
fr: {
|
|
57
|
+
tag_model: "Modèle",
|
|
58
|
+
tag_quota: "Quota",
|
|
59
|
+
tag_5h: "5h",
|
|
60
|
+
tag_7d: "7j",
|
|
61
|
+
tag_reset: "Réinit",
|
|
62
|
+
tag_3p_5h: "Tiers 5h",
|
|
63
|
+
tag_3p_7d: "Tiers 7j",
|
|
64
|
+
tag_argon_7d: "Argon 7j",
|
|
65
|
+
tag_argon_reset: "Réinit Argon",
|
|
66
|
+
tag_context: "Contexte",
|
|
67
|
+
tag_dir: "Dossier",
|
|
68
|
+
tag_project: "Projet",
|
|
69
|
+
tag_host: "Hôte",
|
|
70
|
+
tag_lines: "Lignes",
|
|
71
|
+
tag_files: "Fichiers",
|
|
72
|
+
tag_commit: "Commit",
|
|
73
|
+
tag_stash: "Stash",
|
|
74
|
+
tag_worktree: "Worktree",
|
|
75
|
+
tag_plan: "Offre",
|
|
76
|
+
tag_agent: "Agent",
|
|
77
|
+
tag_artifacts: "Artefacts",
|
|
78
|
+
tag_session: "Session",
|
|
79
|
+
tag_ram: "RAM",
|
|
80
|
+
tag_cache_read: "Cache lu",
|
|
81
|
+
tag_cache_create: "Cache écrit",
|
|
82
|
+
tag_cache_hit: "Succès cache",
|
|
83
|
+
tag_tokens: "Jetons",
|
|
84
|
+
tag_clock: "Heure",
|
|
85
|
+
tag_today: "Aujourd'hui",
|
|
86
|
+
|
|
87
|
+
rem: "rest.",
|
|
88
|
+
over_200k: ">200k",
|
|
89
|
+
now: "maintenant",
|
|
90
|
+
unit_d: "j",
|
|
91
|
+
unit_h: "h",
|
|
92
|
+
unit_hr: "h",
|
|
93
|
+
unit_m: "m",
|
|
94
|
+
subagent: "sous-agent",
|
|
95
|
+
subagents: "sous-agents",
|
|
96
|
+
task: "tâche",
|
|
97
|
+
tasks: "tâches",
|
|
98
|
+
tokens_in: "entrée",
|
|
99
|
+
tokens_out: "sortie",
|
|
100
|
+
sandbox_net: "sandbox (réseau)",
|
|
101
|
+
sandbox_isolated: "sandbox (isolée)",
|
|
102
|
+
cmd_preview: "[cmd]",
|
|
103
|
+
cmd_blocked: "[bloqué]",
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const DEFAULT_LANGUAGE = "en";
|
|
108
|
+
const SUPPORTED_LANGUAGES = Object.keys(MESSAGES);
|
|
109
|
+
|
|
110
|
+
// The supported code a setting asks for: the code itself (any case), then its
|
|
111
|
+
// base language (fr-FR -> fr), or null.
|
|
112
|
+
function matchLanguage(code) {
|
|
113
|
+
if (typeof code !== "string") {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const wanted = code.trim().toLowerCase();
|
|
117
|
+
const base = wanted.split("-")[0];
|
|
118
|
+
return (
|
|
119
|
+
SUPPORTED_LANGUAGES.find((id) => id.toLowerCase() === wanted) ??
|
|
120
|
+
SUPPORTED_LANGUAGES.find((id) => id.toLowerCase() === base) ??
|
|
121
|
+
null
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveLanguage(code) {
|
|
126
|
+
return matchLanguage(code) ?? DEFAULT_LANGUAGE;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function t(key, lang = DEFAULT_LANGUAGE) {
|
|
130
|
+
return MESSAGES[lang]?.[key] ?? MESSAGES[DEFAULT_LANGUAGE][key] ?? key;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
MESSAGES,
|
|
135
|
+
DEFAULT_LANGUAGE,
|
|
136
|
+
SUPPORTED_LANGUAGES,
|
|
137
|
+
matchLanguage,
|
|
138
|
+
resolveLanguage,
|
|
139
|
+
t,
|
|
140
|
+
};
|
package/src/lib/layouts/lines.js
CHANGED
|
@@ -56,6 +56,7 @@ const {
|
|
|
56
56
|
RAM_DANGER,
|
|
57
57
|
TITLE_MAX,
|
|
58
58
|
} = require("../widget-style");
|
|
59
|
+
const { t } = require("../i18n");
|
|
59
60
|
|
|
60
61
|
const LINES = NAMED_COLORS;
|
|
61
62
|
|
|
@@ -202,7 +203,7 @@ function rampColor(at, palette) {
|
|
|
202
203
|
function buildLinesSegment(id, session, palette, emojis, ascii = false, widget = {}, config = {}) {
|
|
203
204
|
// Raw widgets keep only their value: no icon, no label.
|
|
204
205
|
const raw = isRaw(widget, config);
|
|
205
|
-
const tag = (icon) => widgetTag(id, { emojis, raw, icon, widget, ascii });
|
|
206
|
+
const tag = (icon) => widgetTag(id, { emojis, raw, icon, widget, ascii, lang: session.lang });
|
|
206
207
|
// Meters: 25 cells, or 12 when the bar is fitted to a narrow terminal;
|
|
207
208
|
// Classic draws the same cells as blocks.
|
|
208
209
|
const classic = config.layout === "classic";
|
|
@@ -214,7 +215,8 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
214
215
|
classic,
|
|
215
216
|
elapsedPct: isPaced(widget, config) ? quota.elapsedPct : null,
|
|
216
217
|
});
|
|
217
|
-
const over200k = session.exceeds200k ?
|
|
218
|
+
const over200k = session.exceeds200k ? ` ${t("over_200k", session.lang)}` : "";
|
|
219
|
+
const remWord = t("rem", session.lang);
|
|
218
220
|
|
|
219
221
|
function quotaState(quota) {
|
|
220
222
|
const thresh = resolveQuotaThresholds(widget, config);
|
|
@@ -235,14 +237,16 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
235
237
|
renderDotMeter(rem, dots, ascii, palette, true, pace(quota, thresh)) +
|
|
236
238
|
" " +
|
|
237
239
|
fg(color) +
|
|
238
|
-
`${rem.toFixed(1)}%
|
|
240
|
+
`${rem.toFixed(1)}% ${remWord}` +
|
|
239
241
|
RESET
|
|
240
242
|
);
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
function quotaPct(quota) {
|
|
244
246
|
const { rem, blinkSeq, color } = quotaState(quota);
|
|
245
|
-
return
|
|
247
|
+
return (
|
|
248
|
+
blinkSeq + fg(palette.text) + tag() + fg(color) + `${rem.toFixed(1)}% ${remWord}` + RESET
|
|
249
|
+
);
|
|
246
250
|
}
|
|
247
251
|
|
|
248
252
|
function quotaReset(quota, weekly) {
|
|
@@ -307,7 +311,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
307
311
|
tag() +
|
|
308
312
|
`${quota.label} ` +
|
|
309
313
|
fg(color) +
|
|
310
|
-
`${rem.toFixed(1)}%
|
|
314
|
+
`${rem.toFixed(1)}% ${remWord}` +
|
|
311
315
|
fg(palette.text) +
|
|
312
316
|
reset +
|
|
313
317
|
RESET
|
|
@@ -404,7 +408,8 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
404
408
|
const dirname = limitWidth(
|
|
405
409
|
shortenPath(homePath(session.cwd), widget?.depth, ascii),
|
|
406
410
|
widget,
|
|
407
|
-
ascii
|
|
411
|
+
ascii,
|
|
412
|
+
session.lang
|
|
408
413
|
);
|
|
409
414
|
if (!dirname) {
|
|
410
415
|
return null;
|
|
@@ -518,7 +523,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
518
523
|
return fg(palette.text) + tag() + String(session.artifacts) + RESET;
|
|
519
524
|
|
|
520
525
|
case "tasks": {
|
|
521
|
-
const text = formatTasks(session.subagents, session.tasks);
|
|
526
|
+
const text = formatTasks(session.subagents, session.tasks, session.lang);
|
|
522
527
|
if (!text) {
|
|
523
528
|
return null;
|
|
524
529
|
}
|
|
@@ -543,7 +548,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
543
548
|
return fg(palette.text) + tag() + limitWidth(session.title, widget, ascii, TITLE_MAX) + RESET;
|
|
544
549
|
|
|
545
550
|
case "sessionTime": {
|
|
546
|
-
const text = formatSessionTime(session.sessionStartMs);
|
|
551
|
+
const text = formatSessionTime(session.sessionStartMs, Date.now(), session.lang);
|
|
547
552
|
return text ? fg(palette.text) + tag() + text + RESET : null;
|
|
548
553
|
}
|
|
549
554
|
|
|
@@ -553,7 +558,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
553
558
|
}
|
|
554
559
|
const net = session.sandboxNet;
|
|
555
560
|
const color = net ? palette.quotaMed || palette.orange || LINES.orange : palette.git;
|
|
556
|
-
const text = net ? "
|
|
561
|
+
const text = t(net ? "sandbox_net" : "sandbox_isolated", session.lang);
|
|
557
562
|
return fg(color) + tag(net ? "🌐" : undefined) + text + RESET;
|
|
558
563
|
}
|
|
559
564
|
|
|
@@ -607,7 +612,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
607
612
|
fg(palette.text) +
|
|
608
613
|
tag() +
|
|
609
614
|
(widget?.split
|
|
610
|
-
? formatTokenSplit(session.inputTokens, session.outputTokens, ascii)
|
|
615
|
+
? formatTokenSplit(session.inputTokens, session.outputTokens, ascii, session.lang)
|
|
611
616
|
: formatTokens(session.totalTokens)) +
|
|
612
617
|
RESET
|
|
613
618
|
);
|
|
@@ -617,7 +622,8 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
617
622
|
const usage = formatUsage(
|
|
618
623
|
session.usage?.[id === "tokensToday" ? "today" : "week"],
|
|
619
624
|
widget,
|
|
620
|
-
ascii
|
|
625
|
+
ascii,
|
|
626
|
+
session.lang
|
|
621
627
|
);
|
|
622
628
|
if (!usage) {
|
|
623
629
|
return null;
|
package/src/lib/layouts/pills.js
CHANGED
|
@@ -62,6 +62,7 @@ const {
|
|
|
62
62
|
RAM_DANGER,
|
|
63
63
|
TITLE_MAX,
|
|
64
64
|
} = require("../widget-style");
|
|
65
|
+
const { t } = require("../i18n");
|
|
65
66
|
|
|
66
67
|
const WHITE = [255, 255, 255];
|
|
67
68
|
|
|
@@ -174,14 +175,15 @@ function pill(color, text, blink = false) {
|
|
|
174
175
|
function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, config = {}) {
|
|
175
176
|
// Raw widgets keep only their value: no icon, no label.
|
|
176
177
|
const raw = isRaw(widget, config);
|
|
177
|
-
const tag = (icon) => widgetTag(id, { emojis, raw, icon, widget, ascii });
|
|
178
|
+
const tag = (icon) => widgetTag(id, { emojis, raw, icon, widget, ascii, lang: session.lang });
|
|
178
179
|
const lbl = (value, icon) => ` ${tag(icon)}${value} `;
|
|
179
180
|
// The pill frames a colored meter; plain-text meters (ASCII) get brackets.
|
|
180
181
|
const open = ascii ? "[" : "";
|
|
181
182
|
const close = ascii ? "]" : "";
|
|
182
183
|
const meterLabel = () => ` ${tag()}${open}`;
|
|
183
184
|
const pace = (quota) => (isPaced(widget, config) ? quota.elapsedPct : null);
|
|
184
|
-
const over200k = session.exceeds200k ?
|
|
185
|
+
const over200k = session.exceeds200k ? ` ${t("over_200k", session.lang)}` : "";
|
|
186
|
+
const remWord = t("rem", session.lang);
|
|
185
187
|
|
|
186
188
|
if (hasNoData(id, session) || hiddenByThreshold(id, widget, session)) {
|
|
187
189
|
return null;
|
|
@@ -222,7 +224,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
222
224
|
warnColor: colors.caution,
|
|
223
225
|
});
|
|
224
226
|
const reset = quota.reset ? `${ascii ? ", " : " · "}${quota.reset}` : "";
|
|
225
|
-
return pill(color, lbl(`${quota.label} ${rem.toFixed(1)}%
|
|
227
|
+
return pill(color, lbl(`${quota.label} ${rem.toFixed(1)}% ${remWord}${reset}`), blink);
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
case "fiveHourBar": {
|
|
@@ -243,7 +245,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
243
245
|
color,
|
|
244
246
|
rem,
|
|
245
247
|
meterLabel(),
|
|
246
|
-
`${close} ${rem.toFixed(1)}%
|
|
248
|
+
`${close} ${rem.toFixed(1)}% ${remWord} `,
|
|
247
249
|
true,
|
|
248
250
|
blink,
|
|
249
251
|
pace(session.fiveHour)
|
|
@@ -264,7 +266,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
264
266
|
critColor: colors.warn,
|
|
265
267
|
warnColor: colors.caution,
|
|
266
268
|
});
|
|
267
|
-
return pill(color, lbl(`${rem.toFixed(1)}%
|
|
269
|
+
return pill(color, lbl(`${rem.toFixed(1)}% ${remWord}`), blink);
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
case "fiveHourReset": {
|
|
@@ -300,7 +302,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
300
302
|
color,
|
|
301
303
|
rem,
|
|
302
304
|
meterLabel(),
|
|
303
|
-
`${close} ${rem.toFixed(1)}%
|
|
305
|
+
`${close} ${rem.toFixed(1)}% ${remWord} `,
|
|
304
306
|
true,
|
|
305
307
|
blink,
|
|
306
308
|
pace(session.weekly)
|
|
@@ -321,7 +323,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
321
323
|
critColor: colors.warn,
|
|
322
324
|
warnColor: colors.caution,
|
|
323
325
|
});
|
|
324
|
-
return pill(color, lbl(`${rem.toFixed(1)}%
|
|
326
|
+
return pill(color, lbl(`${rem.toFixed(1)}% ${remWord}`), blink);
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
case "weeklyReset": {
|
|
@@ -358,7 +360,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
358
360
|
color,
|
|
359
361
|
rem,
|
|
360
362
|
meterLabel(),
|
|
361
|
-
`${close} ${rem.toFixed(1)}%
|
|
363
|
+
`${close} ${rem.toFixed(1)}% ${remWord} `,
|
|
362
364
|
true,
|
|
363
365
|
blink,
|
|
364
366
|
pace(session.thirdPartyFiveHour)
|
|
@@ -383,7 +385,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
383
385
|
color,
|
|
384
386
|
rem,
|
|
385
387
|
meterLabel(),
|
|
386
|
-
`${close} ${rem.toFixed(1)}%
|
|
388
|
+
`${close} ${rem.toFixed(1)}% ${remWord} `,
|
|
387
389
|
true,
|
|
388
390
|
blink,
|
|
389
391
|
pace(session.thirdPartyWeekly)
|
|
@@ -410,7 +412,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
410
412
|
color,
|
|
411
413
|
rem,
|
|
412
414
|
meterLabel(),
|
|
413
|
-
`${close} ${rem.toFixed(1)}%
|
|
415
|
+
`${close} ${rem.toFixed(1)}% ${remWord} `,
|
|
414
416
|
true,
|
|
415
417
|
blink,
|
|
416
418
|
pace(session.argonWeekly)
|
|
@@ -497,7 +499,8 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
497
499
|
const name = limitWidth(
|
|
498
500
|
shortenPath(homePath(session.cwd), widget?.depth, ascii),
|
|
499
501
|
widget,
|
|
500
|
-
ascii
|
|
502
|
+
ascii,
|
|
503
|
+
session.lang
|
|
501
504
|
);
|
|
502
505
|
if (!name) {
|
|
503
506
|
return null;
|
|
@@ -603,7 +606,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
603
606
|
return pill(colors.meta, lbl(String(session.artifacts)));
|
|
604
607
|
|
|
605
608
|
case "tasks": {
|
|
606
|
-
const text = formatTasks(session.subagents, session.tasks);
|
|
609
|
+
const text = formatTasks(session.subagents, session.tasks, session.lang);
|
|
607
610
|
if (!text) {
|
|
608
611
|
return null;
|
|
609
612
|
}
|
|
@@ -627,7 +630,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
627
630
|
return pill(colors.meta, lbl(limitWidth(session.title, widget, ascii, TITLE_MAX)));
|
|
628
631
|
|
|
629
632
|
case "sessionTime": {
|
|
630
|
-
const text = formatSessionTime(session.sessionStartMs);
|
|
633
|
+
const text = formatSessionTime(session.sessionStartMs, Date.now(), session.lang);
|
|
631
634
|
return text ? pill(colors.meta, lbl(text)) : null;
|
|
632
635
|
}
|
|
633
636
|
|
|
@@ -653,7 +656,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
653
656
|
return null;
|
|
654
657
|
}
|
|
655
658
|
const net = session.sandboxNet;
|
|
656
|
-
const text = net ? "
|
|
659
|
+
const text = t(net ? "sandbox_net" : "sandbox_isolated", session.lang);
|
|
657
660
|
const color = net ? colors.warn : colors.git;
|
|
658
661
|
return pill(color, lbl(text, net ? "🌐" : undefined));
|
|
659
662
|
}
|
|
@@ -683,7 +686,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
683
686
|
colors.meta,
|
|
684
687
|
lbl(
|
|
685
688
|
widget?.split
|
|
686
|
-
? formatTokenSplit(session.inputTokens, session.outputTokens, ascii)
|
|
689
|
+
? formatTokenSplit(session.inputTokens, session.outputTokens, ascii, session.lang)
|
|
687
690
|
: formatTokens(session.totalTokens)
|
|
688
691
|
)
|
|
689
692
|
);
|
|
@@ -693,7 +696,8 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
693
696
|
const usage = formatUsage(
|
|
694
697
|
session.usage?.[id === "tokensToday" ? "today" : "week"],
|
|
695
698
|
widget,
|
|
696
|
-
ascii
|
|
699
|
+
ascii,
|
|
700
|
+
session.lang
|
|
697
701
|
);
|
|
698
702
|
if (!usage) {
|
|
699
703
|
return null;
|
package/src/lib/paths.js
CHANGED
package/src/lib/session.js
CHANGED
|
@@ -8,20 +8,21 @@ const { resolveGit, resolveGitDetails } = require("./git");
|
|
|
8
8
|
const { resolveUsage, conversationsDir } = require("./usage");
|
|
9
9
|
const { isWsl, resolveInstallDir, writeFileAtomic, PRIVATE_FILE_MODE } = require("./paths");
|
|
10
10
|
const { logWarning } = require("./log");
|
|
11
|
+
const { resolveLanguage, t } = require("./i18n");
|
|
11
12
|
|
|
12
13
|
const FIVE_HOURS = 5 * 3600;
|
|
13
14
|
const SEVEN_DAYS = 7 * 86400;
|
|
14
15
|
const ARGON_BUCKET = "gemini-argon-wtu-weekly";
|
|
15
|
-
// The buckets Antigravity sends, with the short
|
|
16
|
+
// The buckets Antigravity sends, with the message key of the short name the bar shows.
|
|
16
17
|
const QUOTA_BUCKETS = {
|
|
17
|
-
"gemini-5h": {
|
|
18
|
-
"gemini-weekly": {
|
|
18
|
+
"gemini-5h": { key: "tag_5h", window: FIVE_HOURS },
|
|
19
|
+
"gemini-weekly": { key: "tag_7d", window: SEVEN_DAYS },
|
|
19
20
|
// Gemini 4 Argon on Google AI Pro: a weekly quota of its own, with no 5-hour
|
|
20
21
|
// window, sent whatever model is in use. On Ultra Argon counts toward the
|
|
21
22
|
// Gemini buckets and this one isn't sent.
|
|
22
|
-
[ARGON_BUCKET]: {
|
|
23
|
-
"3p-5h": {
|
|
24
|
-
"3p-weekly": {
|
|
23
|
+
[ARGON_BUCKET]: { key: "tag_argon_7d", window: SEVEN_DAYS },
|
|
24
|
+
"3p-5h": { key: "tag_3p_5h", window: FIVE_HOURS },
|
|
25
|
+
"3p-weekly": { key: "tag_3p_7d", window: SEVEN_DAYS },
|
|
25
26
|
};
|
|
26
27
|
// No quota window lasts a year: a reset further out is a broken value.
|
|
27
28
|
const MAX_RESET_SECONDS = 366 * 86400;
|
|
@@ -189,7 +190,7 @@ function percent(value) {
|
|
|
189
190
|
return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : 0;
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
function readQuota(data, key, windowSeconds) {
|
|
193
|
+
function readQuota(data, key, windowSeconds, lang) {
|
|
193
194
|
const quota = data?.quota?.[key];
|
|
194
195
|
|
|
195
196
|
if (!quota || typeof quota !== "object") {
|
|
@@ -221,7 +222,7 @@ function readQuota(data, key, windowSeconds) {
|
|
|
221
222
|
|
|
222
223
|
return {
|
|
223
224
|
remaining,
|
|
224
|
-
reset: hasReset ? formatReset(resetSeconds) : "",
|
|
225
|
+
reset: hasReset ? formatReset(resetSeconds, lang) : "",
|
|
225
226
|
resetSeconds,
|
|
226
227
|
elapsedPct,
|
|
227
228
|
resetTime: resetValid && quota.reset_time ? String(quota.reset_time) : "",
|
|
@@ -245,15 +246,15 @@ function bucketGroup(key) {
|
|
|
245
246
|
// (third-party) buckets for the others. Argon falls back to the Gemini
|
|
246
247
|
// buckets, where its usage counts when there is no Argon bucket. Without a
|
|
247
248
|
// matching bucket every bucket counts. Disabled buckets never do.
|
|
248
|
-
function tightestQuota(data) {
|
|
249
|
+
function tightestQuota(data, lang) {
|
|
249
250
|
const buckets = data?.quota && typeof data.quota === "object" ? data.quota : {};
|
|
250
251
|
const usable = [];
|
|
251
252
|
|
|
252
253
|
for (const key of Object.keys(buckets)) {
|
|
253
254
|
const known = QUOTA_BUCKETS[key];
|
|
254
|
-
const quota = readQuota(data, key, known ? known.window : 0);
|
|
255
|
+
const quota = readQuota(data, key, known ? known.window : 0, lang);
|
|
255
256
|
if (quota && !quota.disabled) {
|
|
256
|
-
usable.push({ ...quota, key, label: known ? known.
|
|
257
|
+
usable.push({ ...quota, key, label: known ? t(known.key, lang) : key.slice(0, 20) });
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
@@ -303,6 +304,8 @@ function contextUsedPercent(data) {
|
|
|
303
304
|
// options.readUsage: the live statusline may read conversation metadata and
|
|
304
305
|
// databases; previews use the usage cache and a synthetic sample start time.
|
|
305
306
|
function extractSession(data, config, options = {}) {
|
|
307
|
+
// The bar's own words follow config.language; payload text never changes.
|
|
308
|
+
const lang = resolveLanguage(config?.language);
|
|
306
309
|
const git = resolveGit(data, config);
|
|
307
310
|
const gitDetails = resolveGitDetails(data, config);
|
|
308
311
|
// Once git status has been read for gitFiles, the branch's dirty marker can
|
|
@@ -361,6 +364,7 @@ function extractSession(data, config, options = {}) {
|
|
|
361
364
|
}
|
|
362
365
|
|
|
363
366
|
return {
|
|
367
|
+
lang,
|
|
364
368
|
os: osName,
|
|
365
369
|
ramPct,
|
|
366
370
|
username: username || "",
|
|
@@ -380,12 +384,12 @@ function extractSession(data, config, options = {}) {
|
|
|
380
384
|
exceeds200k: data.exceeds_200k_tokens === true,
|
|
381
385
|
cacheRead: count(data.context_window?.current_usage?.cache_read_input_tokens),
|
|
382
386
|
cacheCreate: count(data.context_window?.current_usage?.cache_creation_input_tokens),
|
|
383
|
-
fiveHour: readQuota(data, "gemini-5h", FIVE_HOURS),
|
|
384
|
-
weekly: readQuota(data, "gemini-weekly", SEVEN_DAYS),
|
|
385
|
-
thirdPartyFiveHour: readQuota(data, "3p-5h", FIVE_HOURS),
|
|
386
|
-
thirdPartyWeekly: readQuota(data, "3p-weekly", SEVEN_DAYS),
|
|
387
|
-
argonWeekly: readQuota(data, ARGON_BUCKET, SEVEN_DAYS),
|
|
388
|
-
tightestQuota: tightestQuota(data),
|
|
387
|
+
fiveHour: readQuota(data, "gemini-5h", FIVE_HOURS, lang),
|
|
388
|
+
weekly: readQuota(data, "gemini-weekly", SEVEN_DAYS, lang),
|
|
389
|
+
thirdPartyFiveHour: readQuota(data, "3p-5h", FIVE_HOURS, lang),
|
|
390
|
+
thirdPartyWeekly: readQuota(data, "3p-weekly", SEVEN_DAYS, lang),
|
|
391
|
+
argonWeekly: readQuota(data, ARGON_BUCKET, SEVEN_DAYS, lang),
|
|
392
|
+
tightestQuota: tightestQuota(data, lang),
|
|
389
393
|
cwd: firstText(data.cwd, data.workspace?.current_dir),
|
|
390
394
|
project: firstText(data.workspace?.project_dir),
|
|
391
395
|
plan: firstText(data.plan_tier),
|
package/src/lib/widget-style.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { parseColor } = require("./colors");
|
|
4
4
|
const { clipText, stripControl, visibleWidth } = require("./formatters");
|
|
5
|
+
const { DEFAULT_LANGUAGE, t } = require("./i18n");
|
|
5
6
|
|
|
6
7
|
// Basic names the exec widget's color option always accepted; the Lines
|
|
7
8
|
// layout also uses them as its fallback palette.
|
|
@@ -82,51 +83,52 @@ function isPaced(widget, config) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// Each widget's icon and label, shared by both layouts so Pills and Lines
|
|
85
|
-
// read the same.
|
|
86
|
+
// read the same. name is the English label (configure shows it), key its
|
|
87
|
+
// message in i18n.js. iconOnly: with emojis on, the icon stands in for the label
|
|
86
88
|
// (a meter or the model name already says what it is).
|
|
87
89
|
const WIDGET_TAGS = {
|
|
88
|
-
model: { icon: "🤖", name: "Model", iconOnly: true },
|
|
89
|
-
quota: { icon: "⏳", name: "Quota" },
|
|
90
|
-
fiveHourBar: { icon: "⏳", name: "5h", iconOnly: true },
|
|
91
|
-
fiveHourPct: { icon: "⏳", name: "5h" },
|
|
92
|
-
fiveHourReset: { icon: "🔄", name: "Reset" },
|
|
93
|
-
weeklyBar: { icon: "📅", name: "7d", iconOnly: true },
|
|
94
|
-
weeklyPct: { icon: "📅", name: "7d" },
|
|
95
|
-
weeklyReset: { icon: "🔄", name: "Reset" },
|
|
96
|
-
thirdPartyFiveHour: { icon: "🧪", name: "3P 5h" },
|
|
97
|
-
thirdPartyWeekly: { icon: "🧪", name: "3P 7d" },
|
|
98
|
-
argonWeekly: { icon: "🔮", name: "Argon 7d" },
|
|
99
|
-
argonReset: { icon: "🔄", name: "Argon reset" },
|
|
100
|
-
context: { icon: "💾", name: "Context", iconOnly: true },
|
|
101
|
-
contextPct: { icon: "💾", name: "Context" },
|
|
102
|
-
contextTokens: { icon: "💾", name: "Context" },
|
|
103
|
-
cwd: { icon: "📂", name: "Dir" },
|
|
104
|
-
project: { icon: "📦", name: "Project" },
|
|
90
|
+
model: { icon: "🤖", name: "Model", key: "tag_model", iconOnly: true },
|
|
91
|
+
quota: { icon: "⏳", name: "Quota", key: "tag_quota" },
|
|
92
|
+
fiveHourBar: { icon: "⏳", name: "5h", key: "tag_5h", iconOnly: true },
|
|
93
|
+
fiveHourPct: { icon: "⏳", name: "5h", key: "tag_5h" },
|
|
94
|
+
fiveHourReset: { icon: "🔄", name: "Reset", key: "tag_reset" },
|
|
95
|
+
weeklyBar: { icon: "📅", name: "7d", key: "tag_7d", iconOnly: true },
|
|
96
|
+
weeklyPct: { icon: "📅", name: "7d", key: "tag_7d" },
|
|
97
|
+
weeklyReset: { icon: "🔄", name: "Reset", key: "tag_reset" },
|
|
98
|
+
thirdPartyFiveHour: { icon: "🧪", name: "3P 5h", key: "tag_3p_5h" },
|
|
99
|
+
thirdPartyWeekly: { icon: "🧪", name: "3P 7d", key: "tag_3p_7d" },
|
|
100
|
+
argonWeekly: { icon: "🔮", name: "Argon 7d", key: "tag_argon_7d" },
|
|
101
|
+
argonReset: { icon: "🔄", name: "Argon reset", key: "tag_argon_reset" },
|
|
102
|
+
context: { icon: "💾", name: "Context", key: "tag_context", iconOnly: true },
|
|
103
|
+
contextPct: { icon: "💾", name: "Context", key: "tag_context" },
|
|
104
|
+
contextTokens: { icon: "💾", name: "Context", key: "tag_context" },
|
|
105
|
+
cwd: { icon: "📂", name: "Dir", key: "tag_dir" },
|
|
106
|
+
project: { icon: "📦", name: "Project", key: "tag_project" },
|
|
105
107
|
os: { icon: "💻", name: "" },
|
|
106
|
-
host: { icon: "👤", name: "Host", iconOnly: true },
|
|
108
|
+
host: { icon: "👤", name: "Host", key: "tag_host", iconOnly: true },
|
|
107
109
|
git: { icon: "🌿", name: "" },
|
|
108
|
-
gitChanges: { icon: "📝", name: "Lines" },
|
|
109
|
-
gitFiles: { icon: "📄", name: "Files" },
|
|
110
|
-
gitSha: { icon: "📌", name: "Commit" },
|
|
111
|
-
gitStash: { icon: "💼", name: "Stash" },
|
|
112
|
-
gitWorktree: { icon: "🌳", name: "Worktree" },
|
|
113
|
-
plan: { icon: "⭐", name: "Plan" },
|
|
114
|
-
agentState: { icon: "🟣", name: "Agent" },
|
|
115
|
-
artifacts: { icon: "📎", name: "Artifacts" },
|
|
110
|
+
gitChanges: { icon: "📝", name: "Lines", key: "tag_lines" },
|
|
111
|
+
gitFiles: { icon: "📄", name: "Files", key: "tag_files" },
|
|
112
|
+
gitSha: { icon: "📌", name: "Commit", key: "tag_commit" },
|
|
113
|
+
gitStash: { icon: "💼", name: "Stash", key: "tag_stash" },
|
|
114
|
+
gitWorktree: { icon: "🌳", name: "Worktree", key: "tag_worktree" },
|
|
115
|
+
plan: { icon: "⭐", name: "Plan", key: "tag_plan" },
|
|
116
|
+
agentState: { icon: "🟣", name: "Agent", key: "tag_agent" },
|
|
117
|
+
artifacts: { icon: "📎", name: "Artifacts", key: "tag_artifacts" },
|
|
116
118
|
tasks: { icon: "👥", name: "" },
|
|
117
119
|
mode: { icon: "🧭", name: "" },
|
|
118
120
|
title: { icon: "💬", name: "" },
|
|
119
|
-
sessionTime: { icon: "⏱️", name: "Session" },
|
|
120
|
-
ram: { icon: "💻", name: "RAM" },
|
|
121
|
+
sessionTime: { icon: "⏱️", name: "Session", key: "tag_session" },
|
|
122
|
+
ram: { icon: "💻", name: "RAM", key: "tag_ram" },
|
|
121
123
|
version: { icon: "🔖", name: "" },
|
|
122
124
|
sandbox: { icon: "🔒", name: "" },
|
|
123
|
-
cacheRead: { icon: "📥", name: "Cache read" },
|
|
124
|
-
cacheCreate: { icon: "📤", name: "Cache create" },
|
|
125
|
-
cacheHit: { icon: "⚡", name: "Cache hit" },
|
|
126
|
-
totalTokens: { icon: "🪙", name: "Tokens" },
|
|
127
|
-
clock: { icon: "🕒", name: "Clock", iconOnly: true },
|
|
128
|
-
tokensToday: { icon: "📈", name: "Today" },
|
|
129
|
-
tokensWeek: { icon: "📊", name: "7d" },
|
|
125
|
+
cacheRead: { icon: "📥", name: "Cache read", key: "tag_cache_read" },
|
|
126
|
+
cacheCreate: { icon: "📤", name: "Cache create", key: "tag_cache_create" },
|
|
127
|
+
cacheHit: { icon: "⚡", name: "Cache hit", key: "tag_cache_hit" },
|
|
128
|
+
totalTokens: { icon: "🪙", name: "Tokens", key: "tag_tokens" },
|
|
129
|
+
clock: { icon: "🕒", name: "Clock", key: "tag_clock", iconOnly: true },
|
|
130
|
+
tokensToday: { icon: "📈", name: "Today", key: "tag_today" },
|
|
131
|
+
tokensWeek: { icon: "📊", name: "7d", key: "tag_7d" },
|
|
130
132
|
customText: { icon: "🏷️", name: "" },
|
|
131
133
|
};
|
|
132
134
|
|
|
@@ -155,13 +157,16 @@ function customIcon(widget) {
|
|
|
155
157
|
// The text before a widget's value: "🔄 Reset: ", "Reset: ", or nothing for
|
|
156
158
|
// raw widgets. icon overrides the table's for state icons (idle agent,
|
|
157
159
|
// sandbox with network); the user's own label and icon win over both.
|
|
158
|
-
function widgetTag(
|
|
160
|
+
function widgetTag(
|
|
161
|
+
id,
|
|
162
|
+
{ emojis = true, raw = false, icon, widget, ascii = false, lang = DEFAULT_LANGUAGE } = {}
|
|
163
|
+
) {
|
|
159
164
|
const tag = WIDGET_TAGS[id];
|
|
160
165
|
if (raw || !tag) {
|
|
161
166
|
return "";
|
|
162
167
|
}
|
|
163
168
|
const label = customLabel(widget, ascii);
|
|
164
|
-
const name = label ?? tag.name;
|
|
169
|
+
const name = label ?? (tag.key ? t(tag.key, lang) : tag.name);
|
|
165
170
|
const named = name ? `${name}: ` : "";
|
|
166
171
|
if (!emojis) {
|
|
167
172
|
return named;
|