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 +5 -0
- package/bin/opencode-plugin.js +3 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.js +173 -112
- package/package.json +17 -3
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
|
+
|
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
|
-
}
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
572
|
+
debugLog("OpenCommand Plugin activating...");
|
|
585
573
|
try {
|
|
586
574
|
const config = await this.ensureStarted();
|
|
587
575
|
if (config)
|
|
588
|
-
|
|
576
|
+
debugLog(`✓ OpenCommand proxy ready at ${config.port}`);
|
|
589
577
|
}
|
|
590
578
|
catch (error) {
|
|
591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
636
|
+
debugLog("OpenCommand Plugin deactivating...");
|
|
616
637
|
await this.proxyManager.stop();
|
|
617
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
674
|
+
debugLog(`✓ Proxy config persisted to ${configPath}`);
|
|
651
675
|
}
|
|
652
676
|
catch (err) {
|
|
653
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 = [...
|
|
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
|
-
|
|
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 =
|
|
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
|
|
844
|
-
|
|
845
|
-
|
|
910
|
+
const runtime = getRuntimePlugin();
|
|
911
|
+
const proxyConfig = runtime.getCurrentProxyConfig();
|
|
912
|
+
runtime.preloadForOpenCode();
|
|
846
913
|
return {
|
|
847
914
|
apiKey: PROVIDER_API_KEY_PLACEHOLDER,
|
|
848
|
-
baseURL:
|
|
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
|
-
|
|
855
|
-
|
|
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
|
-
|
|
867
|
-
|
|
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.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "OpenCommand - CommandCode API Plugin for OpenCode",
|
|
5
|
-
"main": "
|
|
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
|
}
|