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
|
@@ -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
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket client for communicating with relay-pty Unix socket
|
|
3
|
+
* Used for programmatic message injection and status queries
|
|
4
|
+
*/
|
|
5
|
+
import { createConnection } from 'node:net';
|
|
6
|
+
export class RelayPtyClient {
|
|
7
|
+
socket = null;
|
|
8
|
+
socketPath;
|
|
9
|
+
responseBuffer = '';
|
|
10
|
+
constructor(socketPath) {
|
|
11
|
+
this.socketPath = socketPath;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Connect to the relay-pty socket
|
|
15
|
+
*/
|
|
16
|
+
async connect() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
this.socket = createConnection(this.socketPath, () => {
|
|
19
|
+
resolve();
|
|
20
|
+
});
|
|
21
|
+
this.socket.on('error', (err) => {
|
|
22
|
+
reject(new Error(`Failed to connect to ${this.socketPath}: ${err.message}`));
|
|
23
|
+
});
|
|
24
|
+
this.socket.on('data', (data) => {
|
|
25
|
+
this.responseBuffer += data.toString();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Send a request and wait for response
|
|
31
|
+
*/
|
|
32
|
+
async sendRequest(request) {
|
|
33
|
+
if (!this.socket) {
|
|
34
|
+
throw new Error('Not connected. Call connect() first.');
|
|
35
|
+
}
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const timeout = setTimeout(() => {
|
|
38
|
+
reject(new Error('Request timed out'));
|
|
39
|
+
}, 10000);
|
|
40
|
+
// Clear buffer before sending
|
|
41
|
+
this.responseBuffer = '';
|
|
42
|
+
// Set up response handler
|
|
43
|
+
const checkResponse = () => {
|
|
44
|
+
const lines = this.responseBuffer.split('\n');
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
if (line.trim()) {
|
|
47
|
+
try {
|
|
48
|
+
const response = JSON.parse(line);
|
|
49
|
+
clearTimeout(timeout);
|
|
50
|
+
resolve(response);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Not valid JSON yet, keep waiting
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Keep checking
|
|
59
|
+
setTimeout(checkResponse, 50);
|
|
60
|
+
};
|
|
61
|
+
// Send request
|
|
62
|
+
const requestStr = JSON.stringify(request) + '\n';
|
|
63
|
+
this.socket.write(requestStr, (err) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
reject(err);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
checkResponse();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Inject a message into the CLI
|
|
76
|
+
*/
|
|
77
|
+
async inject(message) {
|
|
78
|
+
const request = {
|
|
79
|
+
type: 'inject',
|
|
80
|
+
id: `ts-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
81
|
+
from: message.from,
|
|
82
|
+
body: message.body,
|
|
83
|
+
priority: message.priority ?? 0,
|
|
84
|
+
};
|
|
85
|
+
return this.sendRequest(request);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get current status of the CLI session
|
|
89
|
+
*/
|
|
90
|
+
async getStatus() {
|
|
91
|
+
const request = { type: 'status' };
|
|
92
|
+
return this.sendRequest(request);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Wait for a specific message to be delivered
|
|
96
|
+
* Returns when the message reaches 'delivered' or 'failed' status
|
|
97
|
+
*/
|
|
98
|
+
async waitForDelivered(messageId, timeoutMs = 30000) {
|
|
99
|
+
const startTime = Date.now();
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const checkBuffer = () => {
|
|
102
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
103
|
+
reject(new Error(`Timeout waiting for message ${messageId} to be delivered`));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const lines = this.responseBuffer.split('\n');
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (line.trim()) {
|
|
109
|
+
try {
|
|
110
|
+
const response = JSON.parse(line);
|
|
111
|
+
if (response.type === 'inject_result' &&
|
|
112
|
+
response.id === messageId &&
|
|
113
|
+
(response.status === 'delivered' || response.status === 'failed')) {
|
|
114
|
+
resolve(response);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Not valid JSON, skip
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Keep checking
|
|
124
|
+
setTimeout(checkBuffer, 100);
|
|
125
|
+
};
|
|
126
|
+
checkBuffer();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Close the connection
|
|
131
|
+
*/
|
|
132
|
+
close() {
|
|
133
|
+
if (this.socket) {
|
|
134
|
+
this.socket.destroy();
|
|
135
|
+
this.socket = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Helper to create a connected client
|
|
141
|
+
*/
|
|
142
|
+
export async function createClient(socketPath) {
|
|
143
|
+
const client = new RelayPtyClient(socketPath);
|
|
144
|
+
await client.connect();
|
|
145
|
+
return client;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get socket path for a named session
|
|
149
|
+
*/
|
|
150
|
+
export function getSocketPath(sessionName) {
|
|
151
|
+
return `/tmp/relay-pty-${sessionName}.sock`;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=socket-client.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CLI Tester Container Entrypoint
|
|
3
|
+
# Simplified entrypoint for CLI authentication testing
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Ensure config directories exist with correct permissions
|
|
8
|
+
mkdir -p ~/.claude ~/.codex ~/.gemini ~/.cursor ~/.config ~/.local/share/opencode 2>/dev/null || true
|
|
9
|
+
|
|
10
|
+
echo "========================================"
|
|
11
|
+
echo " CLI Auth Tester Environment"
|
|
12
|
+
echo "========================================"
|
|
13
|
+
echo ""
|
|
14
|
+
|
|
15
|
+
# Show available CLIs
|
|
16
|
+
echo "Available CLI tools:"
|
|
17
|
+
for cli in claude codex gemini opencode droid copilot; do
|
|
18
|
+
if command -v $cli &> /dev/null; then
|
|
19
|
+
version=$($cli --version 2>/dev/null | head -n1 || echo "installed")
|
|
20
|
+
echo " ✓ $cli ($version)"
|
|
21
|
+
else
|
|
22
|
+
echo " ✗ $cli (not installed)"
|
|
23
|
+
fi
|
|
24
|
+
done
|
|
25
|
+
# Cursor installs as 'agent'
|
|
26
|
+
if command -v agent &> /dev/null; then
|
|
27
|
+
version=$(agent --version 2>/dev/null | head -n1 || echo "installed")
|
|
28
|
+
echo " ✓ cursor/agent ($version)"
|
|
29
|
+
else
|
|
30
|
+
echo " ✗ cursor/agent (not installed)"
|
|
31
|
+
fi
|
|
32
|
+
echo ""
|
|
33
|
+
|
|
34
|
+
# Show relay-pty status
|
|
35
|
+
if command -v relay-pty &> /dev/null; then
|
|
36
|
+
echo "relay-pty: ✓ available"
|
|
37
|
+
else
|
|
38
|
+
echo "relay-pty: ✗ not found"
|
|
39
|
+
fi
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
42
|
+
# Show usage
|
|
43
|
+
echo "Quick Start:"
|
|
44
|
+
echo " test-cli.sh claude # Test Claude CLI with relay-pty"
|
|
45
|
+
echo " test-cli.sh codex # Test Codex CLI with relay-pty"
|
|
46
|
+
echo " verify-auth.sh claude # Check if credentials exist"
|
|
47
|
+
echo " clear-auth.sh claude # Clear credentials for fresh test"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "For debugging:"
|
|
50
|
+
echo " DEBUG=1 test-cli.sh cursor # Verbose output"
|
|
51
|
+
echo ""
|
|
52
|
+
|
|
53
|
+
# Execute the provided command or drop into shell
|
|
54
|
+
if [ $# -gt 0 ]; then
|
|
55
|
+
exec "$@"
|
|
56
|
+
else
|
|
57
|
+
exec /bin/bash
|
|
58
|
+
fi
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-relay/cli-tester",
|
|
3
|
+
"version": "2.0.21",
|
|
4
|
+
"description": "Manual interactive testing for CLI authentication flows",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"start": "./scripts/start.sh",
|
|
11
|
+
"start:clean": "./scripts/start.sh --clean",
|
|
12
|
+
"start:daemon": "./scripts/start.sh --daemon",
|
|
13
|
+
"docker:build": "docker compose -f docker/docker-compose.yml build",
|
|
14
|
+
"docker:up": "docker compose -f docker/docker-compose.yml up",
|
|
15
|
+
"docker:up:daemon": "docker compose -f docker/docker-compose.yml --profile daemon up",
|
|
16
|
+
"docker:down": "docker compose -f docker/docker-compose.yml down",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cli",
|
|
22
|
+
"testing",
|
|
23
|
+
"authentication",
|
|
24
|
+
"relay-pty"
|
|
25
|
+
],
|
|
26
|
+
"author": "Agent Workforce",
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.3.3",
|
|
30
|
+
"vitest": "^1.2.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Clear CLI credentials for fresh testing
|
|
3
|
+
# Usage: ./clear-auth.sh <cli|all>
|
|
4
|
+
# Example: ./clear-auth.sh claude
|
|
5
|
+
# ./clear-auth.sh all
|
|
6
|
+
|
|
7
|
+
CLI=${1:-}
|
|
8
|
+
|
|
9
|
+
if [ -z "$CLI" ]; then
|
|
10
|
+
echo "Usage: ./clear-auth.sh <cli|all>"
|
|
11
|
+
echo ""
|
|
12
|
+
echo "Options:"
|
|
13
|
+
echo " claude - Clear Claude credentials"
|
|
14
|
+
echo " codex - Clear Codex credentials"
|
|
15
|
+
echo " gemini - Clear Gemini credentials"
|
|
16
|
+
echo " cursor - Clear Cursor credentials"
|
|
17
|
+
echo " opencode - Clear OpenCode credentials"
|
|
18
|
+
echo " droid - Clear Droid credentials"
|
|
19
|
+
echo " copilot - Clear GitHub Copilot credentials"
|
|
20
|
+
echo " all - Clear all credentials"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
clear_cli() {
|
|
25
|
+
local cli=$1
|
|
26
|
+
local dir=""
|
|
27
|
+
local files=()
|
|
28
|
+
|
|
29
|
+
case $cli in
|
|
30
|
+
claude)
|
|
31
|
+
dir="$HOME/.claude"
|
|
32
|
+
files=(".credentials.json" "settings.json" "settings.local.json")
|
|
33
|
+
;;
|
|
34
|
+
codex)
|
|
35
|
+
dir="$HOME/.codex"
|
|
36
|
+
files=("auth.json" "config.json" "config.toml")
|
|
37
|
+
;;
|
|
38
|
+
gemini)
|
|
39
|
+
dir="$HOME/.gemini"
|
|
40
|
+
files=("credentials.json" "settings.json")
|
|
41
|
+
# Also check gcloud location
|
|
42
|
+
if [ -f "$HOME/.config/gcloud/application_default_credentials.json" ]; then
|
|
43
|
+
echo " Removing: $HOME/.config/gcloud/application_default_credentials.json"
|
|
44
|
+
rm -f "$HOME/.config/gcloud/application_default_credentials.json"
|
|
45
|
+
fi
|
|
46
|
+
;;
|
|
47
|
+
cursor|agent)
|
|
48
|
+
# Cursor CLI installs as 'agent', credentials in ~/.cursor/
|
|
49
|
+
dir="$HOME/.cursor"
|
|
50
|
+
files=("auth.json" "settings.json")
|
|
51
|
+
;;
|
|
52
|
+
opencode)
|
|
53
|
+
dir="$HOME/.local/share/opencode"
|
|
54
|
+
files=("auth.json")
|
|
55
|
+
;;
|
|
56
|
+
droid)
|
|
57
|
+
dir="$HOME/.droid"
|
|
58
|
+
files=("auth.json")
|
|
59
|
+
;;
|
|
60
|
+
copilot)
|
|
61
|
+
# GitHub Copilot uses gh CLI auth - stored in ~/.config/gh/
|
|
62
|
+
dir="$HOME/.config/gh"
|
|
63
|
+
files=("hosts.yml" "config.yml")
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
echo "Unknown CLI: $cli"
|
|
67
|
+
return 1
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
|
|
71
|
+
echo "Clearing credentials for: $cli"
|
|
72
|
+
|
|
73
|
+
if [ -d "$dir" ]; then
|
|
74
|
+
for file in "${files[@]}"; do
|
|
75
|
+
if [ -f "$dir/$file" ]; then
|
|
76
|
+
echo " Removing: $dir/$file"
|
|
77
|
+
rm -f "$dir/$file"
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
echo " ✓ Done"
|
|
81
|
+
else
|
|
82
|
+
echo " (no config directory found)"
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
echo "========================================"
|
|
87
|
+
echo " Clearing CLI Credentials"
|
|
88
|
+
echo "========================================"
|
|
89
|
+
echo ""
|
|
90
|
+
|
|
91
|
+
if [ "$CLI" = "all" ]; then
|
|
92
|
+
for c in claude codex gemini cursor opencode droid copilot; do
|
|
93
|
+
clear_cli "$c"
|
|
94
|
+
echo ""
|
|
95
|
+
done
|
|
96
|
+
else
|
|
97
|
+
clear_cli "$CLI"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo ""
|
|
101
|
+
echo "Credentials cleared. Run test-cli.sh to test fresh authentication."
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Send a message to a CLI via relay-pty socket
|
|
3
|
+
# Usage: ./inject-message.sh <session-name> <message>
|
|
4
|
+
# Example: ./inject-message.sh test-claude "What is 2+2?"
|
|
5
|
+
|
|
6
|
+
NAME=${1:-test-claude}
|
|
7
|
+
MESSAGE=${2:-"Test message from inject script"}
|
|
8
|
+
SOCKET="/tmp/relay-pty-${NAME}.sock"
|
|
9
|
+
|
|
10
|
+
if [ ! -S "$SOCKET" ]; then
|
|
11
|
+
echo "Error: Socket not found: $SOCKET"
|
|
12
|
+
echo ""
|
|
13
|
+
echo "Make sure relay-pty is running with --name ${NAME}"
|
|
14
|
+
echo "Run: test-cli.sh ${NAME#test-}"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Generate unique message ID
|
|
19
|
+
MSG_ID="manual-$(date +%s)-$RANDOM"
|
|
20
|
+
|
|
21
|
+
echo "Sending message to $NAME..."
|
|
22
|
+
echo " Socket: $SOCKET"
|
|
23
|
+
echo " Message ID: $MSG_ID"
|
|
24
|
+
echo " Body: $MESSAGE"
|
|
25
|
+
echo ""
|
|
26
|
+
|
|
27
|
+
# Build JSON request
|
|
28
|
+
REQUEST=$(cat <<EOF
|
|
29
|
+
{"type":"inject","id":"$MSG_ID","from":"Tester","body":"$MESSAGE","priority":0}
|
|
30
|
+
EOF
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Send request and read response
|
|
34
|
+
# nc -U connects to Unix socket, -q 1 waits 1 second for response
|
|
35
|
+
echo "$REQUEST" | nc -U "$SOCKET" -q 2 | while read -r line; do
|
|
36
|
+
if [ -n "$line" ]; then
|
|
37
|
+
echo "Response: $line" | jq '.' 2>/dev/null || echo "Response: $line"
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
echo ""
|
|
42
|
+
echo "Message sent. Check the CLI session to see if it was delivered."
|