# Onboarding

Guided first-run setup for new installations. The web UI offers a skip/guided choice before Claude OAuth — if the user skips, defaults are applied server-side for steps 1-6 and the agent resumes from step 7. This runs automatically at every session start when `currentStep` is less than 8. Resume from the first incomplete step — skip any step whose number is at or below `currentStep`.

After completing each step, persist progress immediately by calling `onboarding-complete-step` with the step number just completed. This ensures that if the session ends, the next session resumes from the right place.

Every user selection and every document presentation in onboarding renders through `render-component` — never as markdown text, bullet lists, or inline chat content. Selections use `single-select` or `multi-select`; file content (SOUL, KNOWLEDGE, config) uses `document-editor`. Describing options in prose or pasting file content into a chat message bypasses the UI and the user's ability to review and edit before saving. Never use directional words ("above", "below") when referring to a rendered component — just name it or refer to "the browser", "the editor", etc. The `document-editor` component renders one file at a time — never call `render-component` with `document-editor` twice in the same turn. Present one file, wait for the user's approval or edit, then present the next.

## Preamble — Specialist introduction

The system prompt includes a `<specialist-agents>` block containing pre-assembled specialist names and summaries. Render this data as an info-card — do not read any agent files.

Call `render-component` with `name: "info-card"` and data with `title: "Your specialist agents"`, `subtitle: "These specialist agents are installed and ready to help. You can delegate tasks to them at any time."`, and `fields` as an array built from the `<specialist-agents>` data — one entry per specialist with `label` as the specialist name and `value` as the summary. Do not include any `actions` — the card auto-submits.

This is informational, not a numbered step. Do not call `onboarding-complete-step`.

## Step 1 — Plugin selection

*(skip if `currentStep` >= 1)*

Call `onboarding-plugin-options` — it returns the fully-assembled options array with correct classification (core → locked, defaultEnabled → recommended, available → optional) derived from brand.json. Do not reclassify, reorder, or transform the entries — use them as-is.

Append one additional entry to the end of the array: `{ value: "stripe", label: "stripe", description: "Live access to payment and business data.", group: "Claude Official" }`.

Call `render-component` with `name: "multi-select"` and data as an object with `selectAll: true`, `submitMessage: "Install the following plugins: {{values}}"`, `skipLabel: "Skip"`, `skipMessage: "Skip plugin installation"`, and `options` set to the array from the tool (with the Claude Official entry appended).

Wait for the user's component submission before continuing. Their response will either list the plugins they selected or indicate they skipped. The submission contains only non-locked (optional) plugin names — core plugins are always active and not included in the submission.

- For each selected **Claude Official plugin**, run: `claude plugin install <name>@claude-plugins-official`
- For each selected **Maxy plugin**, read the current `account.json` via `account-manage`, add the plugin name to the `enabledPlugins` array (create the array if absent), and write back via `Edit`. Use the `value` field (not `label`) as the plugin name — e.g., `docs` not `maxy-guide`. This activates the plugin's behaviour and MCP server from the next session.

After installing/enabling (or skipping), call `onboarding-complete-step` with step 1.

If any Maxy plugins were enabled in this step, call `session-reset` immediately after completing step 1. Maxy plugin MCP servers only load when a new subprocess spawns — the current session cannot access them. After the reset, the new session calls `onboarding-get` (returning `currentStep = 2`) and resumes from Step 2 with all newly enabled plugins available. If no Maxy plugins were enabled (user skipped or selected only Claude Official plugins), proceed directly to Step 2 — no reset needed.

## Step 2 — WiFi setup

*(skip if `currentStep` >= 2)*

Connect the device to WiFi so it can operate without an ethernet cable. This step is skippable — ethernet users who prefer a wired connection can skip and set up WiFi later through conversation at any time.

First, check whether the device has WiFi hardware by calling the `wifi` tool with `action: "status"`. If the result says "No WiFi hardware detected", skip this step automatically — tell the user their device does not have WiFi capability and proceed to the next step. Call `onboarding-complete-step` with step 2.

If WiFi hardware is present, check whether the device is already connected to a WiFi network. If the `wifi status` result shows an active WiFi connection with an IP address, confirm with the user: "You're already connected to WiFi network **{ssid}**. Would you like to keep this connection or switch to a different network?" If they want to keep it, call `onboarding-complete-step` with step 2 and proceed.

If WiFi hardware is present but not connected, ask: "Would you like to connect to WiFi? This lets your device work without an ethernet cable. You can skip this and set up WiFi later if you prefer."

