agy-powerline 1.6.0 → 1.8.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.fr.md +10 -0
  3. package/package.json +1 -1
  4. package/src/cli/bundle.js +3 -0
  5. package/src/cli/caps.js +3 -0
  6. package/src/cli/config-guard.js +3 -0
  7. package/src/cli/configure.js +17 -0
  8. package/src/cli/doctor.js +16 -1
  9. package/src/cli/export.js +3 -0
  10. package/src/cli/gradient.js +3 -0
  11. package/src/cli/import.js +3 -0
  12. package/src/cli/init.js +18 -1
  13. package/src/cli/layout.js +3 -0
  14. package/src/cli/preset.js +3 -0
  15. package/src/cli/repo-url.js +3 -0
  16. package/src/cli/report.js +3 -0
  17. package/src/cli/theme.js +3 -0
  18. package/src/cli/tui-style.js +3 -0
  19. package/src/cli/uninstall.js +3 -0
  20. package/src/index.js +15 -0
  21. package/src/lib/catalog.js +3 -0
  22. package/src/lib/colors.js +3 -0
  23. package/src/lib/config-check.js +3 -0
  24. package/src/lib/config.js +17 -1
  25. package/src/lib/exec.js +9 -8
  26. package/src/lib/fit.js +3 -0
  27. package/src/lib/formatters.js +20 -14
  28. package/src/lib/git.js +3 -0
  29. package/src/lib/i18n.js +143 -0
  30. package/src/lib/layouts/lines.js +20 -11
  31. package/src/lib/layouts/pills.js +23 -16
  32. package/src/lib/log.js +3 -0
  33. package/src/lib/mock-data.js +3 -0
  34. package/src/lib/paths.js +4 -0
  35. package/src/lib/payload-dump.js +3 -0
  36. package/src/lib/powerline.js +3 -0
  37. package/src/lib/prompt.js +3 -0
  38. package/src/lib/run.js +3 -0
  39. package/src/lib/session.js +24 -17
  40. package/src/lib/shared.js +3 -0
  41. package/src/lib/themes.js +3 -0
  42. package/src/lib/usage.js +3 -0
  43. package/src/lib/widget-options.js +3 -0
  44. package/src/lib/widget-style.js +46 -38
  45. package/src/runtime/statusline.js +4 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ 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.8.0] - 2026-10-25
