feat(onboarding): add browser setup entry

This commit is contained in:
Benjamin Shafii
2026-01-29 16:55:46 -08:00
parent 22be6e0a2e
commit f32973ae4e
4 changed files with 210 additions and 2 deletions

View File

@@ -0,0 +1,12 @@
---
name: browser-setup
description: Guide user through Chrome browser automation setup
---
Help the user set up browser automation.
1. Ask: "Do you have Chrome installed on this computer?"
2. If they say no or are unsure, guide them to install Chrome
3. If yes, check if browser MCP/plugin is available
4. If not available, guide them to install the OpenCode browser extension
5. Once setup is complete, offer to run a simple first task (e.g., "Let's try opening a webpage")

View File

@@ -0,0 +1,114 @@
# Browser Entry Button
**Branch:** `feat/browser-entry-button`
**Priority:** P1
---
## Goal
Add a single entry button in the empty chat state that triggers browser automation setup via OpenCode command.
---
## Implementation
### Location
`packages/app/src/app/pages/session.tsx` L1025-1037
### Code references
- Empty state render: `packages/app/src/app/pages/session.tsx:1039-1049`
- Command runner used by slash commands: `packages/app/src/app/pages/session.tsx:645-663` (`runOpenCodeCommand`)
- Command registry wiring: `packages/app/src/app/pages/session.tsx:881-892`
- Programmatic command execution: `packages/app/src/app/command-state.ts:293-313`
- `.opencode/commands` loader: `packages/app/src/app/command-state.ts:334`
- Existing button pattern: `packages/app/src/app/pages/dashboard.tsx:557` (`onClick={() => props.runCommand(command)}`)
- Command write API (desktop): `packages/app/src/app/lib/tauri.ts:223` (`opencodeCommandWrite`)
- Frontmatter parser: `packages/app/src/app/utils/index.ts:271` (`parseTemplateFrontmatter`)
### Current empty state
```tsx
<Show when={props.messages.length === 0}>
<div class="text-center py-16 px-6 space-y-6">
<h3 class="text-xl font-medium">Start a conversation</h3>
<p class="text-gray-10 text-sm">Describe what you want to do...</p>
</div>
</Show>
```
### Add button
```tsx
<Show when={props.messages.length === 0}>
<div class="text-center py-16 px-6 space-y-6">
<div class="w-16 h-16 bg-gray-2 rounded-3xl mx-auto flex items-center justify-center border border-gray-6">
<Zap class="text-gray-7" />
</div>
<div class="space-y-2">
<h3 class="text-xl font-medium">What do you want to do?</h3>
<p class="text-gray-10 text-sm max-w-sm mx-auto">
Pick a starting point or just type below.
</p>
</div>
<div class="flex justify-center">
<button
type="button"
class="px-4 py-2.5 rounded-xl border border-gray-6 bg-gray-2 text-sm text-gray-12 hover:bg-gray-3 hover:border-gray-7 transition-all"
onClick={() => {
void (async () => {
const command = await ensureBrowserSetupCommand();
if (command) {
runOpenCodeCommand(command);
}
})();
}}
>
Automate your browser
</button>
</div>
</div>
</Show>
```
**Why `runOpenCodeCommand`**
- It handles `$ARGUMENTS` and opens the command run modal when needed (`session.tsx:645-663`)
- Keeps behavior identical to slash commands
### Create OpenCode command
File: `.opencode/commands/browser-setup.md`
**App template source:** `packages/app/src/app/data/commands/browser-setup.md`
If the command is missing in the active workspace, the button auto-writes it via `opencodeCommandWrite` (desktop only) before running.
```markdown
---
name: browser-setup
description: Guide user through Chrome browser automation setup
---
Help the user set up browser automation.
1. Ask: "Do you have Chrome installed on this computer?"
2. If they say no or are unsure, guide them to install Chrome
3. If yes, check if browser MCP/plugin is available
4. If not available, guide them to install the OpenCode browser extension
5. Once setup is complete, offer to run a simple first task (e.g., "Let's try opening a webpage")
```
---
## Flow (handled by LLM)
1. User clicks "Automate your browser"
2. Command triggers, LLM asks: "Do you have Chrome installed?"
3. User answers Yes/No
4. LLM guides through extension install if needed
5. LLM offers first browser task
---
## NOT doing
- Custom guided prompts in the UI
- Multiple entry buttons (start with one)
- Hardcoded installation scripts

