fix: sync-models will now prompt for API key when there is none, and it won't terminate when there is no opencode.json

This commit is contained in:
nhat.nguyenhong 2026-09-24 14:07:32 +00:00
parent ac29d26e12
commit 78a4f42bc0
2 changed files with 40 additions and 12 deletions

View File

@ -99,6 +99,11 @@ without writing. Each row shows the model id and name plus a second line with it
(`ctx 1M (1000000)`); the catalog carries no pricing fields, so no cost is shown. Needs an
interactive terminal.
Before anything else the script requires a CommandCode API key (from
`provider.commandcode.options.apiKey` or `COMMANDCODE_API_KEY`). If neither is present — including
when `opencode.json` does not exist yet — it exits and points you at the CommandCode Console
(<https://commandcode.ai/studio/provider>) to generate one. It never writes without a key.
Then restart opencode and confirm:
```powershell

View File

@ -7,15 +7,16 @@
// the result into the global opencode config. Deselecting a model removes it from the config.
//
// Run: node scripts/sync-models.mjs (build first: npm run build)
import { readFileSync, writeFileSync, renameSync } from "node:fs";
import { mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { dirname, join } from "node:path";
import readline from "node:readline";
import { createCommandCode } from "../dist/index.js";
import { redact } from "../dist/redact.js";
import { DEFAULT_BASE_URL, DEFAULT_CC_VERSION, DEFAULT_MAX_TOKENS, MODELS_PATH } from "../dist/constants.js";
const CONFIG_PATH = join(homedir(), ".config", "opencode", "opencode.json");
const CONSOLE_URL = "https://commandcode.ai/studio/provider";
const CONCURRENCY = 6;
// 64x64 solid red PNG, used only to provoke a vision-accepting vs vision-rejecting response.
@ -36,8 +37,15 @@ const NO_IMAGE =
const VISION_HEURISTIC = /claude|gpt-5|gemini|grok|qwen.*vl|vision|(?:^|[^a-z])vl(?:[^a-z]|$)|omni|multimodal/i;
function readConfig(path) {
const text = readFileSync(path, "utf8");
return { text, data: JSON.parse(text) };
try {
const text = readFileSync(path, "utf8");
return { text, data: JSON.parse(text), exists: true };
} catch (error) {
// A fresh install has no opencode.json yet; treat it as empty so the console
// guidance / env-key path can still run. Bad JSON or unreadable files still throw.
if (error.code === "ENOENT") return { text: "", data: {}, exists: false };
throw error;
}
}
function detectIndent(text) {
@ -360,23 +368,36 @@ function pickModels(catalog, existingIds) {
}
async function main() {
if (!process.stdin.isTTY || !process.stdout.isTTY) {
console.error("Interactive picker needs a TTY; nothing was written.");
process.exit(1);
}
let config;
let text;
let exists;
try {
({ text, data: config } = readConfig(CONFIG_PATH));
({ text, data: config, exists } = readConfig(CONFIG_PATH));
} catch (error) {
console.error(`Cannot read ${CONFIG_PATH}: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
// Mandatory first step: without a CommandCode key there is nothing to fetch or
// probe, so point a fresh user at the Console before doing anything else.
const key = resolveKey(config, process.env);
if (!key) {
console.error("No API key: set COMMANDCODE_API_KEY or provider.commandcode.options.apiKey");
const where = exists
? `No CommandCode API key in ${CONFIG_PATH}.`
: `No opencode.json found at ${CONFIG_PATH}.`;
console.error(
`${where}\n` +
`Generate an API key in the CommandCode Console: ${CONSOLE_URL}\n` +
(exists
? `Add it as provider.commandcode.options.apiKey, or export COMMANDCODE_API_KEY.`
: `Create that file with provider.commandcode.options.apiKey, or export COMMANDCODE_API_KEY.`) +
`\nNothing was written.`,
);
process.exit(1);
}
if (!process.stdin.isTTY || !process.stdout.isTTY) {
console.error("Interactive picker needs a TTY; nothing was written.");
process.exit(1);
}
@ -447,7 +468,9 @@ async function main() {
config.provider.commandcode.models = merged;
const indent = detectIndent(text);
const trailing = text.endsWith("\n") ? "\n" : "";
const trailing = text === "" || text.endsWith("\n") ? "\n" : "";
// The config directory may not exist yet on a fresh install; create it before the atomic write.
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
writeAtomic(CONFIG_PATH, `${JSON.stringify(config, null, indent)}${trailing}`);
console.log(