pleumrouter 0.5.44 → 0.5.45
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 +8 -3
- package/package.json +1 -1
- package/src/interactive.mjs +130 -0
- package/src/locales/es.mjs +38 -0
- package/src/locales/ja.mjs +38 -0
- package/src/locales/ko.mjs +38 -0
- package/src/locales/zh.mjs +38 -0
- package/src/menu.mjs +217 -2
- package/src/wire.mjs +657 -49
- package/src/workspace.mjs +57 -0
package/src/wire.mjs
CHANGED
|
@@ -2,10 +2,32 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { ROOT, V1, authFetch, getActiveContext } from "./config.mjs";
|
|
5
|
-
import { tomlBasicString } from "./recipes.mjs";
|
|
5
|
+
import { tomlBasicString, RECIPES } from "./recipes.mjs";
|
|
6
6
|
import { t } from "./i18n.mjs";
|
|
7
7
|
|
|
8
|
-
export const
|
|
8
|
+
export const WIRE_DEFAULT_TOOLS = ["claude", "codex"];
|
|
9
|
+
export const WIRE_TOOLS = [
|
|
10
|
+
"claude",
|
|
11
|
+
"codex",
|
|
12
|
+
"opencode",
|
|
13
|
+
"aider",
|
|
14
|
+
"crush",
|
|
15
|
+
"pi",
|
|
16
|
+
"agy",
|
|
17
|
+
"gemini",
|
|
18
|
+
"qwen",
|
|
19
|
+
"copilot",
|
|
20
|
+
"goose",
|
|
21
|
+
"openhands",
|
|
22
|
+
"omp",
|
|
23
|
+
"hermes",
|
|
24
|
+
"minimax",
|
|
25
|
+
"mimo",
|
|
26
|
+
"openclaw",
|
|
27
|
+
"plandex",
|
|
28
|
+
"interpreter",
|
|
29
|
+
];
|
|
30
|
+
|
|
9
31
|
export const WIRE_KEY_NAME = "pleum-desktop";
|
|
10
32
|
export const WIRE_MARKER_START = "# pleum-wire-start";
|
|
11
33
|
export const WIRE_MARKER_END = "# pleum-wire-end";
|
|
@@ -17,11 +39,35 @@ const CLAUDE_ENV_KEYS = [
|
|
|
17
39
|
"CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
|
|
18
40
|
"API_TIMEOUT_MS",
|
|
19
41
|
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC",
|
|
42
|
+
"ANTHROPIC_CUSTOM_MODEL_OPTION",
|
|
43
|
+
"ANTHROPIC_CUSTOM_MODEL_OPTION_NAME",
|
|
44
|
+
"ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION",
|
|
45
|
+
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
46
|
+
"CLAUDE_CODE_SUBAGENT_MODEL_FORCE",
|
|
20
47
|
];
|
|
21
48
|
|
|
49
|
+
export const WIRE_WARNINGS = {
|
|
50
|
+
claude: "Once wired, running `claude` (CLI or Desktop) will route all requests through PleumRouter API credits instead of your Claude Pro/Max subscription until you run `pleum unwire claude`.",
|
|
51
|
+
codex: "Once wired, running `codex` (CLI or Desktop) will route all requests through PleumRouter API credits instead of your ChatGPT Plus/Pro plan or existing OpenAI key until you run `pleum unwire codex`.",
|
|
52
|
+
agy: "Once wired, Antigravity CLI (`agy`) will use PleumRouter Gemini API mode and deduct PleumRouter credits instead of Google account login until you run `pleum unwire agy`.",
|
|
53
|
+
opencode: "Once wired, OpenCode (CLI, Desktop, IDE) will register PleumRouter in ~/.config/opencode/opencode.json and charge PleumRouter credits when Pleum models are used.",
|
|
54
|
+
default: "Once wired, running `{tool}` will route API calls through PleumRouter and deduct PleumRouter credits until you run `pleum unwire {tool}`.",
|
|
55
|
+
};
|
|
56
|
+
|
|
22
57
|
export function wirePaths(home = homedir()) {
|
|
23
58
|
const claude = join(home, ".claude", "settings.json");
|
|
24
59
|
const codex = join(home, ".codex", "config.toml");
|
|
60
|
+
const opencode = join(home, ".config", "opencode", "opencode.json");
|
|
61
|
+
const opencodeAuth = join(home, ".local", "share", "opencode", "auth.json");
|
|
62
|
+
const aider = join(home, ".aider.conf.yml");
|
|
63
|
+
const crush = join(home, ".config", "crush", "crush.json");
|
|
64
|
+
const pi = join(home, ".pi", "agent", "models.json");
|
|
65
|
+
const agy = join(home, ".gemini", "antigravity-cli", "settings.json");
|
|
66
|
+
const shellRc = process.env.SHELL && process.env.SHELL.includes("bash")
|
|
67
|
+
? join(home, ".bashrc")
|
|
68
|
+
: join(home, ".zshrc");
|
|
69
|
+
const state = join(home, ".pleum", "wired-agents.json");
|
|
70
|
+
|
|
25
71
|
return {
|
|
26
72
|
home,
|
|
27
73
|
wire: join(home, ".pleum", "wire.json"),
|
|
@@ -29,6 +75,19 @@ export function wirePaths(home = homedir()) {
|
|
|
29
75
|
claudeBak: `${claude}.pleum.bak`,
|
|
30
76
|
codex,
|
|
31
77
|
codexBak: `${codex}.pleum.bak`,
|
|
78
|
+
opencode,
|
|
79
|
+
opencodeBak: `${opencode}.pleum.bak`,
|
|
80
|
+
opencodeAuth,
|
|
81
|
+
aider,
|
|
82
|
+
aiderBak: `${aider}.pleum.bak`,
|
|
83
|
+
crush,
|
|
84
|
+
crushBak: `${crush}.pleum.bak`,
|
|
85
|
+
pi,
|
|
86
|
+
piBak: `${pi}.pleum.bak`,
|
|
87
|
+
agy,
|
|
88
|
+
agyBak: `${agy}.pleum.bak`,
|
|
89
|
+
shellRc,
|
|
90
|
+
state,
|
|
32
91
|
};
|
|
33
92
|
}
|
|
34
93
|
|
|
@@ -42,15 +101,13 @@ function readText(path) {
|
|
|
42
101
|
|
|
43
102
|
export function writeSecretFile(path, contents, mode = 0o600) {
|
|
44
103
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
45
|
-
// 기존 파일이 0644 등으로 있으면 writeFileSync의 mode는 무시된다(#41) —
|
|
46
|
-
// tmp(강제 chmod) + rename 원자적 교체로 결과물이 항상 mode가 되게 한다.
|
|
47
104
|
const tmp = `${path}.pleum-tmp-${process.pid}`;
|
|
48
105
|
writeFileSync(tmp, contents, { mode });
|
|
49
106
|
try {
|
|
50
107
|
chmodSync(tmp, mode);
|
|
51
108
|
renameSync(tmp, path);
|
|
52
109
|
} catch (e) {
|
|
53
|
-
try { unlinkSync(tmp); } catch {}
|
|
110
|
+
try { unlinkSync(tmp); } catch {}
|
|
54
111
|
throw e;
|
|
55
112
|
}
|
|
56
113
|
}
|
|
@@ -97,10 +154,13 @@ function removeRootValue(toml, name) {
|
|
|
97
154
|
return root.replace(new RegExp(`^${name}\\s*=\\s*.+\\n?`, "m"), "") + rest;
|
|
98
155
|
}
|
|
99
156
|
|
|
100
|
-
|
|
157
|
+
// ── Claude settings.json ──────────────────────────────────────────
|
|
158
|
+
export function wireClaudeSettings(existingJson, { root, key, model } = {}) {
|
|
101
159
|
let obj = {};
|
|
102
160
|
if (existingJson && existingJson.trim()) {
|
|
103
|
-
|
|
161
|
+
try {
|
|
162
|
+
obj = JSON.parse(existingJson);
|
|
163
|
+
} catch {}
|
|
104
164
|
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) obj = {};
|
|
105
165
|
}
|
|
106
166
|
obj.env = {
|
|
@@ -111,12 +171,33 @@ export function wireClaudeSettings(existingJson, { root, key }) {
|
|
|
111
171
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
112
172
|
};
|
|
113
173
|
delete obj.env.ANTHROPIC_API_KEY;
|
|
174
|
+
|
|
175
|
+
if (model) {
|
|
176
|
+
const qualified = /^claude-/.test(model) ? `anthropic/${model}` : model;
|
|
177
|
+
obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION = qualified;
|
|
178
|
+
obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME = `Pleum · ${model}`;
|
|
179
|
+
obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION = "Selected in Pleum wire";
|
|
180
|
+
obj.env.CLAUDE_CODE_SUBAGENT_MODEL = "sonnet";
|
|
181
|
+
obj.env.CLAUDE_CODE_SUBAGENT_MODEL_FORCE = "1";
|
|
182
|
+
obj.model = qualified;
|
|
183
|
+
} else {
|
|
184
|
+
delete obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION;
|
|
185
|
+
delete obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME;
|
|
186
|
+
delete obj.env.ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION;
|
|
187
|
+
delete obj.env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
188
|
+
delete obj.env.CLAUDE_CODE_SUBAGENT_MODEL_FORCE;
|
|
189
|
+
delete obj.model;
|
|
190
|
+
}
|
|
191
|
+
|
|
114
192
|
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
115
193
|
}
|
|
116
194
|
|
|
117
195
|
export function unwireClaudeSettings(existingJson) {
|
|
118
196
|
if (!existingJson || !existingJson.trim()) return `${JSON.stringify({}, null, 2)}\n`;
|
|
119
|
-
|
|
197
|
+
let obj = {};
|
|
198
|
+
try {
|
|
199
|
+
obj = JSON.parse(existingJson);
|
|
200
|
+
} catch {}
|
|
120
201
|
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
121
202
|
return `${JSON.stringify({}, null, 2)}\n`;
|
|
122
203
|
}
|
|
@@ -124,21 +205,28 @@ export function unwireClaudeSettings(existingJson) {
|
|
|
124
205
|
for (const envKey of CLAUDE_ENV_KEYS) delete obj.env[envKey];
|
|
125
206
|
if (Object.keys(obj.env).length === 0) delete obj.env;
|
|
126
207
|
}
|
|
208
|
+
if (obj.model && typeof obj.model === "string" && (obj.model.startsWith("anthropic/") || obj.model.startsWith("pleum/"))) {
|
|
209
|
+
delete obj.model;
|
|
210
|
+
}
|
|
127
211
|
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
128
212
|
}
|
|
129
213
|
|
|
130
|
-
|
|
214
|
+
// ── Codex config.toml ─────────────────────────────────────────────
|
|
215
|
+
export function wireCodexToml(existingToml, { v1, key, model = "gpt-6.1-sol", billingOnly = false } = {}) {
|
|
131
216
|
const src = existingToml ?? "";
|
|
132
217
|
const previousProvider = previousValue(src, "model_provider");
|
|
133
218
|
const previousModel = previousValue(src, "model");
|
|
219
|
+
const targetModel = billingOnly && previousModel ? previousModel : model;
|
|
220
|
+
|
|
134
221
|
let body = stripWireBlock(src);
|
|
135
222
|
body = removeTomlTable(body, "model_providers.pleum.http_headers");
|
|
136
223
|
body = removeTomlTable(body, "model_providers.pleum");
|
|
137
224
|
body = removeRootValue(removeRootValue(body, "model_provider"), "model");
|
|
225
|
+
|
|
138
226
|
const rootBlock = `${WIRE_MARKER_START}
|
|
139
227
|
# previous_model_provider = ${previousProvider}
|
|
140
228
|
# previous_model = ${previousModel}
|
|
141
|
-
model = ${tomlBasicString(
|
|
229
|
+
model = ${tomlBasicString(targetModel)}
|
|
142
230
|
model_provider = "pleum"
|
|
143
231
|
${WIRE_MARKER_END}
|
|
144
232
|
`;
|
|
@@ -170,6 +258,356 @@ export function unwireCodexToml(existingToml) {
|
|
|
170
258
|
return `${restored ? `${restored}\n\n` : ""}${body.trim()}`.replace(/\n{3,}/g, "\n\n").trimStart() + "\n";
|
|
171
259
|
}
|
|
172
260
|
|
|
261
|
+
// ── OpenCode (opencode.json & auth.json) ──────────────────────────
|
|
262
|
+
export function wireOpenCodeJson(existingJson, { v1, models = [], defaultModel } = {}) {
|
|
263
|
+
let obj = {};
|
|
264
|
+
if (existingJson && existingJson.trim()) {
|
|
265
|
+
try {
|
|
266
|
+
obj = JSON.parse(existingJson);
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
269
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) obj = {};
|
|
270
|
+
|
|
271
|
+
obj.provider = obj.provider || {};
|
|
272
|
+
const modelEntries = Object.fromEntries(models.map((id) => [id, {}]));
|
|
273
|
+
if (defaultModel) modelEntries[defaultModel] = {};
|
|
274
|
+
|
|
275
|
+
obj.provider.pleum = {
|
|
276
|
+
npm: "@ai-sdk/openai-compatible",
|
|
277
|
+
name: "PleumRouter",
|
|
278
|
+
options: {
|
|
279
|
+
baseURL: v1,
|
|
280
|
+
},
|
|
281
|
+
models: modelEntries,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (defaultModel) {
|
|
285
|
+
if (obj.model && !obj.model.startsWith("pleum/")) {
|
|
286
|
+
obj._pleum_previous_model = obj.model;
|
|
287
|
+
}
|
|
288
|
+
obj.model = `pleum/${defaultModel}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function unwireOpenCodeJson(existingJson) {
|
|
295
|
+
if (!existingJson || !existingJson.trim()) return `${JSON.stringify({}, null, 2)}\n`;
|
|
296
|
+
let obj = {};
|
|
297
|
+
try {
|
|
298
|
+
obj = JSON.parse(existingJson);
|
|
299
|
+
} catch {}
|
|
300
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return `${JSON.stringify({}, null, 2)}\n`;
|
|
301
|
+
|
|
302
|
+
if (obj.provider && obj.provider.pleum) {
|
|
303
|
+
delete obj.provider.pleum;
|
|
304
|
+
if (Object.keys(obj.provider).length === 0) delete obj.provider;
|
|
305
|
+
}
|
|
306
|
+
if (obj.model && typeof obj.model === "string" && obj.model.startsWith("pleum/")) {
|
|
307
|
+
if (obj._pleum_previous_model) {
|
|
308
|
+
obj.model = obj._pleum_previous_model;
|
|
309
|
+
delete obj._pleum_previous_model;
|
|
310
|
+
} else {
|
|
311
|
+
delete obj.model;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function wireOpenCodeAuth(existingJson, key) {
|
|
318
|
+
let obj = {};
|
|
319
|
+
if (existingJson && existingJson.trim()) {
|
|
320
|
+
try {
|
|
321
|
+
obj = JSON.parse(existingJson);
|
|
322
|
+
} catch {}
|
|
323
|
+
}
|
|
324
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) obj = {};
|
|
325
|
+
obj.pleum = { type: "api", key };
|
|
326
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function unwireOpenCodeAuth(existingJson) {
|
|
330
|
+
if (!existingJson || !existingJson.trim()) return `${JSON.stringify({}, null, 2)}\n`;
|
|
331
|
+
let obj = {};
|
|
332
|
+
try {
|
|
333
|
+
obj = JSON.parse(existingJson);
|
|
334
|
+
} catch {}
|
|
335
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return `${JSON.stringify({}, null, 2)}\n`;
|
|
336
|
+
delete obj.pleum;
|
|
337
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── Aider ~/.aider.conf.yml ───────────────────────────────────────
|
|
341
|
+
export function wireAiderYaml(existingYaml = "", { v1, key, model } = {}) {
|
|
342
|
+
const clean = (existingYaml || "").replace(
|
|
343
|
+
new RegExp(`${WIRE_MARKER_START}[\\s\\S]*?${WIRE_MARKER_END}\\n?`, "g"),
|
|
344
|
+
"",
|
|
345
|
+
).trim();
|
|
346
|
+
|
|
347
|
+
const lines = [
|
|
348
|
+
WIRE_MARKER_START,
|
|
349
|
+
`openai-api-base: ${v1}`,
|
|
350
|
+
`openai-api-key: ${key}`,
|
|
351
|
+
];
|
|
352
|
+
if (model) {
|
|
353
|
+
const cleanModel = model.replace(/^openai\//, "");
|
|
354
|
+
lines.push(`model: openai/${cleanModel}`);
|
|
355
|
+
}
|
|
356
|
+
lines.push(WIRE_MARKER_END);
|
|
357
|
+
|
|
358
|
+
return `${clean ? `${clean}\n\n` : ""}${lines.join("\n")}\n`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function unwireAiderYaml(existingYaml = "") {
|
|
362
|
+
return (existingYaml || "")
|
|
363
|
+
.replace(new RegExp(`${WIRE_MARKER_START}[\\s\\S]*?${WIRE_MARKER_END}\\n?`, "g"), "")
|
|
364
|
+
.trimStart();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ── Crush ~/.config/crush/crush.json ──────────────────────────────
|
|
368
|
+
export function wireCrushConfig(existingJson, { v1, key, models = [] } = {}) {
|
|
369
|
+
let obj = {};
|
|
370
|
+
if (existingJson && existingJson.trim()) {
|
|
371
|
+
try {
|
|
372
|
+
obj = JSON.parse(existingJson);
|
|
373
|
+
} catch {}
|
|
374
|
+
}
|
|
375
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) obj = {};
|
|
376
|
+
obj.providers = obj.providers || {};
|
|
377
|
+
obj.providers.pleum = {
|
|
378
|
+
type: "openai-compat",
|
|
379
|
+
base_url: v1,
|
|
380
|
+
api_key: key,
|
|
381
|
+
models: models.map((id) => ({ id, name: id })),
|
|
382
|
+
};
|
|
383
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function unwireCrushConfig(existingJson) {
|
|
387
|
+
if (!existingJson || !existingJson.trim()) return `${JSON.stringify({}, null, 2)}\n`;
|
|
388
|
+
let obj = {};
|
|
389
|
+
try {
|
|
390
|
+
obj = JSON.parse(existingJson);
|
|
391
|
+
} catch {}
|
|
392
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return `${JSON.stringify({}, null, 2)}\n`;
|
|
393
|
+
if (obj.providers && obj.providers.pleum) {
|
|
394
|
+
delete obj.providers.pleum;
|
|
395
|
+
if (Object.keys(obj.providers).length === 0) delete obj.providers;
|
|
396
|
+
}
|
|
397
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── Pi ~/.pi/agent/models.json ────────────────────────────────────
|
|
401
|
+
export function wirePiConfig(existingJson, { v1, key, models = [] } = {}) {
|
|
402
|
+
let obj = {};
|
|
403
|
+
if (existingJson && existingJson.trim()) {
|
|
404
|
+
try {
|
|
405
|
+
obj = JSON.parse(existingJson);
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) obj = {};
|
|
409
|
+
obj.providers = obj.providers || {};
|
|
410
|
+
obj.providers.pleum = {
|
|
411
|
+
baseUrl: v1,
|
|
412
|
+
api: "openai-completions",
|
|
413
|
+
apiKey: key,
|
|
414
|
+
models: models.map((id) => ({ id })),
|
|
415
|
+
};
|
|
416
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function unwirePiConfig(existingJson) {
|
|
420
|
+
if (!existingJson || !existingJson.trim()) return `${JSON.stringify({}, null, 2)}\n`;
|
|
421
|
+
let obj = {};
|
|
422
|
+
try {
|
|
423
|
+
obj = JSON.parse(existingJson);
|
|
424
|
+
} catch {}
|
|
425
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return `${JSON.stringify({}, null, 2)}\n`;
|
|
426
|
+
if (obj.providers && obj.providers.pleum) {
|
|
427
|
+
delete obj.providers.pleum;
|
|
428
|
+
if (Object.keys(obj.providers).length === 0) delete obj.providers;
|
|
429
|
+
}
|
|
430
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ── Antigravity ~/.gemini/antigravity-cli/settings.json ────────────
|
|
434
|
+
export function wireAgySettings(existingJson, { model } = {}) {
|
|
435
|
+
let obj = {};
|
|
436
|
+
if (existingJson && existingJson.trim()) {
|
|
437
|
+
try {
|
|
438
|
+
obj = JSON.parse(existingJson);
|
|
439
|
+
} catch {}
|
|
440
|
+
}
|
|
441
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) obj = {};
|
|
442
|
+
obj.modelProvider = "gemini";
|
|
443
|
+
if (model) obj.model = model;
|
|
444
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function unwireAgySettings(existingJson) {
|
|
448
|
+
if (!existingJson || !existingJson.trim()) return `${JSON.stringify({}, null, 2)}\n`;
|
|
449
|
+
let obj = {};
|
|
450
|
+
try {
|
|
451
|
+
obj = JSON.parse(existingJson);
|
|
452
|
+
} catch {}
|
|
453
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return `${JSON.stringify({}, null, 2)}\n`;
|
|
454
|
+
if (obj.modelProvider === "gemini") delete obj.modelProvider;
|
|
455
|
+
delete obj.model;
|
|
456
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ── Shell function wrapper (rc files) ─────────────────────────────
|
|
460
|
+
export function wireShellWrapper(existingRc = "", { tool, bin, envMap = {} } = {}) {
|
|
461
|
+
const startMarker = `# pleum-wire:${tool}-start`;
|
|
462
|
+
const endMarker = `# pleum-wire:${tool}-end`;
|
|
463
|
+
const clean = (existingRc || "")
|
|
464
|
+
.replace(new RegExp(`${startMarker}[\\s\\S]*?${endMarker}\\n?`, "g"), "")
|
|
465
|
+
.trim();
|
|
466
|
+
|
|
467
|
+
const envAssignments = Object.entries(envMap)
|
|
468
|
+
.map(([k, v]) => `${k}=${JSON.stringify(String(v))}`)
|
|
469
|
+
.join(" ");
|
|
470
|
+
|
|
471
|
+
const block = `${startMarker}
|
|
472
|
+
${tool}() {
|
|
473
|
+
${envAssignments} command ${bin || tool} "$@"
|
|
474
|
+
}
|
|
475
|
+
${endMarker}`;
|
|
476
|
+
|
|
477
|
+
return `${clean ? `${clean}\n\n` : ""}${block}\n`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function unwireShellWrapper(existingRc = "", tool) {
|
|
481
|
+
const startMarker = `# pleum-wire:${tool}-start`;
|
|
482
|
+
const endMarker = `# pleum-wire:${tool}-end`;
|
|
483
|
+
return (existingRc || "")
|
|
484
|
+
.replace(new RegExp(`${startMarker}[\\s\\S]*?${endMarker}\\n?`, "g"), "")
|
|
485
|
+
.trimStart();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ── Wired State Metadata (~/.pleum/wired-agents.json) ─────────────
|
|
489
|
+
export function readWiredState(paths) {
|
|
490
|
+
try {
|
|
491
|
+
const raw = readText(paths.state);
|
|
492
|
+
return raw ? JSON.parse(raw) : {};
|
|
493
|
+
} catch {
|
|
494
|
+
return {};
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function writeWiredState(paths, state) {
|
|
499
|
+
writeSecretFile(paths.state, `${JSON.stringify(state, null, 2)}\n`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ── Wire Status Check ─────────────────────────────────────────────
|
|
503
|
+
export function getWireStatus(home = homedir()) {
|
|
504
|
+
const paths = wirePaths(home);
|
|
505
|
+
const state = readWiredState(paths);
|
|
506
|
+
const status = {};
|
|
507
|
+
|
|
508
|
+
// 1. Claude
|
|
509
|
+
const claudeText = readText(paths.claude);
|
|
510
|
+
const claudeWired = Boolean(claudeText && claudeText.includes("ANTHROPIC_BASE_URL"));
|
|
511
|
+
status.claude = {
|
|
512
|
+
tool: "claude",
|
|
513
|
+
label: "Claude Code",
|
|
514
|
+
wired: claudeWired,
|
|
515
|
+
mode: state.claude?.mode || (claudeWired ? "billing" : undefined),
|
|
516
|
+
model: state.claude?.model,
|
|
517
|
+
targetPath: paths.claude,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// 2. Codex
|
|
521
|
+
const codexText = readText(paths.codex);
|
|
522
|
+
const codexWired = Boolean(codexText && codexText.includes('model_provider = "pleum"'));
|
|
523
|
+
status.codex = {
|
|
524
|
+
tool: "codex",
|
|
525
|
+
label: "Codex CLI",
|
|
526
|
+
wired: codexWired,
|
|
527
|
+
mode: state.codex?.mode || (codexWired ? "billing" : undefined),
|
|
528
|
+
model: state.codex?.model,
|
|
529
|
+
targetPath: paths.codex,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// 3. OpenCode
|
|
533
|
+
const opencodeText = readText(paths.opencode);
|
|
534
|
+
const opencodeWired = Boolean(opencodeText && opencodeText.includes('"PleumRouter"'));
|
|
535
|
+
status.opencode = {
|
|
536
|
+
tool: "opencode",
|
|
537
|
+
label: "OpenCode",
|
|
538
|
+
wired: opencodeWired,
|
|
539
|
+
mode: state.opencode?.mode || (opencodeWired ? "list" : undefined),
|
|
540
|
+
model: state.opencode?.model,
|
|
541
|
+
targetPath: paths.opencode,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// 4. Aider
|
|
545
|
+
const aiderText = readText(paths.aider);
|
|
546
|
+
const aiderWired = Boolean(aiderText && aiderText.includes("openai-api-base") && aiderText.includes("apirouter.pleum.ai"));
|
|
547
|
+
status.aider = {
|
|
548
|
+
tool: "aider",
|
|
549
|
+
label: "Aider",
|
|
550
|
+
wired: aiderWired,
|
|
551
|
+
mode: state.aider?.mode || (aiderWired ? "billing" : undefined),
|
|
552
|
+
model: state.aider?.model,
|
|
553
|
+
targetPath: paths.aider,
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// 5. Crush
|
|
557
|
+
const crushText = readText(paths.crush);
|
|
558
|
+
const crushWired = Boolean(crushText && crushText.includes("apirouter.pleum.ai"));
|
|
559
|
+
status.crush = {
|
|
560
|
+
tool: "crush",
|
|
561
|
+
label: "Crush",
|
|
562
|
+
wired: crushWired,
|
|
563
|
+
mode: state.crush?.mode || (crushWired ? "list" : undefined),
|
|
564
|
+
model: state.crush?.model,
|
|
565
|
+
targetPath: paths.crush,
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// 6. Pi
|
|
569
|
+
const piText = readText(paths.pi);
|
|
570
|
+
const piWired = Boolean(piText && piText.includes("apirouter.pleum.ai"));
|
|
571
|
+
status.pi = {
|
|
572
|
+
tool: "pi",
|
|
573
|
+
label: "Pi",
|
|
574
|
+
wired: piWired,
|
|
575
|
+
mode: state.pi?.mode || (piWired ? "list" : undefined),
|
|
576
|
+
model: state.pi?.model,
|
|
577
|
+
targetPath: paths.pi,
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// 7. Antigravity (agy)
|
|
581
|
+
const agyText = readText(paths.agy);
|
|
582
|
+
const rcText = readText(paths.shellRc) || "";
|
|
583
|
+
const agyWired = Boolean((agyText && agyText.includes('"modelProvider": "gemini"')) || rcText.includes("# pleum-wire:agy-start"));
|
|
584
|
+
status.agy = {
|
|
585
|
+
tool: "agy",
|
|
586
|
+
label: "Antigravity CLI",
|
|
587
|
+
wired: agyWired,
|
|
588
|
+
mode: state.agy?.mode || (agyWired ? "billing" : undefined),
|
|
589
|
+
model: state.agy?.model,
|
|
590
|
+
targetPath: paths.agy,
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// Shell-based tools
|
|
594
|
+
for (const tool of WIRE_TOOLS) {
|
|
595
|
+
if (status[tool]) continue;
|
|
596
|
+
const r = RECIPES[tool];
|
|
597
|
+
const isWired = rcText.includes(`# pleum-wire:${tool}-start`);
|
|
598
|
+
status[tool] = {
|
|
599
|
+
tool,
|
|
600
|
+
label: r?.label || tool,
|
|
601
|
+
wired: isWired,
|
|
602
|
+
mode: state[tool]?.mode || (isWired ? "billing" : undefined),
|
|
603
|
+
model: state[tool]?.model,
|
|
604
|
+
targetPath: paths.shellRc,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return status;
|
|
609
|
+
}
|
|
610
|
+
|
|
173
611
|
function parseJsonBody(res) {
|
|
174
612
|
return res.json().catch(() => ({}));
|
|
175
613
|
}
|
|
@@ -209,8 +647,6 @@ export async function ensureWireKey({
|
|
|
209
647
|
writeSecretFile(paths.wire, `${JSON.stringify(movedRecord, null, 2)}\n`);
|
|
210
648
|
return movedRecord;
|
|
211
649
|
}
|
|
212
|
-
// 이동은 keys.manage_all 보유자(소유자/관리자)만 가능하다 — 일반 멤버는
|
|
213
|
-
// 아래 새 키 발급으로 공간 귀속 키를 만든다(과금 오귀속보다 키 재발급이 낫다).
|
|
214
650
|
} else {
|
|
215
651
|
return stored;
|
|
216
652
|
}
|
|
@@ -245,15 +681,30 @@ export async function ensureWireKey({
|
|
|
245
681
|
return record;
|
|
246
682
|
}
|
|
247
683
|
|
|
248
|
-
export function parseWireArgs(args) {
|
|
684
|
+
export function parseWireArgs(args = []) {
|
|
249
685
|
const revoke = args.includes("--revoke-key");
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
const
|
|
686
|
+
const billingOnly = args.includes("--billing-only");
|
|
687
|
+
let model;
|
|
688
|
+
const filtered = [];
|
|
689
|
+
for (let i = 0; i < args.length; i++) {
|
|
690
|
+
const a = args[i];
|
|
691
|
+
if (a === "--revoke-key" || a === "--billing-only") continue;
|
|
692
|
+
if (a === "--model" || a === "-m") {
|
|
693
|
+
model = args[++i];
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
filtered.push(a);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const unknown = filtered.find((tool) => !WIRE_TOOLS.includes(tool));
|
|
253
700
|
if (unknown) {
|
|
254
701
|
throw new Error(t("Unsupported tool: {tool}. Use claude or codex.", { tool: unknown }));
|
|
255
702
|
}
|
|
256
|
-
|
|
703
|
+
|
|
704
|
+
const res = { tools: filtered.length === 0 ? [...WIRE_DEFAULT_TOOLS] : [...new Set(filtered)], revoke };
|
|
705
|
+
if (billingOnly) res.billingOnly = true;
|
|
706
|
+
if (model) res.model = model;
|
|
707
|
+
return res;
|
|
257
708
|
}
|
|
258
709
|
|
|
259
710
|
export function wireHelp() {
|
|
@@ -285,47 +736,206 @@ function printDesktopHints(record) {
|
|
|
285
736
|
console.log(t("kept the desktop key on this machine (logout does not delete it)"));
|
|
286
737
|
}
|
|
287
738
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
writeSecretFile(paths.claude, next);
|
|
292
|
-
console.log(t("Wrote {path}", { path: paths.claude }));
|
|
293
|
-
}
|
|
739
|
+
export function applyToolWiring(tool, paths, { key, model, models = [], billingOnly = false, activeContext } = {}) {
|
|
740
|
+
const state = readWiredState(paths);
|
|
741
|
+
const mode = billingOnly ? "billing" : (models.length > 0 ? "list" : (model ? "custom" : "billing"));
|
|
294
742
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
743
|
+
switch (tool) {
|
|
744
|
+
case "claude": {
|
|
745
|
+
backupOnce(paths.claude, paths.claudeBak);
|
|
746
|
+
const next = wireClaudeSettings(readText(paths.claude), { root: ROOT, key, model: billingOnly ? undefined : model });
|
|
747
|
+
writeSecretFile(paths.claude, next);
|
|
748
|
+
console.log(t("Wrote {path}", { path: paths.claude }));
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
case "codex": {
|
|
752
|
+
backupOnce(paths.codex, paths.codexBak);
|
|
753
|
+
const next = wireCodexToml(readText(paths.codex) ?? "", { v1: V1, key, model: model || "gpt-6.1-sol", billingOnly });
|
|
754
|
+
writeSecretFile(paths.codex, next);
|
|
755
|
+
console.log(t("Wrote {path}", { path: paths.codex }));
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
case "opencode": {
|
|
759
|
+
backupOnce(paths.opencode, paths.opencodeBak);
|
|
760
|
+
const nextJson = wireOpenCodeJson(readText(paths.opencode), { v1: V1, models, defaultModel: model });
|
|
761
|
+
writeSecretFile(paths.opencode, nextJson);
|
|
762
|
+
const nextAuth = wireOpenCodeAuth(readText(paths.opencodeAuth), key);
|
|
763
|
+
writeSecretFile(paths.opencodeAuth, nextAuth);
|
|
764
|
+
console.log(t("Wrote {path}", { path: paths.opencode }));
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
case "aider": {
|
|
768
|
+
backupOnce(paths.aider, paths.aiderBak);
|
|
769
|
+
const next = wireAiderYaml(readText(paths.aider), { v1: V1, key, model: billingOnly ? undefined : model });
|
|
770
|
+
writeSecretFile(paths.aider, next);
|
|
771
|
+
console.log(t("Wrote {path}", { path: paths.aider }));
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
case "crush": {
|
|
775
|
+
backupOnce(paths.crush, paths.crushBak);
|
|
776
|
+
const next = wireCrushConfig(readText(paths.crush), { v1: V1, key, models });
|
|
777
|
+
writeSecretFile(paths.crush, next);
|
|
778
|
+
console.log(t("Wrote {path}", { path: paths.crush }));
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
case "pi": {
|
|
782
|
+
backupOnce(paths.pi, paths.piBak);
|
|
783
|
+
const next = wirePiConfig(readText(paths.pi), { v1: V1, key, models });
|
|
784
|
+
writeSecretFile(paths.pi, next);
|
|
785
|
+
console.log(t("Wrote {path}", { path: paths.pi }));
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
case "agy": {
|
|
789
|
+
backupOnce(paths.agy, paths.agyBak);
|
|
790
|
+
const nextAgy = wireAgySettings(readText(paths.agy), { model: billingOnly ? undefined : model });
|
|
791
|
+
writeSecretFile(paths.agy, nextAgy);
|
|
792
|
+
const rcContent = readText(paths.shellRc) || "";
|
|
793
|
+
const nextRc = wireShellWrapper(rcContent, {
|
|
794
|
+
tool: "agy",
|
|
795
|
+
bin: "agy",
|
|
796
|
+
envMap: {
|
|
797
|
+
GOOGLE_GEMINI_BASE_URL: ROOT,
|
|
798
|
+
GEMINI_API_KEY: key,
|
|
799
|
+
GOOGLE_API_KEY: key,
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
writeSecretFile(paths.shellRc, nextRc, 0o644);
|
|
803
|
+
console.log(t("Wrote {path}", { path: paths.agy }));
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
default: {
|
|
807
|
+
const r = RECIPES[tool];
|
|
808
|
+
if (r) {
|
|
809
|
+
const rcContent = readText(paths.shellRc) || "";
|
|
810
|
+
const envMap = r.env(key, model);
|
|
811
|
+
const nextRc = wireShellWrapper(rcContent, { tool, bin: r.bin, envMap });
|
|
812
|
+
writeSecretFile(paths.shellRc, nextRc, 0o644);
|
|
813
|
+
console.log(t("Wrote {path}", { path: paths.shellRc }));
|
|
814
|
+
}
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
301
818
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
819
|
+
state[tool] = {
|
|
820
|
+
wiredAt: new Date().toISOString(),
|
|
821
|
+
mode,
|
|
822
|
+
model,
|
|
823
|
+
models,
|
|
824
|
+
workspace_id: activeContext?.workspace_id,
|
|
825
|
+
workspace_name: activeContext?.workspace_name,
|
|
826
|
+
};
|
|
827
|
+
writeWiredState(paths, state);
|
|
306
828
|
}
|
|
307
829
|
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
830
|
+
export function revertToolWiring(tool, paths) {
|
|
831
|
+
const state = readWiredState(paths);
|
|
832
|
+
|
|
833
|
+
switch (tool) {
|
|
834
|
+
case "claude": {
|
|
835
|
+
if (existsSync(paths.claude)) {
|
|
836
|
+
writeSecretFile(paths.claude, unwireClaudeSettings(readText(paths.claude)));
|
|
837
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.claude }));
|
|
838
|
+
}
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
case "codex": {
|
|
842
|
+
if (existsSync(paths.codex)) {
|
|
843
|
+
writeSecretFile(paths.codex, unwireCodexToml(readText(paths.codex) ?? ""));
|
|
844
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.codex }));
|
|
845
|
+
}
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
case "opencode": {
|
|
849
|
+
if (existsSync(paths.opencode)) {
|
|
850
|
+
writeSecretFile(paths.opencode, unwireOpenCodeJson(readText(paths.opencode)));
|
|
851
|
+
}
|
|
852
|
+
if (existsSync(paths.opencodeAuth)) {
|
|
853
|
+
writeSecretFile(paths.opencodeAuth, unwireOpenCodeAuth(readText(paths.opencodeAuth)));
|
|
854
|
+
}
|
|
855
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.opencode }));
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
case "aider": {
|
|
859
|
+
if (existsSync(paths.aider)) {
|
|
860
|
+
writeSecretFile(paths.aider, unwireAiderYaml(readText(paths.aider)));
|
|
861
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.aider }));
|
|
862
|
+
}
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
case "crush": {
|
|
866
|
+
if (existsSync(paths.crush)) {
|
|
867
|
+
writeSecretFile(paths.crush, unwireCrushConfig(readText(paths.crush)));
|
|
868
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.crush }));
|
|
869
|
+
}
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
case "pi": {
|
|
873
|
+
if (existsSync(paths.pi)) {
|
|
874
|
+
writeSecretFile(paths.pi, unwirePiConfig(readText(paths.pi)));
|
|
875
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.pi }));
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case "agy": {
|
|
880
|
+
if (existsSync(paths.agy)) {
|
|
881
|
+
writeSecretFile(paths.agy, unwireAgySettings(readText(paths.agy)));
|
|
882
|
+
}
|
|
883
|
+
if (existsSync(paths.shellRc)) {
|
|
884
|
+
writeSecretFile(paths.shellRc, unwireShellWrapper(readText(paths.shellRc), "agy"), 0o644);
|
|
885
|
+
}
|
|
886
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.agy }));
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
default: {
|
|
890
|
+
if (existsSync(paths.shellRc)) {
|
|
891
|
+
writeSecretFile(paths.shellRc, unwireShellWrapper(readText(paths.shellRc), tool), 0o644);
|
|
892
|
+
console.log(t("Removed PleumRouter settings from {path}.", { path: paths.shellRc }));
|
|
893
|
+
}
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
delete state[tool];
|
|
899
|
+
writeWiredState(paths, state);
|
|
312
900
|
}
|
|
313
901
|
|
|
314
|
-
export async function wire(args
|
|
315
|
-
|
|
902
|
+
export async function wire(args = [], {
|
|
903
|
+
auth = authFetch,
|
|
904
|
+
home = homedir(),
|
|
905
|
+
model,
|
|
906
|
+
models,
|
|
907
|
+
billingOnly = false,
|
|
908
|
+
activeContext = getActiveContext(),
|
|
909
|
+
} = {}) {
|
|
910
|
+
const parsed = parseWireArgs(args);
|
|
911
|
+
const tools = parsed.tools;
|
|
912
|
+
const isBilling = billingOnly || parsed.billingOnly;
|
|
913
|
+
const targetModel = model || parsed.model;
|
|
316
914
|
const paths = wirePaths(home);
|
|
317
|
-
const record = await ensureWireKey({ auth, paths });
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
915
|
+
const record = await ensureWireKey({ auth, paths, activeContext });
|
|
916
|
+
|
|
917
|
+
for (const tool of tools) {
|
|
918
|
+
applyToolWiring(tool, paths, {
|
|
919
|
+
key: record.key,
|
|
920
|
+
model: targetModel,
|
|
921
|
+
models,
|
|
922
|
+
billingOnly: isBilling,
|
|
923
|
+
activeContext,
|
|
924
|
+
});
|
|
925
|
+
console.log(t("Wired {tool}.", { tool }));
|
|
926
|
+
}
|
|
927
|
+
|
|
321
928
|
printDesktopHints(record);
|
|
322
929
|
}
|
|
323
930
|
|
|
324
|
-
export async function unwire(args, { auth = authFetch, home = homedir() } = {}) {
|
|
931
|
+
export async function unwire(args = [], { auth = authFetch, home = homedir() } = {}) {
|
|
325
932
|
const { tools, revoke } = parseWireArgs(args);
|
|
326
933
|
const paths = wirePaths(home);
|
|
327
|
-
|
|
328
|
-
|
|
934
|
+
|
|
935
|
+
for (const tool of tools) {
|
|
936
|
+
revertToolWiring(tool, paths);
|
|
937
|
+
}
|
|
938
|
+
|
|
329
939
|
if (!revoke) return;
|
|
330
940
|
|
|
331
941
|
const stored = (() => {
|
|
@@ -346,9 +956,7 @@ export async function unwire(args, { auth = authFetch, home = homedir() } = {})
|
|
|
346
956
|
if (existsSync(paths.wire)) {
|
|
347
957
|
try {
|
|
348
958
|
unlinkSync(paths.wire);
|
|
349
|
-
} catch {
|
|
350
|
-
// already gone
|
|
351
|
-
}
|
|
959
|
+
} catch {}
|
|
352
960
|
}
|
|
353
961
|
console.log(t("Revoked desktop API key {id}.", { id: stored.id }));
|
|
354
962
|
}
|