opencommand-plugin 0.0.3 → 0.0.5

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,53 @@
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)
19
+ ```json
20
+ {
21
+ "opencommand.command_code_token": "user_xxx..."
22
+ }
23
+ ```
27
24
 
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
25
+ Do not put this token in synced `opencode.jsonc`.
34
26
 
35
- ## Building
27
+ ## Models in v0.0.4
36
28
 
37
- ```bash
38
- npm install
39
- npm run build
40
- ```
29
+ The proxy detects the active CommandCode subscription through `/alpha/billing/subscriptions` and filters models:
41
30
 
42
- ## Testing
31
+ - Go: open-source models only.
32
+ - Pro: open-source + premium, excluding Opus.
33
+ - Max / Ultra / Teams Pro: open-source + premium including Opus.
43
34
 
44
- ```bash
45
- npm test
46
- ```
35
+ If detection fails, the plugin falls back to the local proxy model endpoint and then to the Go/open-source model list.
47
36
 
48
- ## Usage
37
+ ## Build
49
38
 
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
60
-
61
- ## Configuration
39
+ ```bash
40
+ npm ci
41
+ npm run build
42
+ ```
62
43
 
63
- The plugin exports this configuration to OpenCode:
44
+ ## Test
64
45
 
65
- ```json
66
- {
67
- "opencommand": {
68
- "proxyUrl": "http://localhost:3001",
69
- "apiBaseUrl": "http://localhost:3001/v1"
70
- }
71
- }
46
+ ```bash
47
+ npm test -- --runInBand
72
48
  ```
73
49
 
74
- OpenCode and other clients use this to route requests to the proxy.
50
+ ## v0.0.5 OpenCode loading fix
75
51
 
76
- ## Related Issues
52
+ `opencommand-plugin@0.0.5` publishes an explicit ESM wrapper at `bin/opencode-plugin.js` and should be pinned in `opencode.jsonc` as `opencommand-plugin@0.0.5` (or newer). This avoids stale `@latest` cache entries and ensures OpenCode sees the plugin default export as a function.
77
53
 
78
- - Fase 2 Implementation (#2)
79
- - Depends on: Proxy Core (Fase 1)
80
- - Prepares for: Cookie Scraper (Fase 3)
@@ -0,0 +1,3 @@
1
+ import plugin from '../dist/index.js';
2
+
3
+ export default plugin;
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,8 +94,25 @@ 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<{
103
+ provider: {
104
+ opencommand: {
105
+ npm: string;
106
+ name: string;
107
+ options: {
108
+ apiKey: string;
109
+ baseURL: string;
110
+ };
111
+ models: {
112
+ [k: string]: Record<string, unknown>;
113
+ };
114
+ };
115
+ };
98
116
  auth: {
99
117
  provider: string;
100
118
  loader: () => Promise<{
@@ -106,3 +124,4 @@ export declare const OpenCommandOpenCodePlugin: () => Promise<{
106
124
  config: (config: OpenCodeConfig) => Promise<void>;
107
125
  }>;
108
126
  export default OpenCommandOpenCodePlugin;
127
+ export { OpenCommandOpenCodePlugin as opencommandPlugin };
package/dist/index.js CHANGED
@@ -1,52 +1,16 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.OpenCommandOpenCodePlugin = exports.OpenCommandPlugin = exports.BrowserCookieExtractor = exports.SecretStorage = exports.ProxyManager = exports.COMMAND_CODE_GO_PLAN_MODELS = void 0;
37
- exports.registerOpenCommandProvider = registerOpenCommandProvider;
38
- const cp = __importStar(require("child_process"));
39
- const crypto = __importStar(require("crypto"));
40
- const fs = __importStar(require("fs"));
41
- const fsp = __importStar(require("fs/promises"));
42
- const net = __importStar(require("net"));
43
- const os = __importStar(require("os"));
44
- const path = __importStar(require("path"));
1
+ import * as cp from "child_process";
2
+ import * as crypto from "crypto";
3
+ import * as fs from "fs";
4
+ import * as fsp from "fs/promises";
5
+ import * as net from "net";
6
+ import * as os from "os";
7
+ import * as path from "path";
45
8
  const PROVIDER_ID = "opencommand";
46
9
  const PROVIDER_NAME = "CommandCode";
47
10
  const PROVIDER_API_KEY_PLACEHOLDER = "opencommand";
48
11
  const DEFAULT_PROXY_BASE_URL = "http://localhost:3000/v1";
49
- exports.COMMAND_CODE_GO_PLAN_MODELS = [
12
+ const COMMAND_CODE_API_BASE_URL = "https://api.commandcode.ai";
13
+ export const COMMAND_CODE_GO_PLAN_MODELS = [
50
14
  {
51
15
  id: "deepseek/deepseek-v4-pro",
52
16
  name: "DeepSeek V4 Pro",
@@ -141,7 +105,87 @@ exports.COMMAND_CODE_GO_PLAN_MODELS = [
141
105
  cost: { input: 0.1, output: 0.3, cache_read: 0.02 },
142
106
  },
143
107
  ];
144
- class ProxyManager {
108
+ const COMMAND_CODE_PREMIUM_MODELS = [
109
+ {
110
+ id: "anthropic:claude-opus-4-7",
111
+ name: "Claude Opus 4.7",
112
+ description: "Premium Claude Opus coding and reasoning",
113
+ category: "premium",
114
+ reasoning: true,
115
+ context: 200000,
116
+ output: 8192,
117
+ cost: { input: 0, output: 0 },
118
+ },
119
+ {
120
+ id: "anthropic:claude-opus-4-6",
121
+ name: "Claude Opus 4.6",
122
+ description: "Premium Claude Opus coding and reasoning",
123
+ category: "premium",
124
+ reasoning: true,
125
+ context: 200000,
126
+ output: 8192,
127
+ cost: { input: 0, output: 0 },
128
+ },
129
+ {
130
+ id: "anthropic:claude-sonnet-4-6",
131
+ name: "Claude Sonnet 4.6",
132
+ description: "Premium Claude Sonnet coding",
133
+ category: "premium",
134
+ reasoning: true,
135
+ context: 200000,
136
+ output: 8192,
137
+ cost: { input: 0, output: 0 },
138
+ },
139
+ {
140
+ id: "anthropic:claude-haiku-4-5-20251001",
141
+ name: "Claude Haiku 4.5",
142
+ description: "Fast premium Claude model",
143
+ category: "premium",
144
+ context: 200000,
145
+ output: 8192,
146
+ cost: { input: 0, output: 0 },
147
+ },
148
+ {
149
+ id: "openai:gpt-5.5",
150
+ name: "GPT-5.5",
151
+ description: "Premium OpenAI reasoning model",
152
+ category: "premium",
153
+ reasoning: true,
154
+ context: 200000,
155
+ output: 8192,
156
+ cost: { input: 0, output: 0 },
157
+ },
158
+ {
159
+ id: "openai:gpt-5.4",
160
+ name: "GPT-5.4",
161
+ description: "Premium OpenAI coding model",
162
+ category: "premium",
163
+ reasoning: true,
164
+ context: 200000,
165
+ output: 8192,
166
+ cost: { input: 0, output: 0 },
167
+ },
168
+ {
169
+ id: "openai:gpt-5.4-mini",
170
+ name: "GPT-5.4 Mini",
171
+ description: "Efficient premium OpenAI coding model",
172
+ category: "premium",
173
+ context: 200000,
174
+ output: 8192,
175
+ cost: { input: 0, output: 0 },
176
+ },
177
+ {
178
+ id: "openai:gpt-5.3-codex",
179
+ name: "GPT-5.3 Codex",
180
+ description: "Premium OpenAI Codex coding model",
181
+ category: "premium",
182
+ reasoning: true,
183
+ context: 200000,
184
+ output: 8192,
185
+ cost: { input: 0, output: 0 },
186
+ },
187
+ ];
188
+ export class ProxyManager {
145
189
  constructor(binaryPath = resolveProxyBinaryPath()) {
146
190
  this.proxyProcess = null;
147
191
  this.config = null;
@@ -262,8 +306,7 @@ class ProxyManager {
262
306
  return this.proxyProcess !== null && !this.proxyProcess.killed;
263
307
  }
264
308
  }
265
- exports.ProxyManager = ProxyManager;
266
- class SecretStorage {
309
+ export class SecretStorage {
267
310
  constructor(storageDir = `${process.env.HOME || "/tmp"}/.opencommand`) {
268
311
  this.filePath = `${storageDir}/opencommand-secrets.json`;
269
312
  }
@@ -301,8 +344,7 @@ class SecretStorage {
301
344
  await this.writeStore(store);
302
345
  }
303
346
  }
304
- exports.SecretStorage = SecretStorage;
305
- class BrowserCookieExtractor {
347
+ export class BrowserCookieExtractor {
306
348
  constructor(homeDir = os.homedir()) {
307
349
  this.homeDir = homeDir;
308
350
  }
@@ -396,7 +438,8 @@ class BrowserCookieExtractor {
396
438
  try {
397
439
  fs.copyFileSync(profile.cookieDatabase, tmpDb);
398
440
  const query = profile.browser === "Firefox" ? firefoxCookieQuery : chromiumCookieQuery;
399
- const output = cp.execFileSync("/usr/bin/sqlite3", [tmpDb, query], {
441
+ const output = cp.execFileSync("/usr/bin/sqlite3", [tmpDb], {
442
+ input: query,
400
443
  encoding: "utf8",
401
444
  timeout: 5000,
402
445
  });
@@ -463,7 +506,6 @@ class BrowserCookieExtractor {
463
506
  }
464
507
  }
465
508
  }
466
- exports.BrowserCookieExtractor = BrowserCookieExtractor;
467
509
  function decryptMacChromiumCookie(encryptedValue, safeStoragePassword) {
468
510
  try {
469
511
  const payload = encryptedValue.subarray(0, 3).toString() === "v10"
@@ -485,7 +527,7 @@ const firefoxCookieQuery = [
485
527
  ".mode tabs",
486
528
  "SELECT host, name, value, '' FROM moz_cookies WHERE host LIKE '%commandcode.ai%';",
487
529
  ].join("\n");
488
- class OpenCommandPlugin {
530
+ export class OpenCommandPlugin {
489
531
  constructor(proxyBinaryPath = resolveProxyBinaryPath(), storageDir) {
490
532
  this.commandCodeTokenKey = "opencommand.command_code_token";
491
533
  this.legacyTokenKey = "opencommand.cc_session_token";
@@ -599,7 +641,6 @@ class OpenCommandPlugin {
599
641
  return JSON.stringify(config, null, 2);
600
642
  }
601
643
  }
602
- exports.OpenCommandPlugin = OpenCommandPlugin;
603
644
  function firstDefined(...values) {
604
645
  for (const value of values) {
605
646
  const trimmed = value?.trim();
@@ -622,6 +663,7 @@ function openCodeModelConfig(model) {
622
663
  id: model.id,
623
664
  name: model.name,
624
665
  description: model.description,
666
+ category: model.category ?? "opensource",
625
667
  reasoning: model.reasoning ?? false,
626
668
  tool_call: true,
627
669
  attachment: false,
@@ -636,9 +678,100 @@ function openCodeModelConfig(model) {
636
678
  },
637
679
  };
638
680
  }
639
- function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL) {
681
+ function modelFromProxy(model) {
682
+ if (!model.id)
683
+ return undefined;
684
+ return {
685
+ id: model.id,
686
+ name: model.name ?? model.id,
687
+ description: model.description ?? "CommandCode model",
688
+ category: model.category,
689
+ reasoning: model.reasoning ?? false,
690
+ context: model.limit?.context ?? 200000,
691
+ output: model.limit?.output ?? 8192,
692
+ cost: model.cost ?? { input: 0, output: 0 },
693
+ };
694
+ }
695
+ export async function fetchOpenCommandModels(baseURL) {
696
+ try {
697
+ const response = await fetch(`${baseURL.replace(/\/$/, "")}/models`, {
698
+ headers: { Authorization: `Bearer ${PROVIDER_API_KEY_PLACEHOLDER}` },
699
+ });
700
+ if (!response.ok)
701
+ return undefined;
702
+ const body = (await response.json());
703
+ const models = (body.data ?? [])
704
+ .map(modelFromProxy)
705
+ .filter((model) => Boolean(model));
706
+ return models.length > 0 ? models : undefined;
707
+ }
708
+ catch (error) {
709
+ console.debug("Could not fetch OpenCommand models from local proxy:", error);
710
+ return undefined;
711
+ }
712
+ }
713
+ export function commandCodeModelsForPlan(planID) {
714
+ const models = [...COMMAND_CODE_GO_PLAN_MODELS];
715
+ if (!planCanUsePremium(planID))
716
+ return models;
717
+ for (const model of COMMAND_CODE_PREMIUM_MODELS) {
718
+ if (isOpusModel(model.id) && !planCanUseOpus(planID))
719
+ continue;
720
+ models.push(model);
721
+ }
722
+ return models;
723
+ }
724
+ export async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL = COMMAND_CODE_API_BASE_URL) {
725
+ try {
726
+ const response = await fetch(`${apiBaseURL.replace(/\/$/, "")}/alpha/billing/subscriptions`, {
727
+ headers: {
728
+ Accept: "application/json",
729
+ Authorization: `Bearer ${commandCodeToken}`,
730
+ },
731
+ });
732
+ if (!response.ok)
733
+ return undefined;
734
+ const body = await response.json();
735
+ const planID = parseCommandCodePlanID(body);
736
+ return planID ? commandCodeModelsForPlan(planID) : undefined;
737
+ }
738
+ catch (error) {
739
+ console.debug("Could not detect CommandCode plan for model registration:", error);
740
+ return undefined;
741
+ }
742
+ }
743
+ export function parseCommandCodePlanID(body) {
744
+ const root = body;
745
+ if (typeof root?.planId === "string" && root.planId.trim())
746
+ return root.planId.trim();
747
+ const data = root?.data;
748
+ if (Array.isArray(data)) {
749
+ const active = data.find((item) => {
750
+ const entry = item;
751
+ return typeof entry.planId === "string" && entry.status === "active";
752
+ });
753
+ const first = active ?? data[0];
754
+ return typeof first?.planId === "string" && first.planId.trim()
755
+ ? first.planId.trim()
756
+ : undefined;
757
+ }
758
+ const single = data;
759
+ return typeof single?.planId === "string" && single.planId.trim()
760
+ ? single.planId.trim()
761
+ : undefined;
762
+ }
763
+ function planCanUsePremium(planID) {
764
+ return ["individual-pro", "individual-max", "individual-ultra", "teams-pro"].includes(planID.toLowerCase());
765
+ }
766
+ function planCanUseOpus(planID) {
767
+ return ["individual-max", "individual-ultra", "teams-pro"].includes(planID.toLowerCase());
768
+ }
769
+ function isOpusModel(modelID) {
770
+ return modelID.toLowerCase().includes("opus");
771
+ }
772
+ export function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL, modelDefinitions = COMMAND_CODE_GO_PLAN_MODELS) {
640
773
  const models = {};
641
- for (const model of exports.COMMAND_CODE_GO_PLAN_MODELS) {
774
+ for (const model of modelDefinitions) {
642
775
  models[model.id] = openCodeModelConfig(model);
643
776
  }
644
777
  config.provider = {
@@ -659,7 +792,18 @@ function getRuntimePlugin() {
659
792
  runtimePlugin ?? (runtimePlugin = new OpenCommandPlugin());
660
793
  return runtimePlugin;
661
794
  }
662
- const OpenCommandOpenCodePlugin = async () => ({
795
+ export const OpenCommandOpenCodePlugin = async () => ({
796
+ provider: {
797
+ [PROVIDER_ID]: {
798
+ npm: "@ai-sdk/openai-compatible",
799
+ name: PROVIDER_NAME,
800
+ options: {
801
+ apiKey: PROVIDER_API_KEY_PLACEHOLDER,
802
+ baseURL: DEFAULT_PROXY_BASE_URL,
803
+ },
804
+ models: Object.fromEntries(COMMAND_CODE_GO_PLAN_MODELS.map((model) => [model.id, openCodeModelConfig(model)])),
805
+ },
806
+ },
663
807
  auth: {
664
808
  provider: PROVIDER_ID,
665
809
  loader: async () => {
@@ -674,8 +818,17 @@ const OpenCommandOpenCodePlugin = async () => ({
674
818
  methods: [],
675
819
  },
676
820
  config: async (config) => {
677
- registerOpenCommandProvider(config);
821
+ let baseURL = DEFAULT_PROXY_BASE_URL;
822
+ let models;
823
+ const proxyConfig = await getRuntimePlugin().ensureStarted();
824
+ if (proxyConfig) {
825
+ baseURL = `http://localhost:${proxyConfig.port}/v1`;
826
+ models =
827
+ (await fetchCommandCodePlanModels(proxyConfig.commandCodeToken)) ??
828
+ (await fetchOpenCommandModels(baseURL));
829
+ }
830
+ registerOpenCommandProvider(config, baseURL, models ?? COMMAND_CODE_GO_PLAN_MODELS);
678
831
  },
679
832
  });
680
- exports.OpenCommandOpenCodePlugin = OpenCommandOpenCodePlugin;
681
- exports.default = exports.OpenCommandOpenCodePlugin;
833
+ export default OpenCommandOpenCodePlugin;
834
+ export { OpenCommandOpenCodePlugin as opencommandPlugin };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "opencommand-plugin",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "OpenCommand - CommandCode API Plugin for OpenCode",
5
- "main": "dist/index.js",
5
+ "main": "./bin/opencode-plugin.js",
6
+ "module": "./bin/opencode-plugin.js",
6
7
  "scripts": {
7
8
  "build": "tsc",
8
9
  "test": "jest",
@@ -30,7 +31,20 @@
30
31
  },
31
32
  "files": [
32
33
  "dist/**",
34
+ "bin/**",
33
35
  "README.md",
34
36
  "package.json"
35
- ]
37
+ ],
38
+ "type": "module",
39
+ "types": "./dist/index.d.ts",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.ts",
43
+ "import": "./bin/opencode-plugin.js"
44
+ },
45
+ "./dist/index.js": {
46
+ "types": "./dist/index.d.ts",
47
+ "import": "./dist/index.js"
48
+ }
49
+ }
36
50
  }