pleumrouter 0.4.0 → 0.5.2
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/README.md +12 -11
- package/bin/pleum.mjs +13 -10
- package/package.json +1 -1
- package/src/balance.mjs +5 -13
- package/src/completion.mjs +2 -1
- package/src/config.mjs +11 -7
- package/src/interactive.mjs +14 -0
- package/src/launch.mjs +2 -2
- package/src/locales/es.mjs +18 -1
- package/src/locales/ja.mjs +18 -1
- package/src/locales/ko.mjs +21 -1
- package/src/locales/zh.mjs +18 -1
- package/src/login.mjs +3 -3
- package/src/menu.mjs +29 -14
- package/src/recipes.mjs +15 -13
- package/src/run.mjs +5 -3
- package/src/settings.mjs +168 -0
package/README.md
CHANGED
|
@@ -18,20 +18,20 @@ bun install -g pleumrouter # bun
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npx pleumrouter login # 브라우저 로그인 + 키 자동 저장
|
|
21
|
-
npx pleumrouter launch claude --model claude-sonnet-
|
|
22
|
-
npx pleumrouter launch codex --model gpt-
|
|
23
|
-
npx pleumrouter run gpt-4
|
|
24
|
-
npx pleumrouter run gpt-4
|
|
25
|
-
cat error.log | npx pleumrouter run gpt-4
|
|
21
|
+
npx pleumrouter launch claude --model claude-sonnet-5
|
|
22
|
+
npx pleumrouter launch codex --model gpt-5.3-codex
|
|
23
|
+
npx pleumrouter run gpt-5.4-mini # 터미널 채팅
|
|
24
|
+
npx pleumrouter run gpt-5.4-mini "한 줄 요약: ..." # 1회 실행(스크립트용)
|
|
25
|
+
cat error.log | npx pleumrouter run gpt-5.4-mini "원인은?" # 파이프
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## 명령
|
|
29
29
|
|
|
30
30
|
| 명령 | 설명 |
|
|
31
31
|
|------|------|
|
|
32
|
-
| `pleum` | (인자 없음, TTY) 인터랙티브 런처 메뉴 — 키보드 전용. ↑/↓ 이동 · enter 실행 · → 모델 설정 · esc 종료(서브메뉴에선 뒤로) · ctrl+c 즉시 종료. "에이전트 실행" 진입 시 Claude Code·Codex·opencode·omp·Hermes Agent·Qwen Code·GitHub Copilot CLI 등 지원 에이전트 전체를 설치 여부와 함께 볼 수 있고, 미설치는 확인 후 자동 설치(또는 설정 파일 안내). 실행 종료 후 메뉴 복귀. 파이프에선 도움말 출력 |
|
|
32
|
+
| `pleum` | (인자 없음, TTY) 인터랙티브 런처 메뉴 — 키보드 전용. ↑/↓ 이동 · enter 실행 · → 모델 설정 · esc 종료(서브메뉴에선 뒤로) · ctrl+c 즉시 종료. "에이전트 실행" 진입 시 Claude Code·Codex·opencode·omp·Hermes Agent·Qwen Code·GitHub Copilot CLI 등 지원 에이전트 전체를 설치 여부와 함께 볼 수 있고, 미설치는 확인 후 자동 설치(또는 설정 파일 안내). "설정" 진입 시 언어·에이전트별 기본 모델·계정(로그인/로그아웃)·CLI 업데이트·셸 자동완성을 한곳에서 관리. 실행 종료 후 메뉴 복귀. 파이프에선 도움말 출력 |
|
|
33
33
|
| `pleum login [--device]` | 브라우저로 로그인 → CLI가 API 키를 자동 발급·저장(`~/.pleum/config.json`). `--device`는 SSH·CI |
|
|
34
|
-
| `pleum logout` | 저장된
|
|
34
|
+
| `pleum logout` | 저장된 API 키만 삭제(언어·기본 모델 등 나머지 설정은 유지) |
|
|
35
35
|
| `pleum launch [tool] [--model id] [-- args]` | 에이전트 실행. `--` 뒤 인자는 도구로 그대로 전달. 도구 생략 시 지원 목록·설치 여부 표시 |
|
|
36
36
|
| `pleum run [model] ["프롬프트"]` | 채팅 — 대화형 REPL, 1회 실행, stdin 파이프. 모델 생략 시 화살표로 선택 |
|
|
37
37
|
| `pleum models [query] [--json]` | 모델 목록(공개) — provider·컨텍스트·가격(₩/1M) 컬럼, `--json`은 스크립트용 |
|
|
@@ -61,7 +61,7 @@ cat error.log | npx pleumrouter run gpt-4.1 "원인은?" # 파이프
|
|
|
61
61
|
| `/exit`, `/quit` | 종료 |
|
|
62
62
|
|
|
63
63
|
- 응답은 **마크다운 스트리밍 렌더**(굵게·코드블록 하이라이트·리스트) — 파이프로 빼면 자동으로 원문 그대로
|
|
64
|
-
- 응답 후 비용이
|
|
64
|
+
- 응답 후 비용이 현지 통화(한국 ₩)로 표시(스트리밍 마지막 청크 기준)
|
|
65
65
|
- **Ctrl+C**: 생성 중엔 1회로 중단(대화 유지), 대기 중엔 2초 내 2회로 종료
|
|
66
66
|
- 여러 줄 입력: `"""` 한 줄 → 본문 → `"""` 한 줄
|
|
67
67
|
- 이미지: 메시지에 이미지 파일 경로를 그대로 포함 (예: `이 그림 설명해줘 ./shot.png`)
|
|
@@ -70,10 +70,11 @@ cat error.log | npx pleumrouter run gpt-4.1 "원인은?" # 파이프
|
|
|
70
70
|
|
|
71
71
|
## 자동 실행 지원 도구
|
|
72
72
|
|
|
73
|
-
`claude` · `aider` · `codex` · `goose` · `openhands`
|
|
74
|
-
세팅하고 바로 실행한다. 사용자의 기존 설정 파일은
|
|
73
|
+
`claude` · `aider` · `codex` · `goose` · `openhands` · `opencode` · `omp` · `hermes` · `qwen` · `copilot` —
|
|
74
|
+
env(또는 격리된 `CODEX_HOME`/임시 설정 문자열)만 세팅하고 바로 실행한다. 사용자의 기존 설정 파일은
|
|
75
|
+
건드리지 않는다.
|
|
75
76
|
|
|
76
|
-
`
|
|
77
|
+
`crush` 는 provider 정의가 파일에 필요해 설정 스니펫만 안내한다.
|
|
77
78
|
|
|
78
79
|
## 환경변수
|
|
79
80
|
|
package/bin/pleum.mjs
CHANGED
|
@@ -10,9 +10,10 @@ import { top } from "../src/top.mjs";
|
|
|
10
10
|
import { health } from "../src/health.mjs";
|
|
11
11
|
import { completion } from "../src/completion.mjs";
|
|
12
12
|
import { upgrade } from "../src/upgrade.mjs";
|
|
13
|
-
import {
|
|
13
|
+
import { readConfig, writeConfig, clearCredentials } from "../src/config.mjs";
|
|
14
14
|
import { mainMenu } from "../src/menu.mjs";
|
|
15
15
|
import { lang } from "../src/lang.mjs";
|
|
16
|
+
import { RECIPES } from "../src/recipes.mjs";
|
|
16
17
|
import { t } from "../src/i18n.mjs";
|
|
17
18
|
|
|
18
19
|
// 명령 구문(왼쪽)은 로케일과 무관 — 설명(오른쪽)만 번역, 폭은 가장 긴 구문 기준으로 계산.
|
|
@@ -23,7 +24,7 @@ function buildHelp() {
|
|
|
23
24
|
["", t("--device: browser-less environments (SSH/CI)")],
|
|
24
25
|
["pleum logout", t("Delete the saved key")],
|
|
25
26
|
["pleum launch [tool] [--model id]", t("Run a coding agent against PleumRouter")],
|
|
26
|
-
["", "
|
|
27
|
+
["", `(${Object.keys(RECIPES).join(" · ")})`],
|
|
27
28
|
["", t("omit the tool to see the supported list and install status")],
|
|
28
29
|
['pleum run [model] ["prompt"]', t("Terminal chat (REPL) / one-shot / pipe")],
|
|
29
30
|
["", t("omit the model for an arrow-key picker; /help lists commands")],
|
|
@@ -42,12 +43,12 @@ function buildHelp() {
|
|
|
42
43
|
const body = rows.map(([cmd, desc]) => ` ${cmd.padEnd(width)}${desc}`).join("\n");
|
|
43
44
|
|
|
44
45
|
const examples = [
|
|
45
|
-
["pleum launch claude --model claude-sonnet-
|
|
46
|
-
["pleum launch codex --model gpt-
|
|
47
|
-
["pleum run gpt-4
|
|
48
|
-
[`pleum run gpt-4
|
|
49
|
-
[`cat error.log | pleum run gpt-4
|
|
50
|
-
["pleum models deepseek/deepseek-
|
|
46
|
+
["pleum launch claude --model claude-sonnet-5", null],
|
|
47
|
+
["pleum launch codex --model gpt-5.3-codex -- --full-auto", null],
|
|
48
|
+
["pleum run gpt-5.4-mini", t("interactive")],
|
|
49
|
+
[`pleum run gpt-5.4-mini "${t("Summarize in one line: ...")}"`, t("one-shot")],
|
|
50
|
+
[`cat error.log | pleum run gpt-5.4-mini "${t("what caused this?")}"`, t("pipe")],
|
|
51
|
+
["pleum models deepseek/deepseek-v4-flash --info", t("model detail")],
|
|
51
52
|
["pleum usage --by day --days 7", t("last 7 days, by day")],
|
|
52
53
|
["pleum login --device", t("SSH/CI environment")],
|
|
53
54
|
];
|
|
@@ -83,10 +84,12 @@ try {
|
|
|
83
84
|
if (rest.includes("--device")) await loginDevice();
|
|
84
85
|
else await login();
|
|
85
86
|
break;
|
|
86
|
-
case "logout":
|
|
87
|
-
|
|
87
|
+
case "logout": {
|
|
88
|
+
const cfg = readConfig();
|
|
89
|
+
if (cfg) writeConfig(clearCredentials(cfg)); // 로그인 이력 없으면(cfg=null) 빈 파일을 새로 만들지 않는다
|
|
88
90
|
console.log(`✓ ${t("Logged out")}`);
|
|
89
91
|
break;
|
|
92
|
+
}
|
|
90
93
|
case "lang":
|
|
91
94
|
await lang(rest);
|
|
92
95
|
break;
|
package/package.json
CHANGED
package/src/balance.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { V1
|
|
2
|
-
import {
|
|
3
|
-
import { t } from "./i18n.mjs";
|
|
1
|
+
import { V1 } from "./config.mjs";
|
|
2
|
+
import { requireKey } from "./config.mjs";
|
|
4
3
|
|
|
5
4
|
export async function balance() {
|
|
6
5
|
const key = requireKey();
|
|
@@ -9,18 +8,11 @@ export async function balance() {
|
|
|
9
8
|
});
|
|
10
9
|
if (!res.ok) {
|
|
11
10
|
const body = await res.text().catch(() => "");
|
|
12
|
-
console.error(
|
|
13
|
-
if (res.status === 401) {
|
|
14
|
-
console.error(style.dim(t("The server may not support API-key balance checks yet, or the key may have expired — try pleum login again.")));
|
|
15
|
-
}
|
|
11
|
+
console.error(`잔액 조회 실패 (HTTP ${res.status})${body ? `: ${body}` : ""}`);
|
|
16
12
|
process.exit(1);
|
|
17
13
|
}
|
|
18
14
|
const data = await res.json();
|
|
19
15
|
const plm = data.balance_krw;
|
|
20
|
-
const usd = data.balance_usd != null ?
|
|
21
|
-
console.log(
|
|
22
|
-
const threshold = data.auto_charge_threshold_krw;
|
|
23
|
-
if (threshold != null && plm < threshold) {
|
|
24
|
-
console.log(style.yellow(`⚠ ${t("Balance is below the auto-charge threshold ({threshold} PLM).", { threshold: threshold.toLocaleString("ko-KR") })}`));
|
|
25
|
-
}
|
|
16
|
+
const usd = data.balance_usd != null ? ` / $${data.balance_usd.toFixed(4)}` : "";
|
|
17
|
+
console.log(`잔액: ₩${plm.toLocaleString("ko-KR")}${usd}`);
|
|
26
18
|
}
|
package/src/completion.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Shell 자동완성 스크립트 출력. 명령·도구는 정적, 모델명은 `pleum models`에서 동적으로 채운다.
|
|
2
2
|
// 설치: pleum completion zsh >> ~/.zshrc · pleum completion bash >> ~/.bashrc
|
|
3
3
|
import { t } from "./i18n.mjs";
|
|
4
|
+
import { RECIPES, MANUAL } from "./recipes.mjs";
|
|
4
5
|
|
|
5
6
|
const COMMANDS = "login logout lang launch run models top balance usage health version upgrade completion help";
|
|
6
|
-
const TOOLS = "
|
|
7
|
+
const TOOLS = [...Object.keys(RECIPES), ...Object.keys(MANUAL)].join(" ");
|
|
7
8
|
|
|
8
9
|
function bash() {
|
|
9
10
|
return `# pleum bash completion — eval "$(pleum completion bash)" (${t("or add to ~/.bashrc")})
|
package/src/config.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { readFileSync, writeFileSync, mkdirSync
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { t } from "./i18n.mjs";
|
|
5
5
|
|
|
6
6
|
// 공개 게이트웨이 루트. 셀프호스트/스테이징은 PLEUM_BASE_URL로 덮어쓴다.
|
|
@@ -29,12 +29,16 @@ export function writeConfig(cfg) {
|
|
|
29
29
|
writeFileSync(FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
// 로그인 저장 — 기존 설정(lang·launch_models·last_selection)을 보존한 채 자격증명만 병합.
|
|
33
|
+
// login/loginDevice가 매번 writeConfig({key, base_url})로 전체를 덮어쓰던 버그의 수정.
|
|
34
|
+
export function mergeLoginConfig(existing, patch) {
|
|
35
|
+
return { ...(existing ?? {}), ...patch };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 로그아웃 — key·base_url(자격증명)만 제거. lang·launch_models 등 나머지 설정은 보존.
|
|
39
|
+
export function clearCredentials(existing) {
|
|
40
|
+
const { key, base_url, ...rest } = existing ?? {};
|
|
41
|
+
return rest;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
// 키가 필요한 명령의 공통 가드. 없으면 안내 후 종료.
|
package/src/interactive.mjs
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
// 스피너 + raw-mode 화살표 셀렉터 — zero-dep 라인 플로우 인터랙션(키보드 전용).
|
|
2
2
|
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
3
4
|
import { style, displayWidth, terminalWidth } from "./ansi.mjs";
|
|
4
5
|
import { t } from "./i18n.mjs";
|
|
5
6
|
|
|
7
|
+
// Y/n 확인. message는 "[Y/n] " 접미사까지 포함한 완성 문자열(ensureInstalled 관례 —
|
|
8
|
+
// 로케일별 표기를 번역 쪽이 소유). 비TTY면 프롬프트 없이 defaultYes 즉시 반환
|
|
9
|
+
// (select()의 비TTY 가드와 동일 패턴 — 테스트 용이성 겸용).
|
|
10
|
+
export async function confirm(message, { defaultYes = true, input = process.stdin, output = process.stdout } = {}) {
|
|
11
|
+
if (!input.isTTY || !output.isTTY) return defaultYes;
|
|
12
|
+
const rl = createInterface({ input, output });
|
|
13
|
+
const ans = await new Promise((res) => rl.question(message, res));
|
|
14
|
+
rl.close();
|
|
15
|
+
const a = ans.trim().toLowerCase();
|
|
16
|
+
if (!a) return defaultYes;
|
|
17
|
+
return a.startsWith("y");
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
7
21
|
|
|
8
22
|
// 브레일 스피너. 기본 stderr — stdout 파이프를 더럽히지 않는다. 비TTY면 no-op.
|
package/src/launch.mjs
CHANGED
|
@@ -27,7 +27,7 @@ export function launchHelp() {
|
|
|
27
27
|
const ok = onPath(m.bin) ? t("✓ installed") : `${t("✗ not installed —")} ${m.install}`;
|
|
28
28
|
console.log(` ${name.padEnd(11)} ${m.label.padEnd(13)} ${ok}`);
|
|
29
29
|
}
|
|
30
|
-
console.log(`\n${t("Example: pleum launch claude --model claude-sonnet-
|
|
30
|
+
console.log(`\n${t("Example: pleum launch claude --model claude-sonnet-5")}`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// exit:false면 process.exit 대신 자식 종료 코드를 반환 — 런처 메뉴의 실행 후 복귀용.
|
|
@@ -59,7 +59,7 @@ export async function launch(tool, model, passthrough, { exit = true } = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const args = [...r.args(model), ...passthrough];
|
|
62
|
-
if (r.note) console.log(`ℹ ${r.note}`);
|
|
62
|
+
if (r.note) console.log(`ℹ ${t(r.note)}`);
|
|
63
63
|
console.log(`▶ ${t("Running {label}: {cmd}", { label: r.label, cmd: `${r.bin} ${args.join(" ")}` })}\n`);
|
|
64
64
|
|
|
65
65
|
return await new Promise((resolve) => {
|
package/src/locales/es.mjs
CHANGED
|
@@ -9,6 +9,7 @@ export const ES = {
|
|
|
9
9
|
"/help for commands · /exit to quit · wrap multi-line input in \"\"\" · Ctrl+C to stop generation": "/help para comandos · /exit para salir · envuelve la entrada multilínea en \"\"\" · Ctrl+C para detener la generación",
|
|
10
10
|
"1) Add to {file}:": "1) Añade a {file}:",
|
|
11
11
|
"2) Export the key and run:": "2) Exporta la clave y ejecuta:",
|
|
12
|
+
"Account": "Cuenta",
|
|
12
13
|
"All models — price and context included": "Todos los modelos — precio y contexto incluidos",
|
|
13
14
|
"Anthropic's coding agent — supports subagents": "Agente de codificación de Anthropic — admite subagentes",
|
|
14
15
|
"Attaching and uploading {n} local image(s) from the prompt.": "Adjuntando y subiendo {n} imagen(es) local(es) del prompt.",
|
|
@@ -21,7 +22,7 @@ export const ES = {
|
|
|
21
22
|
"GitHub's terminal coding agent": "Agente de codificación de terminal de GitHub",
|
|
22
23
|
"Available: {list}": "Disponibles: {list}",
|
|
23
24
|
"Balance": "Saldo",
|
|
24
|
-
"Balance is below the auto-charge threshold ({threshold}
|
|
25
|
+
"Balance is below the auto-charge threshold ({threshold} ₩).": "El saldo está por debajo del umbral de recarga automática ({threshold} ₩).",
|
|
25
26
|
"Balance:": "Saldo:",
|
|
26
27
|
"Block's local-first agent": "Agente local-first de Block",
|
|
27
28
|
"Chat with a model": "Chatear con un modelo",
|
|
@@ -29,6 +30,7 @@ export const ES = {
|
|
|
29
30
|
"Check your credit balance": "Consultar tu saldo de crédito",
|
|
30
31
|
"Choose a language": "Elegir un idioma",
|
|
31
32
|
"Choose a number, or search (Enter to cancel): ": "Elige un número, o busca (Enter para cancelar): ",
|
|
33
|
+
"Choose a shell": "Elegir una shell",
|
|
32
34
|
"Code": "Código",
|
|
33
35
|
"Commands:\n /help, /? this help\n /clear reset the conversation context (keeps the system prompt)\n /system [text] set the system prompt (no arg shows the current value)\n /model [id] switch models mid-session (no arg = arrow-key picker)\n /models [query] browse the model list\n /save [name] save the current session (default: last)\n /load [name] load a saved session\n /verbose toggle token/speed stats\n /exit, /quit quit\n Multi-line input: a line with \"\"\" → body → a line with \"\"\"\n Images: include an image file path right in the message (e.g. describe this ./shot.png)": "Comandos:\n /help, /? esta ayuda\n /clear reinicia el contexto de la conversación (conserva el system prompt)\n /system [text] define el system prompt (sin argumento muestra el valor actual)\n /model [id] cambia de modelo durante la sesión (sin argumento = selector con flechas)\n /models [query] explora la lista de modelos\n /save [name] guarda la sesión actual (por defecto: last)\n /load [name] carga una sesión guardada\n /verbose activa/desactiva estadísticas de tokens/velocidad\n /exit, /quit salir\n Entrada multilínea: una línea con \"\"\" → cuerpo → una línea con \"\"\"\n Imágenes: incluye la ruta de un archivo de imagen directamente en el mensaje (p. ej. describe esto ./shot.png)",
|
|
34
36
|
"Context cleared": "Contexto borrado",
|
|
@@ -36,6 +38,8 @@ export const ES = {
|
|
|
36
38
|
"Current language: {name}": "Idioma actual: {name}",
|
|
37
39
|
"Current model:": "Modelo actual:",
|
|
38
40
|
"Current system prompt:": "System prompt actual:",
|
|
41
|
+
"Current version: {version}": "Versión actual: {version}",
|
|
42
|
+
"Default models": "Modelos predeterminados",
|
|
39
43
|
"Delete the saved key": "Eliminar la clave guardada",
|
|
40
44
|
"Error:": "Error:",
|
|
41
45
|
"Example: pleum launch claude --model claude-sonnet-4": "Ejemplo: pleum launch claude --model claude-sonnet-4",
|
|
@@ -64,13 +68,16 @@ export const ES = {
|
|
|
64
68
|
"Health check": "Verificación de estado",
|
|
65
69
|
"Install canceled — manual install: {install}": "Instalación cancelada — instalación manual: {install}",
|
|
66
70
|
"Install failed (exit {code})": "Error de instalación (código de salida {code})",
|
|
71
|
+
"Language": "Idioma",
|
|
67
72
|
"Language set to {name}": "Idioma configurado a {name}",
|
|
73
|
+
"Language, default models, account, updates": "Idioma, modelos predeterminados, cuenta, actualizaciones",
|
|
68
74
|
"Leaderboard": "Ranking",
|
|
69
75
|
"Log in": "Iniciar sesión",
|
|
70
76
|
"Log in at the URL above, then enter the code (within {min} min).": "Inicia sesión en la URL de arriba y luego introduce el código (dentro de {min} min).",
|
|
71
77
|
"Log in from your browser. If it doesn't open automatically, open this URL:": "Inicia sesión desde tu navegador. Si no se abre automáticamente, abre esta URL:",
|
|
72
78
|
"Log in via browser + auto-save the key": "Iniciar sesión por navegador y guardar la clave automáticamente",
|
|
73
79
|
"Log in via browser and save the API key": "Iniciar sesión por navegador y guardar la clave de API",
|
|
80
|
+
"Log out? [Y/n] ": "¿Cerrar sesión? [Y/n] ",
|
|
74
81
|
"Logged in": "Sesión iniciada",
|
|
75
82
|
"Logged in. Saved the key to ~/.pleum/config.json.": "Sesión iniciada. Clave guardada en ~/.pleum/config.json.",
|
|
76
83
|
"Logged out": "Sesión cerrada",
|
|
@@ -104,9 +111,11 @@ export const ES = {
|
|
|
104
111
|
"PleumRouter terminal CLI": "CLI de terminal de PleumRouter",
|
|
105
112
|
"Press Enter to install now": "Pulsa Enter para instalar ahora",
|
|
106
113
|
"Print a shell completion script": "Imprimir un script de autocompletado de shell",
|
|
114
|
+
"Print a zsh/bash completion script": "Imprimir un script de autocompletado de zsh/bash",
|
|
107
115
|
"Print the version": "Imprimir la versión",
|
|
108
116
|
"Provider share": "Cuota por proveedor",
|
|
109
117
|
"Reinstall the latest version": "Reinstalar la última versión",
|
|
118
|
+
"Reinstalling — pleum will exit when finished. Run `pleum` again afterward.": "Reinstalando — pleum se cerrará al terminar. Ejecuta pleum de nuevo después.",
|
|
110
119
|
"Run a coding agent against PleumRouter": "Ejecutar un agente de codificación contra PleumRouter",
|
|
111
120
|
"Run pleum login again to get a new key (recent keys include it by default).": "Ejecuta pleum login de nuevo para obtener una nueva clave (las claves recientes la incluyen por defecto).",
|
|
112
121
|
"Running {label}: {cmd}": "Ejecutando {label}: {cmd}",
|
|
@@ -117,6 +126,9 @@ export const ES = {
|
|
|
117
126
|
"Select a model": "Seleccionar un modelo",
|
|
118
127
|
"Session loaded (model: {model})": "Sesión cargada (modelo: {model})",
|
|
119
128
|
"Session saved:": "Sesión guardada:",
|
|
129
|
+
"Set the default model used per agent": "Definir el modelo predeterminado por agente",
|
|
130
|
+
"Settings": "Configuración",
|
|
131
|
+
"Shell completion": "Autocompletado de shell",
|
|
120
132
|
"Show/change display language (en|ko|ja|zh|es)": "Mostrar/cambiar el idioma de visualización (en|ko|ja|zh|es)",
|
|
121
133
|
"Specify a model: pleum run <model> [\"prompt\"] (model selection isn't available on non-interactive stdin)": "Especifica un modelo: pleum run <model> [\"prompt\"] (la selección de modelo no está disponible en stdin no interactivo)",
|
|
122
134
|
"Spend over the last 30 days": "Gasto de los últimos 30 días",
|
|
@@ -137,12 +149,14 @@ export const ES = {
|
|
|
137
149
|
"Unknown command: {cmd}": "Comando desconocido: {cmd}",
|
|
138
150
|
"Unsupported language: {code}\n Available: {list}": "Idioma no compatible: {code}\n Disponibles: {list}",
|
|
139
151
|
"Unsupported tool: {tool}": "Herramienta no compatible: {tool}",
|
|
152
|
+
"Update CLI": "Actualizar la CLI",
|
|
140
153
|
"Usage": "Uso",
|
|
141
154
|
"Usage report": "Informe de uso",
|
|
142
155
|
"Usage: pleum completion <zsh|bash>": "Uso: pleum completion <zsh|bash>",
|
|
143
156
|
"Usage: pleum launch <tool> [--model id] [-- tool args]\n": "Uso: pleum launch <tool> [--model id] [-- tool args]\n",
|
|
144
157
|
"Usage: pleum models <model-id> --info": "Uso: pleum models <model-id> --info",
|
|
145
158
|
"Usage: pleum run <model> [\"prompt\"]\nModel list: pleum models": "Uso: pleum run <model> [\"prompt\"]\nLista de modelos: pleum models",
|
|
159
|
+
"Use default (clear)": "Usar predeterminado (borrar)",
|
|
146
160
|
"Your spend report (model|provider|api_key|day)": "Tu informe de gasto (model|provider|api_key|day)",
|
|
147
161
|
"by day": "por día",
|
|
148
162
|
"by key": "por clave",
|
|
@@ -162,6 +176,7 @@ export const ES = {
|
|
|
162
176
|
"model detail": "detalle del modelo",
|
|
163
177
|
"monthly": "mensual",
|
|
164
178
|
"not installed": "no instalado",
|
|
179
|
+
"not set": "no configurado",
|
|
165
180
|
"off": "inactivo",
|
|
166
181
|
"omit the model for an arrow-key picker; /help lists commands": "omite el modelo para un selector con flechas; /help lista los comandos",
|
|
167
182
|
"omit the tool to see the supported list and install status": "omite la herramienta para ver la lista compatible y el estado de instalación",
|
|
@@ -194,6 +209,8 @@ export const ES = {
|
|
|
194
209
|
"… {n} more — type to narrow down": "… {n} más — escribe para acotar",
|
|
195
210
|
"↑/↓ move • enter run • → set model • esc quit": "↑/↓ mover • enter ejecutar • → definir modelo • esc salir",
|
|
196
211
|
"↑/↓ move • enter run • → set model • esc back": "↑/↓ mover • enter ejecutar • → definir modelo • esc volver",
|
|
212
|
+
"↑/↓ move • enter select • esc back": "↑/↓ mover • enter seleccionar • esc volver",
|
|
213
|
+
"↑/↓ move • enter set model • esc back": "↑/↓ mover • enter definir modelo • esc volver",
|
|
197
214
|
"⏹ aborted": "⏹ abortado",
|
|
198
215
|
"⏹ generation aborted — the partial reply stays in context": "⏹ generación abortada — la respuesta parcial permanece en el contexto",
|
|
199
216
|
"✓ installed": "✓ instalado",
|
package/src/locales/ja.mjs
CHANGED
|
@@ -9,6 +9,7 @@ export const JA = {
|
|
|
9
9
|
"/help for commands · /exit to quit · wrap multi-line input in \"\"\" · Ctrl+C to stop generation": "/help でコマンド一覧 · /exit で終了 · 複数行入力は \"\"\" で囲む · Ctrl+C で生成停止",
|
|
10
10
|
"1) Add to {file}:": "1) {file} に追加:",
|
|
11
11
|
"2) Export the key and run:": "2) キーをエクスポートして実行:",
|
|
12
|
+
"Account": "アカウント",
|
|
12
13
|
"All models — price and context included": "全モデル — 価格・コンテキスト込み",
|
|
13
14
|
"Anthropic's coding agent — supports subagents": "Anthropic のコーディングエージェント — サブエージェント対応",
|
|
14
15
|
"Attaching and uploading {n} local image(s) from the prompt.": "プロンプトからローカル画像 {n} 件を添付・アップロード中。",
|
|
@@ -21,7 +22,7 @@ export const JA = {
|
|
|
21
22
|
"GitHub's terminal coding agent": "GitHub のターミナルコーディングエージェント",
|
|
22
23
|
"Available: {list}": "利用可能: {list}",
|
|
23
24
|
"Balance": "残高",
|
|
24
|
-
"Balance is below the auto-charge threshold ({threshold}
|
|
25
|
+
"Balance is below the auto-charge threshold ({threshold} ₩).": "残高が自動チャージのしきい値 ({threshold} ₩) を下回っています。",
|
|
25
26
|
"Balance:": "残高:",
|
|
26
27
|
"Block's local-first agent": "Block のローカルファーストエージェント",
|
|
27
28
|
"Chat with a model": "モデルとチャット",
|
|
@@ -29,6 +30,7 @@ export const JA = {
|
|
|
29
30
|
"Check your credit balance": "クレジット残高を確認",
|
|
30
31
|
"Choose a language": "言語を選択",
|
|
31
32
|
"Choose a number, or search (Enter to cancel): ": "番号を選択、または検索してください (Enter でキャンセル): ",
|
|
33
|
+
"Choose a shell": "シェルを選択",
|
|
32
34
|
"Code": "コード",
|
|
33
35
|
"Commands:\n /help, /? this help\n /clear reset the conversation context (keeps the system prompt)\n /system [text] set the system prompt (no arg shows the current value)\n /model [id] switch models mid-session (no arg = arrow-key picker)\n /models [query] browse the model list\n /save [name] save the current session (default: last)\n /load [name] load a saved session\n /verbose toggle token/speed stats\n /exit, /quit quit\n Multi-line input: a line with \"\"\" → body → a line with \"\"\"\n Images: include an image file path right in the message (e.g. describe this ./shot.png)": "コマンド:\n /help, /? このヘルプ\n /clear 会話コンテキストをリセット (システムプロンプトは保持)\n /system [text] システムプロンプトを設定 (引数なしで現在値を表示)\n /model [id] セッション中にモデルを切り替え (引数なし = 矢印キーで選択)\n /models [query] モデル一覧を閲覧\n /save [name] 現在のセッションを保存 (デフォルト: last)\n /load [name] 保存したセッションを読み込み\n /verbose トークン/速度統計の表示切替\n /exit, /quit 終了\n 複数行入力: \"\"\" の行 → 本文 → \"\"\" の行\n 画像: メッセージ内に画像ファイルパスを直接含める (例: describe this ./shot.png)",
|
|
34
36
|
"Context cleared": "コンテキストをクリアしました",
|
|
@@ -36,6 +38,8 @@ export const JA = {
|
|
|
36
38
|
"Current language: {name}": "現在の言語: {name}",
|
|
37
39
|
"Current model:": "現在のモデル:",
|
|
38
40
|
"Current system prompt:": "現在のシステムプロンプト:",
|
|
41
|
+
"Current version: {version}": "現在のバージョン: {version}",
|
|
42
|
+
"Default models": "デフォルトモデル",
|
|
39
43
|
"Delete the saved key": "保存済みキーを削除",
|
|
40
44
|
"Error:": "エラー:",
|
|
41
45
|
"Example: pleum launch claude --model claude-sonnet-4": "例: pleum launch claude --model claude-sonnet-4",
|
|
@@ -64,13 +68,16 @@ export const JA = {
|
|
|
64
68
|
"Health check": "ヘルスチェック",
|
|
65
69
|
"Install canceled — manual install: {install}": "インストールを中止しました — 手動インストール: {install}",
|
|
66
70
|
"Install failed (exit {code})": "インストールに失敗しました (終了コード {code})",
|
|
71
|
+
"Language": "言語",
|
|
67
72
|
"Language set to {name}": "言語を {name} に設定しました",
|
|
73
|
+
"Language, default models, account, updates": "言語・デフォルトモデル・アカウント・アップデート",
|
|
68
74
|
"Leaderboard": "リーダーボード",
|
|
69
75
|
"Log in": "ログイン",
|
|
70
76
|
"Log in at the URL above, then enter the code (within {min} min).": "上記URLでログイン後、コードを入力してください ({min} 分以内)。",
|
|
71
77
|
"Log in from your browser. If it doesn't open automatically, open this URL:": "ブラウザからログインしてください。自動的に開かない場合は、次のURLを開いてください:",
|
|
72
78
|
"Log in via browser + auto-save the key": "ブラウザ経由でログイン + キーを自動保存",
|
|
73
79
|
"Log in via browser and save the API key": "ブラウザ経由でログインしAPIキーを保存",
|
|
80
|
+
"Log out? [Y/n] ": "ログアウトしますか? [Y/n] ",
|
|
74
81
|
"Logged in": "ログイン済み",
|
|
75
82
|
"Logged in. Saved the key to ~/.pleum/config.json.": "ログインしました。キーを ~/.pleum/config.json に保存しました。",
|
|
76
83
|
"Logged out": "ログアウトしました",
|
|
@@ -104,9 +111,11 @@ export const JA = {
|
|
|
104
111
|
"PleumRouter terminal CLI": "PleumRouter ターミナルCLI",
|
|
105
112
|
"Press Enter to install now": "Enter を押すと今すぐインストールします",
|
|
106
113
|
"Print a shell completion script": "シェル補完スクリプトを出力",
|
|
114
|
+
"Print a zsh/bash completion script": "zsh/bash補完スクリプトを出力",
|
|
107
115
|
"Print the version": "バージョンを表示",
|
|
108
116
|
"Provider share": "プロバイダーシェア",
|
|
109
117
|
"Reinstall the latest version": "最新バージョンを再インストール",
|
|
118
|
+
"Reinstalling — pleum will exit when finished. Run `pleum` again afterward.": "再インストール中です — 完了するとpleumは終了します。その後もう一度pleumを実行してください。",
|
|
110
119
|
"Run a coding agent against PleumRouter": "PleumRouter に対してコーディングエージェントを実行",
|
|
111
120
|
"Run pleum login again to get a new key (recent keys include it by default).": "新しいキーを取得するには pleum login を再実行してください (最近のキーはデフォルトで含まれます)。",
|
|
112
121
|
"Running {label}: {cmd}": "{label} を実行中: {cmd}",
|
|
@@ -117,6 +126,9 @@ export const JA = {
|
|
|
117
126
|
"Select a model": "モデルを選択",
|
|
118
127
|
"Session loaded (model: {model})": "セッションを読み込みました (モデル: {model})",
|
|
119
128
|
"Session saved:": "セッションを保存しました:",
|
|
129
|
+
"Set the default model used per agent": "エージェントごとのデフォルトモデルを設定",
|
|
130
|
+
"Settings": "設定",
|
|
131
|
+
"Shell completion": "シェル補完",
|
|
120
132
|
"Show/change display language (en|ko|ja|zh|es)": "表示言語を表示/変更 (en|ko|ja|zh|es)",
|
|
121
133
|
"Specify a model: pleum run <model> [\"prompt\"] (model selection isn't available on non-interactive stdin)": "モデルを指定してください: pleum run <model> [\"prompt\"] (非対話的な標準入力ではモデル選択は使用できません)",
|
|
122
134
|
"Spend over the last 30 days": "過去30日間の支出",
|
|
@@ -137,12 +149,14 @@ export const JA = {
|
|
|
137
149
|
"Unknown command: {cmd}": "不明なコマンド: {cmd}",
|
|
138
150
|
"Unsupported language: {code}\n Available: {list}": "サポートされていない言語です: {code}\n 利用可能: {list}",
|
|
139
151
|
"Unsupported tool: {tool}": "サポートされていないツールです: {tool}",
|
|
152
|
+
"Update CLI": "CLIを更新",
|
|
140
153
|
"Usage": "使用状況",
|
|
141
154
|
"Usage report": "使用状況レポート",
|
|
142
155
|
"Usage: pleum completion <zsh|bash>": "使い方: pleum completion <zsh|bash>",
|
|
143
156
|
"Usage: pleum launch <tool> [--model id] [-- tool args]\n": "使い方: pleum launch <tool> [--model id] [-- tool args]\n",
|
|
144
157
|
"Usage: pleum models <model-id> --info": "使い方: pleum models <model-id> --info",
|
|
145
158
|
"Usage: pleum run <model> [\"prompt\"]\nModel list: pleum models": "使い方: pleum run <model> [\"prompt\"]\nモデル一覧: pleum models",
|
|
159
|
+
"Use default (clear)": "デフォルトを使用(解除)",
|
|
146
160
|
"Your spend report (model|provider|api_key|day)": "支出レポート (model|provider|api_key|day)",
|
|
147
161
|
"by day": "日別",
|
|
148
162
|
"by key": "キー別",
|
|
@@ -162,6 +176,7 @@ export const JA = {
|
|
|
162
176
|
"model detail": "モデル詳細",
|
|
163
177
|
"monthly": "月次",
|
|
164
178
|
"not installed": "未インストール",
|
|
179
|
+
"not set": "未設定",
|
|
165
180
|
"off": "オフ",
|
|
166
181
|
"omit the model for an arrow-key picker; /help lists commands": "モデルを省略すると矢印キーで選択できます; コマンド一覧は /help",
|
|
167
182
|
"omit the tool to see the supported list and install status": "ツールを省略するとサポート一覧とインストール状態を表示します",
|
|
@@ -194,6 +209,8 @@ export const JA = {
|
|
|
194
209
|
"… {n} more — type to narrow down": "… 他{n}件 — 入力して絞り込む",
|
|
195
210
|
"↑/↓ move • enter run • → set model • esc quit": "↑/↓ 移動 • enter 実行 • → モデル設定 • esc 終了",
|
|
196
211
|
"↑/↓ move • enter run • → set model • esc back": "↑/↓ 移動 • enter 実行 • → モデル設定 • esc 戻る",
|
|
212
|
+
"↑/↓ move • enter select • esc back": "↑/↓ 移動 • enter 選択 • esc 戻る",
|
|
213
|
+
"↑/↓ move • enter set model • esc back": "↑/↓ 移動 • enter モデル設定 • esc 戻る",
|
|
197
214
|
"⏹ aborted": "⏹ 中断しました",
|
|
198
215
|
"⏹ generation aborted — the partial reply stays in context": "⏹ 生成を中断しました — 途中までの応答はコンテキストに残ります",
|
|
199
216
|
"✓ installed": "✓ インストール済み",
|
package/src/locales/ko.mjs
CHANGED
|
@@ -101,7 +101,7 @@ export const KO = {
|
|
|
101
101
|
"The server may not support API-key balance checks yet, or the key may have expired — try pleum login again.":
|
|
102
102
|
"서버가 아직 API 키 잔액 조회를 지원하지 않거나 키가 만료됐을 수 있습니다 — pleum login 재시도.",
|
|
103
103
|
"Balance:": "잔액:",
|
|
104
|
-
"Balance is below the auto-charge threshold ({threshold}
|
|
104
|
+
"Balance is below the auto-charge threshold ({threshold} ₩).": "잔액이 자동충전 임계값({threshold} ₩) 아래입니다.",
|
|
105
105
|
|
|
106
106
|
// health.mjs
|
|
107
107
|
"Gateway connection failed: {error}": "게이트웨이 연결 실패: {error}",
|
|
@@ -258,4 +258,24 @@ export const KO = {
|
|
|
258
258
|
"Override base_url: PLEUM_BASE_URL environment variable": "base_url 덮어쓰기: PLEUM_BASE_URL 환경변수",
|
|
259
259
|
"Logged out": "로그아웃됨",
|
|
260
260
|
"Unknown command: {cmd}": "알 수 없는 명령: {cmd}",
|
|
261
|
+
|
|
262
|
+
// settings.mjs
|
|
263
|
+
"Settings": "설정",
|
|
264
|
+
"Language, default models, account, updates": "언어·기본 모델·계정·업데이트",
|
|
265
|
+
"Language": "언어",
|
|
266
|
+
"Default models": "기본 모델",
|
|
267
|
+
"Set the default model used per agent": "에이전트별 기본 모델 설정",
|
|
268
|
+
"Account": "계정",
|
|
269
|
+
"Update CLI": "CLI 업데이트",
|
|
270
|
+
"Current version: {version}": "현재 버전: {version}",
|
|
271
|
+
"Shell completion": "셸 자동완성",
|
|
272
|
+
"Print a zsh/bash completion script": "zsh/bash 자동완성 스크립트 출력",
|
|
273
|
+
"not set": "미설정",
|
|
274
|
+
"Use default (clear)": "기본값 사용(해제)",
|
|
275
|
+
"Log out? [Y/n] ": "로그아웃할까요? [Y/n] ",
|
|
276
|
+
"Choose a shell": "셸 선택",
|
|
277
|
+
"Reinstalling — pleum will exit when finished. Run `pleum` again afterward.":
|
|
278
|
+
"재설치 중입니다 — 완료되면 pleum이 종료됩니다. 이후 다시 pleum을 실행하세요.",
|
|
279
|
+
"↑/↓ move • enter select • esc back": "↑/↓ 이동 • enter 선택 • esc 뒤로",
|
|
280
|
+
"↑/↓ move • enter set model • esc back": "↑/↓ 이동 • enter 모델 설정 • esc 뒤로",
|
|
261
281
|
};
|
package/src/locales/zh.mjs
CHANGED
|
@@ -9,6 +9,7 @@ export const ZH = {
|
|
|
9
9
|
"/help for commands · /exit to quit · wrap multi-line input in \"\"\" · Ctrl+C to stop generation": "/help 查看命令 · /exit 退出 · 多行输入用 \"\"\" 包裹 · Ctrl+C 停止生成",
|
|
10
10
|
"1) Add to {file}:": "1) 添加到 {file}:",
|
|
11
11
|
"2) Export the key and run:": "2) 导出密钥并运行:",
|
|
12
|
+
"Account": "账户",
|
|
12
13
|
"All models — price and context included": "所有模型 — 含价格与上下文",
|
|
13
14
|
"Anthropic's coding agent — supports subagents": "Anthropic 的编程代理 — 支持子代理",
|
|
14
15
|
"Attaching and uploading {n} local image(s) from the prompt.": "正在附加并上传提示中的 {n} 张本地图片。",
|
|
@@ -21,7 +22,7 @@ export const ZH = {
|
|
|
21
22
|
"GitHub's terminal coding agent": "GitHub 的终端编程代理",
|
|
22
23
|
"Available: {list}": "可用:{list}",
|
|
23
24
|
"Balance": "余额",
|
|
24
|
-
"Balance is below the auto-charge threshold ({threshold}
|
|
25
|
+
"Balance is below the auto-charge threshold ({threshold} ₩).": "余额低于自动充值阈值({threshold} ₩)。",
|
|
25
26
|
"Balance:": "余额:",
|
|
26
27
|
"Block's local-first agent": "Block 的本地优先代理",
|
|
27
28
|
"Chat with a model": "与模型对话",
|
|
@@ -29,6 +30,7 @@ export const ZH = {
|
|
|
29
30
|
"Check your credit balance": "查询你的额度余额",
|
|
30
31
|
"Choose a language": "选择语言",
|
|
31
32
|
"Choose a number, or search (Enter to cancel): ": "选择编号,或搜索(Enter 取消): ",
|
|
33
|
+
"Choose a shell": "选择 shell",
|
|
32
34
|
"Code": "代码",
|
|
33
35
|
"Commands:\n /help, /? this help\n /clear reset the conversation context (keeps the system prompt)\n /system [text] set the system prompt (no arg shows the current value)\n /model [id] switch models mid-session (no arg = arrow-key picker)\n /models [query] browse the model list\n /save [name] save the current session (default: last)\n /load [name] load a saved session\n /verbose toggle token/speed stats\n /exit, /quit quit\n Multi-line input: a line with \"\"\" → body → a line with \"\"\"\n Images: include an image file path right in the message (e.g. describe this ./shot.png)": "命令:\n /help, /? 本帮助\n /clear 重置对话上下文(保留系统提示词)\n /system [text] 设置系统提示词(不带参数则显示当前值)\n /model [id] 会话中途切换模型(不带参数 = 方向键选择器)\n /models [query] 浏览模型列表\n /save [name] 保存当前会话(默认:last)\n /load [name] 加载已保存的会话\n /verbose 切换 token/速度统计\n /exit, /quit 退出\n 多行输入:一行 \"\"\" → 正文 → 一行 \"\"\"\n 图片:在消息中直接包含图片文件路径(例如 describe this ./shot.png)",
|
|
34
36
|
"Context cleared": "上下文已清除",
|
|
@@ -36,6 +38,8 @@ export const ZH = {
|
|
|
36
38
|
"Current language: {name}": "当前语言:{name}",
|
|
37
39
|
"Current model:": "当前模型:",
|
|
38
40
|
"Current system prompt:": "当前系统提示词:",
|
|
41
|
+
"Current version: {version}": "当前版本:{version}",
|
|
42
|
+
"Default models": "默认模型",
|
|
39
43
|
"Delete the saved key": "删除已保存的密钥",
|
|
40
44
|
"Error:": "错误:",
|
|
41
45
|
"Example: pleum launch claude --model claude-sonnet-4": "示例:pleum launch claude --model claude-sonnet-4",
|
|
@@ -64,13 +68,16 @@ export const ZH = {
|
|
|
64
68
|
"Health check": "健康检查",
|
|
65
69
|
"Install canceled — manual install: {install}": "安装已取消 — 手动安装:{install}",
|
|
66
70
|
"Install failed (exit {code})": "安装失败(退出码 {code})",
|
|
71
|
+
"Language": "语言",
|
|
67
72
|
"Language set to {name}": "语言已设置为 {name}",
|
|
73
|
+
"Language, default models, account, updates": "语言·默认模型·账户·更新",
|
|
68
74
|
"Leaderboard": "排行榜",
|
|
69
75
|
"Log in": "登录",
|
|
70
76
|
"Log in at the URL above, then enter the code (within {min} min).": "在上方 URL 登录,然后输入代码({min} 分钟内有效)。",
|
|
71
77
|
"Log in from your browser. If it doesn't open automatically, open this URL:": "在浏览器中登录。如果没有自动打开,请手动打开此 URL:",
|
|
72
78
|
"Log in via browser + auto-save the key": "通过浏览器登录 + 自动保存密钥",
|
|
73
79
|
"Log in via browser and save the API key": "通过浏览器登录并保存 API 密钥",
|
|
80
|
+
"Log out? [Y/n] ": "要登出吗? [Y/n] ",
|
|
74
81
|
"Logged in": "已登录",
|
|
75
82
|
"Logged in. Saved the key to ~/.pleum/config.json.": "已登录。密钥已保存至 ~/.pleum/config.json。",
|
|
76
83
|
"Logged out": "已登出",
|
|
@@ -104,9 +111,11 @@ export const ZH = {
|
|
|
104
111
|
"PleumRouter terminal CLI": "PleumRouter 终端 CLI",
|
|
105
112
|
"Press Enter to install now": "按 Enter 立即安装",
|
|
106
113
|
"Print a shell completion script": "输出 shell 自动补全脚本",
|
|
114
|
+
"Print a zsh/bash completion script": "输出 zsh/bash 自动补全脚本",
|
|
107
115
|
"Print the version": "输出版本号",
|
|
108
116
|
"Provider share": "提供商份额",
|
|
109
117
|
"Reinstall the latest version": "重新安装最新版本",
|
|
118
|
+
"Reinstalling — pleum will exit when finished. Run `pleum` again afterward.": "正在重新安装 — 完成后 pleum 将退出。之后请再次运行 pleum。",
|
|
110
119
|
"Run a coding agent against PleumRouter": "通过 PleumRouter 运行编程代理",
|
|
111
120
|
"Run pleum login again to get a new key (recent keys include it by default).": "再次运行 pleum login 获取新密钥(近期签发的密钥默认已包含该权限)。",
|
|
112
121
|
"Running {label}: {cmd}": "正在运行 {label}:{cmd}",
|
|
@@ -117,6 +126,9 @@ export const ZH = {
|
|
|
117
126
|
"Select a model": "选择模型",
|
|
118
127
|
"Session loaded (model: {model})": "会话已加载(模型:{model})",
|
|
119
128
|
"Session saved:": "会话已保存:",
|
|
129
|
+
"Set the default model used per agent": "设置每个代理使用的默认模型",
|
|
130
|
+
"Settings": "设置",
|
|
131
|
+
"Shell completion": "Shell 自动补全",
|
|
120
132
|
"Show/change display language (en|ko|ja|zh|es)": "显示/更改界面语言(en|ko|ja|zh|es)",
|
|
121
133
|
"Specify a model: pleum run <model> [\"prompt\"] (model selection isn't available on non-interactive stdin)": "请指定模型:pleum run <model> [\"prompt\"] (非交互式 stdin 下无法选择模型)",
|
|
122
134
|
"Spend over the last 30 days": "最近 30 天的花费",
|
|
@@ -137,12 +149,14 @@ export const ZH = {
|
|
|
137
149
|
"Unknown command: {cmd}": "未知命令:{cmd}",
|
|
138
150
|
"Unsupported language: {code}\n Available: {list}": "不支持的语言:{code}\n 可用:{list}",
|
|
139
151
|
"Unsupported tool: {tool}": "不支持的工具:{tool}",
|
|
152
|
+
"Update CLI": "更新 CLI",
|
|
140
153
|
"Usage": "用量",
|
|
141
154
|
"Usage report": "用量报告",
|
|
142
155
|
"Usage: pleum completion <zsh|bash>": "用法:pleum completion <zsh|bash>",
|
|
143
156
|
"Usage: pleum launch <tool> [--model id] [-- tool args]\n": "用法:pleum launch <tool> [--model id] [-- tool args]\n",
|
|
144
157
|
"Usage: pleum models <model-id> --info": "用法:pleum models <model-id> --info",
|
|
145
158
|
"Usage: pleum run <model> [\"prompt\"]\nModel list: pleum models": "用法:pleum run <model> [\"prompt\"]\n模型列表:pleum models",
|
|
159
|
+
"Use default (clear)": "使用默认值(清除)",
|
|
146
160
|
"Your spend report (model|provider|api_key|day)": "你的花费报告(model|provider|api_key|day)",
|
|
147
161
|
"by day": "按天",
|
|
148
162
|
"by key": "按密钥",
|
|
@@ -162,6 +176,7 @@ export const ZH = {
|
|
|
162
176
|
"model detail": "模型详情",
|
|
163
177
|
"monthly": "每月",
|
|
164
178
|
"not installed": "未安装",
|
|
179
|
+
"not set": "未设置",
|
|
165
180
|
"off": "关闭",
|
|
166
181
|
"omit the model for an arrow-key picker; /help lists commands": "省略模型以使用方向键选择器;/help 列出所有命令",
|
|
167
182
|
"omit the tool to see the supported list and install status": "省略工具名以查看支持列表与安装状态",
|
|
@@ -194,6 +209,8 @@ export const ZH = {
|
|
|
194
209
|
"… {n} more — type to narrow down": "… 还有 {n} 项 — 输入以缩小范围",
|
|
195
210
|
"↑/↓ move • enter run • → set model • esc quit": "↑/↓ 移动 • enter 运行 • → 设置模型 • esc 退出",
|
|
196
211
|
"↑/↓ move • enter run • → set model • esc back": "↑/↓ 移动 • enter 运行 • → 设置模型 • esc 返回",
|
|
212
|
+
"↑/↓ move • enter select • esc back": "↑/↓ 移动 • enter 选择 • esc 返回",
|
|
213
|
+
"↑/↓ move • enter set model • esc back": "↑/↓ 移动 • enter 设置模型 • esc 返回",
|
|
197
214
|
"⏹ aborted": "⏹ 已中止",
|
|
198
215
|
"⏹ generation aborted — the partial reply stays in context": "⏹ 生成已中止 — 部分回复保留在上下文中",
|
|
199
216
|
"✓ installed": "✓ 已安装",
|
package/src/login.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { ROOT, V1, writeConfig } from "./config.mjs";
|
|
4
|
+
import { ROOT, V1, readConfig, writeConfig, mergeLoginConfig } from "./config.mjs";
|
|
5
5
|
import { t } from "./i18n.mjs";
|
|
6
6
|
|
|
7
7
|
function openBrowser(url) {
|
|
@@ -66,7 +66,7 @@ export async function login() {
|
|
|
66
66
|
}, 120_000).unref();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
writeConfig({ key, base_url: ROOT });
|
|
69
|
+
writeConfig(mergeLoginConfig(readConfig(), { key, base_url: ROOT }));
|
|
70
70
|
console.log(`✓ ${t("Logged in. Saved the key to ~/.pleum/config.json.")}`);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -102,7 +102,7 @@ export async function loginDevice() {
|
|
|
102
102
|
process.exit(1);
|
|
103
103
|
}
|
|
104
104
|
const { key } = await poll.json();
|
|
105
|
-
writeConfig({ key, base_url: ROOT });
|
|
105
|
+
writeConfig(mergeLoginConfig(readConfig(), { key, base_url: ROOT }));
|
|
106
106
|
console.log(`✓ ${t("Logged in. Saved the key to ~/.pleum/config.json.")}`);
|
|
107
107
|
return;
|
|
108
108
|
}
|
package/src/menu.mjs
CHANGED
|
@@ -23,19 +23,23 @@ import { top } from "./top.mjs";
|
|
|
23
23
|
import { usage } from "./usage.mjs";
|
|
24
24
|
import { balance } from "./balance.mjs";
|
|
25
25
|
import { health } from "./health.mjs";
|
|
26
|
+
import { runSettings } from "./settings.mjs";
|
|
26
27
|
import { t } from "./i18n.mjs";
|
|
27
28
|
|
|
29
|
+
// 값은 t() 키(영어 원문)만 담는다 — 여기서 t()를 호출하면 모듈 로드 시점(import 1회)에
|
|
30
|
+
// 번역이 확정돼, 같은 프로세스 안에서 언어를 바꿔도(Settings) 이 설명들만 예전 언어로 남는다.
|
|
31
|
+
// 실제 번역은 소비 지점(buildAgentItems)에서 매번 t()를 호출해 얻는다.
|
|
28
32
|
const AGENT_DESC = {
|
|
29
|
-
claude:
|
|
30
|
-
codex:
|
|
31
|
-
aider:
|
|
32
|
-
goose:
|
|
33
|
-
openhands:
|
|
34
|
-
opencode:
|
|
35
|
-
omp:
|
|
36
|
-
hermes:
|
|
37
|
-
qwen:
|
|
38
|
-
copilot:
|
|
33
|
+
claude: "Anthropic's coding agent — supports subagents",
|
|
34
|
+
codex: "OpenAI's coding agent",
|
|
35
|
+
aider: "Terminal pair programming",
|
|
36
|
+
goose: "Block's local-first agent",
|
|
37
|
+
openhands: "Autonomous dev agent",
|
|
38
|
+
opencode: "Multi-provider terminal coding agent",
|
|
39
|
+
omp: "Bun-based coding agent — Claude routing only",
|
|
40
|
+
hermes: "Nous Research's general-purpose agent",
|
|
41
|
+
qwen: "Alibaba's terminal coding agent",
|
|
42
|
+
copilot: "GitHub's terminal coding agent",
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
const autoInstallable = (r) => !/^https?:/.test(r.install); // URL이면 수동 설치 안내만
|
|
@@ -57,6 +61,7 @@ export function buildItems({ loggedIn, savedModels = {} }) {
|
|
|
57
61
|
{ id: "usage", title: t("Usage report"), desc: t("Spend over the last 30 days") },
|
|
58
62
|
{ id: "balance", title: t("Balance"), desc: t("Check your credit balance") },
|
|
59
63
|
{ id: "health", title: t("Health check"), desc: t("Gateway connectivity · login status") },
|
|
64
|
+
{ id: "settings", title: t("Settings"), desc: t("Language, default models, account, updates") },
|
|
60
65
|
);
|
|
61
66
|
return items;
|
|
62
67
|
}
|
|
@@ -70,7 +75,7 @@ export function buildAgentItems({ installed, savedModels = {} }) {
|
|
|
70
75
|
tool,
|
|
71
76
|
title: `${r.label}${installed[tool] ? "" : autoInstallable(r) ? ` (${t("install")})` : ` (${t("not installed")})`}`,
|
|
72
77
|
desc: installed[tool]
|
|
73
|
-
? `${AGENT_DESC[tool] ?? ""}${savedModels[tool] ? ` · ${t("model")}: ${savedModels[tool]}` : ""}`
|
|
78
|
+
? `${t(AGENT_DESC[tool] ?? "")}${savedModels[tool] ? ` · ${t("model")}: ${savedModels[tool]}` : ""}`
|
|
74
79
|
: autoInstallable(r)
|
|
75
80
|
? `${t("Press Enter to install now")} — ${r.install}`
|
|
76
81
|
: `${t("Manual install:")} ${hyperlink(r.install)}`,
|
|
@@ -91,7 +96,8 @@ export function buildAgentItems({ installed, savedModels = {} }) {
|
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
// 항목별 기본 모델 저장(launch_models). 로그인 전에도 동작 — /v1/models는 공개.
|
|
94
|
-
|
|
99
|
+
// settings.mjs의 "Default models" 화면도 재사용(순환 import — 아래 runList 주석 참고).
|
|
100
|
+
export function saveModel(tool, id) {
|
|
95
101
|
const cfg = readConfig() ?? {};
|
|
96
102
|
cfg.launch_models = { ...cfg.launch_models, [tool]: id };
|
|
97
103
|
writeConfig(cfg);
|
|
@@ -149,8 +155,12 @@ async function ensureInstalled(r) {
|
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
// 화살표 메뉴 루프(키보드 전용) — {item, model?} · "quit"(ctrl+c) · null(esc/q) 반환. 비TTY면 undefined.
|
|
152
|
-
// 메인 메뉴·에이전트
|
|
153
|
-
|
|
158
|
+
// 메인 메뉴·에이전트 서브메뉴·Settings 서브메뉴(settings.mjs)가 공유하는 렌더 루프 —
|
|
159
|
+
// header/footer/초기 커서만 다르다. settings.mjs가 이 함수를 import하고 menu.mjs가
|
|
160
|
+
// settings.mjs의 runSettings를 import하는 순환 참조가 생기지만, 둘 다 모듈 최상위가 아니라
|
|
161
|
+
// 사용자가 실제로 메뉴를 조작하는 async 함수 안에서만 서로를 참조하므로 안전하다
|
|
162
|
+
// (모듈 최상위에서 즉시 호출하면 "Cannot access before initialization"이 난다 — 이 제약 유지 필수).
|
|
163
|
+
export async function runList(items, { header, footer, initialCursor = 0 } = {}) {
|
|
154
164
|
const input = process.stdin;
|
|
155
165
|
const output = process.stdout;
|
|
156
166
|
if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") return undefined;
|
|
@@ -307,6 +317,11 @@ export async function mainMenu() {
|
|
|
307
317
|
case "health":
|
|
308
318
|
await health();
|
|
309
319
|
continue;
|
|
320
|
+
case "settings": {
|
|
321
|
+
const r = await runSettings();
|
|
322
|
+
if (r === "quit") return true; // ctrl+c 또는 재설치(upgrade) 시작 — 전체 종료
|
|
323
|
+
continue; // esc/q — 메인 메뉴로 복귀
|
|
324
|
+
}
|
|
310
325
|
case "agents": {
|
|
311
326
|
const agentPicked = await agentSelect();
|
|
312
327
|
if (agentPicked === "quit") return true; // 서브메뉴에서 ctrl+c → 전체 종료
|
package/src/recipes.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ROOT, V1 } from "./config.mjs";
|
|
2
|
-
import { t } from "./i18n.mjs";
|
|
3
2
|
|
|
4
3
|
// 도구별 설정의 진실 원천. 출처: frontend .../docs/cookbook/agent-integration + 에이전트별 공식 문서 조사(2026-07).
|
|
5
4
|
// type "env" → 환경변수만 세팅하고 바로 실행.
|
|
6
5
|
// type "codex-home" → 격리된 CODEX_HOME에 config.toml을 써서 실행(사용자 ~/.codex 보존).
|
|
6
|
+
// note 필드는 t() 키(영어 원문)만 담는다 — 여기서 t()를 호출하면 모듈 로드 시점에 번역이
|
|
7
|
+
// 고정돼 같은 프로세스 안에서 언어를 바꿔도(Settings) 안내문이 갱신되지 않는다.
|
|
8
|
+
// 실제 번역은 소비 지점(launch.mjs)에서 매번 t()를 호출해 얻는다.
|
|
7
9
|
//
|
|
8
10
|
// 커스텀 엔드포인트를 지원하지 않아 PleumRouter로 라우팅할 수 없는 도구는 등록하지 않는다(조사 결과, 2026-07):
|
|
9
11
|
// Gemini CLI(구글 백엔드 전용, PR#2899 머지 거부) · Cursor CLI(구독 백엔드 전용, 공식 확인) ·
|
|
@@ -44,7 +46,7 @@ export const RECIPES = {
|
|
|
44
46
|
bin: "goose",
|
|
45
47
|
type: "env",
|
|
46
48
|
install: "https://block.github.io/goose/docs/getting-started/installation",
|
|
47
|
-
note:
|
|
49
|
+
note: "Goose sets its model in its own config — use the openai/<id> format in `goose configure`.",
|
|
48
50
|
env: (key) => ({ OPENAI_API_BASE: V1, OPENAI_API_KEY: key }),
|
|
49
51
|
args: () => [],
|
|
50
52
|
},
|
|
@@ -53,7 +55,7 @@ export const RECIPES = {
|
|
|
53
55
|
bin: "openhands",
|
|
54
56
|
type: "env",
|
|
55
57
|
install: "python -m pip install openhands-ai",
|
|
56
|
-
note:
|
|
58
|
+
note: "For OpenHands, prefix the model name with openai/ in its config.",
|
|
57
59
|
env: (key) => ({ OPENAI_API_BASE: V1, OPENAI_API_KEY: key }),
|
|
58
60
|
args: () => [],
|
|
59
61
|
},
|
|
@@ -64,7 +66,7 @@ export const RECIPES = {
|
|
|
64
66
|
install: "npm i -g opencode-ai@latest",
|
|
65
67
|
// opencode는 커스텀 엔드포인트를 opencode.json provider 블록으로만 읽는다 — env var는 없다.
|
|
66
68
|
// 대신 OPENCODE_CONFIG_CONTENT(파일 대신 JSON 문자열을 그대로 읽는 env var)로 파일 없이 주입한다.
|
|
67
|
-
note:
|
|
69
|
+
note: "Generates a temporary PleumRouter provider config for this run only — your own opencode.json is untouched.",
|
|
68
70
|
env: (key, model) => ({
|
|
69
71
|
PLEUM_API_KEY: key,
|
|
70
72
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
@@ -73,12 +75,12 @@ export const RECIPES = {
|
|
|
73
75
|
npm: "@ai-sdk/openai-compatible",
|
|
74
76
|
name: "PleumRouter",
|
|
75
77
|
options: { baseURL: V1, apiKey: "{env:PLEUM_API_KEY}" },
|
|
76
|
-
models: { [model || "gpt-4
|
|
78
|
+
models: { [model || "gpt-5.4-mini"]: {} },
|
|
77
79
|
},
|
|
78
80
|
},
|
|
79
81
|
}),
|
|
80
82
|
}),
|
|
81
|
-
args: (m) => ["--model", `pleum/${m || "gpt-4
|
|
83
|
+
args: (m) => ["--model", `pleum/${m || "gpt-5.4-mini"}`],
|
|
82
84
|
},
|
|
83
85
|
omp: {
|
|
84
86
|
label: "omp (Oh My Pi)",
|
|
@@ -87,7 +89,7 @@ export const RECIPES = {
|
|
|
87
89
|
install: "curl -fsSL https://omp.sh/install | sh",
|
|
88
90
|
// omp는 커스텀 OpenAI 호환 엔드포인트는 YAML 설정 파일로만 읽지만, 내장 anthropic
|
|
89
91
|
// provider만은 claude와 동일한 ANTHROPIC_BASE_URL env var로 리디렉션된다.
|
|
90
|
-
note:
|
|
92
|
+
note: "Needs the Bun runtime (bun >= 1.3.14) on PATH. Only Claude-family models are routed via env vars.",
|
|
91
93
|
env: (key) => ({ ANTHROPIC_BASE_URL: ROOT, ANTHROPIC_API_KEY: key }),
|
|
92
94
|
args: (m) => (m ? ["--model", `anthropic/${m}`] : []),
|
|
93
95
|
},
|
|
@@ -96,7 +98,7 @@ export const RECIPES = {
|
|
|
96
98
|
bin: "hermes",
|
|
97
99
|
type: "env",
|
|
98
100
|
install: "curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash",
|
|
99
|
-
note:
|
|
101
|
+
note: "Pick the model after it starts with the `hermes model` command.",
|
|
100
102
|
env: (key) => ({ OPENAI_BASE_URL: V1, OPENAI_API_KEY: key }),
|
|
101
103
|
args: () => [],
|
|
102
104
|
},
|
|
@@ -105,7 +107,7 @@ export const RECIPES = {
|
|
|
105
107
|
bin: "qwen",
|
|
106
108
|
type: "env",
|
|
107
109
|
install: "npm install -g @qwen-code/qwen-code@latest",
|
|
108
|
-
env: (key, model) => ({ OPENAI_BASE_URL: V1, OPENAI_API_KEY: key, OPENAI_MODEL: model || "gpt-4
|
|
110
|
+
env: (key, model) => ({ OPENAI_BASE_URL: V1, OPENAI_API_KEY: key, OPENAI_MODEL: model || "gpt-5.4-mini" }),
|
|
109
111
|
args: () => [],
|
|
110
112
|
},
|
|
111
113
|
copilot: {
|
|
@@ -113,12 +115,12 @@ export const RECIPES = {
|
|
|
113
115
|
bin: "copilot",
|
|
114
116
|
type: "env",
|
|
115
117
|
install: "npm install -g @github/copilot",
|
|
116
|
-
note:
|
|
118
|
+
note: "Needs GitHub Copilot CLI 2026-04 or later for BYOK provider routing support.",
|
|
117
119
|
env: (key, model) => ({
|
|
118
120
|
COPILOT_PROVIDER_BASE_URL: V1,
|
|
119
121
|
COPILOT_PROVIDER_TYPE: "openai",
|
|
120
122
|
COPILOT_PROVIDER_API_KEY: key,
|
|
121
|
-
COPILOT_MODEL: model || "gpt-4
|
|
123
|
+
COPILOT_MODEL: model || "gpt-5.4-mini",
|
|
122
124
|
}),
|
|
123
125
|
args: () => [],
|
|
124
126
|
},
|
|
@@ -127,7 +129,7 @@ export const RECIPES = {
|
|
|
127
129
|
// Codex는 Chat Completions가 제거돼 Responses API(wire_api="responses")만 지원.
|
|
128
130
|
// 사용자의 ~/.codex/config.toml을 건드리지 않도록 격리 CODEX_HOME에 이 파일을 쓴다.
|
|
129
131
|
export function codexToml(model) {
|
|
130
|
-
const m = model || "gpt-4
|
|
132
|
+
const m = model || "gpt-5.4-mini";
|
|
131
133
|
return `# Generated by pleum CLI. Isolated CODEX_HOME — your ~/.codex is untouched.
|
|
132
134
|
model = "${m}"
|
|
133
135
|
model_provider = "pleum"
|
|
@@ -153,7 +155,7 @@ export const MANUAL = {
|
|
|
153
155
|
"type": "openai-compat",
|
|
154
156
|
"base_url": "${V1}",
|
|
155
157
|
"api_key": "$PLEUM_API_KEY",
|
|
156
|
-
"models": [{ "id": "gpt-4
|
|
158
|
+
"models": [{ "id": "gpt-5.4-mini", "name": "gpt-5.4-mini" }]
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
}`,
|
package/src/run.mjs
CHANGED
|
@@ -246,7 +246,9 @@ async function repl(key, initialModel, initialVerbose) {
|
|
|
246
246
|
rl.prompt();
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
// t() 키(영어 원문)만 담는다 — 모듈 로드 시점에 t()를 호출하면 같은 프로세스 안에서
|
|
250
|
+
// 언어를 바꿔도(Settings) 이 도움말만 예전 언어로 남는다. 실제 번역은 소비 지점(handleSlash)에서.
|
|
251
|
+
const SLASH_HELP = `Commands:
|
|
250
252
|
/help, /? this help
|
|
251
253
|
/clear reset the conversation context (keeps the system prompt)
|
|
252
254
|
/system [text] set the system prompt (no arg shows the current value)
|
|
@@ -257,7 +259,7 @@ const SLASH_HELP = t(`Commands:
|
|
|
257
259
|
/verbose toggle token/speed stats
|
|
258
260
|
/exit, /quit quit
|
|
259
261
|
Multi-line input: a line with """ → body → a line with """
|
|
260
|
-
Images: include an image file path right in the message (e.g. describe this ./shot.png)
|
|
262
|
+
Images: include an image file path right in the message (e.g. describe this ./shot.png)`;
|
|
261
263
|
|
|
262
264
|
async function handleSlash(text, ctx) {
|
|
263
265
|
const [cmd] = text.slice(1).split(/\s+/);
|
|
@@ -265,7 +267,7 @@ async function handleSlash(text, ctx) {
|
|
|
265
267
|
switch (cmd) {
|
|
266
268
|
case "help":
|
|
267
269
|
case "?":
|
|
268
|
-
console.log(SLASH_HELP);
|
|
270
|
+
console.log(t(SLASH_HELP));
|
|
269
271
|
break;
|
|
270
272
|
case "clear":
|
|
271
273
|
ctx.clear();
|
package/src/settings.mjs
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// 런처 Settings 서브메뉴 — agentSelect()(menu.mjs)와 동일한 "한 항목 → 서브메뉴" 패턴.
|
|
2
|
+
// menu.mjs의 runList/saveModel을 가져오고 menu.mjs는 이 모듈의 runSettings를 가져오는
|
|
3
|
+
// 순환 import가 있다 — 둘 다 함수 선언(export function)이라 ESM 인스턴스화 단계에서
|
|
4
|
+
// 먼저 호이스팅되므로 안전하다. 다만 앞으로 이 파일을 고칠 때도 두 모듈 모두 상대방의
|
|
5
|
+
// export를 "async 함수 바디 안에서만" 참조하는 규칙은 유지할 것(모듈 최상위에서 값으로
|
|
6
|
+
// 즉시 참조하면 순환의 평가 순서에 따라 깨질 수 있다).
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { style } from "./ansi.mjs";
|
|
10
|
+
import { select, confirm } from "./interactive.mjs";
|
|
11
|
+
import { readConfig, writeConfig, clearCredentials } from "./config.mjs";
|
|
12
|
+
import { getLocale, LANGS, t } from "./i18n.mjs";
|
|
13
|
+
import { RECIPES } from "./recipes.mjs";
|
|
14
|
+
import { fetchModels, matchModels } from "./models.mjs";
|
|
15
|
+
import { login } from "./login.mjs";
|
|
16
|
+
import { upgrade } from "./upgrade.mjs";
|
|
17
|
+
import { completion } from "./completion.mjs";
|
|
18
|
+
import { lang } from "./lang.mjs";
|
|
19
|
+
import { runList, saveModel } from "./menu.mjs";
|
|
20
|
+
|
|
21
|
+
// Settings 서브메뉴 항목(순수 함수 — 테스트 대상).
|
|
22
|
+
export function buildSettingsItems({ lang, loggedIn, keyPrefix, version }) {
|
|
23
|
+
return [
|
|
24
|
+
{ id: "settings:lang", title: t("Language"), desc: `${t("current")}: ${LANGS[lang] ?? lang}` },
|
|
25
|
+
{ id: "settings:models", title: t("Default models"), desc: t("Set the default model used per agent") },
|
|
26
|
+
{
|
|
27
|
+
id: "settings:account",
|
|
28
|
+
title: t("Account"),
|
|
29
|
+
desc: loggedIn ? `${t("Logged in")} (${t("key")} ${keyPrefix}…)` : t("Not logged in"),
|
|
30
|
+
},
|
|
31
|
+
{ id: "settings:update", title: t("Update CLI"), desc: t("Current version: {version}", { version }) },
|
|
32
|
+
{ id: "settings:completion", title: t("Shell completion"), desc: t("Print a zsh/bash completion script") },
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// "Default models" 서브메뉴 항목(순수 함수 — 테스트 대상). chat + RECIPES 전부.
|
|
37
|
+
// 필드명은 의도적으로 `tool`이 아니라 `toolId` — runList()의 → 키바인딩은 `it.tool`이
|
|
38
|
+
// 있으면 자동으로 menu.mjs의 pickModelFor(클리어 옵션 없음)를 띄운다. 이 화면은 Enter로만
|
|
39
|
+
// 진입해 "Use default (clear)"가 있는 자체 피커(pickDefaultModelFor)를 쓰므로 `tool`을
|
|
40
|
+
// 피해 → 를 비활성 상태로 둔다(그래야 Enter 흐름과 중복 실행되지 않는다).
|
|
41
|
+
export function buildDefaultModelItems({ savedModels = {} }) {
|
|
42
|
+
const tools = ["chat", ...Object.keys(RECIPES)];
|
|
43
|
+
return tools.map((tool) => ({
|
|
44
|
+
id: `defaultmodel:${tool}`,
|
|
45
|
+
toolId: tool,
|
|
46
|
+
title: tool === "chat" ? t("Chat with a model") : RECIPES[tool].label,
|
|
47
|
+
desc: savedModels[tool] ? `${t("model")}: ${savedModels[tool]}` : t("not set"),
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readVersion() {
|
|
52
|
+
// bin/pleum.mjs의 version()과 동일한 1줄 패턴 복제 — bin/pleum.mjs는 entry-guard 없이
|
|
53
|
+
// process.argv 스위치를 최상위에서 바로 실행하는 진입점이라 import하면 CLI 디스패치가
|
|
54
|
+
// 함께 실행돼 버린다. menuSelect()도 같은 이유로 이 패턴을 복제해 쓰고 있다.
|
|
55
|
+
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function clearSavedModel(tool) {
|
|
59
|
+
const cfg = readConfig() ?? {};
|
|
60
|
+
if (cfg.launch_models) delete cfg.launch_models[tool];
|
|
61
|
+
writeConfig(cfg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function settingsSelect() {
|
|
65
|
+
const cfg = readConfig();
|
|
66
|
+
const items = buildSettingsItems({
|
|
67
|
+
lang: getLocale(),
|
|
68
|
+
loggedIn: Boolean(cfg?.key),
|
|
69
|
+
keyPrefix: cfg?.key ? cfg.key.slice(0, 8) : null,
|
|
70
|
+
version: readVersion(),
|
|
71
|
+
});
|
|
72
|
+
return runList(items, {
|
|
73
|
+
header: style.bold(style.plum(t("Settings"))),
|
|
74
|
+
footer: t("↑/↓ move • enter select • esc back"),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const CLEAR_OPTION = { id: "__clear__", display_name: null, isClear: true };
|
|
79
|
+
|
|
80
|
+
async function pickDefaultModelFor(item) {
|
|
81
|
+
let models;
|
|
82
|
+
try {
|
|
83
|
+
models = await fetchModels();
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.log(`${style.red("✗")} ${t("Failed to fetch model list: {error}", { error: e.message })}`);
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const picked = await select([CLEAR_OPTION, ...models], {
|
|
89
|
+
message: `${item.title} — ${t("select a model")}`,
|
|
90
|
+
formatItem: (x) => (x.isClear ? style.dim(t("Use default (clear)")) : `${x.id}${x.display_name ? ` ${style.dim(x.display_name)}` : ""}`),
|
|
91
|
+
matches: matchModels, // 쿼리 입력 시 CLEAR_OPTION은 자연히 필터링됨(id/display_name 불일치) — 별도 처리 불요
|
|
92
|
+
limit: 12,
|
|
93
|
+
});
|
|
94
|
+
return picked ?? undefined; // select()는 취소 시 null — pickModelFor 관례처럼 undefined로 정규화
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function runDefaultModels() {
|
|
98
|
+
for (;;) {
|
|
99
|
+
const items = buildDefaultModelItems({ savedModels: readConfig()?.launch_models ?? {} });
|
|
100
|
+
const picked = await runList(items, {
|
|
101
|
+
header: style.bold(style.plum(t("Default models"))),
|
|
102
|
+
footer: t("↑/↓ move • enter set model • esc back"),
|
|
103
|
+
});
|
|
104
|
+
if (picked === undefined) return undefined;
|
|
105
|
+
if (picked === null) return null;
|
|
106
|
+
if (picked === "quit") return "quit";
|
|
107
|
+
const result = await pickDefaultModelFor(picked.item);
|
|
108
|
+
if (!result) continue; // 취소/조회 실패 — 목록 다시 표시
|
|
109
|
+
if (result.isClear) clearSavedModel(picked.item.toolId);
|
|
110
|
+
else saveModel(picked.item.toolId, result.id);
|
|
111
|
+
// 메인 런처의 →(설정 즉시 실행)와 의도적으로 다르다 — 저장만 하고 목록으로 복귀.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function runAccount() {
|
|
116
|
+
const cfg = readConfig();
|
|
117
|
+
if (cfg?.key) {
|
|
118
|
+
const ok = await confirm(t("Log out? [Y/n] "), { defaultYes: true });
|
|
119
|
+
if (ok) {
|
|
120
|
+
writeConfig(clearCredentials(cfg));
|
|
121
|
+
console.log(`✓ ${t("Logged out")}`);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
await login();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function runCompletion() {
|
|
129
|
+
const picked = await select(
|
|
130
|
+
[{ id: "zsh", label: "zsh" }, { id: "bash", label: "bash" }],
|
|
131
|
+
{ message: t("Choose a shell"), formatItem: (x) => x.label },
|
|
132
|
+
);
|
|
133
|
+
if (picked) completion(picked.id); // 출력만 — rc 파일 자동 수정 없음(기존 completion.mjs 그대로)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Settings 서브메뉴 루프. 반환값은 runList 규약을 그대로 전파: "quit"(ctrl+c 또는 재설치
|
|
137
|
+
// 시작) · null(esc/q, 메인 메뉴로) · undefined(비TTY).
|
|
138
|
+
export async function runSettings() {
|
|
139
|
+
for (;;) {
|
|
140
|
+
const picked = await settingsSelect();
|
|
141
|
+
if (picked === undefined) return undefined;
|
|
142
|
+
if (picked === null) return null;
|
|
143
|
+
if (picked === "quit") return "quit";
|
|
144
|
+
switch (picked.item.id) {
|
|
145
|
+
case "settings:lang":
|
|
146
|
+
await lang([]); // TTY 화살표 피커 재사용 — setLocale()이 i18n 캐시를 즉시 갱신하므로
|
|
147
|
+
continue; // 다음 루프의 buildSettingsItems() 재빌드로 새 언어가 자연히 반영된다
|
|
148
|
+
case "settings:models": {
|
|
149
|
+
const r = await runDefaultModels();
|
|
150
|
+
if (r === "quit") return "quit";
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
case "settings:account":
|
|
154
|
+
await runAccount();
|
|
155
|
+
continue;
|
|
156
|
+
case "settings:update":
|
|
157
|
+
// upgrade()는 자식(npm install) 종료 시 process.exit()을 호출한다. 여기서 continue해
|
|
158
|
+
// 메뉴를 다시 그리면 raw-mode 화면과 npm 출력이 뒤섞이다 프로세스가 갑자기 죽으므로,
|
|
159
|
+
// mainMenu()의 chat 케이스처럼 즉시 루프를 벗어난다.
|
|
160
|
+
console.log(t("Reinstalling — pleum will exit when finished. Run `pleum` again afterward."));
|
|
161
|
+
upgrade();
|
|
162
|
+
return "quit";
|
|
163
|
+
case "settings:completion":
|
|
164
|
+
await runCompletion();
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|