pleumrouter 0.5.25 → 0.5.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pleum.mjs +6 -1
- package/package.json +1 -1
- package/src/interactive.mjs +2 -1
- package/src/launch.mjs +3 -3
- package/src/menu.mjs +53 -17
- package/src/recipes.mjs +53 -33
package/bin/pleum.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { top } from "../src/top.mjs";
|
|
|
11
11
|
import { health } from "../src/health.mjs";
|
|
12
12
|
import { completion } from "../src/completion.mjs";
|
|
13
13
|
import { upgrade } from "../src/upgrade.mjs";
|
|
14
|
-
import { mainMenu } from "../src/menu.mjs";
|
|
14
|
+
import { mainMenu, runAgentMenu } from "../src/menu.mjs";
|
|
15
15
|
import { lang } from "../src/lang.mjs";
|
|
16
16
|
import { RECIPES } from "../src/recipes.mjs";
|
|
17
17
|
import { t } from "../src/i18n.mjs";
|
|
@@ -98,6 +98,11 @@ try {
|
|
|
98
98
|
case "launch": {
|
|
99
99
|
const tool = rest[0];
|
|
100
100
|
if (!tool) {
|
|
101
|
+
const io = process.stdin;
|
|
102
|
+
if (io.isTTY && process.stdout.isTTY && typeof io.setRawMode === "function") {
|
|
103
|
+
await runAgentMenu({ loop: false });
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
101
106
|
launchHelp();
|
|
102
107
|
break;
|
|
103
108
|
}
|
package/package.json
CHANGED
package/src/interactive.mjs
CHANGED
|
@@ -100,6 +100,7 @@ export async function select(items, {
|
|
|
100
100
|
formatItem = String,
|
|
101
101
|
matches = (all, q) => (q ? all.filter((it) => String(formatItem(it)).toLowerCase().includes(q.toLowerCase())) : all),
|
|
102
102
|
limit = 10,
|
|
103
|
+
initialCursor = 0,
|
|
103
104
|
input = process.stdin,
|
|
104
105
|
output = process.stdout,
|
|
105
106
|
} = {}) {
|
|
@@ -107,7 +108,7 @@ export async function select(items, {
|
|
|
107
108
|
|
|
108
109
|
return new Promise((resolve) => {
|
|
109
110
|
let query = "";
|
|
110
|
-
let cursor = 0;
|
|
111
|
+
let cursor = Math.max(0, Math.min(initialCursor, Math.max(0, (items?.length ?? 1) - 1)));
|
|
111
112
|
let lastRows = 0;
|
|
112
113
|
|
|
113
114
|
const rowsFor = (s) => Math.max(1, Math.ceil(displayWidth(s) / Math.max(terminalWidth(), 1)));
|
package/src/launch.mjs
CHANGED
|
@@ -126,7 +126,7 @@ export async function launch(tool, model, passthrough, { exit = true } = {}) {
|
|
|
126
126
|
if (r.type === "crush-home") {
|
|
127
127
|
env.CRUSH_GLOBAL_CONFIG = writeIsolatedHome("crush-home", "crush.json", crushJson(model));
|
|
128
128
|
}
|
|
129
|
-
if (r.type === "agy-settings") ensureAgySettings();
|
|
129
|
+
if (r.type === "agy-settings") ensureAgySettings(model);
|
|
130
130
|
|
|
131
131
|
const args = [...r.args(model), ...passthrough];
|
|
132
132
|
if (r.note) console.log(`ℹ ${t(r.note)}`);
|
|
@@ -171,12 +171,12 @@ function writeIsolatedHome(name, filename, contents) {
|
|
|
171
171
|
return home;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
function ensureAgySettings() {
|
|
174
|
+
function ensureAgySettings(model) {
|
|
175
175
|
const dir = join(homedir(), ".gemini", "antigravity-cli");
|
|
176
176
|
const path = join(dir, "settings.json");
|
|
177
177
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
178
178
|
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
179
|
-
writeFileSync(path, `${JSON.stringify(mergeAgySettings(existing), null, 2)}\n`, { mode: 0o600 });
|
|
179
|
+
writeFileSync(path, `${JSON.stringify(mergeAgySettings(existing, model), null, 2)}\n`, { mode: 0o600 });
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
function printManual(tool, key) {
|
package/src/menu.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { createInterface } from "node:readline";
|
|
|
14
14
|
import { style, displayWidth, terminalWidth, hyperlink, sanitizeTerminalText } from "./ansi.mjs";
|
|
15
15
|
import { keyFeeder, select } from "./interactive.mjs";
|
|
16
16
|
import { readConfig, writeConfig, isLoggedIn, touchSession } from "./config.mjs";
|
|
17
|
-
import { RECIPES, MANUAL } from "./recipes.mjs";
|
|
17
|
+
import { RECIPES, MANUAL, DEFAULT_MODEL } from "./recipes.mjs";
|
|
18
18
|
import { onPath, launch } from "./launch.mjs";
|
|
19
19
|
import { fetchModels, matchModels, listModels, formatModelLabel } from "./models.mjs";
|
|
20
20
|
import { run } from "./run.mjs";
|
|
@@ -120,7 +120,7 @@ function saveLastSelection(id) {
|
|
|
120
120
|
writeConfig(cfg);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
async function pickModelFor(item) {
|
|
123
|
+
export async function pickModelFor(item, defaultModel) {
|
|
124
124
|
let models;
|
|
125
125
|
try {
|
|
126
126
|
models = await fetchModels();
|
|
@@ -128,11 +128,14 @@ async function pickModelFor(item) {
|
|
|
128
128
|
console.log(`${style.red("✗")} ${t("Failed to fetch model list: {error}", { error: sanitizeTerminalText(e.message) })}`);
|
|
129
129
|
return undefined;
|
|
130
130
|
}
|
|
131
|
+
const targetId = defaultModel || DEFAULT_MODEL;
|
|
132
|
+
const initialIndex = models.findIndex((x) => x.id === targetId);
|
|
131
133
|
const m = await select(models, {
|
|
132
134
|
message: `${item.title} — ${t("select a model")}`,
|
|
133
135
|
formatItem: (x) => formatModelLabel(x),
|
|
134
136
|
matches: matchModels,
|
|
135
|
-
limit:
|
|
137
|
+
limit: 10,
|
|
138
|
+
initialCursor: initialIndex >= 0 ? initialIndex : 0,
|
|
136
139
|
});
|
|
137
140
|
return m?.id;
|
|
138
141
|
}
|
|
@@ -229,7 +232,8 @@ export async function runList(items, { header, footer, initialCursor = 0 } = {})
|
|
|
229
232
|
// 선택되면 ollama처럼 즉시 실행으로 이어가고, 취소면 메뉴 복귀.
|
|
230
233
|
const configure = async (it) => {
|
|
231
234
|
suspend();
|
|
232
|
-
const
|
|
235
|
+
const currentModel = it.tool ? (readConfig()?.launch_models?.[it.tool] || DEFAULT_MODEL) : undefined;
|
|
236
|
+
const picked = await pickModelFor(it, currentModel);
|
|
233
237
|
if (picked) {
|
|
234
238
|
input.pause();
|
|
235
239
|
resolve({ item: it, model: picked });
|
|
@@ -292,6 +296,48 @@ async function agentSelect() {
|
|
|
292
296
|
});
|
|
293
297
|
}
|
|
294
298
|
|
|
299
|
+
// 에이전트 실행 메뉴 — bare pleum의 "에이전트 실행"과 pleum launch (인자 없음)가 공유.
|
|
300
|
+
// 에이전트를 고르면 곧바로 핵심 10개 모델 피커를 띄워 기본 모델(deepseek-v4.1-flash) 또는
|
|
301
|
+
// 선택한 우리 모델로 코딩 에이전트를 실행한다.
|
|
302
|
+
export async function runAgentMenu({ loop = false } = {}) {
|
|
303
|
+
const io = process.stdin;
|
|
304
|
+
if (!io.isTTY || !process.stdout.isTTY || typeof io.setRawMode !== "function") return false;
|
|
305
|
+
if (!isLoggedIn()) await login();
|
|
306
|
+
else await touchSession().catch(() => {});
|
|
307
|
+
|
|
308
|
+
for (;;) {
|
|
309
|
+
const agentPicked = await agentSelect();
|
|
310
|
+
if (agentPicked === "quit") return "quit";
|
|
311
|
+
if (!agentPicked) return null;
|
|
312
|
+
|
|
313
|
+
const { item: agentItem } = agentPicked;
|
|
314
|
+
if (agentItem.manual) {
|
|
315
|
+
await launch(agentItem.tool, undefined, [], { exit: false });
|
|
316
|
+
if (!loop) return true;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const r = RECIPES[agentItem.tool];
|
|
321
|
+
if (!onPath(r.bin) && !(await ensureInstalled(r))) {
|
|
322
|
+
if (!loop) return false;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const agentSaved = readConfig()?.launch_models ?? {};
|
|
327
|
+
let chosenModel = agentPicked.model;
|
|
328
|
+
if (!chosenModel) {
|
|
329
|
+
const defaultForTool = agentSaved[agentItem.tool] || DEFAULT_MODEL;
|
|
330
|
+
chosenModel = await pickModelFor(agentItem, defaultForTool);
|
|
331
|
+
if (!chosenModel) continue; // Esc 취소 시 에이전트 목록으로 복귀
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
saveModel(agentItem.tool, chosenModel);
|
|
335
|
+
await launch(agentItem.tool, chosenModel, [], { exit: false });
|
|
336
|
+
console.log();
|
|
337
|
+
if (!loop) return true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
295
341
|
// bare `pleum` 진입점. 비TTY면 false(호출자가 HELP 출력), 그 외 true.
|
|
296
342
|
// ollama처럼 실행이 끝나면 메뉴로 복귀하는 루프(REPL 채팅·ctrl+c 종료 제외).
|
|
297
343
|
export async function mainMenu() {
|
|
@@ -342,19 +388,9 @@ export async function mainMenu() {
|
|
|
342
388
|
continue; // esc/q — 메인 메뉴로 복귀
|
|
343
389
|
}
|
|
344
390
|
case "agents": {
|
|
345
|
-
const
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
const { item: agentItem, model: agentModel } = agentPicked;
|
|
349
|
-
if (agentModel && !agentItem.manual) saveModel(agentItem.tool, agentModel);
|
|
350
|
-
if (!agentItem.manual) {
|
|
351
|
-
const r = RECIPES[agentItem.tool];
|
|
352
|
-
if (!onPath(r.bin) && !(await ensureInstalled(r))) continue;
|
|
353
|
-
}
|
|
354
|
-
const agentSaved = readConfig()?.launch_models ?? {};
|
|
355
|
-
await launch(agentItem.tool, agentModel ?? agentSaved[agentItem.tool], [], { exit: false });
|
|
356
|
-
console.log(); // 에이전트 출력과 다음 메뉴 사이 여백
|
|
357
|
-
continue; // ollama처럼 에이전트 종료 후 메뉴 복귀
|
|
391
|
+
const r = await runAgentMenu({ loop: true });
|
|
392
|
+
if (r === "quit") return true;
|
|
393
|
+
continue;
|
|
358
394
|
}
|
|
359
395
|
}
|
|
360
396
|
}
|
package/src/recipes.mjs
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { ROOT, V1 } from "./config.mjs";
|
|
2
2
|
|
|
3
|
+
export const DEFAULT_MODEL = "deepseek-ai/DeepSeek-V4.1-Flash";
|
|
4
|
+
|
|
5
|
+
export const CORE_MODELS = [
|
|
6
|
+
"deepseek-ai/DeepSeek-V4.1-Flash",
|
|
7
|
+
"claude-sonnet-5",
|
|
8
|
+
"claude-opus-5-5",
|
|
9
|
+
"gpt-6-astra",
|
|
10
|
+
"claude-fable-5-1",
|
|
11
|
+
"gemini-3.8-flash",
|
|
12
|
+
"glm-5.3-flash",
|
|
13
|
+
"kimi-k3",
|
|
14
|
+
"gpt-6-sol",
|
|
15
|
+
"gpt-6-luna",
|
|
16
|
+
];
|
|
17
|
+
|
|
3
18
|
// 도구별 설정의 진실 원천. 출처: frontend .../docs/cookbook/{codex,claude-code,...} + 에이전트별 공식 문서.
|
|
4
19
|
// type "env" → 환경변수만 세팅하고 바로 실행.
|
|
5
20
|
// type "codex-home" → 격리 CODEX_HOME에 config.toml (사용자 ~/.codex 보존).
|
|
@@ -34,7 +49,7 @@ export const RECIPES = {
|
|
|
34
49
|
// /model 이 GET {ANTHROPIC_BASE_URL}/v1/models 를 치게 한다. 기본값은 꺼져 있다.
|
|
35
50
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
|
|
36
51
|
}),
|
|
37
|
-
args: (m) =>
|
|
52
|
+
args: (m) => ["--model", (m || DEFAULT_MODEL).startsWith("anthropic/") ? (m || DEFAULT_MODEL) : `anthropic/${m || DEFAULT_MODEL}`],
|
|
38
53
|
},
|
|
39
54
|
codex: {
|
|
40
55
|
label: "Codex CLI",
|
|
@@ -58,7 +73,7 @@ export const RECIPES = {
|
|
|
58
73
|
referer: "https://aider.chat",
|
|
59
74
|
},
|
|
60
75
|
env: (key) => ({ OPENAI_API_BASE: V1, OPENAI_API_KEY: key }),
|
|
61
|
-
args: (m) =>
|
|
76
|
+
args: (m) => ["--model", (m || DEFAULT_MODEL).startsWith("openai/") ? (m || DEFAULT_MODEL) : `openai/${m || DEFAULT_MODEL}`],
|
|
62
77
|
},
|
|
63
78
|
goose: {
|
|
64
79
|
label: "Goose",
|
|
@@ -98,20 +113,25 @@ export const RECIPES = {
|
|
|
98
113
|
title: "opencode",
|
|
99
114
|
referer: "https://opencode.ai",
|
|
100
115
|
},
|
|
101
|
-
env: (key, model) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
env: (key, model) => {
|
|
117
|
+
const active = model || DEFAULT_MODEL;
|
|
118
|
+
const models = Object.fromEntries(CORE_MODELS.map((id) => [id, {}]));
|
|
119
|
+
models[active] = {};
|
|
120
|
+
return {
|
|
121
|
+
PLEUM_API_KEY: key,
|
|
122
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
123
|
+
provider: {
|
|
124
|
+
pleum: {
|
|
125
|
+
npm: "@ai-sdk/openai-compatible",
|
|
126
|
+
name: "PleumRouter",
|
|
127
|
+
options: { baseURL: V1, apiKey: "{env:PLEUM_API_KEY}" },
|
|
128
|
+
models,
|
|
129
|
+
},
|
|
110
130
|
},
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
args: (m) => ["--model", `pleum/${m ||
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
args: (m) => ["--model", `pleum/${m || DEFAULT_MODEL}`],
|
|
115
135
|
},
|
|
116
136
|
omp: {
|
|
117
137
|
label: "omp (Oh My Pi)",
|
|
@@ -126,7 +146,7 @@ export const RECIPES = {
|
|
|
126
146
|
referer: "https://omp.sh",
|
|
127
147
|
},
|
|
128
148
|
env: (key) => ({ ANTHROPIC_BASE_URL: ROOT, ANTHROPIC_API_KEY: key }),
|
|
129
|
-
args: (m) =>
|
|
149
|
+
args: (m) => ["--model", (m || DEFAULT_MODEL).startsWith("anthropic/") ? (m || DEFAULT_MODEL) : `anthropic/${m || DEFAULT_MODEL}`],
|
|
130
150
|
},
|
|
131
151
|
hermes: {
|
|
132
152
|
label: "Hermes Agent",
|
|
@@ -150,7 +170,7 @@ export const RECIPES = {
|
|
|
150
170
|
title: "Qwen Code",
|
|
151
171
|
referer: "https://github.com/QwenLM/qwen-code",
|
|
152
172
|
},
|
|
153
|
-
env: (key, model) => ({ OPENAI_BASE_URL: V1, OPENAI_API_KEY: key, OPENAI_MODEL: model ||
|
|
173
|
+
env: (key, model) => ({ OPENAI_BASE_URL: V1, OPENAI_API_KEY: key, OPENAI_MODEL: model || DEFAULT_MODEL }),
|
|
154
174
|
args: () => [],
|
|
155
175
|
},
|
|
156
176
|
copilot: {
|
|
@@ -167,7 +187,7 @@ export const RECIPES = {
|
|
|
167
187
|
COPILOT_PROVIDER_BASE_URL: V1,
|
|
168
188
|
COPILOT_PROVIDER_TYPE: "openai",
|
|
169
189
|
COPILOT_PROVIDER_API_KEY: key,
|
|
170
|
-
COPILOT_MODEL: model ||
|
|
190
|
+
COPILOT_MODEL: model || DEFAULT_MODEL,
|
|
171
191
|
}),
|
|
172
192
|
args: () => [],
|
|
173
193
|
},
|
|
@@ -189,7 +209,7 @@ export const RECIPES = {
|
|
|
189
209
|
GEMINI_API_KEY: key,
|
|
190
210
|
GOOGLE_API_KEY: key,
|
|
191
211
|
}),
|
|
192
|
-
args: (m) =>
|
|
212
|
+
args: (m) => ["--model", (m || DEFAULT_MODEL).startsWith("google/") ? (m || DEFAULT_MODEL) : `google/${m || DEFAULT_MODEL}`],
|
|
193
213
|
},
|
|
194
214
|
agy: {
|
|
195
215
|
label: "Antigravity CLI",
|
|
@@ -206,7 +226,7 @@ export const RECIPES = {
|
|
|
206
226
|
GEMINI_API_KEY: key,
|
|
207
227
|
GOOGLE_API_KEY: key,
|
|
208
228
|
}),
|
|
209
|
-
args: (m) =>
|
|
229
|
+
args: (m) => ["--model", (m || DEFAULT_MODEL).startsWith("google/") ? (m || DEFAULT_MODEL) : `google/${m || DEFAULT_MODEL}`],
|
|
210
230
|
},
|
|
211
231
|
pi: {
|
|
212
232
|
label: "Pi",
|
|
@@ -219,7 +239,7 @@ export const RECIPES = {
|
|
|
219
239
|
referer: "https://pi.dev",
|
|
220
240
|
},
|
|
221
241
|
env: (key) => ({ PLEUM_API_KEY: key }),
|
|
222
|
-
args: (m) => ["--model", m ||
|
|
242
|
+
args: (m) => ["--model", m || DEFAULT_MODEL],
|
|
223
243
|
},
|
|
224
244
|
crush: {
|
|
225
245
|
label: "Crush",
|
|
@@ -246,9 +266,9 @@ export const RECIPES = {
|
|
|
246
266
|
env: (key, model) => ({
|
|
247
267
|
OPENAI_BASE_URL: V1,
|
|
248
268
|
OPENAI_API_KEY: key,
|
|
249
|
-
|
|
269
|
+
OPENAI_MODEL: model || DEFAULT_MODEL,
|
|
250
270
|
}),
|
|
251
|
-
args: (m) =>
|
|
271
|
+
args: (m) => ["--model", m || DEFAULT_MODEL],
|
|
252
272
|
},
|
|
253
273
|
mimo: {
|
|
254
274
|
label: "MiMo Code",
|
|
@@ -262,9 +282,9 @@ export const RECIPES = {
|
|
|
262
282
|
env: (key, model) => ({
|
|
263
283
|
OPENAI_BASE_URL: V1,
|
|
264
284
|
OPENAI_API_KEY: key,
|
|
265
|
-
|
|
285
|
+
OPENAI_MODEL: model || DEFAULT_MODEL,
|
|
266
286
|
}),
|
|
267
|
-
args: (m) =>
|
|
287
|
+
args: (m) => ["--model", m || DEFAULT_MODEL],
|
|
268
288
|
},
|
|
269
289
|
openclaw: {
|
|
270
290
|
label: "OpenClaw",
|
|
@@ -278,9 +298,9 @@ export const RECIPES = {
|
|
|
278
298
|
env: (key, model) => ({
|
|
279
299
|
OPENAI_BASE_URL: V1,
|
|
280
300
|
OPENAI_API_KEY: key,
|
|
281
|
-
|
|
301
|
+
OPENAI_MODEL: model || DEFAULT_MODEL,
|
|
282
302
|
}),
|
|
283
|
-
args: (m) =>
|
|
303
|
+
args: (m) => ["--model", m || DEFAULT_MODEL],
|
|
284
304
|
},
|
|
285
305
|
plandex: {
|
|
286
306
|
label: "Plandex",
|
|
@@ -310,7 +330,7 @@ export const RECIPES = {
|
|
|
310
330
|
OPENAI_API_BASE: V1,
|
|
311
331
|
OPENAI_API_KEY: key,
|
|
312
332
|
}),
|
|
313
|
-
args: (m) =>
|
|
333
|
+
args: (m) => ["--model", (m || DEFAULT_MODEL).startsWith("openai/") ? (m || DEFAULT_MODEL) : `openai/${m || DEFAULT_MODEL}`],
|
|
314
334
|
},
|
|
315
335
|
};
|
|
316
336
|
|
|
@@ -357,7 +377,7 @@ export function tomlBasicString(value) {
|
|
|
357
377
|
}
|
|
358
378
|
|
|
359
379
|
export function codexToml(model) {
|
|
360
|
-
const m = model ||
|
|
380
|
+
const m = model || DEFAULT_MODEL;
|
|
361
381
|
return `# Generated by pleum CLI. Isolated CODEX_HOME — your ~/.codex is untouched.
|
|
362
382
|
model = ${tomlBasicString(m)}
|
|
363
383
|
model_provider = "pleum"
|
|
@@ -371,7 +391,7 @@ supports_websockets = false
|
|
|
371
391
|
`;
|
|
372
392
|
}
|
|
373
393
|
|
|
374
|
-
export function mergeAgySettings(existingJson) {
|
|
394
|
+
export function mergeAgySettings(existingJson, model) {
|
|
375
395
|
let obj = {};
|
|
376
396
|
if (existingJson && String(existingJson).trim()) {
|
|
377
397
|
obj = JSON.parse(existingJson);
|
|
@@ -379,11 +399,11 @@ export function mergeAgySettings(existingJson) {
|
|
|
379
399
|
throw new Error("agy settings.json must be a JSON object");
|
|
380
400
|
}
|
|
381
401
|
}
|
|
382
|
-
return { ...obj, modelProvider: "gemini" };
|
|
402
|
+
return { ...obj, modelProvider: "gemini", model: model || DEFAULT_MODEL };
|
|
383
403
|
}
|
|
384
404
|
|
|
385
405
|
export function piModelsJson(model) {
|
|
386
|
-
const m = model ||
|
|
406
|
+
const m = model || DEFAULT_MODEL;
|
|
387
407
|
return `${JSON.stringify({
|
|
388
408
|
providers: {
|
|
389
409
|
pleum: {
|
|
@@ -397,7 +417,7 @@ export function piModelsJson(model) {
|
|
|
397
417
|
}
|
|
398
418
|
|
|
399
419
|
export function crushJson(model) {
|
|
400
|
-
const m = model ||
|
|
420
|
+
const m = model || DEFAULT_MODEL;
|
|
401
421
|
return `${JSON.stringify({
|
|
402
422
|
providers: {
|
|
403
423
|
pleum: {
|