9
+
10
+ ### Added
11
+
12
+ - Each GitHub release has its signed SLSA provenance attached as `agy-powerline-<version>.intoto.jsonl`, next to the `.sigstore.json` attestations
13
+ - GOVERNANCE.md (roles, how decisions are made, continuity), ROADMAP.md (plans to October 2027) and SECURITY-REVIEW.md (the first security review and its findings); SECURITY.md says how vulnerability reports are handled and credited and gives the project's assurance case
14
+
15
+ ### Changed
16
+
17
+ - The README screenshots and agy-powerline.com's images are smaller files, and the home page fetches its hero artwork first
18
+
19
+ ### Fixed
20
+
21
+ - agy-powerline.com's sitemap lists the screenshots at the addresses the site serves them from
22
+
23
+ ## [1.7.0] - 2026-10-24
24
+
25
+ ### Added
26
+
27
+ - `"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)
28
+ - The bar in French with `"language": "fr"`, and agy-powerline.com/fr/ shows its bars in French (#148)
29
+ - agy-powerline.com/.well-known/security.txt sends security reports to GitHub's private vulnerability reporting
30
+
31
+ ### Changed
32
+
33
+ - 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
34
+
8
35
  ## [1.6.0] - 2026-10-24
9
36
 
10
37
  ### 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.6.0",
3
+ "version": "1.8.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/bundle.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  // Builds the installed statusline.js: the runtime script and the library
package/src/cli/caps.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const {
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const { readConfigFile, getConfigPath } = require("../lib/shared");
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const readline = require("readline");
@@ -24,6 +27,7 @@ const {
24
27
  } = require("../lib/shared");
25
28
  const { resolveInstallDir } = require("../lib/paths");
26
29
  const { WIDGET_TAGS } = require("../lib/widget-style");
30
+ const { SUPPORTED_LANGUAGES, resolveLanguage } = require("../lib/i18n");
27
31
  const { editFields: getEditFields, stepNumberField } = require("../lib/widget-options");
28
32
  const { loadEditableConfig } = require("./config-guard");
29
33
  const {
@@ -427,6 +431,10 @@ function previewParts(width) {
427
431
  if (preview.layout === "pills") {
428
432
  chips.unshift(ui.chip("joins", preview.powerline.enabled, key("j")));
429
433
  }
434
+ // Only worth a chip once there is a language to switch to.
435
+ if (SUPPORTED_LANGUAGES.length > 1) {
436
+ chips.push(ui.chip(`lang ${resolveLanguage(preview.language)}`, true, key("u")));
437
+ }
430
438
 
431
439
  return { title, bar: String(rendered).split("\n"), chips: chips.join(" ") };
432
440
  }
@@ -1629,6 +1637,15 @@ function onMain(key) {
1629
1637
  if (persist()) {
1630
1638
  flashMessage = `Pace marker: ${config.pacing ? "on" : "off"}`;
1631
1639
  }
1640
+ return;
1641
+ }
1642
+
1643
+ if (key.name === "u" && SUPPORTED_LANGUAGES.length > 1) {
1644
+ const next = SUPPORTED_LANGUAGES.indexOf(resolveLanguage(config.language)) + 1;
1645
+ config.language = SUPPORTED_LANGUAGES[next % SUPPORTED_LANGUAGES.length];
1646
+ if (persist()) {
1647
+ flashMessage = `Bar language: ${config.language}`;
1648
+ }
1632
1649
  }
1633
1650
  }
1634
1651
 
package/src/cli/doctor.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
@@ -28,6 +31,7 @@ const { runStatuslineCommand } = require("../lib/run");
28
31
  const { detectColorDepth } = require("../lib/colors");
29
32
  const { readLog, getLogPath } = require("../lib/log");
30
33
  const { USAGE_PERIODS, usageAvailable } = require("../lib/usage");
34
+ const { matchLanguage, SUPPORTED_LANGUAGES } = require("../lib/i18n");
31
35
  const { version } = require("../../package.json");
32
36
 
33
37
  const DIM = "\x1b[2m";
@@ -78,7 +82,7 @@ function stripAnsi(text) {
78
82
  function renderOverridesInCommand(command) {
79
83
  const names = [];
80
84
  const pattern =
81
- /(?:^|\s)--(preset|layout|theme|caps|gradient)(?:(?:\s*=\s*|\s+)(?:"([^"]*)"|'([^']*)'|([^\s-][^\s]*)))?/g;
85
+ /(?:^|\s)--(preset|layout|theme|caps|gradient|language)(?:(?:\s*=\s*|\s+)(?:"([^"]*)"|'([^']*)'|([^\s-][^\s]*)))?/g;
82
86
 
83
87
  for (const match of String(command).matchAll(pattern)) {
84
88
  const value = match[2] ?? match[3] ?? match[4] ?? "(missing)";
@@ -193,6 +197,17 @@ async function doctorCommand(options = {}) {
193
197
  console.log(" tokensToday and tokensWeek stay hidden until Node is updated.");
194
198
  }
195
199
 
200
+ // Not a failure either: the bar shows English.
201
+ const language = configState.raw?.language;
202
+ if (language !== undefined && !matchLanguage(language)) {
203
+ const shown = String(language)
204
+ .replace(/[^A-Za-z0-9_-]/g, "?")
205
+ .slice(0, 32);
206
+ console.log();
207
+ console.log(`${YELLOW} language "${shown}" isn't available${RESET}, so the bar shows English.`);
208
+ console.log(` Available: ${SUPPORTED_LANGUAGES.join(", ")}`);
209
+ }
210
+
196
211
  const breaking = cmdBreakingChars(installDir);
