pleumrouter 0.5.30 → 0.5.32

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/bin/pleum.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { login, loginDevice, logout } from "../src/login.mjs";
4
- import { launch, launchHelp } from "../src/launch.mjs";
4
+ import { launch, launchHelp, recoverAgySettings } from "../src/launch.mjs";
5
5
  import { wire, unwire, wireHelp, unwireHelp } from "../src/wire.mjs";
6
6
  import { run } from "../src/run.mjs";
7
7
  import { listModels } from "../src/models.mjs";
@@ -11,12 +11,17 @@ import { top } from "../src/top.mjs";
11
11
  import { health } from "../src/health.mjs";
12
12
  import { completion } from "../src/completion.mjs";
13
13
  import { upgrade } from "../src/upgrade.mjs";
14
+ import { runUpdateCommand } from "../src/update.mjs";
14
15
  import { mainMenu, runAgentMenu } from "../src/menu.mjs";
15
16
  import { lang } from "../src/lang.mjs";
16
17
  import { RECIPES } from "../src/recipes.mjs";
17
18
  import { t } from "../src/i18n.mjs";
18
19
  import { sanitizeTerminalText } from "../src/ansi.mjs";
19
20
 
21
+ // 이전 비정상 종료로 인한 잔존 백업 설정(agy 등)이 있으면 즉시 안전 복구
22
+ recoverAgySettings();
23
+
24
+
20
25
  // 명령 구문(왼쪽)은 로케일과 무관 — 설명(오른쪽)만 번역, 폭은 가장 긴 구문 기준으로 계산.
