opencommand-plugin 0.0.10 → 0.0.11

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
@@ -5,6 +5,7 @@ OpenCode plugin for CommandCode. It starts the local OpenCommand proxy, stores C
5
5
  ## Features
6
6
 
7
7
  - Starts/stops the Go proxy automatically.
8
+ - Ships the proxy binaries in the NPM package for normal OpenCode installs.
8
9
  - Uses a dynamic local port and exposes `http://localhost:<port>/v1` to OpenCode.
9
10
  - Stores the CommandCode API token locally in `~/.opencommand/opencommand-secrets.json`.
10
11
  - Supports `COMMAND_CODE_TOKEN` and `COMMANDCODE_API_KEY` environment overrides.
@@ -24,7 +25,7 @@ Primary token key:
24
25
 
25
26
  Do not put this token in synced `opencode.jsonc`.
26
27
 
27
- ## Models in v0.0.4
28
+ ## Models in v0.0.11
28
29
 
29
30
  The proxy detects the active CommandCode subscription through `/alpha/billing/subscriptions` and filters models:
30
31
 
@@ -49,12 +50,18 @@ npm test -- --runInBand
49
50
 
50
51
  ## v0.0.8 non-blocking OpenCode startup
51
52
 
52
- `opencommand-plugin@0.0.8` keeps OpenCode startup non-blocking: provider registration uses cached/static models immediately, while proxy startup and CommandCode plan/model refresh run in the background. Pin `opencode.jsonc` to `opencommand-plugin@0.0.10` (or newer) instead of `@latest` to avoid stale plugin cache entries.
53
+ `opencommand-plugin@0.0.8` keeps OpenCode startup non-blocking: provider registration uses cached/static models immediately, while proxy startup and CommandCode plan/model refresh run in the background. Pin `opencode.jsonc` to `opencommand-plugin@0.0.11` (or newer) instead of `@latest` to avoid stale plugin cache entries.
53
54
 
54
55
  ## v0.0.9 CommandCode protocol update
55
56
 
56
57
  `opencommand-plugin@0.0.9` updates the bundled proxy to the current CommandCode CLI protocol headers (`x-command-code-version: 0.26.3`, production CLI environment, and OpenCommand project slug). Older `OPENCOMMAND_COMMAND_CODE_CLIENT_VERSION` overrides can no longer downgrade the bundled protocol version.
57
- ## v0.0.10 OpenCode autostart preload
58
58
 
59
- `opencommand-plugin@0.0.10` starts the local proxy from the OpenCode config hook so synced `opencode.jsonc` installs do not depend on an absolute local `file://` plugin path. Use `opencommand-plugin@0.0.10` in iCloud/Open sync configs.
59
+ ## v0.0.11 bundled proxy
60
+
61
+ `opencommand-plugin@0.0.11` ships the Go proxy binaries inside the NPM package for macOS, Linux, and Windows. OpenCode only needs the plugin package; no separate proxy install or local repo checkout is required.
62
+
63
+ The proxy also translates CommandCode streaming events into OpenAI-compatible chat-completion chunks/responses for `@ai-sdk/openai-compatible`.
64
+
65
+ ## v0.0.10 OpenCode autostart preload
60
66
 
67
+ `opencommand-plugin@0.0.10` starts the local proxy from the OpenCode config hook so synced `opencode.jsonc` installs do not depend on an absolute local `file://` plugin path.
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: "anthropic:claude-opus-4-7",
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: "anthropic:claude-opus-4-6",
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: "anthropic:claude-sonnet-4-6",
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: "anthropic:claude-haiku-4-5-20251001",
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: "openai:gpt-5.5",
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: "openai:gpt-5.4",
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: "openai:gpt-5.4-mini",
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: "openai:gpt-5.3-codex",
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
- return firstDefined(process.env.CC_SESSION_COOKIE, await this.secretStorage.get(this.sessionCookieKey));
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
- process.env.OPENCOMMAND_PROXY_PATH,
732
+ bundledProxyBinaryPath(),
710
733
  path.join(os.homedir(), ".opencommand", "proxy"),
711
734
  path.join(process.cwd(), "packages", "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
- provider: {
885
- [PROVIDER_ID]: {
886
- npm: "@ai-sdk/openai-compatible",
887
- name: PROVIDER_NAME,
888
- options: {
889
- apiKey: PROVIDER_API_KEY_PLACEHOLDER,
890
- baseURL: DEFAULT_PROXY_BASE_URL,
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
- auth: {
896
- provider: PROVIDER_ID,
897
- loader: async () => {
898
- const proxyConfig = await getRuntimePlugin().ensureStarted();
899
- if (!proxyConfig)
900
- return null;
901
- return {
902
- apiKey: PROVIDER_API_KEY_PLACEHOLDER,
903
- baseURL: `http://localhost:${proxyConfig.port}/v1`,
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
- methods: [],
907
- },
908
- config: async (config) => {
909
- getRuntimePlugin().preloadForOpenCode();
910
- registerOpenCommandProvider(config, DEFAULT_PROXY_BASE_URL, readCachedOpenCommandModels() ?? COMMAND_CODE_GO_PLAN_MODELS);
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 };
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "opencommand-plugin",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
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/**",