agent-relay 2.0.19 → 2.0.21

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 (142) hide show
  1. package/CHANGELOG.md +217 -24
  2. package/bin/relay-pty-darwin-arm64 +0 -0
  3. package/bin/relay-pty-darwin-x64 +0 -0
  4. package/bin/relay-pty-linux-x64 +0 -0
  5. package/dist/dashboard/out/404.html +1 -1
  6. package/dist/dashboard/out/app/onboarding.html +1 -1
  7. package/dist/dashboard/out/app/onboarding.txt +1 -1
  8. package/dist/dashboard/out/app.html +1 -1
  9. package/dist/dashboard/out/app.txt +1 -1
  10. package/dist/dashboard/out/cloud/link.html +1 -1
  11. package/dist/dashboard/out/cloud/link.txt +1 -1
  12. package/dist/dashboard/out/complete-profile.html +1 -1
  13. package/dist/dashboard/out/complete-profile.txt +1 -1
  14. package/dist/dashboard/out/connect-repos.html +1 -1
  15. package/dist/dashboard/out/connect-repos.txt +1 -1
  16. package/dist/dashboard/out/history.html +1 -1
  17. package/dist/dashboard/out/history.txt +1 -1
  18. package/dist/dashboard/out/index.html +1 -1
  19. package/dist/dashboard/out/index.txt +1 -1
  20. package/dist/dashboard/out/login.html +1 -1
  21. package/dist/dashboard/out/login.txt +1 -1
  22. package/dist/dashboard/out/metrics.html +1 -1
  23. package/dist/dashboard/out/metrics.txt +1 -1
  24. package/dist/dashboard/out/pricing.html +1 -1
  25. package/dist/dashboard/out/pricing.txt +1 -1
  26. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  27. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  28. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  29. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  30. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  31. package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
  32. package/dist/dashboard/out/providers.html +1 -1
  33. package/dist/dashboard/out/providers.txt +1 -1
  34. package/dist/dashboard/out/signup.html +1 -1
  35. package/dist/dashboard/out/signup.txt +1 -1
  36. package/package.json +23 -17
  37. package/packages/api-types/package.json +2 -2
  38. package/packages/bridge/dist/spawner.d.ts +2 -0
  39. package/packages/bridge/dist/spawner.js +76 -24
  40. package/packages/bridge/package.json +8 -8
  41. package/packages/cli-tester/README.md +277 -0
  42. package/packages/cli-tester/dist/index.d.ts +21 -0
  43. package/packages/cli-tester/dist/index.js +21 -0
  44. package/packages/cli-tester/dist/utils/credential-check.d.ts +56 -0
  45. package/packages/cli-tester/dist/utils/credential-check.js +230 -0
  46. package/packages/cli-tester/dist/utils/socket-client.d.ts +76 -0
  47. package/packages/cli-tester/dist/utils/socket-client.js +153 -0
  48. package/packages/cli-tester/docker/entrypoint.sh +58 -0
  49. package/packages/cli-tester/package.json +32 -0
  50. package/packages/cli-tester/scripts/clear-auth.sh +101 -0
  51. package/packages/cli-tester/scripts/inject-message.sh +42 -0
  52. package/packages/cli-tester/scripts/start.sh +71 -0
  53. package/packages/cli-tester/scripts/test-cli.sh +56 -0
  54. package/packages/cli-tester/scripts/test-full-spawn.sh +238 -0
  55. package/packages/cli-tester/scripts/test-registration.sh +182 -0
  56. package/packages/cli-tester/scripts/test-setup-flow.sh +202 -0
  57. package/packages/cli-tester/scripts/test-spawn.sh +140 -0
  58. package/packages/cli-tester/scripts/test-with-daemon.sh +247 -0
  59. package/packages/cli-tester/scripts/verify-auth.sh +112 -0
  60. package/packages/cloud/package.json +6 -6
  61. package/packages/config/dist/cli-auth-config.js +65 -0
  62. package/packages/config/package.json +2 -2
  63. package/packages/continuity/package.json +1 -1
  64. package/packages/daemon/dist/router.js +4 -4
  65. package/packages/daemon/dist/server.js +38 -19
  66. package/packages/daemon/dist/spawn-manager.d.ts +4 -0
  67. package/packages/daemon/dist/spawn-manager.js +2 -0
  68. package/packages/daemon/package.json +12 -12
  69. package/packages/dashboard/dist/server.js +4 -0
  70. package/packages/dashboard/package.json +14 -14
  71. package/packages/dashboard/ui-dist/404.html +1 -1
  72. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  73. package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
  74. package/packages/dashboard/ui-dist/app.html +1 -1
  75. package/packages/dashboard/ui-dist/app.txt +1 -1
  76. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  77. package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
  78. package/packages/dashboard/ui-dist/complete-profile.html +1 -1
  79. package/packages/dashboard/ui-dist/complete-profile.txt +1 -1
  80. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  81. package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
  82. package/packages/dashboard/ui-dist/history.html +1 -1
  83. package/packages/dashboard/ui-dist/history.txt +1 -1
  84. package/packages/dashboard/ui-dist/index.html +1 -1
  85. package/packages/dashboard/ui-dist/index.txt +1 -1
  86. package/packages/dashboard/ui-dist/login.html +1 -1
  87. package/packages/dashboard/ui-dist/login.txt +1 -1
  88. package/packages/dashboard/ui-dist/metrics.html +1 -1
  89. package/packages/dashboard/ui-dist/metrics.txt +1 -1
  90. package/packages/dashboard/ui-dist/pricing.html +1 -1
  91. package/packages/dashboard/ui-dist/pricing.txt +1 -1
  92. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  93. package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
  94. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  95. package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
  96. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  97. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
  98. package/packages/dashboard/ui-dist/providers.html +1 -1
  99. package/packages/dashboard/ui-dist/providers.txt +1 -1
  100. package/packages/dashboard/ui-dist/signup.html +1 -1
  101. package/packages/dashboard/ui-dist/signup.txt +1 -1
  102. package/packages/dashboard-server/dist/server.js +4 -0
  103. package/packages/dashboard-server/package.json +12 -12
  104. package/packages/hooks/package.json +4 -4
  105. package/packages/mcp/package.json +2 -2
  106. package/packages/memory/package.json +2 -2
  107. package/packages/policy/package.json +2 -2
  108. package/packages/protocol/package.json +1 -1
  109. package/packages/resiliency/package.json +1 -1
  110. package/packages/sdk/README.md +512 -58
  111. package/packages/sdk/dist/client.d.ts +135 -1
  112. package/packages/sdk/dist/client.js +338 -0
  113. package/packages/sdk/dist/index.d.ts +2 -1
  114. package/packages/sdk/dist/index.js +2 -0
  115. package/packages/sdk/dist/logs.d.ts +61 -0
  116. package/packages/sdk/dist/logs.js +95 -0
  117. package/packages/sdk/dist/protocol/index.d.ts +1 -1
  118. package/packages/sdk/dist/protocol/types.d.ts +186 -1
  119. package/packages/sdk/package.json +3 -3
  120. package/packages/spawner/package.json +2 -2
  121. package/packages/state/package.json +1 -1
  122. package/packages/storage/dist/sqlite-adapter.js +2 -0
  123. package/packages/storage/package.json +2 -2
  124. package/packages/telemetry/package.json +1 -1
  125. package/packages/trajectory/package.json +2 -2
  126. package/packages/user-directory/package.json +2 -2
  127. package/packages/utils/package.json +1 -1
  128. package/packages/wrapper/dist/base-wrapper.js +27 -10
  129. package/packages/wrapper/dist/relay-pty-orchestrator.js +16 -16
  130. package/packages/wrapper/dist/tmux-wrapper.js +16 -0
  131. package/packages/wrapper/package.json +7 -7
  132. package/scripts/hooks/install.sh +16 -0
  133. package/scripts/hooks/pre-commit +60 -0
  134. package/specs/PRIMITIVES_ROADMAP.md +2154 -0
  135. /package/dist/dashboard/out/_next/static/{cREcLZyPb-5NyVZje0Qfe → 7MZPqYkVGw3EGzVBkVmY9}/_buildManifest.js +0 -0
  136. /package/dist/dashboard/out/_next/static/{cREcLZyPb-5NyVZje0Qfe → 7MZPqYkVGw3EGzVBkVmY9}/_ssgManifest.js +0 -0
  137. /package/packages/dashboard/ui-dist/_next/static/{N3ajGnJqRESKyCjDvyU52 → 7MZPqYkVGw3EGzVBkVmY9}/_buildManifest.js +0 -0
  138. /package/packages/dashboard/ui-dist/_next/static/{N3ajGnJqRESKyCjDvyU52 → 7MZPqYkVGw3EGzVBkVmY9}/_ssgManifest.js +0 -0
  139. /package/packages/dashboard/ui-dist/_next/static/{UQiyWwBxIP-9it3GYVBDL → iJ3Uiz3IrqUJL7IxKZHiV}/_buildManifest.js +0 -0
  140. /package/packages/dashboard/ui-dist/_next/static/{UQiyWwBxIP-9it3GYVBDL → iJ3Uiz3IrqUJL7IxKZHiV}/_ssgManifest.js +0 -0
  141. /package/packages/dashboard/ui-dist/_next/static/{cREcLZyPb-5NyVZje0Qfe → l-jd878zUJ_IlraqEWMZc}/_buildManifest.js +0 -0
  142. /package/packages/dashboard/ui-dist/_next/static/{cREcLZyPb-5NyVZje0Qfe → l-jd878zUJ_IlraqEWMZc}/_ssgManifest.js +0 -0