197
212
  if (breaking.length) {
198
213
  failed += 1;
package/src/cli/export.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const {
package/src/cli/import.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
package/src/cli/init.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
@@ -19,6 +22,7 @@ const {
19
22
  THEME_ORDER,
20
23
  canonicalThemeId,
21
24
  } = require("../lib/shared");
25
+ const { matchLanguage, SUPPORTED_LANGUAGES } = require("../lib/i18n");
22
26
  const {
23
27
  AGY_SETTINGS,
24
28
  RUNTIME_FILES,
@@ -89,7 +93,7 @@ function unknownChoice(kind, value, ids) {
89
93
  return null;
90
94
  }
91
95
 
92
- // --preset, --layout and --theme, checked before anything is written. Returns
96
+ // --preset, --layout, --theme and --language, checked before anything is written. Returns
93
97
  // the ids given ({} for none), or null after printing what's wrong.
94
98
  function readChoiceFlags(options) {
95
99
  const choices = {};
@@ -113,6 +117,16 @@ function readChoiceFlags(options) {
113
117
  }
114
118
  choices.theme = theme;
115
119
  }
120
+ if (options.language !== undefined) {
121
+ const language = matchLanguage(options.language);
122
+ if (!language) {
123
+ console.error(`${RED}error${RESET} Unknown language "${options.language}".`);
124
+ console.error(` Available languages: ${SUPPORTED_LANGUAGES.join(", ")}`);
125
+ process.exitCode = 1;
126
+ return null;
127
+ }
128
+ choices.language = language;
129
+ }
116
130
  return choices;
117
131
  }
118
132
 
@@ -124,6 +138,9 @@ function applyChoices(config, choices) {
124
138
  if (choices.theme) {
125
139
  next.theme = choices.theme;
126
140
  }
141
+ if (choices.language) {
142
+ next.language = choices.language;
143
+ }
127
144
  return next;
128
145
  }
129
146
 
package/src/cli/layout.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const {
package/src/cli/preset.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const {
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const { repository } = require("../../package.json");
package/src/cli/report.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
package/src/cli/theme.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const {
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  // Building blocks for the configure TUI: wordmark, tabs, panels, keycaps and
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
package/src/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) 2026 agy-powerline contributors
4
+
2
5
  "use strict";
3
6
 
4
7
  const fs = require("fs");
@@ -28,6 +31,7 @@ const {
28
31
  } = require("./lib/shared");
29
32
  const { STYLE_ORDER } = require("./lib/powerline");
30
33
  const { GRADIENT_ORDER } = require("./lib/themes");
34
+ const { SUPPORTED_LANGUAGES } = require("./lib/i18n");
31
35
  const { stripControl } = require("./lib/formatters");
32
36
  const { resolveInstallDir, isTrustedInstallDir } = require("./lib/paths");
33
37
  const { isLogEnabled, setLogDir, logError, jsonErrorMessage } = require("./lib/log");
@@ -75,6 +79,11 @@ const COMMANDS = [
75
79
  value: "<id>",
76
80
  help: "use this theme, or dark, light, colorblind (skips the question)",
77
81
  },
82
+ language: {
83
+ type: "string",
84
+ value: "<code>",
85
+ help: `show the bar in this language (${SUPPORTED_LANGUAGES.join(", ")})`,
86
+ },
78
87
  },
79
88
  run: (_arg, opts) =>
80
89
  initCommand({
@@ -232,6 +241,11 @@ const COMMANDS = [
232
241
  value: "<name|colors>",
233
242
  help: "use a gradient preset, hex stops, or off",
234
243
  },
244
+ language: {
245
+ type: "string",
246
+ value: "<code>",
247
+ help: `use a language for this render (${SUPPORTED_LANGUAGES.join(", ")})`,
248
+ },
235
249
  "dump-payload": {
236
250
  type: "string",
237
251
  value: "<path>",
@@ -384,6 +398,7 @@ function renderOverrideError(name, value) {
384
398
  preset: ["preset", `Available presets: ${PRESET_ORDER.join(", ")}`],
385
399
  caps: ["cap style", `Available styles: ${STYLE_ORDER.join(", ")}`],
386
400
  gradient: ["gradient", `Available gradients: ${GRADIENT_ORDER.join(", ")}, off`],
401
+ language: ["language", `Available languages: ${SUPPORTED_LANGUAGES.join(", ")}`],
387
402
  }[name];
388
403
  console.error(`${RED}error${RESET} Unknown ${what} "${stripControl(String(value))}".`);
389
404
  console.error(` ${list}`);
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  // description: one sentence, shown in the configure details panel.
package/src/lib/colors.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const CSI = "\x1b[";
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const { defaultConfig, canonicalThemeId, LAYOUTS } = require("./shared");
package/src/lib/config.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
@@ -8,6 +11,7 @@ const { stripControl, normalizeClock } = require("./formatters");
8
11
  const { normalizePowerline, applyStyle, STYLES } = require("./powerline");
9
12
  const { writeFileAtomic, PRIVATE_FILE_MODE } = require("./paths");
10
13
  const { isMeterStyle } = require("./widget-style");
14
+ const { resolveLanguage, matchLanguage } = require("./i18n");
11
15
 
12
16
  // Display text in widget configs is printed as-is, so strip terminal control
13
17
  // from it. cmd/command are left alone: they are executed, not printed.
@@ -126,6 +130,7 @@ function defaultConfig() {
126
130
  ascii: false,
127
131
  color: "auto",
128
132
  layout: "pills",
133
+ language: "en",
129
134
  pacing: true,
130
135
  minimalist: false,
131
136
  rowGap: false,
@@ -185,6 +190,8 @@ function mergeConfig(raw) {
185
190
  ascii: raw?.ascii === true,
186
191
  color: resolveColorModeId(raw?.color ?? fallback.color),
187
192
  layout: resolveLayoutId(raw?.layout ?? fallback.layout),
193
+ // A language without messages shows English (fr-FR uses fr when there is one).
194
+ language: resolveLanguage(raw?.language),
188
195
  pacing: raw?.pacing !== false,
189
196
  minimalist: raw?.minimalist === true,
190
197
  rowGap: raw?.rowGap === true,
@@ -237,7 +244,7 @@ function readConfigFile() {
237
244
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
238
245
  return { exists: true, config: defaultConfig(), error: "top-level value is not an object" };
239
246
  }
240
- return { exists: true, config: mergeConfig(raw) };
247
+ return { exists: true, config: mergeConfig(raw), raw };
241
248
  } catch (err) {
242
249
  // Newer Node quotes part of the file in the message; it may hold anything.
243
250
  return { exists: true, config: defaultConfig(), error: stripControl(err.message) };
@@ -475,6 +482,15 @@ function applyRenderOverrides(baseConfig, overrides = {}) {
475
482
  }
476
483
  }
477
484
 
485
+ if (overrides.language !== undefined) {
486
+ const language = matchLanguage(overrides.language);
487
+ if (language) {
488
+ config = mergeConfig({ ...config, language });
489
+ } else {
490
+ invalid.push("language");
491
+ }
492
+ }
493
+
478
494
  return { config, invalid };
479
495
  }
480
496
 
package/src/lib/exec.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");
@@ -8,6 +11,7 @@ const { execSync } = require("child_process");
8
11
  const { stripControl, sanitizeDeep } = require("./formatters");
9
12
  const { RESET, getActiveDepth } = require("./colors");
10
13
  const { logWarning } = require("./log");
14
+ const { t } = require("./i18n");
11
15
  const { PRIVATE_FILE_MODE } = require("./paths");
12
16
 
13
17
  const DEFAULT_TIMEOUT_MS = 500;
@@ -15,10 +19,6 @@ const DEFAULT_INTERVAL_SEC = 10;
15
19
  const MAX_CACHE_AGE_SEC = 3600;
16
20
  const MAX_OUTPUT_CHARS = 80;
17
21
 
18
- // Placeholders shown instead of running the command.
19
- const EXEC_PREVIEW_TEXT = "[cmd]";
20
- const EXEC_BLOCKED_TEXT = "[blocked]";
21
-
22
22
  const memoryCache = new Map();
23
23
 
24
24
  function getCacheFilePath() {
@@ -31,7 +31,9 @@ function getCacheFilePath() {
31
31
  } catch {
32
32
  // Fallback
33
33
  }
34
- // Per-user name so other accounts on a shared machine cannot pre-create it.
34
+ // Per-user name so accounts don't share one cache. Another account could
35
+ // still guess and pre-create it in a shared /tmp, so the runtime never comes
36
+ // here: exec widgets only run from a config file in the install dir above.
35
37
  let user = "user";
36
38
  try {
37
39
  user = os.userInfo().username.replace(/[^A-Za-z0-9_.-]/g, "_") || user;
@@ -233,7 +235,8 @@ function execWidgetOutput(widget, session = {}) {
233
235
  return output;
234
236
  }
235
237
 
236
- return session.execMode === "blocked" ? EXEC_BLOCKED_TEXT : EXEC_PREVIEW_TEXT;
238
+ // Placeholders shown instead of running the command.
239
+ return t(session.execMode === "blocked" ? "cmd_blocked" : "cmd_preview", session.lang);
237
240
  }
238
241
 
239
242
  function runExecWidget(widget, session = {}) {
@@ -349,6 +352,4 @@ module.exports = {
349
352
  saveDiskCache,
350
353
  DEFAULT_TIMEOUT_MS,
351
354
  DEFAULT_INTERVAL_SEC,
352
- EXEC_PREVIEW_TEXT,
353
- EXEC_BLOCKED_TEXT,
354
355
  };
package/src/lib/fit.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const { charWidth, visibleWidth } = require("./formatters");
@@ -1,14 +1,18 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const os = require("os");
4
7
  const path = require("path");
5
8
  const { rgb } = require("./colors");
9
+ const { t } = require("./i18n");
6
10
 
7
- function formatReset(seconds) {
11
+ function formatReset(seconds, lang) {
8
12
  seconds = Number(seconds);
9
13
 
10
14
  if (!Number.isFinite(seconds) || seconds <= 0) {
11
- return "now";
15
+ return t("now", lang);
12
16
  }
13
17
 
14
18
  const totalMinutes = Math.floor(seconds / 60);
@@ -17,23 +21,23 @@ function formatReset(seconds) {
17
21
  const minutes = totalMinutes % 60;
18
22
 
19
23
  if (days > 0) {
20
- return `${days}d ${hours}h`;
24
+ return `${days}${t("unit_d", lang)} ${hours}${t("unit_h", lang)}`;
21
25
  }
22
26
 
23
27
  if (hours > 0) {
24
- return `${hours}hr ${minutes}m`;
28
+ return `${hours}${t("unit_hr", lang)} ${minutes}${t("unit_m", lang)}`;
25
29
  }
26
30
 
27
- return `${Math.max(1, minutes)}m`;
31
+ return `${Math.max(1, minutes)}${t("unit_m", lang)}`;
28
32
  }
29
33
 
30
- function formatSessionTime(startMs, nowMs = Date.now()) {
34
+ function formatSessionTime(startMs, nowMs = Date.now(), lang) {
31
35
  const start = Number(startMs);
32
36
  const now = Number(nowMs);
33
37
  if (!Number.isFinite(start) || start <= 0 || !Number.isFinite(now) || now - start < 60000) {
34
38
  return "";
35
39
  }
36
- return formatReset((now - start) / 1000);
40
+ return formatReset((now - start) / 1000, lang);
37
41
  }
38
42
 
39
43
  const MONTHS_SHORT = [
@@ -274,22 +278,22 @@ function shortModelName(name, ascii = false) {
274
278
  }
275
279
 
276
280
  // Session input and output tokens: "88.2K↑ 61.1K↓", or "88.2K in 61.1K out".
277
- function formatTokenSplit(input, output, ascii = false) {
281
+ function formatTokenSplit(input, output, ascii = false, lang) {
278
282
  return ascii
279
- ? `${formatTokens(input)} in ${formatTokens(output)} out`
283
+ ? `${formatTokens(input)} ${t("tokens_in", lang)} ${formatTokens(output)} ${t("tokens_out", lang)}`
280
284
  : `${formatTokens(input)}↑ ${formatTokens(output)}↓`;
281
285
  }
282
286
 
283
287
  // The usage widgets' text: "1.2M", or "1.0M↑ 245.3K↓" with "split", plus the
284
288
  // share of a "budget" when one is set ("1.2M (60%)"). null with no tokens.
285
- function formatUsage(tokens, widget = {}, ascii = false) {
289
+ function formatUsage(tokens, widget = {}, ascii = false, lang) {
286
290
  const input = Number(tokens?.input) || 0;
287
291
  const output = Number(tokens?.output) || 0;
288
292
  const total = input + output;
289
293
  if (!(total > 0)) {
290
294
  return null;
291
295
  }
292
- const value = widget?.split ? formatTokenSplit(input, output, ascii) : formatTokens(total);
296
+ const value = widget?.split ? formatTokenSplit(input, output, ascii, lang) : formatTokens(total);
293
297
  const budget = widget?.budget;
294
298
  const pct =
295
299
  typeof budget === "number" && Number.isFinite(budget) && budget > 0
@@ -620,9 +624,11 @@ function formatGitOperation(op) {
620
624
  }
621
625
 
622
626
  // "2 subagents, 1 task", or "" when nothing runs.
623
- function formatTasks(subagents, tasks) {
624
- const part = (n, word) => (n > 0 ? `${n} ${word}${n === 1 ? "" : "s"}` : "");
625
- return [part(subagents, "subagent"), part(tasks, "task")].filter(Boolean).join(", ");
627
+ function formatTasks(subagents, tasks, lang) {
628
+ const part = (n, one, many) => (n > 0 ? `${n} ${t(n === 1 ? one : many, lang)}` : "");
629
+ return [part(subagents, "subagent", "subagents"), part(tasks, "task", "tasks")]
630
+ .filter(Boolean)
631
+ .join(", ");
626
632
  }
627
633
 
628
634
  // "accept-edits, NORMAL", or "" in the default mode with Vim editing off.
package/src/lib/git.js CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 agy-powerline contributors
3
+
1
4
  "use strict";
2
5
 
3
6
  const fs = require("fs");