Skip to content

feat(settings): add interactive setup wizard for initial configuration#204

Merged
jlia0 merged 3 commits intomainfrom
claude/migrate-setup-to-web-hNfeP
Mar 12, 2026
Merged

feat(settings): add interactive setup wizard for initial configuration#204
jlia0 merged 3 commits intomainfrom
claude/migrate-setup-to-web-hNfeP

Conversation

@jlia0
Copy link
Copy Markdown
Collaborator

@jlia0 jlia0 commented Mar 12, 2026

Description

Add an interactive setup wizard to guide users through initial TinyClaw configuration. When settings are empty or missing, users are presented with a multi-step wizard instead of a blank configuration page. The wizard collects channel preferences, AI provider/model selection, workspace configuration, and agent definitions, then persists everything via a new /api/setup endpoint.

This improves the onboarding experience by replacing manual JSON editing with a guided form-based flow.

Changes

  • Settings page (tinyoffice/src/app/settings/page.tsx):

    • Add SetupWizard component with 5-step flow: Channels → Provider → Workspace → Agents → Review
    • Detect empty settings on load and show wizard instead of editor
    • Add "Run Setup" button to re-trigger wizard from main settings view
    • Include provider/model definitions and channel configuration constants
    • Add helper functions for ID sanitization and settings building
  • Settings API (packages/server/src/routes/settings.ts):

    • Add POST /api/setup endpoint to atomically write settings and initialize directories
    • Create TINYCLAW_HOME structure (logs, files)
    • Copy template files (.claude, heartbeat.md, AGENTS.md)
    • Create workspace and agent working directories
  • API client (tinyoffice/src/lib/api.ts):

    • Export runSetup() function and AgentConfig type for wizard integration

Testing

  • Wizard displays when settings are empty or missing
  • All 5 steps validate input before allowing progression
  • Settings are correctly built from form inputs and persisted via /api/setup
  • Directories and template files are created as expected
  • Existing settings page functionality remains unchanged

Checklist

  • PR title follows conventional commit format
  • Changes tested locally with empty and existing settings
  • No new warnings or errors introduced
  • API endpoint properly handles directory creation and file copying

https://claude.ai/code/session_01JANJmrpc35awJqJvQCS3z2

Adds a step-by-step setup wizard to the settings page that replaces the
CLI setup flow. The wizard walks through channels, provider/model,
workspace, and agents configuration — then writes settings.json and
creates all directories via a new POST /api/setup endpoint.

https://claude.ai/code/session_01JANJmrpc35awJqJvQCS3z2
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR introduces a guided 5-step setup wizard (/setup) to replace blank-slate manual JSON editing for first-time TinyClaw configuration, and adds a matching POST /api/setup server endpoint that atomically writes settings and bootstraps the TINYCLAW_HOME directory structure. The wizard integrates cleanly with the existing settings page (redirect on empty config, "Run Setup" shortcut) and the AppShell refactor correctly hides the sidebar during onboarding.

Key observations:

  • Workspace path traversal — The buildSettings() sanitizer intentionally allows / and ., so a workspace name like ../../etc results in a server-side fs.mkdirSync call outside the user's home directory. The server should validate the resolved path stays within an expected root before accepting it.
  • No error handling in /api/setup — All fs.* calls are unwrapped; a permission error or full disk will bubble up as an unhelpful 500. A try-catch with a structured error response would let the UI surface the real failure message.
  • Heartbeat interval is unguarded — The number input has no min constraint, so 0 or negative values pass through into settings.json and could misbehave in the monitoring system.
  • Dead codeReviewItem is defined at the bottom of settings/page.tsx but is never used there; it was correctly placed in setup/page.tsx and should be removed from the settings page.
  • Previously flagged issues (missing token validation on step 0, duplicate agent ID collisions, and $HOME not expanding on the server) have all been addressed in this revision.

