@4yi-dev/cli 0.1.10 → 0.1.12
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 +5 -1
- package/bin/4yi.mjs +1 -0
- package/package.json +1 -1
- package/src/connect.mjs +186 -16
- package/src/opencode.mjs +14 -4
package/README.md
CHANGED
|
@@ -32,7 +32,11 @@ Both packages install the `4yi` command. The dev package points at `https://xcla
|
|
|
32
32
|
|
|
33
33
|
`4yi code` installs OpenCode under `~/.4yi/vendor/opencode`, writes an OpenCode config under `~/.4yi/opencode/opencode.json`, and launches OpenCode with a 4YI provider.
|
|
34
34
|
|
|
35
|
-
`4yi connect claude` and `4yi connect codex` first check for the corresponding local CLI. If it is missing, 4YI asks before installing the official npm package. Pass `--yes` to approve that CLI installation non-interactively. Desktop apps are detected
|
|
35
|
+
`4yi connect claude` and `4yi connect codex` first check for the corresponding local CLI. If it is missing, 4YI asks before installing the official npm package. Pass `--yes` to approve that CLI installation non-interactively. Desktop apps are detected but are never installed automatically.
|
|
36
|
+
|
|
37
|
+
On macOS, `4yi connect claude` also configures an installed Claude Desktop through its official third-party Gateway mode. The Desktop credential helper reads the current token from `~/.4yi/config.json`; the token is not copied into Claude's configuration. Quit and reopen Claude Desktop only after its active tasks finish. Third-party mode has a separate session list from the normal Claude profile. Run `4yi restore claude`, then reopen Claude Desktop, to return to the previous profile and session list.
|
|
38
|
+
|
|
39
|
+
`--scope project` changes only the project's Claude Code settings and never changes the global Claude Desktop profile.
|
|
36
40
|
|
|
37
41
|
For local development:
|
|
38
42
|
|
package/bin/4yi.mjs
CHANGED
|
@@ -11,6 +11,7 @@ if (command === "help" || command === "--help" || command === "-h") {
|
|
|
11
11
|
console.log(" 4yi code --model X pin model X as the default for future sessions");
|
|
12
12
|
console.log(" 4yi connect <claude|codex|all> connect existing coding tools to 4YI");
|
|
13
13
|
console.log(" --yes install a missing CLI without prompting");
|
|
14
|
+
console.log(" Claude on macOS also configures installed Claude Desktop 3P Gateway mode");
|
|
14
15
|
console.log(" 4yi status [claude|codex|all] inspect the current connection");
|
|
15
16
|
console.log(" 4yi restore <claude|codex|all> restore the latest protected config");
|
|
16
17
|
process.exit(0);
|
package/package.json
CHANGED
package/src/connect.mjs
CHANGED
|
@@ -20,6 +20,8 @@ const CODEX_ROOT_START = "# >>> 4yi-cli codex-root";
|
|
|
20
20
|
const CODEX_ROOT_END = "# <<< 4yi-cli codex-root";
|
|
21
21
|
const CODEX_PROVIDER_START = "# >>> 4yi-cli codex-provider";
|
|
22
22
|
const CODEX_PROVIDER_END = "# <<< 4yi-cli codex-provider";
|
|
23
|
+
const CLAUDE_DESKTOP_PROFILE_ID = "4e8e29f6-e0ba-4a2f-8a5f-4e2dcf95f3f0";
|
|
24
|
+
const CLAUDE_DESKTOP_PROFILE_FILE = `${CLAUDE_DESKTOP_PROFILE_ID}.json`;
|
|
23
25
|
const TOOL_METADATA = {
|
|
24
26
|
claude: {
|
|
25
27
|
label: "Claude Code",
|
|
@@ -51,22 +53,39 @@ export async function prepareConnectionTools({ target = "all", autoInstall = fal
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
export function connectionPaths({
|
|
56
|
+
export function connectionPaths({
|
|
57
|
+
home = os.homedir(),
|
|
58
|
+
cwd = process.cwd(),
|
|
59
|
+
codexHome = process.env.CODEX_HOME,
|
|
60
|
+
platform = process.platform,
|
|
61
|
+
} = {}) {
|
|
55
62
|
const codexDir = codexHome || path.join(home, ".codex");
|
|
56
|
-
|
|
63
|
+
const paths = {
|
|
57
64
|
claudeUser: path.join(home, ".claude", "settings.json"),
|
|
58
65
|
claudeProject: path.join(cwd, ".claude", "settings.local.json"),
|
|
59
66
|
codexConfig: path.join(codexDir, "config.toml"),
|
|
60
67
|
codexCatalog: path.join(codexDir, "model-catalogs", "4yi.json"),
|
|
61
68
|
};
|
|
69
|
+
if (platform === "darwin") {
|
|
70
|
+
const desktopDir = path.join(home, "Library", "Application Support", "Claude-3p");
|
|
71
|
+
const libraryDir = path.join(desktopDir, "configLibrary");
|
|
72
|
+
Object.assign(paths, {
|
|
73
|
+
claudeDesktopDir: desktopDir,
|
|
74
|
+
claudeDesktopConfig: path.join(desktopDir, "claude_desktop_config.json"),
|
|
75
|
+
claudeDesktopLibraryMeta: path.join(libraryDir, "_meta.json"),
|
|
76
|
+
claudeDesktopProfile: path.join(libraryDir, CLAUDE_DESKTOP_PROFILE_FILE),
|
|
77
|
+
claudeDesktopCredentialHelper: path.join(home, ".4yi", "helpers", "claude-desktop-credential"),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return paths;
|
|
62
81
|
}
|
|
63
82
|
|
|
64
|
-
function atomicWrite(file, value) {
|
|
83
|
+
function atomicWrite(file, value, mode = 0o600) {
|
|
65
84
|
ensureDir(path.dirname(file));
|
|
66
85
|
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
67
|
-
fs.writeFileSync(temp, value, { mode
|
|
86
|
+
fs.writeFileSync(temp, value, { mode });
|
|
68
87
|
fs.renameSync(temp, file);
|
|
69
|
-
try { fs.chmodSync(file,
|
|
88
|
+
try { fs.chmodSync(file, mode); } catch { /* Windows may ignore POSIX modes. */ }
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
function timestamp() {
|
|
@@ -216,6 +235,103 @@ function chooseModel(models, preferred, keyword) {
|
|
|
216
235
|
return match || models[0];
|
|
217
236
|
}
|
|
218
237
|
|
|
238
|
+
function readJsonObject(file) {
|
|
239
|
+
if (!fs.existsSync(file) || fs.statSync(file).size === 0) return {};
|
|
240
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
241
|
+
if (!value || Array.isArray(value) || typeof value !== "object") {
|
|
242
|
+
throw new Error(`${file} must contain a JSON object.`);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function claudeDesktopCredentialHelper() {
|
|
248
|
+
return `#!/bin/sh
|
|
249
|
+
set -eu
|
|
250
|
+
|
|
251
|
+
config_file="$HOME/.4yi/config.json"
|
|
252
|
+
if [ ! -f "$config_file" ]; then
|
|
253
|
+
echo "4YI session not found. Run: 4yi login" >&2
|
|
254
|
+
exit 1
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
token=$(/usr/bin/plutil -extract token raw -o - "$config_file" 2>/dev/null) || {
|
|
258
|
+
echo "4YI session is invalid. Run: 4yi login" >&2
|
|
259
|
+
exit 1
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if [ -z "$token" ] || [ "$token" = "null" ]; then
|
|
263
|
+
echo "4YI session is missing a token. Run: 4yi login" >&2
|
|
264
|
+
exit 1
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
printf '%s' "$token"
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function configureClaudeDesktop({
|
|
272
|
+
home,
|
|
273
|
+
claudeBaseUrl,
|
|
274
|
+
backupGroup,
|
|
275
|
+
stdout,
|
|
276
|
+
platform = process.platform,
|
|
277
|
+
env = process.env,
|
|
278
|
+
exists = fs.existsSync,
|
|
279
|
+
} = {}) {
|
|
280
|
+
if (platform !== "darwin") return { configured: false, supported: false };
|
|
281
|
+
|
|
282
|
+
const detected = desktopAppCandidates("claude", { home, platform, env }).some((candidate) => exists(candidate));
|
|
283
|
+
if (!detected) {
|
|
284
|
+
stdout("Claude App was not detected in a standard install location. Claude Code is connected; Claude Desktop was not changed.");
|
|
285
|
+
return { configured: false, supported: true, detected: false };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let gatewayUrl;
|
|
289
|
+
try {
|
|
290
|
+
gatewayUrl = new URL(claudeBaseUrl);
|
|
291
|
+
} catch {
|
|
292
|
+
stdout(`Claude Desktop was not configured because its Gateway URL is invalid: ${claudeBaseUrl}`);
|
|
293
|
+
return { configured: false, supported: true, detected: true };
|
|
294
|
+
}
|
|
295
|
+
if (gatewayUrl.protocol !== "https:") {
|
|
296
|
+
stdout("Claude Desktop was not configured because official 3P Gateway mode requires an HTTPS endpoint.");
|
|
297
|
+
return { configured: false, supported: true, detected: true };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const paths = connectionPaths({ home, platform });
|
|
301
|
+
const files = [
|
|
302
|
+
paths.claudeDesktopConfig,
|
|
303
|
+
paths.claudeDesktopLibraryMeta,
|
|
304
|
+
paths.claudeDesktopProfile,
|
|
305
|
+
paths.claudeDesktopCredentialHelper,
|
|
306
|
+
];
|
|
307
|
+
for (const file of files) backupFile("claude", file, home, backupGroup);
|
|
308
|
+
|
|
309
|
+
const gateway = gatewayUrl.toString().replace(/\/+$/, "");
|
|
310
|
+
const inference = {
|
|
311
|
+
inferenceProvider: "gateway",
|
|
312
|
+
inferenceGatewayBaseUrl: gateway,
|
|
313
|
+
inferenceCredentialKind: "helper-script",
|
|
314
|
+
inferenceCredentialHelper: paths.claudeDesktopCredentialHelper,
|
|
315
|
+
inferenceGatewayAuthScheme: "bearer",
|
|
316
|
+
disableEssentialTelemetry: true,
|
|
317
|
+
disableNonessentialTelemetry: true,
|
|
318
|
+
};
|
|
319
|
+
const desktopConfig = { ...readJsonObject(paths.claudeDesktopConfig), deploymentMode: "3p", ...inference };
|
|
320
|
+
const libraryMeta = { ...readJsonObject(paths.claudeDesktopLibraryMeta), appliedId: CLAUDE_DESKTOP_PROFILE_ID };
|
|
321
|
+
const profile = { ...readJsonObject(paths.claudeDesktopProfile), ...inference };
|
|
322
|
+
|
|
323
|
+
// Activate last so Claude never observes a selected profile before its
|
|
324
|
+
// credential helper and config-library entry are durable on disk.
|
|
325
|
+
atomicWrite(paths.claudeDesktopCredentialHelper, claudeDesktopCredentialHelper(), 0o700);
|
|
326
|
+
atomicWrite(paths.claudeDesktopProfile, `${JSON.stringify(profile, null, 2)}\n`);
|
|
327
|
+
atomicWrite(paths.claudeDesktopLibraryMeta, `${JSON.stringify(libraryMeta, null, 2)}\n`);
|
|
328
|
+
atomicWrite(paths.claudeDesktopConfig, `${JSON.stringify(desktopConfig, null, 2)}\n`);
|
|
329
|
+
|
|
330
|
+
stdout(`Connected Claude Desktop (official 3P Gateway): ${paths.claudeDesktopProfile}`);
|
|
331
|
+
stdout("Quit and reopen Claude Desktop when no tasks are running. It will use a separate 3P session list; `4yi restore claude` switches back to the previous profile.");
|
|
332
|
+
return { configured: true, supported: true, detected: true, paths };
|
|
333
|
+
}
|
|
334
|
+
|
|
219
335
|
async function loadClaudeModels(session, claudeBaseUrl) {
|
|
220
336
|
const base = claudeBaseUrl.replace(/\/+$/, "");
|
|
221
337
|
const response = await requestJson(base, "/v1/models", { token: session.token });
|
|
@@ -224,8 +340,8 @@ async function loadClaudeModels(session, claudeBaseUrl) {
|
|
|
224
340
|
return models;
|
|
225
341
|
}
|
|
226
342
|
|
|
227
|
-
async function connectClaude({ session, home, cwd, scope, claudeBaseUrl, stdout }) {
|
|
228
|
-
const paths = connectionPaths({ home, cwd });
|
|
343
|
+
async function connectClaude({ session, home, cwd, scope, claudeBaseUrl, stdout, tooling = {} }) {
|
|
344
|
+
const paths = connectionPaths({ home, cwd, platform: tooling.platform });
|
|
229
345
|
const files = scope === "project" ? [paths.claudeProject] : scope === "both" ? [paths.claudeUser, paths.claudeProject] : [paths.claudeUser];
|
|
230
346
|
const models = await loadClaudeModels(session, claudeBaseUrl);
|
|
231
347
|
const defaultModel = chooseModel(models, "claude-sonnet-4-6", "sonnet");
|
|
@@ -236,11 +352,7 @@ async function connectClaude({ session, home, cwd, scope, claudeBaseUrl, stdout
|
|
|
236
352
|
const backupGroup = timestamp();
|
|
237
353
|
for (const file of files) {
|
|
238
354
|
const backup = backupFile("claude", file, home, backupGroup);
|
|
239
|
-
|
|
240
|
-
if (fs.existsSync(file) && fs.statSync(file).size > 0) {
|
|
241
|
-
data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
242
|
-
if (!data || Array.isArray(data) || typeof data !== "object") throw new Error(`${file} must contain a JSON object.`);
|
|
243
|
-
}
|
|
355
|
+
const data = readJsonObject(file);
|
|
244
356
|
const env = data.env && !Array.isArray(data.env) && typeof data.env === "object" ? data.env : {};
|
|
245
357
|
Object.assign(env, {
|
|
246
358
|
ANTHROPIC_BASE_URL: claudeBaseUrl.replace(/\/+$/, ""),
|
|
@@ -260,6 +372,11 @@ async function connectClaude({ session, home, cwd, scope, claudeBaseUrl, stdout
|
|
|
260
372
|
stdout(`Backup: ${backup}`);
|
|
261
373
|
}
|
|
262
374
|
stdout(`Available Claude models: ${models.join(", ")}`);
|
|
375
|
+
if (scope === "project") {
|
|
376
|
+
stdout("Claude Desktop was not changed because --scope project only configures this project.");
|
|
377
|
+
} else {
|
|
378
|
+
configureClaudeDesktop({ home, claudeBaseUrl, backupGroup, stdout, ...tooling });
|
|
379
|
+
}
|
|
263
380
|
}
|
|
264
381
|
|
|
265
382
|
function removeManagedBlock(text, start, end) {
|
|
@@ -311,6 +428,16 @@ function buildCodexCatalog(models, template) {
|
|
|
311
428
|
description: `${planModel.display_name || planModel.id} via 4YI Gateway with OpenAI Responses tool support.`,
|
|
312
429
|
supported_in_api: true,
|
|
313
430
|
use_responses_lite: false,
|
|
431
|
+
// The template is whatever `codex debug models --bundled` returns on this
|
|
432
|
+
// machine, and that is not a stable schema: 0.147 carries this field,
|
|
433
|
+
// 0.150 omits it, and connect installs the latest codex -- so it upgrades
|
|
434
|
+
// its own template source into the version that drops it. Codex.app
|
|
435
|
+
// requires it and will not start without one, so it is set here rather
|
|
436
|
+
// than inherited. The endpoint is authoritative when it answers; `true`
|
|
437
|
+
// is the fallback because every model this catalog can describe is served
|
|
438
|
+
// through the Responses ingress, which requires `parallel_tool_calls` on
|
|
439
|
+
// each request.
|
|
440
|
+
supports_parallel_tool_calls: planModel.supports_parallel_tool_calls ?? true,
|
|
314
441
|
tool_mode: undefined,
|
|
315
442
|
})).map((model) => {
|
|
316
443
|
delete model.tool_mode;
|
|
@@ -406,8 +533,7 @@ export async function connect({ target = "all", session, home = os.homedir(), cw
|
|
|
406
533
|
await prepareConnectionTools({ target: normalized, autoInstall: options.autoInstall, stdout, tooling });
|
|
407
534
|
}
|
|
408
535
|
if (normalized === "claude" || normalized === "all") {
|
|
409
|
-
await connectClaude({ session, home, cwd, scope, claudeBaseUrl: urls.claudeBaseUrl, stdout });
|
|
410
|
-
reportDesktopApp("claude", { ...tooling, home, stdout });
|
|
536
|
+
await connectClaude({ session, home, cwd, scope, claudeBaseUrl: urls.claudeBaseUrl, stdout, tooling });
|
|
411
537
|
}
|
|
412
538
|
if (normalized === "codex" || normalized === "all") {
|
|
413
539
|
await connectCodex({ session, home, codexHome, codexBaseUrl: urls.codexBaseUrl, stdout, skipCheck: options.skipCheck, tooling });
|
|
@@ -426,12 +552,52 @@ function readClaudeStatus(file) {
|
|
|
426
552
|
}
|
|
427
553
|
}
|
|
428
554
|
|
|
429
|
-
|
|
555
|
+
function readClaudeDesktopStatus(paths) {
|
|
556
|
+
const base = {
|
|
557
|
+
config: paths.claudeDesktopConfig,
|
|
558
|
+
profile: paths.claudeDesktopProfile,
|
|
559
|
+
connected: false,
|
|
560
|
+
};
|
|
561
|
+
try {
|
|
562
|
+
const config = readJsonObject(paths.claudeDesktopConfig);
|
|
563
|
+
const meta = readJsonObject(paths.claudeDesktopLibraryMeta);
|
|
564
|
+
const profile = readJsonObject(paths.claudeDesktopProfile);
|
|
565
|
+
const selected = meta.appliedId === CLAUDE_DESKTOP_PROFILE_ID;
|
|
566
|
+
const connected = config.deploymentMode === "3p"
|
|
567
|
+
&& selected
|
|
568
|
+
&& profile.inferenceProvider === "gateway"
|
|
569
|
+
&& profile.inferenceCredentialKind === "helper-script"
|
|
570
|
+
&& profile.inferenceGatewayAuthScheme === "bearer"
|
|
571
|
+
&& profile.inferenceCredentialHelper === paths.claudeDesktopCredentialHelper
|
|
572
|
+
&& fs.existsSync(paths.claudeDesktopCredentialHelper);
|
|
573
|
+
return {
|
|
574
|
+
...base,
|
|
575
|
+
connected,
|
|
576
|
+
deploymentMode: config.deploymentMode,
|
|
577
|
+
selected,
|
|
578
|
+
baseUrl: profile.inferenceGatewayBaseUrl,
|
|
579
|
+
credential: profile.inferenceCredentialKind,
|
|
580
|
+
};
|
|
581
|
+
} catch (error) {
|
|
582
|
+
return { ...base, error: error.message };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function connectionStatus({
|
|
587
|
+
target = "all",
|
|
588
|
+
home = os.homedir(),
|
|
589
|
+
cwd = process.cwd(),
|
|
590
|
+
codexHome = process.env.CODEX_HOME,
|
|
591
|
+
scope = "user",
|
|
592
|
+
platform = process.platform,
|
|
593
|
+
stdout = console.log,
|
|
594
|
+
} = {}) {
|
|
430
595
|
const normalized = normalizeTarget(target);
|
|
431
|
-
const paths = connectionPaths({ home, cwd, codexHome });
|
|
596
|
+
const paths = connectionPaths({ home, cwd, codexHome, platform });
|
|
432
597
|
if (normalized === "claude" || normalized === "all") {
|
|
433
598
|
const files = scope === "project" ? [paths.claudeProject] : scope === "both" ? [paths.claudeUser, paths.claudeProject] : [paths.claudeUser];
|
|
434
599
|
for (const file of files) stdout(`Claude Code: ${JSON.stringify(readClaudeStatus(file))}`);
|
|
600
|
+
if (paths.claudeDesktopConfig) stdout(`Claude Desktop: ${JSON.stringify(readClaudeDesktopStatus(paths))}`);
|
|
435
601
|
}
|
|
436
602
|
if (normalized === "codex" || normalized === "all") {
|
|
437
603
|
const text = fs.existsSync(paths.codexConfig) ? fs.readFileSync(paths.codexConfig, "utf8") : "";
|
|
@@ -473,7 +639,11 @@ export const __testing = {
|
|
|
473
639
|
ensureToolCli,
|
|
474
640
|
executableName,
|
|
475
641
|
reportDesktopApp,
|
|
642
|
+
configureClaudeDesktop,
|
|
643
|
+
claudeDesktopCredentialHelper,
|
|
644
|
+
readClaudeDesktopStatus,
|
|
476
645
|
removeManagedBlock,
|
|
477
646
|
removeCodexRootAssignments,
|
|
478
647
|
CLAUDE_MANAGED_ENV_KEYS,
|
|
648
|
+
CLAUDE_DESKTOP_PROFILE_ID,
|
|
479
649
|
};
|
package/src/opencode.mjs
CHANGED
|
@@ -10,6 +10,7 @@ const DEFAULT_CONTEXT_LIMIT = 200000;
|
|
|
10
10
|
const DEFAULT_OUTPUT_LIMIT = 8192;
|
|
11
11
|
|
|
12
12
|
function isClaudeModel(model) {
|
|
13
|
+
if (model?.family === "claude") return true;
|
|
13
14
|
const id = model?.id || "";
|
|
14
15
|
const name = model?.display_name || "";
|
|
15
16
|
return /claude/i.test(id) || /claude/i.test(name);
|
|
@@ -23,14 +24,23 @@ function modelOutputLimit(model) {
|
|
|
23
24
|
return model.output_limit || model.max_output_tokens || model.max_tokens || DEFAULT_OUTPUT_LIMIT;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function modelPickerName(model) {
|
|
28
|
+
const displayName = model.display_name || model.id;
|
|
29
|
+
if (model.family === "claude") {
|
|
30
|
+
return `Claude · ${displayName.replace(/^(?:anthropic[ ._-]*)?claude[ ._-]*/i, "")}`;
|
|
31
|
+
}
|
|
32
|
+
if (model.family === "codex") return `Codex · ${displayName}`;
|
|
33
|
+
return displayName;
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
export function buildOpenCodeConfig({ modelConfig, preferredModel = null, tokenEnv = "FOURYI_CLI_TOKEN", orgEnv = "FOURYI_ORG_ID" }) {
|
|
27
|
-
// Register
|
|
28
|
-
// (`Tab` / `/models`) can move between
|
|
37
|
+
// Register every server-approved coding model so OpenCode's native switcher
|
|
38
|
+
// (`Tab` / `/models`) can move between Claude and Codex. Claude stays the default.
|
|
29
39
|
const list = modelConfig.models || [];
|
|
30
40
|
const models = {};
|
|
31
41
|
for (const model of list) {
|
|
32
42
|
const entry = {
|
|
33
|
-
name: model
|
|
43
|
+
name: modelPickerName(model),
|
|
34
44
|
limit: {
|
|
35
45
|
context: modelContextLimit(model),
|
|
36
46
|
output: modelOutputLimit(model),
|
|
@@ -111,7 +121,7 @@ export function ensureOpenCodeRuntime({ home = os.homedir(), stdout = console.lo
|
|
|
111
121
|
|
|
112
122
|
export async function runCode({ session, home = os.homedir(), argv = [], stdout = console.log, preferredModel = null } = {}) {
|
|
113
123
|
if (!session?.token) throw new Error("Not signed in. Run `4yi login`.");
|
|
114
|
-
const modelConfig = await requestJson(session.baseUrl, "/api/cli/models", { token: session.token });
|
|
124
|
+
const modelConfig = await requestJson(session.baseUrl, "/api/cli/models?runtime=opencode", { token: session.token });
|
|
115
125
|
const list = modelConfig.models || [];
|
|
116
126
|
if (list.length === 0) throw new Error("No chat models available for this organization.");
|
|
117
127
|
|