If the user wants to proceed:

1. Call the `wifi` tool with `action: "scan"` to list visible networks.
2. Call `render-component` with `name: "single-select"` and data with `submitMessage: "Connect to {{value}}"` and `options` built from the scan results — each network as `{ value: "<ssid>", label: "<ssid>", description: "<signal>% signal — <security>" }`. Sort by signal strength descending. If your network doesn't appear in the list, you can connect by name — just tell me the SSID and password.
3. Wait for the user's selection. Then ask for the WiFi password via conversation (do not use a rendered component for passwords).
4. Call the `wifi` tool with `action: "connect"`, the selected `ssid`, and the `password`.
5. If the connection succeeds and an IP is assigned, the tool confirms it is safe to unplug ethernet. If the connection fails (wrong password, network not found, no IP assigned), show the error and offer to retry or skip.

After the WiFi is connected (or the user skips), call `onboarding-complete-step` with step 2.

## Step 3 — Output style

*(skip if `currentStep` >= 3)*

Call `render-component` to surface a radio selector UI. Pass `name: "single-select"` and data with `submitMessage: "Set output style to {{value}}"` and `options` as an array:

- `{ value: "default", label: "Default", description: "Efficient and task-oriented — concise responses without extra explanation." }`
- `{ value: "explanatory", label: "Explanatory", description: "Adds educational insights between steps, great for learning." }`

Wait for the user's component submission before continuing. Their response will specify the chosen style. Call `account-update` with field `outputStyle` and the chosen value. Call `onboarding-complete-step` with step 3.

## Step 4 — Thinking view

*(skip if `currentStep` >= 4)*

Call `render-component` to surface a radio selector UI. Pass `name: "single-select"` and data with `submitMessage: "Set thinking view to {{value}}"` and `options` as an array:

- `{ value: "default", label: "Thinking visible", description: "Shows reasoning expanded, collapses tool details." }`
- `{ value: "expanded", label: "Everything visible", description: "Expands all reasoning and tool details." }`
- `{ value: "collapsed", label: "Responses only", description: "Collapses all reasoning and tool details." }`

Wait for the user's component submission before continuing. Their response will specify the chosen view. Call `account-update` with field `thinkingView` and the chosen value. Call `onboarding-complete-step` with step 4.

## Step 5 — Timezone

*(skip if `currentStep` >= 5)*

The platform auto-detected the server's timezone and stored it on the user's profile at first login. Confirm it with the user so scheduling, cron events, and time formatting use the correct locale.

Call `profile-read` to retrieve the current `UserProfile`. Extract the `timezone` field. If it is already set, present it for confirmation: "Your timezone is set to **{timezone}**. Is this correct?"

Call `render-component` with `name: "single-select"` and data with `submitMessage: "Set timezone to {{value}}"` and `options` as an array. Include the auto-detected timezone as the first (default) option, plus common alternatives:

- `{ value: "{current_timezone}", label: "{current_timezone}", description: "Auto-detected from your device." }`
- `{ value: "Europe/London", label: "Europe/London", description: "GMT / BST" }`
- `{ value: "America/New_York", label: "America/New_York", description: "Eastern Time" }`
- `{ value: "America/Los_Angeles", label: "America/Los_Angeles", description: "Pacific Time" }`

Omit any option that duplicates the auto-detected timezone. If the user selects "Other", ask them to type their IANA timezone identifier (e.g. `Asia/Tokyo`, `Australia/Sydney`).

After the user confirms or selects a timezone, call `profile-update` with `profileFields: { timezone: "<selected_value>" }`. Call `onboarding-complete-step` with step 5.

## Step 6 — Admin personality

*(skip if `currentStep` >= 6)*

Write the admin agent personality. All paths are relative to the account directory (the working directory).

First, check whether personalisation is already complete: read `agents/admin/SOUL.md`. If it contains actual business content (not just the `# Soul` header), call `onboarding-complete-step` with step 6 and skip to business onboarding — the personalisation is already in place.

If the admin SOUL file (`agents/admin/SOUL.md`) is empty or missing business content, the user must provide personalisation input through conversation. Product knowledge in the graph is background context — not a substitute for the user describing their business identity and preferences. Ask the user about:

- Their business: who the customers are, current stage, and any relevant context
- Tone and personality: how the agent should sound (formal, casual, warm, blunt, etc.), language preferences

