opencommand-plugin 0.0.4 → 0.0.6

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
@@ -46,3 +46,8 @@ npm run build
46
46
  ```bash
47
47
  npm test -- --runInBand
48
48
  ```
49
+
50
+ ## v0.0.6 non-blocking OpenCode startup
51
+
52
+ `opencommand-plugin@0.0.6` 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.6` (or newer) instead of `@latest` to avoid stale plugin cache entries.
53
+
@@ -0,0 +1,3 @@
1
+ import plugin from '../dist/index.js';
2
+
3
+ export default plugin;
package/dist/index.d.ts CHANGED
@@ -46,6 +46,7 @@ export declare class ProxyManager {
46
46
  constructor(binaryPath?: string);
47
47
  findAvailablePort(): Promise<number>;
48
48
  start(startConfig: ProxyStartConfig): Promise<ProxyConfig>;
49
+ private isPortAvailable;
49
50
  stop(): Promise<void>;
50
51
  private waitForHealthz;
51
52
  getConfig(): ProxyConfig | null;
@@ -77,13 +78,19 @@ export declare class OpenCommandPlugin {
77
78
  private proxyManager;
78
79
  private secretStorage;
79
80
  private cookieExtractor;
81
+ private startPromise;
82
+ private preloadPromise;
80
83
  private readonly commandCodeTokenKey;
81
84
  private readonly legacyTokenKey;
82
85
  private readonly sessionCookieKey;
83
86
  constructor(proxyBinaryPath?: string, storageDir?: string);
84
87
  activate(): Promise<void>;
85
88
  ensureStarted(): Promise<ProxyConfig | undefined>;
89
+ preloadForOpenCode(): void;
90
+ private preload;
91
+ private startProxy;
86
92
  deactivate(): Promise<void>;
93
+ getCurrentProxyConfig(): ProxyConfig | undefined;
87
94
  private loadCommandCodeToken;
88
95
  private loadSessionCookie;
89
96
  private saveOpenCodeConfig;
@@ -99,15 +106,30 @@ export declare function commandCodeModelsForPlan(planID: string): CommandCodeMod
99
106
  export declare function fetchCommandCodePlanModels(commandCodeToken: string, apiBaseURL?: string): Promise<CommandCodeModelDefinition[] | undefined>;
100
107
  export declare function parseCommandCodePlanID(body: unknown): string | undefined;
101
108
  export declare function registerOpenCommandProvider(config: OpenCodeConfig, baseURL?: string, modelDefinitions?: CommandCodeModelDefinition[]): void;
109
+ export declare function readCachedOpenCommandModels(maxAgeMs?: number): CommandCodeModelDefinition[] | undefined;
102
110
  export declare const OpenCommandOpenCodePlugin: () => Promise<{
111
+ provider: {
112
+ opencommand: {
113
+ npm: string;
114
+ name: string;
115
+ options: {
116
+ apiKey: string;
117
+ baseURL: string;
118
+ };
119
+ models: {
120
+ [k: string]: Record<string, unknown>;
121
+ };
122
+ };
123
+ };
103
124
  auth: {
104
125
  provider: string;
105
126
  loader: () => Promise<{
106
127
  apiKey: string;
107
128
  baseURL: string;
108
- } | null>;
129
+ }>;
109
130
  methods: never[];
110
131
  };
111
132
  config: (config: OpenCodeConfig) => Promise<void>;
112
133
  }>;
113
134
  export default OpenCommandOpenCodePlugin;
135
+ export { OpenCommandOpenCodePlugin as opencommandPlugin };
package/dist/index.js CHANGED
@@ -1,57 +1,32 @@
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.fetchOpenCommandModels = fetchOpenCommandModels;
38
- exports.commandCodeModelsForPlan = commandCodeModelsForPlan;
39
- exports.fetchCommandCodePlanModels = fetchCommandCodePlanModels;
40
- exports.parseCommandCodePlanID = parseCommandCodePlanID;
41
- exports.registerOpenCommandProvider = registerOpenCommandProvider;
42
- const cp = __importStar(require("child_process"));
43
- const crypto = __importStar(require("crypto"));
44
- const fs = __importStar(require("fs"));
45
- const fsp = __importStar(require("fs/promises"));
46
- const net = __importStar(require("net"));
47
- const os = __importStar(require("os"));
48
- 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";
49
8
  const PROVIDER_ID = "opencommand";
50
9
  const PROVIDER_NAME = "CommandCode";
51
10
  const PROVIDER_API_KEY_PLACEHOLDER = "opencommand";
52
11
  const DEFAULT_PROXY_BASE_URL = "http://localhost:3000/v1";
53
12
  const COMMAND_CODE_API_BASE_URL = "https://api.commandcode.ai";
54
- exports.COMMAND_CODE_GO_PLAN_MODELS = [
13
+ const MODEL_CACHE_MAX_AGE_MS = 10 * 60 * 1000;
14
+ function isDebugEnabled() {
15
+ return ["1", "true", "yes", "on"].includes((process.env.OPENCOMMAND_DEBUG ?? "").trim().toLowerCase());
16
+ }
17
+ function debugLog(...args) {
18
+ if (isDebugEnabled())
19
+ console.log(...args);
20
+ }
21
+ function debugWarn(...args) {
22
+ if (isDebugEnabled())
23
+ console.warn(...args);
24
+ }
25
+ function debugError(...args) {
26
+ if (isDebugEnabled())
27
+ console.error(...args);
28
+ }
29
+ export const COMMAND_CODE_GO_PLAN_MODELS = [
55
30
  {
56
31
  id: "deepseek/deepseek-v4-pro",
57
32
  name: "DeepSeek V4 Pro",
@@ -226,7 +201,7 @@ const COMMAND_CODE_PREMIUM_MODELS = [
226
201
  cost: { input: 0, output: 0 },
227
202
  },
228
203
  ];
229
- class ProxyManager {
204
+ export class ProxyManager {
230
205
  constructor(binaryPath = resolveProxyBinaryPath()) {
231
206
  this.proxyProcess = null;
232
207
  this.config = null;
@@ -247,7 +222,10 @@ class ProxyManager {
247
222
  });
248
223
  }
249
224
  async start(startConfig) {
250
- const port = await this.findAvailablePort();
225
+ const preferredPort = parseProxyPort(process.env.OPENCOMMAND_PROXY_PORT) ?? 3000;
226
+ const port = (await this.isPortAvailable(preferredPort))
227
+ ? preferredPort
228
+ : await this.findAvailablePort();
251
229
  this.config = {
252
230
  port,
253
231
  commandCodeToken: startConfig.commandCodeToken,
@@ -263,20 +241,32 @@ class ProxyManager {
263
241
  if (startConfig.sessionCookie) {
264
242
  env.CC_SESSION_COOKIE = startConfig.sessionCookie;
265
243
  }
244
+ const debug = isDebugEnabled();
266
245
  this.proxyProcess = cp.spawn(this.proxyBinaryPath, [], {
267
246
  env,
268
- stdio: ["ignore", "pipe", "pipe"],
269
- });
270
- this.proxyProcess.stdout?.on("data", (data) => {
271
- console.log(`[Proxy stdout] ${data}`);
272
- });
273
- this.proxyProcess.stderr?.on("data", (data) => {
274
- console.error(`[Proxy stderr] ${data}`);
247
+ stdio: ["ignore", debug ? "pipe" : "ignore", debug ? "pipe" : "ignore"],
275
248
  });
249
+ if (debug) {
250
+ this.proxyProcess.stdout?.on("data", (data) => {
251
+ debugLog(`[Proxy stdout] ${data}`);
252
+ });
253
+ this.proxyProcess.stderr?.on("data", (data) => {
254
+ debugError(`[Proxy stderr] ${data}`);
255
+ });
256
+ }
276
257
  await this.waitForHealthz(port, 30000);
277
- console.log(`✓ Proxy started on port ${port}`);
258
+ debugLog(`✓ Proxy started on port ${port}`);
278
259
  return this.config;
279
260
  }
261
+ async isPortAvailable(port) {
262
+ return new Promise((resolve) => {
263
+ const server = net.createServer();
264
+ server.once("error", () => resolve(false));
265
+ server.listen(port, () => {
266
+ server.close(() => resolve(true));
267
+ });
268
+ });
269
+ }
280
270
  async stop() {
281
271
  const proc = this.proxyProcess;
282
272
  if (!proc)
@@ -292,7 +282,7 @@ class ProxyManager {
292
282
  };
293
283
  proc.once("exit", finish);
294
284
  proc.once("error", (error) => {
295
- console.error("Proxy process error while stopping:", error);
285
+ debugError("Proxy process error while stopping:", error);
296
286
  finish();
297
287
  });
298
288
  try {
@@ -300,7 +290,7 @@ class ProxyManager {
300
290
  proc.kill("SIGTERM");
301
291
  }
302
292
  catch (error) {
303
- console.error("Failed to send SIGTERM to proxy process:", error);
293
+ debugError("Failed to send SIGTERM to proxy process:", error);
304
294
  finish();
305
295
  return;
306
296
  }
@@ -310,7 +300,7 @@ class ProxyManager {
310
300
  proc.kill("SIGKILL");
311
301
  }
312
302
  catch (error) {
313
- console.error("Failed to send SIGKILL to proxy process:", error);
303
+ debugError("Failed to send SIGKILL to proxy process:", error);
314
304
  }
315
305
  }
316
306
  finish();
@@ -331,7 +321,7 @@ class ProxyManager {
331
321
  return;
332
322
  }
333
323
  catch (err) {
334
- console.debug(`Health check attempt failed for port ${port}:`, err);
324
+ debugLog(`Health check attempt failed for port ${port}:`, err);
335
325
  }
336
326
  await new Promise((r) => setTimeout(r, 500));
337
327
  }
@@ -347,8 +337,7 @@ class ProxyManager {
347
337
  return this.proxyProcess !== null && !this.proxyProcess.killed;
348
338
  }
349
339
  }
350
- exports.ProxyManager = ProxyManager;
351
- class SecretStorage {
340
+ export class SecretStorage {
352
341
  constructor(storageDir = `${process.env.HOME || "/tmp"}/.opencommand`) {
353
342
  this.filePath = `${storageDir}/opencommand-secrets.json`;
354
343
  }
@@ -359,7 +348,7 @@ class SecretStorage {
359
348
  }
360
349
  catch (error) {
361
350
  if (error.code !== "ENOENT") {
362
- console.debug("Could not read OpenCommand secret store:", error);
351
+ debugLog("Could not read OpenCommand secret store:", error);
363
352
  }
364
353
  return {};
365
354
  }
@@ -386,8 +375,7 @@ class SecretStorage {
386
375
  await this.writeStore(store);
387
376
  }
388
377
  }
389
- exports.SecretStorage = SecretStorage;
390
- class BrowserCookieExtractor {
378
+ export class BrowserCookieExtractor {
391
379
  constructor(homeDir = os.homedir()) {
392
380
  this.homeDir = homeDir;
393
381
  }
@@ -481,7 +469,8 @@ class BrowserCookieExtractor {
481
469
  try {
482
470
  fs.copyFileSync(profile.cookieDatabase, tmpDb);
483
471
  const query = profile.browser === "Firefox" ? firefoxCookieQuery : chromiumCookieQuery;
484
- const output = cp.execFileSync("/usr/bin/sqlite3", [tmpDb, query], {
472
+ const output = cp.execFileSync("/usr/bin/sqlite3", [tmpDb], {
473
+ input: query,
485
474
  encoding: "utf8",
486
475
  timeout: 5000,
487
476
  });
@@ -501,7 +490,7 @@ class BrowserCookieExtractor {
501
490
  });
502
491
  }
503
492
  catch (error) {
504
- console.debug(`Could not read ${profile.browser} cookies:`, error);
493
+ debugLog(`Could not read ${profile.browser} cookies:`, error);
505
494
  return [];
506
495
  }
507
496
  finally {
@@ -548,7 +537,6 @@ class BrowserCookieExtractor {
548
537
  }
549
538
  }
550
539
  }
551
- exports.BrowserCookieExtractor = BrowserCookieExtractor;
552
540
  function decryptMacChromiumCookie(encryptedValue, safeStoragePassword) {
553
541
  try {
554
542
  const payload = encryptedValue.subarray(0, 3).toString() === "v10"
@@ -570,7 +558,7 @@ const firefoxCookieQuery = [
570
558
  ".mode tabs",
571
559
  "SELECT host, name, value, '' FROM moz_cookies WHERE host LIKE '%commandcode.ai%';",
572
560
  ].join("\n");
573
- class OpenCommandPlugin {
561
+ export class OpenCommandPlugin {
574
562
  constructor(proxyBinaryPath = resolveProxyBinaryPath(), storageDir) {
575
563
  this.commandCodeTokenKey = "opencommand.command_code_token";
576
564
  this.legacyTokenKey = "opencommand.cc_session_token";
@@ -581,28 +569,61 @@ class OpenCommandPlugin {
581
569
  this.cookieExtractor = new BrowserCookieExtractor();
582
570
  }
583
571
  async activate() {
584
- console.log("OpenCommand Plugin activating...");
572
+ debugLog("OpenCommand Plugin activating...");
585
573
  try {
586
574
  const config = await this.ensureStarted();
587
575
  if (config)
588
- console.log(`✓ OpenCommand proxy ready at ${config.port}`);
576
+ debugLog(`✓ OpenCommand proxy ready at ${config.port}`);
589
577
  }
590
578
  catch (error) {
591
- console.error("Failed to start proxy:", error);
579
+ debugError("Failed to start proxy:", error);
592
580
  }
593
581
  }
594
582
  async ensureStarted() {
583
+ const existingConfig = this.proxyManager.getConfig();
584
+ if (existingConfig && this.proxyManager.isRunning())
585
+ return existingConfig;
586
+ this.startPromise ?? (this.startPromise = this.startProxy());
587
+ try {
588
+ return await this.startPromise;
589
+ }
590
+ finally {
591
+ this.startPromise = undefined;
592
+ }
593
+ }
594
+ preloadForOpenCode() {
595
+ if (this.preloadPromise)
596
+ return;
597
+ this.preloadPromise = this.preload()
598
+ .catch((error) => {
599
+ debugLog("OpenCommand background preload failed:", error);
600
+ })
601
+ .finally(() => {
602
+ this.preloadPromise = undefined;
603
+ });
604
+ }
605
+ async preload() {
606
+ const proxyConfig = await this.ensureStarted();
607
+ if (!proxyConfig)
608
+ return;
609
+ const baseURL = `http://localhost:${proxyConfig.port}/v1`;
610
+ const models = (await fetchCommandCodePlanModels(proxyConfig.commandCodeToken)) ??
611
+ (await fetchOpenCommandModels(baseURL));
612
+ if (models)
613
+ writeCachedOpenCommandModels(models);
614
+ }
615
+ async startProxy() {
595
616
  const existingConfig = this.proxyManager.getConfig();
596
617
  if (existingConfig && this.proxyManager.isRunning())
597
618
  return existingConfig;
598
619
  const commandCodeToken = await this.loadCommandCodeToken();
599
620
  if (!commandCodeToken) {
600
- console.warn("No COMMAND_CODE_TOKEN found. Please configure your CommandCode API key.");
621
+ debugWarn("No COMMAND_CODE_TOKEN found. Please configure your CommandCode API key.");
601
622
  return undefined;
602
623
  }
603
624
  const sessionCookie = await this.loadSessionCookie();
604
625
  if (!sessionCookie) {
605
- console.warn("No CommandCode Studio cookie found. Inference works, but usage scraping may be unavailable.");
626
+ debugWarn("No CommandCode Studio cookie found. Inference works, but usage scraping may be unavailable.");
606
627
  }
607
628
  const config = await this.proxyManager.start({
608
629
  commandCodeToken,
@@ -612,9 +633,12 @@ class OpenCommandPlugin {
612
633
  return config;
613
634
  }
614
635
  async deactivate() {
615
- console.log("OpenCommand Plugin deactivating...");
636
+ debugLog("OpenCommand Plugin deactivating...");
616
637
  await this.proxyManager.stop();
617
- console.log("✓ Proxy stopped");
638
+ debugLog("✓ Proxy stopped");
639
+ }
640
+ getCurrentProxyConfig() {
641
+ return this.proxyManager.getConfig() ?? undefined;
618
642
  }
619
643
  async loadCommandCodeToken() {
620
644
  const token = firstDefined(process.env.COMMAND_CODE_TOKEN, process.env.COMMANDCODE_API_KEY, await this.secretStorage.get(this.commandCodeTokenKey), await this.secretStorage.get(this.legacyTokenKey));
@@ -630,13 +654,13 @@ class OpenCommandPlugin {
630
654
  const extracted = await this.cookieExtractor.extractCommandCodeCookie();
631
655
  if (extracted) {
632
656
  await this.secretStorage.set(this.sessionCookieKey, extracted);
633
- console.log("✓ CommandCode Studio cookie discovered from local browser profile");
657
+ debugLog("✓ CommandCode Studio cookie discovered from local browser profile");
634
658
  }
635
659
  return extracted;
636
660
  }
637
661
  saveOpenCodeConfig(config) {
638
662
  const proxyUrl = `http://localhost:${config.port}`;
639
- console.log("Proxy configuration saved:", {
663
+ debugLog("Proxy configuration saved:", {
640
664
  opencommand: { proxyUrl, apiBaseUrl: `${proxyUrl}/v1` },
641
665
  });
642
666
  this.persistProxyConfig(proxyUrl, config.port);
@@ -647,15 +671,15 @@ class OpenCommandPlugin {
647
671
  try {
648
672
  fs.mkdirSync(configDir, { recursive: true });
649
673
  fs.writeFileSync(configPath, JSON.stringify({ url, port, updatedAt: new Date().toISOString() }, null, 2), { mode: 0o644 });
650
- console.log(`✓ Proxy config persisted to ${configPath}`);
674
+ debugLog(`✓ Proxy config persisted to ${configPath}`);
651
675
  }
652
676
  catch (err) {
653
- console.error("Failed to persist proxy config:", err);
677
+ debugError("Failed to persist proxy config:", err);
654
678
  }
655
679
  }
656
680
  async setCommandCodeToken(token) {
657
681
  await this.secretStorage.set(this.commandCodeTokenKey, token);
658
- console.log("✓ CommandCode API key saved securely");
682
+ debugLog("✓ CommandCode API key saved securely");
659
683
  await this.restartIfPossible();
660
684
  }
661
685
  async setSessionToken(token) {
@@ -663,7 +687,7 @@ class OpenCommandPlugin {
663
687
  }
664
688
  async setSessionCookie(cookie) {
665
689
  await this.secretStorage.set(this.sessionCookieKey, cookie);
666
- console.log("✓ CommandCode Studio cookie saved securely");
690
+ debugLog("✓ CommandCode Studio cookie saved securely");
667
691
  await this.restartIfPossible();
668
692
  }
669
693
  async restartIfPossible() {
@@ -684,7 +708,6 @@ class OpenCommandPlugin {
684
708
  return JSON.stringify(config, null, 2);
685
709
  }
686
710
  }
687
- exports.OpenCommandPlugin = OpenCommandPlugin;
688
711
  function firstDefined(...values) {
689
712
  for (const value of values) {
690
713
  const trimmed = value?.trim();
@@ -698,7 +721,7 @@ function resolveProxyBinaryPath() {
698
721
  process.env.OPENCOMMAND_PROXY_PATH,
699
722
  path.join(os.homedir(), ".opencommand", "proxy"),
700
723
  path.join(process.cwd(), "packages", "proxy", "proxy"),
701
- path.join(__dirname, "proxy"),
724
+ path.join(process.cwd(), "proxy"),
702
725
  ].filter((candidate) => Boolean(candidate));
703
726
  return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[candidates.length - 1];
704
727
  }
@@ -736,7 +759,7 @@ function modelFromProxy(model) {
736
759
  cost: model.cost ?? { input: 0, output: 0 },
737
760
  };
738
761
  }
739
- async function fetchOpenCommandModels(baseURL) {
762
+ export async function fetchOpenCommandModels(baseURL) {
740
763
  try {
741
764
  const response = await fetch(`${baseURL.replace(/\/$/, "")}/models`, {
742
765
  headers: { Authorization: `Bearer ${PROVIDER_API_KEY_PLACEHOLDER}` },
@@ -750,12 +773,12 @@ async function fetchOpenCommandModels(baseURL) {
750
773
  return models.length > 0 ? models : undefined;
751
774
  }
752
775
  catch (error) {
753
- console.debug("Could not fetch OpenCommand models from local proxy:", error);
776
+ debugLog("Could not fetch OpenCommand models from local proxy:", error);
754
777
  return undefined;
755
778
  }
756
779
  }
757
- function commandCodeModelsForPlan(planID) {
758
- const models = [...exports.COMMAND_CODE_GO_PLAN_MODELS];
780
+ export function commandCodeModelsForPlan(planID) {
781
+ const models = [...COMMAND_CODE_GO_PLAN_MODELS];
759
782
  if (!planCanUsePremium(planID))
760
783
  return models;
761
784
  for (const model of COMMAND_CODE_PREMIUM_MODELS) {
@@ -765,7 +788,7 @@ function commandCodeModelsForPlan(planID) {
765
788
  }
766
789
  return models;
767
790
  }
768
- async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL = COMMAND_CODE_API_BASE_URL) {
791
+ export async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL = COMMAND_CODE_API_BASE_URL) {
769
792
  try {
770
793
  const response = await fetch(`${apiBaseURL.replace(/\/$/, "")}/alpha/billing/subscriptions`, {
771
794
  headers: {
@@ -780,11 +803,11 @@ async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL = COMMAND
780
803
  return planID ? commandCodeModelsForPlan(planID) : undefined;
781
804
  }
782
805
  catch (error) {
783
- console.debug("Could not detect CommandCode plan for model registration:", error);
806
+ debugLog("Could not detect CommandCode plan for model registration:", error);
784
807
  return undefined;
785
808
  }
786
809
  }
787
- function parseCommandCodePlanID(body) {
810
+ export function parseCommandCodePlanID(body) {
788
811
  const root = body;
789
812
  if (typeof root?.planId === "string" && root.planId.trim())
790
813
  return root.planId.trim();
@@ -813,7 +836,7 @@ function planCanUseOpus(planID) {
813
836
  function isOpusModel(modelID) {
814
837
  return modelID.toLowerCase().includes("opus");
815
838
  }
816
- function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL, modelDefinitions = exports.COMMAND_CODE_GO_PLAN_MODELS) {
839
+ export function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL, modelDefinitions = COMMAND_CODE_GO_PLAN_MODELS) {
817
840
  const models = {};
818
841
  for (const model of modelDefinitions) {
819
842
  models[model.id] = openCodeModelConfig(model);
@@ -831,37 +854,75 @@ function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL, m
831
854
  },
832
855
  };
833
856
  }
857
+ export function readCachedOpenCommandModels(maxAgeMs = MODEL_CACHE_MAX_AGE_MS) {
858
+ try {
859
+ const raw = fs.readFileSync(openCommandModelCachePath(), "utf8");
860
+ const cache = JSON.parse(raw);
861
+ if (!cache.updatedAt || !Array.isArray(cache.models))
862
+ return undefined;
863
+ const updatedAt = Date.parse(cache.updatedAt);
864
+ if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > maxAgeMs) {
865
+ return undefined;
866
+ }
867
+ return cache.models.length > 0 ? cache.models : undefined;
868
+ }
869
+ catch {
870
+ return undefined;
871
+ }
872
+ }
873
+ function writeCachedOpenCommandModels(models) {
874
+ try {
875
+ const cachePath = openCommandModelCachePath();
876
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
877
+ fs.writeFileSync(cachePath, JSON.stringify({ updatedAt: new Date().toISOString(), models }, null, 2), { mode: 0o644 });
878
+ }
879
+ catch (error) {
880
+ debugLog("Could not write OpenCommand model cache:", error);
881
+ }
882
+ }
883
+ function openCommandModelCachePath() {
884
+ return path.join(process.env.HOME || "/tmp", ".opencommand", "model-cache.json");
885
+ }
886
+ function parseProxyPort(value) {
887
+ const port = Number(value);
888
+ return Number.isInteger(port) && port > 0 && port < 65536 ? port : undefined;
889
+ }
834
890
  let runtimePlugin;
835
891
  function getRuntimePlugin() {
836
892
  runtimePlugin ?? (runtimePlugin = new OpenCommandPlugin());
837
893
  return runtimePlugin;
838
894
  }
839
- const OpenCommandOpenCodePlugin = async () => ({
895
+ export const OpenCommandOpenCodePlugin = async () => ({
896
+ provider: {
897
+ [PROVIDER_ID]: {
898
+ npm: "@ai-sdk/openai-compatible",
899
+ name: PROVIDER_NAME,
900
+ options: {
901
+ apiKey: PROVIDER_API_KEY_PLACEHOLDER,
902
+ baseURL: DEFAULT_PROXY_BASE_URL,
903
+ },
904
+ models: Object.fromEntries(COMMAND_CODE_GO_PLAN_MODELS.map((model) => [model.id, openCodeModelConfig(model)])),
905
+ },
906
+ },
840
907
  auth: {
841
908
  provider: PROVIDER_ID,
842
909
  loader: async () => {
843
- const proxyConfig = await getRuntimePlugin().ensureStarted();
844
- if (!proxyConfig)
845
- return null;
910
+ const runtime = getRuntimePlugin();
911
+ const proxyConfig = runtime.getCurrentProxyConfig();
912
+ runtime.preloadForOpenCode();
846
913
  return {
847
914
  apiKey: PROVIDER_API_KEY_PLACEHOLDER,
848
- baseURL: `http://localhost:${proxyConfig.port}/v1`,
915
+ baseURL: proxyConfig
916
+ ? `http://localhost:${proxyConfig.port}/v1`
917
+ : DEFAULT_PROXY_BASE_URL,
849
918
  };
850
919
  },
851
920
  methods: [],
852
921
  },
853
922
  config: async (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);
923
+ registerOpenCommandProvider(config, DEFAULT_PROXY_BASE_URL, readCachedOpenCommandModels() ?? COMMAND_CODE_GO_PLAN_MODELS);
924
+ getRuntimePlugin().preloadForOpenCode();
864
925
  },
865
926
  });
866
- exports.OpenCommandOpenCodePlugin = OpenCommandOpenCodePlugin;
867
- exports.default = exports.OpenCommandOpenCodePlugin;
927
+ export default OpenCommandOpenCodePlugin;
928
+ export { OpenCommandOpenCodePlugin as opencommandPlugin };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "opencommand-plugin",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
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
  }