-
Notifications
You must be signed in to change notification settings - Fork 34
feat: support polling straight from auth login #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,8 +45,7 @@ export function createAuthCli( | |
| }); | ||
| } | ||
|
|
||
| // Agent mode: initiate device auth, store pending state, return immediately. | ||
| // The agent drives the polling loop via `auth status --interval`. | ||
| // Agent mode: initiate device auth, store pending state, yield code immediately. | ||
| const authRequest = await authResource.initiateDeviceAuth(clientName); | ||
| storage.setPendingDeviceAuth({ | ||
| device_code: authRequest.device_code, | ||
|
|
@@ -55,17 +54,83 @@ export function createAuthCli( | |
| verification_url: authRequest.verification_url_complete, | ||
| phrase: authRequest.user_code, | ||
| }); | ||
|
|
||
| const interval = c.options.interval; | ||
| const maxAttempts = c.options.maxAttempts; | ||
|
|
||
| if (interval <= 0) { | ||
| // No polling requested: return code with _next hint (original behavior). | ||
| yield { | ||
| verification_url: authRequest.verification_url_complete, | ||
| phrase: authRequest.user_code, | ||
| instruction: | ||
| 'Present the verification_url to the user and ask them to approve in the Link app. Then call `auth status --interval 5 --max-attempts 60` to poll until authenticated. Do not wait for the user to reply — start polling immediately.', | ||
| _next: { | ||
| command: 'auth status --interval 5 --max-attempts 60', | ||
| poll_interval_seconds: authRequest.interval, | ||
| until: 'authenticated is true', | ||
| }, | ||
| }; | ||
| return; | ||
| } | ||
|
|
||
| // Inline polling: emit code to stderr (visible immediately even while | ||
| // stdout is buffered), then yield it as structured output for MCP streaming. | ||
| process.stderr.write( | ||
| `\nVerification URL: ${authRequest.verification_url_complete}\nPhrase: ${authRequest.user_code}\n\nOpen the URL, log in to Link, and enter the phrase to approve.\nPolling for approval...\n\n`, | ||
| ); | ||
| yield { | ||
| verification_url: authRequest.verification_url_complete, | ||
| phrase: authRequest.user_code, | ||
| instruction: | ||
| 'Present the verification_url to the user and ask them to approve in the Link app. Then call `auth status --interval 5 --max-attempts 60` to poll until authenticated. Do not wait for the user to reply — start polling immediately.', | ||
| _next: { | ||
| command: 'auth status --interval 5 --max-attempts 60', | ||
| poll_interval_seconds: authRequest.interval, | ||
| until: 'authenticated is true', | ||
| }, | ||
| 'Present the verification_url to the user and ask them to approve in the Link app. Polling has started automatically — no further action needed.', | ||
| }; | ||
|
|
||
| const deadline = Date.now() + c.options.timeout * 1000; | ||
| let attempts = 0; | ||
|
|
||
| while (true) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this code repeated in the other auth polling command? |
||
| await new Promise((resolve) => setTimeout(resolve, interval * 1000)); | ||
|
|
||
| const pending = storage.getPendingDeviceAuth(); | ||
| if (!pending) { | ||
| return c.error({ | ||
| code: 'AUTH_EXPIRED', | ||
| message: | ||
| 'Device authorization expired. Please run auth login again.', | ||
| }); | ||
| } | ||
|
|
||
| try { | ||
| const tokens = await authResource.pollDeviceAuth(pending.device_code); | ||
| if (tokens) { | ||
| storage.setAuth(tokens); | ||
| storage.clearPendingDeviceAuth(); | ||
| yield { | ||
| authenticated: true, | ||
| token_type: tokens.token_type, | ||
| credentials_path: storage.getPath(), | ||
| }; | ||
| return; | ||
| } | ||
| } catch (err: unknown) { | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| return c.error({ code: 'AUTH_FAILED', message }); | ||
| } | ||
|
|
||
| attempts++; | ||
| const shouldStop = | ||
| (maxAttempts > 0 && attempts >= maxAttempts) || | ||
| Date.now() >= deadline; | ||
|
|
||
| if (shouldStop) { | ||
| return c.error({ | ||
| code: 'POLLING_TIMEOUT', | ||
| message: | ||
| 'Timed out waiting for user approval. The verification code may have expired — run auth login again to get a new one.', | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,11 +90,13 @@ If the response includes an `update` field, a newer version of `link-cli` is ava | |
| If not authenticated: | ||
|
|
||
| ```bash | ||
| link-cli auth login --client-name "<your-agent-name>" | ||
| link-cli auth login --client-name "<your-agent-name>" --interval 5 --timeout 300 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should make this the default behavior. Agents seem good with the |
||
| ``` | ||
|
|
||
| Replace `<your-agent-name>` with the name of your agent or application (for example, `"Personal Assistant"`, `"Shopping Bot"`). This name appears in the user's Link app when they approve the connection. Use a clear, unique, identifiable name. | ||
|
|
||
| With `--interval 5 --timeout 300`, the command yields the verification code immediately (present it to the user right away), then polls inline until authenticated or timed out. No separate `auth status` call is needed. | ||
|
|
||
| DO NOT PROCEED until the user is authenticated with Link. | ||
|
|
||
| Always check the current authentication status before starting a new login flow — the user might already be logged in. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be json output/respect the output format