theclawbay 0.3.52 → 0.3.54

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.
@@ -31,7 +31,12 @@ const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(
31
31
  const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
32
32
  const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
33
33
  const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
34
+ const GSD_AGENT_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".gsd", "agent");
35
+ const GSD_MODELS_PATH = node_path_1.default.join(GSD_AGENT_DIR, "models.json");
36
+ const GSD_AUTH_PATH = node_path_1.default.join(GSD_AGENT_DIR, "auth.json");
37
+ const GSD_SETTINGS_PATH = node_path_1.default.join(GSD_AGENT_DIR, "settings.json");
34
38
  const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
39
+ const GSD_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "gsd.restore.json");
35
40
  const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
36
41
  const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
37
42
  const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
@@ -414,6 +419,168 @@ async function cleanupClineConfig() {
414
419
  await removeFileIfExists(CLINE_RESTORE_STATE_PATH);
415
420
  return changed;
416
421
  }
422
+ function isEmptyObject(value) {
423
+ return Object.keys(value).length === 0;
424
+ }
425
+ function normalizeGsdRestoreSnapshot(value) {
426
+ if (typeof value !== "object" || value === null || Array.isArray(value))
427
+ return null;
428
+ const obj = value;
429
+ return {
430
+ version: 1,
431
+ modelsExisted: obj.modelsExisted === true,
432
+ providerConfig: typeof obj.providerConfig === "object" && obj.providerConfig !== null && !Array.isArray(obj.providerConfig)
433
+ ? obj.providerConfig
434
+ : null,
435
+ authExisted: obj.authExisted === true,
436
+ authEntry: typeof obj.authEntry === "object" && obj.authEntry !== null && !Array.isArray(obj.authEntry)
437
+ ? obj.authEntry
438
+ : null,
439
+ settingsExisted: obj.settingsExisted === true,
440
+ defaultProvider: typeof obj.defaultProvider === "string" ? obj.defaultProvider : null,
441
+ defaultModel: typeof obj.defaultModel === "string" ? obj.defaultModel : null,
442
+ };
443
+ }
444
+ function normalizeGsdCredential(value) {
445
+ if (typeof value !== "object" || value === null || Array.isArray(value))
446
+ return null;
447
+ return { ...value };
448
+ }
449
+ async function cleanupGsdConfig() {
450
+ const snapshotRaw = await readFileIfExists(GSD_RESTORE_STATE_PATH);
451
+ if (snapshotRaw === null || !snapshotRaw.trim())
452
+ return false;
453
+ let snapshot = null;
454
+ try {
455
+ snapshot = normalizeGsdRestoreSnapshot(JSON.parse(snapshotRaw));
456
+ }
457
+ catch {
458
+ snapshot = null;
459
+ }
460
+ if (!snapshot)
461
+ return false;
462
+ let changed = false;
463
+ const modelsRaw = await readFileIfExists(GSD_MODELS_PATH);
464
+ if (modelsRaw !== null) {
465
+ let fileChanged = false;
466
+ let doc = objectRecordOr({}, {});
467
+ try {
468
+ doc = modelsRaw.trim() ? objectRecordOr(JSON.parse(modelsRaw), {}) : {};
469
+ }
470
+ catch {
471
+ doc = {};
472
+ }
473
+ const providersRoot = objectRecordOr(doc.providers, {});
474
+ if (snapshot.providerConfig) {
475
+ const currentProvider = objectRecordOr(providersRoot[DEFAULT_PROVIDER_ID], {});
476
+ const nextProvider = JSON.stringify(snapshot.providerConfig);
477
+ if (JSON.stringify(currentProvider) !== nextProvider) {
478
+ providersRoot[DEFAULT_PROVIDER_ID] = snapshot.providerConfig;
479
+ fileChanged = true;
480
+ }
481
+ }
482
+ else if (DEFAULT_PROVIDER_ID in providersRoot) {
483
+ delete providersRoot[DEFAULT_PROVIDER_ID];
484
+ fileChanged = true;
485
+ }
486
+ if (isEmptyObject(providersRoot)) {
487
+ if ("providers" in doc)
488
+ fileChanged = true;
489
+ delete doc.providers;
490
+ }
491
+ else {
492
+ doc.providers = providersRoot;
493
+ }
494
+ if (!fileChanged) {
495
+ // Preserve the file exactly if nothing changed.
496
+ }
497
+ else if (!snapshot.modelsExisted && isEmptyObject(doc)) {
498
+ changed = (await removeFileIfExists(GSD_MODELS_PATH)) || changed;
499
+ }
500
+ else {
501
+ await writeJsonFile(GSD_MODELS_PATH, doc);
502
+ changed = true;
503
+ }
504
+ }
505
+ const authRaw = await readFileIfExists(GSD_AUTH_PATH);
506
+ if (authRaw !== null) {
507
+ let fileChanged = false;
508
+ let doc = objectRecordOr({}, {});
509
+ try {
510
+ doc = authRaw.trim() ? objectRecordOr(JSON.parse(authRaw), {}) : {};
511
+ }
512
+ catch {
513
+ doc = {};
514
+ }
515
+ if (snapshot.authEntry) {
516
+ const currentEntry = normalizeGsdCredential(doc[DEFAULT_PROVIDER_ID]);
517
+ const nextEntry = JSON.stringify(snapshot.authEntry);
518
+ if (JSON.stringify(currentEntry) !== nextEntry) {
519
+ doc[DEFAULT_PROVIDER_ID] = snapshot.authEntry;
520
+ fileChanged = true;
521
+ }
522
+ }
523
+ else if (DEFAULT_PROVIDER_ID in doc) {
524
+ delete doc[DEFAULT_PROVIDER_ID];
525
+ fileChanged = true;
526
+ }
527
+ if (!fileChanged) {
528
+ // Preserve the file exactly if nothing changed.
529
+ }
530
+ else if (!snapshot.authExisted && isEmptyObject(doc)) {
531
+ changed = (await removeFileIfExists(GSD_AUTH_PATH)) || changed;
532
+ }
533
+ else {
534
+ await writeJsonFile(GSD_AUTH_PATH, doc, 0o600);
535
+ changed = true;
536
+ }
537
+ }
538
+ const settingsRaw = await readFileIfExists(GSD_SETTINGS_PATH);
539
+ if (settingsRaw !== null) {
540
+ let fileChanged = false;
541
+ let doc = objectRecordOr({}, {});
542
+ try {
543
+ doc = settingsRaw.trim() ? objectRecordOr(JSON.parse(settingsRaw), {}) : {};
544
+ }
545
+ catch {
546
+ doc = {};
547
+ }
548
+ if (doc.defaultProvider === DEFAULT_PROVIDER_ID) {
549
+ if (snapshot.defaultProvider === null) {
550
+ if ("defaultProvider" in doc) {
551
+ delete doc.defaultProvider;
552
+ fileChanged = true;
553
+ }
554
+ }
555
+ else if (doc.defaultProvider !== snapshot.defaultProvider) {
556
+ doc.defaultProvider = snapshot.defaultProvider;
557
+ fileChanged = true;
558
+ }
559
+ if (snapshot.defaultModel === null) {
560
+ if ("defaultModel" in doc) {
561
+ delete doc.defaultModel;
562
+ fileChanged = true;
563
+ }
564
+ }
565
+ else if (doc.defaultModel !== snapshot.defaultModel) {
566
+ doc.defaultModel = snapshot.defaultModel;
567
+ fileChanged = true;
568
+ }
569
+ }
570
+ if (!fileChanged) {
571
+ // Preserve the file exactly if nothing changed.
572
+ }
573
+ else if (!snapshot.settingsExisted && isEmptyObject(doc)) {
574
+ changed = (await removeFileIfExists(GSD_SETTINGS_PATH)) || changed;
575
+ }
576
+ else {
577
+ await writeJsonFile(GSD_SETTINGS_PATH, doc);
578
+ changed = true;
579
+ }
580
+ }
581
+ await removeFileIfExists(GSD_RESTORE_STATE_PATH);
582
+ return changed;
583
+ }
417
584
  async function cleanupRooConfig() {
418
585
  const snapshotRaw = await readFileIfExists(ROO_SETTINGS_STATE_PATH);
419
586
  let changed = false;
@@ -773,6 +940,7 @@ class LogoutCommand extends base_command_1.BaseCommand {
773
940
  let updatedCodexConfig = false;
774
941
  let updatedContinueConfig = false;
775
942
  let updatedClineConfig = false;
943
+ let updatedGsdConfig = false;
776
944
  let updatedOpenClawConfig = false;
777
945
  let updatedOpenCodeConfig = false;
778
946
  let updatedKiloConfig = false;
@@ -840,6 +1008,8 @@ class LogoutCommand extends base_command_1.BaseCommand {
840
1008
  updatedContinueConfig = await cleanupContinueConfig();
841
1009
  progress.update("Reverting Cline");
842
1010
  updatedClineConfig = await cleanupClineConfig();
1011
+ progress.update("Reverting GSD");
1012
+ updatedGsdConfig = await cleanupGsdConfig();
843
1013
  progress.update("Reverting OpenClaw");
844
1014
  updatedOpenClawConfig = await cleanupOpenClawConfig();
845
1015
  progress.update("Reverting OpenCode");
@@ -881,6 +1051,8 @@ class LogoutCommand extends base_command_1.BaseCommand {
881
1051
  revertedTargets.push("Continue");
882
1052
  if (updatedClineConfig)
883
1053
  revertedTargets.push("Cline");
1054
+ if (updatedGsdConfig)
1055
+ revertedTargets.push("GSD");
884
1056
  if (updatedOpenClawConfig)
885
1057
  revertedTargets.push("OpenClaw");
886
1058
  if (updatedOpenCodeConfig)
@@ -981,6 +1153,7 @@ class LogoutCommand extends base_command_1.BaseCommand {
981
1153
  }
982
1154
  this.log(`- Continue config cleaned: ${updatedContinueConfig ? "yes" : "no"}`);
983
1155
  this.log(`- Cline config cleaned: ${updatedClineConfig ? "yes" : "no"}`);
1156
+ this.log(`- GSD config cleaned: ${updatedGsdConfig ? "yes" : "no"}`);
984
1157
  this.log(`- OpenClaw config cleaned: ${updatedOpenClawConfig ? "yes" : "no"}`);
985
1158
  this.log(`- OpenCode config cleaned: ${updatedOpenCodeConfig ? "yes" : "no"}`);
986
1159
  this.log(`- Kilo config cleaned: ${updatedKiloConfig ? "yes" : "no"}`);
@@ -41,12 +41,18 @@ const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
41
41
  const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
42
42
  const CLAUDE_ENV_API_KEY_NAME = "ANTHROPIC_API_KEY";
43
43
  const CLAUDE_ENV_BASE_URL_NAME = "ANTHROPIC_BASE_URL";
44
+ const CLAUDE_ENV_SIMPLE_MODE_NAME = "CLAUDE_CODE_SIMPLE";
44
45
  const ENV_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "env");
45
46
  const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.yaml");
46
47
  const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
47
48
  const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
48
49
  const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
50
+ const GSD_AGENT_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".gsd", "agent");
51
+ const GSD_MODELS_PATH = node_path_1.default.join(GSD_AGENT_DIR, "models.json");
52
+ const GSD_AUTH_PATH = node_path_1.default.join(GSD_AGENT_DIR, "auth.json");
53
+ const GSD_SETTINGS_PATH = node_path_1.default.join(GSD_AGENT_DIR, "settings.json");
49
54
  const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
55
+ const GSD_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "gsd.restore.json");
50
56
  const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
51
57
  const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
52
58
  const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
@@ -61,7 +67,7 @@ const SHELL_END = "# theclawbay-shell-managed:end";
61
67
  const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
62
68
  const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
63
69
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
64
- const SETUP_CLIENT_IDS = ["codex", "claude", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo"];
70
+ const SETUP_CLIENT_IDS = ["codex", "claude", "continue", "cline", "gsd", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo"];
65
71
  const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
66
72
  const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
67
73
  const CANONICAL_CODEX_NATIVE_PROXY_SUFFIX = "/backend-api/codex";
@@ -161,7 +167,7 @@ function logSetupCompactSummary(params) {
161
167
  .filter((client) => params.selectedSetupClients.has(client.id))
162
168
  .map((client) => client.summaryLabel);
163
169
  const restartTargets = params.setupClients
164
- .filter((client) => params.selectedSetupClients.has(client.id) && ["continue", "cline", "roo", "trae", "zo"].includes(client.id))
170
+ .filter((client) => params.selectedSetupClients.has(client.id) && ["continue", "cline", "gsd", "roo", "trae", "zo"].includes(client.id))
165
171
  .map((client) => client.summaryLabel);
166
172
  if (configured.length > 0) {
167
173
  params.log(`Configured: ${formatSummaryList(configured)}`);
@@ -1119,6 +1125,21 @@ async function detectCodexClient() {
1119
1125
  }
1120
1126
  return false;
1121
1127
  }
1128
+ async function detectGsdClient() {
1129
+ if (hasCommand("gsd"))
1130
+ return true;
1131
+ const candidates = [
1132
+ GSD_AGENT_DIR,
1133
+ GSD_MODELS_PATH,
1134
+ GSD_AUTH_PATH,
1135
+ GSD_SETTINGS_PATH,
1136
+ ];
1137
+ for (const candidate of candidates) {
1138
+ if (await pathExists(candidate))
1139
+ return true;
1140
+ }
1141
+ return false;
1142
+ }
1122
1143
  async function detectContinueClient() {
1123
1144
  if (hasCommand("cn") || hasCommand("continue"))
1124
1145
  return true;
@@ -1371,6 +1392,100 @@ async function writeClineConfig(params) {
1371
1392
  await writeJsonObjectFile(CLINE_SECRETS_PATH, secrets, 0o600);
1372
1393
  return [CLINE_GLOBAL_STATE_PATH, CLINE_SECRETS_PATH];
1373
1394
  }
1395
+ function buildGsdModels(models) {
1396
+ const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
1397
+ return models
1398
+ .filter((model) => Boolean(model.id))
1399
+ .map((model) => {
1400
+ const pricing = supportedModelMap.get(model.id);
1401
+ return {
1402
+ id: model.id,
1403
+ name: model.name || model.id,
1404
+ reasoning: true,
1405
+ input: ["text", "image"],
1406
+ cost: {
1407
+ input: pricing?.inputPer1M ?? 0,
1408
+ output: pricing?.outputPer1M ?? 0,
1409
+ cacheRead: pricing?.cachedInputPer1M ?? 0,
1410
+ cacheWrite: pricing?.inputPer1M ?? 0,
1411
+ },
1412
+ contextWindow: modelContextLimit(model.id),
1413
+ maxTokens: modelOutputLimit(model.id),
1414
+ };
1415
+ });
1416
+ }
1417
+ function normalizeGsdCredential(value) {
1418
+ if (typeof value !== "object" || value === null || Array.isArray(value))
1419
+ return null;
1420
+ return { ...value };
1421
+ }
1422
+ async function writeGsdConfig(params) {
1423
+ const existingModelsRaw = await readFileIfExists(GSD_MODELS_PATH);
1424
+ const existingAuthRaw = await readFileIfExists(GSD_AUTH_PATH);
1425
+ const existingSettingsRaw = await readFileIfExists(GSD_SETTINGS_PATH);
1426
+ const existingSnapshotRaw = await readFileIfExists(GSD_RESTORE_STATE_PATH);
1427
+ let modelsDoc = {};
1428
+ if (existingModelsRaw?.trim()) {
1429
+ try {
1430
+ modelsDoc = objectRecordOr(JSON.parse(existingModelsRaw), {});
1431
+ }
1432
+ catch {
1433
+ throw new Error(`invalid JSON in GSD config: ${GSD_MODELS_PATH}`);
1434
+ }
1435
+ }
1436
+ let authDoc = {};
1437
+ if (existingAuthRaw?.trim()) {
1438
+ try {
1439
+ authDoc = objectRecordOr(JSON.parse(existingAuthRaw), {});
1440
+ }
1441
+ catch {
1442
+ throw new Error(`invalid JSON in GSD auth: ${GSD_AUTH_PATH}`);
1443
+ }
1444
+ }
1445
+ let settingsDoc = {};
1446
+ if (existingSettingsRaw?.trim()) {
1447
+ try {
1448
+ settingsDoc = objectRecordOr(JSON.parse(existingSettingsRaw), {});
1449
+ }
1450
+ catch {
1451
+ throw new Error(`invalid JSON in GSD settings: ${GSD_SETTINGS_PATH}`);
1452
+ }
1453
+ }
1454
+ if (!existingSnapshotRaw?.trim()) {
1455
+ const providersRoot = objectRecordOr(modelsDoc.providers, {});
1456
+ const snapshot = {
1457
+ version: 1,
1458
+ modelsExisted: existingModelsRaw !== null,
1459
+ providerConfig: objectRecordOr(providersRoot[DEFAULT_PROVIDER_ID], {}),
1460
+ authExisted: existingAuthRaw !== null,
1461
+ authEntry: normalizeGsdCredential(authDoc[DEFAULT_PROVIDER_ID]),
1462
+ settingsExisted: existingSettingsRaw !== null,
1463
+ defaultProvider: typeof settingsDoc.defaultProvider === "string" ? settingsDoc.defaultProvider : null,
1464
+ defaultModel: typeof settingsDoc.defaultModel === "string" ? settingsDoc.defaultModel : null,
1465
+ };
1466
+ if (Object.keys(snapshot.providerConfig ?? {}).length === 0)
1467
+ snapshot.providerConfig = null;
1468
+ await writeJsonObjectFile(GSD_RESTORE_STATE_PATH, snapshot, 0o600);
1469
+ }
1470
+ const providersRoot = objectRecordOr(modelsDoc.providers, {});
1471
+ providersRoot[DEFAULT_PROVIDER_ID] = {
1472
+ baseUrl: openAiCompatibleProxyUrl(params.backendUrl),
1473
+ apiKey: ENV_KEY_NAME,
1474
+ api: "openai-responses",
1475
+ models: buildGsdModels(params.models),
1476
+ };
1477
+ modelsDoc.providers = providersRoot;
1478
+ authDoc[DEFAULT_PROVIDER_ID] = {
1479
+ type: "api_key",
1480
+ key: params.apiKey,
1481
+ };
1482
+ settingsDoc.defaultProvider = DEFAULT_PROVIDER_ID;
1483
+ settingsDoc.defaultModel = params.model.trim() || DEFAULT_CODEX_MODEL;
1484
+ await writeJsonObjectFile(GSD_MODELS_PATH, modelsDoc);
1485
+ await writeJsonObjectFile(GSD_AUTH_PATH, authDoc, 0o600);
1486
+ await writeJsonObjectFile(GSD_SETTINGS_PATH, settingsDoc);
1487
+ return [GSD_MODELS_PATH, GSD_AUTH_PATH, GSD_SETTINGS_PATH];
1488
+ }
1374
1489
  async function writeRooConfig(params) {
1375
1490
  const hosts = await detectExtensionHosts(["rooveterinaryinc.roo-cline-"]);
1376
1491
  if (hosts.length === 0) {
@@ -1448,7 +1563,7 @@ async function persistApiKeyEnv(params) {
1448
1563
  `export ${ENV_KEY_NAME}=${shellQuote(params.apiKey)}`,
1449
1564
  ];
1450
1565
  if (params.claudeEnabled) {
1451
- envLines.push("", "# Official Claude Code CLI", `export ${CLAUDE_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`, `export ${CLAUDE_ENV_BASE_URL_NAME}=${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
1566
+ envLines.push("", "# Official Claude Code CLI", `export ${CLAUDE_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`, `export ${CLAUDE_ENV_BASE_URL_NAME}=${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`, `export ${CLAUDE_ENV_SIMPLE_MODE_NAME}=1`);
1452
1567
  }
1453
1568
  const envContents = `${envLines.join("\n")}\n`;
1454
1569
  await promises_1.default.writeFile(ENV_FILE, envContents, "utf8");
@@ -1483,10 +1598,12 @@ async function persistApiKeyEnv(params) {
1483
1598
  if (params.claudeEnabled) {
1484
1599
  process.env[CLAUDE_ENV_API_KEY_NAME] = params.apiKey;
1485
1600
  process.env[CLAUDE_ENV_BASE_URL_NAME] = anthropicCompatibleProxyUrl(params.backendUrl);
1601
+ process.env[CLAUDE_ENV_SIMPLE_MODE_NAME] = "1";
1486
1602
  }
1487
1603
  else {
1488
1604
  delete process.env[CLAUDE_ENV_API_KEY_NAME];
1489
1605
  delete process.env[CLAUDE_ENV_BASE_URL_NAME];
1606
+ delete process.env[CLAUDE_ENV_SIMPLE_MODE_NAME];
1490
1607
  }
1491
1608
  return updated;
1492
1609
  }
@@ -1498,7 +1615,7 @@ async function persistFishEnv(params) {
1498
1615
  `set -gx ${ENV_KEY_NAME} ${shellQuote(params.apiKey)}`,
1499
1616
  ];
1500
1617
  if (params.claudeEnabled) {
1501
- lines.push("", "# Official Claude Code CLI", `set -gx ${CLAUDE_ENV_API_KEY_NAME} ${shellQuote(params.apiKey)}`, `set -gx ${CLAUDE_ENV_BASE_URL_NAME} ${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
1618
+ lines.push("", "# Official Claude Code CLI", `set -gx ${CLAUDE_ENV_API_KEY_NAME} ${shellQuote(params.apiKey)}`, `set -gx ${CLAUDE_ENV_BASE_URL_NAME} ${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`, `set -gx ${CLAUDE_ENV_SIMPLE_MODE_NAME} 1`);
1502
1619
  }
1503
1620
  await promises_1.default.writeFile(fishConfPath, `${lines.join("\n")}\n`, "utf8");
1504
1621
  return [fishConfPath];
@@ -1523,7 +1640,7 @@ async function persistPowerShellEnv(params) {
1523
1640
  `$env:${ENV_KEY_NAME} = ${powerShellQuote(params.apiKey)}`,
1524
1641
  ];
1525
1642
  if (params.claudeEnabled) {
1526
- lines.push(`$env:${CLAUDE_ENV_API_KEY_NAME} = ${powerShellQuote(params.apiKey)}`, `$env:${CLAUDE_ENV_BASE_URL_NAME} = ${powerShellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
1643
+ lines.push(`$env:${CLAUDE_ENV_API_KEY_NAME} = ${powerShellQuote(params.apiKey)}`, `$env:${CLAUDE_ENV_BASE_URL_NAME} = ${powerShellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`, `$env:${CLAUDE_ENV_SIMPLE_MODE_NAME} = "1"`);
1527
1644
  }
1528
1645
  lines.push(SHELL_END);
1529
1646
  const next = appendManagedBlock(cleaned, lines);
@@ -2012,11 +2129,12 @@ class SetupCommand extends base_command_1.BaseCommand {
2012
2129
  managed?.backendUrl ??
2013
2130
  DEFAULT_BACKEND_URL;
2014
2131
  const backendUrl = normalizeUrl(backendRaw, "--backend");
2015
- const [codexDetected, claudeDetected, continueDetected, clineDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected] = await Promise.all([
2132
+ const [codexDetected, claudeDetected, continueDetected, clineDetected, gsdDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected] = await Promise.all([
2016
2133
  detectCodexClient(),
2017
2134
  detectClaudeCodeClient(),
2018
2135
  detectContinueClient(),
2019
2136
  detectClineClient(),
2137
+ detectGsdClient(),
2020
2138
  detectKiloClient(),
2021
2139
  detectRooClient(),
2022
2140
  detectTraeClient(),
@@ -2060,6 +2178,15 @@ class SetupCommand extends base_command_1.BaseCommand {
2060
2178
  icon: "◈",
2061
2179
  siteUrl: "https://cline.bot",
2062
2180
  },
2181
+ {
2182
+ id: "gsd",
2183
+ label: "Get Shit Done 2",
2184
+ summaryLabel: "GSD",
2185
+ detected: gsdDetected,
2186
+ recommended: true,
2187
+ icon: "▣",
2188
+ siteUrl: "https://github.com/gsd-build/gsd-2",
2189
+ },
2063
2190
  {
2064
2191
  id: "openclaw",
2065
2192
  label: "OpenClaw",
@@ -2179,6 +2306,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2179
2306
  let modelCacheMigration = null;
2180
2307
  let continueConfigPath = null;
2181
2308
  let clineConfigPaths = [];
2309
+ let gsdConfigPaths = [];
2182
2310
  let openClawConfigPath = null;
2183
2311
  let openClawCliWarning = null;
2184
2312
  let openCodeConfigPaths = [];
@@ -2274,6 +2402,15 @@ class SetupCommand extends base_command_1.BaseCommand {
2274
2402
  apiKey: authCredential,
2275
2403
  });
2276
2404
  }
2405
+ if (selectedSetupClients.has("gsd")) {
2406
+ progress.update("Configuring GSD");
2407
+ gsdConfigPaths = await writeGsdConfig({
2408
+ backendUrl,
2409
+ model: resolved?.model ?? DEFAULT_CODEX_MODEL,
2410
+ models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
2411
+ apiKey: authCredential,
2412
+ });
2413
+ }
2277
2414
  if (selectedSetupClients.has("openclaw")) {
2278
2415
  progress.update("Configuring OpenClaw");
2279
2416
  const openClawResult = await setupOpenClaw({
@@ -2351,8 +2488,8 @@ class SetupCommand extends base_command_1.BaseCommand {
2351
2488
  if (resolved?.note)
2352
2489
  summaryNotes.add(resolved.note);
2353
2490
  if (claudeAccess?.enabled) {
2354
- summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY for the official client.");
2355
- summaryNotes.add("Claude Code may still show your claude.ai login in `claude auth status`. Restart Claude Code and approve `Use custom API key` if prompted, or enable it in `/config`.");
2491
+ summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY, and CLAUDE_CODE_SIMPLE=1 for the official client.");
2492
+ summaryNotes.add("If Claude Code is already open, restart it so the custom-key env takes effect cleanly. `claude auth status` should show `authMethod: api_key` once the new env is loaded.");
2356
2493
  }
2357
2494
  else if (claudeAccess?.note) {
2358
2495
  summaryNotes.add(claudeAccess.note);
@@ -2496,6 +2633,16 @@ class SetupCommand extends base_command_1.BaseCommand {
2496
2633
  else {
2497
2634
  this.log("- Cline: not detected (skipped)");
2498
2635
  }
2636
+ if (selectedSetupClients.has("gsd")) {
2637
+ this.log(`- GSD: configured (${gsdConfigPaths.join(", ")})`);
2638
+ this.log("- GSD note: configured with the OpenAI Responses API so tool calling works reliably.");
2639
+ }
2640
+ else if (gsdDetected) {
2641
+ this.log("- GSD: detected but skipped");
2642
+ }
2643
+ else {
2644
+ this.log("- GSD: not detected (skipped)");
2645
+ }
2499
2646
  const openClawDetected = setupClients.find((client) => client.id === "openclaw")?.detected ?? false;
2500
2647
  if (selectedSetupClients.has("openclaw")) {
2501
2648
  this.log(`- OpenClaw: configured (${openClawConfigPath})`);
@@ -2621,7 +2768,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2621
2768
  });
2622
2769
  }
2623
2770
  }
2624
- SetupCommand.description = "One-time setup: configure Codex, Claude Code, plus any detected Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Trae, Aider, or Zo installs to use The Claw Bay";
2771
+ SetupCommand.description = "One-time setup: configure Codex, Claude Code, plus any detected Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Trae, Aider, or Zo installs to use The Claw Bay";
2625
2772
  SetupCommand.flags = {
2626
2773
  backend: core_1.Flags.string({
2627
2774
  required: false,
@@ -2639,7 +2786,7 @@ SetupCommand.flags = {
2639
2786
  }),
2640
2787
  clients: core_1.Flags.string({
2641
2788
  required: false,
2642
- description: "Detected local clients to configure: codex, claude, continue, cline, openclaw, opencode, kilo, roo, trae, aider, zo",
2789
+ description: "Detected local clients to configure: codex, claude, continue, cline, gsd, openclaw, opencode, kilo, roo, trae, aider, zo",
2643
2790
  }),
2644
2791
  yes: core_1.Flags.boolean({
2645
2792
  required: false,
@@ -5,11 +5,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createBrowserLinkedDeviceSession = createBrowserLinkedDeviceSession;
7
7
  const node_crypto_1 = __importDefault(require("node:crypto"));
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
9
  const node_http_1 = __importDefault(require("node:http"));
9
10
  const node_os_1 = __importDefault(require("node:os"));
11
+ const node_path_1 = __importDefault(require("node:path"));
10
12
  const node_child_process_1 = require("node:child_process");
13
+ const paths_1 = require("./config/paths");
11
14
  const BROWSER_SETUP_TIMEOUT_MS = 15 * 60000;
12
15
  const BROWSER_SETUP_POLL_INTERVAL_MS = 2000;
16
+ const INSTALL_ID_PATH = node_path_1.default.join(paths_1.theclawbayConfigDir, "install-id");
13
17
  function preferredBrowserCallbackPort() {
14
18
  const parsed = Number(process.env.THECLAWBAY_CALLBACK_PORT?.trim() || "");
15
19
  if (Number.isFinite(parsed) && parsed > 0 && parsed < 65536) {
@@ -46,6 +50,30 @@ function openUrlInBrowser(url) {
46
50
  return false;
47
51
  }
48
52
  }
53
+ function normalizeInstallId(value) {
54
+ const normalized = value.trim();
55
+ if (normalized.length < 16 || normalized.length > 128)
56
+ return null;
57
+ return normalized;
58
+ }
59
+ async function getOrCreateInstallId() {
60
+ try {
61
+ const existing = normalizeInstallId(await promises_1.default.readFile(INSTALL_ID_PATH, "utf8"));
62
+ if (existing)
63
+ return existing;
64
+ }
65
+ catch (error) {
66
+ const err = error;
67
+ if (err.code !== "ENOENT") {
68
+ throw new Error(`failed to read local install identity: ${err.message}`);
69
+ }
70
+ }
71
+ const installId = node_crypto_1.default.randomUUID();
72
+ await promises_1.default.mkdir(node_path_1.default.dirname(INSTALL_ID_PATH), { recursive: true });
73
+ await promises_1.default.writeFile(INSTALL_ID_PATH, `${installId}\n`, "utf8");
74
+ await promises_1.default.chmod(INSTALL_ID_PATH, 0o600).catch(() => { });
75
+ return installId;
76
+ }
49
77
  function htmlPage(title, message) {
50
78
  return `<!doctype html>
51
79
  <html lang="en">
@@ -239,6 +267,7 @@ async function createBrowserLinkedDeviceSession(params) {
239
267
  const exchangeSignal = exchangeAbortController.signal;
240
268
  let exchangeInFlight = null;
241
269
  try {
270
+ const installId = await getOrCreateInstallId();
242
271
  const startResponse = await fetch(`${params.backendUrl}/api/setup/device-session/start`, {
243
272
  method: "POST",
244
273
  headers: { "Content-Type": "application/json" },
@@ -246,6 +275,7 @@ async function createBrowserLinkedDeviceSession(params) {
246
275
  callbackUrl: callbackServer.callbackUrl,
247
276
  state,
248
277
  deviceLabel: label,
278
+ installId,
249
279
  selectedClients: params.selectedClients,
250
280
  }),
251
281
  signal: AbortSignal.timeout(20000),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.52",
4
- "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
3
+ "version": "0.3.54",
4
+ "description": "CLI for connecting Codex, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "theclawbay": "dist/index.js"
@@ -35,6 +35,7 @@
35
35
  "api-key",
36
36
  "continue",
37
37
  "cline",
38
+ "gsd",
38
39
  "openclaw",
39
40
  "opencode",
40
41
  "kilo",