Use `memory-search` to check for any business context already in the graph — reference it to avoid re-asking what's already known, but still confirm with the user. The conversation shapes the personality; the graph fills in factual detail.

SOUL is personality — not a product brochure. Factual business data belongs in the graph. SOUL contains only what the agent needs to *be itself*: the business name, your role within it, tone, language preferences, and working style.

Present the admin SOUL via `render-component` with `name: "document-editor"` and `data: { title: "Admin agent personality", content: <the markdown>, filePath: "agents/admin/SOUL.md" }`. The user must approve (or edit and approve) the content before any Write call. When the user approves or edits, you receive `{ action: "approve", content: "<markdown>", filePath: "<path>" }` or `{ action: "save", content: "<markdown>", filePath: "<path>" }`. Write the `content` to the specified `filePath` using the Write tool.

After the admin SOUL is written and approved, call `onboarding-complete-step` with step 6.

**Document ingestion.** If the user uploaded any documents during Step 6 (or earlier in the session), dispatch the `content-producer` subagent to ingest them AFTER calling `onboarding-complete-step` — not before. Use the Agent tool with `run_in_background: true`. The critical path (SOUL file, step completion) must not depend on document ingestion succeeding. If no documents were uploaded, skip this step.

**Next steps.** After completing onboarding, let the user know that everything configured during onboarding — plugins, WiFi, output style, thinking view, timezone, and personality — can be changed at any time through conversation. Then suggest two things the user can do next — both are optional and available whenever they are ready:

1. **Set up remote access** — Cloudflare Tunnel connects the platform to a custom domain so the user and their customers can reach it from anywhere. Ask to get started.
2. **Set up the public-facing agent** — an Anthropic API key lets visitors chat with the business. Ask to get started.

## Step 7 — Cloudflare Tunnel

*(skip if `currentStep` >= 7)*

Remote access via a custom domain. Without this, the platform is only reachable on the local network. Check `tunnel-status` — if the tunnel is already running, skip this step.

If the tunnel is not configured or `tunnel-status` shows a CRITICAL nameserver warning, ask the user: "Would you like to set up remote access now? This connects your platform to a custom domain so you and your customers can reach it from anywhere. You will need a Cloudflare account and a domain. If you do not have these yet, we will set them up together now."

If the user wants to proceed: call `render-component` with `name: "browser-viewer"` and `data: { title: "Cloudflare" }` FIRST, then use `plugin-read` to load `cloudflare/skills/setup-tunnel.md` and follow its instructions exactly. The entire Cloudflare setup — sign-in, domain addition, nameserver changes at the registrar — happens in the VNC browser via `specialists:personal-assistant`. The user never leaves the chat. Do not improvise an alternative flow. Do not tell the user to go to a website themselves.

If the user skips, let them know they can set up Cloudflare at any time by asking. Also mention that remote access is still possible without Cloudflare — WhatsApp, Telegram, and Email channels all connect directly to the device, so the user (and their customers) can reach the agent from anywhere through those channels without needing a tunnel or custom domain.

After the tunnel is set up (or the user skips), call `onboarding-complete-step` with step 7.

## Step 8 — Anthropic API key

*(skip if `currentStep` >= 8)*

The public-facing agent uses the Anthropic API directly. Without a key, visitors cannot chat with the business. This step is optional — the user can skip it and set up the API key at any time through conversation.

Check if the key exists by calling the `api-key-verify` tool — it returns `missing` if no key file is found. If the key already exists, skip this step.

If missing, ask: "Would you like to set up the API key for your public-facing agent now? This is what lets visitors chat with your business. You can skip this and set it up later if you prefer."

Before opening the browser, explain: this step uses the Anthropic Console (platform.claude.com), which is a separate service from the Claude account you signed into earlier. Anthropic keeps the two — Claude and the API Console — as independent services with separate sessions and billing, even though the same credentials work on both. You may need to sign in once more using the same account.

If the user wants to proceed, use `plugin-read` to load `anthropic/skills/get-api-key.md` and follow its instructions. The skill delegates browser automation to `specialists:personal-assistant` — all Playwright interaction runs in the specialist's isolated context, keeping the main session lightweight. The user can see the browser via the VNC viewer rendered in the chat.

If the user skips, let them know: "No problem — you can ask me to set up the API key whenever you're ready. Your admin agent works without it; only the public-facing agent needs it to handle visitor conversations."

After the key is stored (or the user skips, or the key was already present), call `onboarding-complete-step` with step 8.