Confidence Score: 3/5

  • Safe to merge after addressing the path traversal issue and adding error handling to the setup endpoint.
  • The three previously critical issues (token validation, duplicate agent IDs, $HOME expansion) are all fixed. Remaining concerns are a path traversal risk in workspace path sanitization that could attempt directory creation outside the home directory, missing try-catch around filesystem operations in the setup endpoint, and an unguarded heartbeat interval. None of these will crash the wizard under normal use, but they represent edge cases that could produce confusing failures or unintended filesystem access.
  • tinyoffice/src/app/setup/page.tsx (path sanitization) and packages/server/src/routes/settings.ts (missing error handling) need the most attention before merging.

Important Files Changed

Filename Overview
tinyoffice/src/app/setup/page.tsx New 5-step setup wizard; workspace path sanitization allows directory traversal via . and / in the regex, and the heartbeat interval input permits zero/negative values.
packages/server/src/routes/settings.ts Adds POST /api/setup with home-path expansion and directory bootstrapping; no try-catch around filesystem operations means any fs error surfaces as an unhelpful 500.
tinyoffice/src/app/settings/page.tsx Refactored to redirect empty-settings users to /setup and adds a "Run Setup" link; contains a dead ReviewItem helper function that is unused in this file.
tinyoffice/src/components/app-shell.tsx New thin wrapper that hides the Sidebar on the /setup route; clean and correct.
tinyoffice/src/lib/api.ts Exports the new runSetup() function and AgentConfig type; straightforward addition with no issues.

Sequence Diagram

sequenceDiagram
    participant User as User Browser
    participant Settings as /settings page
    participant Setup as /setup page
    participant API as Server API
    participant FS as Filesystem

    User->>Settings: Load settings page
    Settings->>API: GET /api/settings
    API-->>Settings: Settings (empty / error)
    Settings-->>User: "Setup Required" — link to /setup

    User->>Setup: Navigate to /setup
    Setup-->>User: Step 0 — Select Channels + tokens
    User->>Setup: Next (validates tokens)
    Setup-->>User: Step 1 — Provider, Model, Heartbeat
    User->>Setup: Next
    Setup-->>User: Step 2 — Workspace name/path
    User->>Setup: Next
    Setup-->>User: Step 3 — Agents (id, name, provider, model)
    User->>Setup: Next (validates unique IDs)
    Setup-->>User: Step 4 — Review (buildSettings() preview)

    User->>Setup: Click "Finish Setup"
    Setup->>Setup: buildSettings() → Settings object
    Setup->>API: POST /api/setup (Settings JSON)
    API->>API: expandHomePath(workspace.path)
    API->>API: expandHomePath(agent.working_directory) for each agent
    API->>FS: Write settings.json
    API->>FS: mkdirSync(TINYCLAW_HOME/logs)
    API->>FS: mkdirSync(TINYCLAW_HOME/files)
    API->>FS: Copy .claude, heartbeat.md, AGENTS.md templates
    API->>FS: mkdirSync(workspace.path)
    API->>FS: ensureAgentDirectory(agent.working_directory) × N
    API-->>Setup: { ok: true, settings }
    Setup->>User: router.push("/")
Loading

Comments Outside Diff (1)

  1. tinyoffice/src/app/setup/page.tsx, line 547-554 (link)

    Workspace path sanitization allows directory traversal

    The regex [^a-zA-Z0-9_/~.-] intentionally permits . and /, which means a user can enter something like ../../etc as the workspace name. After buildSettings() prepends ~/, it becomes ~/../../etc. On the server, expandHomePath resolves this via path.join(home, '../../etc'), which normalises to a path outside the user's home directory (e.g. /etc). The fs.mkdirSync call will then attempt to create that directory.

    In practice this will usually fail with EACCES if the server is not running as root, but the intent to confine workspaces to the user's home directory is defeated. The same traversal path applies to agent working_directory values, since they're constructed from workspacePath.

    Consider resolving the path and asserting it stays within an expected prefix before writing settings or creating directories on the server side:

    import path from 'path';
    import os from 'os';
    
    const resolved = path.resolve(expandHomePath(workspacePath) ?? workspacePath);
    const allowed = os.homedir();
    if (!resolved.startsWith(allowed + path.sep) && resolved !== allowed) {
        return c.json({ error: 'Workspace path must be inside the home directory' }, 400);
    }

