Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,17 @@ Dominic (boss) + contributors

## Status
Draft plan ready. Next step: scaffold wrapper + extension.

---

## Completed Milestones

### ✅ Home Screen Flow (feat/home-screen-flow)
- **Home panel** (`panels/home.ts`): Branded "Welcome to OpenClaw Code" webview with lobster icon, dark red/black theme
- **Detection**: Checks `~/.openclaw/` directory existence on startup
- **Install flow**: If not installed → "Install OpenClaw" button → opens terminal with `npm install -g openclaw@latest && openclaw onboard`
- **Configure flow**: If installed → "Configure OpenClaw" button → opens side panel to edit `~/.openclaw/openclaw.json`
- **Config panel** (`panels/setup.ts`): Reads/writes openclaw.json, editable model + channels fields
- **Status panel** (`panels/status.ts`): Gateway running detection with start/stop buttons
- **Extension** (`extension.ts`): Registers `openclaw.home`, `openclaw.configure`, `openclaw.install`, `openclaw.status` commands; auto-shows Home on activation
- **package.json**: Updated commands list, bumped to v0.2.0
12 changes: 8 additions & 4 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "openclaw-vscode",
"displayName": "OpenClaw",
"description": "OpenClaw home screen, local setup wizard, and status panel for VS Code",
"version": "0.1.0",
"description": "OpenClaw home screen, install wizard, configuration, and status panel for VS Code",
"version": "0.2.0",
"publisher": "openclaw",
"repository": {
"type": "git",
Expand All @@ -23,8 +23,12 @@
"title": "OpenClaw: Home"
},
{
"command": "openclaw.setupLocal",
"title": "OpenClaw: Setup Local"
"command": "openclaw.configure",
"title": "OpenClaw: Configure"
},
{
"command": "openclaw.install",
"title": "OpenClaw: Install"
},
{
"command": "openclaw.status",
Expand Down
15 changes: 10 additions & 5 deletions apps/extension/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import * as vscode from 'vscode';
import { HomePanel } from './panels/home';
import { SetupPanel } from './panels/setup';
import { ConfigPanel } from './panels/setup';
import { StatusPanel } from './panels/status';

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('openclaw.home', () => {
HomePanel.createOrShow(context.extensionUri);
}),
vscode.commands.registerCommand('openclaw.setupLocal', () => {
SetupPanel.createOrShow(context.extensionUri);
vscode.commands.registerCommand('openclaw.configure', () => {
ConfigPanel.createOrShow(context.extensionUri);
}),
vscode.commands.registerCommand('openclaw.install', () => {
const terminal = vscode.window.createTerminal('OpenClaw Install');
terminal.show();
terminal.sendText('npm install -g openclaw@latest && openclaw onboard');
}),
vscode.commands.registerCommand('openclaw.status', () => {
StatusPanel.createOrShow(context.extensionUri);
}),
);

// Show home panel on first activation
vscode.commands.executeCommand('openclaw.home');
// Auto-show Home panel on startup
HomePanel.createOrShow(context.extensionUri);
}

export function deactivate() {}
132 changes: 104 additions & 28 deletions apps/extension/src/panels/home.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

export class HomePanel {
public static currentPanel: HomePanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionUri: vscode.Uri;
private _disposables: vscode.Disposable[] = [];

private constructor(panel: vscode.WebviewPanel) {
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
this._panel = panel;
this._panel.webview.html = this._getHtml();
this._extensionUri = extensionUri;
this._update();
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
this._panel.webview.onDidReceiveMessage(msg => {
if (msg.command) {
Expand All @@ -22,9 +27,10 @@ export class HomePanel {
return;
}
const panel = vscode.window.createWebviewPanel(
'openclawHome', 'OpenClaw Home', vscode.ViewColumn.One, { enableScripts: true }
'openclawHome', 'OpenClaw Home', vscode.ViewColumn.One,
{ enableScripts: true, localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')] }
);
HomePanel.currentPanel = new HomePanel(panel);
HomePanel.currentPanel = new HomePanel(panel, extensionUri);
}

public dispose() {
Expand All @@ -33,45 +39,115 @@ export class HomePanel {
this._disposables.forEach(d => d.dispose());
}

private _getHtml(): string {
private _update() {
const openclawDir = path.join(os.homedir(), '.openclaw');
const isInstalled = fs.existsSync(openclawDir);
const iconUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'media', 'icon.png')
);
this._panel.webview.html = this._getHtml(isInstalled, iconUri.toString());
}

private _getHtml(isInstalled: boolean, iconUri: string): string {
const statusIcon = isInstalled ? '✅' : '⚠️';
const statusText = isInstalled ? 'OpenClaw detected' : 'OpenClaw not found';
const statusClass = isInstalled ? 'detected' : 'not-found';
const buttonLabel = isInstalled ? 'Configure OpenClaw' : 'Install OpenClaw';
const buttonCommand = isInstalled ? 'openclaw.configure' : 'openclaw.install';

return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--vscode-font-family);
background: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
display: flex; flex-direction: column; align-items: center;
font-family: var(--vscode-font-family, -apple-system, BlinkMacSystemFont, sans-serif);
background: #1a1a1a;
color: #e0e0e0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 40px 20px;
}
h1 { font-size: 32px; margin-bottom: 8px; }
.accent { color: #00d4aa; }
.subtitle { color: var(--vscode-descriptionForeground); margin-bottom: 32px; }
.actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
button {
background: #00d4aa; color: #1a1a2e; border: none; padding: 10px 20px;
border-radius: 6px; font-size: 14px; cursor: pointer; font-weight: 600;
.logo {
width: 96px;
height: 96px;
margin-bottom: 24px;
filter: drop-shadow(0 4px 12px rgba(220, 40, 40, 0.3));
}
h1 {
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
color: #fff;
}
button:hover { background: #00b894; }
button.secondary {
background: transparent; border: 1px solid var(--vscode-button-border, #555);
color: var(--vscode-editor-foreground);
h1 .accent { color: #dc2828; }
.tagline {
color: #888;
font-size: 14px;
margin-bottom: 32px;
}
.status {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
margin-bottom: 28px;
padding: 8px 16px;
border-radius: 6px;
background: rgba(255,255,255,0.04);
}
.status.detected { color: #4ade80; }
.status.not-found { color: #facc15; }
.btn-primary {
background: #dc2828;
color: #fff;
border: none;
padding: 12px 28px;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
}
.btn-primary:hover { background: #b91c1c; }
.btn-secondary {
background: transparent;
color: #aaa;
border: 1px solid #444;
padding: 10px 20px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
margin-top: 12px;
transition: border-color 0.15s;
}
.btn-secondary:hover { border-color: #888; color: #ddd; }
.links {
margin-top: 48px;
display: flex;
gap: 24px;
}
.links { margin-top: 40px; }
.links a {
color: var(--vscode-textLink-foreground); text-decoration: none; margin: 0 12px;
color: #666;
text-decoration: none;
font-size: 12px;
transition: color 0.15s;
}
.links a:hover { color: #dc2828; }
</style>
</head>
<body>
<h1>🐾 <span class="accent">OpenClaw</span></h1>
<p class="subtitle">Your AI-powered development companion</p>
<div class="actions">
<button onclick="cmd('openclaw.setupLocal')">Setup Local Environment</button>
<button class="secondary" onclick="cmd('openclaw.status')">Check Status</button>
</div>
<img class="logo" src="${iconUri}" alt="OpenClaw" />
<h1>Welcome to <span class="accent">OpenClaw</span> Code</h1>
<p class="tagline">Your AI-powered development environment</p>
<div class="status ${statusClass}">${statusIcon} ${statusText}</div>
<button class="btn-primary" onclick="cmd('${buttonCommand}')">${buttonLabel}</button>
<button class="btn-secondary" onclick="cmd('openclaw.status')">Check Status</button>
<div class="links">
<a href="https://github.com/damoahdominic/occ">GitHub</a>
<a href="https://openclaw.ai">Website</a>
Expand Down
Loading