mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
fix: keep login fallback prompt visible (#594)
This commit is contained in:
parent
0abde8662a
commit
f2863af267
3 changed files with 58 additions and 9 deletions
5
.changeset/login-browser-fallback.md
Normal file
5
.changeset/login-browser-fallback.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Fix device login to keep the URL and code visible when the browser cannot be opened.
|
||||
|
|
@ -17,18 +17,17 @@ export async function runLoginFlow(): Promise<never> {
|
|||
uiMode: 'cli',
|
||||
});
|
||||
const controller = new AbortController();
|
||||
process.once('SIGINT', () => controller.abort());
|
||||
process.once('SIGINT', () => {
|
||||
controller.abort();
|
||||
});
|
||||
try {
|
||||
const result = await harness.auth.login(undefined, {
|
||||
signal: controller.signal,
|
||||
onDeviceCode: (data) => {
|
||||
const url = data.verificationUriComplete || data.verificationUri;
|
||||
// Best-effort: try to open the user's default browser at the
|
||||
// pre-baked URL (which already embeds the user code). Print the
|
||||
// URL + code as a fallback for headless boxes / when openUrl
|
||||
// silently fails (it `execFile`s `open`/`xdg-open`/`cmd start`
|
||||
// with no error handling — see `utils/open-url.ts`).
|
||||
openUrl(url);
|
||||
// Print the manual fallback before attempting to open the user's
|
||||
// browser so headless/browser-opener failures never hide the URL
|
||||
// and code needed to complete login.
|
||||
process.stderr.write(
|
||||
[
|
||||
'',
|
||||
|
|
@ -43,15 +42,20 @@ export async function runLoginFlow(): Promise<never> {
|
|||
.filter((line): line is string => line !== undefined)
|
||||
.join('\n'),
|
||||
);
|
||||
try {
|
||||
openUrl(url);
|
||||
} catch {
|
||||
// Best effort only: the manual fallback has already been printed.
|
||||
}
|
||||
},
|
||||
});
|
||||
process.stderr.write(`Logged in to ${result.providerName}.\n`);
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) {
|
||||
process.stderr.write('Login cancelled.\n');
|
||||
} else {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
process.stderr.write(`Login failed: ${message}\n`);
|
||||
}
|
||||
process.exit(1);
|
||||
|
|
|
|||
|
|
@ -122,6 +122,46 @@ describe('kimi login', () => {
|
|||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it('still prints device code prompt when opening the browser fails', async () => {
|
||||
vi.mocked(openUrl).mockImplementation(() => {
|
||||
throw new Error('no browser');
|
||||
});
|
||||
mockLogin.mockImplementation(
|
||||
async (
|
||||
_providerName: string | undefined,
|
||||
options: {
|
||||
onDeviceCode?: (data: {
|
||||
userCode: string;
|
||||
verificationUri: string;
|
||||
verificationUriComplete: string;
|
||||
expiresIn: number | null;
|
||||
}) => void | Promise<void>;
|
||||
},
|
||||
) => {
|
||||
await options.onDeviceCode?.({
|
||||
userCode: 'ABCD-EFGH',
|
||||
verificationUri: 'https://example.com/v',
|
||||
verificationUriComplete: 'https://example.com/v?code=ABCD-EFGH',
|
||||
expiresIn: 600,
|
||||
});
|
||||
return { providerName: 'kimi-code', ok: true };
|
||||
},
|
||||
);
|
||||
|
||||
const program = new Command('kimi').exitOverride();
|
||||
registerLoginCommand(program);
|
||||
|
||||
await expect(program.parseAsync(['node', 'kimi', 'login'])).rejects.toThrow(ExitCalled);
|
||||
|
||||
const writtenChunks = stderrSpy.mock.calls.map((call: unknown[]) => String(call[0]));
|
||||
expect(writtenChunks.some((chunk: string) => chunk.includes('ABCD-EFGH'))).toBe(true);
|
||||
expect(writtenChunks.some((chunk: string) => chunk.includes('https://example.com/v'))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(openUrl).toHaveBeenCalledWith('https://example.com/v?code=ABCD-EFGH');
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it('exits 1 when auth.login throws', async () => {
|
||||
mockLogin.mockRejectedValue(new Error('boom'));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue