# zidane-ui/onboarding/headless

> The setup wizard without components: the OnboardingHost contract, the run controller and the strings of the wizard.

Import: `import { … } from 'zidane-ui/onboarding/headless'`. Source: [ui/src/onboarding/headless/index.ts](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/index.ts).

Use this entry point to implement a host for the setup wizard, or to run the wizard without React. `OnboardingHost` holds what the wizard reads and the actions that it asks for. It reads what zidane found, the providers, the themes and the app icons. It asks to sign in, save a key, import, add a connector, install an extension and write the result. If you leave out a member, the wizard hides its step or its control. `createOnboardingController` connects a host to the core's run (`createOnboardingRun` in `zidane/chat/pure`). When the user passes a step, it starts the imports and installs that the user picked there. When the user passes the last step, it saves the result through `host.commit`. `createFixtureOnboardingHost` ([`zidane-ui/onboarding/testing`](https://zidane.si/docs/reference/ui-onboarding-testing.md)) answers every member from demo data, for tests, galleries and docs.

```ts
import { createOnboardingController } from 'zidane-ui/onboarding/headless'
import { createFixtureOnboardingHost } from 'zidane-ui/onboarding/testing'

const host = createFixtureOnboardingHost({ delayMs: 0, onCommit: result => console.log(result.settings) })
const run = createOnboardingController(host, host.context.get()!, { start: 'features' })
run.dispatch({ type: 'set-settings', settings: { spaces: true } })
run.dispatch({ type: 'next' }) // Ready
run.dispatch({ type: 'next' }) // saves { set: { spaces: true }, reset: [] }
```

## Functions

### `connectorsFromImport`

Function.

The connectors that an import brings over: the MCP servers that it copies
and that have the id of a connector. The Connectors step marks them, and the
run does not add them twice.

```ts
function connectorsFromImport(
  preview: OnboardingImportPreviewState,
  selection: OnboardingImportSelection | undefined,
): readonly string[]
```

**Parameters**

- `preview` (`OnboardingImportPreviewState`): What the import brings over.
- `selection` (`OnboardingImportSelection | undefined`): What the user picked to import.

**Returns** `readonly string[]`

**Example**

```ts
import { connectorsFromImport } from 'zidane/chat/pure'

const none = { copy: [], skip: [] }
const preview = { sessions: { project: 0, all: 0 }, mcps: { copy: ['github', 'my-db'], skip: [] }, skills: none, commands: none, agents: none, notes: [] }
const selection = { sessions: false, allProjects: false, mcps: true, skills: false, commands: false, agents: false }
connectorsFromImport({ kind: 'ready', preview }, selection) // ['github']
```

