@vsuryav/agent-sim 0.1.1 → 0.1.2
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/package.json +1 -1
- package/src/collector/remote-sync.ts +125 -6
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'fs';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
authSessionCompleteResponseSchema,
|
|
7
7
|
authSessionResponseSchema,
|
|
8
8
|
pullResponseSchema,
|
|
9
9
|
type CollectedAgent,
|
|
@@ -21,6 +21,24 @@ interface Config {
|
|
|
21
21
|
token?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
type DeviceLoginStartResponse = {
|
|
25
|
+
mode: 'device';
|
|
26
|
+
client_id: string;
|
|
27
|
+
device_code: string;
|
|
28
|
+
user_code: string;
|
|
29
|
+
verification_uri: string;
|
|
30
|
+
verification_uri_complete?: string;
|
|
31
|
+
poll_interval_ms: number;
|
|
32
|
+
expires_in_ms: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type WebLoginStartResponse = {
|
|
36
|
+
mode: 'web';
|
|
37
|
+
url: string;
|
|
38
|
+
session_id: string;
|
|
39
|
+
poll_interval_ms: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
24
42
|
export function loadConfig(): Config {
|
|
25
43
|
try {
|
|
26
44
|
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) as Config;
|
|
@@ -88,6 +106,81 @@ async function pollForLoginSession(
|
|
|
88
106
|
throw new Error(`Login timed out. Re-run the ${APP_NAME} login command and finish GitHub auth within 5 minutes.`);
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
async function pollForGitHubDeviceAccessToken(
|
|
110
|
+
loginStart: DeviceLoginStartResponse,
|
|
111
|
+
): Promise<string> {
|
|
112
|
+
const deadline = Date.now() + loginStart.expires_in_ms;
|
|
113
|
+
let intervalMs = loginStart.poll_interval_ms;
|
|
114
|
+
|
|
115
|
+
while (Date.now() < deadline) {
|
|
116
|
+
const res = await fetch('https://github.com/login/oauth/access_token', {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: {
|
|
119
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
120
|
+
Accept: 'application/json',
|
|
121
|
+
},
|
|
122
|
+
body: new URLSearchParams({
|
|
123
|
+
client_id: loginStart.client_id,
|
|
124
|
+
device_code: loginStart.device_code,
|
|
125
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const body = await res.json() as {
|
|
130
|
+
access_token?: string;
|
|
131
|
+
interval?: number;
|
|
132
|
+
error?: string;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (body.access_token) {
|
|
136
|
+
return body.access_token;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (body.error === 'authorization_pending') {
|
|
140
|
+
await sleep(intervalMs);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (body.error === 'slow_down') {
|
|
145
|
+
intervalMs = body.interval ? body.interval * 1000 : intervalMs + 5000;
|
|
146
|
+
await sleep(intervalMs);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (body.error === 'access_denied') {
|
|
151
|
+
throw new Error('GitHub login was denied. Re-run the login command and try again.');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (body.error === 'expired_token') {
|
|
155
|
+
throw new Error('GitHub device login expired. Re-run the login command and try again.');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw new Error(`GitHub device login failed: ${body.error ?? res.statusText}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
throw new Error(`Login timed out. Re-run the ${APP_NAME} login command and finish GitHub auth within 5 minutes.`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isWebLoginStartResponse(value: unknown): value is WebLoginStartResponse {
|
|
165
|
+
if (!value || typeof value !== 'object') return false;
|
|
166
|
+
const record = value as Record<string, unknown>;
|
|
167
|
+
return typeof record.url === 'string'
|
|
168
|
+
&& typeof record.session_id === 'string'
|
|
169
|
+
&& typeof record.poll_interval_ms === 'number';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isDeviceLoginStartResponse(value: unknown): value is DeviceLoginStartResponse {
|
|
173
|
+
if (!value || typeof value !== 'object') return false;
|
|
174
|
+
const record = value as Record<string, unknown>;
|
|
175
|
+
return record.mode === 'device'
|
|
176
|
+
&& typeof record.client_id === 'string'
|
|
177
|
+
&& typeof record.device_code === 'string'
|
|
178
|
+
&& typeof record.user_code === 'string'
|
|
179
|
+
&& typeof record.verification_uri === 'string'
|
|
180
|
+
&& typeof record.poll_interval_ms === 'number'
|
|
181
|
+
&& typeof record.expires_in_ms === 'number';
|
|
182
|
+
}
|
|
183
|
+
|
|
91
184
|
export async function login(serverUrl: string): Promise<void> {
|
|
92
185
|
const config = loadConfig();
|
|
93
186
|
config.serverUrl = serverUrl;
|
|
@@ -99,13 +192,39 @@ export async function login(serverUrl: string): Promise<void> {
|
|
|
99
192
|
const error = body as { error?: string };
|
|
100
193
|
throw new Error(`Login failed: ${error.error ?? res.statusText}`);
|
|
101
194
|
}
|
|
102
|
-
const { url, session_id, poll_interval_ms } = authLoginStartResponseSchema.parse(body);
|
|
103
195
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
196
|
+
let session;
|
|
197
|
+
if (isDeviceLoginStartResponse(body)) {
|
|
198
|
+
console.log('\n Open GitHub device login:\n');
|
|
199
|
+
console.log(` ${body.verification_uri_complete ?? body.verification_uri}\n`);
|
|
200
|
+
if (!body.verification_uri_complete) {
|
|
201
|
+
console.log(` Enter this code: ${body.user_code}\n`);
|
|
202
|
+
}
|
|
203
|
+
console.log(' Waiting for GitHub authorization to complete...\n');
|
|
204
|
+
|
|
205
|
+
const githubAccessToken = await pollForGitHubDeviceAccessToken(body);
|
|
206
|
+
const exchangeRes = await fetch(`${serverUrl}/auth/exchange/github-token`, {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: {
|
|
209
|
+
'Content-Type': 'application/json',
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify({ access_token: githubAccessToken }),
|
|
212
|
+
});
|
|
213
|
+
const exchangeBody = await exchangeRes.json() as unknown;
|
|
214
|
+
if (!exchangeRes.ok) {
|
|
215
|
+
const error = exchangeBody as { error?: string };
|
|
216
|
+
throw new Error(`Login failed: ${error.error ?? exchangeRes.statusText}`);
|
|
217
|
+
}
|
|
218
|
+
session = authSessionCompleteResponseSchema.parse(exchangeBody);
|
|
219
|
+
} else if (isWebLoginStartResponse(body)) {
|
|
220
|
+
console.log(`\n Open this URL to login with GitHub:\n`);
|
|
221
|
+
console.log(` ${body.url}\n`);
|
|
222
|
+
console.log(' Waiting for GitHub authorization to complete...\n');
|
|
223
|
+
session = await pollForLoginSession(serverUrl, body.session_id, body.poll_interval_ms);
|
|
224
|
+
} else {
|
|
225
|
+
throw new Error('Login failed: server returned an unrecognized auth response');
|
|
226
|
+
}
|
|
107
227
|
|
|
108
|
-
const session = await pollForLoginSession(serverUrl, session_id, poll_interval_ms);
|
|
109
228
|
config.token = session.token;
|
|
110
229
|
saveConfig(config);
|
|
111
230
|
console.log(` Logged in as ${session.githubLogin}. Token saved.`);
|