opencommand-plugin 0.0.1 → 0.0.3

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +75 -53
  2. package/dist/index.js +458 -103
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,45 +1,56 @@
1
1
  export interface ProxyConfig {
2
2
  port: number;
3
- sessionToken: string;
3
+ commandCodeToken: string;
4
+ sessionCookie?: string;
4
5
  }
6
+ export interface ProxyStartConfig {
7
+ commandCodeToken: string;
8
+ sessionCookie?: string;
9
+ }
10
+ interface CommandCodeModelDefinition {
11
+ id: string;
12
+ name: string;
13
+ description: string;
14
+ reasoning?: boolean;
15
+ context: number;
16
+ output: number;
17
+ cost: {
18
+ input: number;
19
+ output: number;
20
+ cache_read?: number;
21
+ cache_write?: number;
22
+ };
23
+ }
24
+ interface OpenCodeProviderConfig {
25
+ npm: string;
26
+ name: string;
27
+ options: {
28
+ apiKey: string;
29
+ baseURL: string;
30
+ };
31
+ models: Record<string, unknown>;
32
+ }
33
+ interface OpenCodeConfig {
34
+ provider?: Record<string, OpenCodeProviderConfig>;
35
+ }
36
+ interface CookiePair {
37
+ name: string;
38
+ value: string;
39
+ }
40
+ export declare const COMMAND_CODE_GO_PLAN_MODELS: CommandCodeModelDefinition[];
5
41
  export declare class ProxyManager {
6
42
  private proxyProcess;
7
43
  private config;
8
44
  private proxyBinaryPath;
9
- constructor(binaryPath: string);
10
- /**
11
- * Find an available port (dynamic allocation)
12
- */
45
+ constructor(binaryPath?: string);
13
46
  findAvailablePort(): Promise<number>;
14
- /**
15
- * Start the proxy server
16
- */
17
- start(sessionToken: string): Promise<ProxyConfig>;
18
- /**
19
- * Stop the proxy server
20
- */
47
+ start(startConfig: ProxyStartConfig): Promise<ProxyConfig>;
21
48
  stop(): Promise<void>;
22
- /**
23
- * Wait for proxy to be healthy
24
- */
25
49
  private waitForHealthz;
26
- /**
27
- * Get current proxy configuration
28
- */
29
50
  getConfig(): ProxyConfig | null;
30
- /**
31
- * Get proxy base URL
32
- */
33
51
  getBaseUrl(): string | null;
34
- /**
35
- * Check if proxy is running
36
- */
37
52
  isRunning(): boolean;
38
53
  }