Last reviewed commit: ec58197

Comment on lines +47 to +58
const templateItems = ['.claude', 'heartbeat.md', 'AGENTS.md'];
for (const item of templateItems) {
const srcPath = path.join(SCRIPT_DIR, item);
const destPath = path.join(TINYCLAW_HOME, item);
if (fs.existsSync(srcPath)) {
if (fs.statSync(srcPath).isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Template files unconditionally overwritten on re-run

The "Run Setup" button on the settings page can trigger this endpoint again on an already-configured system. Since there's no existence check on destPath, files like .claude (which often holds custom Claude configuration) and AGENTS.md will be silently overwritten with the bundled templates, discarding any user modifications.

Consider guarding the copy with an !fs.existsSync(destPath) check, or at least documenting this destructive behaviour:

if (fs.existsSync(srcPath) && !fs.existsSync(destPath)) {
    if (fs.statSync(srcPath).isDirectory()) {
        copyDirSync(srcPath, destPath);
    } else {
        fs.copyFileSync(srcPath, destPath);
    }
}

@jlia0
Copy link
Copy Markdown
Collaborator Author

jlia0 commented Mar 12, 2026

@greptileai review

Comment on lines +46 to +98
app.post('/api/setup', async (c) => {
const settings = (await c.req.json()) as Settings;

if (settings.workspace?.path) {
settings.workspace.path = expandHomePath(settings.workspace.path);
}
if (settings.agents) {
for (const agent of Object.values(settings.agents)) {
if (agent.working_directory) {
agent.working_directory = expandHomePath(agent.working_directory) || agent.working_directory;
}
}
}

// Write settings.json
fs.mkdirSync(path.dirname(SETTINGS_FILE), { recursive: true });
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
log('INFO', '[API] Setup: settings.json written');

// Create TINYCLAW_HOME directories
fs.mkdirSync(path.join(TINYCLAW_HOME, 'logs'), { recursive: true });
fs.mkdirSync(path.join(TINYCLAW_HOME, 'files'), { recursive: true });

// Copy template files into TINYCLAW_HOME
const templateItems = ['.claude', 'heartbeat.md', 'AGENTS.md'];
for (const item of templateItems) {
const srcPath = path.join(SCRIPT_DIR, item);
const destPath = path.join(TINYCLAW_HOME, item);
if (fs.existsSync(srcPath)) {
if (fs.statSync(srcPath).isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}

// Create workspace directory
const workspacePath = settings.workspace?.path;
if (workspacePath) {
fs.mkdirSync(workspacePath, { recursive: true });
}

// Create agent directories
if (settings.agents) {
for (const agent of Object.values(settings.agents)) {
ensureAgentDirectory(agent.working_directory);
}
}

log('INFO', '[API] Setup complete');
return c.json({ ok: true, settings });
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filesystem errors surface as unhandled exceptions

The POST /api/setup handler performs several fs.* operations (mkdirSync, copyFileSync, etc.) with no surrounding try-catch. If any of these fail — due to permission denied, disk full, or a missing parent directory — Hono will propagate the exception as an unhandled 500, and the client's apiFetch will only see "Internal Server Error" rather than a descriptive message.

Wrapping the side-effectful block in a try-catch lets you return a structured error that the UI can display to the user:

try {
    // ... all fs.mkdirSync / copyFileSync / ensureAgentDirectory calls ...
} catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    log('ERROR', `[API] Setup failed: ${msg}`);
    return c.json({ ok: false, error: msg }, 500);
}

@jlia0 jlia0 merged commit 9f174a0 into main Mar 12, 2026
1 check passed
@jlia0 jlia0 deleted the claude/migrate-setup-to-web-hNfeP branch March 12, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants