diff --git a/src/renderer/src/components/settings/ZcodeCliSetupSection.test.tsx b/src/renderer/src/components/settings/ZcodeCliSetupSection.test.tsx
new file mode 100644
index 00000000000..b8f37d86883
--- /dev/null
+++ b/src/renderer/src/components/settings/ZcodeCliSetupSection.test.tsx
@@ -0,0 +1,74 @@
+// @vitest-environment happy-dom
+
+import '@testing-library/jest-dom/vitest'
+
+import { cleanup, fireEvent, render, screen } from '@testing-library/react'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { ZcodeCliSetupSection } from './ZcodeCliSetupSection'
+
+const mocks = vi.hoisted(() => ({ settings: { agentCmdOverrides: { zcode: '' } } }))
+vi.mock('@/store', () => ({
+ useAppStore: (selector: (state: unknown) => unknown) => selector(mocks)
+}))
+vi.mock('../onboarding/OnboardingInlineCommandTerminal', () => ({
+ OnboardingInlineCommandTerminal: ({
+ command,
+ description
+ }: {
+ command: string
+ description: string
+ }) => (
+
+
{command}
+
{description}
+
+ )
+}))
+vi.mock('./SearchableSetting', () => ({
+ SearchableSetting: ({ children }: { children: React.ReactNode }) => {children}
+}))
+
+beforeEach(() => {
+ mocks.settings.agentCmdOverrides.zcode = ''
+})
+afterEach(() => cleanup())
+
+describe('ZCode CLI-owned sign-in setup', () => {
+ it('starts no terminal until the user opens a supported flow', () => {
+ render( )
+ expect(screen.queryByTestId('setup-terminal')).not.toBeInTheDocument()
+ expect(screen.getByText(/ZCode’s masked API-key setup/)).toBeInTheDocument()
+ expect(screen.getByText(/use \/model to choose a GLM model/)).toBeInTheDocument()
+ })
+
+ it.each([
+ ['Z.AI browser sign-in', 'zcode login zai --no-browser'],
+ ['BigModel browser sign-in', 'zcode login bigmodel --no-browser'],
+ ['ZCode API-key setup', 'zcode']
+ ])('delegates %s to the supported upstream CLI command', async (label, command) => {
+ render( )
+ fireEvent.click(screen.getByRole('button', { name: label }))
+ expect(await screen.findByTestId('setup-terminal')).toHaveTextContent(command)
+ expect(screen.getByRole('button', { name: 'Z.AI browser sign-in' })).toBeDisabled()
+ expect(screen.getByRole('button', { name: 'BigModel browser sign-in' })).toBeDisabled()
+ expect(screen.getByRole('button', { name: 'ZCode API-key setup' })).toBeDisabled()
+ if (label === 'ZCode API-key setup') {
+ expect(screen.getByText(/enter \/login.*masked prompt/)).toBeInTheDocument()
+ } else {
+ expect(screen.getByText(/authorization URL printed by ZCode/)).toBeInTheDocument()
+ }
+ fireEvent.click(screen.getByRole('button', { name: 'Close setup terminal' }))
+ expect(screen.queryByTestId('setup-terminal')).not.toBeInTheDocument()
+ expect(screen.getByRole('button', { name: label })).toBeEnabled()
+ })
+
+ it('preserves the configured CLI executable instead of inventing model flags', async () => {
+ mocks.settings.agentCmdOverrides.zcode = 'node "C:\\ZCode CLI\\zcode.cjs"'
+ render( )
+ fireEvent.click(screen.getByRole('button', { name: 'BigModel browser sign-in' }))
+ expect(await screen.findByTestId('setup-terminal')).toHaveTextContent(
+ 'node "C:\\ZCode CLI\\zcode.cjs" login bigmodel --no-browser'
+ )
+ expect(screen.getByTestId('setup-terminal')).not.toHaveTextContent('--model')
+ })
+})
diff --git a/src/renderer/src/components/settings/ZcodeCliSetupSection.tsx b/src/renderer/src/components/settings/ZcodeCliSetupSection.tsx
new file mode 100644
index 00000000000..922c926380d
--- /dev/null
+++ b/src/renderer/src/components/settings/ZcodeCliSetupSection.tsx
@@ -0,0 +1,122 @@
+import { lazy, Suspense, useState } from 'react'
+import { translate } from '@/i18n/i18n'
+import { useAppStore } from '@/store'
+import { Button } from '../ui/button'
+import { SearchableSetting } from './SearchableSetting'
+
+const OnboardingInlineCommandTerminal = lazy(() =>
+ import('../onboarding/OnboardingInlineCommandTerminal').then((module) => ({
+ default: module.OnboardingInlineCommandTerminal
+ }))
+)
+
+type SetupChoice = 'zai' | 'bigmodel' | 'api-key'
+
+export function ZcodeCliSetupSection(): React.JSX.Element {
+ const commandOverride = useAppStore((s) => s.settings?.agentCmdOverrides?.zcode)
+ const [choice, setChoice] = useState(null)
+ const executable = commandOverride?.trim() || 'zcode'
+ const command = choice === 'api-key' ? executable : `${executable} login ${choice} --no-browser`
+
+ const title = translate(
+ 'auto.components.settings.ZcodeCliSetupSection.title',
+ 'Use GLM with ZCode'
+ )
+ const description = translate(
+ 'auto.components.settings.ZcodeCliSetupSection.description',
+ 'Sign in to use GLM as a coding agent. The quota API key above only monitors usage; ZCode owns its own sign-in and model selection.'
+ )
+
+ return (
+
+ {title}
+ {description}
+
+ {translate(
+ 'auto.components.settings.ZcodeCliSetupSection.help',
+ 'Choose browser sign-in for your plan site, or use ZCode’s masked API-key setup. After signing in, start ZCode and use /model to choose a GLM model.'
+ )}
+
+
+ setChoice('zai')}
+ >
+ {translate('auto.components.settings.ZcodeCliSetupSection.zai', 'Z.AI browser sign-in')}
+
+ setChoice('bigmodel')}
+ >
+ {translate(
+ 'auto.components.settings.ZcodeCliSetupSection.bigmodel',
+ 'BigModel browser sign-in'
+ )}
+
+ setChoice('api-key')}
+ >
+ {translate('auto.components.settings.ZcodeCliSetupSection.apiKey', 'ZCode API-key setup')}
+
+ {choice ? (
+ setChoice(null)}>
+ {translate(
+ 'auto.components.settings.ZcodeCliSetupSection.close',
+ 'Close setup terminal'
+ )}
+
+ ) : null}
+
+ {choice ? (
+
+ {translate(
+ 'auto.components.settings.ZcodeCliSetupSection.loading',
+ 'Loading sign-in terminal…'
+ )}
+
+ }
+ >
+
+
+ ) : null}
+
+ )
+}
diff --git a/src/renderer/src/components/settings/ZcodePlanAccountsSection.tsx b/src/renderer/src/components/settings/ZcodePlanAccountsSection.tsx
index 973396b9f88..c49f383f32c 100644
--- a/src/renderer/src/components/settings/ZcodePlanAccountsSection.tsx
+++ b/src/renderer/src/components/settings/ZcodePlanAccountsSection.tsx
@@ -18,6 +18,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '.
import { SearchableSetting } from './SearchableSetting'
import { collectZcodeUsageWindows, ZcodeUsageWindowView } from './zcode-plan-usage-windows'
import { useZcodePlanCredentials } from './use-zcode-plan-credentials'
+import { ZcodeCliSetupSection } from './ZcodeCliSetupSection'
import { UnsealedCredentialNotice } from './UnsealedCredentialNotice'
const SEARCH_KEYWORDS = [
@@ -330,6 +331,8 @@ export function ZcodePlanAccountsSection(): React.JSX.Element {
+ {credentialEditable ? : null}
+
{usageWindows.length > 0 ? (
{
+ await waitForSessionReady(orcaPage)
+ await orcaPage.evaluate(() => {
+ const state = window.__store?.getState()
+ state?.openSettingsTarget({ pane: 'accounts', repoId: null, sectionId: 'accounts-zcode' })
+ state?.openSettingsPage()
+ })
+ const section = orcaPage.locator('#accounts-zcode')
+ await expect(section).toBeVisible({ timeout: 30_000 })
+ await section.screenshot({ path: testInfo.outputPath('01-setup-buttons.png') })
+ for (const [label, command] of [
+ ['Z.AI browser sign-in', 'zcode login zai --no-browser'],
+ ['BigModel browser sign-in', 'zcode login bigmodel --no-browser'],
+ ['ZCode API-key setup', 'zcode']
+ ] as const) {
+ await section.getByRole('button', { name: label, exact: true }).click()
+ const close = section.getByRole('button', { name: 'Close setup terminal', exact: true })
+ await expect(close).toBeVisible({ timeout: 15_000 })
+ const tabId = await section
+ .locator('[data-terminal-tab-id]')
+ .first()
+ .getAttribute('data-terminal-tab-id')
+ if (!tabId) {
+ throw new Error('Setup terminal did not mount')
+ }
+ await expectTerminalAccessibilityText(orcaPage, tabId, command)
+ await expect(section.getByRole('button', { name: label, exact: true })).toBeDisabled()
+ await section.screenshot({ path: testInfo.outputPath(`${label}.png`) })
+ await close.click()
+ await expect(close).toHaveCount(0)
+ await expect(section.locator('[data-terminal-tab-id]')).toHaveCount(0)
+ await expect(section.getByRole('button', { name: label, exact: true })).toBeEnabled()
+ }
+})