diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index 126de0b..64283b9 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -453,6 +453,7 @@ export const en = { 'ai.dirty': 'Uncommitted files in the app folder ({count}): commit or stash them first, so Undo can restore exactly what the AI changes.', 'ai.noGit': 'The app folder is not in a git repository: Fix with AI needs git for the diff and Undo.', 'ai.cancelled': 'Cancelled', + 'ai.signedOut': 'Claude Code is not signed in: run claude once in a terminal and sign in with /login, or copy the prompt for your AI agent', 'ai.timedOut': 'Stopped after 10 minutes', 'ai.noChanges': 'No files changed', 'ai.changed': 'Changed files: {count}', diff --git a/apps/desktop/src/i18n/tr.ts b/apps/desktop/src/i18n/tr.ts index 9a27102..fd814f4 100644 --- a/apps/desktop/src/i18n/tr.ts +++ b/apps/desktop/src/i18n/tr.ts @@ -455,6 +455,7 @@ export const tr: Record = { 'ai.dirty': 'Uygulama klasöründe commit edilmemiş {count} dosya var: önce commit et ya da stash yap ki Geri al tam olarak yapay zekânın değiştirdiğini geri yüklesin.', 'ai.noGit': 'Uygulama klasörü bir git deposunda değil: Yapay zekâyla düzelt fark ve Geri al için git ister.', 'ai.cancelled': 'İptal edildi', + 'ai.signedOut': 'Claude Code oturum açmamış: bir terminalde claude çalıştırıp /login ile giriş yapın ya da istemi yapay zekâ ajanınız için kopyalayın', 'ai.timedOut': '10 dakika sonra durduruldu', 'ai.noChanges': 'Hiçbir dosya değişmedi', 'ai.changed': 'Değişen dosya: {count}', diff --git a/apps/desktop/src/ui/AiFix.tsx b/apps/desktop/src/ui/AiFix.tsx index d0c85d4..b864dcd 100644 --- a/apps/desktop/src/ui/AiFix.tsx +++ b/apps/desktop/src/ui/AiFix.tsx @@ -90,7 +90,9 @@ export function AiFix({ projectId, f, claude, busy, setBusy, rescan, stillThere, ); else if (p.kind === 'done') { const o = p.run.outcome; - const head = o.cancelled ? t('ai.cancelled') : o.timed_out ? t('ai.timedOut') : !p.run.changes.length ? t('ai.noChanges') : t('ai.changed', { count: p.run.changes.length }); + // a claude CLI that is not signed in answers at once ("Not logged in · Please run /login"): say so, offer the copy + const signedOut = !p.run.changes.length && signedOutText(o.summary); + const head = o.cancelled ? t('ai.cancelled') : o.timed_out ? t('ai.timedOut') : signedOut ? t('ai.signedOut') : !p.run.changes.length ? t('ai.noChanges') : t('ai.changed', { count: p.run.changes.length }); panel = (
{head}{p.run.changes.length > 0 && !p.undone ? ` · ${stillThere ? t('ai.stillThere') : t('ai.gone')}` : ''} @@ -100,6 +102,7 @@ export function AiFix({ projectId, f, claude, busy, setBusy, rescan, stillThere,
{p.run.changes.length > 0 && } {p.run.changes.length > 0 && } + {signedOut && } {!p.run.changes.length && }
)} @@ -113,3 +116,6 @@ export function AiFix({ projectId, f, claude, busy, setBusy, rescan, stillThere, ); return <>{p.kind === 'idle' || p.kind === 'running' ?
{button}
: null}{panel}; } + +/** The claude CLI's answer when it has no sign-in (it exits at once, nothing changed). */ +export const signedOutText = (s: string): boolean => /not logged in|please run \/login|invalid api key|authentication/i.test(s); diff --git a/apps/desktop/tests/ai-signed-out.test.ts b/apps/desktop/tests/ai-signed-out.test.ts new file mode 100644 index 0000000..af37a3a --- /dev/null +++ b/apps/desktop/tests/ai-signed-out.test.ts @@ -0,0 +1,10 @@ +// The claude CLI without a sign-in exits at once with this result text (seen with a real run): the AI fix panel says +// "not signed in" and offers the copy instead of a bare failure. +import { expect, test } from 'bun:test'; +import { signedOutText } from '../src/ui/AiFix'; + +test('signed-out claude answers are recognised', () => { + expect(signedOutText('Not logged in · Please run /login')).toBe(true); + expect(signedOutText('Invalid API key · Fix external API key')).toBe(true); + expect(signedOutText('Removed NSAllowsArbitraryLoads; no http URLs are loaded.')).toBe(false); +});