21
26
  function buildHelp() {
22
27
  const rows = [
@@ -39,7 +44,8 @@ function buildHelp() {
39
44
  ["pleum health", t("Check gateway connectivity and login status")],
40
45
  ["pleum lang [code]", t("Show/change display language (en|ko|ja|zh|es)")],
41
46
  ["pleum completion <zsh|bash>", t("Print a shell completion script")],
42
- ["pleum upgrade", t("Reinstall the latest version")],
47
+ ["pleum update [--force]", t("Update CLI to the latest version")],
48
+ ["pleum upgrade", t("Alias for pleum update --force")],
43
49
  ["pleum version", t("Print the version")],
44
50
  ];
45
51
  const width = Math.max(...rows.map(([cmd]) => cmd.length)) + 2;
@@ -141,8 +147,9 @@ try {
141
147
  case "completion":
142
148
  completion(rest[0]);
143
149
  break;
150
+ case "update":
144
151
  case "upgrade":
145
- upgrade();
152
+ await runUpdateCommand(rest);
146
153
  break;
147
154
  case "version":
148
155
  case "-v":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pleumrouter",
3
- "version": "0.5.30",
3
+ "version": "0.5.32",
4
4
  "description": "Official PleumRouter terminal CLI — launch any coding agent against PleumRouter.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/ansi.mjs CHANGED
@@ -102,6 +102,10 @@ export function terminalWidth(fallback = 80) {
102
102
  return process.stdout.columns || fallback;
103
103
  }
104
104
 
105
+ export function terminalHeight(fallback = 24) {
106
+ return process.stdout.rows || fallback;
107
+ }
108
+
105
109
  export function divider(width = Math.min(terminalWidth(), 60), styler = style) {
106
110
  return styler.gray("─".repeat(Math.max(1, width)));
107
111
  }
@@ -3,7 +3,7 @@
3
3
  import { t } from "./i18n.mjs";
4
4
  import { launchToolNames } from "./recipes.mjs";
5
5
 
6
- const COMMANDS = "login logout lang launch wire unwire run models top balance usage health version upgrade completion help";
6
+ const COMMANDS = "login logout lang launch wire unwire run models top balance usage health version update upgrade completion help";
7
7
  const TOOLS = launchToolNames().join(" ");
8
8
 
9
9
  function bash() {
package/src/config.mjs CHANGED
@@ -241,3 +241,16 @@ export async function authFetch(path, init = {}) {
241
241
  });
242
242
  }
243
243
 
244
+ /** 자동 업데이트 활성화 여부 (기본값: true). */
245
+ export function isAutoUpdateEnabled(cfg = readConfig()) {
246
+ return cfg?.auto_update !== false;
247
+ }
248
+
249
+ /** 자동 업데이트 설정 저장. */
250
+ export function setAutoUpdateEnabled(enabled) {
251
+ const cfg = readConfig() ?? {};
252
+ cfg.auto_update = Boolean(enabled);
253
+ writeConfig(cfg);
254
+ }
255
+
256
+
@@ -109,6 +109,7 @@ export async function select(items, {
109
109
  return new Promise((resolve) => {
110
110
  let query = "";
111
111
  let cursor = Math.max(0, Math.min(initialCursor, Math.max(0, (items?.length ?? 1) - 1)));
112
+ let windowStart = 0;
112
113
  let lastRows = 0;
113
114
 
114
115
  const rowsFor = (s) => Math.max(1, Math.ceil(displayWidth(s) / Math.max(terminalWidth(), 1)));
@@ -121,15 +122,33 @@ export async function select(items, {
121
122
  const render = () => {
122
123
  const pool = matches(items, query);
123
124
  if (cursor >= pool.length) cursor = Math.max(0, pool.length - 1);
124
- const shown = pool.slice(0, limit);
125
+ if (cursor < windowStart) {
126
+ windowStart = cursor;
127
+ } else if (cursor >= windowStart + limit) {
128
+ windowStart = cursor - limit + 1;
129
+ }
130
+ windowStart = Math.max(0, Math.min(windowStart, Math.max(0, pool.length - limit)));
131
+
132
+ const shown = pool.slice(windowStart, windowStart + limit);
125
133
  const lines = [
126
134
  `${style.plum("?")} ${style.bold(message)} ${style.dim(t("type to filter · ↑↓ move · Enter select · Esc cancel"))}`,
127
135
  `${style.gray("›")} ${query}`,
128
- ...shown.map((it, i) =>
129
- i === cursor ? `${style.plum("❯")} ${style.bold(formatItem(it))}` : ` ${style.dim(formatItem(it))}`,
130
- ),
131
136
  ];
132
- if (pool.length > shown.length) lines.push(style.dim(` ${t("… {n} more — type to narrow down", { n: pool.length - shown.length })}`));
137
+ if (windowStart > 0) {
138
+ lines.push(style.dim(` ${t("↑ {n} more above", { n: windowStart })}`));
139
+ }
140
+ lines.push(
141
+ ...shown.map((it, idx) => {
142
+ const itemIndex = windowStart + idx;
143
+ return itemIndex === cursor
144
+ ? `${style.plum("❯")} ${style.bold(formatItem(it))}`
145
+ : ` ${style.dim(formatItem(it))}`;
146
+ }),
147
+ );
148
+ const remainingBelow = pool.length - (windowStart + shown.length);
149
+ if (remainingBelow > 0) {
150
+ lines.push(style.dim(` ${t("… {n} more — type to narrow down", { n: remainingBelow })}`));
151
+ }
133
152
  if (!pool.length) lines.push(style.dim(` ${t("No matches")}`));
134
153
  erase();
135
154
  const frame = lines.join("\n");
@@ -158,12 +177,19 @@ export async function select(items, {
158
177
  const safeCursor = Math.max(0, Math.min(cursor, pool.length - 1));
159
178
  return finish(pool[safeCursor] ?? null);
160
179
  }
161
- if (key.type === "up") cursor = Math.max(0, cursor - 1);
162
- else if (key.type === "down") cursor = Math.min(limit - 1, cursor + 1);
163
- else if (key.type === "backspace") query = query.slice(0, -1);
164
- else if (key.type === "char") {
180
+ if (key.type === "up") {
181
+ cursor = Math.max(0, cursor - 1);
182
+ } else if (key.type === "down") {
183
+ const pool = matches(items, query);
184
+ cursor = Math.min(Math.max(0, pool.length - 1), cursor + 1);
185
+ } else if (key.type === "backspace") {
186
+ query = query.slice(0, -1);
187
+ cursor = 0;
188
+ windowStart = 0;
189
+ } else if (key.type === "char") {
165
190
  query += key.value;
166
191
  cursor = 0;
192
+ windowStart = 0;
167
193
  } else continue;
168
194
  dirty = true;
169
195
  }
package/src/launch.mjs CHANGED
@@ -146,16 +146,28 @@ export async function launch(tool, model, passthrough, { exit = true } = {}) {
146
146
  if (exit) process.exit(130);
147
147
  };
148
148
 
149
+ const onException = (err) => {
150
+ cleanup();
151
+ if (err) console.error(err);
152
+ if (exit) process.exit(1);
153
+ };
154
+
149
155
  process.once("SIGINT", onSignal);
150
156
  process.once("SIGTERM", onSignal);
151
157
  process.once("SIGHUP", onSignal);
158
+ process.once("SIGQUIT", onSignal);
152
159
  process.once("exit", cleanup);
160
+ process.once("uncaughtException", onException);
161
+ process.once("unhandledRejection", onException);
153
162
 
154
163
  const removeListeners = () => {
155
164
  process.removeListener("SIGINT", onSignal);
156
165
  process.removeListener("SIGTERM", onSignal);
157
166
  process.removeListener("SIGHUP", onSignal);
167
+ process.removeListener("SIGQUIT", onSignal);
158
168
  process.removeListener("exit", cleanup);
169
+ process.removeListener("uncaughtException", onException);
170
+ process.removeListener("unhandledRejection", onException);
159
171
  };
160
172
 
161
173
  const activeCtx = getActiveContext();
@@ -228,6 +240,12 @@ export function setupAgySettings(model, {
228
240
  rm = rmSync,
229
241
  } = {}) {
230
242
  const { settingsPath, backupPath } = paths;
243
+
244
+ // 비정상 종료로 남은 이전 백업이 있으면 먼저 안전 복구
245
+ if (exists(backupPath)) {
246
+ recoverAgySettings({ paths, read, write, exists, rm });
247
+ }
248
+
231
249
  const originalExists = exists(settingsPath);
232
250
  let originalContent = null;
233
251
  let parsed = null;
@@ -241,7 +259,8 @@ export function setupAgySettings(model, {
241
259
  }
242
260
  }
243
261
 
244
- // 1. 비정상 종료 대비 백업 메타데이터 저장
262
+ // 1. 비정상 종료 대비 백업 메타데이터 저장 (gemini 값은 pleum의 잔재이므로 백업에서도 배제)
263
+ const hadProvider = Boolean(parsed && "modelProvider" in parsed && parsed.modelProvider !== "gemini");
245
264
  try {
246
265
  mkdir(dirname(backupPath), { recursive: true, mode: 0o700 });
247
266
  write(
@@ -250,8 +269,8 @@ export function setupAgySettings(model, {
250
269
  {
251
270
  settingsPath,
252
271
  originalExists,
253
- originalHadModelProvider: Boolean(parsed && "modelProvider" in parsed),
254
- originalModelProvider: parsed?.modelProvider ?? null,
272
+ originalHadModelProvider: hadProvider,
273
+ originalModelProvider: hadProvider ? parsed?.modelProvider : null,
255
274
  originalHadModel: Boolean(parsed && "model" in parsed),
256
275
  originalModel: parsed?.model ?? null,
257
276
  originalContent,
@@ -280,7 +299,7 @@ export function setupAgySettings(model, {
280
299
  if (currentContent) {
281
300
  try {
282
301
  const currentObj = JSON.parse(currentContent);
283
- if (parsed && "modelProvider" in parsed) {
302
+ if (hadProvider && parsed?.modelProvider) {
284
303
  currentObj.modelProvider = parsed.modelProvider;
285
304
  } else {
286
305
  delete currentObj.modelProvider;
@@ -328,7 +347,7 @@ export function recoverAgySettings({
328
347
  const curContent = read(settingsPath, "utf8");
329
348
  try {
330
349
  const curObj = JSON.parse(curContent);
331
- if (backup.originalHadModelProvider) {
350
+ if (backup.originalHadModelProvider && backup.originalModelProvider && backup.originalModelProvider !== "gemini") {
332
351
  curObj.modelProvider = backup.originalModelProvider;
333
352
  } else {
334
353
  delete curObj.modelProvider;
@@ -71,8 +71,16 @@ export const ES = {
71
71
  "Necesita GitHub Copilot CLI 2026-04 o posterior para el enrutamiento de proveedores BYOK.",
72
72
  "Gemini CLI speaks Google's native generateContent format — set the model after launch with `/model google/<id>`.":
73
73
  "Gemini CLI usa el formato nativo generateContent de Google — establece el modelo tras el inicio con `/model google/<id>`.",
74
- "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. GEMINI_API_KEY alone is ignored. Gemini-family models only.":
75
- "Escribe modelProvider=gemini en ~/.gemini/antigravity-cli/settings.json. GEMINI_API_KEY sola se ignora. Solo familia Gemini.",
74
+ "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. Routes any coding model via PleumRouter.":
75
+ "Escribe modelProvider=gemini en ~/.gemini/antigravity-cli/settings.json. Enruta cualquier modelo de codificación a través de PleumRouter.",
76
+ "↑ {n} more above": "↑ {n} más arriba",
77
+ "↓ {n} more below": "↓ {n} más abajo",
78
+ "Auto (best route)": "Auto (mejor ruta)",
79
+ "PleumRouter auto-routing — lowest price, lowest latency, automatic failover":
80
+ "Enrutamiento automático de PleumRouter: mejor precio, menor latencia y conmutación por error",
81
+ "Pin to {provider} (@{provider})": "Fijar al proveedor {provider} (@{provider})",
82
+ "Select routing provider": "Seleccionar proveedor de enrutamiento",
83
+ "... view all 200+ models": "... ver todos los 200+ modelos",
76
84
  "Uses an isolated ~/.pleum/pi-home (PI_CODING_AGENT_DIR). Your ~/.pi is untouched.":
77
85
  "Usa un ~/.pleum/pi-home aislado (PI_CODING_AGENT_DIR). No toca ~/.pi.",
78
86
  "Uses an isolated ~/.pleum/crush-home (CRUSH_GLOBAL_CONFIG). Your crush.json is untouched.":
@@ -280,5 +288,49 @@ export const ES = {
280
288
  "by tag": "por etiqueta",
281
289
  "by user": "por usuario",
282
290
  "Failed to fetch balance ({status})": "Error al obtener el saldo ({status})",
291
+
292
+ // Update & Auto-update
293
+ "Update CLI to the latest version": "Actualizar CLI a la última versión",
294
+ "Alias for pleum update --force": "Alias de pleum update --force",
295
+ "Auto update": "Actualización automática",
296
+ "Enabled (updates automatically on start)": "Activada (comprueba y actualiza al inicio)",
297
+ "Disabled": "Desactivada",
298
+ "Update available": "Actualización disponible",
299
+ "Update available: v{current} → v{latest}": "Actualización disponible: v{current} → v{latest}",
300
+ "Update available: v{current} → v{latest}. Run 'pleum update' to update.": "Actualización disponible: v{current} → v{latest}. Ejecute 'pleum update' para actualizar.",
301
+ "Auto-updating PleumRouter CLI...": "Actualizando automáticamente PleumRouter CLI...",
302
+ "Updating pleumrouter CLI...": "Actualizando PleumRouter CLI...",
303
+ "Updating pleumrouter (force reinstall)...": "Actualizando PleumRouter (reinstalación forzada)...",
304
+ "Already using the latest version of pleumrouter (v{version}).": "Ya está utilizando la última versión de PleumRouter (v{version}).",
305
+ "Use --force (-f) to reinstall anyway.": "Utilice --force (-f) para reinstalar de todos modos.",
306
+ "Updating pleumrouter from v{from} to v{to}...": "Actualizando PleumRouter de v{from} a v{to}...",
307
+ "Update completed! Please run `pleum` again to use the new version.": "¡Actualización completada! Ejecute `pleum` de nuevo para usar la nueva versión.",
308
+ "Update completed! Please run `pleum` again.": "¡Actualización completada! Ejecute `pleum` de nuevo.",
309
+
310
+ // Claude Code Tier Mapping
311
+ "Recommended Preset": "Configuración recomendada",
312
+ "Use Previous Configuration": "Usar configuración anterior",
313
+ "Configure Tiers Manually": "Configurar los 3 modelos manualmente",
314
+ "Choose specific models for Sonnet (main), Opus (thinker), and Haiku (subagent)": "Elegir modelos específicos para Sonnet (principal), Opus (pensamiento) y Haiku (subagente)",
315
+ "Select model tier mapping": "Seleccionar asignación de niveles de modelo",
316
+ "Main model (Sonnet tier)": "Modelo principal (nivel Sonnet)",
317
+ "Thinker model (Opus tier)": "Modelo de razonamiento (nivel Opus)",
318
+ "Subagent model (Haiku tier)": "Modelo de subagente (nivel Haiku)",
319
+
320
+ // Codex & agy Effort
321
+ "Select reasoning effort": "Seleccionar esfuerzo de razonamiento",
322
+ "Medium (Recommended)": "Medio (Recomendado)",
323
+ "Balanced reasoning speed and depth": "Velocidad y profundidad de razonamiento equilibradas",
324
+ "High (Deep reasoning)": "Alto (Razonamiento profundo)",
325
+ "Maximum reasoning depth for complex tasks": "Máxima profundidad de razonamiento para tareas complejas",
326
+ "Low (Fast response)": "Bajo (Respuesta rápida)",
327
+ "Lightweight reasoning for quick answers": "Razonamiento ligero para respuestas rápidas",
328
+ "High (Recommended)": "Alto (Recomendado)",
329
+ "High reasoning effort for standard tasks": "Alto esfuerzo de razonamiento para tareas estándar",
330
+ "Max (Maximum reasoning)": "Máximo (Razonamiento extremo)",
331
+ "Full reasoning effort for difficult problems": "Esfuerzo de razonamiento completo para problemas difíciles",
332
+ "Medium (Standard)": "Medio (Estándar)",
333
+ "Balanced reasoning effort": "Esfuerzo de razonamiento equilibrado",
334
+ "Low reasoning effort for rapid iteration": "Bajo esfuerzo de razonamiento para iteraciones rápidas",
283
335
  };
284
336
 
@@ -72,8 +72,16 @@ export const JA = {
72
72
  "BYOK プロバイダールーティングに対応した 2026-04 以降の GitHub Copilot CLI が必要です。",
73
73
  "Gemini CLI speaks Google's native generateContent format — set the model after launch with `/model google/<id>`.":
74
74
  "Gemini CLI は Google のネイティブ generateContent 形式を使用します — 起動後に `/model google/<id>` でモデルを設定してください。",
75
- "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. GEMINI_API_KEY alone is ignored. Gemini-family models only.":
76
- "~/.gemini/antigravity-cli/settings.json に modelProvider=gemini を書き込みます。GEMINI_API_KEY だけでは無視されます。Gemini 系のみです。",
75
+ "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. Routes any coding model via PleumRouter.":
76
+ "~/.gemini/antigravity-cli/settings.json に modelProvider=gemini を書き込みます。あらゆるコーディングモデルを PleumRouter でルーティングします。",
77
+ "↑ {n} more above": "↑ 上にあと {n} 件",
78
+ "↓ {n} more below": "↓ 下にあと {n} 件",
79
+ "Auto (best route)": "Auto (最適な自動ルーティング)",
80
+ "PleumRouter auto-routing — lowest price, lowest latency, automatic failover":
81
+ "PleumRouter 自動ルーティング — 最安値・低遅延・自動フェイルオーバー",
82
+ "Pin to {provider} (@{provider})": "{provider} プロバイダーに固定 (@{provider})",
83
+ "Select routing provider": "ルーティングプロバイダーの選択",
84
+ "... view all 200+ models": "... 全 200+ モデルを表示",
77
85
  "Uses an isolated ~/.pleum/pi-home (PI_CODING_AGENT_DIR). Your ~/.pi is untouched.":
78
86
  "隔離した ~/.pleum/pi-home (PI_CODING_AGENT_DIR) だけを使います。~/.pi は触れません。",
79
87
  "Uses an isolated ~/.pleum/crush-home (CRUSH_GLOBAL_CONFIG). Your crush.json is untouched.":
@@ -280,5 +288,49 @@ export const JA = {
280
288
  "by tag": "タグ別",
281
289
  "by user": "ユーザー別",
282
290
  "Failed to fetch balance ({status})": "残高の取得に失敗しました ({status})",
291
+
292
+ // Update & Auto-update
293
+ "Update CLI to the latest version": "CLIを最新バージョンにアップデート",
294
+ "Alias for pleum update --force": "pleum update --force のエイリアス",
295
+ "Auto update": "自動アップデート",
296
+ "Enabled (updates automatically on start)": "有効 (起動時に自動確認・更新)",
297
+ "Disabled": "無効",
298
+ "Update available": "更新可能",
299
+ "Update available: v{current} → v{latest}": "アップデートがあります: v{current} → v{latest}",
300
+ "Update available: v{current} → v{latest}. Run 'pleum update' to update.": "アップデートがあります: v{current} → v{latest}。「pleum update」を実行して更新してください。",
301
+ "Auto-updating PleumRouter CLI...": "PleumRouter CLI を自動アップデート中...",
302
+ "Updating pleumrouter CLI...": "PleumRouter CLI をアップデート中...",
303
+ "Updating pleumrouter (force reinstall)...": "PleumRouter をアップデート中 (強制再インストール)...",
304
+ "Already using the latest version of pleumrouter (v{version}).": "すでに最新バージョンの PleumRouter を使用しています (v{version})。",
305
+ "Use --force (-f) to reinstall anyway.": "強制再インストールするには --force (-f) を使用してください。",
306
+ "Updating pleumrouter from v{from} to v{to}...": "PleumRouter を v{from} から v{to} へアップデート中...",
307
+ "Update completed! Please run `pleum` again to use the new version.": "アップデート完了!新しいバージョンを使うには `pleum` を再起動してください。",
308
+ "Update completed! Please run `pleum` again.": "アップデート完了!`pleum` を再起動してください。",
309
+
310
+ // Claude Code Tier Mapping
311
+ "Recommended Preset": "推奨プリセット構成",
312
+ "Use Previous Configuration": "前回の構成を使用",
313
+ "Configure Tiers Manually": "3つのモデルを手動設定",
314
+ "Choose specific models for Sonnet (main), Opus (thinker), and Haiku (subagent)": "Sonnet(メイン)、Opus(思考)、Haiku(サブエージェント)のモデルを個別に選択",
315
+ "Select model tier mapping": "モデルティアのマッピングを選択",
316
+ "Main model (Sonnet tier)": "メインモデル (Sonnet ティア)",
317
+ "Thinker model (Opus tier)": "思考モデル (Opus ティア)",
318
+ "Subagent model (Haiku tier)": "サブエージェントモデル (Haiku ティア)",
319
+
320
+ // Codex & agy Effort
321
+ "Select reasoning effort": "推論強度 (effort) の選択",
322
+ "Medium (Recommended)": "中 (推奨)",
323
+ "Balanced reasoning speed and depth": "推論速度と深さのバランス",
324
+ "High (Deep reasoning)": "高 (深層推論)",
325
+ "Maximum reasoning depth for complex tasks": "複雑なタスク向けの最大の推論深度",
326
+ "Low (Fast response)": "低 (高速応答)",
327
+ "Lightweight reasoning for quick answers": "素早い応答のための軽量推論",
328
+ "High (Recommended)": "高 (推奨)",
329
+ "High reasoning effort for standard tasks": "標準タスク向けの高い推論強度",
330
+ "Max (Maximum reasoning)": "最大 (極限推論)",
331
+ "Full reasoning effort for difficult problems": "難関タスク向けの最大の推論強度",
332
+ "Medium (Standard)": "中 (標準)",
333
+ "Balanced reasoning effort": "バランスの取れた標準的な推論強度",
334
+ "Low reasoning effort for rapid iteration": "迅速なイテレーション向けの低い推論強度",
283
335
  };
284
336
 
@@ -52,8 +52,16 @@ export const KO = {
52
52
  "BYOK provider 라우팅을 지원하는 2026-04 이후 버전의 GitHub Copilot CLI가 필요합니다.",
53
53
  "Gemini CLI speaks Google's native generateContent format — set the model after launch with `/model google/<id>`.":
54
54
  "Gemini CLI는 Google의 네이티브 generateContent 포맷을 사용합니다 — 실행 후 `/model google/<id>`로 모델을 설정하세요.",
55
- "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. GEMINI_API_KEY alone is ignored. Gemini-family models only.":
56
- "~/.gemini/antigravity-cli/settings.json 에 modelProvider=gemini 를 씁니다. GEMINI_API_KEY만 있으면 무시됩니다. Gemini 계열만 됩니다.",
55
+ "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. Routes any coding model via PleumRouter.":
56
+ "~/.gemini/antigravity-cli/settings.json 에 modelProvider=gemini 를 씁니다. 모든 코딩 모델을 PleumRouter로 라우팅합니다.",
57
+ "↑ {n} more above": "↑ 위에 {n}개 더",
58
+ "↓ {n} more below": "↓ 아래에 {n}개 더",
59
+ "Auto (best route)": "Auto (최적 자동 라우팅)",
60
+ "PleumRouter auto-routing — lowest price, lowest latency, automatic failover":
61
+ "PleumRouter 최적 자동 라우팅 — 최저가·최저지연·자동 페일오버",
62
+ "Pin to {provider} (@{provider})": "{provider} 제공자로 고정 (@{provider})",
63
+ "Select routing provider": "라우팅 제공자 선택",
64
+ "... view all 200+ models": "... 전체 200+ 모델 보기",
57
65
  "Uses an isolated ~/.pleum/pi-home (PI_CODING_AGENT_DIR). Your ~/.pi is untouched.":
58
66
  "격리된 ~/.pleum/pi-home (PI_CODING_AGENT_DIR)만 씁니다. ~/.pi는 건드리지 않습니다.",
59
67
  "Uses an isolated ~/.pleum/crush-home (CRUSH_GLOBAL_CONFIG). Your crush.json is untouched.":
@@ -343,5 +351,49 @@ export const KO = {
343
351
  "by tag": "태그별",
344
352
  "by user": "사용자별",
345
353
  "Failed to fetch balance ({status})": "잔액 조회 실패 ({status})",
354
+
355
+ // Update & Auto-update
356
+ "Update CLI to the latest version": "CLI를 최신 버전으로 업데이트",
357
+ "Alias for pleum update --force": "pleum update --force의 별칭",
358
+ "Auto update": "자동 업데이트",
359
+ "Enabled (updates automatically on start)": "켜짐 (시작 시 자동 확인 및 업데이트)",
360
+ "Disabled": "꺼짐",
361
+ "Update available": "업데이트 가능",
362
+ "Update available: v{current} → v{latest}": "업데이트 가능: v{current} → v{latest}",
363
+ "Update available: v{current} → v{latest}. Run 'pleum update' to update.": "업데이트 가능: v{current} → v{latest}. 'pleum update'를 실행하여 업데이트하세요.",
364
+ "Auto-updating PleumRouter CLI...": "PleumRouter CLI 자동 업데이트 중...",
365
+ "Updating pleumrouter CLI...": "PleumRouter CLI 업데이트 중...",
366
+ "Updating pleumrouter (force reinstall)...": "PleumRouter 업데이트 중 (강제 재설치)...",
367
+ "Already using the latest version of pleumrouter (v{version}).": "이미 최신 버전의 PleumRouter를 사용 중입니다 (v{version}).",
368
+ "Use --force (-f) to reinstall anyway.": "재설치하려면 --force (-f) 옵션을 사용하세요.",
369
+ "Updating pleumrouter from v{from} to v{to}...": "PleumRouter를 v{from}에서 v{to}로 업데이트 중...",
370
+ "Update completed! Please run `pleum` again to use the new version.": "업데이트 완료! 새 버전을 사용하려면 `pleum`을 다시 실행하세요.",
371
+ "Update completed! Please run `pleum` again.": "업데이트 완료! `pleum`을 다시 실행하세요.",
372
+
373
+ // Claude Code Tier Mapping
374
+ "Recommended Preset": "추천 기본 구성",
375
+ "Use Previous Configuration": "직전 구성 사용",
376
+ "Configure Tiers Manually": "직접 3개 모델 지정",
377
+ "Choose specific models for Sonnet (main), Opus (thinker), and Haiku (subagent)": "Sonnet(메인), Opus(추론), Haiku(서브에이전트)에 사용할 모델을 직접 선택합니다",
378
+ "Select model tier mapping": "모델 티어 매핑 선택",
379
+ "Main model (Sonnet tier)": "메인 모델 (Sonnet 티어)",
380
+ "Thinker model (Opus tier)": "심층 추론 모델 (Opus 티어)",
381
+ "Subagent model (Haiku tier)": "서브에이전트 모델 (Haiku 티어)",
382
+
383
+ // Codex & agy Effort
384
+ "Select reasoning effort": "추론 강도(effort) 선택",
385
+ "Medium (Recommended)": "보통 (추천)",
386
+ "Balanced reasoning speed and depth": "추론 속도와 깊이의 균형",
387
+ "High (Deep reasoning)": "높음 (심층 추론)",
388
+ "Maximum reasoning depth for complex tasks": "복잡한 작업을 위한 최대 추론 깊이",
389
+ "Low (Fast response)": "낮음 (빠른 응답)",
390
+ "Lightweight reasoning for quick answers": "빠른 응답을 위한 가벼운 추론",
391
+ "High (Recommended)": "높음 (추천)",
392
+ "High reasoning effort for standard tasks": "일반 작업을 위한 높은 추론 강도",
393
+ "Max (Maximum reasoning)": "최대 (한계 추론)",
394
+ "Full reasoning effort for difficult problems": "고난도 문제를 위한 최대 추론 강도",
395
+ "Medium (Standard)": "보통 (표준)",
396
+ "Balanced reasoning effort": "균형 잡힌 표준 추론 강도",
397
+ "Low reasoning effort for rapid iteration": "빠른 반복 작업을 위한 낮은 추론 강도",
346
398
  };
347
399
 
@@ -71,8 +71,16 @@ export const ZH = {
71
71
  "需要 2026-04 或更新版本的 GitHub Copilot CLI 才支持 BYOK provider 路由。",
72
72
  "Gemini CLI speaks Google's native generateContent format — set the model after launch with `/model google/<id>`.":
73
73
  "Gemini CLI 使用 Google 原生 generateContent 格式 — 启动后用 `/model google/<id>` 设置模型。",
74
- "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. GEMINI_API_KEY alone is ignored. Gemini-family models only.":
75
- "会把 modelProvider=gemini 写入 ~/.gemini/antigravity-cli/settings.json。只设 GEMINI_API_KEY 会被忽略。仅 Gemini 系列。",
74
+ "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. Routes any coding model via PleumRouter.":
75
+ "将 modelProvider=gemini 写入 ~/.gemini/antigravity-cli/settings.json。通过 PleumRouter 路由所有编码模型。",
76
+ "↑ {n} more above": "↑ 上方还有 {n} 项",
77
+ "↓ {n} more below": "↓ 下方还有 {n} 项",
78
+ "Auto (best route)": "Auto (最佳自动路由)",
79
+ "PleumRouter auto-routing — lowest price, lowest latency, automatic failover":
80
+ "PleumRouter 自动路由 — 最优价格·超低延迟·自动故障转移",
81
+ "Pin to {provider} (@{provider})": "固定到 {provider} 提供商 (@{provider})",
82
+ "Select routing provider": "选择路由提供商",
83
+ "... view all 200+ models": "... 查看全部 200+ 个模型",
76
84
  "Uses an isolated ~/.pleum/pi-home (PI_CODING_AGENT_DIR). Your ~/.pi is untouched.":
77
85
  "只用隔离的 ~/.pleum/pi-home(PI_CODING_AGENT_DIR),不改 ~/.pi。",
78
86
  "Uses an isolated ~/.pleum/crush-home (CRUSH_GLOBAL_CONFIG). Your crush.json is untouched.":
@@ -280,5 +288,49 @@ export const ZH = {
280
288
  "by tag": "按标签",
281
289
  "by user": "按用户",
282
290
  "Failed to fetch balance ({status})": "获取余额失败 ({status})",
291
+
292
+ // Update & Auto-update
293
+ "Update CLI to the latest version": "将 CLI 更新到最新版本",
294
+ "Alias for pleum update --force": "pleum update --force 的别名",
295
+ "Auto update": "自动更新",
296
+ "Enabled (updates automatically on start)": "已启用 (启动时自动检查并更新)",
297
+ "Disabled": "已禁用",
298
+ "Update available": "有可用更新",
299
+ "Update available: v{current} → v{latest}": "有可用更新: v{current} → v{latest}",
300
+ "Update available: v{current} → v{latest}. Run 'pleum update' to update.": "有可用更新: v{current} → v{latest}。运行 'pleum update' 进行更新。",
301
+ "Auto-updating PleumRouter CLI...": "正在自动更新 PleumRouter CLI...",
302
+ "Updating pleumrouter CLI...": "正在更新 PleumRouter CLI...",
303
+ "Updating pleumrouter (force reinstall)...": "正在更新 PleumRouter (强制重新安装)...",
304
+ "Already using the latest version of pleumrouter (v{version}).": "已在使用最新版本的 PleumRouter (v{version})。",
305
+ "Use --force (-f) to reinstall anyway.": "如需强制重新安装,请使用 --force (-f)。",
306
+ "Updating pleumrouter from v{from} to v{to}...": "正在将 PleumRouter 从 v{from} 更新到 v{to}...",
307
+ "Update completed! Please run `pleum` again to use the new version.": "更新完成!请重新运行 `pleum` 以使用新版本。",
308
+ "Update completed! Please run `pleum` again.": "更新完成!请重新运行 `pleum`。",
309
+
310
+ // Claude Code Tier Mapping
311
+ "Recommended Preset": "推荐默认配置",
312
+ "Use Previous Configuration": "使用上次配置",
313
+ "Configure Tiers Manually": "手动配置3个模型",
314
+ "Choose specific models for Sonnet (main), Opus (thinker), and Haiku (subagent)": "为 Sonnet (主模型)、Opus (深度思考) 和 Haiku (子代理) 分别指定模型",
315
+ "Select model tier mapping": "选择模型层级映射",
316
+ "Main model (Sonnet tier)": "主模型 (Sonnet 层级)",
317
+ "Thinker model (Opus tier)": "深度思考模型 (Opus 层级)",
318
+ "Subagent model (Haiku tier)": "子代理模型 (Haiku 层级)",
319
+
320
+ // Codex & agy Effort
321
+ "Select reasoning effort": "选择推理强度 (effort)",
322
+ "Medium (Recommended)": "中 (推荐)",
323
+ "Balanced reasoning speed and depth": "推理速度与深度的平衡",
324
+ "High (Deep reasoning)": "高 (深度推理)",
325
+ "Maximum reasoning depth for complex tasks": "适合复杂任务的最大推理深度",
326
+ "Low (Fast response)": "低 (快速响应)",
327
+ "Lightweight reasoning for quick answers": "快速解答的轻量推理",
328
+ "High (Recommended)": "高 (推荐)",
329
+ "High reasoning effort for standard tasks": "标准任务的高推理强度",
330
+ "Max (Maximum reasoning)": "最大 (极值推理)",
331
+ "Full reasoning effort for difficult problems": "困难问题的全力推理强度",
332
+ "Medium (Standard)": "中 (标准)",
333
+ "Balanced reasoning effort": "平衡的标准推理强度",
334
+ "Low reasoning effort for rapid iteration": "快速迭代的低推理强度",
283
335
  };
284
336