opencommand-plugin 0.0.3 → 0.0.4

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 CHANGED
@@ -1,80 +1,48 @@
1
- # OpenCommand Plugin - OpenCode Integration
1
+ # OpenCommand Plugin
2
2
 
3
- Plugin for OpenCode that manages the CommandCode proxy lifecycle.
3
+ OpenCode plugin for CommandCode. It starts the local OpenCommand proxy, stores CommandCode auth locally, registers the `opencommand` provider, and loads the right model list for the active CommandCode plan.
4
4
 
5
5
  ## Features
6
6
 
7
- - Automatic proxy startup on editor launch
8
- - Dynamic port allocation (no conflicts)
9
- - Secure session token storage via SecretStorage
10
- - Health checks (proxy readiness verification)
11
- - Graceful shutdown on editor close
12
- - Configuration export to OpenCode
7
+ - Starts/stops the Go proxy automatically.
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.
13
14
 
14
- ## Architecture
15
+ ## Auth
15
16
 
16
- ### ProxyManager
17
- Handles proxy process lifecycle:
18
- - Start/stop proxy on dynamic port
19
- - Health check polling
20
- - Process signal handling
17
+ Primary token key:
21
18
 
22
- ### SecretStorage
23
- Secure credential management:
24
- - Store CC_SESSION_COOKIE securely
25
- - Retrieve only when needed
26
- - Mock implementation (real version uses OpenCode API)
27
-
28
- ### OpenCommandPlugin
29
- Main plugin interface:
30
- - activate() - Initialize on editor startup
31
- - deactivate() - Clean up on shutdown
32
- - setSessionToken() - Configure CommandCode credentials
33
- - getProxyConfig() - Get current config
34
-
35
- ## Building
36
-
37
- ```bash
38
- npm install
39
- npm run build
19
+ ```json
20
+ {
21
+ "opencommand.command_code_token": "user_xxx..."
22
+ }
40
23
  ```
41
24
 
42
- ## Testing
25
+ Do not put this token in synced `opencode.jsonc`.
43
26
 
44
- ```bash
45
- npm test
46
- ```
27
+ ## Models in v0.0.4
47
28
 
48
- ## Usage
29
+ The proxy detects the active CommandCode subscription through `/alpha/billing/subscriptions` and filters models:
49
30
 
50
- 1. User installs plugin in OpenCode
51
- 2. Plugin prompts for CC_SESSION_COOKIE
52
- 3. On startup:
53
- - Token retrieved from SecretStorage
54
- - Proxy started on random available port
55
- - Configuration saved to OpenCode
56
- - Clients use http://localhost:PORT/v1 for API calls
57
- 4. On shutdown:
58
- - Proxy process terminated gracefully
59
- - SIGTERM → wait 5s → SIGKILL
31
+ - Go: open-source models only.
32
+ - Pro: open-source + premium, excluding Opus.
33
+ - Max / Ultra / Teams Pro: open-source + premium including Opus.
60
34
 
61
- ## Configuration
35
+ If detection fails, the plugin falls back to the local proxy model endpoint and then to the Go/open-source model list.
62
36
 
63
- The plugin exports this configuration to OpenCode:
37
+ ## Build
64
38
 
65
- ```json
66
- {
67
- "opencommand": {
68
- "proxyUrl": "http://localhost:3001",
69
- "apiBaseUrl": "http://localhost:3001/v1"
70
- }
71
- }
39
+ ```bash
40
+ npm ci
41
+ npm run build
72
42
  ```
73
43
 
74
- OpenCode and other clients use this to route requests to the proxy.
75
-
76
- ## Related Issues
44
+ ## Test
77
45
 
