agent-relay 2.0.18 → 2.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/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/_next/static/chunks/320-a6304232cd0ee2ce.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/631-16b905e5920f9b59.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ecb16ffd3b36262b.js +1 -0
- package/dist/dashboard/out/_next/static/css/{45361ce86b2847c4.css → 6892f8422896ef7a.css} +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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/dashboard/out/login.html +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/dashboard/out/signup.html +2 -2
- 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 +75 -23
- 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/dist/api/cli-pty-runner.js +9 -6
- package/packages/cloud/package.json +6 -6
- package/packages/config/dist/cli-auth-config.js +75 -0
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/dist/cli-auth.js +5 -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/app/providers/page.tsx +2 -2
- package/packages/dashboard/ui/react-components/ProviderConnectionList.tsx +23 -8
- package/packages/dashboard/ui/react-components/SpawnModal.tsx +16 -6
- package/packages/dashboard/ui/react-components/settings/WorkspaceSettingsPanel.tsx +22 -6
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-a6304232cd0ee2ce.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/631-16b905e5920f9b59.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-ecb16ffd3b36262b.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/css/{45361ce86b2847c4.css → 6892f8422896ef7a.css} +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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/packages/dashboard/ui-dist/login.html +2 -2
- 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 +2 -2
- 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 +2 -2
- package/packages/dashboard/ui-dist/signup.html +2 -2
- 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/idle-detector.d.ts +4 -0
- package/packages/wrapper/dist/idle-detector.js +21 -8
- package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +5 -0
- package/packages/wrapper/dist/relay-pty-orchestrator.js +60 -5
- 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/scripts/postinstall.js +41 -1
- package/specs/PRIMITIVES_ROADMAP.md +2154 -0
- package/dist/dashboard/out/_next/static/chunks/320-402ffc8646b31da1.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/631-af51bad94027527a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-402ffc8646b31da1.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/631-af51bad94027527a.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +0 -1
- /package/dist/dashboard/out/_next/static/{JIjqkuDKNeoSg7KaMMuhx → PwtT8u1tFMW_S1HUv0i5S}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{JIjqkuDKNeoSg7KaMMuhx → PwtT8u1tFMW_S1HUv0i5S}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{JIjqkuDKNeoSg7KaMMuhx → 52xh7eSCZzG97BVf5zzLY}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{JIjqkuDKNeoSg7KaMMuhx → 52xh7eSCZzG97BVf5zzLY}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{nmkOi7bqeDmLMoWBih8lz → NN1eZ4W4r5XU6mkmJWV2-}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{nmkOi7bqeDmLMoWBih8lz → NN1eZ4W4r5XU6mkmJWV2-}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{wk_gKRNSPpWE-ZhGL6UMl → PwtT8u1tFMW_S1HUv0i5S}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{wk_gKRNSPpWE-ZhGL6UMl → PwtT8u1tFMW_S1HUv0i5S}/_ssgManifest.js +0 -0
|
@@ -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
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential checking utilities for CLI authentication testing
|
|
3
|
+
* Verifies and parses credential files for various CLI tools
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, existsSync, unlinkSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Get the credential file path for a CLI
|
|
10
|
+
*/
|
|
11
|
+
export function getCredentialPath(cli) {
|
|
12
|
+
const home = homedir();
|
|
13
|
+
switch (cli) {
|
|
14
|
+
case 'claude':
|
|
15
|
+
return join(home, '.claude', '.credentials.json');
|
|
16
|
+
case 'codex':
|
|
17
|
+
return join(home, '.codex', 'auth.json');
|
|
18
|
+
case 'gemini':
|
|
19
|
+
return join(home, '.config', 'gcloud', 'application_default_credentials.json');
|
|
20
|
+
case 'cursor':
|
|
21
|
+
return join(home, '.cursor', 'auth.json');
|
|
22
|
+
case 'opencode':
|
|
23
|
+
return join(home, '.local', 'share', 'opencode', 'auth.json');
|
|
24
|
+
case 'droid':
|
|
25
|
+
return join(home, '.droid', 'auth.json');
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unknown CLI type: ${cli}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get all config paths for a CLI (for clearing)
|
|
32
|
+
*/
|
|
33
|
+
export function getConfigPaths(cli) {
|
|
34
|
+
const home = homedir();
|
|
35
|
+
switch (cli) {
|
|
36
|
+
case 'claude':
|
|
37
|
+
return [
|
|
38
|
+
join(home, '.claude', '.credentials.json'),
|
|
39
|
+
join(home, '.claude', 'settings.json'),
|
|
40
|
+
join(home, '.claude', 'settings.local.json'),
|
|
41
|
+
];
|
|
42
|
+
case 'codex':
|
|
43
|
+
return [
|
|
44
|
+
join(home, '.codex', 'auth.json'),
|
|
45
|
+
join(home, '.codex', 'config.json'),
|
|
46
|
+
join(home, '.codex', 'config.toml'),
|
|
47
|
+
];
|
|
48
|
+
case 'gemini':
|
|
49
|
+
return [
|
|
50
|
+
join(home, '.config', 'gcloud', 'application_default_credentials.json'),
|
|
51
|
+
join(home, '.gemini', 'credentials.json'),
|
|
52
|
+
join(home, '.gemini', 'settings.json'),
|
|
53
|
+
];
|
|
54
|
+
case 'cursor':
|
|
55
|
+
return [
|
|
56
|
+
join(home, '.cursor', 'auth.json'),
|
|
57
|
+
join(home, '.cursor', 'settings.json'),
|
|
58
|
+
];
|
|
59
|
+
case 'opencode':
|
|
60
|
+
return [join(home, '.local', 'share', 'opencode', 'auth.json')];
|
|
61
|
+
case 'droid':
|
|
62
|
+
return [join(home, '.droid', 'auth.json')];
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`Unknown CLI type: ${cli}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract token info from credential data based on CLI type
|
|
69
|
+
*/
|
|
70
|
+
function extractTokenInfo(cli, data) {
|
|
71
|
+
let hasAccessToken = false;
|
|
72
|
+
let hasRefreshToken = false;
|
|
73
|
+
let expiresAt;
|
|
74
|
+
switch (cli) {
|
|
75
|
+
case 'claude': {
|
|
76
|
+
// Claude format: { claudeAiOauth: { accessToken, refreshToken, expiresAt } }
|
|
77
|
+
const oauth = data.claudeAiOauth;
|
|
78
|
+
if (oauth) {
|
|
79
|
+
hasAccessToken = typeof oauth.accessToken === 'string' && oauth.accessToken.length > 0;
|
|
80
|
+
hasRefreshToken = typeof oauth.refreshToken === 'string' && oauth.refreshToken.length > 0;
|
|
81
|
+
if (typeof oauth.expiresAt === 'number') {
|
|
82
|
+
expiresAt = new Date(oauth.expiresAt);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case 'codex': {
|
|
88
|
+
// Codex format: { tokens: { access_token, refresh_token, expires_at } }
|
|
89
|
+
const tokens = data.tokens;
|
|
90
|
+
if (tokens) {
|
|
91
|
+
hasAccessToken =
|
|
92
|
+
typeof tokens.access_token === 'string' && tokens.access_token.length > 0;
|
|
93
|
+
hasRefreshToken =
|
|
94
|
+
typeof tokens.refresh_token === 'string' && tokens.refresh_token.length > 0;
|
|
95
|
+
if (typeof tokens.expires_at === 'number') {
|
|
96
|
+
expiresAt = new Date(tokens.expires_at * 1000); // Unix timestamp
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case 'gemini': {
|
|
102
|
+
// Google OAuth format: { access_token, refresh_token, expiry_date }
|
|
103
|
+
hasAccessToken =
|
|
104
|
+
typeof data.access_token === 'string' && data.access_token.length > 0;
|
|
105
|
+
hasRefreshToken =
|
|
106
|
+
typeof data.refresh_token === 'string' && data.refresh_token.length > 0;
|
|
107
|
+
if (typeof data.expiry_date === 'number') {
|
|
108
|
+
expiresAt = new Date(data.expiry_date);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'cursor':
|
|
113
|
+
case 'opencode':
|
|
114
|
+
case 'droid': {
|
|
115
|
+
// Generic format: { accessToken, refreshToken } or { access_token, refresh_token }
|
|
116
|
+
hasAccessToken =
|
|
117
|
+
(typeof data.accessToken === 'string' && data.accessToken.length > 0) ||
|
|
118
|
+
(typeof data.access_token === 'string' && data.access_token.length > 0);
|
|
119
|
+
hasRefreshToken =
|
|
120
|
+
(typeof data.refreshToken === 'string' && data.refreshToken.length > 0) ||
|
|
121
|
+
(typeof data.refresh_token === 'string' && data.refresh_token.length > 0);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { hasAccessToken, hasRefreshToken, expiresAt };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Redact sensitive values in credential data
|
|
129
|
+
*/
|
|
130
|
+
function redactData(data) {
|
|
131
|
+
const redacted = {};
|
|
132
|
+
for (const [key, value] of Object.entries(data)) {
|
|
133
|
+
if (typeof value === 'string') {
|
|
134
|
+
// Redact anything that looks like a token
|
|
135
|
+
if (key.toLowerCase().includes('token') ||
|
|
136
|
+
key.toLowerCase().includes('secret') ||
|
|
137
|
+
key.toLowerCase().includes('key') ||
|
|
138
|
+
value.length > 40) {
|
|
139
|
+
redacted[key] = '[REDACTED]';
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
redacted[key] = value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (typeof value === 'object' && value !== null) {
|
|
146
|
+
redacted[key] = redactData(value);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
redacted[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return redacted;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check credentials for a specific CLI
|
|
156
|
+
*/
|
|
157
|
+
export function checkCredentials(cli) {
|
|
158
|
+
const filePath = getCredentialPath(cli);
|
|
159
|
+
const result = {
|
|
160
|
+
cli,
|
|
161
|
+
exists: false,
|
|
162
|
+
valid: false,
|
|
163
|
+
hasAccessToken: false,
|
|
164
|
+
hasRefreshToken: false,
|
|
165
|
+
filePath,
|
|
166
|
+
};
|
|
167
|
+
if (!existsSync(filePath)) {
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
result.exists = true;
|
|
171
|
+
try {
|
|
172
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
173
|
+
const data = JSON.parse(content);
|
|
174
|
+
const tokenInfo = extractTokenInfo(cli, data);
|
|
175
|
+
result.hasAccessToken = tokenInfo.hasAccessToken;
|
|
176
|
+
result.hasRefreshToken = tokenInfo.hasRefreshToken;
|
|
177
|
+
result.expiresAt = tokenInfo.expiresAt;
|
|
178
|
+
// Valid if at least access token is present
|
|
179
|
+
result.valid = result.hasAccessToken;
|
|
180
|
+
// Include redacted data for debugging
|
|
181
|
+
result.data = redactData(data);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
result.error = err instanceof Error ? err.message : 'Unknown error';
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Clear credentials for a specific CLI
|
|
190
|
+
*/
|
|
191
|
+
export function clearCredentials(cli) {
|
|
192
|
+
const paths = getConfigPaths(cli);
|
|
193
|
+
const cleared = [];
|
|
194
|
+
const errors = [];
|
|
195
|
+
for (const path of paths) {
|
|
196
|
+
if (existsSync(path)) {
|
|
197
|
+
try {
|
|
198
|
+
unlinkSync(path);
|
|
199
|
+
cleared.push(path);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
errors.push(`Failed to remove ${path}: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { cleared, errors };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Clear all CLI credentials
|
|
210
|
+
*/
|
|
211
|
+
export function clearAllCredentials() {
|
|
212
|
+
const clis = ['claude', 'codex', 'gemini', 'cursor', 'opencode', 'droid'];
|
|
213
|
+
const results = {};
|
|
214
|
+
for (const cli of clis) {
|
|
215
|
+
results[cli] = clearCredentials(cli);
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check all CLI credentials
|
|
221
|
+
*/
|
|
222
|
+
export function checkAllCredentials() {
|
|
223
|
+
const clis = ['claude', 'codex', 'gemini', 'cursor', 'opencode', 'droid'];
|
|
224
|
+
const results = {};
|
|
225
|
+
for (const cli of clis) {
|
|
226
|
+
results[cli] = checkCredentials(cli);
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=credential-check.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket client for communicating with relay-pty Unix socket
|
|
3
|
+
* Used for programmatic message injection and status queries
|
|
4
|
+
*/
|
|
5
|
+
export interface InjectRequest {
|
|
6
|
+
type: 'inject';
|
|
7
|
+
id: string;
|
|
8
|
+
from: string;
|
|
9
|
+
body: string;
|
|
10
|
+
priority?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface StatusRequest {
|
|
13
|
+
type: 'status';
|
|
14
|
+
}
|
|
15
|
+
export interface InjectResponse {
|
|
16
|
+
type: 'inject_result';
|
|
17
|
+
id: string;
|
|
18
|
+
status: 'queued' | 'injecting' | 'delivered' | 'failed';
|
|
19
|
+
timestamp: number;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface StatusResponse {
|
|
23
|
+
type: 'status';
|
|
24
|
+
agent_idle: boolean;
|
|
25
|
+
queue_length: number;
|
|
26
|
+
cursor_position: {
|
|
27
|
+
row: number;
|
|
28
|
+
col: number;
|
|
29
|
+
} | null;
|
|
30
|
+
last_output_ms: number;
|
|
31
|
+
}
|
|
32
|
+
export type RelayPtyResponse = InjectResponse | StatusResponse;
|
|
33
|
+
export declare class RelayPtyClient {
|
|
34
|
+
private socket;
|
|
35
|
+
private socketPath;
|
|
36
|
+
private responseBuffer;
|
|
37
|
+
constructor(socketPath: string);
|
|
38
|
+
/**
|
|
39
|
+
* Connect to the relay-pty socket
|
|
40
|
+
*/
|
|
41
|
+
connect(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Send a request and wait for response
|
|
44
|
+
*/
|
|
45
|
+
private sendRequest;
|
|
46
|
+
/**
|
|
47
|
+
* Inject a message into the CLI
|
|
48
|
+
*/
|
|
49
|
+
inject(message: {
|
|
50
|
+
from: string;
|
|
51
|
+
body: string;
|
|
52
|
+
priority?: number;
|
|
53
|
+
}): Promise<InjectResponse>;
|
|
54
|
+
/**
|
|
55
|
+
* Get current status of the CLI session
|
|
56
|
+
*/
|
|
57
|
+
getStatus(): Promise<StatusResponse>;
|
|
58
|
+
/**
|
|
59
|
+
* Wait for a specific message to be delivered
|
|
60
|
+
* Returns when the message reaches 'delivered' or 'failed' status
|
|
61
|
+
*/
|
|
62
|
+
waitForDelivered(messageId: string, timeoutMs?: number): Promise<InjectResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* Close the connection
|
|
65
|
+
*/
|
|
66
|
+
close(): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Helper to create a connected client
|
|
70
|
+
*/
|
|
71
|
+
export declare function createClient(socketPath: string): Promise<RelayPtyClient>;
|
|
72
|
+
/**
|
|
73
|
+
* Get socket path for a named session
|
|
74
|
+
*/
|
|
75
|
+
export declare function getSocketPath(sessionName: string): string;
|
|
76
|
+
//# sourceMappingURL=socket-client.d.ts.map
|