39
- /**
40
- * File-based persistent secret storage.
41
- * Fix: Persists session token across plugin restarts (not instance-local Map).
42
- */
43
54
  export declare class SecretStorage {
44
55
  private filePath;
45
56
  constructor(storageDir?: string);
@@ -49,38 +60,49 @@ export declare class SecretStorage {
49
60
  get(key: string): Promise<string | undefined>;
50
61
  delete(key: string): Promise<void>;
51
62
  }
52
- /**
53
- * OpenCommand Plugin Main Class
54
- */
63
+ export declare class BrowserCookieExtractor {
64
+ private readonly homeDir;
65
+ constructor(homeDir?: string);
66
+ extractCommandCodeCookie(): Promise<string | undefined>;
67
+ static buildCookieHeader(pairs: CookiePair[]): string | undefined;
68
+ private findBrowserProfiles;
69
+ private profileDirectories;
70
+ private readCommandCodeCookies;
71
+ private cookiePairFromRow;
72
+ private decryptChromiumCookie;
73
+ private safeStoragePassword;
74
+ }
55
75
  export declare class OpenCommandPlugin {
56
76
  private proxyManager;
57
77
  private secretStorage;
58
- private SESSION_TOKEN_KEY;
59
- constructor(proxyBinaryPath: string, storageDir?: string);
60
- /**
61
- * Initialize plugin on editor startup
62
- */
78
+ private cookieExtractor;
79
+ private readonly commandCodeTokenKey;
80
+ private readonly legacyTokenKey;
81
+ private readonly sessionCookieKey;
82
+ constructor(proxyBinaryPath?: string, storageDir?: string);
63
83
  activate(): Promise<void>;
64
- /**
65
- * Clean up on editor shutdown
66
- */
84
+ ensureStarted(): Promise<ProxyConfig | undefined>;
67
85
  deactivate(): Promise<void>;
68
- /**
69
- * Save proxy config to OpenCode configuration.
70
- * Fix: Persist to file so OpenCodeBar can discover the dynamic port.
71
- */
86
+ private loadCommandCodeToken;
87
+ private loadSessionCookie;
72
88
  private saveOpenCodeConfig;
73
- /**
74
- * Persist proxy URL and port to a config file (Issue #5).
75
- * OpenCodeBar reads this to discover the dynamically allocated port.
76
- */
77
89
  private persistProxyConfig;
78
- /**
79
- * Set session token via command
80
- */
90
+ setCommandCodeToken(token: string): Promise<void>;
81
91
  setSessionToken(token: string): Promise<void>;
82
- /**
83
- * Get proxy configuration
84
- */
92
+ setSessionCookie(cookie: string): Promise<void>;
93
+ private restartIfPossible;
85
94
  getProxyConfig(): string;
86
95
  }
96
+ export declare function registerOpenCommandProvider(config: OpenCodeConfig, baseURL?: string): void;
97
+ export declare const OpenCommandOpenCodePlugin: () => Promise<{
98
+ auth: {
99
+ provider: string;
100
+ loader: () => Promise<{
101
+ apiKey: string;
102
+ baseURL: string;
103
+ } | null>;
104
+ methods: never[];
105
+ };
106
+ config: (config: OpenCodeConfig) => Promise<void>;
107
+ }>;
108
+ export default OpenCommandOpenCodePlugin;
package/dist/index.js CHANGED
@@ -33,18 +33,120 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.OpenCommandPlugin = exports.SecretStorage = exports.ProxyManager = void 0;
36
+ exports.OpenCommandOpenCodePlugin = exports.OpenCommandPlugin = exports.BrowserCookieExtractor = exports.SecretStorage = exports.ProxyManager = exports.COMMAND_CODE_GO_PLAN_MODELS = void 0;
37
+ exports.registerOpenCommandProvider = registerOpenCommandProvider;
37
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"));
38
42
  const net = __importStar(require("net"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const PROVIDER_ID = "opencommand";
46
+ const PROVIDER_NAME = "CommandCode";
47
+ const PROVIDER_API_KEY_PLACEHOLDER = "opencommand";
48
+ const DEFAULT_PROXY_BASE_URL = "http://localhost:3000/v1";
49
+ exports.COMMAND_CODE_GO_PLAN_MODELS = [
50
+ {
51
+ id: "deepseek/deepseek-v4-pro",
52
+ name: "DeepSeek V4 Pro",
53
+ description: "Hybrid-attention long-context reasoning",
54
+ reasoning: true,
55
+ context: 1000000,
56
+ output: 8192,
57
+ cost: { input: 1.74, output: 3.48, cache_read: 0.0145 },
58
+ },
59
+ {
60
+ id: "deepseek/deepseek-v4-flash",
61
+ name: "DeepSeek V4 Flash",
62
+ description: "Fast hybrid-attention reasoning",
63
+ reasoning: true,
64
+ context: 1000000,
65
+ output: 8192,
66
+ cost: { input: 0.14, output: 0.28, cache_read: 0.01 },
67
+ },
68
+ {
69
+ id: "moonshotai/Kimi-K2.6",
70
+ name: "Kimi K2.6",
71
+ description: "Long-horizon coding with vision",
72
+ context: 256000,
73
+ output: 8192,
74
+ cost: { input: 0.95, output: 4.0, cache_read: 0.16 },
75
+ },
76
+ {
77
+ id: "moonshotai/Kimi-K2.5",
78
+ name: "Kimi K2.5",
79
+ description: "Multimodal frontend coding",
80
+ context: 256000,
81
+ output: 8192,
82
+ cost: { input: 0.6, output: 3.0, cache_read: 0.1 },
83
+ },
84
+ {
85
+ id: "zai-org/GLM-5.1",
86
+ name: "GLM-5.1",
87
+ description: "Long-horizon autonomous coding agent",
88
+ context: 200000,
89
+ output: 8192,
90
+ cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
91
+ },
92
+ {
93
+ id: "zai-org/GLM-5",
94
+ name: "GLM-5",
95
+ description: "Multi-mode thinking and long-range planning",
96
+ context: 200000,
97
+ output: 8192,
98
+ cost: { input: 1.0, output: 3.2, cache_read: 0.2 },
99
+ },
100
+ {
101
+ id: "MiniMaxAI/MiniMax-M2.7",
102
+ name: "MiniMax M2.7",
103
+ description: "End-to-end software engineering agent",
104
+ context: 200000,
105
+ output: 8192,
106
+ cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
107
+ },
108
+ {
109
+ id: "MiniMaxAI/MiniMax-M2.5",
110
+ name: "MiniMax M2.5",
111
+ description: "Cross-platform full-stack agentic development",
112
+ context: 200000,
113
+ output: 8192,
114
+ cost: { input: 0.27, output: 0.95, cache_read: 0.03 },
115
+ },
116
+ {
117
+ id: "Qwen/Qwen3.6-Max-Preview",
118
+ name: "Qwen 3.6 Max Preview",
119
+ description: "Vibe coding and efficient agent execution",
120
+ reasoning: true,
121
+ context: 200000,
122
+ output: 8192,
123
+ cost: { input: 1.3, output: 7.8, cache_read: 0.26, cache_write: 1.63 },
124
+ },
125
+ {
126
+ id: "Qwen/Qwen3.6-Plus",
127
+ name: "Qwen 3.6 Plus",
128
+ description: "Agentic coding and reasoning",
129
+ reasoning: true,
130
+ context: 200000,
131
+ output: 8192,
132
+ cost: { input: 0.5, output: 3.0, cache_read: 0.1 },
133
+ },
134
+ {
135
+ id: "stepfun/Step-3.5-Flash",
136
+ name: "Step 3.5 Flash",
137
+ description: "Fast sparse-MoE agentic reasoning",
138
+ reasoning: true,
139
+ context: 1000000,
140
+ output: 8192,
141
+ cost: { input: 0.1, output: 0.3, cache_read: 0.02 },
142
+ },
143
+ ];
39
144
  class ProxyManager {
40
- constructor(binaryPath) {
145
+ constructor(binaryPath = resolveProxyBinaryPath()) {
41
146
  this.proxyProcess = null;
42
147
  this.config = null;
43
148
  this.proxyBinaryPath = binaryPath;
44
149
  }
45
- /**
46
- * Find an available port (dynamic allocation)
47
- */
48
150
  async findAvailablePort() {
49
151
  return new Promise((resolve, reject) => {
50
152
  const server = net.createServer();
@@ -52,53 +154,48 @@ class ProxyManager {
52
154
  const address = server.address();
53
155
  if (address && typeof address === "object" && address.port) {
54
156
  server.close(() => resolve(address.port));
157
+ return;
55
158
  }
56
- else {
57
- reject(new Error("Failed to find available port"));
58
- }
159
+ reject(new Error("Failed to find available port"));
59
160
  });
60
161
  server.on("error", reject);
61
162
  });
62
163
  }
63
- /**
64
- * Start the proxy server
65
- */
66
- async start(sessionToken) {
67
- // Find available port
164
+ async start(startConfig) {
68
165
  const port = await this.findAvailablePort();
69
- this.config = { port, sessionToken };
70
- // Prepare environment
166
+ this.config = {
167
+ port,
168
+ commandCodeToken: startConfig.commandCodeToken,
169
+ sessionCookie: startConfig.sessionCookie,
170
+ };
71
171
  const env = {
72
172
  ...process.env,
73
173
  PORT: String(port),
74
- CC_SESSION_COOKIE: sessionToken,
174
+ COMMAND_CODE_TOKEN: startConfig.commandCodeToken,
175
+ COMMANDCODE_API_KEY: startConfig.commandCodeToken,
75
176
  PRODUCTION: "true",
76
177
  };
77
- // Start proxy process
178
+ if (startConfig.sessionCookie) {
179
+ env.CC_SESSION_COOKIE = startConfig.sessionCookie;
180
+ }
78
181
  this.proxyProcess = cp.spawn(this.proxyBinaryPath, [], {
79
182
  env,
80
183
  stdio: ["ignore", "pipe", "pipe"],
81
184
  });
82
- // Log output
83
185
  this.proxyProcess.stdout?.on("data", (data) => {
84
186
  console.log(`[Proxy stdout] ${data}`);
85
187
  });
86
188
  this.proxyProcess.stderr?.on("data", (data) => {
87
189
  console.error(`[Proxy stderr] ${data}`);
88
190
  });
89
- // Wait for proxy to be ready
90
191
  await this.waitForHealthz(port, 30000);
91
192
  console.log(`✓ Proxy started on port ${port}`);
92
193
  return this.config;
93
194
  }
94
- /**
95
- * Stop the proxy server
96
- */
97
195
  async stop() {
98
196
  const proc = this.proxyProcess;
99
- if (!proc) {
197
+ if (!proc)
100
198
  return;
101
- }
102
199
  return new Promise((resolve) => {
103
200
  let resolved = false;
104
201
  const finish = () => {
@@ -114,9 +211,8 @@ class ProxyManager {
114
211
  finish();
115
212
  });
116
213
  try {
117
- if (!proc.killed) {
214
+ if (!proc.killed)
118
215
  proc.kill("SIGTERM");
119
- }
120
216
  }
121
217
  catch (error) {
122
218
  console.error("Failed to send SIGTERM to proxy process:", error);
@@ -136,9 +232,6 @@ class ProxyManager {
136
232
  }, 5000);
137
233
  });
138
234
  }
139
- /**
140
- * Wait for proxy to be healthy
141
- */
142
235
  async waitForHealthz(port, timeoutMs) {
143
236
  const startTime = Date.now();
144
237
  while (Date.now() - startTime < timeoutMs) {
@@ -149,9 +242,8 @@ class ProxyManager {
149
242
  signal: controller.signal,
150
243
  });
151
244
  clearTimeout(timeoutId);
152
- if (response.ok) {
245
+ if (response.ok)
153
246
  return;
154
- }
155
247
  }
156
248
  catch (err) {
157
249
  console.debug(`Health check attempt failed for port ${port}:`, err);
@@ -160,38 +252,24 @@ class ProxyManager {
160
252
  }
161
253
  throw new Error(`Proxy failed to become healthy after ${timeoutMs}ms on port ${port}`);
162
254
  }
163
- /**
164
- * Get current proxy configuration
165
- */
166
255
  getConfig() {
167
256
  return this.config;
168
257
  }
169
- /**
170
- * Get proxy base URL
171
- */
172
258
  getBaseUrl() {
173
259
  return this.config ? `http://localhost:${this.config.port}` : null;
174
260
  }
175
- /**
176
- * Check if proxy is running
177
- */
178
261
  isRunning() {
179
262
  return this.proxyProcess !== null && !this.proxyProcess.killed;
180
263
  }
181
264
  }
182
265
  exports.ProxyManager = ProxyManager;
183
- /**
184
- * File-based persistent secret storage.
185
- * Fix: Persists session token across plugin restarts (not instance-local Map).
186
- */
187
266
  class SecretStorage {
188
267
  constructor(storageDir = `${process.env.HOME || "/tmp"}/.opencommand`) {
189
268
  this.filePath = `${storageDir}/opencommand-secrets.json`;
190
269
  }
191
270
  async readStore() {
192
- const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
193
271
  try {
194
- const data = await fs.readFile(this.filePath, "utf-8");
272
+ const data = await fsp.readFile(this.filePath, "utf-8");
195
273
  return JSON.parse(data);
196
274
  }
197
275
  catch (error) {
@@ -202,11 +280,9 @@ class SecretStorage {
202
280
  }
203
281
  }
204
282
  async writeStore(store) {
205
- const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
206
- const path = await Promise.resolve().then(() => __importStar(require("path")));
207
283
  const dir = path.dirname(this.filePath);
208
- await fs.mkdir(dir, { recursive: true });
209
- await fs.writeFile(this.filePath, JSON.stringify(store, null, 2), {
284
+ await fsp.mkdir(dir, { recursive: true });
285
+ await fsp.writeFile(this.filePath, JSON.stringify(store, null, 2), {
210
286
  mode: 0o600,
211
287
  });
212
288
  }
@@ -226,70 +302,261 @@ class SecretStorage {
226
302
  }
227
303
  }
228
304
  exports.SecretStorage = SecretStorage;
229
- /**
230
- * OpenCommand Plugin Main Class
231
- */
305
+ class BrowserCookieExtractor {
306
+ constructor(homeDir = os.homedir()) {
307
+ this.homeDir = homeDir;
308
+ }
309
+ async extractCommandCodeCookie() {
310
+ const rows = this.findBrowserProfiles().flatMap((profile) => this.readCommandCodeCookies(profile));
311
+ const pairs = rows
312
+ .map((row) => this.cookiePairFromRow(row))
313
+ .filter((pair) => pair !== undefined);
314
+ return BrowserCookieExtractor.buildCookieHeader(pairs);
315
+ }
316
+ static buildCookieHeader(pairs) {
317
+ const unique = new Map();
318
+ for (const pair of pairs) {
319
+ if (pair.name && pair.value)
320
+ unique.set(pair.name, pair.value);
321
+ }
322
+ if (unique.size === 0)
323
+ return undefined;
324
+ return [...unique.entries()]
325
+ .map(([name, value]) => `${name}=${value}`)
326
+ .join("; ");
327
+ }
328
+ findBrowserProfiles() {
329
+ const candidates = [
330
+ {
331
+ browser: "Chrome",
332
+ base: "Library/Application Support/Google/Chrome",
333
+ services: ["Chrome Safe Storage", "Google Chrome Safe Storage"],
334
+ },
335
+ {
336
+ browser: "Comet",
337
+ base: "Library/Application Support/Comet",
338
+ services: ["Comet Safe Storage", "Chrome Safe Storage"],
339
+ },
340
+ {
341
+ browser: "Brave",
342
+ base: "Library/Application Support/BraveSoftware/Brave-Browser",
343
+ services: ["Brave Safe Storage", "Chrome Safe Storage"],
344
+ },
345
+ {
346
+ browser: "Edge",
347
+ base: "Library/Application Support/Microsoft Edge",
348
+ services: ["Microsoft Edge Safe Storage", "Chrome Safe Storage"],
349
+ },
350
+ ];
351
+ const profiles = [];
352
+ for (const candidate of candidates) {
353
+ const basePath = path.join(this.homeDir, candidate.base);
354
+ for (const profileDir of this.profileDirectories(basePath)) {
355
+ for (const cookieDatabase of [
356
+ path.join(profileDir, "Network", "Cookies"),
357
+ path.join(profileDir, "Cookies"),
358
+ ]) {
359
+ if (fs.existsSync(cookieDatabase)) {
360
+ profiles.push({
361
+ browser: candidate.browser,
362
+ safeStorageServices: candidate.services,
363
+ cookieDatabase,
364
+ });
365
+ }
366
+ }
367
+ }
368
+ }
369
+ const firefoxBase = path.join(this.homeDir, "Library/Application Support/Firefox/Profiles");
370
+ for (const profileDir of this.profileDirectories(firefoxBase)) {
371
+ const cookieDatabase = path.join(profileDir, "cookies.sqlite");
372
+ if (fs.existsSync(cookieDatabase)) {
373
+ profiles.push({
374
+ browser: "Firefox",
375
+ safeStorageServices: [],
376
+ cookieDatabase,
377
+ });
378
+ }
379
+ }
380
+ return profiles;
381
+ }
382
+ profileDirectories(basePath) {
383
+ if (!fs.existsSync(basePath))
384
+ return [];
385
+ const directCookie = path.join(basePath, "cookies.sqlite");
386
+ if (fs.existsSync(directCookie))
387
+ return [basePath];
388
+ return fs
389
+ .readdirSync(basePath, { withFileTypes: true })
390
+ .filter((entry) => entry.isDirectory())
391
+ .map((entry) => path.join(basePath, entry.name));
392
+ }
393
+ readCommandCodeCookies(profile) {
394
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencommand-cookies-"));
395
+ const tmpDb = path.join(tmpDir, "cookies.sqlite");
396
+ try {
397
+ fs.copyFileSync(profile.cookieDatabase, tmpDb);
398
+ const query = profile.browser === "Firefox" ? firefoxCookieQuery : chromiumCookieQuery;
399
+ const output = cp.execFileSync("/usr/bin/sqlite3", [tmpDb, query], {
400
+ encoding: "utf8",
401
+ timeout: 5000,
402
+ });
403
+ return output
404
+ .split("\n")
405
+ .filter(Boolean)
406
+ .map((line) => {
407
+ const [host = "", name = "", value = "", encryptedHex = ""] = line.split("\t");
408
+ return {
409
+ host,
410
+ name,
411
+ value,
412
+ encryptedHex,
413
+ browser: profile.browser,
414
+ safeStorageServices: profile.safeStorageServices,
415
+ };
416
+ });
417
+ }
418
+ catch (error) {
419
+ console.debug(`Could not read ${profile.browser} cookies:`, error);
420
+ return [];
421
+ }
422
+ finally {
423
+ fs.rmSync(tmpDir, { recursive: true, force: true });
424
+ }
425
+ }
426
+ cookiePairFromRow(row) {
427
+ if (!row.host.includes("commandcode.ai") || !row.name)
428
+ return undefined;
429
+ const value = row.value || this.decryptChromiumCookie(row);
430
+ if (!value)
431
+ return undefined;
432
+ return { name: row.name, value };
433
+ }
434
+ decryptChromiumCookie(row) {
435
+ if (!row.encryptedHex || row.browser === "Firefox")
436
+ return undefined;
437
+ const encrypted = Buffer.from(row.encryptedHex, "hex");
438
+ if (encrypted.length <= 3)
439
+ return undefined;
440
+ for (const service of row.safeStorageServices) {
441
+ const password = this.safeStoragePassword(service);
442
+ if (!password)
443
+ continue;
444
+ const decrypted = decryptMacChromiumCookie(encrypted, password);
445
+ if (decrypted)
446
+ return decrypted;
447
+ }
448
+ return undefined;
449
+ }
450
+ safeStoragePassword(service) {
451
+ try {
452
+ return cp
453
+ .execFileSync("/usr/bin/security", [
454
+ "find-generic-password",
455
+ "-w",
456
+ "-s",
457
+ service,
458
+ ], { encoding: "utf8", timeout: 5000 })
459
+ .trim();
460
+ }
461
+ catch {
462
+ return undefined;
463
+ }
464
+ }
465
+ }
466
+ exports.BrowserCookieExtractor = BrowserCookieExtractor;
467
+ function decryptMacChromiumCookie(encryptedValue, safeStoragePassword) {
468
+ try {
469
+ const payload = encryptedValue.subarray(0, 3).toString() === "v10"
470
+ ? encryptedValue.subarray(3)
471
+ : encryptedValue;
472
+ const key = crypto.pbkdf2Sync(safeStoragePassword, "saltysalt", 1003, 16, "sha1");
473
+ const decipher = crypto.createDecipheriv("aes-128-cbc", key, Buffer.alloc(16, " "));
474
+ return Buffer.concat([decipher.update(payload), decipher.final()]).toString("utf8");
475
+ }
476
+ catch {
477
+ return undefined;
478
+ }
479
+ }
480
+ const chromiumCookieQuery = [
481
+ ".mode tabs",
482
+ "SELECT host_key, name, value, hex(encrypted_value) FROM cookies WHERE host_key LIKE '%commandcode.ai%';",
483
+ ].join("\n");
484
+ const firefoxCookieQuery = [
485
+ ".mode tabs",
486
+ "SELECT host, name, value, '' FROM moz_cookies WHERE host LIKE '%commandcode.ai%';",
487
+ ].join("\n");
232
488
  class OpenCommandPlugin {
233
- constructor(proxyBinaryPath, storageDir) {
234
- this.SESSION_TOKEN_KEY = "opencommand.cc_session_token";
489
+ constructor(proxyBinaryPath = resolveProxyBinaryPath(), storageDir) {
490
+ this.commandCodeTokenKey = "opencommand.command_code_token";
491
+ this.legacyTokenKey = "opencommand.cc_session_token";
492
+ this.sessionCookieKey = "opencommand.cc_session_cookie";
235
493
  this.proxyManager = new ProxyManager(proxyBinaryPath);
236
494
  const dir = storageDir || `${process.env.HOME || "/tmp"}/.opencommand`;
237
495
  this.secretStorage = new SecretStorage(dir);
496
+ this.cookieExtractor = new BrowserCookieExtractor();
238
497
  }
239
- /**
240
- * Initialize plugin on editor startup
241
- */
242
498
  async activate() {
243
499
  console.log("OpenCommand Plugin activating...");
244
- // Try to retrieve stored session token
245
- const storedToken = await this.secretStorage.get(this.SESSION_TOKEN_KEY);
246
- if (!storedToken) {
247
- console.warn("No CC_SESSION_COOKIE found. Please configure token.");
248
- return;
249
- }
250
- // Start proxy
251
500
  try {
252
- const config = await this.proxyManager.start(storedToken);
253
- console.log(`✓ OpenCommand proxy ready at ${config.port}`);
254
- // Store config for use by OpenCode
255
- this.saveOpenCodeConfig(config);
501
+ const config = await this.ensureStarted();
502
+ if (config)
503
+ console.log(`✓ OpenCommand proxy ready at ${config.port}`);
256
504
  }
257
505
  catch (error) {
258
506
  console.error("Failed to start proxy:", error);
259
507
  }
260
508
  }
261
- /**
262
- * Clean up on editor shutdown
263
- */
509
+ async ensureStarted() {
510
+ const existingConfig = this.proxyManager.getConfig();
511
+ if (existingConfig && this.proxyManager.isRunning())
512
+ return existingConfig;
513
+ const commandCodeToken = await this.loadCommandCodeToken();
514
+ if (!commandCodeToken) {
515
+ console.warn("No COMMAND_CODE_TOKEN found. Please configure your CommandCode API key.");
516
+ return undefined;
517
+ }
518
+ const sessionCookie = await this.loadSessionCookie();
519
+ if (!sessionCookie) {
520
+ console.warn("No CommandCode Studio cookie found. Inference works, but usage scraping may be unavailable.");
521
+ }
522
+ const config = await this.proxyManager.start({
523
+ commandCodeToken,
524
+ sessionCookie,
525
+ });
526
+ this.saveOpenCodeConfig(config);
527
+ return config;
528
+ }
264
529
  async deactivate() {
265
530
  console.log("OpenCommand Plugin deactivating...");
266
531
  await this.proxyManager.stop();
267
532
  console.log("✓ Proxy stopped");
268
533
  }
269
- /**
270
- * Save proxy config to OpenCode configuration.
271
- * Fix: Persist to file so OpenCodeBar can discover the dynamic port.
272
- */
534
+ async loadCommandCodeToken() {
535
+ 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));
536
+ if (token && token === (await this.secretStorage.get(this.legacyTokenKey))) {
537
+ await this.secretStorage.set(this.commandCodeTokenKey, token);
538
+ }
539
+ return token;
540
+ }
541
+ async loadSessionCookie() {
542
+ const configured = firstDefined(process.env.CC_SESSION_COOKIE, await this.secretStorage.get(this.sessionCookieKey));
543
+ if (configured)
544
+ return configured;
545
+ const extracted = await this.cookieExtractor.extractCommandCodeCookie();
546
+ if (extracted) {
547
+ await this.secretStorage.set(this.sessionCookieKey, extracted);
548
+ console.log("✓ CommandCode Studio cookie discovered from local browser profile");
549
+ }
550
+ return extracted;
551
+ }
273
552
  saveOpenCodeConfig(config) {
274
553
  const proxyUrl = `http://localhost:${config.port}`;
275
- const openCodeConfig = {
276
- opencommand: {
277
- proxyUrl,
278
- apiBaseUrl: `${proxyUrl}/v1`,
279
- },
280
- };
281
- // Log for debugging
282
- console.log("Proxy configuration saved:", openCodeConfig);
283
- // Persist to config file (readable by OpenCodeBar)
554
+ console.log("Proxy configuration saved:", {
555
+ opencommand: { proxyUrl, apiBaseUrl: `${proxyUrl}/v1` },
556
+ });
284
557
  this.persistProxyConfig(proxyUrl, config.port);
285
558
  }
286
- /**
287
- * Persist proxy URL and port to a config file (Issue #5).
288
- * OpenCodeBar reads this to discover the dynamically allocated port.
289
- */
290
559
  persistProxyConfig(url, port) {
291
- const fs = require("fs");
292
- const path = require("path");
293
560
  const configDir = `${process.env.HOME || "/tmp"}/.opencommand`;
294
561
  const configPath = path.join(configDir, "proxy-config.json");
295
562
  try {
@@ -301,26 +568,114 @@ class OpenCommandPlugin {
301
568
  console.error("Failed to persist proxy config:", err);
302
569
  }
303
570
  }
304
- /**
305
- * Set session token via command
306
- */
571
+ async setCommandCodeToken(token) {
572
+ await this.secretStorage.set(this.commandCodeTokenKey, token);
573
+ console.log("✓ CommandCode API key saved securely");
574
+ await this.restartIfPossible();
575
+ }
307
576
  async setSessionToken(token) {
308
- await this.secretStorage.set(this.SESSION_TOKEN_KEY, token);
309
- console.log("✓ Session token saved securely");
310
- // Restart proxy with new token
577
+ await this.setCommandCodeToken(token);
578
+ }
579
+ async setSessionCookie(cookie) {
580
+ await this.secretStorage.set(this.sessionCookieKey, cookie);
581
+ console.log("✓ CommandCode Studio cookie saved securely");
582
+ await this.restartIfPossible();
583
+ }
584
+ async restartIfPossible() {
585
+ const commandCodeToken = await this.loadCommandCodeToken();
586
+ if (!commandCodeToken)
587
+ return;
311
588
  await this.proxyManager.stop();
312
- const config = await this.proxyManager.start(token);
589
+ const config = await this.proxyManager.start({
590
+ commandCodeToken,
591
+ sessionCookie: await this.loadSessionCookie(),
592
+ });
313
593
  this.saveOpenCodeConfig(config);
314
594
  }
315
- /**
316
- * Get proxy configuration
317
- */
318
595
  getProxyConfig() {
319
596
  const config = this.proxyManager.getConfig();
320
- if (!config) {
597
+ if (!config)
321
598
  return "Proxy not running";
322
- }
323
599
  return JSON.stringify(config, null, 2);
324
600
  }
325
601
  }
326
602
  exports.OpenCommandPlugin = OpenCommandPlugin;
603
+ function firstDefined(...values) {
604
+ for (const value of values) {
605
+ const trimmed = value?.trim();
606
+ if (trimmed)
607
+ return trimmed;
608
+ }
609
+ return undefined;
610
+ }
611
+ function resolveProxyBinaryPath() {
612
+ const candidates = [
613
+ process.env.OPENCOMMAND_PROXY_PATH,
614
+ path.join(os.homedir(), ".opencommand", "proxy"),
615
+ path.join(process.cwd(), "packages", "proxy", "proxy"),
616
+ path.join(__dirname, "proxy"),
617
+ ].filter((candidate) => Boolean(candidate));
618
+ return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[candidates.length - 1];
619
+ }
620
+ function openCodeModelConfig(model) {
621
+ return {
622
+ id: model.id,
623
+ name: model.name,
624
+ description: model.description,
625
+ reasoning: model.reasoning ?? false,
626
+ tool_call: true,
627
+ attachment: false,
628
+ cost: model.cost,
629
+ limit: {
630
+ context: model.context,
631
+ output: model.output,
632
+ },
633
+ modalities: {
634
+ input: ["text"],
635
+ output: ["text"],
636
+ },
637
+ };
638
+ }
639
+ function registerOpenCommandProvider(config, baseURL = DEFAULT_PROXY_BASE_URL) {
640
+ const models = {};
641
+ for (const model of exports.COMMAND_CODE_GO_PLAN_MODELS) {
642
+ models[model.id] = openCodeModelConfig(model);
643
+ }
644
+ config.provider = {
645
+ ...(config.provider ?? {}),
646
+ [PROVIDER_ID]: {
647
+ npm: "@ai-sdk/openai-compatible",
648
+ name: PROVIDER_NAME,
649
+ options: {
650
+ apiKey: PROVIDER_API_KEY_PLACEHOLDER,
651
+ baseURL,
652
+ },
653
+ models,
654
+ },
655
+ };
656
+ }
657
+ let runtimePlugin;
658
+ function getRuntimePlugin() {
659
+ runtimePlugin ?? (runtimePlugin = new OpenCommandPlugin());
660
+ return runtimePlugin;
661
+ }
662
+ const OpenCommandOpenCodePlugin = async () => ({
663
+ auth: {
664
+ provider: PROVIDER_ID,
665
+ loader: async () => {
666
+ const proxyConfig = await getRuntimePlugin().ensureStarted();
667
+ if (!proxyConfig)
668
+ return null;
669
+ return {
670
+ apiKey: PROVIDER_API_KEY_PLACEHOLDER,
671
+ baseURL: `http://localhost:${proxyConfig.port}/v1`,
672
+ };
673
+ },
674
+ methods: [],
675
+ },
676
+ config: async (config) => {
677
+ registerOpenCommandProvider(config);
678
+ },
679
+ });
680
+ exports.OpenCommandOpenCodePlugin = OpenCommandOpenCodePlugin;
681
+ exports.default = exports.OpenCommandOpenCodePlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencommand-plugin",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "OpenCommand - CommandCode API Plugin for OpenCode",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {