@4yi-dev/cli 0.1.5 → 0.1.6
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/4yi.mjs +23 -2
- package/package.json +1 -1
- package/src/config.mjs +10 -0
- package/src/opencode.mjs +27 -11
package/bin/4yi.mjs
CHANGED
|
@@ -5,10 +5,30 @@ import { runCode } from "../src/opencode.mjs";
|
|
|
5
5
|
const command = process.argv[2] || "help";
|
|
6
6
|
|
|
7
7
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
8
|
-
console.log("Usage: 4yi <login|whoami|logout|code>");
|
|
8
|
+
console.log("Usage: 4yi <login|whoami|logout|code [--model <id>]>");
|
|
9
|
+
console.log(" 4yi code launch OpenCode; switch models live with Tab / /models");
|
|
10
|
+
console.log(" 4yi code --model X pin model X as the default for future sessions");
|
|
9
11
|
process.exit(0);
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
/** Split out `--model <id>` / `--model=<id>` / `-m <id>`; the rest pass through to OpenCode. */
|
|
15
|
+
function parseCodeArgs(args) {
|
|
16
|
+
let preferredModel = null;
|
|
17
|
+
const passthrough = [];
|
|
18
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
19
|
+
const arg = args[i];
|
|
20
|
+
if (arg === "--model" || arg === "-m") {
|
|
21
|
+
preferredModel = args[i + 1] ?? null;
|
|
22
|
+
i += 1;
|
|
23
|
+
} else if (arg.startsWith("--model=")) {
|
|
24
|
+
preferredModel = arg.slice("--model=".length);
|
|
25
|
+
} else {
|
|
26
|
+
passthrough.push(arg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { preferredModel, passthrough };
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
if (command === "--version" || command === "-v") {
|
|
13
33
|
const pkg = await import("../package.json", { with: { type: "json" } });
|
|
14
34
|
console.log(pkg.default.version);
|
|
@@ -27,7 +47,8 @@ try {
|
|
|
27
47
|
console.log("Signed out.");
|
|
28
48
|
} else if (command === "code") {
|
|
29
49
|
const session = loadSession();
|
|
30
|
-
const
|
|
50
|
+
const { preferredModel, passthrough } = parseCodeArgs(process.argv.slice(3));
|
|
51
|
+
const code = await runCode({ session, argv: passthrough, preferredModel });
|
|
31
52
|
process.exit(Number(code || 0));
|
|
32
53
|
} else {
|
|
33
54
|
console.error(`Unknown command: ${command}`);
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -48,3 +48,13 @@ export function writeConfig(config, home = os.homedir()) {
|
|
|
48
48
|
ensureDir(paths.homeDir);
|
|
49
49
|
fs.writeFileSync(paths.configFile, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/** The user's pinned default model id for `4yi code`, or null if unset. */
|
|
53
|
+
export function getPreferredModel(home = os.homedir()) {
|
|
54
|
+
return readConfig(home).preferred_model || null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Persist the pinned default model id, preserving the rest of the config (e.g. session). */
|
|
58
|
+
export function setPreferredModel(model, home = os.homedir()) {
|
|
59
|
+
writeConfig({ ...readConfig(home), preferred_model: model }, home);
|
|
60
|
+
}
|
package/src/opencode.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
-
import { pathsForHome, ensureDir } from "./config.mjs";
|
|
5
|
+
import { pathsForHome, ensureDir, getPreferredModel, setPreferredModel } from "./config.mjs";
|
|
6
6
|
import { requestJson } from "./http.mjs";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_OPENCODE_PACKAGE = "opencode-ai";
|
|
@@ -23,10 +23,12 @@ function modelOutputLimit(model) {
|
|
|
23
23
|
return model.output_limit || model.max_output_tokens || model.max_tokens || DEFAULT_OUTPUT_LIMIT;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function buildOpenCodeConfig({ modelConfig, tokenEnv = "FOURYI_CLI_TOKEN", orgEnv = "FOURYI_ORG_ID" }) {
|
|
26
|
+
export function buildOpenCodeConfig({ modelConfig, preferredModel = null, tokenEnv = "FOURYI_CLI_TOKEN", orgEnv = "FOURYI_ORG_ID" }) {
|
|
27
|
+
// Register EVERY model the server returned so OpenCode's native switcher
|
|
28
|
+
// (`Tab` / `/models`) can move between them. Claude stays the default.
|
|
29
|
+
const list = modelConfig.models || [];
|
|
27
30
|
const models = {};
|
|
28
|
-
const
|
|
29
|
-
for (const model of claudeModels) {
|
|
31
|
+
for (const model of list) {
|
|
30
32
|
models[model.id] = {
|
|
31
33
|
name: model.display_name || model.id,
|
|
32
34
|
limit: {
|
|
@@ -35,9 +37,14 @@ export function buildOpenCodeConfig({ modelConfig, tokenEnv = "FOURYI_CLI_TOKEN"
|
|
|
35
37
|
},
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const has = (id) => !!id && list.some((model) => model.id === id);
|
|
41
|
+
const firstClaude = list.find(isClaudeModel)?.id;
|
|
42
|
+
// Precedence: an explicit/persisted preferred model → server default → first Claude → first model.
|
|
43
|
+
const defaultModel =
|
|
44
|
+
(has(preferredModel) && preferredModel) ||
|
|
45
|
+
(has(modelConfig.default_model) && modelConfig.default_model) ||
|
|
46
|
+
firstClaude ||
|
|
47
|
+
list[0]?.id;
|
|
41
48
|
|
|
42
49
|
return {
|
|
43
50
|
"$schema": "https://opencode.ai/config.json",
|
|
@@ -99,18 +106,27 @@ export function ensureOpenCodeRuntime({ home = os.homedir(), stdout = console.lo
|
|
|
99
106
|
return bin;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
export async function runCode({ session, home = os.homedir(), argv = [], stdout = console.log } = {}) {
|
|
109
|
+
export async function runCode({ session, home = os.homedir(), argv = [], stdout = console.log, preferredModel = null } = {}) {
|
|
103
110
|
if (!session?.token) throw new Error("Not signed in. Run `4yi login`.");
|
|
104
111
|
const modelConfig = await requestJson(session.baseUrl, "/api/cli/models", { token: session.token });
|
|
105
|
-
|
|
106
|
-
if (
|
|
112
|
+
const list = modelConfig.models || [];
|
|
113
|
+
if (list.length === 0) throw new Error("No chat models available for this organization.");
|
|
114
|
+
|
|
115
|
+
// Resolve the model to launch with: an explicit `--model` (validated + persisted)
|
|
116
|
+
// takes precedence over a previously persisted preference; both must be entitled.
|
|
117
|
+
const valid = (id) => !!id && list.some((model) => model.id === id);
|
|
118
|
+
const explicit = valid(preferredModel) ? preferredModel : null;
|
|
119
|
+
const persisted = !explicit && valid(getPreferredModel(home)) ? getPreferredModel(home) : null;
|
|
120
|
+
const effectivePreferred = explicit || persisted || null;
|
|
121
|
+
if (explicit) setPreferredModel(explicit, home);
|
|
122
|
+
if (preferredModel && !explicit) stdout(`Model "${preferredModel}" is not available; using the default instead.`);
|
|
107
123
|
|
|
108
124
|
const paths = pathsForHome(home);
|
|
109
125
|
ensureDir(paths.opencodeConfigDir);
|
|
110
126
|
for (const dir of ["config", "data", "cache", "state"]) {
|
|
111
127
|
ensureDir(path.join(paths.opencodeConfigDir, "xdg", dir));
|
|
112
128
|
}
|
|
113
|
-
fs.writeFileSync(paths.opencodeConfigFile, `${JSON.stringify(buildOpenCodeConfig({ modelConfig }), null, 2)}\n`, { mode: 0o600 });
|
|
129
|
+
fs.writeFileSync(paths.opencodeConfigFile, `${JSON.stringify(buildOpenCodeConfig({ modelConfig, preferredModel: effectivePreferred }), null, 2)}\n`, { mode: 0o600 });
|
|
114
130
|
|
|
115
131
|
const bin = ensureOpenCodeRuntime({ home, stdout });
|
|
116
132
|
stdout("Launching OpenCode with 4YI models...");
|