opencommand-plugin 0.0.10 → 0.0.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/LICENSE +21 -0
- package/README.md +152 -28
- package/dist/index.d.ts +6 -1
- package/dist/index.js +140 -41
- package/dist/proxy/opencommand-proxy-darwin-amd64 +0 -0
- package/dist/proxy/opencommand-proxy-darwin-arm64 +0 -0
- package/dist/proxy/opencommand-proxy-linux-amd64 +0 -0
- package/dist/proxy/opencommand-proxy-linux-arm64 +0 -0
- package/dist/proxy/opencommand-proxy-windows-amd64.exe +0 -0
- package/package.json +4 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenCommand
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,20 +1,58 @@
|
|
|
1
|
-
# OpenCommand
|
|
1
|
+
# OpenCommand
|
|
2
2
|
|
|
3
|
-
OpenCode
|
|
3
|
+
OpenCommand is één OpenCode/NPM-plugin voor CommandCode. De plugin registreert de provider `opencommand`, start lokaal een OpenAI-compatible proxy en bundelt die proxy-binaries zelf in het NPM-package.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Er is dus geen losse proxy-installatie of lokale repo-checkout meer nodig in OpenCode. De Go-code staat nog wel in `proxy/`, maar alleen als broncode: bij `npm run build:proxy` worden de binaries naar `dist/proxy/` gebouwd en met `opencommand-plugin` gepubliceerd.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Uses a dynamic local port and exposes `http://localhost:<port>/v1` to OpenCode.
|
|
9
|
-
- Stores the CommandCode API token locally in `~/.opencommand/opencommand-secrets.json`.
|
|
10
|
-
- Supports `COMMAND_CODE_TOKEN` and `COMMANDCODE_API_KEY` environment overrides.
|
|
11
|
-
- Attempts local browser cookie discovery for CommandCode Studio usage scraping.
|
|
12
|
-
- Registers OpenCode provider `opencommand` using `@ai-sdk/openai-compatible`.
|
|
13
|
-
- Detects the active CommandCode plan and registers the matching OpenCode model list.
|
|
7
|
+
## Huidige structuur
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
```text
|
|
10
|
+
.
|
|
11
|
+
├── bin/opencode-plugin.js # OpenCode plugin entrypoint
|
|
12
|
+
├── src/index.ts # plugin runtime/provider/proxy manager
|
|
13
|
+
├── proxy/ # Go source voor de lokale proxy
|
|
14
|
+
├── scripts/build-proxy-binaries.mjs
|
|
15
|
+
├── tests/ # Jest tests voor plugin/runtime
|
|
16
|
+
├── package.json # NPM package: opencommand-plugin
|
|
17
|
+
└── dist/ # build output, niet getrackt
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Gebruik met OpenCode
|
|
21
|
+
|
|
22
|
+
Gebruik na publicatie:
|
|
23
|
+
|
|
24
|
+
```jsonc
|
|
25
|
+
{
|
|
26
|
+
"plugin": ["opencommand-plugin@latest"],
|
|
27
|
+
"model": "opencommand/deepseek/deepseek-v4-flash",
|
|
28
|
+
"small_model": "opencommand/deepseek/deepseek-v4-flash"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Voor een vaste versie kun je `opencommand-plugin@0.0.12` pinnen.
|
|
33
|
+
|
|
34
|
+
Bij OpenCode-start doet de plugin dit automatisch:
|
|
35
|
+
|
|
36
|
+
1. kiest de juiste gebundelde proxybinary voor macOS/Linux/Windows;
|
|
37
|
+
2. start de proxy op een lokale poort;
|
|
38
|
+
3. schrijft `~/.opencommand/proxy-config.json` met de actieve URL;
|
|
39
|
+
4. registreert provider `opencommand` met `http://localhost:<port>/v1`;
|
|
40
|
+
5. detecteert het actieve CommandCode-plan en registreert de bijpassende modellen.
|
|
41
|
+
|
|
42
|
+
OpenCode moet opnieuw worden gestart nadat `opencode.jsonc` of de pluginversie is gewijzigd, want plugin-config wordt bij startup geladen.
|
|
43
|
+
|
|
44
|
+
## Auth-model
|
|
45
|
+
|
|
46
|
+
Gebruik primair een CommandCode token (`user_...`). Zet secrets niet in gesyncte `opencode.jsonc`.
|
|
16
47
|
|
|
17
|
-
|
|
48
|
+
Prioriteit:
|
|
49
|
+
|
|
50
|
+
1. inkomende `Authorization: Bearer user_...` requests;
|
|
51
|
+
2. `COMMAND_CODE_TOKEN`;
|
|
52
|
+
3. alias `COMMANDCODE_API_KEY`;
|
|
53
|
+
4. lokale secret `~/.opencommand/opencommand-secrets.json` met sleutel `opencommand.command_code_token`.
|
|
54
|
+
|
|
55
|
+
Voorbeeld:
|
|
18
56
|
|
|
19
57
|
```json
|
|
20
58
|
{
|
|
@@ -22,39 +60,125 @@ Primary token key:
|
|
|
22
60
|
}
|
|
23
61
|
```
|
|
24
62
|
|
|
25
|
-
|
|
63
|
+
De plugin maakt `~/.opencommand` met `0700` permissies en schrijft secrets met `0600`. `CC_SESSION_COOKIE` is alleen bedoeld voor CommandCode Studio usage scraping, niet als primaire model-auth.
|
|
64
|
+
|
|
65
|
+
## Lokale bestanden
|
|
66
|
+
|
|
67
|
+
OpenCommand gebruikt alleen lokale bestanden buiten de repo:
|
|
68
|
+
|
|
69
|
+
- `~/.opencommand/opencommand-secrets.json` — CommandCode token en optionele Studio-cookie.
|
|
70
|
+
- `~/.opencommand/proxy-config.json` — actieve proxy URL/poort.
|
|
71
|
+
- `~/.opencommand/model-cache.json` — laatst bekende plan/model-lijst.
|
|
72
|
+
|
|
73
|
+
Voorbeeld `proxy-config.json`:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"url": "http://localhost:3000",
|
|
78
|
+
"port": 3000,
|
|
79
|
+
"updatedAt": "2026-05-19T20:00:00.000Z"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Browser-cookie extractie
|
|
26
84
|
|
|
27
|
-
|
|
85
|
+
Voor usage scraping kan de plugin op macOS proberen een CommandCode Studio-cookie te vinden in Chrome, Comet, Brave, Edge of Firefox. Gevonden cookies worden lokaal opgeslagen als:
|
|
28
86
|
|
|
29
|
-
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"opencommand.cc_session_cookie": "..."
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Cookie-waarden worden niet gelogd en niet in `opencode.jsonc` opgeslagen.
|
|
94
|
+
|
|
95
|
+
## Model-detectie per plan
|
|
96
|
+
|
|
97
|
+
De proxy/plugin vraagt `/alpha/billing/subscriptions` op en filtert modellen per plan:
|
|
30
98
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
99
|
+
- `individual-go`: open-source modellen.
|
|
100
|
+
- `individual-pro`: open-source + premium, zonder Opus.
|
|
101
|
+
- `individual-max`, `individual-ultra`, `teams-pro`: open-source + premium inclusief Opus.
|
|
34
102
|
|
|
35
|
-
|
|
103
|
+
Als plan-detectie faalt, valt OpenCommand terug op de lokale cached/static Go/open-source lijst zodat OpenCode alsnog kan starten.
|
|
36
104
|
|
|
37
|
-
##
|
|
105
|
+
## Lokale API
|
|
106
|
+
|
|
107
|
+
De proxy biedt OpenAI-compatible endpoints:
|
|
108
|
+
|
|
109
|
+
- `POST /v1/chat/completions` — streaming en non-streaming chat completions.
|
|
110
|
+
- `GET /v1/models` — plan-gefilterde OpenAI model-lijst.
|
|
111
|
+
- `GET /v1/models/allowed` — CommandCode Studio allowed-models/usage info.
|
|
112
|
+
- `GET /v1/account/usage` — usage summary.
|
|
113
|
+
- `GET /healthz` — health check.
|
|
114
|
+
|
|
115
|
+
De proxy vertaalt OpenAI `messages`, `tools`, `tool_choice` en streaming responses naar/van het huidige CommandCode CLI-protocol.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
Root package:
|
|
38
120
|
|
|
39
121
|
```bash
|
|
40
122
|
npm ci
|
|
123
|
+
npm test -- --runInBand
|
|
41
124
|
npm run build
|
|
125
|
+
npm run build:proxy
|
|
126
|
+
npm pack --dry-run
|
|
42
127
|
```
|
|
43
128
|
|
|
44
|
-
|
|
129
|
+
Go proxy tests:
|
|
45
130
|
|
|
46
131
|
```bash
|
|
47
|
-
|
|
132
|
+
cd proxy
|
|
133
|
+
go vet ./...
|
|
134
|
+
go test -race ./...
|
|
135
|
+
go build -o proxy main.go
|
|
48
136
|
```
|
|
49
137
|
|
|
50
|
-
|
|
138
|
+
`npm pack` bevat alleen de runtimebestanden:
|
|
139
|
+
|
|
140
|
+
- `README.md`
|
|
141
|
+
- `package.json`
|
|
142
|
+
- `bin/opencode-plugin.js`
|
|
143
|
+
- `dist/index.js`
|
|
144
|
+
- `dist/index.d.ts`
|
|
145
|
+
- `dist/proxy/opencommand-proxy-*`
|
|
146
|
+
|
|
147
|
+
De bronmappen `src/`, `tests/` en `proxy/` worden niet gepubliceerd in het NPM-package.
|
|
148
|
+
|
|
149
|
+
## Publicatie
|
|
150
|
+
|
|
151
|
+
Package: `opencommand-plugin`.
|
|
152
|
+
|
|
153
|
+
De publish workflow staat in `.github/workflows/npm-publish.yml`. Deze workflow bouwt/test de root package, bouwt de proxybinaries en controleert dat alle vereiste bestanden in het NPM-package zitten.
|
|
154
|
+
|
|
155
|
+
## Release notes
|
|
156
|
+
|
|
157
|
+
### v0.0.12
|
|
158
|
+
|
|
159
|
+
- OpenAI-compatible chat schema voor OpenCode requests.
|
|
160
|
+
- Deterministische samenvoeging van system/developer messages naar `params.system`.
|
|
161
|
+
- Conversie van OpenAI tool calls/results naar CommandCode `tool-call`/`tool-result` parts.
|
|
162
|
+
- Conversie van OpenAI `tools` en `tool_choice` naar CommandCode tool schema.
|
|
163
|
+
- Fixes voor CommandCode 400-errors rond `params.messages[0].role`, string content en `params.tools[0].name`.
|
|
164
|
+
- Repo-layout opgeschoond: root is nu het NPM/OpenCode package; `proxy/` is alleen Go-source voor bundled binaries.
|
|
165
|
+
|
|
166
|
+
### v0.0.11
|
|
167
|
+
|
|
168
|
+
- NPM-package bundelt proxybinaries voor macOS, Linux en Windows.
|
|
169
|
+
- OpenCode heeft alleen `opencommand-plugin` nodig; geen losse proxy-installatie.
|
|
170
|
+
- Proxy vertaalt CommandCode streaming events naar OpenAI-compatible chat-completion chunks/responses.
|
|
171
|
+
|
|
172
|
+
### v0.0.10
|
|
51
173
|
|
|
52
|
-
|
|
174
|
+
- Plugin start de lokale proxy vanuit de OpenCode config hook.
|
|
175
|
+
- Gesyncte `opencode.jsonc` heeft geen absolute lokale `file://` plugin path meer nodig.
|
|
53
176
|
|
|
54
|
-
|
|
177
|
+
### v0.0.9
|
|
55
178
|
|
|
56
|
-
|
|
57
|
-
|
|
179
|
+
- CommandCode protocol headers bijgewerkt naar CLI-versie `0.26.3`.
|
|
180
|
+
- Productie CLI-environment en OpenCommand project slug toegevoegd.
|
|
58
181
|
|
|
59
|
-
|
|
182
|
+
### v0.0.8
|
|
60
183
|
|
|
184
|
+
- Non-blocking OpenCode startup: cached/static models direct, proxy/plan refresh op de achtergrond.
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,9 @@ interface CookiePair {
|
|
|
38
38
|
name: string;
|
|
39
39
|
value: string;
|
|
40
40
|
}
|
|
41
|
+
interface CommandCodeCookieExtractor {
|
|
42
|
+
extractCommandCodeCookie(): Promise<string | undefined>;
|
|
43
|
+
}
|
|
41
44
|
export declare const COMMAND_CODE_GO_PLAN_MODELS: CommandCodeModelDefinition[];
|
|
42
45
|
export declare class ProxyManager {
|
|
43
46
|
private proxyProcess;
|
|
@@ -77,12 +80,13 @@ export declare class BrowserCookieExtractor {
|
|
|
77
80
|
export declare class OpenCommandPlugin {
|
|
78
81
|
private proxyManager;
|
|
79
82
|
private secretStorage;
|
|
83
|
+
private browserCookieExtractor;
|
|
80
84
|
private startPromise;
|
|
81
85
|
private preloadPromise;
|
|
82
86
|
private readonly commandCodeTokenKey;
|
|
83
87
|
private readonly legacyTokenKey;
|
|
84
88
|
private readonly sessionCookieKey;
|
|
85
|
-
constructor(proxyBinaryPath?: string, storageDir?: string);
|
|
89
|
+
constructor(proxyBinaryPath?: string, storageDir?: string, browserCookieExtractor?: CommandCodeCookieExtractor);
|
|
86
90
|
activate(): Promise<void>;
|
|
87
91
|
ensureStarted(): Promise<ProxyConfig | undefined>;
|
|
88
92
|
preloadForOpenCode(): void;
|
|
@@ -100,6 +104,7 @@ export declare class OpenCommandPlugin {
|
|
|
100
104
|
private restartIfPossible;
|
|
101
105
|
getProxyConfig(): string;
|
|
102
106
|
}
|
|
107
|
+
export declare function resolveProxyBinaryPath(): string;
|
|
103
108
|
export declare function fetchOpenCommandModels(baseURL: string): Promise<CommandCodeModelDefinition[] | undefined>;
|
|
104
109
|
export declare function commandCodeModelsForPlan(planID: string): CommandCodeModelDefinition[];
|
|
105
110
|
export declare function fetchCommandCodePlanModels(commandCodeToken: string, apiBaseURL?: string): Promise<CommandCodeModelDefinition[] | undefined>;
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,14 @@ import * as fsp from "fs/promises";
|
|
|
5
5
|
import * as net from "net";
|
|
6
6
|
import * as os from "os";
|
|
7
7
|
import * as path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
8
9
|
const PROVIDER_ID = "opencommand";
|
|
9
10
|
const PROVIDER_NAME = "CommandCode";
|
|
10
11
|
const PROVIDER_API_KEY_PLACEHOLDER = "opencommand";
|
|
11
12
|
const DEFAULT_PROXY_BASE_URL = "http://localhost:3000/v1";
|
|
12
13
|
const COMMAND_CODE_API_BASE_URL = "https://api.commandcode.ai";
|
|
14
|
+
const COMMAND_CODE_CLIENT_VERSION = "0.26.3";
|
|
15
|
+
const COMMAND_CODE_PROJECT_SLUG = "opencommand";
|
|
13
16
|
const MODEL_CACHE_MAX_AGE_MS = 10 * 60 * 1000;
|
|
14
17
|
function isDebugEnabled() {
|
|
15
18
|
return ["1", "true", "yes", "on"].includes((process.env.OPENCOMMAND_DEBUG ?? "").trim().toLowerCase());
|
|
@@ -123,7 +126,7 @@ export const COMMAND_CODE_GO_PLAN_MODELS = [
|
|
|
123
126
|
];
|
|
124
127
|
const COMMAND_CODE_PREMIUM_MODELS = [
|
|
125
128
|
{
|
|
126
|
-
id: "
|
|
129
|
+
id: "claude-opus-4-7",
|
|
127
130
|
name: "Claude Opus 4.7",
|
|
128
131
|
description: "Premium Claude Opus coding and reasoning",
|
|
129
132
|
category: "premium",
|
|
@@ -133,7 +136,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
133
136
|
cost: { input: 0, output: 0 },
|
|
134
137
|
},
|
|
135
138
|
{
|
|
136
|
-
id: "
|
|
139
|
+
id: "claude-opus-4-6",
|
|
137
140
|
name: "Claude Opus 4.6",
|
|
138
141
|
description: "Premium Claude Opus coding and reasoning",
|
|
139
142
|
category: "premium",
|
|
@@ -143,7 +146,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
143
146
|
cost: { input: 0, output: 0 },
|
|
144
147
|
},
|
|
145
148
|
{
|
|
146
|
-
id: "
|
|
149
|
+
id: "claude-sonnet-4-6",
|
|
147
150
|
name: "Claude Sonnet 4.6",
|
|
148
151
|
description: "Premium Claude Sonnet coding",
|
|
149
152
|
category: "premium",
|
|
@@ -153,7 +156,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
153
156
|
cost: { input: 0, output: 0 },
|
|
154
157
|
},
|
|
155
158
|
{
|
|
156
|
-
id: "
|
|
159
|
+
id: "claude-haiku-4-5-20251001",
|
|
157
160
|
name: "Claude Haiku 4.5",
|
|
158
161
|
description: "Fast premium Claude model",
|
|
159
162
|
category: "premium",
|
|
@@ -162,7 +165,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
162
165
|
cost: { input: 0, output: 0 },
|
|
163
166
|
},
|
|
164
167
|
{
|
|
165
|
-
id: "
|
|
168
|
+
id: "gpt-5.5",
|
|
166
169
|
name: "GPT-5.5",
|
|
167
170
|
description: "Premium OpenAI reasoning model",
|
|
168
171
|
category: "premium",
|
|
@@ -172,7 +175,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
172
175
|
cost: { input: 0, output: 0 },
|
|
173
176
|
},
|
|
174
177
|
{
|
|
175
|
-
id: "
|
|
178
|
+
id: "gpt-5.4",
|
|
176
179
|
name: "GPT-5.4",
|
|
177
180
|
description: "Premium OpenAI coding model",
|
|
178
181
|
category: "premium",
|
|
@@ -182,7 +185,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
182
185
|
cost: { input: 0, output: 0 },
|
|
183
186
|
},
|
|
184
187
|
{
|
|
185
|
-
id: "
|
|
188
|
+
id: "gpt-5.4-mini",
|
|
186
189
|
name: "GPT-5.4 Mini",
|
|
187
190
|
description: "Efficient premium OpenAI coding model",
|
|
188
191
|
category: "premium",
|
|
@@ -191,7 +194,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
|
|
|
191
194
|
cost: { input: 0, output: 0 },
|
|
192
195
|
},
|
|
193
196
|
{
|
|
194
|
-
id: "
|
|
197
|
+
id: "gpt-5.3-codex",
|
|
195
198
|
name: "GPT-5.3 Codex",
|
|
196
199
|
description: "Premium OpenAI Codex coding model",
|
|
197
200
|
category: "premium",
|
|
@@ -355,10 +358,12 @@ export class SecretStorage {
|
|
|
355
358
|
}
|
|
356
359
|
async writeStore(store) {
|
|
357
360
|
const dir = path.dirname(this.filePath);
|
|
358
|
-
await fsp.mkdir(dir, { recursive: true });
|
|
361
|
+
await fsp.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
362
|
+
await fsp.chmod(dir, 0o700).catch(() => undefined);
|
|
359
363
|
await fsp.writeFile(this.filePath, JSON.stringify(store, null, 2), {
|
|
360
364
|
mode: 0o600,
|
|
361
365
|
});
|
|
366
|
+
await fsp.chmod(this.filePath, 0o600).catch(() => undefined);
|
|
362
367
|
}
|
|
363
368
|
async set(key, value) {
|
|
364
369
|
const store = await this.readStore();
|
|
@@ -559,11 +564,12 @@ const firefoxCookieQuery = [
|
|
|
559
564
|
"SELECT host, name, value, '' FROM moz_cookies WHERE host LIKE '%commandcode.ai%';",
|
|
560
565
|
].join("\n");
|
|
561
566
|
export class OpenCommandPlugin {
|
|
562
|
-
constructor(proxyBinaryPath = resolveProxyBinaryPath(), storageDir) {
|
|
567
|
+
constructor(proxyBinaryPath = resolveProxyBinaryPath(), storageDir, browserCookieExtractor = new BrowserCookieExtractor()) {
|
|
563
568
|
this.commandCodeTokenKey = "opencommand.command_code_token";
|
|
564
569
|
this.legacyTokenKey = "opencommand.cc_session_token";
|
|
565
570
|
this.sessionCookieKey = "opencommand.cc_session_cookie";
|
|
566
571
|
this.proxyManager = new ProxyManager(proxyBinaryPath);
|
|
572
|
+
this.browserCookieExtractor = browserCookieExtractor;
|
|
567
573
|
const dir = storageDir || `${process.env.HOME || "/tmp"}/.opencommand`;
|
|
568
574
|
this.secretStorage = new SecretStorage(dir);
|
|
569
575
|
}
|
|
@@ -644,7 +650,21 @@ export class OpenCommandPlugin {
|
|
|
644
650
|
return token;
|
|
645
651
|
}
|
|
646
652
|
async loadConfiguredSessionCookie() {
|
|
647
|
-
|
|
653
|
+
const configuredCookie = firstDefined(process.env.CC_SESSION_COOKIE, await this.secretStorage.get(this.sessionCookieKey));
|
|
654
|
+
if (configuredCookie)
|
|
655
|
+
return configuredCookie;
|
|
656
|
+
try {
|
|
657
|
+
const browserCookie = await this.browserCookieExtractor.extractCommandCodeCookie();
|
|
658
|
+
if (!browserCookie)
|
|
659
|
+
return undefined;
|
|
660
|
+
await this.secretStorage.set(this.sessionCookieKey, browserCookie);
|
|
661
|
+
debugLog("✓ CommandCode Studio cookie discovered from local browser storage");
|
|
662
|
+
return browserCookie;
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
debugLog("Could not discover CommandCode Studio cookie from browser storage:", error);
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
648
668
|
}
|
|
649
669
|
saveOpenCodeConfig(config) {
|
|
650
670
|
const proxyUrl = `http://localhost:${config.port}`;
|
|
@@ -704,15 +724,49 @@ function firstDefined(...values) {
|
|
|
704
724
|
}
|
|
705
725
|
return undefined;
|
|
706
726
|
}
|
|
707
|
-
function resolveProxyBinaryPath() {
|
|
727
|
+
export function resolveProxyBinaryPath() {
|
|
728
|
+
if (process.env.OPENCOMMAND_PROXY_PATH?.trim()) {
|
|
729
|
+
return process.env.OPENCOMMAND_PROXY_PATH.trim();
|
|
730
|
+
}
|
|
708
731
|
const candidates = [
|
|
709
|
-
|
|
732
|
+
bundledProxyBinaryPath(),
|
|
710
733
|
path.join(os.homedir(), ".opencommand", "proxy"),
|
|
711
|
-
path.join(process.cwd(), "
|
|
734
|
+
path.join(process.cwd(), "proxy", "proxy"),
|
|
712
735
|
path.join(process.cwd(), "proxy"),
|
|
713
736
|
].filter((candidate) => Boolean(candidate));
|
|
714
737
|
return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[candidates.length - 1];
|
|
715
738
|
}
|
|
739
|
+
function bundledProxyBinaryPath() {
|
|
740
|
+
const goos = process.platform === "win32" ? "windows" : process.platform;
|
|
741
|
+
const goarch = process.arch === "x64" ? "amd64" : process.arch;
|
|
742
|
+
if (!isSupportedBundledProxyTarget(goos, goarch))
|
|
743
|
+
return undefined;
|
|
744
|
+
const ext = goos === "windows" ? ".exe" : "";
|
|
745
|
+
const binaryName = `opencommand-proxy-${goos}-${goarch}${ext}`;
|
|
746
|
+
return path.join(currentModuleDirectory(), "proxy", binaryName);
|
|
747
|
+
}
|
|
748
|
+
function currentModuleDirectory() {
|
|
749
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
750
|
+
try {
|
|
751
|
+
Error.prepareStackTrace = (_, stack) => stack;
|
|
752
|
+
const stack = new Error().stack;
|
|
753
|
+
const fileName = stack.find((frame) => frame.getFileName()?.endsWith(path.join("src", "index.ts")))?.getFileName()
|
|
754
|
+
?? stack.find((frame) => frame.getFileName()?.endsWith(path.join("dist", "index.js")))?.getFileName()
|
|
755
|
+
?? stack[0]?.getFileName();
|
|
756
|
+
return fileName ? path.dirname(normalizeStackFileName(fileName)) : process.cwd();
|
|
757
|
+
}
|
|
758
|
+
finally {
|
|
759
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function normalizeStackFileName(fileName) {
|
|
763
|
+
return fileName.startsWith("file:") ? fileURLToPath(fileName) : fileName;
|
|
764
|
+
}
|
|
765
|
+
function isSupportedBundledProxyTarget(goos, goarch) {
|
|
766
|
+
return ((goos === "darwin" && (goarch === "arm64" || goarch === "amd64")) ||
|
|
767
|
+
(goos === "linux" && (goarch === "arm64" || goarch === "amd64")) ||
|
|
768
|
+
(goos === "windows" && goarch === "amd64"));
|
|
769
|
+
}
|
|
716
770
|
function openCodeModelConfig(model) {
|
|
717
771
|
return {
|
|
718
772
|
id: model.id,
|
|
@@ -780,6 +834,7 @@ export async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL =
|
|
|
780
834
|
try {
|
|
781
835
|
const response = await fetch(`${apiBaseURL.replace(/\/$/, "")}/alpha/billing/subscriptions`, {
|
|
782
836
|
headers: {
|
|
837
|
+
...commandCodeClientHeaders(),
|
|
783
838
|
Accept: "application/json",
|
|
784
839
|
Authorization: `Bearer ${commandCodeToken}`,
|
|
785
840
|
},
|
|
@@ -795,6 +850,19 @@ export async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL =
|
|
|
795
850
|
return undefined;
|
|
796
851
|
}
|
|
797
852
|
}
|
|
853
|
+
function commandCodeClientHeaders() {
|
|
854
|
+
return {
|
|
855
|
+
"User-Agent": `CommandCodeCLI/${COMMAND_CODE_CLIENT_VERSION} OpenCommandPlugin`,
|
|
856
|
+
"X-Command-Code-Version": COMMAND_CODE_CLIENT_VERSION,
|
|
857
|
+
"X-CLI-Environment": "production",
|
|
858
|
+
"X-Project-Slug": COMMAND_CODE_PROJECT_SLUG,
|
|
859
|
+
"X-CommandCode-Client": "cli",
|
|
860
|
+
"X-CommandCode-Client-Version": COMMAND_CODE_CLIENT_VERSION,
|
|
861
|
+
"X-CommandCode-CLI-Version": COMMAND_CODE_CLIENT_VERSION,
|
|
862
|
+
"X-Command-Code-Client-Version": COMMAND_CODE_CLIENT_VERSION,
|
|
863
|
+
"X-Command-Code-CLI-Version": COMMAND_CODE_CLIENT_VERSION,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
798
866
|
export function parseCommandCodePlanID(body) {
|
|
799
867
|
const root = body;
|
|
800
868
|
if (typeof root?.planId === "string" && root.planId.trim())
|
|
@@ -871,6 +939,29 @@ function writeCachedOpenCommandModels(models) {
|
|
|
871
939
|
function openCommandModelCachePath() {
|
|
872
940
|
return path.join(process.env.HOME || "/tmp", ".opencommand", "model-cache.json");
|
|
873
941
|
}
|
|
942
|
+
function proxyBaseURLForRegistration() {
|
|
943
|
+
const runtimeConfig = runtimePlugin?.getCurrentProxyConfig();
|
|
944
|
+
if (runtimeConfig)
|
|
945
|
+
return `http://localhost:${runtimeConfig.port}/v1`;
|
|
946
|
+
const persistedBaseURL = readPersistedProxyBaseURL();
|
|
947
|
+
return persistedBaseURL ?? DEFAULT_PROXY_BASE_URL;
|
|
948
|
+
}
|
|
949
|
+
function readPersistedProxyBaseURL() {
|
|
950
|
+
try {
|
|
951
|
+
const configPath = path.join(process.env.HOME || "/tmp", ".opencommand", "proxy-config.json");
|
|
952
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
953
|
+
if (typeof config.port === "number" && Number.isInteger(config.port) && config.port > 0 && config.port < 65536) {
|
|
954
|
+
return `http://localhost:${config.port}/v1`;
|
|
955
|
+
}
|
|
956
|
+
if (typeof config.url === "string" && config.url.trim()) {
|
|
957
|
+
return `${config.url.replace(/\/$/, "")}/v1`;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
return undefined;
|
|
964
|
+
}
|
|
874
965
|
function parseProxyPort(value) {
|
|
875
966
|
const port = Number(value);
|
|
876
967
|
return Number.isInteger(port) && port > 0 && port < 65536 ? port : undefined;
|
|
@@ -880,35 +971,43 @@ function getRuntimePlugin() {
|
|
|
880
971
|
runtimePlugin ?? (runtimePlugin = new OpenCommandPlugin());
|
|
881
972
|
return runtimePlugin;
|
|
882
973
|
}
|
|
883
|
-
export const OpenCommandOpenCodePlugin = async () =>
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
974
|
+
export const OpenCommandOpenCodePlugin = async () => {
|
|
975
|
+
const baseURL = proxyBaseURLForRegistration();
|
|
976
|
+
return {
|
|
977
|
+
provider: {
|
|
978
|
+
[PROVIDER_ID]: {
|
|
979
|
+
npm: "@ai-sdk/openai-compatible",
|
|
980
|
+
name: PROVIDER_NAME,
|
|
981
|
+
options: {
|
|
982
|
+
apiKey: PROVIDER_API_KEY_PLACEHOLDER,
|
|
983
|
+
baseURL,
|
|
984
|
+
},
|
|
985
|
+
models: Object.fromEntries(COMMAND_CODE_GO_PLAN_MODELS.map((model) => [model.id, openCodeModelConfig(model)])),
|
|
891
986
|
},
|
|
892
|
-
models: Object.fromEntries(COMMAND_CODE_GO_PLAN_MODELS.map((model) => [model.id, openCodeModelConfig(model)])),
|
|
893
987
|
},
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
return
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
988
|
+
auth: {
|
|
989
|
+
provider: PROVIDER_ID,
|
|
990
|
+
loader: async () => {
|
|
991
|
+
const proxyConfig = await getRuntimePlugin().ensureStarted();
|
|
992
|
+
if (!proxyConfig)
|
|
993
|
+
return null;
|
|
994
|
+
return {
|
|
995
|
+
apiKey: PROVIDER_API_KEY_PLACEHOLDER,
|
|
996
|
+
baseURL: `http://localhost:${proxyConfig.port}/v1`,
|
|
997
|
+
};
|
|
998
|
+
},
|
|
999
|
+
methods: [],
|
|
905
1000
|
},
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1001
|
+
config: async (config) => {
|
|
1002
|
+
const plugin = getRuntimePlugin();
|
|
1003
|
+
plugin.preloadForOpenCode();
|
|
1004
|
+
const currentConfig = plugin.getCurrentProxyConfig();
|
|
1005
|
+
const registrationBaseURL = currentConfig
|
|
1006
|
+
? `http://localhost:${currentConfig.port}/v1`
|
|
1007
|
+
: proxyBaseURLForRegistration();
|
|
1008
|
+
registerOpenCommandProvider(config, registrationBaseURL, readCachedOpenCommandModels() ?? COMMAND_CODE_GO_PLAN_MODELS);
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
};
|
|
913
1012
|
export default OpenCommandOpenCodePlugin;
|
|
914
1013
|
export { OpenCommandOpenCodePlugin as opencommandPlugin };
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencommand-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "OpenCommand - CommandCode API Plugin for OpenCode",
|
|
5
5
|
"main": "./bin/opencode-plugin.js",
|
|
6
6
|
"module": "./bin/opencode-plugin.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
|
+
"build:proxy": "node scripts/build-proxy-binaries.mjs",
|
|
9
10
|
"test": "jest --config jest.config.cjs",
|
|
10
11
|
"dev": "tsc --watch",
|
|
11
12
|
"clean": "rm -rf dist",
|
|
12
|
-
"prepack": "npm run build"
|
|
13
|
+
"prepack": "npm run build && npm run build:proxy"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
15
16
|
"opencode",
|
|
@@ -26,9 +27,7 @@
|
|
|
26
27
|
"ts-jest": "^29.0.0",
|
|
27
28
|
"typescript": "^5.0.0"
|
|
28
29
|
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"node-fetch": "^3.0.0"
|
|
31
|
-
},
|
|
30
|
+
"dependencies": {},
|
|
32
31
|
"files": [
|
|
33
32
|
"dist/**",
|
|
34
33
|
"bin/**",
|