patchcord 0.3.56 → 0.3.58
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/bin/patchcord.mjs +175 -251
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -77,8 +77,8 @@ if (cmd === "plugin-path") {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// ── main flow: global setup + project setup (or just install/agent for back-compat) ──
|
|
80
|
-
if (!cmd || cmd === "install" || cmd === "agent") {
|
|
81
|
-
const flags = process.argv.slice(3);
|
|
80
|
+
if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd === "--no-browser") {
|
|
81
|
+
const flags = cmd?.startsWith("--") ? process.argv.slice(2) : process.argv.slice(3);
|
|
82
82
|
const fullStatusline = flags.includes("--full");
|
|
83
83
|
const { readFileSync, writeFileSync } = await import("fs");
|
|
84
84
|
|
|
@@ -252,254 +252,46 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
252
252
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
253
253
|
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
console.log(` ${cyan}2.${r} Codex CLI`);
|
|
258
|
-
console.log(` ${cyan}3.${r} Cursor`);
|
|
259
|
-
console.log(` ${cyan}4.${r} Windsurf`);
|
|
260
|
-
console.log(` ${cyan}5.${r} Gemini CLI`);
|
|
261
|
-
console.log(` ${cyan}6.${r} VS Code (Copilot)`);
|
|
262
|
-
console.log(` ${cyan}7.${r} Zed`);
|
|
263
|
-
console.log(` ${cyan}8.${r} OpenCode\n`);
|
|
264
|
-
|
|
265
|
-
const choice = (await ask(`${dim}Choose (1-8):${r} `)).trim();
|
|
266
|
-
const isCodex = choice === "2";
|
|
267
|
-
const isCursor = choice === "3";
|
|
268
|
-
const isWindsurf = choice === "4";
|
|
269
|
-
const isGemini = choice === "5";
|
|
270
|
-
const isVSCode = choice === "6";
|
|
271
|
-
const isZed = choice === "7";
|
|
272
|
-
const isOpenCode = choice === "8";
|
|
273
|
-
|
|
274
|
-
if (!["1", "2", "3", "4", "5", "6", "7", "8"].includes(choice)) {
|
|
275
|
-
console.error("Invalid choice.");
|
|
276
|
-
rl.close();
|
|
277
|
-
process.exit(1);
|
|
278
|
-
}
|
|
255
|
+
// Tool picker only shown for --token bypass. Browser flow gets tool from web.
|
|
256
|
+
let choice = "";
|
|
279
257
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
const folderType = detectFolder(cwd);
|
|
285
|
-
const folderName = cwd.split("/").pop() || cwd.split("\\").pop() || cwd;
|
|
286
|
-
|
|
287
|
-
if (folderType === "HOME") {
|
|
288
|
-
console.log(`\n ${red}✗ You're in your home folder.${r}`);
|
|
289
|
-
console.log(` ${yellow}Patchcord must be installed inside a project folder —${r}`);
|
|
290
|
-
console.log(` ${yellow}the folder where your agent actually runs.${r}`);
|
|
291
|
-
console.log(` ${dim}cd into your project first, then run this again.${r}`);
|
|
292
|
-
rl.close();
|
|
293
|
-
process.exit(0);
|
|
294
|
-
} else if (folderType === "CONTAINER") {
|
|
295
|
-
console.log(`\n ${yellow}⚠ This looks like a projects container, not a project.${r}`);
|
|
296
|
-
console.log(` ${dim}${cwd}${r}`);
|
|
297
|
-
console.log(` ${dim}Patchcord should be installed inside a project, not the folder above it.${r}`);
|
|
298
|
-
const proceed = (await ask(` ${dim}Set up here anyway? (y/N):${r} `)).trim().toLowerCase();
|
|
299
|
-
if (proceed !== "y" && proceed !== "yes") {
|
|
300
|
-
console.log(` ${dim}cd into your project and run this again.${r}`);
|
|
301
|
-
rl.close();
|
|
302
|
-
process.exit(0);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
258
|
+
const CLIENT_TYPE_MAP = {
|
|
259
|
+
"claude_code": "1", "codex": "2", "cursor": "3", "windsurf": "4",
|
|
260
|
+
"gemini": "5", "vscode": "6", "zed": "7", "opencode": "8",
|
|
261
|
+
};
|
|
305
262
|
|
|
306
|
-
console.log(`\n ${dim}Agent identity:${r} ${bold}${folderName}${r}`);
|
|
307
|
-
console.log(` ${dim}Folder:${r} ${cwd}`);
|
|
308
|
-
}
|
|
309
263
|
|
|
310
264
|
|
|
311
|
-
// Check if already configured
|
|
312
|
-
if (choice === "1") {
|
|
313
|
-
const mcpPath = join(cwd, ".mcp.json");
|
|
314
|
-
if (existsSync(mcpPath)) {
|
|
315
|
-
try {
|
|
316
|
-
const existing = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
317
|
-
if (existing.mcpServers?.patchcord) {
|
|
318
|
-
console.log(`\n ${yellow}⚠ Claude Code already configured in this project${r}`);
|
|
319
|
-
console.log(` ${dim}${mcpPath}${r}`);
|
|
320
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
321
|
-
if (replace !== "y" && replace !== "yes") {
|
|
322
|
-
console.log("Keeping existing config.");
|
|
323
|
-
rl.close();
|
|
324
|
-
process.exit(0);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
} catch {}
|
|
328
|
-
}
|
|
329
|
-
} else if (isCursor) {
|
|
330
|
-
const cursorPath = join(cwd, ".cursor", "mcp.json");
|
|
331
|
-
if (existsSync(cursorPath)) {
|
|
332
|
-
try {
|
|
333
|
-
const existing = JSON.parse(readFileSync(cursorPath, "utf-8"));
|
|
334
|
-
if (existing.mcpServers?.patchcord) {
|
|
335
|
-
console.log(`\n ${yellow}⚠ Cursor already configured in this project${r}`);
|
|
336
|
-
console.log(` ${dim}${cursorPath}${r}`);
|
|
337
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
338
|
-
if (replace !== "y" && replace !== "yes") {
|
|
339
|
-
console.log("Keeping existing config.");
|
|
340
|
-
rl.close();
|
|
341
|
-
process.exit(0);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} catch {}
|
|
345
|
-
}
|
|
346
|
-
// Warn about global config conflict
|
|
347
|
-
const globalCursor = join(HOME, ".cursor", "mcp.json");
|
|
348
|
-
if (existsSync(globalCursor)) {
|
|
349
|
-
try {
|
|
350
|
-
const global = JSON.parse(readFileSync(globalCursor, "utf-8"));
|
|
351
|
-
if (global.mcpServers?.patchcord) {
|
|
352
|
-
console.log(`\n ${yellow}⚠ Patchcord is also configured globally in Cursor${r}`);
|
|
353
|
-
console.log(` ${dim}${globalCursor}${r}`);
|
|
354
|
-
console.log(` ${yellow}Having both global AND per-project will cause duplicate tool calls.${r}`);
|
|
355
|
-
console.log(` ${dim}Remove patchcord from global config: Cursor Settings → MCP → remove patchcord${r}`);
|
|
356
|
-
}
|
|
357
|
-
} catch {}
|
|
358
|
-
}
|
|
359
|
-
} else if (isWindsurf) {
|
|
360
|
-
const wsPath = join(HOME, ".codeium", "windsurf", "mcp_config.json");
|
|
361
|
-
if (existsSync(wsPath)) {
|
|
362
|
-
try {
|
|
363
|
-
const content = readFileSync(wsPath, "utf-8").trim();
|
|
364
|
-
const existing = content ? JSON.parse(content) : {};
|
|
365
|
-
if (existing.mcpServers?.patchcord) {
|
|
366
|
-
console.log(`\n ${yellow}⚠ Windsurf already configured${r}`);
|
|
367
|
-
console.log(` ${dim}${wsPath}${r}`);
|
|
368
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
369
|
-
if (replace !== "y" && replace !== "yes") {
|
|
370
|
-
console.log("Keeping existing config.");
|
|
371
|
-
rl.close();
|
|
372
|
-
process.exit(0);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
} catch {}
|
|
376
|
-
}
|
|
377
|
-
} else if (isGemini) {
|
|
378
|
-
const geminiPath = join(HOME, ".gemini", "settings.json");
|
|
379
|
-
if (existsSync(geminiPath)) {
|
|
380
|
-
try {
|
|
381
|
-
const existing = safeReadJson(geminiPath) || {};
|
|
382
|
-
if (existing.mcpServers?.patchcord) {
|
|
383
|
-
console.log(`\n ${yellow}⚠ Gemini CLI already configured${r}`);
|
|
384
|
-
console.log(` ${dim}${geminiPath}${r}`);
|
|
385
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
386
|
-
if (replace !== "y" && replace !== "yes") {
|
|
387
|
-
console.log("Keeping existing config.");
|
|
388
|
-
rl.close();
|
|
389
|
-
process.exit(0);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
} catch {}
|
|
393
|
-
}
|
|
394
|
-
} else if (isVSCode) {
|
|
395
|
-
const vscodePath = join(cwd, ".vscode", "mcp.json");
|
|
396
|
-
if (existsSync(vscodePath)) {
|
|
397
|
-
try {
|
|
398
|
-
const existing = JSON.parse(readFileSync(vscodePath, "utf-8"));
|
|
399
|
-
if (existing.servers?.patchcord) {
|
|
400
|
-
console.log(`\n ${yellow}⚠ VS Code already configured in this project${r}`);
|
|
401
|
-
console.log(` ${dim}${vscodePath}${r}`);
|
|
402
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
403
|
-
if (replace !== "y" && replace !== "yes") {
|
|
404
|
-
console.log("Keeping existing config.");
|
|
405
|
-
rl.close();
|
|
406
|
-
process.exit(0);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
} catch {}
|
|
410
|
-
}
|
|
411
|
-
} else if (isZed) {
|
|
412
|
-
const zedPath = process.platform === "darwin"
|
|
413
|
-
? join(HOME, "Library", "Application Support", "Zed", "settings.json")
|
|
414
|
-
: join(HOME, ".config", "zed", "settings.json");
|
|
415
|
-
if (existsSync(zedPath)) {
|
|
416
|
-
try {
|
|
417
|
-
const existing = safeReadJson(zedPath) || {};
|
|
418
|
-
if (existing.context_servers?.patchcord) {
|
|
419
|
-
console.log(`\n ${yellow}⚠ Zed already configured${r}`);
|
|
420
|
-
console.log(` ${dim}${zedPath}${r}`);
|
|
421
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
422
|
-
if (replace !== "y" && replace !== "yes") {
|
|
423
|
-
console.log("Keeping existing config.");
|
|
424
|
-
rl.close();
|
|
425
|
-
process.exit(0);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
} catch {}
|
|
429
|
-
}
|
|
430
|
-
} else if (isOpenCode) {
|
|
431
|
-
const ocPath = join(cwd, "opencode.json");
|
|
432
|
-
if (existsSync(ocPath)) {
|
|
433
|
-
try {
|
|
434
|
-
const existing = JSON.parse(readFileSync(ocPath, "utf-8"));
|
|
435
|
-
if (existing.mcp?.patchcord) {
|
|
436
|
-
console.log(`\n ${yellow}⚠ OpenCode already configured in this project${r}`);
|
|
437
|
-
console.log(` ${dim}${ocPath}${r}`);
|
|
438
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
439
|
-
if (replace !== "y" && replace !== "yes") {
|
|
440
|
-
console.log("Keeping existing config.");
|
|
441
|
-
rl.close();
|
|
442
|
-
process.exit(0);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
} catch {}
|
|
446
|
-
}
|
|
447
|
-
} else if (isCodex) {
|
|
448
|
-
// Check global config for stale patchcord MCP — user may have run installer in ~ by mistake
|
|
449
|
-
const globalCodexConfig = join(HOME, ".codex", "config.toml");
|
|
450
|
-
if (existsSync(globalCodexConfig)) {
|
|
451
|
-
const globalContent = readFileSync(globalCodexConfig, "utf-8");
|
|
452
|
-
if (globalContent.includes("[mcp_servers.patchcord-codex]")) {
|
|
453
|
-
console.log(`\n ${red}⚠ Patchcord is in your GLOBAL Codex config!${r}`);
|
|
454
|
-
console.log(` ${dim}${globalCodexConfig}${r}`);
|
|
455
|
-
console.log(` ${yellow}This overrides per-project config and causes conflicts.${r}`);
|
|
456
|
-
const cleanGlobal = (await ask(` ${dim}Remove patchcord from global config? (Y/n):${r} `)).trim().toLowerCase();
|
|
457
|
-
if (cleanGlobal !== "n" && cleanGlobal !== "no") {
|
|
458
|
-
const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\]\n(?:(?!\[)[^\n]*\n?)*/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
459
|
-
writeFileSync(globalCodexConfig, cleaned + "\n");
|
|
460
|
-
console.log(` ${green}✓${r} Removed from global config`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const configPath = join(cwd, ".codex", "config.toml");
|
|
465
|
-
if (existsSync(configPath)) {
|
|
466
|
-
const content = readFileSync(configPath, "utf-8");
|
|
467
|
-
if (content.includes("[mcp_servers.patchcord-codex]")) {
|
|
468
|
-
console.log(`\n ${yellow}⚠ Codex CLI already configured in this project${r}`);
|
|
469
|
-
console.log(` ${dim}${configPath}${r}`);
|
|
470
|
-
const replace = (await ask(` ${dim}Replace? (y/N):${r} `)).trim().toLowerCase();
|
|
471
|
-
if (replace !== "y" && replace !== "yes") {
|
|
472
|
-
console.log("Keeping existing config.");
|
|
473
|
-
rl.close();
|
|
474
|
-
process.exit(0);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
265
|
let token = "";
|
|
481
266
|
let identity = "";
|
|
482
267
|
let serverUrl = "https://mcp.patchcord.dev";
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
token
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
268
|
+
let apiUrl = "https://api.patchcord.dev";
|
|
269
|
+
let clientType = "";
|
|
270
|
+
|
|
271
|
+
// --token bypass for power users / CI
|
|
272
|
+
const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
|
|
273
|
+
|| (flags.includes("--token") ? flags[flags.indexOf("--token") + 1] : "");
|
|
274
|
+
|
|
275
|
+
if (tokenFlag) {
|
|
276
|
+
// --token bypass: need tool picker in terminal
|
|
277
|
+
console.log(`\n${bold}Which tool are you setting up?${r}\n`);
|
|
278
|
+
console.log(` ${cyan}1.${r} Claude Code ${cyan}5.${r} Gemini CLI`);
|
|
279
|
+
console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code`);
|
|
280
|
+
console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed`);
|
|
281
|
+
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode\n`);
|
|
282
|
+
choice = (await ask(`${dim}Choose (1-8):${r} `)).trim();
|
|
283
|
+
if (!["1","2","3","4","5","6","7","8"].includes(choice)) {
|
|
284
|
+
console.error("Invalid choice.");
|
|
492
285
|
rl.close();
|
|
493
286
|
process.exit(1);
|
|
494
287
|
}
|
|
495
|
-
|
|
288
|
+
token = tokenFlag.trim();
|
|
496
289
|
if (!isSafeToken(token)) {
|
|
497
|
-
console.
|
|
290
|
+
console.error("Invalid token format.");
|
|
498
291
|
rl.close();
|
|
499
292
|
process.exit(1);
|
|
500
293
|
}
|
|
501
|
-
|
|
502
|
-
console.log("Validating...");
|
|
294
|
+
console.log("Validating token...");
|
|
503
295
|
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
504
296
|
if (validateResp) {
|
|
505
297
|
try {
|
|
@@ -509,29 +301,161 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
509
301
|
} catch {}
|
|
510
302
|
}
|
|
511
303
|
if (!identity) {
|
|
512
|
-
console.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
304
|
+
console.error("Token not recognized.");
|
|
305
|
+
rl.close();
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
rl.close();
|
|
309
|
+
} else {
|
|
310
|
+
// Browser connect flow
|
|
311
|
+
rl.close();
|
|
312
|
+
|
|
313
|
+
function canOpenBrowser() {
|
|
314
|
+
if (process.env.SSH_CLIENT || process.env.SSH_TTY) return false;
|
|
315
|
+
if (!process.env.DISPLAY && process.platform === "linux") return false;
|
|
316
|
+
if (flags.includes("--no-browser")) return false;
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function openBrowser(url) {
|
|
321
|
+
try {
|
|
322
|
+
if (process.platform === "darwin") execSync(`open "${url}"`, { stdio: "ignore" });
|
|
323
|
+
else if (process.platform === "win32") execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
324
|
+
else execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
325
|
+
return true;
|
|
326
|
+
} catch { return false; }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Create session
|
|
330
|
+
let sessionId = "";
|
|
331
|
+
try {
|
|
332
|
+
const resp = run(`curl -sf --max-time 10 -X POST "${apiUrl}/api/connect/session" -H "Content-Type: application/json" -d '{"tool":"${choice}"}'`);
|
|
333
|
+
if (resp) {
|
|
334
|
+
const data = JSON.parse(resp);
|
|
335
|
+
sessionId = data.session_id || "";
|
|
336
|
+
}
|
|
337
|
+
} catch {}
|
|
338
|
+
|
|
339
|
+
if (!sessionId) {
|
|
340
|
+
// Fallback to manual token paste if connect API unavailable
|
|
341
|
+
console.log(`\n${dim}Browser connect unavailable. Paste token manually.${r}`);
|
|
342
|
+
console.log(`${dim}Get your token at:${r} ${cyan}https://patchcord.dev/console${r}`);
|
|
343
|
+
const { createInterface: createRL2 } = await import("readline");
|
|
344
|
+
const rl2 = createRL2({ input: process.stdin, output: process.stdout });
|
|
345
|
+
const ask2 = (q) => new Promise((resolve) => rl2.question(q, resolve));
|
|
346
|
+
token = (await ask2(`\n${bold}Paste your agent token:${r} `)).trim();
|
|
347
|
+
rl2.close();
|
|
348
|
+
if (!token || !isSafeToken(token)) {
|
|
349
|
+
console.error("Invalid token.");
|
|
516
350
|
process.exit(1);
|
|
517
351
|
}
|
|
518
|
-
|
|
519
|
-
|
|
352
|
+
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
353
|
+
if (validateResp) {
|
|
354
|
+
try {
|
|
355
|
+
const data = JSON.parse(validateResp);
|
|
356
|
+
identity = `${data.agent_id}@${data.namespace_id}`;
|
|
357
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r}`);
|
|
358
|
+
} catch {}
|
|
359
|
+
}
|
|
360
|
+
if (!identity) {
|
|
361
|
+
console.error("Token not recognized.");
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
// Open browser or show URL
|
|
366
|
+
const connectUrl = `https://patchcord.dev/connect?session=${sessionId}`;
|
|
367
|
+
|
|
368
|
+
if (canOpenBrowser()) {
|
|
369
|
+
const opened = openBrowser(connectUrl);
|
|
370
|
+
if (opened) {
|
|
371
|
+
console.log(`\n ${green}✓${r} Browser opened.`);
|
|
372
|
+
} else {
|
|
373
|
+
console.log(`\n ${dim}Could not open browser. Open this URL manually:${r}`);
|
|
374
|
+
console.log(`\n ${cyan}${connectUrl}${r}\n`);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
console.log(`\n ${dim}Can't open a browser on this machine.${r}`);
|
|
378
|
+
console.log(` ${dim}Open this URL on any device:${r}`);
|
|
379
|
+
console.log(`\n ${cyan}${connectUrl}${r}\n`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.log(` ${dim}⏳ Waiting for you to complete setup in the browser...${r}`);
|
|
383
|
+
console.log(` ${dim} (press Ctrl+C to cancel)${r}\n`);
|
|
384
|
+
|
|
385
|
+
// SSE listener — wait for session completion
|
|
386
|
+
const http = await import("https");
|
|
387
|
+
const sseResult = await new Promise((resolve, reject) => {
|
|
388
|
+
const timeout = setTimeout(() => {
|
|
389
|
+
reject(new Error("Session expired. Run npx patchcord@latest again."));
|
|
390
|
+
}, 5 * 60 * 1000);
|
|
391
|
+
|
|
392
|
+
function connect() {
|
|
393
|
+
const req = http.get(`${apiUrl}/api/connect/session/${sessionId}/wait`, {
|
|
394
|
+
headers: { "Accept": "text/event-stream" },
|
|
395
|
+
}, (res) => {
|
|
396
|
+
if (res.statusCode !== 200) {
|
|
397
|
+
clearTimeout(timeout);
|
|
398
|
+
reject(new Error(`Server returned ${res.statusCode}`));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
let buffer = "";
|
|
402
|
+
res.on("data", (chunk) => {
|
|
403
|
+
buffer += chunk.toString();
|
|
404
|
+
const lines = buffer.split("\n");
|
|
405
|
+
buffer = lines.pop();
|
|
406
|
+
for (const line of lines) {
|
|
407
|
+
if (line.startsWith("data: ")) {
|
|
408
|
+
try {
|
|
409
|
+
const payload = JSON.parse(line.slice(6));
|
|
410
|
+
if (payload.error) {
|
|
411
|
+
clearTimeout(timeout);
|
|
412
|
+
reject(new Error(payload.error));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (payload.token) {
|
|
416
|
+
clearTimeout(timeout);
|
|
417
|
+
resolve(payload);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
} catch {}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
res.on("end", () => {
|
|
425
|
+
// Connection dropped — retry
|
|
426
|
+
setTimeout(connect, 2000);
|
|
427
|
+
});
|
|
428
|
+
res.on("error", () => {
|
|
429
|
+
setTimeout(connect, 2000);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
req.on("error", () => {
|
|
433
|
+
setTimeout(connect, 2000);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
connect();
|
|
438
|
+
});
|
|
520
439
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (!
|
|
526
|
-
console.error(
|
|
527
|
-
rl.close();
|
|
440
|
+
token = sseResult.token;
|
|
441
|
+
identity = `${sseResult.agent_id}@${sseResult.namespace_id}`;
|
|
442
|
+
clientType = sseResult.client_type || "";
|
|
443
|
+
choice = CLIENT_TYPE_MAP[clientType] || "";
|
|
444
|
+
if (!choice) {
|
|
445
|
+
console.error(`Unknown tool type: ${clientType}`);
|
|
528
446
|
process.exit(1);
|
|
529
447
|
}
|
|
530
|
-
|
|
448
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r} connected.`);
|
|
531
449
|
}
|
|
532
450
|
}
|
|
533
451
|
|
|
534
|
-
|
|
452
|
+
const isCodex = choice === "2";
|
|
453
|
+
const isCursor = choice === "3";
|
|
454
|
+
const isWindsurf = choice === "4";
|
|
455
|
+
const isGemini = choice === "5";
|
|
456
|
+
const isVSCode = choice === "6";
|
|
457
|
+
const isZed = choice === "7";
|
|
458
|
+
const isOpenCode = choice === "8";
|
|
535
459
|
|
|
536
460
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
537
461
|
|