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/LIESMICH.txt +11 -9
- package/README.md +7 -7
- package/bin/blun.js +28 -43
- package/bin/core-bootstrap.js +1 -2
- package/bin/king.js +28 -43
- package/bin/launcher-runtime.js +82 -657
- package/bin/update-notice.js +22 -126
- package/blun.mjs +11 -1703
- package/package.json +1 -1
- package/release-planned-removals.json +13 -2
- package/bin/running-update-preference.cjs +0 -68
- package/bin/running-update.cjs +0 -588
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
|
|
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 = () => {}
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
519333
|
+
process.stdout.write(`${gutter}Bye!\n`);
|
|
519934
519334
|
const hints = [];
|
|
519935
|
-
if (
|
|
519936
|
-
if (
|
|
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
|
|
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,
|
|
519857
|
+
export { configureTelegramPluginLaunchMode, handleMainCommand, main };
|