Skip to content

Commit 4c4ea42

Browse files
authored
Merge pull request #7 from otto-assistant/feat/dynamic-models-auth-36eb
feat: live Command Code model catalog + API key auth prompt
2 parents 9c29fd4 + c750c1b commit 4c4ea42

5 files changed

Lines changed: 549 additions & 106 deletions

File tree

README.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,25 @@ opencode plugin file://$PWD
6565

6666
## Authenticate
6767

68-
Requires the plugin to be installed (see above). Uses the **Command Code Go plan ($1/mo)** — same browser login as `cmd login`. No Studio API key.
68+
Requires the plugin to be installed (see above). Preferred: **Command Code Go plan ($1/mo)** browser login (same as `cmd login`). You can also paste an API key / session token.
6969

7070
```bash
7171
# Option A — sync from Command Code CLI (recommended)
7272
npm i -g command-code@latest
73-
cmd login # browser → Studio CLI auth (Go plan). Laguna S 2.1 is $0 credits.
73+
cmd login # browser → Studio CLI auth (Go plan)
7474
opencode auth login --provider command-code
7575
# pick "Use existing cmd login session"
7676

7777
# Option B — browser OAuth (Go $1)
7878
opencode auth login --provider command-code
7979
# pick "Login with Command Code (Go $1)"
80+
81+
# Option C — paste API key / session token
82+
opencode auth login --provider command-code
83+
# pick "Enter Command Code API key" and paste into the prompt field
8084
```
8185

82-
Then start OpenCode, pick provider **command-code**, and choose Laguna S 2.1 free:
86+
Then start OpenCode, pick provider **command-code**, and choose any model from the live catalog (`cmd --list-models`). Laguna S 2.1 free is the default when available:
8387

8488
```bash
8589
opencode run "Summarise this repository in five bullets." --model command-code/laguna-s-2.1-free
@@ -92,8 +96,9 @@ Laguna S 2.1 free requires an active Go (or higher) account with credits on file
9296
| | |
9397
|---|---|
9498
| **Gateway proxy** | Talks to `api.commandcode.ai/alpha/generate` — same transport as the `cmd` CLI (`mode=agent`). |
95-
| **Laguna S 2.1 free** | 256k context, reasoning, $0 while the deal lasts. Default aliases resolve here. |
96-
| **Go-plan auth** | Browser OAuth like `cmd login` ($1/mo). Syncs `~/.commandcode/auth.json`. No Studio API key. |
99+
| **Live model catalog** | Discovered from `cmd --list-models` (enriched by the installed CLI’s models.md) — not hardcoded. |
100+
| **Laguna S 2.1 free** | Default when present — 256k context, $0 while the deal lasts. |
101+
| **Auth options** | Go-plan browser OAuth, sync from `cmd login`, or paste an API key / session token. |
97102
| **Agent-grade tools** | OpenCode tool calls park and resume; MCP-prefixed tools are tracked separately. |
98103
| **Attachments** | Images, PDFs, text/binary files from OpenCode. Text-only models get image placeholders (Command Code behavior). |
99104
| **Auto-compact** | Context-fraction tips + tiered client compact before the 256k window overflows. |
@@ -109,9 +114,9 @@ OpenCode
109114
└─ poolside/laguna-s-2.1-free (default)
110115
```
111116

112-
Model catalog: aliases `laguna-s-2.1-free` / `laguna` / `default` (and siblings) map to
113-
`poolside/laguna-s-2.1-free`. Auth credentials are mirrored between OpenCode `auth.json`
114-
and `~/.commandcode/auth.json`.
117+
Model catalog: live from `cmd --list-models`. Aliases like `laguna` / `laguna-s-2.1-free`
118+
resolve to `poolside/laguna-s-2.1-free` when that model is present. Auth credentials are
119+
mirrored between OpenCode `auth.json` and `~/.commandcode/auth.json`.
115120

116121
## Requirements
117122

@@ -157,10 +162,10 @@ Local pin refresh after a release:
157162
## FAQ
158163

159164
**Do I need a Studio API key?**
160-
No. Use Go-plan browser login (or sync from `cmd login`).
165+
No. Prefer Go-plan browser login or sync from `cmd login`. You can also use **Enter Command Code API key** if you already have a session token.
161166

162-
**Is Laguna free?**
163-
Yes — `poolside/laguna-s-2.1-free` is the default free model ($0 while the deal lasts).
167+
**Are only Laguna models available?**
168+
No. The plugin loads the full live catalog from `cmd --list-models`. Laguna is just the default free model used for testing.
164169

165170
**Why isn’t Command Code listed without the plugin?**
166171
OpenCode doesn’t ship Command Code as a built-in provider; this plugin registers it.

