(options: {
stateDir: string;
flags: CliFlags;
env?: EnvMap;
io?: AuthIo;
commandLabel?: string;
})
| 195 | } |
| 196 | |
| 197 | export async function loginWithDeviceAuth(options: { |
| 198 | stateDir: string; |
| 199 | flags: CliFlags; |
| 200 | env?: EnvMap; |
| 201 | io?: AuthIo; |
| 202 | commandLabel?: string; |
| 203 | }): Promise<{ |
| 204 | accessToken: string; |
| 205 | expiresAt?: string; |
| 206 | session: CliSessionRecord; |
| 207 | }> { |
| 208 | const env = options.env ?? options.io?.env ?? process.env; |
| 209 | const authMode = detectAuthMode(env, options.io); |
| 210 | if (authMode === 'non-interactive') { |
| 211 | throw buildNonInteractiveLoginError(options.commandLabel ?? 'agent-device connect', env); |
| 212 | } |
| 213 | const cloudBaseUrl = resolveCloudBaseUrl(env); |
| 214 | const start = await postJson<DeviceAuthStartResponse>({ |
| 215 | baseUrl: cloudBaseUrl, |
| 216 | pathName: DEVICE_AUTH_START_PATH, |
| 217 | body: { |
| 218 | client: 'agent-device', |
| 219 | tenant: options.flags.tenant, |
| 220 | runId: options.flags.runId, |
| 221 | daemonBaseUrl: options.flags.daemonBaseUrl, |
| 222 | session: options.flags.session, |
| 223 | }, |
| 224 | fetchImpl: options.io?.fetch, |
| 225 | }); |
| 226 | assertDeviceAuthStart(start); |
| 227 | |
| 228 | const verificationUrl = start.verificationUriComplete ?? start.verificationUri; |
| 229 | const printableUrl = |
| 230 | authMode === 'local-browser' |
| 231 | ? start.verificationUri |
| 232 | : appendUserCode(start.verificationUri, start.userCode); |
| 233 | if (authMode === 'local-browser') { |
| 234 | writeStderr(options.io, `Opening ${start.verificationUri}...\n`); |
| 235 | await openBrowser(verificationUrl, options.io); |
| 236 | } else { |
| 237 | writeStderr( |
| 238 | options.io, |
| 239 | `Open this URL on your machine:\n${printableUrl}\n\nWaiting for approval for 10 minutes...\n`, |
| 240 | ); |
| 241 | } |
| 242 | |
| 243 | const approved = await pollDeviceAuth({ |
| 244 | cloudBaseUrl, |
| 245 | deviceCode: start.deviceCode, |
| 246 | expiresIn: start.expiresIn, |
| 247 | interval: start.interval, |
| 248 | fetchImpl: options.io?.fetch, |
| 249 | now: options.io?.now, |
| 250 | }); |
| 251 | const refreshCredential = |
| 252 | approved.cliSession?.refreshCredential ?? approved.cliSession?.refreshToken; |
| 253 | if (!hasToken(approved.accessToken) || !hasToken(refreshCredential)) { |
| 254 | throw new AppError('UNAUTHORIZED', 'Device authorization did not return CLI credentials.'); |
no test coverage detected