View File

@@ -0,0 +1,12 @@
---
name: browser-setup
description: Guide user through Chrome browser automation setup
---
Help the user set up browser automation.
1. Ask: "Do you have Chrome installed on this computer?"
2. If they say no or are unsure, guide them to install Chrome
3. If yes, check if browser MCP/plugin is available
4. If not available, guide them to install the OpenCode browser extension
5. Once setup is complete, offer to run a simple first task (e.g., "Let's try opening a webpage")

View File

@@ -35,6 +35,9 @@ import WorkspaceChip from "../components/workspace-chip";
import ProviderAuthModal from "../components/provider-auth-modal";
import StatusBar from "../components/status-bar";
import type { OpenworkServerStatus } from "../lib/openwork-server";
import browserSetupCommandTemplate from "../data/commands/browser-setup.md?raw";
import { opencodeCommandWrite } from "../lib/tauri";
import { isTauriRuntime, parseTemplateFrontmatter } from "../utils";
import MessageList from "../components/session/message-list";
import Composer from "../components/session/composer";
@@ -147,6 +150,57 @@ export default function SessionView(props: SessionViewProps) {
const agentLabel = createMemo(() => props.selectedSessionAgent ?? "Default agent");
const buildBrowserSetupCommand = () => {
const parsed = parseTemplateFrontmatter(browserSetupCommandTemplate);
const name = parsed?.data.name?.trim() || "browser-setup";
const description =
parsed?.data.description?.trim() || "Guide user through Chrome browser automation setup";
const template = parsed?.body?.trim() || browserSetupCommandTemplate.trim();
return { name, description, template };
};
const ensureBrowserSetupCommand = async (): Promise<WorkspaceCommand | null> => {
const existing = props.commands.find((item) => item.name === "browser-setup");
if (existing) return existing;
if (props.activeWorkspaceDisplay.workspaceType === "remote") {
setCommandToast("Browser setup command is only available in local workspaces.");
return null;
}
if (!isTauriRuntime()) {
setCommandToast("Browser setup is available in the desktop app.");
return null;
}
const root = props.activeWorkspaceDisplay.path?.trim() ?? "";
if (!root) {
setCommandToast("Pick a workspace folder to install the command.");
return null;
}
try {
const draft = buildBrowserSetupCommand();
await opencodeCommandWrite({
scope: "workspace",
projectDir: root,
command: draft,
});
return {
name: draft.name,
description: draft.description,
template: draft.template,
scope: "workspace",
};
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to install browser setup command";
setCommandToast(message);
return null;
}
};
const loadAgentOptions = async (force = false) => {
if (agentPickerBusy()) return agentOptions();
if (agentPickerReady() && !force) return agentOptions();
@@ -1042,11 +1096,27 @@ export default function SessionView(props: SessionViewProps) {
<Zap class="text-gray-7" />
</div>
<div class="space-y-2">
<h3 class="text-xl font-medium">Start a conversation</h3>
<h3 class="text-xl font-medium">What do you want to do?</h3>
<p class="text-gray-10 text-sm max-w-sm mx-auto">
Describe what you want to do, and OpenWork will take it from there.
Pick a starting point or just type below.
</p>
</div>
<div class="flex justify-center">
<button
type="button"
class="px-4 py-2.5 rounded-xl border border-gray-6 bg-gray-2 text-sm text-gray-12 hover:bg-gray-3 hover:border-gray-7 transition-all"
onClick={() => {
void (async () => {
const command = await ensureBrowserSetupCommand();
if (command) {
runOpenCodeCommand(command);
}
})();
}}
>
Automate your browser
</button>
</div>
</div>
</Show>