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.
- package/CHANGELOG.md +217 -24
- package/bin/relay-pty-darwin-arm64 +0 -0
- package/bin/relay-pty-darwin-x64 +0 -0
- package/bin/relay-pty-linux-x64 +0 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +1 -1
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +1 -1
- package/dist/dashboard/out/complete-profile.html +1 -1
- package/dist/dashboard/out/complete-profile.txt +1 -1
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +1 -1
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +1 -1
- package/dist/dashboard/out/login.html +1 -1
- package/dist/dashboard/out/login.txt +1 -1
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +1 -1
- package/dist/dashboard/out/pricing.html +1 -1
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +1 -1
- package/dist/dashboard/out/signup.txt +1 -1
- package/package.json +23 -17
- package/packages/api-types/package.json +2 -2
- package/packages/bridge/dist/spawner.d.ts +2 -0
- package/packages/bridge/dist/spawner.js +76 -24
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/README.md +277 -0
- package/packages/cli-tester/dist/index.d.ts +21 -0
- package/packages/cli-tester/dist/index.js +21 -0
- package/packages/cli-tester/dist/utils/credential-check.d.ts +56 -0
- package/packages/cli-tester/dist/utils/credential-check.js +230 -0
- package/packages/cli-tester/dist/utils/socket-client.d.ts +76 -0
- package/packages/cli-tester/dist/utils/socket-client.js +153 -0
- package/packages/cli-tester/docker/entrypoint.sh +58 -0
- package/packages/cli-tester/package.json +32 -0
- package/packages/cli-tester/scripts/clear-auth.sh +101 -0
- package/packages/cli-tester/scripts/inject-message.sh +42 -0
- package/packages/cli-tester/scripts/start.sh +71 -0
- package/packages/cli-tester/scripts/test-cli.sh +56 -0
- package/packages/cli-tester/scripts/test-full-spawn.sh +238 -0
- package/packages/cli-tester/scripts/test-registration.sh +182 -0
- package/packages/cli-tester/scripts/test-setup-flow.sh +202 -0
- package/packages/cli-tester/scripts/test-spawn.sh +140 -0
- package/packages/cli-tester/scripts/test-with-daemon.sh +247 -0
- package/packages/cli-tester/scripts/verify-auth.sh +112 -0
- package/packages/cloud/package.json +6 -6
- package/packages/config/dist/cli-auth-config.js +65 -0
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/dist/router.js +4 -4
- package/packages/daemon/dist/server.js +38 -19
- package/packages/daemon/dist/spawn-manager.d.ts +4 -0
- package/packages/daemon/dist/spawn-manager.js +2 -0
- package/packages/daemon/package.json +12 -12
- package/packages/dashboard/dist/server.js +4 -0
- package/packages/dashboard/package.json +14 -14
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +1 -1
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
- package/packages/dashboard/ui-dist/complete-profile.html +1 -1
- package/packages/dashboard/ui-dist/complete-profile.txt +1 -1
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +1 -1
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +1 -1
- package/packages/dashboard/ui-dist/login.html +1 -1
- package/packages/dashboard/ui-dist/login.txt +1 -1
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +1 -1
- package/packages/dashboard/ui-dist/pricing.html +1 -1
- package/packages/dashboard/ui-dist/pricing.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +1 -1
- package/packages/dashboard/ui-dist/signup.html +1 -1
- package/packages/dashboard/ui-dist/signup.txt +1 -1
- package/packages/dashboard-server/dist/server.js +4 -0
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/README.md +512 -58
- package/packages/sdk/dist/client.d.ts +135 -1
- package/packages/sdk/dist/client.js +338 -0
- package/packages/sdk/dist/index.d.ts +2 -1
- package/packages/sdk/dist/index.js +2 -0
- package/packages/sdk/dist/logs.d.ts +61 -0
- package/packages/sdk/dist/logs.js +95 -0
- package/packages/sdk/dist/protocol/index.d.ts +1 -1
- package/packages/sdk/dist/protocol/types.d.ts +186 -1
- package/packages/sdk/package.json +3 -3
- package/packages/spawner/package.json +2 -2
- package/packages/state/package.json +1 -1
- package/packages/storage/dist/sqlite-adapter.js +2 -0
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/dist/base-wrapper.js +27 -10
- package/packages/wrapper/dist/relay-pty-orchestrator.js +16 -16
- package/packages/wrapper/dist/tmux-wrapper.js +16 -0
- package/packages/wrapper/package.json +7 -7
- package/scripts/hooks/install.sh +16 -0
- package/scripts/hooks/pre-commit +60 -0
- package/specs/PRIMITIVES_ROADMAP.md +2154 -0
- /package/dist/dashboard/out/_next/static/{cREcLZyPb-5NyVZje0Qfe → 7MZPqYkVGw3EGzVBkVmY9}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{cREcLZyPb-5NyVZje0Qfe → 7MZPqYkVGw3EGzVBkVmY9}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{N3ajGnJqRESKyCjDvyU52 → 7MZPqYkVGw3EGzVBkVmY9}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{N3ajGnJqRESKyCjDvyU52 → 7MZPqYkVGw3EGzVBkVmY9}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{UQiyWwBxIP-9it3GYVBDL → iJ3Uiz3IrqUJL7IxKZHiV}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{UQiyWwBxIP-9it3GYVBDL → iJ3Uiz3IrqUJL7IxKZHiV}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{cREcLZyPb-5NyVZje0Qfe → l-jd878zUJ_IlraqEWMZc}/_buildManifest.js +0 -0
- /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(
|
|
296
|
-
this.registryPath = path.join(
|
|
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(
|
|
301
|
-
this.workersPath = path.join(
|
|
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
|
|
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
|
|
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
|
-
|
|
610
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
|
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 (
|
|
649
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
26
|
-
"@agent-relay/config": "2.0.
|
|
27
|
-
"@agent-relay/utils": "2.0.
|
|
28
|
-
"@agent-relay/policy": "2.0.
|
|
29
|
-
"@agent-relay/user-directory": "2.0.
|
|
30
|
-
"@agent-relay/wrapper": "2.0.
|
|
31
|
-
"@agent-relay/mcp": "2.0.
|
|
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
|