Source: [dist/media-type-BzRZ4FqT.d.ts:10891](https://github.com/Tahul/zidane/blob/main/dist/media-type-BzRZ4FqT.d.ts#L10891)

### `createOnboardingController`

Function.

Starts a run over a host.

```ts
function createOnboardingController(
  host: OnboardingHost,
  setup: OnboardingSetup,
  options?: OnboardingRunOptions,
): OnboardingRun
```

**Parameters**

- `host` (`OnboardingHost`): The app's host.
- `setup` (`OnboardingSetup`): What the run starts from: `host.context`'s value once detection finished.
- `options?` (`OnboardingRunOptions`, default `{}`): The step to open at, and the clock.

**Returns** `OnboardingRun`: The controller. Call `dispose` when the wizard closes.

Source: [ui/src/onboarding/headless/controller.ts:95](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/controller.ts#L95)

### `onboardingPlanContext`

Function.

What decides the steps of a run on this host: the setup's mode and surface,
and the steps whose host member is missing left out.

```ts
function onboardingPlanContext(
  host: OnboardingHost,
  setup: OnboardingSetup,
): OnboardingPlanContext
```

**Parameters**

- `host` (`OnboardingHost`): The host.
- `setup` (`OnboardingSetup`): What the run starts from.

**Returns** `OnboardingPlanContext`

Source: [ui/src/onboarding/headless/controller.ts:36](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/controller.ts#L36)

## Constants

### `DEFAULT_ONBOARDING_LABELS`

Constant.

The default English strings of the setup wizard, merged over the design system's labels.

```ts
const DEFAULT_ONBOARDING_LABELS: { accent: string; accentTheme: string; addDiagnosticToChat: (message: string) => string; added: string; address: string; addressPlaceholder: string; addsConnector: (connector: string) => string; addToChat: string; agentName: (agent: OnboardingAgentId) => string; allowOnce: string; allSources: string; alwaysAllow: string; apiKeyFor: (provider: string) => string; appIcon: string; approvalAsk: (command: string) => string; approvedByModel: string; askedWhenUnsure: string; back: string; background: string; browse: string; browseFailed: (message: string) => string; browsing: (source: string) => string; cancel: string; change: string; changeLater: string; changeStep: (step: string) => string; clearPicks: string; close: string; closeTab: (name: string) => string; code: string; completionSuggestions: string; configure: string; confirm: string; connected: string; connectFirst: string; connectProviderAction: string; connectProviderBody: string; connectProviderTitle: string; continue: string; copied: string; copy: string; copyCode: string; copyImage: string; copyImageBase64: string; copyImageDataUrl: string; createToken: string; credentialsAt: (path: string) => string; current: string; custom: string; darkTheme: string; demoAddress: string; demoAllowed: string; demoApproved: string; demoAsksYou: string; demoBlocked: string; demoBranch: string; demoCalls: string; demoCommitCommand: string; demoCommitMessage: string; demoEstimateSession: string; demoEstimateTask: string; demoFlowCalls: readonly ['read_file src/app.ts', 'edit src/limits.ts', 'bun test', 'git commit']; demoFolders: readonly ['my-app', '.zidane', 'extensions', 'deploy.ts']; demoGenericTitle: string; demoGhost: string; demoGoal: string; demoIssue: string; demoIssuePrompt: string; demoMessage: string; demoMessageCalls: readonly ['read app.ts', 'edit api.ts']; demoNotifyApp: string; demoNotifyBody: string; demoPageButton: string; demoPersonalSessions: readonly ['Plan the trip to Lisbon', 'Sort the tax receipts']; demoProjectPath: string; demoPromptGhost: string; demoPromptStart: string; demoQueued: string; demoReminderFrom: string; demoReminderIn: string; demoReminderNow: string; demoReminderText: string; demoReminderTyped: string; demoRiskyCall: string; demoRunEnded: string; demoScript: readonly ['const files = await glob(\'src/**/*.ts\')', 'for (const file of files) await read(file)']; demoSearchQuery: string; demoSearchResults: readonly ['vite.dev/guide/migration', 'github.com/vitejs/vite/releases', 'vite.dev/blog/announcing-vite6']; demoSecretDots: string; demoSecretPath: string; demoSecretPrompt: string; demoSecretSave: string; demoSecretSaved: string; demoSpacePersonal: string; demoSpaceWork: string; demoSteeredCall: string; demoStep: string; demoTab: string; demoTerminalError: string; demoTerminalLines: readonly ['$ bun dev', 'ready on localhost:5173', 'GET /chat 200 in 12 ms']; demoTitle: string; demoTyped: string; demoUpdateReady: string; demoUsageLabel: string; demoUsagePercent: string; demoVersion: string; demoWorkSessions: readonly ['Fix the login redirect', 'Review the rate limit']; deny: string; detecting: string; diagnosticsCheckedBy: (source: string) => string; diagnosticsChecking: string; diagnosticsNone: string; done: string; editorConflict: string; editorConflictDetail: string; editorEmpty: string; editorOverwrite: string; editorReadFailed: (message: string) => string; editorReload: string; effort: string; effortHint: string; effortLabel: (level: NonNullable<'off' | 'max' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'adaptive' | undefined>) => string; errorFailed: (region: string) => string; errorStack: string; errorTryAgain: string; experimental: string; filePath: (path: string) => string; fileReadOnly: (path: string) => string; fileTree: string; fileUnsaved: string; fileUnsavedTitle: (path: string) => string; filterSkills: string; findClose: string; findCloseHint: string; findInFile: string; findMatchCase: string; findNext: string; findNextHint: string; findNoResults: string; findPrevious: string; findPreviousHint: string; findReferences: string; found: string; foundAgent: (agent: OnboardingAgentId) => string; foundEnvKey: (envKey: string) => string; foundGithub: (login: string | undefined, source: string) => string; foundLocalServer: (server: OnboardingLocalServer) => string; foundSaved: (provider: string) => string; foundSignedIn: (provider: string) => string; fromImport: (agent: string) => string; fromRepo: (repo: string) => string; fullSetup: string; fullSetupDetail: string; fullSetupTime: string; goToDefinition: string; goToStep: (step: string) => string; importAgents: string; importAllProjects: string; importCommands: string; importFrom: (agent: string) => string; importMcps: string; importNames: (list: readonly string[]) => string; importNothing: string; importSessions: string; importSessionsDetail: (project: number, all: number) => string; importSkills: string; importSkipped: (count: number) => string; installed: string; installing: string; installProvider: (provider: string) => string; isDefault: (name: string) => string; lightTheme: string; listingTruncated: string; loading: string; localFound: (server: OnboardingLocalServer) => string; matchesPreset: (preset: string) => string; messagesDuringRun: string; messagesDuringRunHint: string; modalBack: string; modalFailed: string; mode: string; modeDark: string; model: string; modeLight: string; modeSystem: string; moreOptions: string; moreProviders: string; needs: (list: readonly string[]) => string; needsAccount: (account: string) => string; needsCommand: (name: string, optional: boolean) => string; needsConnector: (connector: string) => string; needsDecisions: string; next: string; noAccount: string; noGhLogin: string; noReferences: string; noSkillMatch: string; noSkills: string; noSkillsHint: string; notNow: string; of: (index: number, count: number) => string; openFiles: string; openInNewTab: string; pack: string; packHint: string; paletteBack: string; paletteBackHint: string; paletteChoose: string; paletteEmpty: string; paletteFilter: string; paletteLabel: string; paletteLoading: string; paletteNoMatch: (query: string) => string; paletteOpen: string; paletteRun: string; paletteScopeHint: string; paletteTurnOff: string; paletteTurnOn: string; presetInUse: string; presetYours: string; prevents: string; preview: string; previewPresets: string; previous: string; quickStart: string; quickStartDetail: string; quickStartTime: string; readInPlaceDetail: string; readInPlaceLabel: string; readInPlaceTitle: (agents: readonly string[]) => string; readyConfigured: string; readyEnv: (envKey: string) => string; readyKey: string; readyOAuth: string; recapAccent: (accent: string) => string; recapIcon: (icon: string) => string; recapLookSystem: (dark: string, light: string) => string; recapNone: string; recapProvider: (provider: string, model?: string) => string; recapSwitched: (on: readonly string[], off: readonly string[]) => string; recommended: string; recommendedSources: string; reload: string; rename: string; retry: string; runsWithoutAsking: string; sampleCall: string; sampleCommand: string; sampleShortCommand: string; sandboxed: string; save: string; saveFailed: (message: string) => string; saving: string; sceneAddServer: string; sceneAgent: string; sceneApiKey: string; sceneBranches: readonly ['main', 'fix-login']; sceneCall: string; sceneClick: string; sceneCmdClick: string; sceneCode: string; sceneCodeTypo: string; sceneCommand: string; sceneCredentials: string; sceneDesktop: string; sceneDiagnostic: string; sceneEditor: string; sceneEnvKey: string; sceneExtensionAdds: readonly ['/standup', 'deploy', 'Panel', 'Provider']; sceneExtensionCode: readonly ['export default defineExtension({', '  name: \'hello\',', '  setup(ctx) {', '    ctx.registerCommand(\'standup\')']; sceneExtensionFile: string; sceneExtensionName: string; sceneFileName: string; sceneFilesPanel: string; sceneHost: string; sceneListening: string; sceneLocal: string; sceneMcpFile: string; sceneNeedsTrust: string; sceneNewRun: string; sceneOnlyYou: string; sceneOr: string; scenePath: string; sceneProjectExtension: string; scenePromptParts: readonly ['zidane rules', 'AGENTS.md', 'MCP servers', 'Skills']; scenePromptTokens: readonly ['2.1k', '640', '320', '410']; sceneProvider: string; sceneResult: string; sceneSandbox: string; sceneSandboxes: readonly ['Docker', 'E2B', 'Daytona']; sceneSaved: string; sceneSecretCommand: string; sceneSecretDots: string; sceneSecretName: string; sceneSecretOutput: string; sceneSecretPrompt: string; sceneSecretSave: string; sceneSecretsFolder: string; sceneSecretTtl: string; sceneServer: string; sceneServerStart: string; sceneService: string; sceneSession: string; sceneSignedIn: string; sceneSkillAbouts: readonly ['Test first', 'Read PDFs']; sceneSkillNames: readonly ['tdd', 'pdf', 'docx']; sceneSkillPrompt: string; sceneSkillsFolder: string; sceneSkillUsing: string; sceneSystemFile: string; sceneSystemPrompt: string; sceneTerminal: string; sceneToken: string; sceneTokenCreate: string; sceneWorktree: string; sceneWss: string; sceneZidaneFolder: string; screenBack: (screen: string) => string; screenBackKey: string; selectAll: string; setup: string; signIn: string; signInCopyCode: string; signInDone: (account?: string) => string; signInEnterCode: (host: string) => string; signInFailed: string; signInInBrowser: (service: string) => string; signInNotOpened: string; signInOpenPage: string; signInPaste: string; signInStarting: string; signInSubmit: string; signInWith: (account: string) => string; skillCount: (count: number) => string; skillsPicked: (count: number) => string; skip: string; skipSetup: string; sourceChipName: (label: string, picked: number) => string; sourceField: string; sourceName: (label: string, repo: string) => string; sourcePlaceholder: string; spendCap: string; spendCapDetail: string; stars: (count: number) => string; start: string; stepLabel: (step: OnboardingStepId) => string; stepLede: (step: OnboardingStepId) => string; stepOf: (index: number, count: number) => string; steps: string; stepTitle: (step: OnboardingStepId) => string; taskDone: string; taskFailed: string; taskNeedsSignIn: string; taskQueued: string; taskRunning: string; terminal: string; terminalExited: (code: number) => string; terminalLoadFailed: (message: string) => string; testAgain: string; testFailed: string; testing: string; testOk: (model: string, seconds: string) => string; toAdd: (count: number) => string; toInstall: (count: number) => string; tokenFor: (connector: string) => string; tokenOptional: string; tokenOptionalAuth: string; tokenOrGhLogin: string; tokenSignIn: string; tutorialOnServer: (topic: TutorialTopic, host: string) => TutorialWords; tutorials: Readonly<Record<TutorialTopic, TutorialWords>>; unlocks: string; useApiKey: string; usedIn: (step: string) => string; useGhLogin: string; usePreset: (preset: string) => string; usesGhLogin: (login?: string) => string; useSignIn: string; usesTokens: string; useToken: string; viaExtension: string; viewOnGithub: string; viewOnNpm: string; whatYoullSee: string; working: string; wrapLines: string; zoomFit: string; zoomPercent: (percent: number) => string }
```

Source: [ui/src/onboarding/headless/labels.ts:38](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/labels.ts#L38)

## Interfaces

### `OnboardingAppIcon`

Interface.

An app icon that Look offers.

```ts
interface OnboardingAppIcon
```

**Properties**

- `id` (`string`): Stable id, stored in `OnboardingAppearance.icon`.
- `label` (`string`): Display name.
- `src` (`string`): URL of a square image of the icon. A data URL works.

Source: [ui/src/onboarding/headless/host.ts:78](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L78)

### `OnboardingControllerOptions`

Interface.

Options of `createOnboardingRun`.

```ts
interface OnboardingControllerOptions
```

**Properties**

- `now?` (`() => Date`): The clock of the records. It defaults to the current time.
- `start?` (`OnboardingStepId`): Opens the run at this step instead of its first one, when the run has it.

Source: [dist/media-type-BzRZ4FqT.d.ts:10941](https://github.com/Tahul/zidane/blob/main/dist/media-type-BzRZ4FqT.d.ts#L10941)

### `OnboardingHost`

Interface.

What an app implements to run the setup wizard.

```ts
interface OnboardingHost
```

**Properties**

- `appIcons?` (`{ list: Readable<readonly OnboardingAppIcon[]>; note?: string }`): App icons for Look, for an app that has several. Absent: Look offers no icon.
- `connectors?` (`{ installed: Readable<readonly string[]>; add(id: string, input?: ConnectorInput): Promise<void>; githubToken?(): Promise<string | null>; signIn?(id: string): SignInFlow }`): Connectors. Absent: the step is left out.
- `context` (`Readable<OnboardingSetup | null>`): What the wizard starts from. It is `null` while the app still detects.
- `extensions?` (`{ installed: Readable<readonly string[]>; install(name: string, onProgress?: (message: string) => void): Promise<void> }`): Native extensions. Absent: the step is left out.
- `imports?` (`{ preview(): Promise<OnboardingImportPreview>; run(selection: OnboardingImportSelection): Promise<void> }`): Bring your setup. Absent: the step is left out.
- `providers` (`{ list: Readable<readonly OnboardingProvider[]>; models?(key: string): Promise<readonly OnboardingModelInfo[]>; saveKey?(key: string, input: { fields?: Record<string, string>; value?: string }): Promise<void>; signIn?(key: string): SignInFlow; test?(key: string, model?: string): Promise<ProviderTestResult> }`): The providers Connect lists, and how to set them up.
- `skills?` (`{ images?: SkillSourceImages; installed: Readable<readonly string[]>; browse(input: string): Promise<SkillSourceListing>; install(source: string, rels: readonly string[], onProgress?: (message: string) => void): Promise<void> }`): Skills. Absent: the step is left out.
- `themes?` (`{ list: Readable<readonly Theme[]>; preview(appearance: OnboardingAppearance | null): void }`): Themes for Look. Absent: the step is left out.
- `commit` (`(result: OnboardingResult) => Promise<void>`): Writes what a finished run decided.

Source: [ui/src/onboarding/headless/host.ts:108](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L108)

### `OnboardingModelInfo`

Interface.

A model Connect offers as the default.

```ts
interface OnboardingModelInfo
```

**Properties**

- `defaultEffort?` (`OnboardingEffort`): The effort it runs at when none is picked.
- `efforts?` (`readonly OnboardingEffort[]`): The efforts the model takes, lowest first. Absent: no effort pick.
- `id` (`string`): Model id.
- `label` (`string`): Display name.
- `recommended?` (`boolean`): The model the wizard picks first.

Source: [ui/src/onboarding/headless/host.ts:64](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L64)

### `OnboardingSetup`

Interface.

What the wizard starts from.

```ts
interface OnboardingSetup
```

**Properties**

- `appearance?` (`OnboardingAppearance`): The look in effect now, for a desktop app.
- `credentialsPath?` (`string`): Where credentials are stored, for Connect's note (`~/.zidane/credentials.json`).
- `detection` (`OnboardingDetection`): What was found on this machine.
- `mode` (`OnboardingMode`): How much of it runs.
- `provider?` (`OnboardingProviderPick`): The provider, model and effort in use now, for a re-run.
- `settings` (`Partial<Settings>`): The stored settings: explicit choices only, without the defaults.
- `surface` (`OnboardingSurface`): The app that runs it.

Source: [ui/src/onboarding/headless/host.ts:33](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L33)

### `ProviderTestResult`

Interface.

The outcome of Connect's live test.

```ts
interface ProviderTestResult
```

**Properties**

- `durationMs` (`number`): How long it took, in milliseconds.
- `message?` (`string`): Why it failed.
- `model?` (`string`): The model the test ran on.
- `ok` (`boolean`): The model answered.

Source: [ui/src/onboarding/headless/host.ts:88](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L88)

### `SignInFlow`

Interface.

A browser sign-in in progress: its state, and the ways to answer it or stop it.

```ts
interface SignInFlow
```

**Properties**

- `state` (`Readable<OnboardingSignInState>`): Where the sign-in is. `done` and `error` end it.
- `cancel` (`() => void`): Stops the sign-in.
- `submit?` (`(text: string) => void`): Answers a `prompt`, or passes a pasted redirect URL or code. Absent: the flow takes no input.

Source: [ui/src/onboarding/headless/host.ts:51](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L51)

### `SkillSourceImages`

Interface.

Where the skill source browser finds the pictures of a source.

```ts
interface SkillSourceImages
```

**Properties**

- `avatar` (`(owner: string) => string | undefined`): The avatar of a GitHub owner. Absent: the owner's letter.
- `preview` (`(owner: string, repo: string) => string | undefined`): The preview image of a repository, 2:1. Absent: no preview.

Source: [ui/src/onboarding/headless/host.ts:100](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L100)

## Types

### `OnboardingController`

Type.

A run and the work it starts. Its stores are `Readable`s, so `useReadable` reads them.

```ts
type OnboardingController = OnboardingRun
```

Source: [ui/src/onboarding/headless/controller.ts:27](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/controller.ts#L27)

### `OnboardingEffort`

Type.

A reasoning effort level, as the model picker names it.

```ts
type OnboardingEffort = NonNullable<OnboardingProviderPick['effort']>
```

Source: [ui/src/onboarding/headless/host.ts:61](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/host.ts#L61)

### `OnboardingImportPreviewState`

Type.

What an import from Claude Code brings over.

- `none`: the run offers no import, because there is no Claude Code setup or no import.
- `loading`: the host is reading it.
- `ready`: the preview.
- `failed`: the host failed to read it.

```ts
type OnboardingImportPreviewState =
  | { kind: 'none' }
  | { kind: 'loading' }
  | { kind: 'ready'; preview: OnboardingImportPreview }
  | { kind: 'failed'; message: string }
```

Source: [dist/media-type-BzRZ4FqT.d.ts:10863](https://github.com/Tahul/zidane/blob/main/dist/media-type-BzRZ4FqT.d.ts#L10863)

### `OnboardingLabels`

Type.

The setup wizard's strings: [`DEFAULT_ONBOARDING_LABELS`](https://zidane.si/docs/reference/ui-onboarding-headless.md#DEFAULT_ONBOARDING_LABELS)' keys.

```ts
type OnboardingLabels = typeof DEFAULT_ONBOARDING_LABELS
```

Source: [ui/src/onboarding/headless/labels.ts:307](https://github.com/Tahul/zidane/blob/main/ui/src/onboarding/headless/labels.ts#L307)

### `OnboardingPhase`

Type.

Where a run is in its life.

- `running`: the user is going through the steps.
- `saving`: the last step was passed, and the host is writing the result.
- `saved`: the result is written. The app closes the wizard.
- `failed`: writing failed. The run is back on its step, and moving on again tries again.

```ts
type OnboardingPhase =
  | { kind: 'running' }
  | { kind: 'saving' }
  | { kind: 'saved'; result: OnboardingResult }
  | { kind: 'failed'; message: string }
```

Source: [dist/media-type-BzRZ4FqT.d.ts:10844](https://github.com/Tahul/zidane/blob/main/dist/media-type-BzRZ4FqT.d.ts#L10844)