78
- - Fase 2 Implementation (#2)
79
- - Depends on: Proxy Core (Fase 1)
80
- - Prepares for: Cookie Scraper (Fase 3)
46
+ ```bash
47
+ npm test -- --runInBand
48
+ ```
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ interface CommandCodeModelDefinition {
11
11
  id: string;
12
12
  name: string;
13
13
  description: string;
14
+ category?: string;
14
15
  reasoning?: boolean;
15
16
  context: number;
16
17
  output: number;
@@ -93,7 +94,11 @@ export declare class OpenCommandPlugin {
93
94
  private restartIfPossible;
94
95
  getProxyConfig(): string;
95
96
  }
96
- export declare function registerOpenCommandProvider(config: OpenCodeConfig, baseURL?: string): void;
97
+ export declare function fetchOpenCommandModels(baseURL: string): Promise<CommandCodeModelDefinition[] | undefined>;
98
+ export declare function commandCodeModelsForPlan(planID: string): CommandCodeModelDefinition[];
99
+ export declare function fetchCommandCodePlanModels(commandCodeToken: string, apiBaseURL?: string): Promise<CommandCodeModelDefinition[] | undefined>;
100
+ export declare function parseCommandCodePlanID(body: unknown): string | undefined;
101
+ export declare function registerOpenCommandProvider(config: OpenCodeConfig, baseURL?: string, modelDefinitions?: CommandCodeModelDefinition[]): void;
97
102
  export declare const OpenCommandOpenCodePlugin: () => Promise<{
98
103
  auth: {
99
104
  provider: string;
package/dist/index.js CHANGED
@@ -34,6 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.OpenCommandOpenCodePlugin = exports.OpenCommandPlugin = exports.BrowserCookieExtractor = exports.SecretStorage = exports.ProxyManager = exports.COMMAND_CODE_GO_PLAN_MODELS = void 0;
37
+ exports.fetchOpenCommandModels = fetchOpenCommandModels;
38
+ exports.commandCodeModelsForPlan = commandCodeModelsForPlan;
39
+ exports.fetchCommandCodePlanModels = fetchCommandCodePlanModels;
40
+ exports.parseCommandCodePlanID = parseCommandCodePlanID;
37
41
  exports.registerOpenCommandProvider = registerOpenCommandProvider;
38
42
  const cp = __importStar(require("child_process"));
39
43
  const crypto = __importStar(require("crypto"));
@@ -46,6 +50,7 @@ const PROVIDER_ID = "opencommand";
46
50
  const PROVIDER_NAME = "CommandCode";
47
51
  const PROVIDER_API_KEY_PLACEHOLDER = "opencommand";
48
52
  const DEFAULT_PROXY_BASE_URL = "http://localhost:3000/v1";
53
+ const COMMAND_CODE_API_BASE_URL = "https://api.commandcode.ai";
49
54
  exports.COMMAND_CODE_GO_PLAN_MODELS = [
50
55
  {
51
56
  id: "deepseek/deepseek-v4-pro",
@@ -141,6 +146,86 @@ exports.COMMAND_CODE_GO_PLAN_MODELS = [
141
146
  cost: { input: 0.1, output: 0.3, cache_read: 0.02 },
142
147
  },
143
148
  ];
149
+ const COMMAND_CODE_PREMIUM_MODELS = [
150
+ {
151
+ id: "anthropic:claude-opus-4-7",
152
+ name: "Claude Opus 4.7",
153
+ description: "Premium Claude Opus coding and reasoning",
154
+ category: "premium",
155
+ reasoning: true,
156
+ context: 200000,
157
+ output: 8192,
158
+ cost: { input: 0, output: 0 },
159
+ },
160
+ {
161
+ id: "anthropic:claude-opus-4-6",
162
+ name: "Claude Opus 4.6",
163
+ description: "Premium Claude Opus coding and reasoning",
164
+ category: "premium",
165
+ reasoning: true,
166
+ context: 200000,
167
+ output: 8192,
168
+ cost: { input: 0, output: 0 },
169
+ },
170
+ {
171
+ id: "anthropic:claude-sonnet-4-6",
172
+ name: "Claude Sonnet 4.6",
173
+ description: "Premium Claude Sonnet coding",
174
+ category: "premium",
175
+ reasoning: true,
176
+ context: 200000,
177
+ output: 8192,
178
+ cost: { input: 0, output: 0 },
179
+ },
180
+ {
181
+ id: "anthropic:claude-haiku-4-5-20251001",
182
+ name: "Claude Haiku 4.5",
183
+ description: "Fast premium Claude model",
184
+ category: "premium",
185
+ context: 200000,
186
+ output: 8192,
187
+ cost: { input: 0, output: 0 },
188
+ },
189
+ {
190
+ id: "openai:gpt-5.5",
191
+ name: "GPT-5.5",
192
+ description: "Premium OpenAI reasoning model",
193
+ category: "premium",
194
+ reasoning: true,
195
+ context: 200000,
196
+ output: 8192,
197
+ cost: { input: 0, output: 0 },
198
+ },
199
+ {
200
+ id: "openai:gpt-5.4",
201
+ name: "GPT-5.4",
202
+ description: "Premium OpenAI coding model",
203
+ category: "premium",
204
+ reasoning: true,
205
+ context: 200000,
206
+ output: 8192,
207
+ cost: { input: 0, output: 0 },
208
+ },
209
+ {
210
+ id: "openai:gpt-5.4-mini",
211
+ name: "GPT-5.4 Mini",
212
+ description: "Efficient premium OpenAI coding model",
213
+ category: "premium",
214
+ context: 200000,
215
+ output: 8192,
216
+ cost: { input: 0, output: 0 },
217
+ },
218
+ {
219
+ id: "openai:gpt-5.3-codex",
220
+ name: "GPT-5.3 Codex",
221
+ description: "Premium OpenAI Codex coding model",
222
+ category: "premium",
223
+ reasoning: true,
224
+ context: 200000,
225
+ output: 8192,
226
+ cost: { input: 0, output: 0 },
227
+ },
228
+ ];
144
229
  class ProxyManager {
145
230
  constructor(binaryPath = resolveProxyBinaryPath()) {
146
231
  this.proxyProcess = null;
@@ -622,6 +707,7 @@ function openCodeModelConfig(model) {
622
707
  id: model.id,
623
708
  name: model.name,
624
709
  description: model.description,
710
+ category: model.category ?? "opensource",
625
711
  reasoning: model.reasoning ?? false,
626
712
  tool_call: true,
627
713
  attachment: false,
@@ -636,9 +722,100 @@ function openCodeModelConfig(model) {
636
722
  },
637
723
  };
638
724
  }
639
- function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL) {
725
+ function modelFromProxy(model) {
726
+ if (!model.id)
727
+ return undefined;
728
+ return {
729
+ id: model.id,
730
+ name: model.name ?? model.id,
731
+ description: model.description ?? "CommandCode model",
732
+ category: model.category,
733
+ reasoning: model.reasoning ?? false,
734
+ context: model.limit?.context ?? 200000,
735
+ output: model.limit?.output ?? 8192,
736
+ cost: model.cost ?? { input: 0, output: 0 },
737
+ };
738
+ }
739
+ async function fetchOpenCommandModels(baseURL) {
740
+ try {
741
+ const response = await fetch(`${baseURL.replace(/\/$/, "")}/models`, {
742
+ headers: { Authorization: `Bearer ${PROVIDER_API_KEY_PLACEHOLDER}` },
743
+ });
744
+ if (!response.ok)
745
+ return undefined;
746
+ const body = (await response.json());
747
+ const models = (body.data ?? [])
748
+ .map(modelFromProxy)
749
+ .filter((model) => Boolean(model));
750
+ return models.length > 0 ? models : undefined;
751
+ }
752
+ catch (error) {
753
+ console.debug("Could not fetch OpenCommand models from local proxy:", error);
754
+ return undefined;
755
+ }
756
+ }
757
+ function commandCodeModelsForPlan(planID) {
758
+ const models = [...exports.COMMAND_CODE_GO_PLAN_MODELS];
759
+ if (!planCanUsePremium(planID))
760
+ return models;
761
+ for (const model of COMMAND_CODE_PREMIUM_MODELS) {
762
+ if (isOpusModel(model.id) && !planCanUseOpus(planID))
763
+ continue;
764
+ models.push(model);
765
+ }
766
+ return models;
767
+ }
768
+ async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL = COMMAND_CODE_API_BASE_URL) {
769
+ try {
770
+ const response = await fetch(`${apiBaseURL.replace(/\/$/, "")}/alpha/billing/subscriptions`, {
771
+ headers: {
772
+ Accept: "application/json",
773
+ Authorization: `Bearer ${commandCodeToken}`,
774
+ },
775
+ });
776
+ if (!response.ok)
777
+ return undefined;
778
+ const body = await response.json();
779
+ const planID = parseCommandCodePlanID(body);
780
+ return planID ? commandCodeModelsForPlan(planID) : undefined;
781
+ }
782
+ catch (error) {
783
+ console.debug("Could not detect CommandCode plan for model registration:", error);
784
+ return undefined;
785
+ }
786
+ }
787
+ function parseCommandCodePlanID(body) {
788
+ const root = body;
789
+ if (typeof root?.planId === "string" && root.planId.trim())
790
+ return root.planId.trim();
791
+ const data = root?.data;
792
+ if (Array.isArray(data)) {
793
+ const active = data.find((item) => {
794
+ const entry = item;
795
+ return typeof entry.planId === "string" && entry.status === "active";
796
+ });
797
+ const first = active ?? data[0];
798
+ return typeof first?.planId === "string" && first.planId.trim()
799
+ ? first.planId.trim()
800
+ : undefined;
801
+ }
802
+ const single = data;
803
+ return typeof single?.planId === "string" && single.planId.trim()
804
+ ? single.planId.trim()
805
+ : undefined;
806
+ }
807
+ function planCanUsePremium(planID) {
808
+ return ["individual-pro", "individual-max", "individual-ultra", "teams-pro"].includes(planID.toLowerCase());
809
+ }
810
+ function planCanUseOpus(planID) {
811
+ return ["individual-max", "individual-ultra", "teams-pro"].includes(planID.toLowerCase());
812
+ }
813
+ function isOpusModel(modelID) {
814
+ return modelID.toLowerCase().includes("opus");
815
+ }
816
+ function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL, modelDefinitions = exports.COMMAND_CODE_GO_PLAN_MODELS) {
640
817
  const models = {};
641
- for (const model of exports.COMMAND_CODE_GO_PLAN_MODELS) {
818
+ for (const model of modelDefinitions) {
642
819
  models[model.id] = openCodeModelConfig(model);
643
820
  }
644
821
  config.provider = {
@@ -674,7 +851,16 @@ const OpenCommandOpenCodePlugin = async () => ({
674
851
  methods: [],
675
852
  },
676
853
  config: async (config) => {
677
- registerOpenCommandProvider(config);
854
+ let baseURL = DEFAULT_PROXY_BASE_URL;
855
+ let models;
856
+ const proxyConfig = await getRuntimePlugin().ensureStarted();
857
+ if (proxyConfig) {
858
+ baseURL = `http://localhost:${proxyConfig.port}/v1`;
859
+ models =
860
+ (await fetchCommandCodePlanModels(proxyConfig.commandCodeToken)) ??
861
+ (await fetchOpenCommandModels(baseURL));
862
+ }
863
+ registerOpenCommandProvider(config, baseURL, models ?? exports.COMMAND_CODE_GO_PLAN_MODELS);
678
864
  },
679
865
  });
680
866
  exports.OpenCommandOpenCodePlugin = OpenCommandOpenCodePlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencommand-plugin",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "OpenCommand - CommandCode API Plugin for OpenCode",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {