pleumrouter 0.5.46 → 0.5.48
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 +1 -1
- package/bin/pleum.mjs +1 -1
- package/package.json +1 -1
- package/src/completion.mjs +3 -2
- package/src/install.mjs +3 -0
- package/src/locales/es.mjs +8 -2
- package/src/locales/ja.mjs +8 -2
- package/src/locales/ko.mjs +8 -2
- package/src/locales/zh.mjs +8 -2
- package/src/wire.mjs +156 -27
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ SSH/CI에서는 `pleum login --device`를 쓰세요.
|
|
|
39
39
|
| `pleum logout` | 서버 세션 revoke + 로컬 토큰 삭제(언어·기본 모델 등 나머지 설정은 유지) |
|
|
40
40
|
| `pleum launch [tool] [--model id] [-- args]` | 에이전트 실행. `--` 뒤 인자는 도구로 그대로 전달. 도구 생략 시 지원 목록·설치 여부 표시 |
|
|
41
41
|
| `pleum wire [claude\|codex]` | Desktop용으로 `~/.claude/settings.json`·`~/.codex/config.toml`에 같은 `plm_` 키를 병합. 생략 시 둘 다 |
|
|
42
|
-
| `pleum unwire [
|
|
42
|
+
| `pleum unwire [tool...] [--revoke-key]` | wire가 바꾼 설정을 원래 값으로 복원(도구 생략 시 배선된 도구 전부). `--revoke-key`는 서버 키도 폐기 — 다른 배선 도구가 남아 있으면 거부 |
|
|
43
43
|
| `pleum run [model] ["프롬프트"] [--attach image]` | 채팅 — 대화형 REPL, 1회 실행, stdin 파이프. 모델 생략 시 화살표로 선택. 이미지 업로드는 명시적으로 `--attach ./shot.png`(1회 실행) 또는 REPL의 `@./shot.png`를 사용한다. `--skip-cache`(이번 요청 응답 캐시 스킵), `--tag a,b`(리포팅 태그, ≤10) |
|
|
44
44
|
| `pleum models [query] [--json]` | 모델 목록(공개) — provider·컨텍스트·가격(₩/1M) 컬럼, `--json`은 스크립트용 |
|
|
45
45
|
| `pleum models <id> --info` | 모델 상세 — 가격·컨텍스트·provider별 지연/처리량/업타임·주간 사용량·형제 모델 |
|
package/bin/pleum.mjs
CHANGED
|
@@ -38,7 +38,7 @@ function buildHelp() {
|
|
|
38
38
|
["", `(${Object.keys(RECIPES).join(" · ")})`],
|
|
39
39
|
["", t("omit the tool to see the supported list and install status")],
|
|
40
40
|
["pleum wire [claude|codex]", t("persist config in ~/.claude or ~/.codex for Desktop apps")],
|
|
41
|
-
["pleum unwire [
|
|
41
|
+
["pleum unwire [tool]", t("Restore settings changed by wire (omit the tool = all wired tools)")],
|
|
42
42
|
['pleum run [model] ["prompt"] [--attach image]', t("Terminal chat (REPL) / one-shot / pipe")],
|
|
43
43
|
["", t("omit the model for an arrow-key picker; /help lists commands")],
|
|
44
44
|
["pleum models [query] [--json]", t("Model list (price, context included)")],
|
package/package.json
CHANGED
package/src/completion.mjs
CHANGED
|
@@ -49,13 +49,14 @@ _pleum() {
|
|
|
49
49
|
_pleum "$@"`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export function completion(shell) {
|
|
52
|
+
export function completion(shell, platform = process.platform) {
|
|
53
53
|
if (shell === "bash") return void console.log(bash());
|
|
54
54
|
if (shell === "zsh") return void console.log(zsh());
|
|
55
55
|
console.error(
|
|
56
56
|
t("Usage: pleum completion <zsh|bash>") + "\n" +
|
|
57
57
|
` zsh: eval "$(pleum completion zsh)" (${t("or add to ~/.zshrc")})\n` +
|
|
58
|
-
` bash: eval "$(pleum completion bash)" (${t("or add to ~/.bashrc")})
|
|
58
|
+
` bash: eval "$(pleum completion bash)" (${t("or add to ~/.bashrc")})` +
|
|
59
|
+
(platform === "win32" ? `\n${t("PowerShell and cmd completion are not supported; use Git Bash or WSL.")}` : ""),
|
|
59
60
|
);
|
|
60
61
|
process.exit(1);
|
|
61
62
|
}
|
package/src/install.mjs
CHANGED
|
@@ -82,9 +82,12 @@ function runCurlPipe(pipe, spawnImpl, resolve) {
|
|
|
82
82
|
export async function runInstallCommand(install, {
|
|
83
83
|
spawnImpl = spawn,
|
|
84
84
|
resolve = resolveLaunchCommand,
|
|
85
|
+
platform = process.platform,
|
|
85
86
|
} = {}) {
|
|
86
87
|
const parsed = parseInstallCommand(install);
|
|
87
88
|
if (parsed.manual) return { manual: true, reason: parsed.reason, code: null };
|
|
89
|
+
// Windows엔 sh/bash가 없다 — curl|sh는 시도하지 않고 수동 안내로 넘긴다.
|
|
90
|
+
if (parsed.pipe && platform === "win32") return { manual: true, reason: "windows-pipe", code: null };
|
|
88
91
|
if (parsed.pipe) {
|
|
89
92
|
return { manual: false, code: await runCurlPipe(parsed.pipe, spawnImpl, resolve) };
|
|
90
93
|
}
|
package/src/locales/es.mjs
CHANGED
|
@@ -161,11 +161,12 @@ export const ES = {
|
|
|
161
161
|
"Unknown command: /{cmd} (/help)": "Comando desconocido: /{cmd} (/help)",
|
|
162
162
|
"Unknown command: {cmd}": "Comando desconocido: {cmd}",
|
|
163
163
|
"Usage: pleum wire [claude|codex]\n": "Uso: pleum wire [claude|codex]\n",
|
|
164
|
-
"Usage: pleum unwire [
|
|
164
|
+
"Usage: pleum unwire [tool...] [--revoke-key]\n": "Uso: pleum unwire [tool...] [--revoke-key]\n",
|
|
165
165
|
"Example: pleum wire": "Ejemplo: pleum wire",
|
|
166
166
|
"Omit the tool name to wire both claude and codex.": "Si omites el nombre, cablea claude y codex.",
|
|
167
|
+
"Omit the tool name to unwire every wired tool.": "Si omites el nombre, desconecta todas las herramientas conectadas.",
|
|
167
168
|
"persist config in ~/.claude or ~/.codex for Desktop apps": "guardar la config en ~/.claude o ~/.codex para apps Desktop",
|
|
168
|
-
"
|
|
169
|
+
"Restore settings changed by wire (omit the tool = all wired tools)": "Restaura la config que cambió wire (sin herramienta = todas las conectadas)",
|
|
169
170
|
"Unsupported tool: {tool}. Use claude or codex.": "Herramienta no compatible: {tool}. Usa claude o codex.",
|
|
170
171
|
"Wrote {path}": "Escrito en {path}",
|
|
171
172
|
"Wired {tool}.": "Cableado {tool}.",
|
|
@@ -179,6 +180,9 @@ export const ES = {
|
|
|
179
180
|
"Cursor IDE: Settings → Models → OpenAI Compatible. Base URL {v1}, same plm_ key. Cursor CLI is out of scope.":
|
|
180
181
|
"Cursor IDE: Settings → Models → OpenAI Compatible. Base URL {v1}, la misma clave plm_. Cursor CLI queda fuera de alcance.",
|
|
181
182
|
"Removed PleumRouter settings from {path}.": "Se quitó la config de PleumRouter de {path}.",
|
|
183
|
+
"{tool} is not wired by PleumRouter; left unchanged.": "{tool} no está conectado por PleumRouter; se dejó sin cambios.",
|
|
184
|
+
"No tools are wired by PleumRouter.": "PleumRouter no tiene herramientas conectadas.",
|
|
185
|
+
"Other tools still use this key: {tools}. Run `pleum unwire --revoke-key` to unwire all of them first.": "Otras herramientas aún usan esta clave: {tools}. Antes ejecuta `pleum unwire --revoke-key` para desconectarlas todas.",
|
|
182
186
|
"No stored desktop API key.": "No hay clave API de escritorio guardada.",
|
|
183
187
|
"Revoked desktop API key {id}.": "Se revocó la clave API de escritorio {id}.",
|
|
184
188
|
"Failed to revoke the desktop API key (HTTP {status})": "No se pudo revocar la clave API de escritorio (HTTP {status})",
|
|
@@ -189,6 +193,8 @@ export const ES = {
|
|
|
189
193
|
"Update CLI": "Actualizar la CLI",
|
|
190
194
|
"Usage": "Uso",
|
|
191
195
|
"Usage: pleum completion <zsh|bash>": "Uso: pleum completion <zsh|bash>",
|
|
196
|
+
"{tool}: wire needs a zsh/bash shell and is not supported on Windows. Use `pleum launch {tool}` instead.": "{tool}: wire necesita un shell zsh/bash y no es compatible con Windows. Usa `pleum launch {tool}` en su lugar.",
|
|
197
|
+
"PowerShell and cmd completion are not supported; use Git Bash or WSL.": "El autocompletado para PowerShell y cmd no es compatible; usa Git Bash o WSL.",
|
|
192
198
|
"Usage: pleum launch <tool> [--model id] [-- tool args]\n": "Uso: pleum launch <tool> [--model id] [-- tool args]\n",
|
|
193
199
|
"Usage: pleum models <model-id> --info": "Uso: pleum models <model-id> --info",
|
|
194
200
|
"Usage: pleum run <model> [\"prompt\"]\nModel list: pleum models": "Uso: pleum run <model> [\"prompt\"]\nLista de modelos: pleum models",
|
package/src/locales/ja.mjs
CHANGED
|
@@ -99,6 +99,7 @@ export const JA = {
|
|
|
99
99
|
"Session expired. Run:\n pleum login": "セッションの有効期限が切れました。実行してください:\n pleum login",
|
|
100
100
|
"OAuth session": "OAuth セッション",
|
|
101
101
|
"Omit the tool name to wire both claude and codex.": "ツール名を省略すると claude と codex の両方を配線します。",
|
|
102
|
+
"Omit the tool name to unwire every wired tool.": "ツール名を省略すると、配線済みのツールをすべて解除します。",
|
|
102
103
|
"Log in via browser (OAuth session, no API key)": "ブラウザでログイン(OAuth セッション、API キーなし)",
|
|
103
104
|
"Revoke session and clear local tokens": "セッションを取り消し、ローカル トークンを削除",
|
|
104
105
|
"Logged out": "ログアウトしました",
|
|
@@ -136,7 +137,7 @@ export const JA = {
|
|
|
136
137
|
"Print a zsh/bash completion script": "zsh/bash補完スクリプトを出力",
|
|
137
138
|
"Print the version": "バージョンを表示",
|
|
138
139
|
"Provider share": "プロバイダーシェア",
|
|
139
|
-
"
|
|
140
|
+
"Restore settings changed by wire (omit the tool = all wired tools)": "wire が変更した設定を元に戻す(ツール省略で配線済みすべて)",
|
|
140
141
|
"Revoked desktop API key {id}.": "デスクトップ API キー {id} を失効しました。",
|
|
141
142
|
"Run a coding agent against PleumRouter": "PleumRouter に対してコーディングエージェントを実行",
|
|
142
143
|
"Run pleum login again to get a new key (recent keys include it by default).": "新しいキーを取得するには pleum login を再実行してください (最近のキーはデフォルトで含まれます)。",
|
|
@@ -179,6 +180,9 @@ export const JA = {
|
|
|
179
180
|
"Cursor IDE: Settings → Models → OpenAI Compatible. Base URL {v1}, same plm_ key. Cursor CLI is out of scope.":
|
|
180
181
|
"Cursor IDE: Settings → Models → OpenAI Compatible。Base URL {v1}、同じ plm_ キー。Cursor CLI は対象外です。",
|
|
181
182
|
"Removed PleumRouter settings from {path}.": "{path} から PleumRouter 設定を削除しました。",
|
|
183
|
+
"{tool} is not wired by PleumRouter; left unchanged.": "{tool} は PleumRouter で配線されていないため、そのままにしました。",
|
|
184
|
+
"No tools are wired by PleumRouter.": "PleumRouter で配線されたツールはありません。",
|
|
185
|
+
"Other tools still use this key: {tools}. Run `pleum unwire --revoke-key` to unwire all of them first.": "他のツールがまだこのキーを使っています: {tools}。先に `pleum unwire --revoke-key` ですべて解除してください。",
|
|
182
186
|
"Failed to create a desktop API key (HTTP {status})": "デスクトップ API キーの発行に失敗しました (HTTP {status})",
|
|
183
187
|
"kept the desktop key on this machine (logout does not delete it)": "このマシンのデスクトップキーは残します(logout では消しません)",
|
|
184
188
|
"Unsupported language: {code}\n Available: {list}": "サポートされていない言語です: {code}\n 利用可能: {list}",
|
|
@@ -187,10 +191,12 @@ export const JA = {
|
|
|
187
191
|
"Update CLI": "CLIを更新",
|
|
188
192
|
"Usage": "使用状況",
|
|
189
193
|
"Usage: pleum completion <zsh|bash>": "使い方: pleum completion <zsh|bash>",
|
|
194
|
+
"{tool}: wire needs a zsh/bash shell and is not supported on Windows. Use `pleum launch {tool}` instead.": "{tool}: wire は zsh/bash シェルが必要なため Windows では使えません。代わりに `pleum launch {tool}` を使ってください。",
|
|
195
|
+
"PowerShell and cmd completion are not supported; use Git Bash or WSL.": "PowerShell・cmd の補完には対応していません。Git Bash か WSL を使ってください。",
|
|
190
196
|
"Usage: pleum launch <tool> [--model id] [-- tool args]\n": "使い方: pleum launch <tool> [--model id] [-- tool args]\n",
|
|
191
197
|
"Usage: pleum models <model-id> --info": "使い方: pleum models <model-id> --info",
|
|
192
198
|
"Usage: pleum run <model> [\"prompt\"]\nModel list: pleum models": "使い方: pleum run <model> [\"prompt\"]\nモデル一覧: pleum models",
|
|
193
|
-
"Usage: pleum unwire [
|
|
199
|
+
"Usage: pleum unwire [tool...] [--revoke-key]\n": "使い方: pleum unwire [tool...] [--revoke-key]\n",
|
|
194
200
|
"Usage: pleum wire [claude|codex]\n": "使い方: pleum wire [claude|codex]\n",
|
|
195
201
|
"Use default (clear)": "デフォルトを使用(解除)",
|
|
196
202
|
"Your spend report (model|provider|api_key|day|tag|user)": "支出レポート (model|provider|api_key|day|tag|user)",
|
package/src/locales/ko.mjs
CHANGED
|
@@ -65,6 +65,8 @@ export const KO = {
|
|
|
65
65
|
|
|
66
66
|
// completion.mjs
|
|
67
67
|
"Usage: pleum completion <zsh|bash>": "사용법: pleum completion <zsh|bash>",
|
|
68
|
+
"{tool}: wire needs a zsh/bash shell and is not supported on Windows. Use `pleum launch {tool}` instead.": "{tool}: wire는 zsh/bash 셸이 필요해 Windows에서 지원하지 않습니다. 대신 `pleum launch {tool}`을 사용하세요.",
|
|
69
|
+
"PowerShell and cmd completion are not supported; use Git Bash or WSL.": "PowerShell·cmd 자동완성은 지원하지 않습니다. Git Bash나 WSL을 사용하세요.",
|
|
68
70
|
"or add to ~/.zshrc": "또는 ~/.zshrc에 추가",
|
|
69
71
|
"or add to ~/.bashrc": "또는 ~/.bashrc에 추가",
|
|
70
72
|
"or add _pleum to fpath": "또는 fpath에 _pleum로 추가",
|
|
@@ -268,11 +270,12 @@ export const KO = {
|
|
|
268
270
|
"Logged out": "로그아웃됨",
|
|
269
271
|
"Unknown command: {cmd}": "알 수 없는 명령: {cmd}",
|
|
270
272
|
"Usage: pleum wire [claude|codex]\n": "사용법: pleum wire [claude|codex]\n",
|
|
271
|
-
"Usage: pleum unwire [
|
|
273
|
+
"Usage: pleum unwire [tool...] [--revoke-key]\n": "사용법: pleum unwire [tool...] [--revoke-key]\n",
|
|
272
274
|
"Example: pleum wire": "예: pleum wire",
|
|
273
275
|
"Omit the tool name to wire both claude and codex.": "도구 이름을 생략하면 claude와 codex를 모두 배선합니다.",
|
|
276
|
+
"Omit the tool name to unwire every wired tool.": "도구 이름을 생략하면 배선된 도구를 모두 해제합니다.",
|
|
274
277
|
"persist config in ~/.claude or ~/.codex for Desktop apps": "Desktop 앱용으로 ~/.claude 또는 ~/.codex에 설정을 저장",
|
|
275
|
-
"
|
|
278
|
+
"Restore settings changed by wire (omit the tool = all wired tools)": "wire가 바꾼 설정을 복원 (도구 생략 = 배선된 도구 전부)",
|
|
276
279
|
"Unsupported tool: {tool}. Use claude or codex.": "지원하지 않는 도구: {tool}. claude 또는 codex를 쓰세요.",
|
|
277
280
|
"Wrote {path}": "{path}에 썼습니다",
|
|
278
281
|
"Wired {tool}.": "{tool}을 배선했습니다.",
|
|
@@ -286,6 +289,9 @@ export const KO = {
|
|
|
286
289
|
"Cursor IDE: Settings → Models → OpenAI Compatible. Base URL {v1}, same plm_ key. Cursor CLI is out of scope.":
|
|
287
290
|
"Cursor IDE: 설정 → Models → OpenAI Compatible. Base URL {v1}, 같은 plm_ 키. Cursor CLI는 범위 밖입니다.",
|
|
288
291
|
"Removed PleumRouter settings from {path}.": "{path}에서 PleumRouter 설정을 제거했습니다.",
|
|
292
|
+
"{tool} is not wired by PleumRouter; left unchanged.": "{tool}은(는) PleumRouter가 배선하지 않아 그대로 둡니다.",
|
|
293
|
+
"No tools are wired by PleumRouter.": "PleumRouter가 배선한 도구가 없습니다.",
|
|
294
|
+
"Other tools still use this key: {tools}. Run `pleum unwire --revoke-key` to unwire all of them first.": "다른 도구가 이 키를 아직 쓰고 있습니다: {tools}. 먼저 `pleum unwire --revoke-key`로 전부 해제하세요.",
|
|
289
295
|
"No stored desktop API key.": "저장된 데스크톱 API 키가 없습니다.",
|
|
290
296
|
"Revoked desktop API key {id}.": "데스크톱 API 키 {id}를 폐기했습니다.",
|
|
291
297
|
"Failed to revoke the desktop API key (HTTP {status})": "데스크톱 API 키 폐기에 실패했습니다 (HTTP {status})",
|
package/src/locales/zh.mjs
CHANGED
|
@@ -161,11 +161,12 @@ export const ZH = {
|
|
|
161
161
|
"Unknown command: /{cmd} (/help)": "未知命令:/{cmd} (/help)",
|
|
162
162
|
"Unknown command: {cmd}": "未知命令:{cmd}",
|
|
163
163
|
"Usage: pleum wire [claude|codex]\n": "用法:pleum wire [claude|codex]\n",
|
|
164
|
-
"Usage: pleum unwire [
|
|
164
|
+
"Usage: pleum unwire [tool...] [--revoke-key]\n": "用法:pleum unwire [tool...] [--revoke-key]\n",
|
|
165
165
|
"Example: pleum wire": "示例:pleum wire",
|
|
166
166
|
"Omit the tool name to wire both claude and codex.": "省略工具名会同时接线 claude 和 codex。",
|
|
167
|
+
"Omit the tool name to unwire every wired tool.": "省略工具名会解除所有已接线的工具。",
|
|
167
168
|
"persist config in ~/.claude or ~/.codex for Desktop apps": "为 Desktop 应用把配置写入 ~/.claude 或 ~/.codex",
|
|
168
|
-
"
|
|
169
|
+
"Restore settings changed by wire (omit the tool = all wired tools)": "还原 wire 修改的设置(省略工具名 = 所有已接线的工具)",
|
|
169
170
|
"Unsupported tool: {tool}. Use claude or codex.": "不支持的工具:{tool}。请使用 claude 或 codex。",
|
|
170
171
|
"Wrote {path}": "已写入 {path}",
|
|
171
172
|
"Wired {tool}.": "已接线 {tool}。",
|
|
@@ -179,6 +180,9 @@ export const ZH = {
|
|
|
179
180
|
"Cursor IDE: Settings → Models → OpenAI Compatible. Base URL {v1}, same plm_ key. Cursor CLI is out of scope.":
|
|
180
181
|
"Cursor IDE:设置 → Models → OpenAI Compatible。Base URL {v1},同一把 plm_ 密钥。Cursor CLI 不在范围内。",
|
|
181
182
|
"Removed PleumRouter settings from {path}.": "已从 {path} 移除 PleumRouter 设置。",
|
|
183
|
+
"{tool} is not wired by PleumRouter; left unchanged.": "{tool} 不是由 PleumRouter 接线的,保持不变。",
|
|
184
|
+
"No tools are wired by PleumRouter.": "没有由 PleumRouter 接线的工具。",
|
|
185
|
+
"Other tools still use this key: {tools}. Run `pleum unwire --revoke-key` to unwire all of them first.": "其他工具仍在使用此密钥:{tools}。请先运行 `pleum unwire --revoke-key` 全部解除。",
|
|
182
186
|
"No stored desktop API key.": "没有保存的桌面 API 密钥。",
|
|
183
187
|
"Revoked desktop API key {id}.": "已撤销桌面 API 密钥 {id}。",
|
|
184
188
|
"Failed to revoke the desktop API key (HTTP {status})": "撤销桌面 API 密钥失败(HTTP {status})",
|
|
@@ -189,6 +193,8 @@ export const ZH = {
|
|
|
189
193
|
"Update CLI": "更新 CLI",
|
|
190
194
|
"Usage": "用量",
|
|
191
195
|
"Usage: pleum completion <zsh|bash>": "用法:pleum completion <zsh|bash>",
|
|
196
|
+
"{tool}: wire needs a zsh/bash shell and is not supported on Windows. Use `pleum launch {tool}` instead.": "{tool}:wire 需要 zsh/bash shell,不支持 Windows。请改用 `pleum launch {tool}`。",
|
|
197
|
+
"PowerShell and cmd completion are not supported; use Git Bash or WSL.": "不支持 PowerShell 和 cmd 自动补全,请使用 Git Bash 或 WSL。",
|
|
192
198
|
"Usage: pleum launch <tool> [--model id] [-- tool args]\n": "用法:pleum launch <tool> [--model id] [-- tool args]\n",
|
|
193
199
|
"Usage: pleum models <model-id> --info": "用法:pleum models <model-id> --info",
|
|
194
200
|
"Usage: pleum run <model> [\"prompt\"]\nModel list: pleum models": "用法:pleum run <model> [\"prompt\"]\n模型列表:pleum models",
|
package/src/wire.mjs
CHANGED
|
@@ -196,6 +196,49 @@ function removeRootValue(toml, name) {
|
|
|
196
196
|
return root.replace(new RegExp(`^${name}\\s*=\\s*.+\\n?`, "m"), "") + rest;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
200
|
+
|
|
201
|
+
// 블록을 지운 뒤 남는 구분 빈 줄(wire가 붙인 것)을 정리한다: 끝의 연속 줄바꿈은 하나로, 남은 게 없으면 "".
|
|
202
|
+
function tidyUnwired(text) {
|
|
203
|
+
const out = text.trimStart().replace(/\n{2,}$/, "\n");
|
|
204
|
+
return out.trim() ? out : "";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 키 순서를 order에 맞춰 되살린다. 사라진 키는 건너뛰고, 그 뒤에 생긴 키는 현재 순서로 뒤에 붙는다.
|
|
208
|
+
function reorder(obj, order = []) {
|
|
209
|
+
const out = {};
|
|
210
|
+
for (const key of order) if (Object.hasOwn(obj, key)) out[key] = obj[key];
|
|
211
|
+
return Object.assign(out, obj);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// pleum이 배선한 Claude env인가: 게이트웨이가 우리 ROOT이거나 토큰이 plm_ 키.
|
|
215
|
+
function claudeEnvWired(env) {
|
|
216
|
+
const base = String(env?.ANTHROPIC_BASE_URL ?? "").replace(/\/+$/, "");
|
|
217
|
+
return base === ROOT || String(env?.ANTHROPIC_AUTH_TOKEN ?? "").startsWith("plm_");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// wire가 덮어쓰기 전의 사용자 값(claude·agy). 이미 pleum이 배선한 텍스트는 pleum 값을 원본으로 오인하므로 null.
|
|
221
|
+
// agy의 "gemini" provider는 launch.mjs와 같이 pleum 잔재로 본다.
|
|
222
|
+
function pickPrevious(tool, text) {
|
|
223
|
+
let obj;
|
|
224
|
+
try {
|
|
225
|
+
obj = parseJsonObject(text);
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
if (tool === "agy") {
|
|
230
|
+
return obj.modelProvider === "gemini" ? null : { model: obj.model, modelProvider: obj.modelProvider };
|
|
231
|
+
}
|
|
232
|
+
if (claudeEnvWired(obj.env)) return null;
|
|
233
|
+
const env = isPlainObject(obj.env) ? obj.env : undefined;
|
|
234
|
+
return {
|
|
235
|
+
env: Object.fromEntries(CLAUDE_ENV_KEYS.filter((k) => env?.[k] !== undefined).map((k) => [k, env[k]])),
|
|
236
|
+
model: obj.model,
|
|
237
|
+
order: Object.keys(obj),
|
|
238
|
+
envOrder: env && Object.keys(env),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
199
242
|
// ── Claude settings.json ──────────────────────────────────────────
|
|
200
243
|
export function wireClaudeSettings(existingJson, { root, key, model } = {}) {
|
|
201
244
|
const obj = parseJsonObject(existingJson);
|
|
@@ -228,16 +271,28 @@ export function wireClaudeSettings(existingJson, { root, key, model } = {}) {
|
|
|
228
271
|
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
229
272
|
}
|
|
230
273
|
|
|
231
|
-
|
|
274
|
+
// previous: wire 전 사용자 값(pickPrevious). 없으면 pleum 키만 지운다.
|
|
275
|
+
export function unwireClaudeSettings(existingJson, previous = {}) {
|
|
232
276
|
const obj = parseJsonObject(existingJson);
|
|
233
|
-
|
|
277
|
+
// wire는 obj.model과 같은 값을 ANTHROPIC_CUSTOM_MODEL_OPTION에도 쓴다 — claude- 접두사가 없는 모델도 이걸로 알아본다.
|
|
278
|
+
const customModel = isPlainObject(obj.env) ? obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION : undefined;
|
|
279
|
+
if (isPlainObject(obj.env)) {
|
|
234
280
|
for (const envKey of CLAUDE_ENV_KEYS) delete obj.env[envKey];
|
|
235
|
-
if (Object.keys(obj.env).length === 0) delete obj.env;
|
|
236
281
|
}
|
|
237
|
-
if (
|
|
238
|
-
|
|
282
|
+
if (previous.env && Object.keys(previous.env).length > 0) {
|
|
283
|
+
obj.env = { ...(isPlainObject(obj.env) ? obj.env : {}), ...previous.env };
|
|
239
284
|
}
|
|
240
|
-
|
|
285
|
+
if (isPlainObject(obj.env)) {
|
|
286
|
+
obj.env = reorder(obj.env, previous.envOrder);
|
|
287
|
+
if (Object.keys(obj.env).length === 0 && !previous.envOrder) delete obj.env;
|
|
288
|
+
}
|
|
289
|
+
// model은 pleum이 넣은 값이거나 billing wire가 지워 비어 있을 때만 되돌린다 — 그 뒤 사용자가 바꾼 값은 둔다.
|
|
290
|
+
const pleumModel = typeof obj.model === "string" && (obj.model.startsWith("anthropic/") || obj.model.startsWith("pleum/") || obj.model === customModel);
|
|
291
|
+
if (pleumModel || obj.model === undefined) {
|
|
292
|
+
if (previous.model === undefined) delete obj.model;
|
|
293
|
+
else obj.model = previous.model;
|
|
294
|
+
}
|
|
295
|
+
return `${JSON.stringify(reorder(obj, previous.order), null, 2)}\n`;
|
|
241
296
|
}
|
|
242
297
|
|
|
243
298
|
// ── Codex config.toml ─────────────────────────────────────────────
|
|
@@ -287,7 +342,10 @@ export function unwireCodexToml(existingToml) {
|
|
|
287
342
|
previousModel ? `model = ${previousModel}` : "",
|
|
288
343
|
previousProvider ? `model_provider = ${previousProvider}` : "",
|
|
289
344
|
].filter(Boolean).join("\n");
|
|
290
|
-
|
|
345
|
+
// 복원한 root 줄 바로 뒤가 root 키면 붙이고(원래 모양), 테이블이면 빈 줄 하나를 둔다.
|
|
346
|
+
const rest = body.trim();
|
|
347
|
+
const gap = !restored || !rest ? "" : rest.startsWith("[") ? "\n\n" : "\n";
|
|
348
|
+
return `${restored}${gap}${rest}`.replace(/\n{3,}/g, "\n\n").trimStart() + "\n";
|
|
291
349
|
}
|
|
292
350
|
|
|
293
351
|
// ── OpenCode (opencode.json & auth.json) ──────────────────────────
|
|
@@ -369,9 +427,7 @@ export function wireAiderYaml(existingYaml = "", { v1, key, model } = {}) {
|
|
|
369
427
|
}
|
|
370
428
|
|
|
371
429
|
export function unwireAiderYaml(existingYaml = "") {
|
|
372
|
-
return (existingYaml || "")
|
|
373
|
-
.replace(new RegExp(`${WIRE_MARKER_START}[\\s\\S]*?${WIRE_MARKER_END}\\n?`, "g"), "")
|
|
374
|
-
.trimStart();
|
|
430
|
+
return tidyUnwired((existingYaml || "").replace(new RegExp(`${WIRE_MARKER_START}[\\s\\S]*?${WIRE_MARKER_END}\\n?`, "g"), ""));
|
|
375
431
|
}
|
|
376
432
|
|
|
377
433
|
// ── Crush ~/.config/crush/crush.json ──────────────────────────────
|
|
@@ -426,10 +482,18 @@ export function wireAgySettings(existingJson, { model } = {}) {
|
|
|
426
482
|
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
427
483
|
}
|
|
428
484
|
|
|
429
|
-
|
|
485
|
+
// previous: wire 전 사용자 값. wiredModel: undefined=모름(옛 호출, model을 지운다), null=pleum이 model을 쓰지 않음, 문자열=pleum이 쓴 값.
|
|
486
|
+
// model은 pleum이 쓴 값일 때만 되돌린다.
|
|
487
|
+
export function unwireAgySettings(existingJson, previous = {}, wiredModel) {
|
|
430
488
|
const obj = parseJsonObject(existingJson);
|
|
431
|
-
if (obj.modelProvider === "gemini")
|
|
432
|
-
|
|
489
|
+
if (obj.modelProvider === "gemini") {
|
|
490
|
+
if (previous.modelProvider === undefined) delete obj.modelProvider;
|
|
491
|
+
else obj.modelProvider = previous.modelProvider;
|
|
492
|
+
}
|
|
493
|
+
if (wiredModel === undefined || obj.model === wiredModel) {
|
|
494
|
+
if (previous.model === undefined) delete obj.model;
|
|
495
|
+
else obj.model = previous.model;
|
|
496
|
+
}
|
|
433
497
|
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
434
498
|
}
|
|
435
499
|
|
|
@@ -529,9 +593,7 @@ ${endMarker}`;
|
|
|
529
593
|
export function unwireShellWrapper(existingRc = "", tool) {
|
|
530
594
|
const startMarker = `# pleum-wire:${tool}-start`;
|
|
531
595
|
const endMarker = `# pleum-wire:${tool}-end`;
|
|
532
|
-
return (existingRc || "")
|
|
533
|
-
.replace(new RegExp(`${startMarker}[\\s\\S]*?${endMarker}\\n?`, "g"), "")
|
|
534
|
-
.trimStart();
|
|
596
|
+
return tidyUnwired((existingRc || "").replace(new RegExp(`${startMarker}[\\s\\S]*?${endMarker}\\n?`, "g"), ""));
|
|
535
597
|
}
|
|
536
598
|
|
|
537
599
|
// ── Wired State Metadata (~/.pleum/wired-agents.json) ─────────────
|
|
@@ -550,13 +612,24 @@ export function writeWiredState(paths, state) {
|
|
|
550
612
|
|
|
551
613
|
// ── Wire Status Check ─────────────────────────────────────────────
|
|
552
614
|
export function getWireStatus(home = homedir()) {
|
|
553
|
-
|
|
615
|
+
return wireStatusOf(wirePaths(home));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// paths를 그대로 받아, revert가 쓰는 rc 경로와 같은 기준으로 판정한다.
|
|
619
|
+
function wireStatusOf(paths) {
|
|
554
620
|
const state = readWiredState(paths);
|
|
555
621
|
const status = {};
|
|
556
622
|
|
|
557
|
-
// 1. Claude
|
|
623
|
+
// 1. Claude — 게이트웨이/토큰이 pleum 것일 때만(사용자 본인 게이트웨이는 배선 아님). 파싱 불가(JSONC 등)는 예전 텍스트 검사.
|
|
558
624
|
const claudeText = readText(paths.claude);
|
|
559
|
-
const claudeWired =
|
|
625
|
+
const claudeWired = (() => {
|
|
626
|
+
if (!claudeText) return false;
|
|
627
|
+
try {
|
|
628
|
+
return claudeEnvWired(parseJsonObject(claudeText).env);
|
|
629
|
+
} catch {
|
|
630
|
+
return claudeText.includes("ANTHROPIC_BASE_URL");
|
|
631
|
+
}
|
|
632
|
+
})();
|
|
560
633
|
status.claude = {
|
|
561
634
|
tool: "claude",
|
|
562
635
|
label: "Claude Code",
|
|
@@ -627,9 +700,10 @@ export function getWireStatus(home = homedir()) {
|
|
|
627
700
|
};
|
|
628
701
|
|
|
629
702
|
// 7. Antigravity (agy)
|
|
703
|
+
// settings의 gemini provider만으로는 사용자 본인 설정·launch 임시값과 구분이 안 되므로, rc 블록이나 배선 기록이 함께 있어야 한다.
|
|
630
704
|
const agyText = readText(paths.agy);
|
|
631
705
|
const rcText = readText(paths.shellRc) || "";
|
|
632
|
-
const agyWired = Boolean((
|
|
706
|
+
const agyWired = Boolean(rcText.includes("# pleum-wire:agy-start") || (state.agy && agyText?.includes('"modelProvider": "gemini"')));
|
|
633
707
|
status.agy = {
|
|
634
708
|
tool: "agy",
|
|
635
709
|
label: "Antigravity CLI",
|
|
@@ -785,7 +859,8 @@ export function wireHelp() {
|
|
|
785
859
|
}
|
|
786
860
|
|
|
787
861
|
export function unwireHelp() {
|
|
788
|
-
console.log(t("Usage: pleum unwire [
|
|
862
|
+
console.log(t("Usage: pleum unwire [tool...] [--revoke-key]\n"));
|
|
863
|
+
console.log(` ${t("Omit the tool name to unwire every wired tool.")}`);
|
|
789
864
|
}
|
|
790
865
|
|
|
791
866
|
function printDesktopHints(record) {
|
|
@@ -807,14 +882,37 @@ function printDesktopHints(record) {
|
|
|
807
882
|
console.log(t("kept the desktop key on this machine (logout does not delete it)"));
|
|
808
883
|
}
|
|
809
884
|
|
|
810
|
-
|
|
885
|
+
// 이 기록의 wire가 agy settings에 쓴 model: 문자열, 안 썼으면 null(billing·모델 없음), 기록이 없으면 undefined.
|
|
886
|
+
function agyWiredModel(record) {
|
|
887
|
+
return record ? (record.mode === "billing" ? null : (record.model ?? null)) : undefined;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// 이미 배선 기록이 있으면 그 원본을 그대로 쓴다(재배선이 pleum 값을 원본으로 덮지 않게). 0.5.45~46 기록(previous 없음)은
|
|
891
|
+
// 현재 파일이 이미 배선된 상태라 .pleum.bak에서, 기록이 없으면 현재 파일에서 뽑는다(배선된 파일이면 .pleum.bak).
|
|
892
|
+
function originalValues(tool, paths, state, text) {
|
|
893
|
+
if (state[tool]?.previous) return state[tool].previous;
|
|
894
|
+
const fromText = state[tool] ? null : pickPrevious(tool, text);
|
|
895
|
+
return fromText ?? pickPrevious(tool, readText(paths[`${tool}Bak`])) ?? {};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// 셸 rc 래퍼 없이 설정 파일만으로 배선되는 도구. 나머지는 zsh/bash rc가 필요해 Windows에서 효과가 없다.
|
|
899
|
+
const FILE_WIRED_TOOLS = new Set(["claude", "codex", "opencode", "aider", "crush", "pi"]);
|
|
900
|
+
|
|
901
|
+
export function applyToolWiring(tool, paths, { key, model, models = [], billingOnly = false, activeContext, platform = process.platform } = {}) {
|
|
902
|
+
if (platform === "win32" && !FILE_WIRED_TOOLS.has(tool)) {
|
|
903
|
+
throw new Error(t("{tool}: wire needs a zsh/bash shell and is not supported on Windows. Use `pleum launch {tool}` instead.", { tool }));
|
|
904
|
+
}
|
|
811
905
|
const state = readWiredState(paths);
|
|
812
906
|
const mode = billingOnly ? "billing" : (models.length > 0 ? "list" : (model ? "custom" : "billing"));
|
|
907
|
+
let previous; // claude·agy가 덮어쓰는 사용자 원래 값 — unwire가 복원한다
|
|
813
908
|
|
|
814
909
|
switch (tool) {
|
|
815
910
|
case "claude": {
|
|
816
911
|
// 파싱이 끝나야 백업·쓰기로 간다 — 깨진 파일이면 .pleum.bak도 남기지 않는다.
|
|
817
|
-
const next = transformConfig(paths.claude, (text) =>
|
|
912
|
+
const next = transformConfig(paths.claude, (text) => {
|
|
913
|
+
previous = originalValues(tool, paths, state, text);
|
|
914
|
+
return wireClaudeSettings(text, { root: ROOT, key, model: billingOnly ? undefined : model });
|
|
915
|
+
});
|
|
818
916
|
backupOnce(paths.claude, paths.claudeBak);
|
|
819
917
|
writeSecretFile(paths.claude, next);
|
|
820
918
|
console.log(t("Wrote {path}", { path: paths.claude }));
|
|
@@ -858,7 +956,13 @@ export function applyToolWiring(tool, paths, { key, model, models = [], billingO
|
|
|
858
956
|
break;
|
|
859
957
|
}
|
|
860
958
|
case "agy": {
|
|
861
|
-
const
|
|
959
|
+
const writes = billingOnly ? undefined : model;
|
|
960
|
+
const nextAgy = transformConfig(paths.agy, (text) => {
|
|
961
|
+
previous = originalValues(tool, paths, state, text);
|
|
962
|
+
// model 없이 재배선하면 이전 wire가 쓴 pleum model을 걷어낸다(파일이 마지막 wire 기록과 어긋나지 않게).
|
|
963
|
+
const stale = agyWiredModel(state.agy);
|
|
964
|
+
return wireAgySettings(!writes && stale ? unwireAgySettings(text, previous, stale) : text, { model: writes });
|
|
965
|
+
});
|
|
862
966
|
backupOnce(paths.agy, paths.agyBak);
|
|
863
967
|
writeSecretFile(paths.agy, nextAgy);
|
|
864
968
|
wireShellTool(paths, "agy", {
|
|
@@ -889,17 +993,25 @@ export function applyToolWiring(tool, paths, { key, model, models = [], billingO
|
|
|
889
993
|
models,
|
|
890
994
|
workspace_id: activeContext?.workspace_id,
|
|
891
995
|
workspace_name: activeContext?.workspace_name,
|
|
996
|
+
...(previous && { previous }),
|
|
892
997
|
};
|
|
893
998
|
writeWiredState(paths, state);
|
|
894
999
|
}
|
|
895
1000
|
|
|
896
1001
|
export function revertToolWiring(tool, paths) {
|
|
897
1002
|
const state = readWiredState(paths);
|
|
1003
|
+
// 기록도 없고 pleum이 배선한 흔적도 없으면 사용자 본인 설정이다 — 한 글자도 쓰지 않는다.
|
|
1004
|
+
if (!state[tool] && !wireStatusOf(paths)[tool]?.wired) {
|
|
1005
|
+
console.log(t("{tool} is not wired by PleumRouter; left unchanged.", { tool }));
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
898
1008
|
|
|
899
1009
|
switch (tool) {
|
|
900
1010
|
case "claude": {
|
|
901
1011
|
if (existsSync(paths.claude)) {
|
|
902
|
-
|
|
1012
|
+
// 기록이 없으면(0.5.45~46) .pleum.bak에서 원래 값을 뽑는다.
|
|
1013
|
+
const previous = state.claude?.previous ?? pickPrevious("claude", readText(paths.claudeBak)) ?? {};
|
|
1014
|
+
writeSecretFile(paths.claude, transformConfig(paths.claude, (text) => unwireClaudeSettings(text, previous)));
|
|
903
1015
|
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.claude }));
|
|
904
1016
|
}
|
|
905
1017
|
break;
|
|
@@ -943,7 +1055,8 @@ export function revertToolWiring(tool, paths) {
|
|
|
943
1055
|
}
|
|
944
1056
|
case "agy": {
|
|
945
1057
|
if (existsSync(paths.agy)) {
|
|
946
|
-
|
|
1058
|
+
const previous = state.agy?.previous ?? pickPrevious("agy", readText(paths.agyBak)) ?? {};
|
|
1059
|
+
writeSecretFile(paths.agy, transformConfig(paths.agy, (text) => unwireAgySettings(text, previous, agyWiredModel(state.agy))));
|
|
947
1060
|
}
|
|
948
1061
|
unwireShellTool(paths, "agy");
|
|
949
1062
|
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.agy }));
|
|
@@ -1009,9 +1122,19 @@ export async function wire(args = [], {
|
|
|
1009
1122
|
if (failures.length) throw new Error(failures.join("\n"));
|
|
1010
1123
|
}
|
|
1011
1124
|
|
|
1125
|
+
// 배선된 도구: 감지된 것 ∪ 배선 기록(state)에 있는 것(WIRE_TOOLS 순).
|
|
1126
|
+
function wiredTools(paths) {
|
|
1127
|
+
const state = readWiredState(paths);
|
|
1128
|
+
const status = wireStatusOf(paths);
|
|
1129
|
+
return WIRE_TOOLS.filter((tool) => state[tool] || status[tool]?.wired);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1012
1132
|
export async function unwire(args = [], { auth = authFetch, home = homedir() } = {}) {
|
|
1013
|
-
const { tools, revoke } = parseWireArgs(args);
|
|
1133
|
+
const { tools: named, revoke } = parseWireArgs(args);
|
|
1014
1134
|
const paths = wirePaths(home);
|
|
1135
|
+
// 도구를 안 적으면 wire 기본값(claude·codex)이 아니라 배선된 도구 전부 — 키를 같이 쓰기 때문이다.
|
|
1136
|
+
const tools = args.some((a) => WIRE_TOOLS.includes(a)) ? named : wiredTools(paths);
|
|
1137
|
+
if (tools.length === 0) console.log(t("No tools are wired by PleumRouter."));
|
|
1015
1138
|
|
|
1016
1139
|
const failures = [];
|
|
1017
1140
|
for (const tool of tools) {
|
|
@@ -1026,6 +1149,12 @@ export async function unwire(args = [], { auth = authFetch, home = homedir() } =
|
|
|
1026
1149
|
|
|
1027
1150
|
if (!revoke) return;
|
|
1028
1151
|
|
|
1152
|
+
// 키는 모든 배선 도구가 공유한다 — 다른 도구가 남아 있으면 폐기하지 않는다(그 도구들이 죽은 키로 실패한다).
|
|
1153
|
+
const others = wiredTools(paths).filter((tool) => !tools.includes(tool));
|
|
1154
|
+
if (others.length) {
|
|
1155
|
+
throw new Error(t("Other tools still use this key: {tools}. Run `pleum unwire --revoke-key` to unwire all of them first.", { tools: others.join(", ") }));
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1029
1158
|
const stored = (() => {
|
|
1030
1159
|
try {
|
|
1031
1160
|
return JSON.parse(readText(paths.wire) ?? "");
|