opencommand-plugin 0.0.1 → 0.0.2
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/dist/index.d.ts +33 -51
- package/dist/index.js +273 -97
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,45 +1,29 @@
|
|
|
1
1
|
export interface ProxyConfig {
|
|
2
2
|
port: number;
|
|
3
|
-
|
|
3
|
+
commandCodeToken: string;
|
|
4
|
+
sessionCookie?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ProxyStartConfig {
|
|
7
|
+
commandCodeToken: string;
|
|
8
|
+
sessionCookie?: string;
|
|
9
|
+
}
|
|
10
|
+
interface CookiePair {
|
|
11
|
+
name: string;
|
|
12
|
+
value: string;
|
|
4
13
|
}
|
|
5
14
|
export declare class ProxyManager {
|
|
6
15
|
private proxyProcess;
|
|
7
16
|
private config;
|
|
8
17
|
private proxyBinaryPath;
|
|
9
18
|
constructor(binaryPath: string);
|
|
10
|
-
/**
|
|
11
|
-
* Find an available port (dynamic allocation)
|
|
12
|
-
*/
|
|
13
19
|
findAvailablePort(): Promise<number>;
|
|
14
|
-
|
|
15
|
-
* Start the proxy server
|
|
16
|
-
*/
|
|
17
|
-
start(sessionToken: string): Promise<ProxyConfig>;
|
|
18
|
-
/**
|
|
19
|
-
* Stop the proxy server
|
|
20
|
-
*/
|
|
20
|
+
start(startConfig: ProxyStartConfig): Promise<ProxyConfig>;
|
|
21
21
|
stop(): Promise<void>;
|
|
22
|
-
/**
|
|
23
|
-
* Wait for proxy to be healthy
|
|
24
|
-
*/
|
|
25
22
|
private waitForHealthz;
|
|
26
|
-
/**
|
|
27
|
-
* Get current proxy configuration
|
|
28
|
-
*/
|
|
29
23
|
getConfig(): ProxyConfig | null;
|
|
30
|
-
/**
|
|
31
|
-
* Get proxy base URL
|
|
32
|
-
*/
|
|
33
24
|
getBaseUrl(): string | null;
|
|
34
|
-
/**
|
|
35
|
-
* Check if proxy is running
|
|
36
|
-
*/
|
|
37
25
|
isRunning(): boolean;
|
|
38
26
|
}
|
|
39
|
-
/**
|
|
40
|
-
* File-based persistent secret storage.
|
|
41
|
-
* Fix: Persists session token across plugin restarts (not instance-local Map).
|
|
42
|
-
*/
|
|
43
27
|
export declare class SecretStorage {
|
|
44
28
|
private filePath;
|
|
45
29
|
constructor(storageDir?: string);
|
|
@@ -49,38 +33,36 @@ export declare class SecretStorage {
|
|
|
49
33
|
get(key: string): Promise<string | undefined>;
|
|
50
34
|
delete(key: string): Promise<void>;
|
|
51
35
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
export declare class BrowserCookieExtractor {
|
|
37
|
+
private readonly homeDir;
|
|
38
|
+
constructor(homeDir?: string);
|
|
39
|
+
extractCommandCodeCookie(): Promise<string | undefined>;
|
|
40
|
+
static buildCookieHeader(pairs: CookiePair[]): string | undefined;
|
|
41
|
+
private findBrowserProfiles;
|
|
42
|
+
private profileDirectories;
|
|
43
|
+
private readCommandCodeCookies;
|
|
44
|
+
private cookiePairFromRow;
|
|
45
|
+
private decryptChromiumCookie;
|
|
46
|
+
private safeStoragePassword;
|
|
47
|
+
}
|
|
55
48
|
export declare class OpenCommandPlugin {
|
|
56
49
|
private proxyManager;
|
|
57
50
|
private secretStorage;
|
|
58
|
-
private
|
|
51
|
+
private cookieExtractor;
|
|
52
|
+
private readonly commandCodeTokenKey;
|
|
53
|
+
private readonly legacyTokenKey;
|
|
54
|
+
private readonly sessionCookieKey;
|
|
59
55
|
constructor(proxyBinaryPath: string, storageDir?: string);
|
|
60
|
-
/**
|
|
61
|
-
* Initialize plugin on editor startup
|
|
62
|
-
*/
|
|
63
56
|
activate(): Promise<void>;
|
|
64
|
-
/**
|
|
65
|
-
* Clean up on editor shutdown
|
|
66
|
-
*/
|
|
67
57
|
deactivate(): Promise<void>;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* Fix: Persist to file so OpenCodeBar can discover the dynamic port.
|
|
71
|
-
*/
|
|
58
|
+
private loadCommandCodeToken;
|
|
59
|
+
private loadSessionCookie;
|
|
72
60
|
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
61
|
private persistProxyConfig;
|
|
78
|
-
|
|
79
|
-
* Set session token via command
|
|
80
|
-
*/
|
|
62
|
+
setCommandCodeToken(token: string): Promise<void>;
|
|
81
63
|
setSessionToken(token: string): Promise<void>;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*/
|
|
64
|
+
setSessionCookie(cookie: string): Promise<void>;
|
|
65
|
+
private restartIfPossible;
|
|
85
66
|
getProxyConfig(): string;
|
|
86
67
|
}
|
|
68
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -33,18 +33,20 @@ 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.OpenCommandPlugin = exports.BrowserCookieExtractor = exports.SecretStorage = exports.ProxyManager = void 0;
|
|
37
37
|
const cp = __importStar(require("child_process"));
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const fsp = __importStar(require("fs/promises"));
|
|
38
41
|
const net = __importStar(require("net"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
39
44
|
class ProxyManager {
|
|
40
45
|
constructor(binaryPath) {
|
|
41
46
|
this.proxyProcess = null;
|
|
42
47
|
this.config = null;
|
|
43
48
|
this.proxyBinaryPath = binaryPath;
|
|
44
49
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Find an available port (dynamic allocation)
|
|
47
|
-
*/
|
|
48
50
|
async findAvailablePort() {
|
|
49
51
|
return new Promise((resolve, reject) => {
|
|
50
52
|
const server = net.createServer();
|
|
@@ -52,53 +54,48 @@ class ProxyManager {
|
|
|
52
54
|
const address = server.address();
|
|
53
55
|
if (address && typeof address === "object" && address.port) {
|
|
54
56
|
server.close(() => resolve(address.port));
|
|
57
|
+
return;
|
|
55
58
|
}
|
|
56
|
-
|
|
57
|
-
reject(new Error("Failed to find available port"));
|
|
58
|
-
}
|
|
59
|
+
reject(new Error("Failed to find available port"));
|
|
59
60
|
});
|
|
60
61
|
server.on("error", reject);
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
-
* Start the proxy server
|
|
65
|
-
*/
|
|
66
|
-
async start(sessionToken) {
|
|
67
|
-
// Find available port
|
|
64
|
+
async start(startConfig) {
|
|
68
65
|
const port = await this.findAvailablePort();
|
|
69
|
-
this.config = {
|
|
70
|
-
|
|
66
|
+
this.config = {
|
|
67
|
+
port,
|
|
68
|
+
commandCodeToken: startConfig.commandCodeToken,
|
|
69
|
+
sessionCookie: startConfig.sessionCookie,
|
|
70
|
+
};
|
|
71
71
|
const env = {
|
|
72
72
|
...process.env,
|
|
73
73
|
PORT: String(port),
|
|
74
|
-
|
|
74
|
+
COMMAND_CODE_TOKEN: startConfig.commandCodeToken,
|
|
75
|
+
COMMANDCODE_API_KEY: startConfig.commandCodeToken,
|
|
75
76
|
PRODUCTION: "true",
|
|
76
77
|
};
|
|
77
|
-
|
|
78
|
+
if (startConfig.sessionCookie) {
|
|
79
|
+
env.CC_SESSION_COOKIE = startConfig.sessionCookie;
|
|
80
|
+
}
|
|
78
81
|
this.proxyProcess = cp.spawn(this.proxyBinaryPath, [], {
|
|
79
82
|
env,
|
|
80
83
|
stdio: ["ignore", "pipe", "pipe"],
|
|
81
84
|
});
|
|
82
|
-
// Log output
|
|
83
85
|
this.proxyProcess.stdout?.on("data", (data) => {
|
|
84
86
|
console.log(`[Proxy stdout] ${data}`);
|
|
85
87
|
});
|
|
86
88
|
this.proxyProcess.stderr?.on("data", (data) => {
|
|
87
89
|
console.error(`[Proxy stderr] ${data}`);
|
|
88
90
|
});
|
|
89
|
-
// Wait for proxy to be ready
|
|
90
91
|
await this.waitForHealthz(port, 30000);
|
|
91
92
|
console.log(`✓ Proxy started on port ${port}`);
|
|
92
93
|
return this.config;
|
|
93
94
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Stop the proxy server
|
|
96
|
-
*/
|
|
97
95
|
async stop() {
|
|
98
96
|
const proc = this.proxyProcess;
|
|
99
|
-
if (!proc)
|
|
97
|
+
if (!proc)
|
|
100
98
|
return;
|
|
101
|
-
}
|
|
102
99
|
return new Promise((resolve) => {
|
|
103
100
|
let resolved = false;
|
|
104
101
|
const finish = () => {
|
|
@@ -114,9 +111,8 @@ class ProxyManager {
|
|
|
114
111
|
finish();
|
|
115
112
|
});
|
|
116
113
|
try {
|
|
117
|
-
if (!proc.killed)
|
|
114
|
+
if (!proc.killed)
|
|
118
115
|
proc.kill("SIGTERM");
|
|
119
|
-
}
|
|
120
116
|
}
|
|
121
117
|
catch (error) {
|
|
122
118
|
console.error("Failed to send SIGTERM to proxy process:", error);
|
|
@@ -136,9 +132,6 @@ class ProxyManager {
|
|
|
136
132
|
}, 5000);
|
|
137
133
|
});
|
|
138
134
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Wait for proxy to be healthy
|
|
141
|
-
*/
|
|
142
135
|
async waitForHealthz(port, timeoutMs) {
|
|
143
136
|
const startTime = Date.now();
|
|
144
137
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -149,9 +142,8 @@ class ProxyManager {
|
|
|
149
142
|
signal: controller.signal,
|
|
150
143
|
});
|
|
151
144
|
clearTimeout(timeoutId);
|
|
152
|
-
if (response.ok)
|
|
145
|
+
if (response.ok)
|
|
153
146
|
return;
|
|
154
|
-
}
|
|
155
147
|
}
|
|
156
148
|
catch (err) {
|
|
157
149
|
console.debug(`Health check attempt failed for port ${port}:`, err);
|
|
@@ -160,38 +152,24 @@ class ProxyManager {
|
|
|
160
152
|
}
|
|
161
153
|
throw new Error(`Proxy failed to become healthy after ${timeoutMs}ms on port ${port}`);
|
|
162
154
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Get current proxy configuration
|
|
165
|
-
*/
|
|
166
155
|
getConfig() {
|
|
167
156
|
return this.config;
|
|
168
157
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Get proxy base URL
|
|
171
|
-
*/
|
|
172
158
|
getBaseUrl() {
|
|
173
159
|
return this.config ? `http://localhost:${this.config.port}` : null;
|
|
174
160
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Check if proxy is running
|
|
177
|
-
*/
|
|
178
161
|
isRunning() {
|
|
179
162
|
return this.proxyProcess !== null && !this.proxyProcess.killed;
|
|
180
163
|
}
|
|
181
164
|
}
|
|
182
165
|
exports.ProxyManager = ProxyManager;
|
|
183
|
-
/**
|
|
184
|
-
* File-based persistent secret storage.
|
|
185
|
-
* Fix: Persists session token across plugin restarts (not instance-local Map).
|
|
186
|
-
*/
|
|
187
166
|
class SecretStorage {
|
|
188
167
|
constructor(storageDir = `${process.env.HOME || "/tmp"}/.opencommand`) {
|
|
189
168
|
this.filePath = `${storageDir}/opencommand-secrets.json`;
|
|
190
169
|
}
|
|
191
170
|
async readStore() {
|
|
192
|
-
const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
193
171
|
try {
|
|
194
|
-
const data = await
|
|
172
|
+
const data = await fsp.readFile(this.filePath, "utf-8");
|
|
195
173
|
return JSON.parse(data);
|
|
196
174
|
}
|
|
197
175
|
catch (error) {
|
|
@@ -202,11 +180,9 @@ class SecretStorage {
|
|
|
202
180
|
}
|
|
203
181
|
}
|
|
204
182
|
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
183
|
const dir = path.dirname(this.filePath);
|
|
208
|
-
await
|
|
209
|
-
await
|
|
184
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
185
|
+
await fsp.writeFile(this.filePath, JSON.stringify(store, null, 2), {
|
|
210
186
|
mode: 0o600,
|
|
211
187
|
});
|
|
212
188
|
}
|
|
@@ -226,70 +202,253 @@ class SecretStorage {
|
|
|
226
202
|
}
|
|
227
203
|
}
|
|
228
204
|
exports.SecretStorage = SecretStorage;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
205
|
+
class BrowserCookieExtractor {
|
|
206
|
+
constructor(homeDir = os.homedir()) {
|
|
207
|
+
this.homeDir = homeDir;
|
|
208
|
+
}
|
|
209
|
+
async extractCommandCodeCookie() {
|
|
210
|
+
const rows = this.findBrowserProfiles().flatMap((profile) => this.readCommandCodeCookies(profile));
|
|
211
|
+
const pairs = rows
|
|
212
|
+
.map((row) => this.cookiePairFromRow(row))
|
|
213
|
+
.filter((pair) => pair !== undefined);
|
|
214
|
+
return BrowserCookieExtractor.buildCookieHeader(pairs);
|
|
215
|
+
}
|
|
216
|
+
static buildCookieHeader(pairs) {
|
|
217
|
+
const unique = new Map();
|
|
218
|
+
for (const pair of pairs) {
|
|
219
|
+
if (pair.name && pair.value)
|
|
220
|
+
unique.set(pair.name, pair.value);
|
|
221
|
+
}
|
|
222
|
+
if (unique.size === 0)
|
|
223
|
+
return undefined;
|
|
224
|
+
return [...unique.entries()]
|
|
225
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
226
|
+
.join("; ");
|
|
227
|
+
}
|
|
228
|
+
findBrowserProfiles() {
|
|
229
|
+
const candidates = [
|
|
230
|
+
{
|
|
231
|
+
browser: "Chrome",
|
|
232
|
+
base: "Library/Application Support/Google/Chrome",
|
|
233
|
+
services: ["Chrome Safe Storage", "Google Chrome Safe Storage"],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
browser: "Comet",
|
|
237
|
+
base: "Library/Application Support/Comet",
|
|
238
|
+
services: ["Comet Safe Storage", "Chrome Safe Storage"],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
browser: "Brave",
|
|
242
|
+
base: "Library/Application Support/BraveSoftware/Brave-Browser",
|
|
243
|
+
services: ["Brave Safe Storage", "Chrome Safe Storage"],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
browser: "Edge",
|
|
247
|
+
base: "Library/Application Support/Microsoft Edge",
|
|
248
|
+
services: ["Microsoft Edge Safe Storage", "Chrome Safe Storage"],
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
const profiles = [];
|
|
252
|
+
for (const candidate of candidates) {
|
|
253
|
+
const basePath = path.join(this.homeDir, candidate.base);
|
|
254
|
+
for (const profileDir of this.profileDirectories(basePath)) {
|
|
255
|
+
for (const cookieDatabase of [
|
|
256
|
+
path.join(profileDir, "Network", "Cookies"),
|
|
257
|
+
path.join(profileDir, "Cookies"),
|
|
258
|
+
]) {
|
|
259
|
+
if (fs.existsSync(cookieDatabase)) {
|
|
260
|
+
profiles.push({
|
|
261
|
+
browser: candidate.browser,
|
|
262
|
+
safeStorageServices: candidate.services,
|
|
263
|
+
cookieDatabase,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const firefoxBase = path.join(this.homeDir, "Library/Application Support/Firefox/Profiles");
|
|
270
|
+
for (const profileDir of this.profileDirectories(firefoxBase)) {
|
|
271
|
+
const cookieDatabase = path.join(profileDir, "cookies.sqlite");
|
|
272
|
+
if (fs.existsSync(cookieDatabase)) {
|
|
273
|
+
profiles.push({
|
|
274
|
+
browser: "Firefox",
|
|
275
|
+
safeStorageServices: [],
|
|
276
|
+
cookieDatabase,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return profiles;
|
|
281
|
+
}
|
|
282
|
+
profileDirectories(basePath) {
|
|
283
|
+
if (!fs.existsSync(basePath))
|
|
284
|
+
return [];
|
|
285
|
+
const directCookie = path.join(basePath, "cookies.sqlite");
|
|
286
|
+
if (fs.existsSync(directCookie))
|
|
287
|
+
return [basePath];
|
|
288
|
+
return fs
|
|
289
|
+
.readdirSync(basePath, { withFileTypes: true })
|
|
290
|
+
.filter((entry) => entry.isDirectory())
|
|
291
|
+
.map((entry) => path.join(basePath, entry.name));
|
|
292
|
+
}
|
|
293
|
+
readCommandCodeCookies(profile) {
|
|
294
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencommand-cookies-"));
|
|
295
|
+
const tmpDb = path.join(tmpDir, "cookies.sqlite");
|
|
296
|
+
try {
|
|
297
|
+
fs.copyFileSync(profile.cookieDatabase, tmpDb);
|
|
298
|
+
const query = profile.browser === "Firefox" ? firefoxCookieQuery : chromiumCookieQuery;
|
|
299
|
+
const output = cp.execFileSync("/usr/bin/sqlite3", [tmpDb, query], {
|
|
300
|
+
encoding: "utf8",
|
|
301
|
+
timeout: 5000,
|
|
302
|
+
});
|
|
303
|
+
return output
|
|
304
|
+
.split("\n")
|
|
305
|
+
.filter(Boolean)
|
|
306
|
+
.map((line) => {
|
|
307
|
+
const [host = "", name = "", value = "", encryptedHex = ""] = line.split("\t");
|
|
308
|
+
return {
|
|
309
|
+
host,
|
|
310
|
+
name,
|
|
311
|
+
value,
|
|
312
|
+
encryptedHex,
|
|
313
|
+
browser: profile.browser,
|
|
314
|
+
safeStorageServices: profile.safeStorageServices,
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.debug(`Could not read ${profile.browser} cookies:`, error);
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
cookiePairFromRow(row) {
|
|
327
|
+
if (!row.host.includes("commandcode.ai") || !row.name)
|
|
328
|
+
return undefined;
|
|
329
|
+
const value = row.value || this.decryptChromiumCookie(row);
|
|
330
|
+
if (!value)
|
|
331
|
+
return undefined;
|
|
332
|
+
return { name: row.name, value };
|
|
333
|
+
}
|
|
334
|
+
decryptChromiumCookie(row) {
|
|
335
|
+
if (!row.encryptedHex || row.browser === "Firefox")
|
|
336
|
+
return undefined;
|
|
337
|
+
const encrypted = Buffer.from(row.encryptedHex, "hex");
|
|
338
|
+
if (encrypted.length <= 3)
|
|
339
|
+
return undefined;
|
|
340
|
+
for (const service of row.safeStorageServices) {
|
|
341
|
+
const password = this.safeStoragePassword(service);
|
|
342
|
+
if (!password)
|
|
343
|
+
continue;
|
|
344
|
+
const decrypted = decryptMacChromiumCookie(encrypted, password);
|
|
345
|
+
if (decrypted)
|
|
346
|
+
return decrypted;
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
safeStoragePassword(service) {
|
|
351
|
+
try {
|
|
352
|
+
return cp
|
|
353
|
+
.execFileSync("/usr/bin/security", [
|
|
354
|
+
"find-generic-password",
|
|
355
|
+
"-w",
|
|
356
|
+
"-s",
|
|
357
|
+
service,
|
|
358
|
+
], { encoding: "utf8", timeout: 5000 })
|
|
359
|
+
.trim();
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
exports.BrowserCookieExtractor = BrowserCookieExtractor;
|
|
367
|
+
function decryptMacChromiumCookie(encryptedValue, safeStoragePassword) {
|
|
368
|
+
try {
|
|
369
|
+
const payload = encryptedValue.subarray(0, 3).toString() === "v10"
|
|
370
|
+
? encryptedValue.subarray(3)
|
|
371
|
+
: encryptedValue;
|
|
372
|
+
const key = crypto.pbkdf2Sync(safeStoragePassword, "saltysalt", 1003, 16, "sha1");
|
|
373
|
+
const decipher = crypto.createDecipheriv("aes-128-cbc", key, Buffer.alloc(16, " "));
|
|
374
|
+
return Buffer.concat([decipher.update(payload), decipher.final()]).toString("utf8");
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const chromiumCookieQuery = [
|
|
381
|
+
".mode tabs",
|
|
382
|
+
"SELECT host_key, name, value, hex(encrypted_value) FROM cookies WHERE host_key LIKE '%commandcode.ai%';",
|
|
383
|
+
].join("\n");
|
|
384
|
+
const firefoxCookieQuery = [
|
|
385
|
+
".mode tabs",
|
|
386
|
+
"SELECT host, name, value, '' FROM moz_cookies WHERE host LIKE '%commandcode.ai%';",
|
|
387
|
+
].join("\n");
|
|
232
388
|
class OpenCommandPlugin {
|
|
233
389
|
constructor(proxyBinaryPath, storageDir) {
|
|
234
|
-
this.
|
|
390
|
+
this.commandCodeTokenKey = "opencommand.command_code_token";
|
|
391
|
+
this.legacyTokenKey = "opencommand.cc_session_token";
|
|
392
|
+
this.sessionCookieKey = "opencommand.cc_session_cookie";
|
|
235
393
|
this.proxyManager = new ProxyManager(proxyBinaryPath);
|
|
236
394
|
const dir = storageDir || `${process.env.HOME || "/tmp"}/.opencommand`;
|
|
237
395
|
this.secretStorage = new SecretStorage(dir);
|
|
396
|
+
this.cookieExtractor = new BrowserCookieExtractor();
|
|
238
397
|
}
|
|
239
|
-
/**
|
|
240
|
-
* Initialize plugin on editor startup
|
|
241
|
-
*/
|
|
242
398
|
async activate() {
|
|
243
399
|
console.log("OpenCommand Plugin activating...");
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
console.warn("No CC_SESSION_COOKIE found. Please configure token.");
|
|
400
|
+
const commandCodeToken = await this.loadCommandCodeToken();
|
|
401
|
+
if (!commandCodeToken) {
|
|
402
|
+
console.warn("No COMMAND_CODE_TOKEN found. Please configure your CommandCode API key.");
|
|
248
403
|
return;
|
|
249
404
|
}
|
|
250
|
-
|
|
405
|
+
const sessionCookie = await this.loadSessionCookie();
|
|
406
|
+
if (!sessionCookie) {
|
|
407
|
+
console.warn("No CommandCode Studio cookie found. Inference works, but usage scraping may be unavailable.");
|
|
408
|
+
}
|
|
251
409
|
try {
|
|
252
|
-
const config = await this.proxyManager.start(
|
|
410
|
+
const config = await this.proxyManager.start({
|
|
411
|
+
commandCodeToken,
|
|
412
|
+
sessionCookie,
|
|
413
|
+
});
|
|
253
414
|
console.log(`✓ OpenCommand proxy ready at ${config.port}`);
|
|
254
|
-
// Store config for use by OpenCode
|
|
255
415
|
this.saveOpenCodeConfig(config);
|
|
256
416
|
}
|
|
257
417
|
catch (error) {
|
|
258
418
|
console.error("Failed to start proxy:", error);
|
|
259
419
|
}
|
|
260
420
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Clean up on editor shutdown
|
|
263
|
-
*/
|
|
264
421
|
async deactivate() {
|
|
265
422
|
console.log("OpenCommand Plugin deactivating...");
|
|
266
423
|
await this.proxyManager.stop();
|
|
267
424
|
console.log("✓ Proxy stopped");
|
|
268
425
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
426
|
+
async loadCommandCodeToken() {
|
|
427
|
+
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));
|
|
428
|
+
if (token && token === (await this.secretStorage.get(this.legacyTokenKey))) {
|
|
429
|
+
await this.secretStorage.set(this.commandCodeTokenKey, token);
|
|
430
|
+
}
|
|
431
|
+
return token;
|
|
432
|
+
}
|
|
433
|
+
async loadSessionCookie() {
|
|
434
|
+
const configured = firstDefined(process.env.CC_SESSION_COOKIE, await this.secretStorage.get(this.sessionCookieKey));
|
|
435
|
+
if (configured)
|
|
436
|
+
return configured;
|
|
437
|
+
const extracted = await this.cookieExtractor.extractCommandCodeCookie();
|
|
438
|
+
if (extracted) {
|
|
439
|
+
await this.secretStorage.set(this.sessionCookieKey, extracted);
|
|
440
|
+
console.log("✓ CommandCode Studio cookie discovered from local browser profile");
|
|
441
|
+
}
|
|
442
|
+
return extracted;
|
|
443
|
+
}
|
|
273
444
|
saveOpenCodeConfig(config) {
|
|
274
445
|
const proxyUrl = `http://localhost:${config.port}`;
|
|
275
|
-
|
|
276
|
-
opencommand: {
|
|
277
|
-
|
|
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)
|
|
446
|
+
console.log("Proxy configuration saved:", {
|
|
447
|
+
opencommand: { proxyUrl, apiBaseUrl: `${proxyUrl}/v1` },
|
|
448
|
+
});
|
|
284
449
|
this.persistProxyConfig(proxyUrl, config.port);
|
|
285
450
|
}
|
|
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
451
|
persistProxyConfig(url, port) {
|
|
291
|
-
const fs = require("fs");
|
|
292
|
-
const path = require("path");
|
|
293
452
|
const configDir = `${process.env.HOME || "/tmp"}/.opencommand`;
|
|
294
453
|
const configPath = path.join(configDir, "proxy-config.json");
|
|
295
454
|
try {
|
|
@@ -301,26 +460,43 @@ class OpenCommandPlugin {
|
|
|
301
460
|
console.error("Failed to persist proxy config:", err);
|
|
302
461
|
}
|
|
303
462
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
463
|
+
async setCommandCodeToken(token) {
|
|
464
|
+
await this.secretStorage.set(this.commandCodeTokenKey, token);
|
|
465
|
+
console.log("✓ CommandCode API key saved securely");
|
|
466
|
+
await this.restartIfPossible();
|
|
467
|
+
}
|
|
307
468
|
async setSessionToken(token) {
|
|
308
|
-
await this.
|
|
309
|
-
|
|
310
|
-
|
|
469
|
+
await this.setCommandCodeToken(token);
|
|
470
|
+
}
|
|
471
|
+
async setSessionCookie(cookie) {
|
|
472
|
+
await this.secretStorage.set(this.sessionCookieKey, cookie);
|
|
473
|
+
console.log("✓ CommandCode Studio cookie saved securely");
|
|
474
|
+
await this.restartIfPossible();
|
|
475
|
+
}
|
|
476
|
+
async restartIfPossible() {
|
|
477
|
+
const commandCodeToken = await this.loadCommandCodeToken();
|
|
478
|
+
if (!commandCodeToken)
|
|
479
|
+
return;
|
|
311
480
|
await this.proxyManager.stop();
|
|
312
|
-
const config = await this.proxyManager.start(
|
|
481
|
+
const config = await this.proxyManager.start({
|
|
482
|
+
commandCodeToken,
|
|
483
|
+
sessionCookie: await this.loadSessionCookie(),
|
|
484
|
+
});
|
|
313
485
|
this.saveOpenCodeConfig(config);
|
|
314
486
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Get proxy configuration
|
|
317
|
-
*/
|
|
318
487
|
getProxyConfig() {
|
|
319
488
|
const config = this.proxyManager.getConfig();
|
|
320
|
-
if (!config)
|
|
489
|
+
if (!config)
|
|
321
490
|
return "Proxy not running";
|
|
322
|
-
}
|
|
323
491
|
return JSON.stringify(config, null, 2);
|
|
324
492
|
}
|
|
325
493
|
}
|
|
326
494
|
exports.OpenCommandPlugin = OpenCommandPlugin;
|
|
495
|
+
function firstDefined(...values) {
|
|
496
|
+
for (const value of values) {
|
|
497
|
+
const trimmed = value?.trim();
|
|
498
|
+
if (trimmed)
|
|
499
|
+
return trimmed;
|
|
500
|
+
}
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|