pleumrouter 0.5.36 → 0.5.39
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 +6 -4
- package/bin/pleum.mjs +5 -2
- package/package.json +1 -1
- package/src/config.mjs +11 -14
- package/src/epipe.mjs +10 -0
- package/src/install.mjs +65 -0
- package/src/launch.mjs +125 -9
- package/src/locales/es.mjs +17 -55
- package/src/locales/ja.mjs +17 -55
- package/src/locales/ko.mjs +17 -59
- package/src/locales/zh.mjs +17 -55
- package/src/login.mjs +24 -12
- package/src/menu.mjs +80 -167
- package/src/models.mjs +10 -134
- package/src/recipes.mjs +40 -42
- package/src/settings.mjs +0 -1
- package/src/update.mjs +8 -3
- package/src/wire.mjs +13 -3
- package/src/upgrade.mjs +0 -10
package/src/recipes.mjs
CHANGED
|
@@ -34,15 +34,10 @@ export const RECIPES = {
|
|
|
34
34
|
label: "Claude Code",
|
|
35
35
|
bin: "claude",
|
|
36
36
|
type: "env",
|
|
37
|
-
unsetEnv: ["ANTHROPIC_API_KEY"],
|
|
38
|
-
resolveModel: (explicit
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
sonnet: explicit || cfg?.launch_models?.claude || cfg?.claude_tiers?.sonnet || "claude-sonnet-5",
|
|
42
|
-
opus: cfg?.claude_tiers?.opus || "claude-opus-5-5",
|
|
43
|
-
haiku: cfg?.claude_tiers?.haiku || "claude-haiku-4-5",
|
|
44
|
-
};
|
|
45
|
-
},
|
|
37
|
+
unsetEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_CUSTOM_MODEL_OPTION", "ANTHROPIC_CUSTOM_MODEL_OPTION_NAME", "ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION"],
|
|
38
|
+
resolveModel: (explicit) => explicit && typeof explicit === "object"
|
|
39
|
+
? explicit
|
|
40
|
+
: explicit ? { mode: "custom", model: explicit } : { mode: "native" },
|
|
46
41
|
install: "npm i -g @anthropic-ai/claude-code",
|
|
47
42
|
// 앱 랭킹 귀속 메타 — 백엔드 featured_agents / UA fallback과 동기화.
|
|
48
43
|
attribution: {
|
|
@@ -51,11 +46,7 @@ export const RECIPES = {
|
|
|
51
46
|
},
|
|
52
47
|
// 함정: ANTHROPIC_BASE_URL은 반드시 루트(/v1 없이) — CLI가 /v1/messages를 스스로 덧붙인다.
|
|
53
48
|
// /v1을 넣으면 /v1/v1/messages가 되어 실패.
|
|
54
|
-
env: (key,
|
|
55
|
-
const isObj = m && typeof m === "object";
|
|
56
|
-
const sonnet = (isObj ? m.sonnet : m) || "claude-sonnet-5";
|
|
57
|
-
const opus = (isObj ? m.opus : null) || "claude-opus-5-5";
|
|
58
|
-
const haiku = (isObj ? m.haiku : null) || "claude-haiku-4-5";
|
|
49
|
+
env: (key, selection) => {
|
|
59
50
|
// Claude 모델의 provider-qualified ID만 보정한다. 다른 회사 모델에
|
|
60
51
|
// anthropic/을 붙이면 카탈로그에 없는 ID가 되어 요청이 실패한다.
|
|
61
52
|
const toAnthropic = (x) => /^claude-/.test(x) ? `anthropic/${x}` : x;
|
|
@@ -65,30 +56,40 @@ export const RECIPES = {
|
|
|
65
56
|
// CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY는 의도적으로 설정하지 않는다:
|
|
66
57
|
// 활성화 시 Claude Code가 GET /v1/models를 호출해 카탈로그 200+개 모델을
|
|
67
58
|
// /model 메뉴에 쏟아부어 화면이 난잡해지는 원인이 된다.
|
|
68
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: toAnthropic(sonnet),
|
|
59
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: toAnthropic("claude-sonnet-5"),
|
|
69
60
|
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME: "Sonnet",
|
|
70
61
|
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION: "Sonnet 5 · Efficient for routine tasks",
|
|
71
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: toAnthropic(opus),
|
|
62
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: toAnthropic("claude-opus-5-5"),
|
|
72
63
|
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME: "Opus",
|
|
73
64
|
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION: "Opus 5.5 · Best for everyday, complex tasks",
|
|
74
|
-
|
|
65
|
+
ANTHROPIC_DEFAULT_FABLE_MODEL: toAnthropic("claude-fable-5-1"),
|
|
66
|
+
ANTHROPIC_DEFAULT_FABLE_MODEL_NAME: "Fable",
|
|
67
|
+
ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION: "Fable 5.1 · Long-running tasks",
|
|
68
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: toAnthropic("claude-haiku-4-5"),
|
|
75
69
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME: "Haiku",
|
|
76
70
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION: "Haiku 4.5 · Fastest for quick answers",
|
|
77
|
-
ANTHROPIC_SMALL_MODEL: toAnthropic(haiku),
|
|
71
|
+
ANTHROPIC_SMALL_MODEL: toAnthropic("claude-haiku-4-5"),
|
|
72
|
+
...(selection?.mode === "custom" ? {
|
|
73
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: toAnthropic(selection.model),
|
|
74
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: `Pleum · ${selection.model}`,
|
|
75
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION: "Selected in Pleum for this session",
|
|
76
|
+
CLAUDE_CODE_SUBAGENT_MODEL: "sonnet",
|
|
77
|
+
CLAUDE_CODE_SUBAGENT_MODEL_FORCE: "1",
|
|
78
|
+
} : {}),
|
|
78
79
|
};
|
|
79
80
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
args: (selection) => ["--model", selection?.mode === "custom"
|
|
82
|
+
? (/^claude-/.test(selection.model) ? `anthropic/${selection.model}` : selection.model)
|
|
83
|
+
: "sonnet"],
|
|
83
84
|
},
|
|
84
85
|
codex: {
|
|
85
86
|
label: "Codex CLI",
|
|
86
87
|
bin: "codex",
|
|
87
88
|
type: "env",
|
|
88
89
|
defaultModel: "gpt-6-sol",
|
|
89
|
-
resolveModel: (explicit
|
|
90
|
+
resolveModel: (explicit) => explicit && typeof explicit === "object"
|
|
90
91
|
? explicit
|
|
91
|
-
: {
|
|
92
|
+
: { mode: explicit ? "custom" : "native", model: explicit || "gpt-6-sol" },
|
|
92
93
|
install: "npm i -g @openai/codex",
|
|
93
94
|
attribution: {
|
|
94
95
|
title: "Codex",
|
|
@@ -97,7 +98,6 @@ export const RECIPES = {
|
|
|
97
98
|
env: (key) => ({ PLEUM_API_KEY: key }),
|
|
98
99
|
args: (model) => {
|
|
99
100
|
const m = model && typeof model === "object" ? model.model : model;
|
|
100
|
-
const effort = model && typeof model === "object" ? model.effort : null;
|
|
101
101
|
const provider = {
|
|
102
102
|
name: "PleumRouter",
|
|
103
103
|
base_url: V1,
|
|
@@ -110,7 +110,6 @@ export const RECIPES = {
|
|
|
110
110
|
"-c", 'model_provider="pleum"',
|
|
111
111
|
"-c", `model_providers.pleum=${providerToml}`,
|
|
112
112
|
"--model", m || "gpt-6-sol",
|
|
113
|
-
...(effort ? ["-c", `model_reasoning_effort=${tomlBasicString(effort)}`] : []),
|
|
114
113
|
];
|
|
115
114
|
},
|
|
116
115
|
},
|
|
@@ -118,12 +117,11 @@ export const RECIPES = {
|
|
|
118
117
|
label: "Antigravity CLI",
|
|
119
118
|
bin: "agy",
|
|
120
119
|
type: "agy-settings",
|
|
121
|
-
|
|
122
|
-
resolveModel: (explicit, cfg) => explicit && typeof explicit === "object"
|
|
120
|
+
resolveModel: (explicit) => explicit && typeof explicit === "object"
|
|
123
121
|
? explicit
|
|
124
|
-
:
|
|
122
|
+
: explicit ? { mode: "custom", model: explicit } : { mode: "native" },
|
|
125
123
|
install: "https://antigravity.google/cli/install.sh",
|
|
126
|
-
note: "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json.
|
|
124
|
+
note: "Writes modelProvider=gemini into ~/.gemini/antigravity-cli/settings.json. Supported Gemini models route via PleumRouter.",
|
|
127
125
|
attribution: {
|
|
128
126
|
title: "Antigravity CLI",
|
|
129
127
|
referer: "https://www.antigravity.google/docs/cli/install",
|
|
@@ -134,14 +132,9 @@ export const RECIPES = {
|
|
|
134
132
|
GOOGLE_API_KEY: key,
|
|
135
133
|
}),
|
|
136
134
|
args: (m) => {
|
|
137
|
-
|
|
138
|
-
const model =
|
|
139
|
-
|
|
140
|
-
// agy의 --model은 내장 Gemini 모델만 받는다. 다른 카탈로그 ID는
|
|
141
|
-
// settings.json model로 전달하고 CLI 인자에는 싣지 않는다.
|
|
142
|
-
return /^(?:google\/)?gemini-/.test(model)
|
|
143
|
-
? ["--model", model.replace(/^google\//, ""), "--effort", effort || "high"]
|
|
144
|
-
: [];
|
|
135
|
+
if (!m || m.mode === "native") return [];
|
|
136
|
+
const model = typeof m === "object" ? (m.cliModel || m.model) : m;
|
|
137
|
+
return ["--model", model.replace(/^google\//, "")];
|
|
145
138
|
},
|
|
146
139
|
},
|
|
147
140
|
opencode: {
|
|
@@ -159,19 +152,22 @@ export const RECIPES = {
|
|
|
159
152
|
title: "opencode",
|
|
160
153
|
referer: "https://opencode.ai",
|
|
161
154
|
},
|
|
162
|
-
env: (key, model) => {
|
|
155
|
+
env: (key, model, catalog = []) => {
|
|
163
156
|
const active = model || "gpt-6-sol";
|
|
164
|
-
const models = Object.fromEntries(
|
|
157
|
+
const models = Object.fromEntries(catalog.map(({ id }) => [id, {}]));
|
|
165
158
|
models[active] = {};
|
|
166
159
|
return {
|
|
167
160
|
PLEUM_API_KEY: key,
|
|
168
161
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
162
|
+
enabled_providers: ["openai"],
|
|
163
|
+
disabled_providers: [],
|
|
169
164
|
provider: {
|
|
170
165
|
openai: {
|
|
171
166
|
npm: "@ai-sdk/openai",
|
|
172
167
|
name: "PleumRouter",
|
|
173
168
|
options: { baseURL: V1, apiKey: "{env:PLEUM_API_KEY}" },
|
|
174
169
|
models,
|
|
170
|
+
whitelist: Object.keys(models),
|
|
175
171
|
},
|
|
176
172
|
},
|
|
177
173
|
}),
|
|
@@ -469,9 +465,11 @@ export function mergeAgySettings(existingJson, model) {
|
|
|
469
465
|
throw new Error("agy settings.json must be a JSON object");
|
|
470
466
|
}
|
|
471
467
|
}
|
|
472
|
-
const
|
|
473
|
-
const
|
|
474
|
-
|
|
468
|
+
const m = typeof model === "object" ? model?.cliModel || model?.model : model;
|
|
469
|
+
const settings = { ...obj, modelProvider: "gemini" };
|
|
470
|
+
if (m) settings.model = m;
|
|
471
|
+
else delete settings.model;
|
|
472
|
+
return settings;
|
|
475
473
|
}
|
|
476
474
|
|
|
477
475
|
export function piModelsJson(model) {
|
package/src/settings.mjs
CHANGED
|
@@ -13,7 +13,6 @@ import { getLocale, LANGS, t } from "./i18n.mjs";
|
|
|
13
13
|
import { RECIPES } from "./recipes.mjs";
|
|
14
14
|
import { fetchModels, matchModels, formatModelLabel } from "./models.mjs";
|
|
15
15
|
import { login, logout } from "./login.mjs";
|
|
16
|
-
import { upgrade } from "./upgrade.mjs";
|
|
17
16
|
import { completion } from "./completion.mjs";
|
|
18
17
|
import { lang } from "./lang.mjs";
|
|
19
18
|
import { switchCreditWorkspace } from "./balance.mjs";
|
package/src/update.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { t } from "./i18n.mjs";
|
|
6
|
+
import { resolveLaunchCommand } from "./launch.mjs";
|
|
6
7
|
|
|
7
8
|
const REGISTRY_URL = "https://registry.npmjs.org/pleumrouter/latest";
|
|
8
9
|
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1시간 캐시
|
|
@@ -112,10 +113,14 @@ export async function checkLatestVersion({
|
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
export function performUpdate({ bin = null } = {}) {
|
|
116
|
-
|
|
116
|
+
export function performUpdate({ bin = null, resolve = resolveLaunchCommand, spawn: spawnImpl = spawn } = {}) {
|
|
117
|
+
// Windows에서 npm.cmd 를 shell 없이 spawn 하면 EINVAL(Node 18.20+) — 공용
|
|
118
|
+
// 경로로 node 진입점을 찾아 우회한다(#10). 비윈도는 명령을 그대로 둔다.
|
|
119
|
+
const target = bin
|
|
120
|
+
? { command: bin, args: ["install", "-g", "pleumrouter@latest"] }
|
|
121
|
+
: resolve("npm", ["install", "-g", "pleumrouter@latest"]);
|
|
117
122
|
return new Promise((resolve) => {
|
|
118
|
-
const child =
|
|
123
|
+
const child = spawnImpl(target.command, target.args, {
|
|
119
124
|
stdio: "inherit",
|
|
120
125
|
shell: false,
|
|
121
126
|
});
|
package/src/wire.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { ROOT, V1, authFetch } from "./config.mjs";
|
|
5
5
|
import { tomlBasicString } from "./recipes.mjs";
|
|
6
6
|
import { t } from "./i18n.mjs";
|
|
@@ -40,9 +40,19 @@ function readText(path) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function writeSecretFile(path, contents, mode = 0o600) {
|
|
43
|
+
export function writeSecretFile(path, contents, mode = 0o600) {
|
|
44
44
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
45
|
-
|
|
45
|
+
// 기존 파일이 0644 등으로 있으면 writeFileSync의 mode는 무시된다(#41) —
|
|
46
|
+
// tmp(강제 chmod) + rename 원자적 교체로 결과물이 항상 mode가 되게 한다.
|
|
47
|
+
const tmp = `${path}.pleum-tmp-${process.pid}`;
|
|
48
|
+
writeFileSync(tmp, contents, { mode });
|
|
49
|
+
try {
|
|
50
|
+
chmodSync(tmp, mode);
|
|
51
|
+
renameSync(tmp, path);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
try { unlinkSync(tmp); } catch {} // 키 평문 잔여를 남기지 않는다
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
function backupOnce(src, bak) {
|
package/src/upgrade.mjs
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { performUpdate, runUpdateCommand } from "./update.mjs";
|
|
2
|
-
|
|
3
|
-
export { performUpdate, runUpdateCommand };
|
|
4
|
-
|
|
5
|
-
// 최신 버전으로 전역 재설치. 하위 호환을 위해 유지하며 process.exit() 호출 규약을 보존한다.
|
|
6
|
-
export async function upgrade() {
|
|
7
|
-
const code = await runUpdateCommand(["--force"], { exit: false });
|
|
8
|
-
process.exit(code ?? 0);
|
|
9
|
-
}
|
|
10
|
-
|