opencommand-plugin 0.0.5 → 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 +2 -2
- package/dist/index.d.ts +9 -1
- package/dist/index.js +139 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ npm run build
|
|
|
47
47
|
npm test -- --runInBand
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
## v0.0.
|
|
50
|
+
## v0.0.6 non-blocking OpenCode startup
|
|
51
51
|
|
|
52
|
-
`opencommand-plugin@0.0.
|
|
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
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,6 +106,7 @@ 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<{
|
|
103
111
|
provider: {
|
|
104
112
|
opencommand: {
|
|
@@ -118,7 +126,7 @@ export declare const OpenCommandOpenCodePlugin: () => Promise<{
|
|
|
118
126
|
loader: () => Promise<{
|
|
119
127
|
apiKey: string;
|
|
120
128
|
baseURL: string;
|
|
121
|
-
}
|
|
129
|
+
}>;
|
|
122
130
|
methods: never[];
|
|
123
131
|
};
|
|
124
132
|
config: (config: OpenCodeConfig) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,22 @@ const PROVIDER_NAME = "CommandCode";
|
|
|
10
10
|
const PROVIDER_API_KEY_PLACEHOLDER = "opencommand";
|
|
11
11
|
const DEFAULT_PROXY_BASE_URL = "http://localhost:3000/v1";
|
|
12
12
|
const COMMAND_CODE_API_BASE_URL = "https://api.commandcode.ai";
|
|
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
|
+
}
|
|
13
29
|
export const COMMAND_CODE_GO_PLAN_MODELS = [
|
|
14
30
|
{
|
|
15
31
|
id: "deepseek/deepseek-v4-pro",
|
|
@@ -206,7 +222,10 @@ export class ProxyManager {
|
|
|
206
222
|
});
|
|
207
223
|
}
|
|
208
224
|
async start(startConfig) {
|
|
209
|
-
const
|
|
225
|
+
const preferredPort = parseProxyPort(process.env.OPENCOMMAND_PROXY_PORT) ?? 3000;
|
|
226
|
+
const port = (await this.isPortAvailable(preferredPort))
|
|
227
|
+
? preferredPort
|
|
228
|
+
: await this.findAvailablePort();
|
|
210
229
|
this.config = {
|
|
211
230
|
port,
|
|
212
231
|
commandCodeToken: startConfig.commandCodeToken,
|
|
@@ -222,20 +241,32 @@ export class ProxyManager {
|
|
|
222
241
|
if (startConfig.sessionCookie) {
|
|
223
242
|
env.CC_SESSION_COOKIE = startConfig.sessionCookie;
|
|
224
243
|
}
|
|
244
|
+
const debug = isDebugEnabled();
|
|
225
245
|
this.proxyProcess = cp.spawn(this.proxyBinaryPath, [], {
|
|
226
246
|
env,
|
|
227
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
228
|
-
});
|
|
229
|
-
this.proxyProcess.stdout?.on("data", (data) => {
|
|
230
|
-
console.log(`[Proxy stdout] ${data}`);
|
|
231
|
-
});
|
|
232
|
-
this.proxyProcess.stderr?.on("data", (data) => {
|
|
233
|
-
console.error(`[Proxy stderr] ${data}`);
|
|
247
|
+
stdio: ["ignore", debug ? "pipe" : "ignore", debug ? "pipe" : "ignore"],
|
|
234
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
|
+
}
|
|
235
257
|
await this.waitForHealthz(port, 30000);
|
|
236
|
-
|
|
258
|
+
debugLog(`✓ Proxy started on port ${port}`);
|
|
237
259
|
return this.config;
|
|
238
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
|
+
}
|
|
239
270
|
async stop() {
|
|
240
271
|
const proc = this.proxyProcess;
|
|
241
272
|
if (!proc)
|
|
@@ -251,7 +282,7 @@ export class ProxyManager {
|
|
|
251
282
|
};
|
|
252
283
|
proc.once("exit", finish);
|
|
253
284
|
proc.once("error", (error) => {
|
|
254
|
-
|
|
285
|
+
debugError("Proxy process error while stopping:", error);
|
|
255
286
|
finish();
|
|
256
287
|
});
|
|
257
288
|
try {
|
|
@@ -259,7 +290,7 @@ export class ProxyManager {
|
|
|
259
290
|
proc.kill("SIGTERM");
|
|
260
291
|
}
|
|
261
292
|
catch (error) {
|
|
262
|
-
|
|
293
|
+
debugError("Failed to send SIGTERM to proxy process:", error);
|
|
263
294
|
finish();
|
|
264
295
|
return;
|
|
265
296
|
}
|
|
@@ -269,7 +300,7 @@ export class ProxyManager {
|
|
|
269
300
|
proc.kill("SIGKILL");
|
|
270
301
|
}
|
|
271
302
|
catch (error) {
|
|
272
|
-
|
|
303
|
+
debugError("Failed to send SIGKILL to proxy process:", error);
|
|
273
304
|
}
|
|
274
305
|
}
|
|
275
306
|
finish();
|
|
@@ -290,7 +321,7 @@ export class ProxyManager {
|
|
|
290
321
|
return;
|
|
291
322
|
}
|
|
292
323
|
catch (err) {
|
|
293
|
-
|
|
324
|
+
debugLog(`Health check attempt failed for port ${port}:`, err);
|
|
294
325
|
}
|
|
295
326
|
await new Promise((r) => setTimeout(r, 500));
|
|
296
327
|
}
|
|
@@ -317,7 +348,7 @@ export class SecretStorage {
|
|
|
317
348
|
}
|
|
318
349
|
catch (error) {
|
|
319
350
|
if (error.code !== "ENOENT") {
|
|
320
|
-
|
|
351
|
+
debugLog("Could not read OpenCommand secret store:", error);
|
|
321
352
|
}
|
|
322
353
|
return {};
|
|
323
354
|
}
|
|
@@ -459,7 +490,7 @@ export class BrowserCookieExtractor {
|
|
|
459
490
|
});
|
|
460
491
|
}
|
|
461
492
|
catch (error) {
|
|
462
|
-
|
|
493
|
+
debugLog(`Could not read ${profile.browser} cookies:`, error);
|
|
463
494
|
return [];
|
|
464
495
|
}
|
|
465
496
|
finally {
|
|
@@ -538,28 +569,61 @@ export class OpenCommandPlugin {
|
|
|
538
569
|
this.cookieExtractor = new BrowserCookieExtractor();
|
|
539
570
|
}
|
|
540
571
|
async activate() {
|
|
541
|
-
|
|
572
|
+
debugLog("OpenCommand Plugin activating...");
|
|
542
573
|
try {
|
|
543
574
|
const config = await this.ensureStarted();
|
|
544
575
|
if (config)
|
|
545
|
-
|
|
576
|
+
debugLog(`✓ OpenCommand proxy ready at ${config.port}`);
|
|
546
577
|
}
|
|
547
578
|
catch (error) {
|
|
548
|
-
|
|
579
|
+
debugError("Failed to start proxy:", error);
|
|
549
580
|
}
|
|
550
581
|
}
|
|
551
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() {
|
|
552
616
|
const existingConfig = this.proxyManager.getConfig();
|
|
553
617
|
if (existingConfig && this.proxyManager.isRunning())
|
|
554
618
|
return existingConfig;
|
|
555
619
|
const commandCodeToken = await this.loadCommandCodeToken();
|
|
556
620
|
if (!commandCodeToken) {
|
|
557
|
-
|
|
621
|
+
debugWarn("No COMMAND_CODE_TOKEN found. Please configure your CommandCode API key.");
|
|
558
622
|
return undefined;
|
|
559
623
|
}
|
|
560
624
|
const sessionCookie = await this.loadSessionCookie();
|
|
561
625
|
if (!sessionCookie) {
|
|
562
|
-
|
|
626
|
+
debugWarn("No CommandCode Studio cookie found. Inference works, but usage scraping may be unavailable.");
|
|
563
627
|
}
|
|
564
628
|
const config = await this.proxyManager.start({
|
|
565
629
|
commandCodeToken,
|
|
@@ -569,9 +633,12 @@ export class OpenCommandPlugin {
|
|
|
569
633
|
return config;
|
|
570
634
|
}
|
|
571
635
|
async deactivate() {
|
|
572
|
-
|
|
636
|
+
debugLog("OpenCommand Plugin deactivating...");
|
|
573
637
|
await this.proxyManager.stop();
|
|
574
|
-
|
|
638
|
+
debugLog("✓ Proxy stopped");
|
|
639
|
+
}
|
|
640
|
+
getCurrentProxyConfig() {
|
|
641
|
+
return this.proxyManager.getConfig() ?? undefined;
|
|
575
642
|
}
|
|
576
643
|
async loadCommandCodeToken() {
|
|
577
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));
|
|
@@ -587,13 +654,13 @@ export class OpenCommandPlugin {
|
|
|
587
654
|
const extracted = await this.cookieExtractor.extractCommandCodeCookie();
|
|
588
655
|
if (extracted) {
|
|
589
656
|
await this.secretStorage.set(this.sessionCookieKey, extracted);
|
|
590
|
-
|
|
657
|
+
debugLog("✓ CommandCode Studio cookie discovered from local browser profile");
|
|
591
658
|
}
|
|
592
659
|
return extracted;
|
|
593
660
|
}
|
|
594
661
|
saveOpenCodeConfig(config) {
|
|
595
662
|
const proxyUrl = `http://localhost:${config.port}`;
|
|
596
|
-
|
|
663
|
+
debugLog("Proxy configuration saved:", {
|
|
597
664
|
opencommand: { proxyUrl, apiBaseUrl: `${proxyUrl}/v1` },
|
|
598
665
|
});
|
|
599
666
|
this.persistProxyConfig(proxyUrl, config.port);
|
|
@@ -604,15 +671,15 @@ export class OpenCommandPlugin {
|
|
|
604
671
|
try {
|
|
605
672
|
fs.mkdirSync(configDir, { recursive: true });
|
|
606
673
|
fs.writeFileSync(configPath, JSON.stringify({ url, port, updatedAt: new Date().toISOString() }, null, 2), { mode: 0o644 });
|
|
607
|
-
|
|
674
|
+
debugLog(`✓ Proxy config persisted to ${configPath}`);
|
|
608
675
|
}
|
|
609
676
|
catch (err) {
|
|
610
|
-
|
|
677
|
+
debugError("Failed to persist proxy config:", err);
|
|
611
678
|
}
|
|
612
679
|
}
|
|
613
680
|
async setCommandCodeToken(token) {
|
|
614
681
|
await this.secretStorage.set(this.commandCodeTokenKey, token);
|
|
615
|
-
|
|
682
|
+
debugLog("✓ CommandCode API key saved securely");
|
|
616
683
|
await this.restartIfPossible();
|
|
617
684
|
}
|
|
618
685
|
async setSessionToken(token) {
|
|
@@ -620,7 +687,7 @@ export class OpenCommandPlugin {
|
|
|
620
687
|
}
|
|
621
688
|
async setSessionCookie(cookie) {
|
|
622
689
|
await this.secretStorage.set(this.sessionCookieKey, cookie);
|
|
623
|
-
|
|
690
|
+
debugLog("✓ CommandCode Studio cookie saved securely");
|
|
624
691
|
await this.restartIfPossible();
|
|
625
692
|
}
|
|
626
693
|
async restartIfPossible() {
|
|
@@ -654,7 +721,7 @@ function resolveProxyBinaryPath() {
|
|
|
654
721
|
process.env.OPENCOMMAND_PROXY_PATH,
|
|
655
722
|
path.join(os.homedir(), ".opencommand", "proxy"),
|
|
656
723
|
path.join(process.cwd(), "packages", "proxy", "proxy"),
|
|
657
|
-
path.join(
|
|
724
|
+
path.join(process.cwd(), "proxy"),
|
|
658
725
|
].filter((candidate) => Boolean(candidate));
|
|
659
726
|
return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[candidates.length - 1];
|
|
660
727
|
}
|
|
@@ -706,7 +773,7 @@ export async function fetchOpenCommandModels(baseURL) {
|
|
|
706
773
|
return models.length > 0 ? models : undefined;
|
|
707
774
|
}
|
|
708
775
|
catch (error) {
|
|
709
|
-
|
|
776
|
+
debugLog("Could not fetch OpenCommand models from local proxy:", error);
|
|
710
777
|
return undefined;
|
|
711
778
|
}
|
|
712
779
|
}
|
|
@@ -736,7 +803,7 @@ export async function fetchCommandCodePlanModels(commandCodeToken, apiBaseURL =
|
|
|
736
803
|
return planID ? commandCodeModelsForPlan(planID) : undefined;
|
|
737
804
|
}
|
|
738
805
|
catch (error) {
|
|
739
|
-
|
|
806
|
+
debugLog("Could not detect CommandCode plan for model registration:", error);
|
|
740
807
|
return undefined;
|
|
741
808
|
}
|
|
742
809
|
}
|
|
@@ -787,6 +854,39 @@ export function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE
|
|
|
787
854
|
},
|
|
788
855
|
};
|
|
789
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
|
+
}
|
|
790
890
|
let runtimePlugin;
|
|
791
891
|
function getRuntimePlugin() {
|
|
792
892
|
runtimePlugin ?? (runtimePlugin = new OpenCommandPlugin());
|
|
@@ -807,27 +907,21 @@ export const OpenCommandOpenCodePlugin = async () => ({
|
|
|
807
907
|
auth: {
|
|
808
908
|
provider: PROVIDER_ID,
|
|
809
909
|
loader: async () => {
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
910
|
+
const runtime = getRuntimePlugin();
|
|
911
|
+
const proxyConfig = runtime.getCurrentProxyConfig();
|
|
912
|
+
runtime.preloadForOpenCode();
|
|
813
913
|
return {
|
|
814
914
|
apiKey: PROVIDER_API_KEY_PLACEHOLDER,
|
|
815
|
-
baseURL:
|
|
915
|
+
baseURL: proxyConfig
|
|
916
|
+
? `http://localhost:${proxyConfig.port}/v1`
|
|
917
|
+
: DEFAULT_PROXY_BASE_URL,
|
|
816
918
|
};
|
|
817
919
|
},
|
|
818
920
|
methods: [],
|
|
819
921
|
},
|
|
820
922
|
config: async (config) => {
|
|
821
|
-
|
|
822
|
-
|
|
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);
|
|
923
|
+
registerOpenCommandProvider(config, DEFAULT_PROXY_BASE_URL, readCachedOpenCommandModels() ?? COMMAND_CODE_GO_PLAN_MODELS);
|
|
924
|
+
getRuntimePlugin().preloadForOpenCode();
|
|
831
925
|
},
|
|
832
926
|
});
|
|
833
927
|
export default OpenCommandOpenCodePlugin;
|