src/index.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/**
22
* OpenCode Command Code Auth Plugin
33
*
4-
* Enables Command Code (Laguna S 2.1 free and gateway models) inside OpenCode via:
5-
* 1. Command Code Go-plan browser OAuth (`cmd login`) — no API key
4+
* Enables Command Code models inside OpenCode via:
5+
* 1. Go-plan browser OAuth, CLI session sync, or pasted API key
66
* 2. Local OpenAI-compatible proxy → api.commandcode.ai /alpha/generate
7-
* 3. Tools/MCP park-resume, attachments, compact, and usage accounting
7+
* 3. Dynamic model catalog from `cmd --list-models`
8+
* 4. Tools/MCP park-resume, attachments, compact, and usage accounting
89
*
910
* Register in opencode.json:
1011
* { "plugin": ["@otto-assistant/opencode-commandcode"] }
@@ -16,6 +17,8 @@ import {
1617
resetPendingCommandLogin,
1718
startCommandBrowserLogin,
1819
syncCommandCodeCredentialsToOpenCode,
20+
validateCommandApiKey,
21+
writeCommandCodeAuth,
1922
} from "./auth-login.js";
2023
import {
2124
DEFAULT_MODEL_ID,
@@ -33,6 +36,7 @@ import {
3336
buildConfigVariants,
3437
buildEffortVariants,
3538
getCommandModels,
39+
refreshCommandModels,
3640
type CommandModel,
3741
} from "./models.js";
3842
import {
@@ -265,7 +269,7 @@ async function loadRuntime(
265269
provider?: { models?: Record<string, unknown> },
266270
): Promise<{ port: number; providerModels: Record<string, unknown> } | undefined> {
267271
await resolveAccessToken(input, getAuth);
268-
const models = getCommandModels();
272+
const models = refreshCommandModels();
269273
await startProxy(async () => resolveAccessToken(input, getAuth));
270274
const providerModels = buildProviderModels(models);
271275
if (provider) provider.models = providerModels;
@@ -310,7 +314,8 @@ export const CommandCodePlugin: Plugin = async (
310314
return synced?.access ?? null;
311315
});
312316

313-
// Always seed Laguna catalog so the provider is discoverable pre-login.
317+
// Always seed the live catalog so the provider is discoverable.
318+
refreshCommandModels();
314319
ensureProviderConfig(config as Record<string, any>, getCommandModels());
315320
},
316321

@@ -413,7 +418,7 @@ export const CommandCodePlugin: Plugin = async (
413418
return {
414419
url: current.url,
415420
instructions:
416-
"Opens Command Code Studio CLI login (Go plan, $1/mo). Approve in the browser — session is posted back automatically. Laguna S 2.1 free costs $0 credits. No API key.",
421+
"Opens Command Code Studio CLI login (Go plan, $1/mo). Approve in the browser — session is posted back automatically. Live models load from `cmd --list-models` after login.",
417422
method: "auto" as const,
418423
async callback() {
419424
try {
@@ -443,7 +448,7 @@ export const CommandCodePlugin: Plugin = async (
443448
return {
444449
url: "https://commandcode.ai/docs/quickstart",
445450
instructions:
446-
"If you already ran `npm i -g command-code@latest && cmd login` (Go $1 plan), click Complete to sync ~/.commandcode/auth.json. No API key.",
451+
"If you already ran `npm i -g command-code@latest && cmd login` (Go $1 plan), click Complete to sync ~/.commandcode/auth.json.",
447452
method: "auto" as const,
448453
async callback() {
449454
const again = syncCommandCodeCredentialsToOpenCode();
@@ -458,6 +463,43 @@ export const CommandCodePlugin: Plugin = async (
458463
};
459464
},
460465
},
466+
{
467+
type: "api",
468+
label: "Enter Command Code API key",
469+
prompts: [
470+
{
471+
type: "text",
472+
key: "apiKey",
473+
message: "Paste your Command Code API key / session token",
474+
placeholder: "user_… or Studio key",
475+
validate: (value) => {
476+
if (!value || !value.trim()) return "API key is required";
477+
if (value.trim().length < 16) return "Key looks too short";
478+
return undefined;
479+
},
480+
},
481+
],
482+
async authorize(inputs) {
483+
const key = inputs?.apiKey?.trim() || inputs?.key?.trim() || "";
484+
if (!key) return { type: "failed" as const };
485+
const check = await validateCommandApiKey(key);
486+
if (!check.valid) {
487+
log.warn(
488+
"[opencode-commandcode] API key validation failed",
489+
check.error,
490+
);
491+
return { type: "failed" as const };
492+
}
493+
writeCommandCodeAuth({
494+
key,
495+
access: key,
496+
refresh: "api-key",
497+
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
498+
});
499+
refreshCommandModels();
500+
return { type: "success" as const, key };
501+
},
502+
},
461503
],
462504
},
463505
};
@@ -466,7 +508,12 @@ export const CommandCodePlugin: Plugin = async (
466508
export default CommandCodePlugin;
467509

468510
export { detectCommandCode } from "./detect.js";
469-
export { getCommandModels, COMMAND_CODE_MODELS } from "./models.js";
511+
export {
512+
getCommandModels,
513+
listCommandCodeModels,
514+
refreshCommandModels,
515+
invalidateCommandModelCache,
516+
} from "./models.js";
470517
export {
471518
startProxy,
472519
stopProxy,

0 commit comments

Comments
 (0)