blun-king-cli 9.1.392 → 9.1.393

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/blun.mjs CHANGED
@@ -235470,7 +235470,7 @@ var init_sub_skill = __esmMin((() => {
235470
235470
  //#region ../../packages/agent-core/src/skill/builtin/update-config.md?raw
235471
235471
  var update_config_default;
235472
235472
  var init_update_config$1 = __esmMin((() => {
235473
- update_config_default = "---\nname: update-config\ndescription: Inspect or edit blun's own config — `config.toml` (model, provider, permission, hooks) and `tui.toml` (theme, editor, notifications, auto-update). Use when the user asks what a setting does or wants to change one.\n---\n\n# Configure blun (update-config)\n\nHelp the user inspect, change, and validate blun's configuration files. The files are **TOML** with **snake_case** keys.\n\n## The two config files\n\nblun has two TOML config files, both under `<BLUN_HOME>/`, both snake_case, but with different ownership — decide which one the user means before doing anything.\n\nThe runtime resolves the data directory as `BLUN_HOME` first, falling back to `~/.blun`. Before doing anything, resolve the actual directory with Bash so you don't write to the wrong place. Check whether `BLUN_HOME` is set and fall back to `~/.blun` when it is empty:\n\n```bash\necho \"$BLUN_HOME\"\necho \"$HOME/.blun\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the rest of this skill, `<BLUN_HOME>` means that resolved root — **never assume `~/.blun`**.\n\n- **`config.toml`** — agent / runtime settings: `default_model`, `providers`, `models`, `thinking`, `permission`, `hooks`, `loop_control`, etc.\n- **`tui.toml`** — terminal-UI / client preferences: `theme`, `[editor].command`, `[notifications]`, `[upgrade].auto_install` (auto-update). These can usually also be changed with the interactive commands `/config`, `/theme`, `/editor`, which is easier — prefer pointing the user at those.\n\nThe \"read → copy → Edit → validate → back up → overwrite\" flow below applies to both files; only **which reload command applies** differs (see Capability 4).\n\n## Prerequisite 1: the official docs are the single source of truth\n\nBefore touching any config, use **FetchURL** to fetch the official config docs as the one authoritative reference for fields (key names, types, allowed values, owning section):\n\n```\nhttps://blun.ai/docs\n```\n\n- Use the **snake_case key names and sections exactly as documented** — don't invent them, don't guess camelCase.\n- If FetchURL is unavailable or the fetch fails, tell the user plainly that you can't reach the online docs, and ask them to paste the relevant section or confirm whether to proceed from what you already know. **Never edit blindly without an authoritative reference.**\n\n## Prerequisite 2: read the target file before any change\n\nBefore any modification, use **Read** on the target config file (decide whether it's `config.toml` or `tui.toml` per the above):\n\n- Location: `<BLUN_HOME>/config.toml` or `<BLUN_HOME>/tui.toml`. For other scopes/files, defer to the official docs.\n- A missing or empty file is fine — you'll create a minimal skeleton later.\n- If the file exists but **fails to parse as TOML**, report the error verbatim and **stop** — never overwrite a broken file in place (it could destroy the user's existing config).\n\n---\n\n## Capability 1: explain configuration (read-only, no file changes)\n\nWhen the user asks \"what config is there\", \"what does this setting do\", or \"how do I use it\":\n\n1. Fetch the official docs (Prerequisite 1).\n2. Read the current `config.toml` (Prerequisite 2).\n3. Answer against both: list the relevant sections / keys, what each is for, **current value vs default**, and the allowed-value range; say which file and section each lives in.\n4. Present it as a compact grouped list or table. **Stay read-only — write no files.**\n\n## Capability 2: make changes for the user (copy → Edit → validate → back up → overwrite)\n\nDon't edit the target file in place, and **don't rewrite it from scratch** — instead copy it, Edit the copy, and keep the original out of any broken state the whole time:\n\n1. **Clarify intent**: which key, what value, and which file (`config.toml` or `tui.toml`). Ask in one line if ambiguous; for discrete choices (e.g. scope) AskUserQuestion is fine, but use plain questions for free-form input.\n2. **Read the target file** (Prerequisite 2): Read it to understand the current state and confirm it parses.\n3. **Copy out a candidate (do not create from scratch)**: use **Bash** to copy the target verbatim — `cp config.toml config-new.toml` (same directory, `-new` suffix; for tui.toml, `cp tui.toml tui-new.toml`). **Leave the original untouched for now.**\n - Only when the target doesn't exist (nothing to copy) should you use **Write** to create a minimal skeleton candidate (e.g. just the comment line `# <BLUN_HOME>/config.toml`).\n4. **Edit the candidate**: use the **Edit** tool on the candidate to **change/add only the target key** — never rewrite the whole file. That way every existing section, entry, comment, and bit of formatting stays exactly as-is; only what should change changes. The candidate is identical to the original, so use the content you read in step 2 to locate the Edit anchor. Check the change against the official docs (key / section / value type / allowed values, snake_case).\n5. **Validate the candidate** (see Capability 3, via `blun doctor`). **If anything fails, keep Editing the candidate and re-validate, looping until it all passes.**\n6. **Back up and overwrite** (only after validation fully passes):\n - **Back up the old file — always create a new timestamped backup, keep all of them, never overwrite an existing backup.** Copy this exactly with **Bash** (for config.toml): `cp config.toml \"config.toml.$(date +%Y%m%d-%H%M%S).bak\"`; for tui.toml: `cp tui.toml \"tui.toml.$(date +%Y%m%d-%H%M%S).bak\"`. Skip the backup only if the target didn't exist.\n - Overwrite with the candidate: `mv config-new.toml config.toml`.\n - If reload errors after the overwrite, the user can recover from **the most recent timestamped backup**.\n7. Tell the user how to apply it (see Capability 4).\n\n## Capability 3: validate the candidate file (must pass before overwrite)\n\nUse **`blun doctor`** to validate the candidate you wrote — it doesn't start the TUI and doesn't modify any file; it runs blun's own parser + schema (syntax and schema together), so it's the authoritative check. Pick the subcommand by which file you changed, and pass the **candidate** path explicitly:\n\n- changed `config.toml` → `blun doctor config <config-new.toml path>`\n- changed `tui.toml` → `blun doctor tui <tui-new.toml path>`\n\nWhen a path is passed explicitly the file must exist (your candidate does, so that's fine). **Exit code 0 = pass (valid or skipped); non-zero = a specified file is missing or the config is invalid** — show the output verbatim, fix the candidate, and re-run, looping until it's 0.\n\nThen do two checks `blun doctor` can't:\n\n1. **Cross-check values against the official docs** (single source of truth): are the key / section / enum values as documented, and snake_case? doctor guarantees \"schema-valid\", but \"valid yet not what the user wanted\" (e.g. a misspelled model alias) needs the docs.\n2. **Completeness**: every existing entry is still present (the candidate fully replaces the target — a dropped line is a deletion).\n\n> To also check whether the currently **active** config is OK overall, run `blun doctor` with no path (it checks the default `config.toml` + `tui.toml`, showing a missing one as skipped).\n\n## Capability 4: tell the user how to apply changes\n\nOnce local validation passes, tell the user how to make the change take effect — **the reload command depends on which file you changed**:\n\n- changed **`config.toml`** → run **`/reload`** in the TUI (reloads the session and applies `config.toml`; it also reloads `tui.toml`).\n- changed **`tui.toml`** → run **`/reload-tui`** (reloads only `tui.toml`, lighter); `/reload` works too (reloads both).\n- changed both → a single **`/reload`** covers it.\n\nNote: `/reload` is available **only when idle** — if a reply is streaming, press Esc / Ctrl-C to stop first. `blun doctor` already validated the schema before the overwrite, so reload should apply cleanly; if it still errors, follow the message to fix it or recover from the most recent timestamped backup. If you don't want to reload now, the **next new session** picks it up automatically.\n\n## Don'ts\n\n- **Always back up before overwriting**, with a **timestamped name and all history kept** — don't skip the backup, don't keep only a single `.bak`, don't overwrite an old backup.\n- Don't drop unrelated entries (the candidate fully replaces the target — a dropped line is a deletion).\n- When you can't reach the docs / have no authoritative reference, don't edit by guessing.\n";
235473
+ update_config_default = "---\nname: update-config\ndescription: Inspect or edit blun's own config — `config.toml` (model, provider, permission, hooks) and `tui.toml` (theme, editor, notifications). Use when the user asks what a setting does or wants to change one.\n---\n\n# Configure blun (update-config)\n\nHelp the user inspect, change, and validate blun's configuration files. The files are **TOML** with **snake_case** keys.\n\n## The two config files\n\nblun has two TOML config files, both under `<BLUN_HOME>/`, both snake_case, but with different ownership — decide which one the user means before doing anything.\n\nThe runtime resolves the data directory as `BLUN_HOME` first, falling back to `~/.blun`. Before doing anything, resolve the actual directory with Bash so you don't write to the wrong place. Check whether `BLUN_HOME` is set and fall back to `~/.blun` when it is empty:\n\n```bash\necho \"$BLUN_HOME\"\necho \"$HOME/.blun\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the rest of this skill, `<BLUN_HOME>` means that resolved root — **never assume `~/.blun`**.\n\n- **`config.toml`** — agent / runtime settings: `default_model`, `providers`, `models`, `thinking`, `permission`, `hooks`, `loop_control`, etc.\n- **`tui.toml`** — terminal-UI / client preferences: `theme`, `[editor].command`, `[notifications]`. These can usually also be changed with the interactive commands `/config`, `/theme`, `/editor`, which is easier — prefer pointing the user at those.\n\nThe \"read → copy → Edit → validate → back up → overwrite\" flow below applies to both files; only **which reload command applies** differs (see Capability 4).\n\n## Prerequisite 1: the official docs are the single source of truth\n\nBefore touching any config, use **FetchURL** to fetch the official config docs as the one authoritative reference for fields (key names, types, allowed values, owning section):\n\n```\nhttps://blun.ai/docs\n```\n\n- Use the **snake_case key names and sections exactly as documented** — don't invent them, don't guess camelCase.\n- If FetchURL is unavailable or the fetch fails, tell the user plainly that you can't reach the online docs, and ask them to paste the relevant section or confirm whether to proceed from what you already know. **Never edit blindly without an authoritative reference.**\n\n## Prerequisite 2: read the target file before any change\n\nBefore any modification, use **Read** on the target config file (decide whether it's `config.toml` or `tui.toml` per the above):\n\n- Location: `<BLUN_HOME>/config.toml` or `<BLUN_HOME>/tui.toml`. For other scopes/files, defer to the official docs.\n- A missing or empty file is fine — you'll create a minimal skeleton later.\n- If the file exists but **fails to parse as TOML**, report the error verbatim and **stop** — never overwrite a broken file in place (it could destroy the user's existing config).\n\n---\n\n## Capability 1: explain configuration (read-only, no file changes)\n\nWhen the user asks \"what config is there\", \"what does this setting do\", or \"how do I use it\":\n\n1. Fetch the official docs (Prerequisite 1).\n2. Read the current `config.toml` (Prerequisite 2).\n3. Answer against both: list the relevant sections / keys, what each is for, **current value vs default**, and the allowed-value range; say which file and section each lives in.\n4. Present it as a compact grouped list or table. **Stay read-only — write no files.**\n\n## Capability 2: make changes for the user (copy → Edit → validate → back up → overwrite)\n\nDon't edit the target file in place, and **don't rewrite it from scratch** — instead copy it, Edit the copy, and keep the original out of any broken state the whole time:\n\n1. **Clarify intent**: which key, what value, and which file (`config.toml` or `tui.toml`). Ask in one line if ambiguous; for discrete choices (e.g. scope) AskUserQuestion is fine, but use plain questions for free-form input.\n2. **Read the target file** (Prerequisite 2): Read it to understand the current state and confirm it parses.\n3. **Copy out a candidate (do not create from scratch)**: use **Bash** to copy the target verbatim — `cp config.toml config-new.toml` (same directory, `-new` suffix; for tui.toml, `cp tui.toml tui-new.toml`). **Leave the original untouched for now.**\n - Only when the target doesn't exist (nothing to copy) should you use **Write** to create a minimal skeleton candidate (e.g. just the comment line `# <BLUN_HOME>/config.toml`).\n4. **Edit the candidate**: use the **Edit** tool on the candidate to **change/add only the target key** — never rewrite the whole file. That way every existing section, entry, comment, and bit of formatting stays exactly as-is; only what should change changes. The candidate is identical to the original, so use the content you read in step 2 to locate the Edit anchor. Check the change against the official docs (key / section / value type / allowed values, snake_case).\n5. **Validate the candidate** (see Capability 3, via `blun doctor`). **If anything fails, keep Editing the candidate and re-validate, looping until it all passes.**\n6. **Back up and overwrite** (only after validation fully passes):\n - **Back up the old file — always create a new timestamped backup, keep all of them, never overwrite an existing backup.** Copy this exactly with **Bash** (for config.toml): `cp config.toml \"config.toml.$(date +%Y%m%d-%H%M%S).bak\"`; for tui.toml: `cp tui.toml \"tui.toml.$(date +%Y%m%d-%H%M%S).bak\"`. Skip the backup only if the target didn't exist.\n - Overwrite with the candidate: `mv config-new.toml config.toml`.\n - If reload errors after the overwrite, the user can recover from **the most recent timestamped backup**.\n7. Tell the user how to apply it (see Capability 4).\n\n## Capability 3: validate the candidate file (must pass before overwrite)\n\nUse **`blun doctor`** to validate the candidate you wrote — it doesn't start the TUI and doesn't modify any file; it runs blun's own parser + schema (syntax and schema together), so it's the authoritative check. Pick the subcommand by which file you changed, and pass the **candidate** path explicitly:\n\n- changed `config.toml` → `blun doctor config <config-new.toml path>`\n- changed `tui.toml` → `blun doctor tui <tui-new.toml path>`\n\nWhen a path is passed explicitly the file must exist (your candidate does, so that's fine). **Exit code 0 = pass (valid or skipped); non-zero = a specified file is missing or the config is invalid** — show the output verbatim, fix the candidate, and re-run, looping until it's 0.\n\nThen do two checks `blun doctor` can't:\n\n1. **Cross-check values against the official docs** (single source of truth): are the key / section / enum values as documented, and snake_case? doctor guarantees \"schema-valid\", but \"valid yet not what the user wanted\" (e.g. a misspelled model alias) needs the docs.\n2. **Completeness**: every existing entry is still present (the candidate fully replaces the target — a dropped line is a deletion).\n\n> To also check whether the currently **active** config is OK overall, run `blun doctor` with no path (it checks the default `config.toml` + `tui.toml`, showing a missing one as skipped).\n\n## Capability 4: tell the user how to apply changes\n\nOnce local validation passes, tell the user how to make the change take effect — **the reload command depends on which file you changed**:\n\n- changed **`config.toml`** → run **`/reload`** in the TUI (reloads the session and applies `config.toml`; it also reloads `tui.toml`).\n- changed **`tui.toml`** → run **`/reload-tui`** (reloads only `tui.toml`, lighter); `/reload` works too (reloads both).\n- changed both → a single **`/reload`** covers it.\n\nNote: `/reload` is available **only when idle** — if a reply is streaming, press Esc / Ctrl-C to stop first. `blun doctor` already validated the schema before the overwrite, so reload should apply cleanly; if it still errors, follow the message to fix it or recover from the most recent timestamped backup. If you don't want to reload now, the **next new session** picks it up automatically.\n\n## Don'ts\n\n- **Always back up before overwriting**, with a **timestamped name and all history kept** — don't skip the backup, don't keep only a single `.bak`, don't overwrite an old backup.\n- Don't drop unrelated entries (the candidate fully replaces the target — a dropped line is a deletion).\n- When you can't reach the docs / have no authoritative reference, don't edit by guessing.\n";
235474
235474
  }));
235475
235475
  //#endregion
235476
235476
  //#region ../../packages/agent-core/src/skill/builtin/update-config.ts
@@ -341270,7 +341270,6 @@ registerUiCatalogFragment({
341270
341270
  });
341271
341271
  registerUiCatalogFragment({
341272
341272
  en: {
341273
- "command.update.description": "Configure updates",
341274
341273
  "command.premortem.description": "Stress-test a plan before execution",
341275
341274
  "command.improve.description": "Show validated learning status",
341276
341275
  "premortem.usage": "Usage: /premortem <plan or decision>",
@@ -341279,21 +341278,8 @@ registerUiCatalogFragment({
341279
341278
  "learning.latest": "Latest candidate",
341280
341279
  "learning.verification": "Verification",
341281
341280
  "learning.empty": "No validated learning candidates yet.",
341282
- "update.title": "Update behavior",
341283
- "update.hint": "↑↓ navigate · Enter select · Esc cancel",
341284
- "update.manual.label": "Manual updates",
341285
- "update.manual.description": "Only update when you run the update command.",
341286
- "update.resume.label": "Automatic: resume session",
341287
- "update.resume.description": "When idle, install the update and resume the current session.",
341288
- "update.new.label": "Automatic: new session",
341289
- "update.new.description": "When idle, install the update and start a new session.",
341290
- "update.off.label": "Automatic updates off",
341291
- "update.off.description": "Do not check for or install updates automatically.",
341292
- "update.saved": "Update behavior saved: {mode}",
341293
- "update.saveFailed": "Could not save update behavior: {error}"
341294
341281
  },
341295
341282
  de: {
341296
- "command.update.description": "Updates konfigurieren",
341297
341283
  "command.premortem.description": "Einen Plan vor der Umsetzung auf Schwachstellen prüfen",
341298
341284
  "command.improve.description": "Status des validierten Lernens anzeigen",
341299
341285
  "premortem.usage": "Verwendung: /premortem <Plan oder Entscheidung>",
@@ -341302,21 +341288,8 @@ registerUiCatalogFragment({
341302
341288
  "learning.latest": "Neuester Kandidat",
341303
341289
  "learning.verification": "Prüfung",
341304
341290
  "learning.empty": "Noch keine validierten Lernkandidaten vorhanden.",
341305
- "update.title": "Update-Verhalten",
341306
- "update.hint": "↑↓ navigieren · Enter auswählen · Esc abbrechen",
341307
- "update.manual.label": "Updates von Hand",
341308
- "update.manual.description": "Nur aktualisieren, wenn du den Update-Befehl startest.",
341309
- "update.resume.label": "Automatisch: Sitzung fortsetzen",
341310
- "update.resume.description": "Im Leerlauf das Update installieren und die laufende Sitzung fortsetzen.",
341311
- "update.new.label": "Automatisch: neue Sitzung",
341312
- "update.new.description": "Im Leerlauf das Update installieren und eine neue Sitzung starten.",
341313
- "update.off.label": "Automatische Updates aus",
341314
- "update.off.description": "Nicht automatisch nach Updates suchen oder sie installieren.",
341315
- "update.saved": "Update-Verhalten gespeichert: {mode}",
341316
- "update.saveFailed": "Update-Verhalten konnte nicht gespeichert werden: {error}"
341317
341291
  },
341318
341292
  es: {
341319
- "command.update.description": "Configurar actualizaciones",
341320
341293
  "command.premortem.description": "Someter un plan a una prueba de estrés antes de ejecutarlo",
341321
341294
  "command.improve.description": "Mostrar el estado del aprendizaje validado",
341322
341295
  "premortem.usage": "Uso: /premortem <plan o decisión>",
@@ -341325,21 +341298,8 @@ registerUiCatalogFragment({
341325
341298
  "learning.latest": "Candidato más reciente",
341326
341299
  "learning.verification": "Verificación",
341327
341300
  "learning.empty": "Todavía no hay candidatos de aprendizaje validados.",
341328
- "update.title": "Comportamiento de las actualizaciones",
341329
- "update.hint": "↑↓ navegar · Enter seleccionar · Esc cancelar",
341330
- "update.manual.label": "Actualizaciones manuales",
341331
- "update.manual.description": "Actualizar solo cuando ejecutes el comando de actualización.",
341332
- "update.resume.label": "Automático: reanudar sesión",
341333
- "update.resume.description": "Cuando esté inactivo, instalar la actualización y reanudar la sesión actual.",
341334
- "update.new.label": "Automático: nueva sesión",
341335
- "update.new.description": "Cuando esté inactivo, instalar la actualización e iniciar una sesión nueva.",
341336
- "update.off.label": "Actualizaciones automáticas desactivadas",
341337
- "update.off.description": "No buscar ni instalar actualizaciones automáticamente.",
341338
- "update.saved": "Comportamiento de las actualizaciones guardado: {mode}",
341339
- "update.saveFailed": "No se pudo guardar el comportamiento de las actualizaciones: {error}"
341340
341301
  },
341341
341302
  fr: {
341342
- "command.update.description": "Configurer les mises à jour",
341343
341303
  "command.premortem.description": "Mettre un plan à l’épreuve avant son exécution",
341344
341304
  "command.improve.description": "Afficher l’état de l’apprentissage validé",
341345
341305
  "premortem.usage": "Utilisation : /premortem <plan ou décision>",
@@ -341348,21 +341308,8 @@ registerUiCatalogFragment({
341348
341308
  "learning.latest": "Candidat le plus récent",
341349
341309
  "learning.verification": "Vérification",
341350
341310
  "learning.empty": "Aucun candidat d’apprentissage validé pour le moment.",
341351
- "update.title": "Comportement des mises à jour",
341352
- "update.hint": "↑↓ naviguer · Entrée sélectionner · Échap annuler",
341353
- "update.manual.label": "Mises à jour manuelles",
341354
- "update.manual.description": "Mettre à jour uniquement lorsque vous exécutez la commande de mise à jour.",
341355
- "update.resume.label": "Automatique : reprendre la session",
341356
- "update.resume.description": "En cas d’inactivité, installer la mise à jour et reprendre la session en cours.",
341357
- "update.new.label": "Automatique : nouvelle session",
341358
- "update.new.description": "En cas d’inactivité, installer la mise à jour et démarrer une nouvelle session.",
341359
- "update.off.label": "Mises à jour automatiques désactivées",
341360
- "update.off.description": "Ne pas rechercher ni installer automatiquement les mises à jour.",
341361
- "update.saved": "Comportement des mises à jour enregistré : {mode}",
341362
- "update.saveFailed": "Impossible d’enregistrer le comportement des mises à jour : {error}"
341363
341311
  },
341364
341312
  sv: {
341365
- "command.update.description": "Konfigurera uppdateringar",
341366
341313
  "command.premortem.description": "Stresstesta en plan före genomförandet",
341367
341314
  "command.improve.description": "Visa status för validerat lärande",
341368
341315
  "premortem.usage": "Användning: /premortem <plan eller beslut>",
@@ -341371,21 +341318,8 @@ registerUiCatalogFragment({
341371
341318
  "learning.latest": "Senaste kandidaten",
341372
341319
  "learning.verification": "Verifiering",
341373
341320
  "learning.empty": "Det finns inga validerade inlärningskandidater ännu.",
341374
- "update.title": "Uppdateringsbeteende",
341375
- "update.hint": "↑↓ navigera · Enter välj · Esc avbryt",
341376
- "update.manual.label": "Manuella uppdateringar",
341377
- "update.manual.description": "Uppdatera endast när du kör uppdateringskommandot.",
341378
- "update.resume.label": "Automatiskt: återuppta sessionen",
341379
- "update.resume.description": "Installera uppdateringen vid inaktivitet och återuppta den aktuella sessionen.",
341380
- "update.new.label": "Automatiskt: ny session",
341381
- "update.new.description": "Installera uppdateringen vid inaktivitet och starta en ny session.",
341382
- "update.off.label": "Automatiska uppdateringar av",
341383
- "update.off.description": "Sök inte efter och installera inte uppdateringar automatiskt.",
341384
- "update.saved": "Uppdateringsbeteende sparat: {mode}",
341385
- "update.saveFailed": "Det gick inte att spara uppdateringsbeteendet: {error}"
341386
341321
  },
341387
341322
  cs: {
341388
- "command.update.description": "Nastavit aktualizace",
341389
341323
  "command.premortem.description": "Prověřit plán před provedením zátěžovým testem",
341390
341324
  "command.improve.description": "Zobrazit stav ověřeného učení",
341391
341325
  "premortem.usage": "Použití: /premortem <plán nebo rozhodnutí>",
@@ -341394,18 +341328,6 @@ registerUiCatalogFragment({
341394
341328
  "learning.latest": "Nejnovější kandidát",
341395
341329
  "learning.verification": "Ověření",
341396
341330
  "learning.empty": "Zatím nejsou k dispozici žádní kandidáti ověřeného učení.",
341397
- "update.title": "Chování aktualizací",
341398
- "update.hint": "↑↓ navigace · Enter vybrat · Esc zrušit",
341399
- "update.manual.label": "Ruční aktualizace",
341400
- "update.manual.description": "Aktualizovat pouze po spuštění příkazu pro aktualizaci.",
341401
- "update.resume.label": "Automaticky: pokračovat v relaci",
341402
- "update.resume.description": "Při nečinnosti nainstalovat aktualizaci a pokračovat v aktuální relaci.",
341403
- "update.new.label": "Automaticky: nová relace",
341404
- "update.new.description": "Při nečinnosti nainstalovat aktualizaci a spustit novou relaci.",
341405
- "update.off.label": "Automatické aktualizace vypnuty",
341406
- "update.off.description": "Nevyhledávat ani neinstalovat aktualizace automaticky.",
341407
- "update.saved": "Chování aktualizací uloženo: {mode}",
341408
- "update.saveFailed": "Chování aktualizací se nepodařilo uložit: {error}"
341409
341331
  }
341410
341332
  });
341411
341333
  //#endregion
@@ -342124,7 +342046,6 @@ const NotificationsConfigSchema = object({
342124
342046
  enabled: boolean$1(),
342125
342047
  condition: NotificationConditionSchema
342126
342048
  });
342127
- const UpgradePreferencesSchema = object({ autoInstall: boolean$1() });
342128
342049
  const InlineSuggestConfigSchema = object({ languages: record(string(), boolean$1()) });
342129
342050
  const HexColorSchema = string().regex(/^#[0-9A-Fa-f]{6}$/u);
342130
342051
  const AppearanceConfigSchema = object({
@@ -342141,8 +342062,7 @@ const TuiConfigFileSchema = object({
342141
342062
  notifications: object({
342142
342063
  enabled: boolean$1().optional(),
342143
342064
  notification_condition: NotificationConditionSchema.optional()
342144
- }).optional(),
342145
- upgrade: object({ auto_install: boolean$1().optional() }).optional()
342065
+ }).optional()
342146
342066
  });
342147
342067
  const TuiConfigSchema = object({
342148
342068
  theme: TuiThemeSchema,
@@ -342151,14 +342071,12 @@ const TuiConfigSchema = object({
342151
342071
  disablePasteBurst: boolean$1(),
342152
342072
  editorCommand: string().nullable(),
342153
342073
  notifications: NotificationsConfigSchema,
342154
- upgrade: UpgradePreferencesSchema,
342155
342074
  inlineSuggest: InlineSuggestConfigSchema
342156
342075
  });
342157
342076
  const DEFAULT_NOTIFICATIONS_CONFIG = {
342158
342077
  enabled: true,
342159
342078
  condition: "unfocused"
342160
342079
  };
342161
- const DEFAULT_UPGRADE_PREFERENCES = { autoInstall: true };
342162
342080
  /**
342163
342081
  * F2 Tippen-mit-Vorschlag: Code-Sprachen standardmäßig an, md/plain aus.
342164
342082
  * Unbekannte Sprachen fallen auf "an" zurück (Opt-out über tui.toml).
@@ -342176,7 +342094,6 @@ const DEFAULT_TUI_CONFIG = TuiConfigSchema.parse({
342176
342094
  disablePasteBurst: false,
342177
342095
  editorCommand: null,
342178
342096
  notifications: DEFAULT_NOTIFICATIONS_CONFIG,
342179
- upgrade: DEFAULT_UPGRADE_PREFERENCES,
342180
342097
  inlineSuggest: { languages: DEFAULT_INLINE_SUGGEST_LANGUAGES }
342181
342098
  });
342182
342099
  /**
@@ -342253,7 +342170,6 @@ function normalizeTuiConfig(config) {
342253
342170
  enabled: config.notifications?.enabled ?? DEFAULT_NOTIFICATIONS_CONFIG.enabled,
342254
342171
  condition: config.notifications?.notification_condition ?? DEFAULT_NOTIFICATIONS_CONFIG.condition
342255
342172
  },
342256
- upgrade: { autoInstall: config.upgrade?.auto_install ?? DEFAULT_UPGRADE_PREFERENCES.autoInstall },
342257
342173
  inlineSuggest: { languages: {
342258
342174
  ...DEFAULT_INLINE_SUGGEST_LANGUAGES,
342259
342175
  ...config.inline_suggest?.languages
@@ -342278,9 +342194,6 @@ command = "${escapeTomlBasicString(config.editorCommand ?? "")}" # Empty uses $V
342278
342194
  enabled = ${String(config.notifications.enabled)} # true | false
342279
342195
  notification_condition = "${config.notifications.condition}" # "unfocused" | "always"
342280
342196
 
342281
- [upgrade]
342282
- auto_install = ${String(config.upgrade.autoInstall)} # true | false
342283
-
342284
342197
  # F2 Tippen-mit-Vorschlag: Inline-Suggest je Sprache an/aus (wirkt ohne Neustart)
342285
342198
  [inline_suggest.languages]
342286
342199
  ${renderInlineSuggestLanguages(config.inlineSuggest.languages)}
@@ -342609,121 +342522,6 @@ function readServerTelemetryConfig(configPath) {
342609
342522
  }
342610
342523
  }
342611
342524
  //#endregion
342612
- //#region src/cli/update/types.ts
342613
- function emptyUpdateCache() {
342614
- return {
342615
- source: "cdn",
342616
- checkedAt: null,
342617
- latest: null,
342618
- manifest: null
342619
- };
342620
- }
342621
- function emptyUpdateInstallState() {
342622
- return {
342623
- active: null,
342624
- lastFailure: null,
342625
- lastSuccess: null
342626
- };
342627
- }
342628
- //#endregion
342629
- //#region src/cli/update/source.ts
342630
- const nodeRequire$5 = createRequire(import.meta.url);
342631
- let cachedSea$1;
342632
- function loadSeaModule$2() {
342633
- if (cachedSea$1 !== void 0) return cachedSea$1;
342634
- try {
342635
- cachedSea$1 = nodeRequire$5("node:sea");
342636
- } catch {
342637
- cachedSea$1 = null;
342638
- }
342639
- return cachedSea$1;
342640
- }
342641
- /** Runtime SEA detection — true when running as a packaged native binary. */
342642
- function detectNativeInstall() {
342643
- const sea = loadSeaModule$2();
342644
- if (sea === null) return false;
342645
- try {
342646
- return sea.isSea();
342647
- } catch {
342648
- return false;
342649
- }
342650
- }
342651
- const PNPM_PATH_SEGMENT = "pnpm/global/";
342652
- const YARN_PATH_SEGMENTS = [".config/yarn/global/", "/.yarn/global/"];
342653
- const BUN_PATH_SEGMENT = ".bun/install/global/";
342654
- const HOMEBREW_PATH_SEGMENT = "/cellar/";
342655
- function normalizeForHeuristic(filePath) {
342656
- return filePath.replaceAll("\\", "/").toLowerCase();
342657
- }
342658
- /**
342659
- * Heuristic classification by package root path segments. Returns the
342660
- * matching `InstallSource` or `null` if no heuristic matches (caller should
342661
- * fall through to npm-prefix comparison).
342662
- */
342663
- function classifyByPathHeuristic(packageRoot) {
342664
- const normalized = normalizeForHeuristic(packageRoot);
342665
- if (normalized.includes(PNPM_PATH_SEGMENT)) return "pnpm-global";
342666
- for (const seg of YARN_PATH_SEGMENTS) if (normalized.includes(seg)) return "yarn-global";
342667
- if (normalized.includes(BUN_PATH_SEGMENT)) return "bun-global";
342668
- if (normalized.includes(HOMEBREW_PATH_SEGMENT)) return "homebrew";
342669
- return null;
342670
- }
342671
- function npmCommand(platform) {
342672
- return platform === "win32" ? "npm.cmd" : "npm";
342673
- }
342674
- function execFileText(command, args) {
342675
- return new Promise((resolveOutput, reject) => {
342676
- execFile(command, [...args], { encoding: "utf-8" }, (error, stdout) => {
342677
- if (error) {
342678
- reject(error);
342679
- return;
342680
- }
342681
- resolveOutput(stdout);
342682
- });
342683
- });
342684
- }
342685
- function normalizePathForComparison(filePath, platform) {
342686
- const trimmed = filePath.trim();
342687
- if (trimmed.length === 0) return null;
342688
- try {
342689
- return normalizeResolvedPath(realpathSync(trimmed), platform);
342690
- } catch {
342691
- return normalizeResolvedPath(resolve(trimmed), platform);
342692
- }
342693
- }
342694
- function normalizeResolvedPath(filePath, platform) {
342695
- const resolvedPath = resolve(filePath);
342696
- return platform === "win32" ? resolvedPath.toLowerCase() : resolvedPath;
342697
- }
342698
- function candidateGlobalPackageDirs(globalPrefix, platform) {
342699
- if (platform === "win32") return [join(globalPrefix, "node_modules", NPM_PACKAGE_NAME)];
342700
- return [join(globalPrefix, "lib", "node_modules", NPM_PACKAGE_NAME), join(globalPrefix, "node_modules", NPM_PACKAGE_NAME)];
342701
- }
342702
- function classifyInstallSource(packageRoot, globalPrefix, platform = process.platform) {
342703
- const normalizedPackageRoot = normalizePathForComparison(packageRoot, platform);
342704
- if (normalizedPackageRoot === null) return "unsupported";
342705
- for (const candidate of candidateGlobalPackageDirs(globalPrefix, platform)) if (normalizePathForComparison(candidate, platform) === normalizedPackageRoot) return "npm-global";
342706
- return "unsupported";
342707
- }
342708
- async function detectInstallSource(deps = {}) {
342709
- const platform = deps.platform ?? process.platform;
342710
- const resolved = {
342711
- getPackageRoot: deps.getPackageRoot ?? getHostPackageRoot,
342712
- getGlobalPrefix: deps.getGlobalPrefix ?? (() => execFileText(npmCommand(platform), ["prefix", "-g"]).then((text) => text.trim())),
342713
- detectNative: deps.detectNative ?? detectNativeInstall,
342714
- platform
342715
- };
342716
- if (resolved.detectNative()) return "native";
342717
- const packageRoot = resolved.getPackageRoot();
342718
- const heuristic = classifyByPathHeuristic(packageRoot);
342719
- if (heuristic !== null) return heuristic;
342720
- try {
342721
- return classifyInstallSource(packageRoot, await resolved.getGlobalPrefix(), resolved.platform);
342722
- } catch {
342723
- return "unsupported";
342724
- }
342725
- }
342726
- //#endregion
342727
342525
  //#region src/utils/process/shell-env.ts
342728
342526
  function detectMultiplexer() {
342729
342527
  if (process.env["TMUX"]) return "tmux";
@@ -402512,7 +402310,7 @@ function parseCommaSeparated(value) {
402512
402310
  if (value === void 0 || value.trim().length === 0) return void 0;
402513
402311
  return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
402514
402312
  }
402515
- function createProgram(version, onMain, onPluginNodeRunner = () => {}, onUpgrade = () => {}) {
402313
+ function createProgram(version, onMain, onPluginNodeRunner = () => {}) {
402516
402314
  const program = new Command(CLI_COMMAND_NAME).description("The Starting Point for Next-Gen Agents").version(version, "-V, --version").allowUnknownOption(false).configureHelp({ helpWidth: 100 }).helpOption("-h, --help", "Show help.").usage("[options] [command]").addHelpText("after", "\nDocumentation: https://blun.ai\n");
402517
402315
  program.addOption(new Option("-S, --session [id]", "Resume a session. With ID: resume that session. Without ID: interactively pick.").argParser((val) => val === true ? "" : val)).addOption(new Option("-r, --resume [id]").hideHelp().argParser((val) => val === true ? "" : val)).option("-c, --continue", "Continue the previous session for the working directory.", false).addOption(new Option("-C").hideHelp().default(false)).option("-y, --yolo", "Automatically approve all actions.", false).option("--auto", "Start in auto permission mode.", false).addOption(new Option("-p, --prompt <prompt>", "Run one prompt non-interactively and print the response.")).addOption(new Option("--output-format <format>", "Output format for prompt mode. Defaults to text.").choices([
402518
402316
  "text",
@@ -402540,9 +402338,6 @@ function createProgram(version, onMain, onPluginNodeRunner = () => {}, onUpgrade
402540
402338
  registerPersonalMemoryCommand(program);
402541
402339
  registerDoctorCommand(program);
402542
402340
  registerVisCommand(program);
402543
- program.command("upgrade").alias("update").description("Upgrade BLUN to the latest version.").action(async () => {
402544
- await onUpgrade();
402545
- });
402546
402341
  program.command("__plugin_run_node", { hidden: true }).argument("<entry>").argument("[args...]").allowUnknownOption(true).action((entry, args) => {
402547
402342
  onPluginNodeRunner(entry, args);
402548
402343
  });
@@ -403387,13 +403182,6 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
403387
403182
  priority: 100,
403388
403183
  availability: "always"
403389
403184
  },
403390
- {
403391
- name: "update",
403392
- aliases: [],
403393
- descriptionKey: "command.update.description",
403394
- priority: 100,
403395
- availability: "always"
403396
- },
403397
403185
  {
403398
403186
  name: "premortem",
403399
403187
  aliases: [],
@@ -414236,8 +414024,6 @@ registerUiCatalogFragment({
414236
414024
  "settings.editor.description": "Set the external editor command.",
414237
414025
  "settings.experiments.label": "Experiments",
414238
414026
  "settings.experiments.description": "Turn experimental features on or off.",
414239
- "settings.upgrade.label": "Automatic updates",
414240
- "settings.upgrade.description": "Turn automatic CLI updates on or off.",
414241
414027
  "settings.usage.label": "Usage",
414242
414028
  "settings.usage.description": "Show session tokens, context window, and plan quotas.",
414243
414029
  "theme.title": "Select theme",
@@ -414247,7 +414033,6 @@ registerUiCatalogFragment({
414247
414033
  "theme.custom": "Custom: {name}",
414248
414034
  "editor.title": "Select external editor",
414249
414035
  "editor.autoDetect": "Detect automatically ($VISUAL / $EDITOR)",
414250
- "updates.title": "Automatic updates",
414251
414036
  "updates.on.label": "On",
414252
414037
  "updates.on.description": "Install new versions in the background.",
414253
414038
  "updates.off.label": "Off",
@@ -414316,8 +414101,6 @@ registerUiCatalogFragment({
414316
414101
  "settings.editor.description": "Befehl für den externen Editor festlegen.",
414317
414102
  "settings.experiments.label": "Experimente",
414318
414103
  "settings.experiments.description": "Experimentelle Funktionen ein- oder ausschalten.",
414319
- "settings.upgrade.label": "Automatische Updates",
414320
- "settings.upgrade.description": "Automatische Updates der Konsole ein- oder ausschalten.",
414321
414104
  "settings.usage.label": "Verbrauch",
414322
414105
  "settings.usage.description": "Sitzungstokens, Kontextfenster und Plankontingente anzeigen.",
414323
414106
  "theme.title": "Design auswählen",
@@ -414327,7 +414110,6 @@ registerUiCatalogFragment({
414327
414110
  "theme.custom": "Benutzerdefiniert: {name}",
414328
414111
  "editor.title": "Externen Editor auswählen",
414329
414112
  "editor.autoDetect": "Automatisch erkennen ($VISUAL / $EDITOR)",
414330
- "updates.title": "Automatische Updates",
414331
414113
  "updates.on.label": "An",
414332
414114
  "updates.on.description": "Neue Versionen im Hintergrund installieren.",
414333
414115
  "updates.off.label": "Aus",
@@ -414396,8 +414178,6 @@ registerUiCatalogFragment({
414396
414178
  "settings.editor.description": "Establecer el comando del editor externo.",
414397
414179
  "settings.experiments.label": "Experimentos",
414398
414180
  "settings.experiments.description": "Activar o desactivar funciones experimentales.",
414399
- "settings.upgrade.label": "Actualizaciones automáticas",
414400
- "settings.upgrade.description": "Activar o desactivar las actualizaciones automáticas de la CLI.",
414401
414181
  "settings.usage.label": "Uso",
414402
414182
  "settings.usage.description": "Mostrar los tokens de la sesión, la ventana de contexto y las cuotas del plan.",
414403
414183
  "theme.title": "Seleccionar un tema",
@@ -414407,7 +414187,6 @@ registerUiCatalogFragment({
414407
414187
  "theme.custom": "Personalizado: {name}",
414408
414188
  "editor.title": "Seleccionar un editor externo",
414409
414189
  "editor.autoDetect": "Detectar automáticamente ($VISUAL / $EDITOR)",
414410
- "updates.title": "Actualizaciones automáticas",
414411
414190
  "updates.on.label": "Activadas",
414412
414191
  "updates.on.description": "Instalar las versiones nuevas en segundo plano.",
414413
414192
  "updates.off.label": "Desactivadas",
@@ -414476,8 +414255,6 @@ registerUiCatalogFragment({
414476
414255
  "settings.editor.description": "Définir la commande de l’éditeur externe.",
414477
414256
  "settings.experiments.label": "Fonctions expérimentales",
414478
414257
  "settings.experiments.description": "Activer ou désactiver les fonctions expérimentales.",
414479
- "settings.upgrade.label": "Mises à jour automatiques",
414480
- "settings.upgrade.description": "Activer ou désactiver les mises à jour automatiques de la CLI.",
414481
414258
  "settings.usage.label": "Utilisation",
414482
414259
  "settings.usage.description": "Afficher les jetons de la session, la fenêtre de contexte et les quotas du forfait.",
414483
414260
  "theme.title": "Choisir le thème",
@@ -414487,7 +414264,6 @@ registerUiCatalogFragment({
414487
414264
  "theme.custom": "Personnalisé : {name}",
414488
414265
  "editor.title": "Choisir l’éditeur externe",
414489
414266
  "editor.autoDetect": "Détecter automatiquement ($VISUAL / $EDITOR)",
414490
- "updates.title": "Mises à jour automatiques",
414491
414267
  "updates.on.label": "Activées",
414492
414268
  "updates.on.description": "Installer les nouvelles versions en arrière-plan.",
414493
414269
  "updates.off.label": "Désactivées",
@@ -414556,8 +414332,6 @@ registerUiCatalogFragment({
414556
414332
  "settings.editor.description": "Ange kommandot för den externa redigeraren.",
414557
414333
  "settings.experiments.label": "Experiment",
414558
414334
  "settings.experiments.description": "Slå på eller av experimentella funktioner.",
414559
- "settings.upgrade.label": "Automatiska uppdateringar",
414560
- "settings.upgrade.description": "Slå på eller av automatiska CLI-uppdateringar.",
414561
414335
  "settings.usage.label": "Användning",
414562
414336
  "settings.usage.description": "Visa sessionens token, kontextfönster och abonnemangskvoter.",
414563
414337
  "theme.title": "Välj tema",
@@ -414567,7 +414341,6 @@ registerUiCatalogFragment({
414567
414341
  "theme.custom": "Anpassat: {name}",
414568
414342
  "editor.title": "Välj extern redigerare",
414569
414343
  "editor.autoDetect": "Identifiera automatiskt ($VISUAL / $EDITOR)",
414570
- "updates.title": "Automatiska uppdateringar",
414571
414344
  "updates.on.label": "På",
414572
414345
  "updates.on.description": "Installera nya versioner i bakgrunden.",
414573
414346
  "updates.off.label": "Av",
@@ -414636,8 +414409,6 @@ registerUiCatalogFragment({
414636
414409
  "settings.editor.description": "Nastavte příkaz externího editoru.",
414637
414410
  "settings.experiments.label": "Experimenty",
414638
414411
  "settings.experiments.description": "Zapněte nebo vypněte experimentální funkce.",
414639
- "settings.upgrade.label": "Automatické aktualizace",
414640
- "settings.upgrade.description": "Zapněte nebo vypněte automatické aktualizace CLI.",
414641
414412
  "settings.usage.label": "Využití",
414642
414413
  "settings.usage.description": "Zobrazit tokeny relace, okno kontextu a kvóty plánu.",
414643
414414
  "theme.title": "Vyberte motiv",
@@ -414647,7 +414418,6 @@ registerUiCatalogFragment({
414647
414418
  "theme.custom": "Vlastní: {name}",
414648
414419
  "editor.title": "Vyberte externí editor",
414649
414420
  "editor.autoDetect": "Automaticky detekovat ($VISUAL / $EDITOR)",
414650
- "updates.title": "Automatické aktualizace",
414651
414421
  "updates.on.label": "Zapnuto",
414652
414422
  "updates.on.description": "Instalujte nové verze na pozadí.",
414653
414423
  "updates.off.label": "Vypnuto",
@@ -419883,11 +419653,6 @@ registerUiCatalogFragment({
419883
419653
  "config.experiments.updatedReloaded": "Experimental features updated. Session reloaded.",
419884
419654
  "config.experiments.updated": "Experimental features updated.",
419885
419655
  "config.experiments.updateFailed": "Failed to update experimental features: {error}",
419886
- "config.update.alreadyEnabled": "Automatic updates are already enabled.",
419887
- "config.update.alreadyDisabled": "Automatic updates are already disabled.",
419888
- "config.update.saveFailed": "Failed to save automatic update setting: {error}",
419889
- "config.update.enabled": "Automatic updates enabled.",
419890
- "config.update.disabled": "Automatic updates disabled.",
419891
419656
  "config.permission.unchanged": "Permission mode unchanged: {mode}.",
419892
419657
  "config.permission.setFailed": "Failed to set permission mode: {error}",
419893
419658
  "config.permission.set": "Permission mode: {mode}",
@@ -419940,11 +419705,6 @@ registerUiCatalogFragment({
419940
419705
  "config.experiments.updatedReloaded": "Experimentelle Funktionen aktualisiert. Sitzung neu geladen.",
419941
419706
  "config.experiments.updated": "Experimentelle Funktionen aktualisiert.",
419942
419707
  "config.experiments.updateFailed": "Experimentelle Funktionen konnten nicht aktualisiert werden: {error}",
419943
- "config.update.alreadyEnabled": "Automatische Updates sind bereits aktiviert.",
419944
- "config.update.alreadyDisabled": "Automatische Updates sind bereits deaktiviert.",
419945
- "config.update.saveFailed": "Die Einstellung für automatische Updates konnte nicht gespeichert werden: {error}",
419946
- "config.update.enabled": "Automatische Updates aktiviert.",
419947
- "config.update.disabled": "Automatische Updates deaktiviert.",
419948
419708
  "config.permission.unchanged": "Berechtigungsmodus unverändert: {mode}.",
419949
419709
  "config.permission.setFailed": "Berechtigungsmodus konnte nicht gesetzt werden: {error}",
419950
419710
  "config.permission.set": "Berechtigungsmodus: {mode}",
@@ -419997,11 +419757,6 @@ registerUiCatalogFragment({
419997
419757
  "config.experiments.updatedReloaded": "Funciones experimentales actualizadas. Sesión recargada.",
419998
419758
  "config.experiments.updated": "Funciones experimentales actualizadas.",
419999
419759
  "config.experiments.updateFailed": "No se pudieron actualizar las funciones experimentales: {error}",
420000
- "config.update.alreadyEnabled": "Las actualizaciones automáticas ya están activadas.",
420001
- "config.update.alreadyDisabled": "Las actualizaciones automáticas ya están desactivadas.",
420002
- "config.update.saveFailed": "No se pudo guardar la configuración de actualizaciones automáticas: {error}",
420003
- "config.update.enabled": "Actualizaciones automáticas activadas.",
420004
- "config.update.disabled": "Actualizaciones automáticas desactivadas.",
420005
419760
  "config.permission.unchanged": "El modo de permisos no ha cambiado: {mode}.",
420006
419761
  "config.permission.setFailed": "No se pudo establecer el modo de permisos: {error}",
420007
419762
  "config.permission.set": "Modo de permisos: {mode}",
@@ -420054,11 +419809,6 @@ registerUiCatalogFragment({
420054
419809
  "config.experiments.updatedReloaded": "Fonctionnalités expérimentales mises à jour. Session rechargée.",
420055
419810
  "config.experiments.updated": "Fonctionnalités expérimentales mises à jour.",
420056
419811
  "config.experiments.updateFailed": "Impossible de mettre à jour les fonctionnalités expérimentales : {error}",
420057
- "config.update.alreadyEnabled": "Les mises à jour automatiques sont déjà activées.",
420058
- "config.update.alreadyDisabled": "Les mises à jour automatiques sont déjà désactivées.",
420059
- "config.update.saveFailed": "Impossible d’enregistrer le réglage des mises à jour automatiques : {error}",
420060
- "config.update.enabled": "Mises à jour automatiques activées.",
420061
- "config.update.disabled": "Mises à jour automatiques désactivées.",
420062
419812
  "config.permission.unchanged": "Mode d’autorisation inchangé : {mode}.",
420063
419813
  "config.permission.setFailed": "Impossible de définir le mode d’autorisation : {error}",
420064
419814
  "config.permission.set": "Mode d’autorisation : {mode}",
@@ -420111,11 +419861,6 @@ registerUiCatalogFragment({
420111
419861
  "config.experiments.updatedReloaded": "Experimentella funktioner har uppdaterats. Sessionen har lästs in på nytt.",
420112
419862
  "config.experiments.updated": "Experimentella funktioner har uppdaterats.",
420113
419863
  "config.experiments.updateFailed": "Det gick inte att uppdatera experimentella funktioner: {error}",
420114
- "config.update.alreadyEnabled": "Automatiska uppdateringar är redan aktiverade.",
420115
- "config.update.alreadyDisabled": "Automatiska uppdateringar är redan inaktiverade.",
420116
- "config.update.saveFailed": "Det gick inte att spara inställningen för automatiska uppdateringar: {error}",
420117
- "config.update.enabled": "Automatiska uppdateringar har aktiverats.",
420118
- "config.update.disabled": "Automatiska uppdateringar har inaktiverats.",
420119
419864
  "config.permission.unchanged": "Behörighetsläget är oförändrat: {mode}.",
420120
419865
  "config.permission.setFailed": "Det gick inte att ställa in behörighetsläget: {error}",
420121
419866
  "config.permission.set": "Behörighetsläge: {mode}",
@@ -420168,11 +419913,6 @@ registerUiCatalogFragment({
420168
419913
  "config.experiments.updatedReloaded": "Experimentální funkce aktualizovány. Relace znovu načtena.",
420169
419914
  "config.experiments.updated": "Experimentální funkce aktualizovány.",
420170
419915
  "config.experiments.updateFailed": "Nepodařilo se aktualizovat experimentální funkce: {error}",
420171
- "config.update.alreadyEnabled": "Automatické aktualizace jsou již povoleny.",
420172
- "config.update.alreadyDisabled": "Automatické aktualizace jsou již zakázány.",
420173
- "config.update.saveFailed": "Nepodařilo se uložit nastavení automatické aktualizace: {error}",
420174
- "config.update.enabled": "Automatické aktualizace povoleny.",
420175
- "config.update.disabled": "Automatické aktualizace zakázány.",
420176
419916
  "config.permission.unchanged": "Režim oprávnění nezměněn: {mode}.",
420177
419917
  "config.permission.setFailed": "Nepodařilo se nastavit režim oprávnění: {error}",
420178
419918
  "config.permission.set": "Režim oprávnění: {mode}",
@@ -420329,7 +420069,6 @@ async function currentTuiConfig(host) {
420329
420069
  editorCommand: host.state.appState.editorCommand,
420330
420070
  disablePasteBurst: host.state.appState.disablePasteBurst ?? DEFAULT_TUI_CONFIG.disablePasteBurst,
420331
420071
  notifications: host.state.appState.notifications,
420332
- upgrade: host.state.appState.upgrade,
420333
420072
  inlineSuggest
420334
420073
  };
420335
420074
  }
@@ -495919,43 +495658,6 @@ async function executeSlashCommand(host, input) {
495919
495658
  return;
495920
495659
  }
495921
495660
  }
495922
- async function handleUpdateCommand(host) {
495923
- const profileHome = process.env["BLUN_HOME"];
495924
- if (typeof profileHome !== "string" || profileHome.length === 0) {
495925
- host.showError(uiText("update.saveFailed", { error: "BLUN_HOME" }));
495926
- return;
495927
- }
495928
- const currentMode = readRunningUpdateMode(profileHome);
495929
- const selectedMode = await new Promise((resolve) => {
495930
- host.mountEditorReplacement(new ChoicePickerComponent({
495931
- title: uiText("update.title"),
495932
- hint: uiText("update.hint"),
495933
- currentValue: currentMode,
495934
- options: Object.values(RUNNING_UPDATE_MODES).map((mode) => ({
495935
- value: mode,
495936
- label: uiText(`update.${mode}.label`),
495937
- description: uiText(`update.${mode}.description`)
495938
- })),
495939
- onSelect: resolve,
495940
- onCancel: () => resolve(void 0)
495941
- }));
495942
- });
495943
- host.restoreEditor();
495944
- if (selectedMode === void 0) return;
495945
- try {
495946
- writeRunningUpdateMode(profileHome, selectedMode);
495947
- } catch (error) {
495948
- host.showError(uiText("update.saveFailed", { error: formatErrorMessage$2(error) }));
495949
- return;
495950
- }
495951
- host.runningUpdatePreparedVersion = void 0;
495952
- host.runningUpdatePreparedMode = void 0;
495953
- host.runningUpdatePreparedNoticeVersion = void 0;
495954
- if (process.connected) try {
495955
- process.send({ type: RUNNING_UPDATE_MODE_MESSAGE, mode: selectedMode });
495956
- } catch {}
495957
- host.showStatus(uiText("update.saved", { mode: uiText(`update.${selectedMode}.label`) }), "success");
495958
- }
495959
495661
  function buildPremortemPrompt(plan) {
495960
495662
  return `<premortem-mode>
495961
495663
  Assume this plan failed six months from now. Identify the most plausible failure modes, their early warning signs, concrete preventive measures, and explicit stop or decision points. Rank findings by impact and likelihood. Separate evidence from assumptions. End with the three actions that reduce the most risk.
@@ -496039,9 +495741,6 @@ async function handleBuiltInSlashCommand(host, name, args) {
496039
495741
  case "settings":
496040
495742
  showSettingsSelector(host);
496041
495743
  return;
496042
- case "update":
496043
- await handleUpdateCommand(host);
496044
- return;
496045
495744
  case "premortem":
496046
495745
  await handlePremortemCommand(host, args);
496047
495746
  return;
@@ -508199,7 +507898,6 @@ var SessionEventHandler = class {
508199
507898
  this.goalCompletionTurnEnded = true;
508200
507899
  this.scheduleQueuedGoalPromotion();
508201
507900
  if (event.reason === "completed") this.host.refreshManagedQuotaWindows();
508202
- requestRunningUpdateAtSafeBoundary(this.host);
508203
507901
  }
508204
507902
  handleStepBegin(event) {
508205
507903
  this.turnWatchdog.recordProgress();
@@ -516166,107 +515864,8 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
516166
515864
  }
516167
515865
  //#endregion
516168
515866
  //#region src/tui/blun-tui.ts
516169
- const {
516170
- AUTO_UPDATE_SETTLE_MS,
516171
- RUNNING_UPDATE_HANDOFF_ACK_MESSAGE,
516172
- RUNNING_UPDATE_HANDOFF_EXIT_CODE,
516173
- RUNNING_UPDATE_HANDOFF_MESSAGE,
516174
- RUNNING_UPDATE_MODE_MESSAGE,
516175
- RUNNING_UPDATE_PREPARED_MESSAGE,
516176
- RUNTIME_EXIT_INTENT_MESSAGE,
516177
- RUNTIME_READY_MESSAGE,
516178
- isSafeRuntimeBoundary,
516179
- pruneRunningUpdateReleases
516180
- } = __require("./bin/running-update.cjs");
516181
- const {
516182
- RUNNING_UPDATE_MODES,
516183
- readRunningUpdateMode,
516184
- writeRunningUpdateMode
516185
- } = __require("./bin/running-update-preference.cjs");
516186
515867
  const { resolveStreamingFlushInterval } = __require("./bin/streaming-flush-performance-policy.cjs");
516187
515868
  const { shouldParseStreamingToolCallImmediately } = __require("./bin/tool-stream-preview-policy.cjs");
516188
- const RUNNING_UPDATE_RESUME_SESSION_ENV = "BLUN_RUNNING_UPDATE_RESUME_SESSION_ID";
516189
- function shouldContinueActiveGoalAfterRunningUpdate(expectedSessionId, currentSessionId, goalStatus) {
516190
- return typeof expectedSessionId === "string" && expectedSessionId.length > 0 && currentSessionId === expectedSessionId && goalStatus === "active";
516191
- }
516192
- function sendRunningRuntimeSession(tui) {
516193
- if (!process.connected) return;
516194
- const sessionId = tui.getCurrentSessionId();
516195
- if (sessionId.length === 0) return;
516196
- try {
516197
- process.send({
516198
- type: RUNTIME_READY_MESSAGE,
516199
- sessionId,
516200
- cwd: tui.state.appState.workDir
516201
- });
516202
- } catch {}
516203
- }
516204
- function notifyRunningRuntimeReady(tui) {
516205
- if (typeof process.env["BLUN_SHARED_HOME"] === "string" && process.env["BLUN_SHARED_HOME"].length > 0) void pruneRunningUpdateReleases(process.env["BLUN_SHARED_HOME"], {
516206
- activePackageRoot: __dirname
516207
- }).catch(() => {});
516208
- sendRunningRuntimeSession(tui);
516209
- }
516210
- function requestRunningUpdateAtSafeBoundary(tui) {
516211
- const version = tui.runningUpdatePreparedVersion;
516212
- const mode = tui.runningUpdatePreparedMode;
516213
- if (typeof version !== "string" || mode !== RUNNING_UPDATE_MODES.RESUME && mode !== RUNNING_UPDATE_MODES.NEW) return false;
516214
- if (!isSafeRuntimeBoundary({
516215
- isShuttingDown: tui.isShuttingDown,
516216
- streamingPhase: tui.state.appState.streamingPhase,
516217
- isCompacting: tui.state.appState.isCompacting,
516218
- queuedMessages: tui.state.queuedMessages.length,
516219
- queuedMessagesDurable: tui.state.queuedMessages.every((item) => (item.mode === "channel" || item.mode === "channel-command") && typeof item.channelAcknowledge === "function"),
516220
- activeToolCalls: tui.streamingUI.hasActiveToolCalls() ? 1 : 0,
516221
- shellCommands: tui.shellOutputStreams.size,
516222
- queueCommandRunning: tui.queueCommandRunning
516223
- })) {
516224
- if (tui.runningUpdatePreparedNoticeVersion !== version) {
516225
- tui.runningUpdatePreparedNoticeVersion = version;
516226
- const notice = runningUpdateNoticeText(version, mode);
516227
- if (typeof tui.showRunningUpdateStatus === "function") tui.showRunningUpdateStatus(notice);
516228
- else tui.showStatus(notice, "success");
516229
- }
516230
- return false;
516231
- }
516232
- if (!process.connected || tui.runningUpdateHandoffRequestedVersion === version) return false;
516233
- const sessionId = tui.getCurrentSessionId();
516234
- if (typeof sessionId !== "string" || sessionId.length === 0) return false;
516235
- tui.runningUpdateHandoffRequestedVersion = version;
516236
- try {
516237
- process.send({
516238
- type: RUNNING_UPDATE_HANDOFF_MESSAGE,
516239
- version,
516240
- mode,
516241
- sessionId,
516242
- cwd: tui.state.appState.workDir
516243
- });
516244
- return true;
516245
- } catch {
516246
- tui.runningUpdateHandoffRequestedVersion = void 0;
516247
- return false;
516248
- }
516249
- }
516250
- function runningUpdateNoticeText(version, mode) {
516251
- const statusKey = mode === RUNNING_UPDATE_MODES.RESUME ? "update.resume.description" : "update.new.description";
516252
- const personaLanguage = readLanguage();
516253
- if (typeof personaLanguage !== "string" || personaLanguage.length === 0) return `${version} · ${uiText(statusKey)}`;
516254
- const personaLocale = Object.entries(PERSONA_LANGUAGE_BY_LOCALE).find((entry) => entry[1] === personaLanguage)?.[0];
516255
- if (typeof personaLocale !== "string" || !AVAILABLE_UI_LOCALE_SET.has(personaLocale)) return version;
516256
- return `${version} · ${uiTextFor(personaLocale, statusKey)}`;
516257
- }
516258
- function installRunningUpdateListener(tui) {
516259
- const handler = (message) => {
516260
- if (message?.type === RUNNING_UPDATE_PREPARED_MESSAGE && typeof message.version === "string" && (message.mode === RUNNING_UPDATE_MODES.RESUME || message.mode === RUNNING_UPDATE_MODES.NEW)) {
516261
- tui.runningUpdatePreparedVersion = message.version;
516262
- tui.runningUpdatePreparedMode = message.mode;
516263
- requestRunningUpdateAtSafeBoundary(tui);
516264
- }
516265
- if (message?.type === RUNNING_UPDATE_HANDOFF_ACK_MESSAGE && message.version === tui.runningUpdateHandoffRequestedVersion && message.sessionId === tui.getCurrentSessionId()) void tui.stop(RUNNING_UPDATE_HANDOFF_EXIT_CODE);
516266
- };
516267
- process.on("message", handler);
516268
- return () => process.off("message", handler);
516269
- }
516270
515869
  function loadingTipKind(mode) {
516271
515870
  if (mode === "waiting" || mode === "tool") return "blun";
516272
515871
  if (mode === "composing") return "composing";
@@ -516304,7 +515903,6 @@ function createInitialAppState(input) {
516304
515903
  editorCommand: input.tuiConfig.editorCommand,
516305
515904
  disablePasteBurst: input.tuiConfig.disablePasteBurst,
516306
515905
  notifications: input.tuiConfig.notifications,
516307
- upgrade: input.tuiConfig.upgrade,
516308
515906
  availableModels: {},
516309
515907
  availableProviders: {},
516310
515908
  sessionTitle: null,
@@ -516352,11 +515950,6 @@ var BlunTUI = class {
516352
515950
  startupLoginRequired = false;
516353
515951
  startupWorkspaceSelectionPending = false;
516354
515952
  startupGoalPromptedSessionId;
516355
- runningUpdatePreparedVersion;
516356
- runningUpdatePreparedMode;
516357
- runningUpdatePreparedNoticeVersion;
516358
- runningUpdatePreparedNoticeComponent;
516359
- runningUpdateMessageDispose;
516360
515953
  startupPhaseMs = {};
516361
515954
  lastActivityMode;
516362
515955
  currentLoadingTip = void 0;
@@ -516493,7 +516086,6 @@ var BlunTUI = class {
516493
516086
  this.editorKeyboard.install();
516494
516087
  this.scrollbackController = new ScrollbackController(this.state);
516495
516088
  this.scrollbackController.install();
516496
- this.runningUpdateMessageDispose = installRunningUpdateListener(this);
516497
516089
  this.buildLayout();
516498
516090
  }
516499
516091
  getSlashCommands() {
@@ -516754,8 +516346,7 @@ var BlunTUI = class {
516754
516346
  if (this.session !== void 0) {
516755
516347
  this.sessionEventHandler.startSubscription();
516756
516348
  if (shouldReplayHistory) {
516757
- const continuedAfterUpdate = this.continueActiveGoalAfterRunningUpdateIfNeeded();
516758
- if (!continuedAfterUpdate) await this.promptStartupResumeGoalIfNeeded();
516349
+ await this.promptStartupResumeGoalIfNeeded();
516759
516350
  if (this.aborted) return;
516760
516351
  }
516761
516352
  this.showSessionWarnings(this.session);
@@ -516920,20 +516511,10 @@ var BlunTUI = class {
516920
516511
  }
516921
516512
  if (choice === "resume") await handleGoalCommand(this, "resume");
516922
516513
  }
516923
- continueActiveGoalAfterRunningUpdateIfNeeded() {
516924
- const expectedSessionId = process.env[RUNNING_UPDATE_RESUME_SESSION_ENV];
516925
- delete process.env[RUNNING_UPDATE_RESUME_SESSION_ENV];
516926
- if (!shouldContinueActiveGoalAfterRunningUpdate(expectedSessionId, this.session?.id, this.state.appState.goal?.status)) return false;
516927
- this.sendNormalUserInput(RESUME_GOAL_INPUT);
516928
- return true;
516929
- }
516930
516514
  async stop(exitCode) {
516931
516515
  if (this.isShuttingDown) return;
516932
516516
  this.isShuttingDown = true;
516933
516517
  await this.flushInputDraft();
516934
- if (exitCode === 0 && process.connected) try {
516935
- process.send({ type: RUNTIME_EXIT_INTENT_MESSAGE });
516936
- } catch {}
516937
516518
  this.unregisterSignalHandlers();
516938
516519
  this.aborted = true;
516939
516520
  this.channelQueueDeadline?.dispose();
@@ -516954,8 +516535,6 @@ var BlunTUI = class {
516954
516535
  this.managedQuotaWarningController.dispose();
516955
516536
  this.state.loopIndicator.dispose();
516956
516537
  this.state.footer.dispose();
516957
- this.runningUpdateMessageDispose?.();
516958
- this.runningUpdateMessageDispose = void 0;
516959
516538
  for (const dispose of this.reverseRpcDisposers) dispose();
516960
516539
  this.reverseRpcDisposers.length = 0;
516961
516540
  this.disposeTerminalTracking();
@@ -517180,7 +516759,6 @@ var BlunTUI = class {
517180
516759
  if (this.shellOutputStreams.size === 0) {
517181
516760
  this.setAppState({ streamingPhase: "idle" });
517182
516761
  this.drainOneQueuedMessage();
517183
- requestRunningUpdateAtSafeBoundary(this);
517184
516762
  }
517185
516763
  }
517186
516764
  queueDrainTimer;
@@ -518596,7 +518174,6 @@ var BlunTUI = class {
518596
518174
  if (resumeState?.warning !== void 0) this.showStatus(uiText("blunTui.warning", { warning: resumeState.warning }), "warning");
518597
518175
  this.showStatus(statusMessage);
518598
518176
  this.showSessionWarnings(session);
518599
- sendRunningRuntimeSession(this);
518600
518177
  }
518601
518178
  async reloadCurrentSessionView(session, statusMessage) {
518602
518179
  await this.personalMemoryController.clear(session);
@@ -518660,7 +518237,6 @@ var BlunTUI = class {
518660
518237
  this.showStatus(uiText("blunTui.session.started", { sessionId: session.id }));
518661
518238
  this.showSessionWarnings(session);
518662
518239
  this.showConfigWarningsIfAny();
518663
- sendRunningRuntimeSession(this);
518664
518240
  }
518665
518241
  /** Surface config.toml load warnings (degraded or kept-previous config) in the status bar. */
518666
518242
  async showConfigWarningsIfAny() {
@@ -518940,18 +518516,6 @@ var BlunTUI = class {
518940
518516
  for (const child of toDispose) if (hasDispose(child)) child.dispose();
518941
518517
  children.splice(0, children.length, ...newChildren);
518942
518518
  }
518943
- showRunningUpdateStatus(message) {
518944
- const next = new StatusMessageComponent(message, "success");
518945
- const current = this.runningUpdatePreparedNoticeComponent;
518946
- const children = this.state.transcriptContainer.children;
518947
- const currentIndex = current === void 0 ? -1 : children.indexOf(current);
518948
- if (currentIndex >= 0) {
518949
- if (hasDispose(current)) current.dispose();
518950
- children.splice(currentIndex, 1, next);
518951
- } else this.state.transcriptContainer.addChild(next);
518952
- this.runningUpdatePreparedNoticeComponent = next;
518953
- this.state.ui.requestRender();
518954
- }
518955
518519
  showStatus(message, color) {
518956
518520
  this.telegramRemoteCommandContext?.responses.push(message);
518957
518521
  this.state.transcriptContainer.addChild(new StatusMessageComponent(message, color));
@@ -519629,167 +519193,12 @@ var BlunTUI = class {
519629
519193
  }
519630
519194
  };
519631
519195
  //#endregion
519632
- //#region src/tui/utils/telegram-update-notice.ts
519633
- const MAX_ACCESS_FILE_BYTES = 256 * 1024;
519634
- const TELEGRAM_DM_ID = /^[1-9]\d*$/u;
519635
- function defaultTelegramStateDir() {
519636
- const explicit = process.env["BLUN_TELEGRAM_STATE_DIR"]?.trim();
519637
- if (explicit !== void 0 && explicit.length > 0) return explicit;
519638
- const blunHome = process.env["BLUN_HOME"]?.trim();
519639
- return join(blunHome !== void 0 && blunHome.length > 0 ? blunHome : join(homedir(), ".blun"), "channels", "telegram");
519640
- }
519641
- function hasConfiguredToken(stateDir) {
519642
- try {
519643
- return readFileSync(join(stateDir, ".env"), "utf8").replace(/^\uFEFF/u, "").split(/\r?\n/u).some((line) => {
519644
- const token = /^BLUN_TELEGRAM_BOT_TOKEN=(.+)$/u.exec(line)?.[1]?.trim();
519645
- return token !== void 0 && token.includes(":") && !/replace|paste|your[-_]|xxxx/iu.test(token);
519646
- });
519647
- } catch {
519648
- return false;
519649
- }
519650
- }
519651
- function readTelegramUpdateRecipients(stateDir = defaultTelegramStateDir()) {
519652
- try {
519653
- const filePath = join(stateDir, "access.json");
519654
- const info = lstatSync(filePath);
519655
- if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_ACCESS_FILE_BYTES) return [];
519656
- const parsed = JSON.parse(readFileSync(filePath, "utf8"));
519657
- if (!Array.isArray(parsed.allowFrom)) return [];
519658
- return [...new Set(parsed.allowFrom.map((value) => String(value).trim()).filter((value) => TELEGRAM_DM_ID.test(value)))];
519659
- } catch {
519660
- return [];
519661
- }
519662
- }
519663
- function claimTelegramUpdateNotice(version, chatId, receiptDir = join(homedir(), ".blun", "updates", "telegram-notices")) {
519664
- const safeVersion = version.trim().replace(/[^0-9A-Za-z._-]/gu, "_").slice(0, 64);
519665
- if (safeVersion.length === 0 || !TELEGRAM_DM_ID.test(chatId)) return null;
519666
- const receiptPath = join(receiptDir, `${safeVersion}-${chatId}.sent`);
519667
- try {
519668
- mkdirSync(receiptDir, {
519669
- recursive: true,
519670
- mode: 448
519671
- });
519672
- writeFileSync(receiptPath, `${(/* @__PURE__ */ new Date()).toISOString()}\n`, {
519673
- encoding: "utf8",
519674
- flag: "wx",
519675
- mode: 384
519676
- });
519677
- return receiptPath;
519678
- } catch {
519679
- return null;
519680
- }
519681
- }
519682
- async function sendTelegramUpdateNotice(text, options = {}) {
519683
- const stateDir = options.stateDir ?? defaultTelegramStateDir();
519684
- const configured = hasConfiguredToken(stateDir);
519685
- const recipients = configured ? readTelegramUpdateRecipients(stateDir) : [];
519686
- const send = options.send ?? ((chatId, message) => sendReplyFallback(chatId, message));
519687
- let sent = 0;
519688
- let attempted = 0;
519689
- for (const chatId of recipients) {
519690
- const receiptPath = options.version === void 0 ? void 0 : claimTelegramUpdateNotice(options.version, chatId, options.receiptDir);
519691
- if (options.version !== void 0 && receiptPath === null) continue;
519692
- attempted += 1;
519693
- if (await send(chatId, text).catch(() => false)) sent += 1;
519694
- else if (receiptPath !== void 0) try {
519695
- unlinkSync(receiptPath);
519696
- } catch {}
519697
- }
519698
- return {
519699
- configured,
519700
- attempted,
519701
- sent
519702
- };
519703
- }
519704
- //#endregion
519705
- //#region src/constant/update.ts
519706
- const UPDATE_PROMPT_PRIMARY = "#1783ff";
519707
- const UPDATE_PROMPT_SUCCESS = "#16A34A";
519708
- const UPDATE_PROMPT_WARNING = "#CA8A04";
519709
- const UPDATE_PROMPT_MUTED = "#999999";
519710
- const UPDATE_PROMPT_TEXT_DIM = "#6B7280";
519711
- //#endregion
519712
- //#region src/cli/update/notice.copy.ts
519713
- registerUiCatalogFragment({
519714
- en: {
519715
- "update.notice.updatedFrom": "BLUN King updated from v{fromVersion} to v{version}.",
519716
- "update.notice.updatedTo": "BLUN King updated to v{version}.",
519717
- "update.notice.highlights": "What's new:"
519718
- },
519719
- de: {
519720
- "update.notice.updatedFrom": "BLUN King wurde von v{fromVersion} auf v{version} aktualisiert.",
519721
- "update.notice.updatedTo": "BLUN King wurde auf v{version} aktualisiert.",
519722
- "update.notice.highlights": "Neu in dieser Version:"
519723
- },
519724
- es: {
519725
- "update.notice.updatedFrom": "BLUN King se ha actualizado de v{fromVersion} a v{version}.",
519726
- "update.notice.updatedTo": "BLUN King se ha actualizado a v{version}.",
519727
- "update.notice.highlights": "Novedades:"
519728
- },
519729
- fr: {
519730
- "update.notice.updatedFrom": "BLUN King est passé de v{fromVersion} à v{version}.",
519731
- "update.notice.updatedTo": "BLUN King a été mis à jour vers v{version}.",
519732
- "update.notice.highlights": "Nouveautés :"
519733
- },
519734
- sv: {
519735
- "update.notice.updatedFrom": "BLUN King har uppdaterats från v{fromVersion} till v{version}.",
519736
- "update.notice.updatedTo": "BLUN King har uppdaterats till v{version}.",
519737
- "update.notice.highlights": "Nyheter:"
519738
- },
519739
- cs: {
519740
- "update.notice.updatedFrom": "BLUN King byl aktualizován z v{fromVersion} na v{version}.",
519741
- "update.notice.updatedTo": "BLUN King byl aktualizován na v{version}.",
519742
- "update.notice.highlights": "Novinky:"
519743
- }
519744
- });
519745
- //#endregion
519746
- //#region src/cli/update/notice.ts
519747
- const OSC_SEQUENCE = /(?:\u001B\]|\u009D)[\s\S]*?(?:\u0007|\u001B\\|\u009C)/g;
519748
- const CSI_SEQUENCE = /(?:\u001B\[|\u009B)[0-?]*[ -/]*[@-~]/g;
519749
- const ESC_SEQUENCE = /\u001B[ -/]*[@-~]/g;
519750
- const CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]+/g;
519751
- function renderUpdateSuccessNotice(state, locale) {
519752
- const version = displayVersion(state.version);
519753
- const fromVersion = state.fromVersion === void 0 ? null : displayVersion(state.fromVersion);
519754
- const headline = fromVersion === null ? uiTextFor(locale, "update.notice.updatedTo", { version }) : uiTextFor(locale, "update.notice.updatedFrom", {
519755
- fromVersion,
519756
- version
519757
- });
519758
- const releaseNotes = sanitizeReleaseNotes(state.releaseNotes ?? []);
519759
- const lines = [headline];
519760
- if (releaseNotes.length > 0) {
519761
- lines.push(uiTextFor(locale, "update.notice.highlights"));
519762
- lines.push(...releaseNotes.map((note) => `- ${note}`));
519763
- }
519764
- return `${lines.join("\n")}\n`;
519765
- }
519766
- function displayVersion(value) {
519767
- const sanitized = sanitizeInlineText(value, 64);
519768
- if (sanitized.length === 0) return "?";
519769
- return sanitized.startsWith("v") ? sanitized.slice(1) : sanitized;
519770
- }
519771
- function sanitizeReleaseNotes(values) {
519772
- return values.map((value) => sanitizeInlineText(value, 160)).filter((value) => value.length > 0).slice(0, 3);
519773
- }
519774
- function sanitizeInlineText(value, maxLength) {
519775
- const sanitized = value.replaceAll(OSC_SEQUENCE, "").replaceAll(CSI_SEQUENCE, "").replaceAll(ESC_SEQUENCE, "").replaceAll(CONTROL_CHARACTERS, " ").replaceAll(/\s+/g, " ").trim();
519776
- let truncated = "";
519777
- let length = 0;
519778
- for (const character of sanitized) {
519779
- if (length >= maxLength) break;
519780
- truncated += character;
519781
- length += 1;
519782
- }
519783
- return truncated.trim();
519784
- }
519785
- //#endregion
519786
519196
  //#region src/cli/run-shell.ts
519787
- async function runShell(opts, version, updateStartupNotice) {
519197
+ async function runShell(opts, version) {
519788
519198
  const startedAt = Date.now();
519789
519199
  const configStartedAt = startedAt;
519790
519200
  let tuiConfig;
519791
519201
  let configWarning;
519792
- let updateNoticeText;
519793
519202
  try {
519794
519203
  tuiConfig = await loadTuiConfig();
519795
519204
  } catch (error) {
@@ -519797,9 +519206,6 @@ async function runShell(opts, version, updateStartupNotice) {
519797
519206
  tuiConfig = error.fallback;
519798
519207
  configWarning = error.message;
519799
519208
  }
519800
- if (updateStartupNotice !== void 0) {
519801
- updateNoticeText = renderUpdateSuccessNotice(updateStartupNotice.details, resolveUiLocale(tuiConfig.uiLocale));
519802
- }
519803
519209
  const palette = await getColorPalette(tuiConfig.theme);
519804
519210
  currentTheme.setPalette(palette);
519805
519211
  const workDir = process.cwd();
@@ -519847,12 +519253,7 @@ async function runShell(opts, version, updateStartupNotice) {
519847
519253
  tuiConfig,
519848
519254
  version,
519849
519255
  workDir,
519850
- startupNotice: configWarning,
519851
- startupTranscriptNotice: updateNoticeText,
519852
- onStartupNoticeShown: updateStartupNotice === void 0 || updateNoticeText === void 0 ? void 0 : async () => {
519853
- const delivery = await sendTelegramUpdateNotice(updateNoticeText, { version: updateStartupNotice.details.version });
519854
- if (!delivery.configured || delivery.attempted === 0 || delivery.sent === delivery.attempted) await updateStartupNotice.acknowledge();
519855
- }
519256
+ startupNotice: configWarning
519856
519257
  });
519857
519258
  initializeCliTelemetry({
519858
519259
  harness,
@@ -519925,15 +519326,14 @@ async function runShell(opts, version, updateStartupNotice) {
519925
519326
  tui.onExit = async (exitCode = 0) => {
519926
519327
  const sessionId = tui.getCurrentSessionId();
519927
519328
  const hasContent = tui.hasSessionContent();
519928
- const runningUpdateHandoff = exitCode === RUNNING_UPDATE_HANDOFF_EXIT_CODE;
519929
519329
  setCrashPhase("shutdown");
519930
519330
  trackLifecycle("exit", { duration_ms: Date.now() - startedAt });
519931
519331
  await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });
519932
519332
  const gutter = " ".repeat(1);
519933
- if (!runningUpdateHandoff) process.stdout.write(`${gutter}Bye!\n`);
519333
+ process.stdout.write(`${gutter}Bye!\n`);
519934
519334
  const hints = [];
519935
- if (!runningUpdateHandoff && sessionId !== "" && hasContent) hints.push(`${gutter}To resume this session: blun -r ${sessionId}`);
519936
- if (!runningUpdateHandoff && tui.exitOpenUrl !== void 0) hints.push(`${gutter}open ${toTerminalHyperlink$1(tui.exitOpenUrl, tui.exitOpenUrl)}`);
519335
+ if (sessionId !== "" && hasContent) hints.push(`${gutter}To resume this session: blun -r ${sessionId}`);
519336
+ if (tui.exitOpenUrl !== void 0) hints.push(`${gutter}open ${toTerminalHyperlink$1(tui.exitOpenUrl, tui.exitOpenUrl)}`);
519937
519337
  if (hints.length > 0) process.stderr.write(`\n${hints.join("\n")}\n`);
519938
519338
  removeCrashHandlers();
519939
519339
  restoreStty();
@@ -519945,7 +519345,6 @@ async function runShell(opts, version, updateStartupNotice) {
519945
519345
  try {
519946
519346
  const initStartedAt = Date.now();
519947
519347
  await tui.start();
519948
- notifyRunningRuntimeReady(tui);
519949
519348
  const initMs = Date.now() - initStartedAt;
519950
519349
  const startupSessionId = tui.getCurrentSessionId();
519951
519350
  const mcpMs = await tui.getStartupMcpMs();
@@ -520000,1052 +519399,6 @@ function isWithin(candidate, root) {
520000
519399
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
520001
519400
  }
520002
519401
  //#endregion
520003
- //#region src/cli/update/release-notes.ts
520004
- init_zod$1();
520005
- const ReleaseNotesSchema = array(string().min(1).max(160)).max(10).readonly();
520006
- //#endregion
520007
- //#region src/cli/update/cdn.ts
520008
- init_zod$1();
520009
- const CDN_FETCH_TIMEOUT_MS = 3e3;
520010
- const RolloutBatchSchema = object({
520011
- percent: number$1().int().min(0).max(100),
520012
- delaySeconds: number$1().int().min(0)
520013
- });
520014
- /**
520015
- * CDN `latest.json` wire format. Deliberately NOT `.strict()` — unknown
520016
- * fields are ignored so future manifest additions never break shipped
520017
- * clients (the plain-text `/latest` taught us that hard-failing on
520018
- * unexpected content bricks the update path forever).
520019
- */
520020
- const UpdateManifestSchema = object({
520021
- version: string().refine((value) => (0, import_semver.valid)(value) !== null, { error: "invalid semver" }),
520022
- publishedAt: string().refine((value) => Number.isFinite(Date.parse(value)), { error: "invalid timestamp" }),
520023
- releaseNotes: ReleaseNotesSchema.optional(),
520024
- rollout: array(RolloutBatchSchema).readonly().default([])
520025
- });
520026
- async function fetchWithTimeout(fetchImpl, input) {
520027
- const controller = new AbortController();
520028
- const timeout = setTimeout(() => {
520029
- controller.abort();
520030
- }, CDN_FETCH_TIMEOUT_MS);
520031
- try {
520032
- return await fetchImpl(input, { signal: controller.signal });
520033
- } finally {
520034
- clearTimeout(timeout);
520035
- }
520036
- }
520037
- /**
520038
- * Fetch the latest published BLUN version from the CDN.
520039
- *
520040
- * **Throws** on any failure (network error, non-2xx, empty body, non-semver
520041
- * text). Callers must catch — `refreshUpdateCache` deliberately lets the
520042
- * error propagate so the existing cache stays intact instead of being
520043
- * overwritten with a null `latest` on a transient blip.
520044
- *
520045
- * `fetchImpl` is injectable for tests; defaults to the global `fetch`.
520046
- */
520047
- async function fetchLatestVersionFromCdn(fetchImpl = fetch) {
520048
- const response = await fetchWithTimeout(fetchImpl, BLUN_CDN_LATEST_URL);
520049
- if (!response.ok) throw new Error(`CDN /latest returned HTTP ${response.status}`);
520050
- const raw = (await response.text()).trim();
520051
- if ((0, import_semver.valid)(raw) === null) throw new Error(`CDN /latest returned invalid semver: ${JSON.stringify(raw)}`);
520052
- return raw;
520053
- }
520054
- async function fetchUpdateManifestFromCdn(fetchImpl) {
520055
- const response = await fetchWithTimeout(fetchImpl, BLUN_CDN_LATEST_JSON_URL);
520056
- if (!response.ok) throw new Error(`CDN /latest.json returned HTTP ${response.status}`);
520057
- return UpdateManifestSchema.parse(JSON.parse(await response.text()));
520058
- }
520059
- /**
520060
- * Fetch the rollout manifest, falling back to the plain-text `/latest` when
520061
- * `latest.json` is unavailable or malformed. The fallback removes any
520062
- * deployment-order coupling between client releases and the CDN file, and a
520063
- * null manifest means "fully rolled out" — exactly the pre-rollout behavior.
520064
- *
520065
- * **Throws** only when both sources fail; callers must catch (see above).
520066
- */
520067
- async function fetchLatestFromCdn(fetchImpl = fetch) {
520068
- const manifest = await fetchUpdateManifestFromCdn(fetchImpl).catch(() => null);
520069
- if (manifest !== null) return {
520070
- latest: manifest.version,
520071
- manifest
520072
- };
520073
- return {
520074
- latest: await fetchLatestVersionFromCdn(fetchImpl),
520075
- manifest: null
520076
- };
520077
- }
520078
- //#endregion
520079
- //#region src/cli/update/cache.ts
520080
- init_zod$1();
520081
- const UpdateCacheSchema = object({
520082
- source: literal("cdn"),
520083
- checkedAt: string().min(1).nullable(),
520084
- latest: string().min(1).nullable(),
520085
- manifest: preprocess((value) => {
520086
- const parsed = UpdateManifestSchema.nullable().safeParse(value === void 0 ? null : value);
520087
- return parsed.success ? parsed.data : null;
520088
- }, union([UpdateManifestSchema, _null$1()]))
520089
- }).strict();
520090
- async function readUpdateCache(filePath = getUpdateStateFile()) {
520091
- try {
520092
- return await readJsonFile(filePath, UpdateCacheSchema, emptyUpdateCache());
520093
- } catch {
520094
- return emptyUpdateCache();
520095
- }
520096
- }
520097
- async function writeUpdateCache(value, filePath = getUpdateStateFile()) {
520098
- await writeJsonFile(filePath, UpdateCacheSchema, value);
520099
- }
520100
- //#endregion
520101
- //#region src/cli/update/refresh.ts
520102
- async function refreshUpdateCache(overrides = {}) {
520103
- const resolved = {
520104
- fetchLatest: overrides.fetchLatest ?? (() => fetchLatestFromCdn()),
520105
- writeCache: overrides.writeCache ?? writeUpdateCache,
520106
- now: overrides.now ?? (() => /* @__PURE__ */ new Date())
520107
- };
520108
- const { latest, manifest } = await resolved.fetchLatest();
520109
- const cache = {
520110
- source: "cdn",
520111
- checkedAt: resolved.now().toISOString(),
520112
- latest,
520113
- manifest
520114
- };
520115
- await resolved.writeCache(cache);
520116
- return cache;
520117
- }
520118
- //#endregion
520119
- //#region src/cli/update/select.ts
520120
- function selectUpdateTarget(currentVersion, latest) {
520121
- if (latest === null) return null;
520122
- if ((0, import_semver.valid)(currentVersion) === null || (0, import_semver.valid)(latest) === null) return null;
520123
- if (!(0, import_semver.gt)(latest, currentVersion)) return null;
520124
- return { version: latest };
520125
- }
520126
- //#endregion
520127
- //#region src/cli/update/install-state.ts
520128
- init_zod$1();
520129
- const InstallSourceSchema = _enum([
520130
- "npm-global",
520131
- "pnpm-global",
520132
- "yarn-global",
520133
- "bun-global",
520134
- "homebrew",
520135
- "native",
520136
- "unsupported"
520137
- ]);
520138
- const UpdateInstallStateSchema = object({
520139
- active: object({
520140
- version: string().min(1),
520141
- fromVersion: string().min(1).optional(),
520142
- releaseNotes: ReleaseNotesSchema.optional(),
520143
- source: InstallSourceSchema,
520144
- startedAt: string().min(1)
520145
- }).strict().nullable(),
520146
- lastFailure: object({
520147
- version: string().min(1),
520148
- failedAt: string().min(1),
520149
- attempts: number$1().int().min(1)
520150
- }).strict().nullable(),
520151
- lastSuccess: object({
520152
- version: string().min(1),
520153
- fromVersion: string().min(1).optional(),
520154
- releaseNotes: ReleaseNotesSchema.optional(),
520155
- initiatedBy: _enum(["automatic", "startup_prompt", "manual", "unknown"]).optional(),
520156
- installedAt: string().min(1),
520157
- notifiedAt: string().min(1).nullable()
520158
- }).strict().nullable()
520159
- }).strict();
520160
- async function readUpdateInstallState(filePath = getUpdateInstallStateFile()) {
520161
- try {
520162
- return await readJsonFile(filePath, UpdateInstallStateSchema, emptyUpdateInstallState());
520163
- } catch {
520164
- return emptyUpdateInstallState();
520165
- }
520166
- }
520167
- async function writeUpdateInstallState(value, filePath = getUpdateInstallStateFile()) {
520168
- await writeJsonFile(filePath, UpdateInstallStateSchema, value);
520169
- }
520170
- //#endregion
520171
- //#region src/tui/startup/update-prompt.copy.ts
520172
- registerUiCatalogFragment({
520173
- en: {
520174
- "update.prompt.title": "BLUN update available",
520175
- "update.prompt.description": "A newer version of BLUN King is ready.",
520176
- "update.prompt.changelog": "View changes",
520177
- "update.prompt.current": "Current",
520178
- "update.prompt.target": "New",
520179
- "update.prompt.source": "Source",
520180
- "update.prompt.command": "Command",
520181
- "update.prompt.hint": "↑↓ select · Enter confirm · Esc postpone",
520182
- "update.prompt.install": "Install now ({version})",
520183
- "update.prompt.postpone": "Postpone"
520184
- },
520185
- de: {
520186
- "update.prompt.title": "BLUN-Update verfügbar",
520187
- "update.prompt.description": "Eine neuere Version von BLUN King steht bereit.",
520188
- "update.prompt.changelog": "Änderungen anzeigen",
520189
- "update.prompt.current": "Aktuell",
520190
- "update.prompt.target": "Neu",
520191
- "update.prompt.source": "Quelle",
520192
- "update.prompt.command": "Befehl",
520193
- "update.prompt.hint": "↑↓ auswählen · Enter bestätigen · Esc aussetzen",
520194
- "update.prompt.install": "Jetzt installieren ({version})",
520195
- "update.prompt.postpone": "Aussetzen"
520196
- },
520197
- es: {
520198
- "update.prompt.title": "Actualización de BLUN disponible",
520199
- "update.prompt.description": "Una versión más reciente de BLUN King está lista.",
520200
- "update.prompt.changelog": "Ver cambios",
520201
- "update.prompt.current": "Actual",
520202
- "update.prompt.target": "Nueva",
520203
- "update.prompt.source": "Origen",
520204
- "update.prompt.command": "Comando",
520205
- "update.prompt.hint": "↑↓ seleccionar · Intro confirmar · Esc posponer",
520206
- "update.prompt.install": "Instalar ahora ({version})",
520207
- "update.prompt.postpone": "Posponer"
520208
- },
520209
- fr: {
520210
- "update.prompt.title": "Mise à jour de BLUN disponible",
520211
- "update.prompt.description": "Une version plus récente de BLUN King est prête.",
520212
- "update.prompt.changelog": "Voir les modifications",
520213
- "update.prompt.current": "Actuelle",
520214
- "update.prompt.target": "Nouvelle",
520215
- "update.prompt.source": "Source",
520216
- "update.prompt.command": "Commande",
520217
- "update.prompt.hint": "↑↓ sélectionner · Entrée confirmer · Échap reporter",
520218
- "update.prompt.install": "Installer maintenant ({version})",
520219
- "update.prompt.postpone": "Reporter"
520220
- },
520221
- sv: {
520222
- "update.prompt.title": "BLUN-uppdatering tillgänglig",
520223
- "update.prompt.description": "En nyare version av BLUN King är redo.",
520224
- "update.prompt.changelog": "Visa ändringar",
520225
- "update.prompt.current": "Nuvarande",
520226
- "update.prompt.target": "Ny",
520227
- "update.prompt.source": "Källa",
520228
- "update.prompt.command": "Kommando",
520229
- "update.prompt.hint": "↑↓ välj · Enter bekräfta · Esc skjut upp",
520230
- "update.prompt.install": "Installera nu ({version})",
520231
- "update.prompt.postpone": "Skjut upp"
520232
- },
520233
- cs: {
520234
- "update.prompt.title": "Je k dispozici aktualizace BLUN",
520235
- "update.prompt.description": "Novější verze BLUN King je připravena.",
520236
- "update.prompt.changelog": "Zobrazit změny",
520237
- "update.prompt.current": "Stávající",
520238
- "update.prompt.target": "Nová",
520239
- "update.prompt.source": "Zdroj",
520240
- "update.prompt.command": "Příkaz",
520241
- "update.prompt.hint": "↑↓ vybrat · Enter potvrdit · Esc odložit",
520242
- "update.prompt.install": "Nainstalovat nyní ({version})",
520243
- "update.prompt.postpone": "Odložit"
520244
- }
520245
- });
520246
- //#endregion
520247
- //#region src/cli/update/prompt.ts
520248
- const CHANGELOG_URL = "https://blun.ai/changelog";
520249
- function createInstallPromptChoices(target, locale = "en") {
520250
- return [{
520251
- value: "install",
520252
- label: uiTextFor(locale, "update.prompt.install", { version: target.version })
520253
- }, {
520254
- value: "skip",
520255
- label: uiTextFor(locale, "update.prompt.postpone")
520256
- }];
520257
- }
520258
- function getDefaultInstallPromptSelection(choices) {
520259
- const skipIndex = choices.findIndex((choice) => choice.value === "skip");
520260
- return Math.max(skipIndex, 0);
520261
- }
520262
- function moveInstallPromptSelection(currentIndex, direction, choiceCount) {
520263
- if (direction === "up") return Math.max(0, currentIndex - 1);
520264
- return Math.min(choiceCount - 1, currentIndex + 1);
520265
- }
520266
- function renderInstallPrompt(options, choices, selectedIndex) {
520267
- const locale = options.locale ?? "en";
520268
- const label = chalk.hex(UPDATE_PROMPT_TEXT_DIM).bold;
520269
- const currentVersion = chalk.hex(UPDATE_PROMPT_WARNING).bold(options.currentVersion);
520270
- const targetVersion = chalk.hex(UPDATE_PROMPT_SUCCESS).bold(options.target.version);
520271
- const sourceLabel = chalk.hex(UPDATE_PROMPT_PRIMARY).bold(options.installSource);
520272
- const command = chalk.hex(UPDATE_PROMPT_PRIMARY)(options.installCommand);
520273
- const changelogText = chalk.hex(UPDATE_PROMPT_PRIMARY).underline(`${uiTextFor(locale, "update.prompt.changelog")}: ${CHANGELOG_URL}`);
520274
- const lines = [
520275
- chalk.hex(UPDATE_PROMPT_PRIMARY).bold(uiTextFor(locale, "update.prompt.title")),
520276
- chalk.hex(UPDATE_PROMPT_MUTED)(uiTextFor(locale, "update.prompt.description")),
520277
- `]8;;${CHANGELOG_URL}\\${changelogText}]8;;\\`,
520278
- "",
520279
- `${label(uiTextFor(locale, "update.prompt.current"))} ${currentVersion}`,
520280
- `${label(uiTextFor(locale, "update.prompt.target"))} ${targetVersion}`,
520281
- `${label(uiTextFor(locale, "update.prompt.source"))} ${sourceLabel}`,
520282
- `${label(uiTextFor(locale, "update.prompt.command"))} ${command}`,
520283
- "",
520284
- chalk.hex(UPDATE_PROMPT_MUTED)(uiTextFor(locale, "update.prompt.hint")),
520285
- ""
520286
- ];
520287
- for (let i = 0; i < choices.length; i++) {
520288
- const choice = choices[i];
520289
- if (choice === void 0) continue;
520290
- const isSelected = i === selectedIndex;
520291
- const content = ` ${isSelected ? "❯" : " "} ${choice.label}`;
520292
- if (isSelected) {
520293
- lines.push(chalk.hex(UPDATE_PROMPT_PRIMARY).bold(content));
520294
- continue;
520295
- }
520296
- lines.push(chalk.hex(UPDATE_PROMPT_TEXT_DIM)(content));
520297
- }
520298
- return lines;
520299
- }
520300
- function writePromptFrame(output, lines, previousLineCount) {
520301
- if (previousLineCount > 0) moveCursor(output, 0, -(previousLineCount - 1));
520302
- for (let i = 0; i < lines.length; i++) {
520303
- clearLine(output, 0);
520304
- cursorTo(output, 0);
520305
- output.write(lines[i] ?? "");
520306
- if (i < lines.length - 1) output.write("\n");
520307
- }
520308
- return lines.length;
520309
- }
520310
- async function promptForInstallChoice(options) {
520311
- const input = options.input ?? process.stdin;
520312
- const output = options.output ?? process.stdout;
520313
- const choices = createInstallPromptChoices(options.target, options.locale);
520314
- let selectedIndex = getDefaultInstallPromptSelection(choices);
520315
- return new Promise((resolve) => {
520316
- let lineCount = 0;
520317
- let finished = false;
520318
- const hadRawMode = "isRaw" in input ? input.isRaw : false;
520319
- const canSetRawMode = typeof input.setRawMode === "function";
520320
- const cleanup = () => {
520321
- input.off("keypress", onKeypress);
520322
- input.off("end", onInputEnd);
520323
- input.off("close", onInputEnd);
520324
- if (canSetRawMode) input.setRawMode(hadRawMode);
520325
- output.write(SHOW_CURSOR);
520326
- output.write("\n");
520327
- };
520328
- const finish = (choice) => {
520329
- if (finished) return;
520330
- finished = true;
520331
- cleanup();
520332
- resolve(choice);
520333
- };
520334
- const onInputEnd = () => {
520335
- finish("skip");
520336
- };
520337
- const render = () => {
520338
- lineCount = writePromptFrame(output, renderInstallPrompt(options, choices, selectedIndex), lineCount);
520339
- };
520340
- const onKeypress = (_input, key) => {
520341
- if (key.name === "up") {
520342
- selectedIndex = moveInstallPromptSelection(selectedIndex, "up", choices.length);
520343
- render();
520344
- return;
520345
- }
520346
- if (key.name === "down") {
520347
- selectedIndex = moveInstallPromptSelection(selectedIndex, "down", choices.length);
520348
- render();
520349
- return;
520350
- }
520351
- if (key.name === "return" || key.name === "enter") {
520352
- finish(choices[selectedIndex]?.value ?? "skip");
520353
- return;
520354
- }
520355
- if (key.name === "escape" || key.ctrl === true && key.name === "c") finish("skip");
520356
- };
520357
- emitKeypressEvents(input);
520358
- if (canSetRawMode) input.setRawMode(true);
520359
- input.resume();
520360
- input.on("keypress", onKeypress);
520361
- input.once("end", onInputEnd);
520362
- input.once("close", onInputEnd);
520363
- output.write(HIDE_CURSOR);
520364
- render();
520365
- });
520366
- }
520367
- //#endregion
520368
- //#region src/cli/update/rollout.ts
520369
- init_src$3();
520370
- /**
520371
- * Hard ceiling for any rollout delay. Combined with the uncovered-bucket
520372
- * fallback below, it guarantees every device sees a release no later than
520373
- * `publishedAt + 24h`, no matter what the published plan says.
520374
- */
520375
- const MAX_ROLLOUT_DELAY_SECONDS = 86400;
520376
- /**
520377
- * Deterministic 0-99 bucket for a device. The version is mixed into the hash
520378
- * so each release reshuffles which devices land in the early batches.
520379
- */
520380
- function rolloutBucket(deviceId, version) {
520381
- return createHash("sha256").update(`${deviceId}:${version}`, "utf-8").digest().readUInt32BE(0) % 100;
520382
- }
520383
- /**
520384
- * Delay assigned to a bucket by the published plan. Batches claim bucket
520385
- * ranges in array order; buckets left uncovered (percents summing under 100)
520386
- * fall into the slowest cohort, and oversized delays are clamped to 24h.
520387
- */
520388
- function rolloutDelayForBucket(rollout, bucket) {
520389
- let cumulative = 0;
520390
- for (const batch of rollout) {
520391
- cumulative += batch.percent;
520392
- if (bucket < cumulative) return Math.min(Math.max(batch.delaySeconds, 0), MAX_ROLLOUT_DELAY_SECONDS);
520393
- }
520394
- if (rollout.length === 0) return 0;
520395
- return MAX_ROLLOUT_DELAY_SECONDS;
520396
- }
520397
- function rolloutDelaySeconds(manifest, deviceId) {
520398
- return rolloutDelayForBucket(manifest.rollout, rolloutBucket(deviceId, manifest.version));
520399
- }
520400
- function isRolloutEligible(manifest, deviceId, now) {
520401
- const publishedAt = Date.parse(manifest.publishedAt);
520402
- if (!Number.isFinite(publishedAt)) return true;
520403
- const delayMs = rolloutDelaySeconds(manifest, deviceId) * 1e3;
520404
- return now.getTime() >= publishedAt + delayMs;
520405
- }
520406
- function attachManifestReleaseNotes(target, manifest) {
520407
- if (target === null || manifest === null || manifest.version !== target.version || manifest.releaseNotes === void 0) return target;
520408
- return {
520409
- ...target,
520410
- releaseNotes: manifest.releaseNotes
520411
- };
520412
- }
520413
- /**
520414
- * Update decision for the passive surfaces (background install, startup
520415
- * prompt, manual-command notice). Devices whose batch is not yet eligible see
520416
- * no update at all. A null manifest (plain-text fallback or legacy cache)
520417
- * keeps the pre-rollout behavior: the latest version is visible immediately.
520418
- *
520419
- * `blun upgrade` must NOT go through this gate — it selects directly from the
520420
- * raw latest version.
520421
- */
520422
- function decidePassiveUpdateTarget(currentVersion, latest, manifest, deviceId, now, bypassRollout = false) {
520423
- if (bypassRollout) {
520424
- if (latest === null) return {
520425
- target: null,
520426
- reason: "no-latest",
520427
- bucket: null,
520428
- delaySeconds: null,
520429
- eligibleAt: null
520430
- };
520431
- const target = attachManifestReleaseNotes(selectUpdateTarget(currentVersion, latest), manifest);
520432
- return {
520433
- target,
520434
- reason: target === null ? "not-newer" : "experimental",
520435
- bucket: null,
520436
- delaySeconds: null,
520437
- eligibleAt: null
520438
- };
520439
- }
520440
- if (manifest === null) {
520441
- if (latest === null) return {
520442
- target: null,
520443
- reason: "no-latest",
520444
- bucket: null,
520445
- delaySeconds: null,
520446
- eligibleAt: null
520447
- };
520448
- const target = selectUpdateTarget(currentVersion, latest);
520449
- return {
520450
- target,
520451
- reason: target === null ? "not-newer" : "no-manifest",
520452
- bucket: null,
520453
- delaySeconds: null,
520454
- eligibleAt: null
520455
- };
520456
- }
520457
- const target = attachManifestReleaseNotes(selectUpdateTarget(currentVersion, manifest.version), manifest);
520458
- if (target === null) return {
520459
- target: null,
520460
- reason: "not-newer",
520461
- bucket: null,
520462
- delaySeconds: null,
520463
- eligibleAt: null
520464
- };
520465
- const bucket = rolloutBucket(deviceId, manifest.version);
520466
- const delaySeconds = rolloutDelayForBucket(manifest.rollout, bucket);
520467
- const publishedAt = Date.parse(manifest.publishedAt);
520468
- const eligibleAt = Number.isFinite(publishedAt) ? new Date(publishedAt + delaySeconds * 1e3).toISOString() : null;
520469
- const eligible = isRolloutEligible(manifest, deviceId, now);
520470
- return {
520471
- target: eligible ? target : null,
520472
- reason: eligible ? "eligible" : "held",
520473
- bucket,
520474
- delaySeconds,
520475
- eligibleAt
520476
- };
520477
- }
520478
- const ROLLOUT_LOG_MAX_BYTES = 256 * 1024;
520479
- /**
520480
- * Append one JSON line describing a passive update decision to
520481
- * `<dataDir>/updates/rollout.log`. Best-effort diagnostics: any I/O failure
520482
- * is swallowed — logging must never affect update prompting. The file is
520483
- * reset once it grows past a small cap so it cannot grow unbounded.
520484
- */
520485
- async function appendRolloutDecisionLog(entry, filePath = getUpdateRolloutLogFile()) {
520486
- try {
520487
- await mkdir(dirname(filePath), { recursive: true });
520488
- const line = `${JSON.stringify(entry)}\n`;
520489
- if (await stat(filePath).then((s) => s.size, () => 0) > ROLLOUT_LOG_MAX_BYTES) {
520490
- await writeFile(filePath, line, "utf-8");
520491
- return;
520492
- }
520493
- await appendFile(filePath, line, "utf-8");
520494
- } catch {}
520495
- }
520496
- /**
520497
- * Stable per-installation id used for bucketing when telemetry has already
520498
- * minted one. Missing ids stay ephemeral here so update preflight never
520499
- * creates the telemetry device_id before telemetry can emit first_launch.
520500
- */
520501
- function resolveUpdateDeviceId() {
520502
- return readBlunDeviceId(resolveSharedBlunHome(resolveBlunHome$1())) ?? randomUUID();
520503
- }
520504
- /**
520505
- * The experimental master switch opts a device out of staged rollouts: the
520506
- * newest version is always visible to the passive update surfaces, exactly as
520507
- * if every release were fully rolled out. Read directly from the env (same
520508
- * truthy values as `BLUN_NO_AUTO_UPDATE`) — the update preflight runs
520509
- * before the harness exists, so the core flag registry is not consulted.
520510
- * `BLUN_NO_AUTO_UPDATE` still wins: disabling updates beats opting in.
520511
- */
520512
- function isRolloutBypassedByExperimentalEnv(env = process.env) {
520513
- const value = (env["BLUN_EXPERIMENTAL_FLAG"] ?? "").trim().toLowerCase();
520514
- return [
520515
- "1",
520516
- "true",
520517
- "yes",
520518
- "on"
520519
- ].includes(value);
520520
- }
520521
- //#endregion
520522
- //#region src/cli/update/preflight.ts
520523
- const USER_VISIBLE_UPDATE_REFRESH_TIMEOUT_MS = 1e3;
520524
- function withCmdSuffix(base, platform) {
520525
- return platform === "win32" ? `${base}.cmd` : base;
520526
- }
520527
- function bunCommand(platform) {
520528
- return platform === "win32" ? "bun.exe" : "bun";
520529
- }
520530
- function installCommandFor(source, version, platform) {
520531
- switch (source) {
520532
- case "npm-global": return `npm install -g ${NPM_PACKAGE_NAME}@${version}`;
520533
- case "pnpm-global": return `pnpm add -g ${NPM_PACKAGE_NAME}@${version}`;
520534
- case "yarn-global": return `yarn global add ${NPM_PACKAGE_NAME}@${version}`;
520535
- case "bun-global": return `bun add -g ${NPM_PACKAGE_NAME}@${version}`;
520536
- case "homebrew": return "brew upgrade blun";
520537
- case "native": return platform === "win32" ? NATIVE_INSTALL_COMMAND_WIN : NATIVE_INSTALL_COMMAND_UNIX;
520538
- case "unsupported": return `npm install -g ${NPM_PACKAGE_NAME}@${version}`;
520539
- }
520540
- }
520541
- function canAutoInstall(source, platform) {
520542
- switch (source) {
520543
- case "npm-global":
520544
- case "pnpm-global":
520545
- case "yarn-global":
520546
- case "bun-global": return true;
520547
- case "homebrew": return false;
520548
- case "native": return platform !== "win32";
520549
- case "unsupported": return false;
520550
- }
520551
- }
520552
- function spawnForSource(source, version, platform) {
520553
- switch (source) {
520554
- case "npm-global": return {
520555
- cmd: withCmdSuffix("npm", platform),
520556
- args: [
520557
- "install",
520558
- "-g",
520559
- `${NPM_PACKAGE_NAME}@${version}`
520560
- ]
520561
- };
520562
- case "pnpm-global": return {
520563
- cmd: withCmdSuffix("pnpm", platform),
520564
- args: [
520565
- "add",
520566
- "-g",
520567
- `${NPM_PACKAGE_NAME}@${version}`
520568
- ]
520569
- };
520570
- case "yarn-global": return {
520571
- cmd: withCmdSuffix("yarn", platform),
520572
- args: [
520573
- "global",
520574
- "add",
520575
- `${NPM_PACKAGE_NAME}@${version}`
520576
- ]
520577
- };
520578
- case "bun-global": return {
520579
- cmd: bunCommand(platform),
520580
- args: [
520581
- "add",
520582
- "-g",
520583
- `${NPM_PACKAGE_NAME}@${version}`
520584
- ]
520585
- };
520586
- case "homebrew": return {
520587
- cmd: "brew",
520588
- args: ["upgrade", "blun"]
520589
- };
520590
- case "native": return {
520591
- cmd: "bash",
520592
- args: ["-c", `set -o pipefail; ${NATIVE_INSTALL_COMMAND_UNIX}`]
520593
- };
520594
- case "unsupported": throw new Error("unsupported install source cannot be auto-installed");
520595
- }
520596
- }
520597
- function formatErrorMessage$1(error) {
520598
- return error instanceof Error ? error.message : String(error);
520599
- }
520600
- function renderManualUpdateMessage(currentVersion, target, source, installCommand) {
520601
- let sourceDesc;
520602
- switch (source) {
520603
- case "npm-global":
520604
- case "pnpm-global":
520605
- case "yarn-global":
520606
- case "bun-global":
520607
- sourceDesc = source;
520608
- break;
520609
- case "homebrew":
520610
- sourceDesc = "homebrew";
520611
- break;
520612
- case "native":
520613
- sourceDesc = "native (windows). Auto-update is not supported on this platform.";
520614
- break;
520615
- case "unsupported":
520616
- sourceDesc = "unsupported package manager or layout.";
520617
- break;
520618
- }
520619
- return `A newer version of ${NPM_PACKAGE_NAME} is available (${currentVersion} -> ${target.version}).\nDetected install source: ${sourceDesc}\nTo update manually, run: ${installCommand}\n`;
520620
- }
520621
- function renderInstallSuccessMessage(target) {
520622
- return `Updated ${NPM_PACKAGE_NAME} to ${target.version}. Restart the CLI to use the new version.\n`;
520623
- }
520624
- function refreshInBackground() {
520625
- refreshUpdateCache().catch(() => {});
520626
- }
520627
- function rolloutTelemetryFor(deviceId, targetVersion, manifest, bypassRollout) {
520628
- const bucket = rolloutBucket(deviceId, targetVersion);
520629
- return {
520630
- rollout_bucket: bucket,
520631
- rollout_delay_seconds: manifest === null || bypassRollout ? 0 : rolloutDelayForBucket(manifest.rollout, bucket),
520632
- rollout_from_manifest: manifest !== null,
520633
- rollout_bypassed: bypassRollout
520634
- };
520635
- }
520636
- /** Record which case a passive version check hit in `updates/rollout.log`. */
520637
- function logRolloutDecision(phase, currentVersion, latest, manifest, decision) {
520638
- appendRolloutDecisionLog({
520639
- ts: nowIso(),
520640
- phase,
520641
- reason: decision.reason,
520642
- current: currentVersion,
520643
- latest,
520644
- target: decision.target?.version ?? null,
520645
- manifestPresent: manifest !== null,
520646
- publishedAt: manifest?.publishedAt ?? null,
520647
- bucket: decision.bucket,
520648
- delaySeconds: decision.delaySeconds,
520649
- eligibleAt: decision.eligibleAt
520650
- });
520651
- }
520652
- async function refreshUserVisibleUpdateTarget(currentVersion, deviceId, bypassRollout, fallbackTarget, fallbackManifest) {
520653
- let timeout;
520654
- const fallback = {
520655
- target: fallbackTarget,
520656
- manifest: fallbackManifest
520657
- };
520658
- try {
520659
- const refresh = refreshUpdateCache().then((refreshed) => {
520660
- const decision = decidePassiveUpdateTarget(currentVersion, refreshed.latest, refreshed.manifest, deviceId, /* @__PURE__ */ new Date(), bypassRollout);
520661
- logRolloutDecision("prompt-refresh", currentVersion, refreshed.latest, refreshed.manifest, decision);
520662
- return {
520663
- target: decision.target,
520664
- manifest: refreshed.manifest
520665
- };
520666
- }).catch(() => fallback);
520667
- const timeoutFallback = new Promise((resolve) => {
520668
- timeout = setTimeout(() => {
520669
- resolve(fallback);
520670
- }, USER_VISIBLE_UPDATE_REFRESH_TIMEOUT_MS);
520671
- });
520672
- return await Promise.race([refresh, timeoutFallback]);
520673
- } catch {
520674
- return fallback;
520675
- } finally {
520676
- if (timeout !== void 0) clearTimeout(timeout);
520677
- }
520678
- }
520679
- function nowIso() {
520680
- return (/* @__PURE__ */ new Date()).toISOString();
520681
- }
520682
- function pendingBackgroundInstallNotice(state, currentVersion, track, logger) {
520683
- const success = state.lastSuccess;
520684
- if (success !== null && success.notifiedAt === null && success.version === currentVersion) return {
520685
- details: {
520686
- version: success.version,
520687
- fromVersion: success.fromVersion,
520688
- releaseNotes: success.releaseNotes
520689
- },
520690
- acknowledge: createUpdateNoticeAcknowledgement(success.version, false, track, logger)
520691
- };
520692
- const active = state.active;
520693
- if (active === null || active.version !== currentVersion) return null;
520694
- if (success !== null && success.version === currentVersion && success.notifiedAt !== null) return null;
520695
- return {
520696
- details: {
520697
- version: active.version,
520698
- fromVersion: active.fromVersion,
520699
- releaseNotes: active.releaseNotes
520700
- },
520701
- acknowledge: createUpdateNoticeAcknowledgement(active.version, true, track, logger)
520702
- };
520703
- }
520704
- function createUpdateNoticeAcknowledgement(version, inferredFromActive, track, logger) {
520705
- let acknowledged = false;
520706
- return async () => {
520707
- if (acknowledged) return;
520708
- acknowledged = true;
520709
- trackUpdateEvent(track, "update_success_notice_shown", {
520710
- version,
520711
- inferred_from_active: inferredFromActive
520712
- });
520713
- logUpdateInfo(logger, "background update success notice shown", {
520714
- version,
520715
- inferredFromActive
520716
- });
520717
- const currentState = await readUpdateInstallState().catch(() => null);
520718
- if (currentState === null) return;
520719
- const nextState = acknowledgeUpdateNoticeInState(currentState, version, inferredFromActive);
520720
- if (nextState === null) return;
520721
- await writeUpdateInstallState(nextState).catch(() => {});
520722
- };
520723
- }
520724
- function acknowledgeUpdateNoticeInState(state, version, inferredFromActive) {
520725
- const notifiedAt = nowIso();
520726
- if (!inferredFromActive) {
520727
- const success = state.lastSuccess;
520728
- if (success === null || success.version !== version || success.notifiedAt !== null) return null;
520729
- return {
520730
- ...state,
520731
- lastSuccess: {
520732
- ...success,
520733
- notifiedAt
520734
- }
520735
- };
520736
- }
520737
- const active = state.active;
520738
- if (active === null || active.version !== version) return null;
520739
- let lastSuccess = state.lastSuccess;
520740
- if (lastSuccess === null) lastSuccess = {
520741
- version: active.version,
520742
- fromVersion: active.fromVersion,
520743
- releaseNotes: active.releaseNotes,
520744
- installedAt: notifiedAt,
520745
- notifiedAt
520746
- };
520747
- else if (lastSuccess.version === version && lastSuccess.notifiedAt === null) lastSuccess = {
520748
- ...lastSuccess,
520749
- notifiedAt
520750
- };
520751
- return {
520752
- ...state,
520753
- active: null,
520754
- lastSuccess
520755
- };
520756
- }
520757
- /**
520758
- * `BLUN_NO_AUTO_UPDATE` (or the legacy `BLUN_CLI_NO_AUTO_UPDATE` alias)
520759
- * fully disables the update preflight — no check, no background install, no
520760
- * prompt. Migrated from blun-cli, where the variable gated all auto-update
520761
- * behavior. Accepts the usual truthy values (`1`/`true`/`yes`/`on`).
520762
- */
520763
- function isAutoUpdateDisabledByEnv(env = process.env) {
520764
- const truthy = (value) => [
520765
- "1",
520766
- "true",
520767
- "yes",
520768
- "on"
520769
- ].includes((value ?? "").trim().toLowerCase());
520770
- return truthy(env["BLUN_NO_AUTO_UPDATE"]) || truthy(env["BLUN_CLI_NO_AUTO_UPDATE"]);
520771
- }
520772
- function trackUpdatePrompted(track, currentVersion, target, source, decision, rolloutTelemetry) {
520773
- trackUpdateEvent(track, "update_prompted", {
520774
- current_version: currentVersion,
520775
- target_version: target.version,
520776
- source,
520777
- decision,
520778
- ...rolloutTelemetry
520779
- });
520780
- }
520781
- function trackUpdateEvent(track, event, properties) {
520782
- try {
520783
- track?.(event, properties);
520784
- } catch {}
520785
- }
520786
- function logUpdateInfo(logger, message, payload) {
520787
- try {
520788
- logger.info(message, payload);
520789
- } catch {}
520790
- }
520791
- function logUpdateWarn(logger, message, payload) {
520792
- try {
520793
- logger.warn(message, payload);
520794
- } catch {}
520795
- }
520796
- async function promptInstall(currentVersion, target, source, installCommand) {
520797
- return promptForInstallChoice({
520798
- currentVersion,
520799
- target,
520800
- installSource: source,
520801
- installCommand,
520802
- locale: await resolveUpdatePromptLocale()
520803
- });
520804
- }
520805
- async function resolveUpdatePromptLocale() {
520806
- try {
520807
- return resolveUiLocale((await loadTuiConfig()).uiLocale);
520808
- } catch {
520809
- return resolveUiLocale("auto");
520810
- }
520811
- }
520812
- async function installUpdate(source, version, platform) {
520813
- const { cmd, args } = spawnForSource(source, version, platform);
520814
- await new Promise((resolve, reject) => {
520815
- const child = spawn(cmd, [...args], {
520816
- stdio: "inherit",
520817
- shell: platform === "win32" ? true : void 0
520818
- });
520819
- child.once("error", reject);
520820
- child.once("exit", (code, signal) => {
520821
- if (code === 0) {
520822
- resolve();
520823
- return;
520824
- }
520825
- const detail = signal !== null ? `signal ${signal}` : `code ${String(code)}`;
520826
- reject(/* @__PURE__ */ new Error(`${cmd} exited with ${detail}`));
520827
- });
520828
- });
520829
- }
520830
- function decideUpdateAction(target, isInteractive, source, platform) {
520831
- if (target === null || !isInteractive) return "none";
520832
- return canAutoInstall(source, platform) ? "prompt-install" : "manual-command";
520833
- }
520834
- async function runUpdatePreflight(currentVersion, options = {}) {
520835
- const stdout = options.stdout ?? process.stdout;
520836
- const stderr = options.stderr ?? process.stderr;
520837
- const logger = options.logger ?? log;
520838
- const platform = process.platform;
520839
- try {
520840
- const isInteractive = options.isTTY ?? (process.stdin.isTTY && process.stdout.isTTY);
520841
- const installState = await readUpdateInstallState().catch(() => emptyUpdateInstallState());
520842
- if (isInteractive) {
520843
- const startupNotice = pendingBackgroundInstallNotice(installState, currentVersion, options.track, logger);
520844
- if (startupNotice !== null) return {
520845
- action: "continue",
520846
- startupNotice
520847
- };
520848
- }
520849
- if (isAutoUpdateDisabledByEnv()) return "continue";
520850
- const deviceId = resolveUpdateDeviceId();
520851
- const bypassRollout = isRolloutBypassedByExperimentalEnv();
520852
- const cache = await readUpdateCache().catch(() => null);
520853
- const cachedManifest = cache?.manifest ?? null;
520854
- const cachedDecision = decidePassiveUpdateTarget(currentVersion, cache?.latest ?? null, cachedManifest, deviceId, /* @__PURE__ */ new Date(), bypassRollout);
520855
- logRolloutDecision("startup-cache", currentVersion, cache?.latest ?? null, cachedManifest, cachedDecision);
520856
- if (!isInteractive) {
520857
- refreshInBackground();
520858
- return "continue";
520859
- }
520860
- const userVisibleUpdate = await refreshUserVisibleUpdateTarget(currentVersion, deviceId, bypassRollout, cachedDecision.target, cachedManifest);
520861
- const userVisibleTarget = userVisibleUpdate.target;
520862
- if (userVisibleTarget === null) return "continue";
520863
- const source = await detectInstallSource().catch(() => "unsupported");
520864
- const decision = decideUpdateAction(userVisibleTarget, true, source, platform);
520865
- const userVisibleRollout = rolloutTelemetryFor(deviceId, userVisibleTarget.version, userVisibleUpdate.manifest, bypassRollout);
520866
- const installCommand = installCommandFor(source, userVisibleTarget.version, platform);
520867
- trackUpdatePrompted(options.track, currentVersion, userVisibleTarget, source, decision, userVisibleRollout);
520868
- if (decision === "manual-command") {
520869
- stdout.write(renderManualUpdateMessage(currentVersion, userVisibleTarget, source, installCommand));
520870
- return "continue";
520871
- }
520872
- if (await promptInstall(currentVersion, userVisibleTarget, source, installCommand) === "skip") return "continue";
520873
- try {
520874
- await installUpdate(source, userVisibleTarget.version, platform);
520875
- await writeUpdateInstallState({
520876
- ...installState,
520877
- active: null,
520878
- lastFailure: null,
520879
- lastSuccess: {
520880
- version: userVisibleTarget.version,
520881
- fromVersion: currentVersion,
520882
- releaseNotes: userVisibleTarget.releaseNotes,
520883
- initiatedBy: "startup_prompt",
520884
- installedAt: nowIso(),
520885
- notifiedAt: null
520886
- }
520887
- }).catch((error) => {
520888
- logUpdateWarn(logger, "foreground update success state write failed", {
520889
- currentVersion,
520890
- targetVersion: userVisibleTarget.version,
520891
- error: formatErrorMessage$1(error)
520892
- });
520893
- });
520894
- stdout.write(renderInstallSuccessMessage(userVisibleTarget));
520895
- return "exit";
520896
- } catch (error) {
520897
- stderr.write(`warning: failed to install ${NPM_PACKAGE_NAME}@${userVisibleTarget.version}: ${formatErrorMessage$1(error)}\n`);
520898
- return "continue";
520899
- }
520900
- } catch {
520901
- return "continue";
520902
- }
520903
- }
520904
- //#endregion
520905
- //#region src/cli/sub/upgrade.ts
520906
- async function handleUpgrade(currentVersion, overrides = {}) {
520907
- const deps = createDefaultUpgradeDeps(overrides);
520908
- let cache;
520909
- try {
520910
- cache = await deps.refreshUpdateCache();
520911
- } catch (error) {
520912
- const reason = formatErrorMessage(error);
520913
- trackUpgradeEvent(deps.track, "upgrade_command_failed", {
520914
- current_version: currentVersion,
520915
- stage: "refresh",
520916
- reason
520917
- });
520918
- logUpgradeWarn(deps.logger, "manual upgrade check failed", {
520919
- currentVersion,
520920
- error
520921
- });
520922
- deps.stderr.write(`error: failed to check for updates: ${reason}\n`);
520923
- return 1;
520924
- }
520925
- const target = selectUpdateTarget(currentVersion, cache.latest);
520926
- if (target === null) {
520927
- trackUpgradeEvent(deps.track, "upgrade_command_no_update", { current_version: currentVersion });
520928
- logUpgradeInfo(deps.logger, "manual upgrade no update", { currentVersion });
520929
- deps.stdout.write(`BLUN is already up to date (${formatDisplayVersion(currentVersion)}).\n`);
520930
- return 0;
520931
- }
520932
- const source = await deps.detectInstallSource().catch(() => "unsupported");
520933
- const installCommand = installCommandFor(source, target.version, deps.platform);
520934
- if (!canAutoInstall(source, deps.platform) || !deps.isInteractive) {
520935
- trackUpgradeEvent(deps.track, "upgrade_command_manual_command", {
520936
- current_version: currentVersion,
520937
- target_version: target.version,
520938
- source
520939
- });
520940
- logUpgradeInfo(deps.logger, "manual upgrade command shown", {
520941
- currentVersion,
520942
- targetVersion: target.version,
520943
- source
520944
- });
520945
- deps.stdout.write(renderManualUpdateMessage(currentVersion, target, source, installCommand));
520946
- return 0;
520947
- }
520948
- trackUpgradeEvent(deps.track, "upgrade_command_prompted", {
520949
- current_version: currentVersion,
520950
- target_version: target.version,
520951
- source
520952
- });
520953
- logUpgradeInfo(deps.logger, "manual upgrade prompted", {
520954
- currentVersion,
520955
- targetVersion: target.version,
520956
- source
520957
- });
520958
- if (await deps.promptForInstallChoice({
520959
- currentVersion,
520960
- target,
520961
- installCommand,
520962
- installSource: source
520963
- }) === "skip") {
520964
- trackUpgradeEvent(deps.track, "upgrade_command_skipped", {
520965
- current_version: currentVersion,
520966
- target_version: target.version,
520967
- source
520968
- });
520969
- logUpgradeInfo(deps.logger, "manual upgrade skipped", {
520970
- currentVersion,
520971
- targetVersion: target.version,
520972
- source
520973
- });
520974
- return 0;
520975
- }
520976
- try {
520977
- trackUpgradeEvent(deps.track, "upgrade_command_install_selected", {
520978
- current_version: currentVersion,
520979
- target_version: target.version,
520980
- source
520981
- });
520982
- await deps.installUpdate(source, target.version, deps.platform);
520983
- trackUpgradeEvent(deps.track, "upgrade_command_succeeded", {
520984
- current_version: currentVersion,
520985
- target_version: target.version,
520986
- source
520987
- });
520988
- logUpgradeInfo(deps.logger, "manual upgrade install succeeded", {
520989
- currentVersion,
520990
- targetVersion: target.version,
520991
- source
520992
- });
520993
- deps.stdout.write(renderInstallSuccessMessage(target));
520994
- return 0;
520995
- } catch (error) {
520996
- trackUpgradeEvent(deps.track, "upgrade_command_failed", {
520997
- current_version: currentVersion,
520998
- target_version: target.version,
520999
- source,
521000
- stage: "install",
521001
- reason: formatErrorMessage(error)
521002
- });
521003
- logUpgradeWarn(deps.logger, "manual upgrade install failed", {
521004
- currentVersion,
521005
- targetVersion: target.version,
521006
- source,
521007
- error
521008
- });
521009
- deps.stderr.write(`warning: failed to install ${NPM_PACKAGE_NAME}@${target.version}: ${formatErrorMessage(error)}\n`);
521010
- return 1;
521011
- }
521012
- }
521013
- function createDefaultUpgradeDeps(overrides) {
521014
- return {
521015
- refreshUpdateCache: overrides.refreshUpdateCache ?? (() => refreshUpdateCache()),
521016
- detectInstallSource: overrides.detectInstallSource ?? (() => detectInstallSource()),
521017
- installUpdate: overrides.installUpdate ?? installUpdate,
521018
- promptForInstallChoice: overrides.promptForInstallChoice ?? promptForInstallChoice,
521019
- platform: overrides.platform ?? process.platform,
521020
- stdout: overrides.stdout ?? process.stdout,
521021
- stderr: overrides.stderr ?? process.stderr,
521022
- isInteractive: overrides.isInteractive ?? (process.stdin.isTTY && process.stdout.isTTY),
521023
- track: overrides.track ?? track,
521024
- logger: overrides.logger ?? log
521025
- };
521026
- }
521027
- function formatDisplayVersion(version) {
521028
- return version.startsWith("v") ? version : `v${version}`;
521029
- }
521030
- function formatErrorMessage(error) {
521031
- return error instanceof Error ? error.message : String(error);
521032
- }
521033
- function trackUpgradeEvent(track, event, properties) {
521034
- try {
521035
- track(event, properties);
521036
- } catch {}
521037
- }
521038
- function logUpgradeInfo(logger, message, payload) {
521039
- try {
521040
- logger.info(message, payload);
521041
- } catch {}
521042
- }
521043
- function logUpgradeWarn(logger, message, payload) {
521044
- try {
521045
- logger.warn(message, payload);
521046
- } catch {}
521047
- }
521048
- //#endregion
521049
519402
  //#region src/native/bundled-telegram.ts
521050
519403
  const BUNDLED_TELEGRAM_BOOTSTRAP_SENTINEL = "BLUN_BUNDLED_TELEGRAM_PLUGIN_BOOTSTRAPPED";
521051
519404
  const REQUIRED_PLUGIN_FILES = [
@@ -521442,12 +519795,6 @@ async function handleMainCommand(opts, version) {
521442
519795
  }
521443
519796
  throw error;
521444
519797
  }
521445
- const preflightResult = await runUpdatePreflight(version, validated.uiMode === "print" ? {
521446
- track,
521447
- isTTY: false
521448
- } : { track });
521449
- if (preflightResult === "exit") process.exit(0);
521450
- const updateStartupNotice = typeof preflightResult === "object" ? preflightResult.startupNotice : void 0;
521451
519798
  const mistakeSync = await startAutomaticMistakeSync({
521452
519799
  homeDir: resolveBlunHome$1(),
521453
519800
  env: process.env
@@ -521458,41 +519805,9 @@ async function handleMainCommand(opts, version) {
521458
519805
  } finally {
521459
519806
  await mistakeSync.stop();
521460
519807
  }
521461
- await runShell(validated.options, version, updateStartupNotice);
519808
+ await runShell(validated.options, version);
521462
519809
  return { headlessCompleted: false };
521463
519810
  }
521464
- async function handleUpgradeCommand(version) {
521465
- const telemetryBootstrap = createCliTelemetryBootstrap();
521466
- const telemetryClient = {
521467
- track,
521468
- withContext: withTelemetryContext,
521469
- setContext: setTelemetryContext
521470
- };
521471
- const harness = createBlunHarness({
521472
- homeDir: telemetryBootstrap.homeDir,
521473
- identity: createBlunHostIdentity(version),
521474
- telemetry: telemetryClient
521475
- });
521476
- let exitCode = 1;
521477
- try {
521478
- await harness.ensureConfigFile();
521479
- initializeCliTelemetry({
521480
- harness,
521481
- bootstrap: telemetryBootstrap,
521482
- config: await harness.getConfig(),
521483
- version,
521484
- uiMode: CLI_UI_MODE
521485
- });
521486
- exitCode = await handleUpgrade(version, {
521487
- track,
521488
- logger: log
521489
- });
521490
- } finally {
521491
- await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS }).catch(() => {});
521492
- await harness.close().catch(() => {});
521493
- }
521494
- process.exit(exitCode);
521495
- }
521496
519811
  function main() {
521497
519812
  process.title = PROCESS_NAME;
521498
519813
  bootstrapBundledTelegramPlugin({
@@ -521526,13 +519841,6 @@ function main() {
521526
519841
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
521527
519842
  process.exit(1);
521528
519843
  });
521529
- }, () => {
521530
- handleUpgradeCommand(version).catch(async (error) => {
521531
- await logStartupFailure("upgrade", error);
521532
- process.stderr.write(formatStartupError(error, { operation: "upgrade" }));
521533
- process.stderr.write(`See log: ${resolveGlobalLogPath(resolveBlunHome$1())}\n`);
521534
- process.exit(1);
521535
- });
521536
519844
  }).parse(process.argv);
521537
519845
  }
521538
519846
  main();
@@ -521546,4 +519854,4 @@ async function logStartupFailure(operation, error) {
521546
519854
  } catch {}
521547
519855
  }
521548
519856
  //#endregion
521549
- export { configureTelegramPluginLaunchMode, handleMainCommand, handleUpgradeCommand, main };
519857
+ export { configureTelegramPluginLaunchMode, handleMainCommand, main };