@@ -290,15 +290,20 @@ export class AgentSpawner {
290
290
  : projectRootOrOptions;
291
291
  const paths = getProjectPaths(options.projectRoot);
292
292
  this.projectRoot = paths.projectRoot;
293
+ // Use explicit teamDir if provided (ensures spawner checks same files as daemon)
294
+ // This is critical in cloud workspaces where detectWorkspacePath may return different paths
295
+ const effectiveTeamDir = options.teamDir ?? paths.teamDir;
293
296
  // Use connected-agents.json (live socket connections) instead of agents.json (historical registry)
294
297
  // This ensures spawned agents have actual daemon connections for channel message delivery
295
- this.agentsPath = path.join(paths.teamDir, 'connected-agents.json');
296
- this.registryPath = path.join(paths.teamDir, 'agents.json');
298
+ this.agentsPath = path.join(effectiveTeamDir, 'connected-agents.json');
299
+ this.registryPath = path.join(effectiveTeamDir, 'agents.json');
300
+ // Debug: log path configuration
301
+ log.info(`AgentSpawner paths: projectRoot=${this.projectRoot} teamDir=${effectiveTeamDir} (explicit=${!!options.teamDir}) agentsPath=${this.agentsPath}`);
297
302
  // Use explicit socketPath if provided (ensures spawned agents connect to same daemon)
298
303
  // Otherwise derive from project paths
299
304
  this.socketPath = options.socketPath ?? paths.socketPath;
300
- this.logsDir = path.join(paths.teamDir, 'worker-logs');
301
- this.workersPath = path.join(paths.teamDir, 'workers.json');
305
+ this.logsDir = path.join(effectiveTeamDir, 'worker-logs');
306
+ this.workersPath = path.join(effectiveTeamDir, 'workers.json');
302
307
  this.dashboardPort = options.dashboardPort;
303
308
  // Store spawn tracking callbacks
304
309
  this.onMarkSpawning = options.onMarkSpawning;
@@ -531,7 +536,7 @@ export class AgentSpawner {
531
536
  * Spawn a new worker agent using relay-pty
532
537
  */
533
538
  async spawn(request) {
534
- const { name, cli, task, team, spawnerName, userId, includeWorkflowConventions } = request;
539
+ const { name, cli, task, team, spawnerName, userId, includeWorkflowConventions, interactive } = request;
535
540
  const debug = process.env.DEBUG_SPAWN === '1';
536
541
  // Validate agent name to prevent path traversal attacks
537
542
  if (name.includes('..') || name.includes('/') || name.includes('\\')) {
@@ -558,7 +563,7 @@ export class AgentSpawner {
558
563
  };
559
564
  }
560
565
  // Enforce agent limit based on plan (MAX_AGENTS is set by provisioner based on plan)
561
- const maxAgents = parseInt(process.env.MAX_AGENTS || '10', 10);
566
+ const maxAgents = parseInt(process.env.MAX_AGENTS ?? '', 10) || 10_000;
562
567
  const currentAgentCount = this.activeWorkers.size;
563
568
  if (currentAgentCount >= maxAgents) {
564
569
  log.warn(`Agent limit reached: ${currentAgentCount}/${maxAgents}`);
@@ -604,17 +609,24 @@ export class AgentSpawner {
604
609
  // Pre-configure MCP permissions for all supported CLIs
605
610
  // This creates/updates CLI-specific settings files with agent-relay permissions
606
611
  ensureMcpPermissions(this.projectRoot, commandName, debug);
607
- // Add --dangerously-skip-permissions for Claude agents
612
+ // Add auto-accept flags for non-interactive agents (normal spawns, not setup terminals)
613
+ // When interactive=true (setup flows), we SKIP these flags so users can respond to prompts
608
614
  const isClaudeCli = commandName.startsWith('claude');
609
- if (isClaudeCli) {
610
- if (!args.includes('--dangerously-skip-permissions')) {
615
+ const isCursorCli = commandName === 'agent' || rawCommandName === 'cursor';
616
+ if (!interactive) {
617
+ // Add --dangerously-skip-permissions for Claude agents
618
+ if (isClaudeCli && !args.includes('--dangerously-skip-permissions')) {
611
619
  args.push('--dangerously-skip-permissions');
612
620
  }
621
+ // Add --force for Cursor agents (auto-approve tool usage in non-interactive mode)
622
+ if (isCursorCli && !args.includes('--force')) {
623
+ args.push('--force');
624
+ }
613
625
  }
614
- // Add --force for Cursor agents (CLI is 'agent', may be passed as 'cursor')
615
- const isCursorCli = commandName === 'agent' || rawCommandName === 'cursor';
616
- if (isCursorCli && !args.includes('--force')) {
617
- args.push('--force');
626
+ else {
627
+ // Interactive mode: log that we're skipping auto-accept flags
628
+ if (debug)
629
+ log.debug(`Interactive mode: skipping auto-accept flags for ${name}`);
618
630
  }
619
631
  // Apply agent config (model, --agent flag) from .claude/agents/ if available
620
632
  // This ensures spawned agents respect their profile settings
@@ -638,15 +650,18 @@ export class AgentSpawner {
638
650
  if (debug)
639
651
  log.debug(`Applied agent config for ${name}: ${args.join(' ')}`);
640
652
  }
641
- // Add --dangerously-bypass-approvals-and-sandbox for Codex agents
653
+ // Add auto-accept flags for Codex and Gemini (only in non-interactive mode)
642
654
  const isCodexCli = commandName.startsWith('codex');
643
- if (isCodexCli && !args.includes('--dangerously-bypass-approvals-and-sandbox')) {
644
- args.push('--dangerously-bypass-approvals-and-sandbox');
645
- }
646
- // Add --yolo for Gemini agents (auto-accept all prompts)
647
655
  const isGeminiCli = commandName === 'gemini';
648
- if (isGeminiCli && !args.includes('--yolo')) {
649
- args.push('--yolo');
656
+ if (!interactive) {
657
+ // Add --dangerously-bypass-approvals-and-sandbox for Codex agents
658
+ if (isCodexCli && !args.includes('--dangerously-bypass-approvals-and-sandbox')) {
659
+ args.push('--dangerously-bypass-approvals-and-sandbox');
660
+ }
661
+ // Add --yolo for Gemini agents (auto-accept all prompts)
662
+ if (isGeminiCli && !args.includes('--yolo')) {
663
+ args.push('--yolo');
664
+ }
650
665
  }
651
666
  // Auto-install MCP config if not present (project-local)
652
667
  // Uses .mcp.json in the project root - doesn't modify global settings
@@ -919,6 +934,23 @@ export class AgentSpawner {
919
934
  await pty.start();
920
935
  if (debug)
921
936
  log.debug(`PTY started, pid: ${pty.pid}`);
937
+ // Cursor CLI shows "Press any key to log in..." prompt that blocks all progress
938
+ // Send a keystroke after startup to bypass this initial prompt
939
+ // Only do this for interactive/setup flows where auth is needed
940
+ // For normal spawns (already authenticated), this prompt won't appear
941
+ if (isCursorCli && interactive) {
942
+ // Wait a moment for CLI to initialize and show the prompt
943
+ await sleep(1500);
944
+ if (debug)
945
+ log.debug(`Sending initial keystroke for Cursor setup to bypass "Press any key" prompt`);
946
+ try {
947
+ // Send Enter key to proceed past the initial prompt
948
+ await pty.write('\r');
949
+ }
950
+ catch (err) {
951
+ log.warn(`Failed to send initial keystroke for Cursor: ${err}`);
952
+ }
953
+ }
922
954
  // Wait for the agent to register with the daemon
923
955
  const registered = await this.waitForAgentRegistration(name, 30_000, 500);
924
956
  if (!registered) {
@@ -1270,22 +1302,39 @@ export class AgentSpawner {
1270
1302
  */
1271
1303
  async waitForAgentRegistration(name, timeoutMs = 30_000, pollIntervalMs = 500) {
1272
1304
  const deadline = Date.now() + timeoutMs;
1305
+ let pollCount = 0;
1273
1306
  while (Date.now() < deadline) {
1274
- if (this.isAgentRegistered(name)) {
1307
+ pollCount++;
1308
+ const connected = this.isAgentConnected(name);
1309
+ const recentlySeen = this.isAgentRecentlySeen(name);
1310
+ // Log first few polls and every 10th poll after that
1311
+ if (pollCount <= 3 || pollCount % 10 === 0) {
1312
+ log.info(`Registration poll #${pollCount} for ${name}: connected=${connected} recentlySeen=${recentlySeen} agentsPath=${this.agentsPath}`);
1313
+ }
1314
+ if (connected && recentlySeen) {
1315
+ log.info(`Agent ${name} registered after ${pollCount} polls`);
1275
1316
  return true;
1276
1317
  }
1277
1318
  await sleep(pollIntervalMs);
1278
1319
  }
1320
+ log.info(`Registration timeout for ${name} after ${pollCount} polls`);
1279
1321
  return false;
1280
1322
  }
1281
1323
  isAgentRegistered(name) {
1282
1324
  return this.isAgentConnected(name) && this.isAgentRecentlySeen(name);
1283
1325
  }
1284
1326
  isAgentConnected(name) {
1285
- if (!this.agentsPath)
1327
+ const debug = process.env.DEBUG_SPAWN === '1';
1328
+ if (!this.agentsPath) {
1329
+ if (debug)
1330
+ log.debug(`isAgentConnected(${name}): no agentsPath`);
1286
1331
  return false;
1287
- if (!fs.existsSync(this.agentsPath))
1332
+ }
1333
+ if (!fs.existsSync(this.agentsPath)) {
1334
+ if (debug)
1335
+ log.debug(`isAgentConnected(${name}): file not found: ${this.agentsPath}`);
1288
1336
  return false;
1337
+ }
1289
1338
  try {
1290
1339
  const raw = JSON.parse(fs.readFileSync(this.agentsPath, 'utf-8'));
1291
1340
  // connected-agents.json format: { agents: string[], users: string[], updatedAt: number }
@@ -1293,6 +1342,9 @@ export class AgentSpawner {
1293
1342
  const agents = Array.isArray(raw?.agents) ? raw.agents : [];
1294
1343
  const updatedAt = typeof raw?.updatedAt === 'number' ? raw.updatedAt : 0;
1295
1344
  const isFresh = Date.now() - updatedAt <= AgentSpawner.ONLINE_THRESHOLD_MS;
1345
+ if (debug) {
1346
+ log.debug(`isAgentConnected(${name}): path=${this.agentsPath} agents=${agents.join(',')} updatedAt=${updatedAt} isFresh=${isFresh}`);
1347
+ }
1296
1348
  if (!isFresh)
1297
1349
  return false;
1298
1350
  // Case-insensitive check to match router behavior
@@ -1300,7 +1352,7 @@ export class AgentSpawner {
1300
1352
  return agents.some((a) => typeof a === 'string' && a.toLowerCase() === lowerName);
1301
1353
  }
1302
1354
  catch (err) {
1303
- log.error('Failed to read connected-agents.json', { error: err.message });
1355
+ log.error('Failed to read connected-agents.json', { error: err.message, path: this.agentsPath });
1304
1356
  return false;
1305
1357
  }
1306
1358
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/bridge",
3
- "version": "2.0.19",
3
+ "version": "2.0.21",
4
4
  "description": "Multi-project bridge client utilities for Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,13 +22,13 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@agent-relay/protocol": "2.0.19",
26
- "@agent-relay/config": "2.0.19",
27
- "@agent-relay/utils": "2.0.19",
28
- "@agent-relay/policy": "2.0.19",
29
- "@agent-relay/user-directory": "2.0.19",
30
- "@agent-relay/wrapper": "2.0.19",
31
- "@agent-relay/mcp": "2.0.19"
25
+ "@agent-relay/protocol": "2.0.21",
26
+ "@agent-relay/config": "2.0.21",
27
+ "@agent-relay/utils": "2.0.21",
28
+ "@agent-relay/policy": "2.0.21",
29
+ "@agent-relay/user-directory": "2.0.21",
30
+ "@agent-relay/wrapper": "2.0.21",
31
+ "@agent-relay/mcp": "2.0.21"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22.19.3",
@@ -0,0 +1,277 @@
1
+ # @agent-relay/cli-tester
2
+
3
+ Manual interactive testing environment for CLI authentication flows.
4
+
5
+ ## Purpose
6
+
7
+ This package provides a Docker-based environment for testing CLI authentication with real OAuth providers. It's designed for:
8
+
9
+ - **Debugging auth issues** - Isolate problems with specific CLIs (e.g., "Cursor doesn't work")
10
+ - **Testing auth flows** - Verify OAuth flows work end-to-end
11
+ - **Message injection** - Test relay-pty message delivery
12
+ - **Credential verification** - Check that credentials are saved correctly
13
+
14
+ ## Quick Start
15
+
16
+ From the relay repo root:
17
+
18
+ ```bash
19
+ # Start the test environment (drops into container shell)
20
+ npm run cli-tester:start
21
+
22
+ # Start with clean credentials (removes any cached auth)
23
+ npm run cli-tester:start:clean
24
+
25
+ # Start with daemon for full integration testing
26
+ npm run cli-tester:start:daemon
27
+ ```
28
+
29
+ ## Inside the Container
30
+
31
+ ### Test a CLI
32
+
33
+ ```bash
34
+ # Test Claude CLI with relay-pty
35
+ ./scripts/test-cli.sh claude
36
+
37
+ # Test Codex with device auth
38
+ ./scripts/test-cli.sh codex --device-auth
39
+
40
+ # Test with debug output
41
+ DEBUG=1 ./scripts/test-cli.sh cursor
42
+ ```
43
+
44
+ ### Verify Credentials
45
+
46
+ ```bash
47
+ # Check if credentials exist (after authenticating)
48
+ ./scripts/verify-auth.sh claude
49
+ ./scripts/verify-auth.sh codex
50
+ ./scripts/verify-auth.sh gemini
51
+ ```
52
+
53
+ ### Inject Messages
54
+
55
+ In a second terminal (while CLI is running):
56
+
57
+ ```bash
58
+ # Send a message via relay-pty socket
59
+ ./scripts/inject-message.sh test-claude "What is 2+2?"
60
+ ```
61
+
62
+ ### Clear Credentials
63
+
64
+ ```bash
65
+ # Clear credentials for fresh testing
66
+ ./scripts/clear-auth.sh claude
67
+ ./scripts/clear-auth.sh all # Clear all CLIs
68
+ ```
69
+
70
+ ## Advanced: Testing Spawn Flow
71
+
72
+ The simple `test-cli.sh` tests the CLI in isolation. For debugging issues where the CLI works in isolation but fails when spawned via the application (e.g., registration timeout), use these advanced tests:
73
+
74
+ ### Test Spawn Behavior
75
+
76
+ Simulates what `AgentSpawner.spawn()` does, including CLI-specific flags:
77
+
78
+ ```bash
79
+ # Test with same flags as spawner (--force for cursor, --dangerously-skip-permissions for claude)
80
+ ./scripts/test-spawn.sh cursor
81
+
82
+ # Test in interactive mode (without auto-accept flags)
83
+ ./scripts/test-spawn.sh cursor --interactive
84
+
85
+ # With verbose debug output
86
+ DEBUG_SPAWN=1 ./scripts/test-spawn.sh cursor
87
+ ```
88
+
89
+ ### Test Registration Flow
90
+
91
+ Monitors the registration files that the spawner polls. This is the step that times out:
92
+
93
+ ```bash
94
+ # Watch registration with 60 second timeout
95
+ ./scripts/test-registration.sh cursor 60
96
+
97
+ # With debug output
98
+ DEBUG_SPAWN=1 ./scripts/test-registration.sh cursor
99
+ ```
100
+
101
+ ### Full Daemon Integration Test
102
+
103
+ Starts a real daemon and tests the complete flow:
104
+
105
+ ```bash
106
+ # Full end-to-end test with daemon
107
+ ./scripts/test-with-daemon.sh cursor
108
+
109
+ # With debug output
110
+ DEBUG=1 ./scripts/test-with-daemon.sh cursor
111
+ ```
112
+
113
+ **Note:** Requires the daemon to be built: `cd packages/daemon && npm run build`
114
+
115
+ ## Debugging a Broken CLI
116
+
117
+ When a CLI isn't working, use this workflow:
118
+
119
+ ```bash
120
+ # 1. Start fresh
121
+ npm run cli-tester:start:clean
122
+
123
+ # 2. Test the problematic CLI
124
+ ./scripts/test-cli.sh cursor
125
+
126
+ # 3. Observe the output for:
127
+ # - Auth URLs being printed
128
+ # - Error messages
129
+ # - Prompt patterns
130
+
131
+ # 4. Check credentials
132
+ ./scripts/verify-auth.sh cursor
133
+ ls -la ~/.cursor/
134
+
135
+ # 5. Compare with a working CLI
136
+ ./scripts/test-cli.sh claude
137
+ ```
138
+
139
+ ## Debugging Registration Timeout
140
+
141
+ If a CLI works in isolation but times out when spawned ("Agent registration timeout"), the issue is in the daemon registration flow.
142
+
143
+ ### Quick Test (Run This First)
144
+
145
+ ```bash
146
+ # Test the EXACT setup flow - this is what TerminalProviderSetup.tsx does
147
+ DEBUG=1 ./scripts/test-full-spawn.sh cursor true
148
+ ```
149
+
150
+ This simulates:
151
+ - `interactive: true` (no --force flag, like setup terminal)
152
+ - 30 second registration timeout
153
+ - Verbose logging of what's happening
154
+
155
+ ### Understanding the Flow
156
+
157
+ **Normal spawn (non-interactive):**
158
+ ```bash
159
+ ./scripts/test-full-spawn.sh cursor # Has --force flag
160
+ ```
161
+
162
+ **Setup terminal (interactive):**
163
+ ```bash
164
+ ./scripts/test-full-spawn.sh cursor true # NO --force flag
165
+ ```
166
+
167
+ The key difference is `interactive: true` **skips auto-accept flags**. Setup terminals expect the user to respond to prompts in the browser terminal.
168
+
169
+ ### What the Tests Show
170
+
171
+ 1. **test-full-spawn.sh** - Simulates spawner's 30s registration timeout
172
+ - Shows poll count (like spawner logs)
173
+ - Shows socket status
174
+ - Captures CLI output to log file
175
+ - Tells you exactly where things fail
176
+
177
+ 2. **test-setup-flow.sh** - Identical to what TerminalProviderSetup.tsx does
178
+ - Uses `__setup__cursor-xxx` naming
179
+ - No CLI flags (interactive mode)
180
+
181
+ ### Debugging Steps
182
+
183
+ ```bash
184
+ # 1. Test in isolation (verify CLI starts)
185
+ ./scripts/test-cli.sh cursor
186
+
187
+ # 2. Test NON-INTERACTIVE spawn (with --force)
188
+ DEBUG=1 ./scripts/test-full-spawn.sh cursor
189
+
190
+ # 3. Test INTERACTIVE spawn (setup terminal flow)
191
+ DEBUG=1 ./scripts/test-full-spawn.sh cursor true
192
+
193
+ # 4. Watch the log file in another terminal
194
+ tail -f /tmp/relay-spawn-*.log
195
+ ```
196
+
197
+ ### Common Causes
198
+
199
+ | Symptom | Cause | Fix |
200
+ |---------|-------|-----|
201
+ | CLI exits immediately | Not installed or crash | Check `which agent` |
202
+ | Socket never created | CLI stuck on early prompt | Check log for prompts |
203
+ | 30s timeout | CLI waiting for user input | Respond to prompts (trust, etc.) |
204
+ | 30s timeout | No daemon to register with | Run with daemon profile |
205
+
206
+ ### The Registration Flow
207
+
208
+ The spawner waits for TWO conditions:
209
+ 1. Agent in `connected-agents.json` (daemon updates this when CLI connects)
210
+ 2. Agent in `agents.json` (relay-pty hook updates this)
211
+
212
+ Without a running daemon, both files are empty → timeout.
213
+
214
+ ## Available CLIs
215
+
216
+ The container includes these pre-installed CLIs:
217
+
218
+ | CLI | Command | Auth Command | Credential Path |
219
+ |-----|---------|--------------|-----------------|
220
+ | Claude | `claude` | (auto) | `~/.claude/.credentials.json` |
221
+ | Codex | `codex` | `login` | `~/.codex/auth.json` |
222
+ | Gemini | `gemini` | (auto) | `~/.gemini/credentials.json` |
223
+ | Cursor | `agent` | (auto) | `~/.cursor/auth.json` |
224
+ | OpenCode | `opencode` | `auth login` | `~/.local/share/opencode/auth.json` |
225
+ | Droid | `droid` | `--login` | `~/.droid/auth.json` |
226
+ | Copilot | `copilot` | `auth login` | `~/.config/gh/hosts.yml` |
227
+
228
+ **Note:** Cursor CLI installs as `agent`, not `cursor`. The test scripts handle this mapping automatically.
229
+
230
+ ## How It Works
231
+
232
+ 1. **relay-pty** wraps the CLI and provides:
233
+ - Unix socket for message injection
234
+ - Output parsing for relay commands
235
+ - Idle detection for message timing
236
+
237
+ 2. **Docker volumes** persist credentials between runs so you don't have to re-authenticate each time.
238
+
239
+ 3. **Shell scripts** provide simple commands for common operations.
240
+
241
+ ## TypeScript API
242
+
243
+ For programmatic use:
244
+
245
+ ```typescript
246
+ import { RelayPtyClient, checkCredentials } from '@agent-relay/cli-tester';
247
+
248
+ // Check credentials
249
+ const result = checkCredentials('claude');
250
+ console.log(result.exists, result.valid, result.hasAccessToken);
251
+
252
+ // Inject messages via socket
253
+ const client = new RelayPtyClient('/tmp/relay-pty-test-claude.sock');
254
+ await client.connect();
255
+ await client.inject({ from: 'Test', body: 'Hello' });
256
+ ```
257
+
258
+ ## File Structure
259
+
260
+ ```
261
+ packages/cli-tester/
262
+ ├── docker/
263
+ │ ├── Dockerfile # Test environment image
264
+ │ └── docker-compose.yml # Container configuration
265
+ ├── scripts/
266
+ │ ├── start.sh # Start container
267
+ │ ├── test-cli.sh # Test a CLI with relay-pty
268
+ │ ├── verify-auth.sh # Check credentials
269
+ │ ├── inject-message.sh # Send message via socket
270
+ │ └── clear-auth.sh # Clear credentials
271
+ ├── src/
272
+ │ └── utils/
273
+ │ ├── socket-client.ts # relay-pty socket communication
274
+ │ └── credential-check.ts # Credential file utilities
275
+ └── tests/
276
+ └── credential-check.test.ts
277
+ ```
@@ -0,0 +1,21 @@
1
+ /**
2
+ * CLI Auth Tester - Manual interactive testing for CLI authentication flows
3
+ *
4
+ * This package provides utilities for testing CLI authentication in a Docker container.
5
+ * Primary use case is debugging auth issues with various CLI tools (Claude, Codex, Gemini, etc.)
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Start the test environment
10
+ * npm run cli-tester:start
11
+ *
12
+ * # Inside container, test a CLI
13
+ * ./scripts/test-cli.sh claude
14
+ *
15
+ * # Verify credentials
16
+ * ./scripts/verify-auth.sh claude
17
+ * ```
18
+ */
19
+ export { RelayPtyClient, createClient, getSocketPath, type InjectRequest, type InjectResponse, type StatusRequest, type StatusResponse, type RelayPtyResponse, } from './utils/socket-client.js';
20
+ export { checkCredentials, clearCredentials, checkAllCredentials, clearAllCredentials, getCredentialPath, getConfigPaths, type CLIType, type CredentialCheck, } from './utils/credential-check.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * CLI Auth Tester - Manual interactive testing for CLI authentication flows
3
+ *
4
+ * This package provides utilities for testing CLI authentication in a Docker container.
5
+ * Primary use case is debugging auth issues with various CLI tools (Claude, Codex, Gemini, etc.)
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Start the test environment
10
+ * npm run cli-tester:start
11
+ *
12
+ * # Inside container, test a CLI
13
+ * ./scripts/test-cli.sh claude
14
+ *
15
+ * # Verify credentials
16
+ * ./scripts/verify-auth.sh claude
17
+ * ```
18
+ */
19
+ export { RelayPtyClient, createClient, getSocketPath, } from './utils/socket-client.js';
20
+ export { checkCredentials, clearCredentials, checkAllCredentials, clearAllCredentials, getCredentialPath, getConfigPaths, } from './utils/credential-check.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Credential checking utilities for CLI authentication testing
3
+ * Verifies and parses credential files for various CLI tools
4
+ */
5
+ export type CLIType = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'droid';
6
+ export interface CredentialCheck {
7
+ /** CLI type being checked */
8
+ cli: CLIType;
9
+ /** Whether the credential file exists */
10
+ exists: boolean;
11
+ /** Whether the credentials appear valid (have required fields) */
12
+ valid: boolean;
13
+ /** Whether an access token is present */
14
+ hasAccessToken: boolean;
15
+ /** Whether a refresh token is present */
16
+ hasRefreshToken: boolean;
17
+ /** Token expiration date if available */
18
+ expiresAt?: Date;
19
+ /** Path to the credential file */
20
+ filePath: string;
21
+ /** Raw credential data (tokens redacted) */
22
+ data?: Record<string, unknown>;
23
+ /** Error message if check failed */
24
+ error?: string;
25
+ }
26
+ /**
27
+ * Get the credential file path for a CLI
28
+ */
29
+ export declare function getCredentialPath(cli: CLIType): string;
30
+ /**
31
+ * Get all config paths for a CLI (for clearing)
32
+ */
33
+ export declare function getConfigPaths(cli: CLIType): string[];
34
+ /**
35
+ * Check credentials for a specific CLI
36
+ */
37
+ export declare function checkCredentials(cli: CLIType): CredentialCheck;
38
+ /**
39
+ * Clear credentials for a specific CLI
40
+ */
41
+ export declare function clearCredentials(cli: CLIType): {
42
+ cleared: string[];
43
+ errors: string[];
44
+ };
45
+ /**
46
+ * Clear all CLI credentials
47
+ */
48
+ export declare function clearAllCredentials(): Record<CLIType, {
49
+ cleared: string[];
50
+ errors: string[];
51
+ }>;
52
+ /**
53
+ * Check all CLI credentials
54
+ */
55
+ export declare function checkAllCredentials(): Record<CLIType, CredentialCheck>;
56
+ //# sourceMappingURL=credential-check.d.ts.map