From dcbb60d65137e5aafc916686725b9ada1b4e84f1 Mon Sep 17 00:00:00 2001 From: Debian Date: Sun, 15 Feb 2026 10:36:51 +0000 Subject: [PATCH] feat: scaffold Electron wrapper + VS Code extension (Milestones 2 & 3) --- .github/workflows/build.yml | 52 +++++++++++++ README.md | 84 +++++++++++++------- apps/extension/package.json | 42 ++++++++++ apps/extension/src/extension.ts | 23 ++++++ apps/extension/src/panels/home.ts | 82 ++++++++++++++++++++ apps/extension/src/panels/setup.ts | 114 ++++++++++++++++++++++++++++ apps/extension/src/panels/status.ts | 92 ++++++++++++++++++++++ apps/extension/tsconfig.json | 12 +++ apps/wrapper/assets/.gitkeep | 0 apps/wrapper/extensions/.gitkeep | 0 apps/wrapper/package.json | 52 +++++++++++++ apps/wrapper/src/download.js | 81 ++++++++++++++++++++ apps/wrapper/src/main.js | 70 +++++++++++++++++ apps/wrapper/src/setup.js | 72 ++++++++++++++++++ apps/wrapper/src/splash.html | 43 +++++++++++ package.json | 15 ++++ 16 files changed, 808 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 apps/extension/package.json create mode 100644 apps/extension/src/extension.ts create mode 100644 apps/extension/src/panels/home.ts create mode 100644 apps/extension/src/panels/setup.ts create mode 100644 apps/extension/src/panels/status.ts create mode 100644 apps/extension/tsconfig.json create mode 100644 apps/wrapper/assets/.gitkeep create mode 100644 apps/wrapper/extensions/.gitkeep create mode 100644 apps/wrapper/package.json create mode 100644 apps/wrapper/src/download.js create mode 100644 apps/wrapper/src/main.js create mode 100644 apps/wrapper/src/setup.js create mode 100644 apps/wrapper/src/splash.html create mode 100644 package.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..b70ad58f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,52 @@ +name: Build OCcode + +on: + push: + branches: [main, scaffold-mvp] + pull_request: + branches: [main] + +jobs: + build-wrapper: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + working-directory: apps/wrapper + - name: Build Electron app + run: npm run build + working-directory: apps/wrapper + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/upload-artifact@v4 + with: + name: occode-${{ matrix.os }} + path: apps/wrapper/dist/* + + build-extension: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + working-directory: apps/extension + - name: Compile extension + run: npm run compile + working-directory: apps/extension + - name: Package VSIX + run: npx @vscode/vsce package + working-directory: apps/extension + - uses: actions/upload-artifact@v4 + with: + name: openclaw-extension + path: apps/extension/*.vsix diff --git a/README.md b/README.md index 29544455..a6ee20b0 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,66 @@ # OCcode -Branded, cross‑platform OpenClaw IDE built on a VS Code portable bundle + custom extension. +**Branded, cross-platform IDE wrapper** that ships a portable VSCodium bundle with the **OpenClaw** VS Code extension pre-installed. ## Structure + ``` apps/ - wrapper/ # Electron app (branding, install flow) - extension/ # VS Code extension (OpenClaw UI + commands) -scripts/ -.github/workflows/ + wrapper/ # Electron app — downloads VSCodium, installs extension, launches editor + extension/ # VS Code extension — Home screen, Setup wizard, Status panel +.github/ + workflows/ # CI: build wrapper (Win/Mac/Linux) + package extension +``` + +## Quick Start + +### Wrapper (Electron) + +```bash +cd apps/wrapper +npm install +npm start +``` + +The wrapper will: +1. Download portable VSCodium (pinned version) to `~/.occode/vscode/` +2. Install any bundled `.vsix` extensions +3. Set default settings (theme, disable welcome) +4. Launch VSCodium with a custom user-data directory + +### Extension (VS Code) + +```bash +cd apps/extension +npm install +npm run compile +``` + +Then press F5 in VS Code to launch the Extension Development Host. + +**Commands:** +- `OpenClaw: Home` — branded home screen with quick links +- `OpenClaw: Setup Local` — wizard to detect OS, check prerequisites (git, node, docker), and guide OpenClaw setup +- `OpenClaw: Status` — shows whether the OpenClaw gateway is running + +## Building + +```bash +# Wrapper — platform-specific installer +cd apps/wrapper && npm run build + +# Extension — .vsix package +cd apps/extension && npx @vscode/vsce package ``` -## Goals -- Ship OCcode.exe / OCcode.dmg / OCcode.AppImage -- Custom home screen + panels via extension -- Install/config OpenClaw locally via extension wizard - -## Plan (MVP) -**Wrapper (Electron)** -1. Download portable VS Code/VSCodium -2. Install OpenClaw extension (.vsix or URL) -3. Write default settings/workspace -4. Launch VS Code - -**Extension (OpenClaw)** -- Custom Home webview -- Install/config local OpenClaw -- Start/stop + logs/status - -## Next -- Scaffold wrapper app -- Scaffold VS Code extension -- Add CI for Windows/Mac/Linux builds +## CI + +GitHub Actions builds the wrapper for Windows, macOS, and Linux on every push to `main`. Extension is compiled and packaged as a `.vsix` artifact. + +## Contributing + +PRs welcome! See [AGENTS.md](AGENTS.md) for the full plan and milestone tracker. + +## License + +MIT diff --git a/apps/extension/package.json b/apps/extension/package.json new file mode 100644 index 00000000..2767f3d9 --- /dev/null +++ b/apps/extension/package.json @@ -0,0 +1,42 @@ +{ + "name": "openclaw-vscode", + "displayName": "OpenClaw", + "description": "OpenClaw home screen, local setup wizard, and status panel for VS Code", + "version": "0.1.0", + "publisher": "openclaw", + "engines": { + "vscode": "^1.85.0" + }, + "categories": ["Other"], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "openclaw.home", + "title": "OpenClaw: Home" + }, + { + "command": "openclaw.setupLocal", + "title": "OpenClaw: Setup Local" + }, + { + "command": "openclaw.status", + "title": "OpenClaw: Status" + } + ] + }, + "scripts": { + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "package": "vsce package" + }, + "devDependencies": { + "@types/vscode": "^1.85.0", + "@types/node": "^20.0.0", + "typescript": "^5.3.0", + "@vscode/vsce": "^2.22.0" + } +} diff --git a/apps/extension/src/extension.ts b/apps/extension/src/extension.ts new file mode 100644 index 00000000..72679a86 --- /dev/null +++ b/apps/extension/src/extension.ts @@ -0,0 +1,23 @@ +import * as vscode from 'vscode'; +import { HomePanel } from './panels/home'; +import { SetupPanel } 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.status', () => { + StatusPanel.createOrShow(context.extensionUri); + }), + ); + + // Show home panel on first activation + vscode.commands.executeCommand('openclaw.home'); +} + +export function deactivate() {} diff --git a/apps/extension/src/panels/home.ts b/apps/extension/src/panels/home.ts new file mode 100644 index 00000000..b91bdb9f --- /dev/null +++ b/apps/extension/src/panels/home.ts @@ -0,0 +1,82 @@ +import * as vscode from 'vscode'; + +export class HomePanel { + public static currentPanel: HomePanel | undefined; + private readonly _panel: vscode.WebviewPanel; + private _disposables: vscode.Disposable[] = []; + + private constructor(panel: vscode.WebviewPanel) { + this._panel = panel; + this._panel.webview.html = this._getHtml(); + this._panel.onDidDispose(() => this.dispose(), null, this._disposables); + } + + public static createOrShow(extensionUri: vscode.Uri) { + if (HomePanel.currentPanel) { + HomePanel.currentPanel._panel.reveal(); + return; + } + const panel = vscode.window.createWebviewPanel( + 'openclawHome', 'OpenClaw Home', vscode.ViewColumn.One, { enableScripts: true } + ); + HomePanel.currentPanel = new HomePanel(panel); + } + + public dispose() { + HomePanel.currentPanel = undefined; + this._panel.dispose(); + this._disposables.forEach(d => d.dispose()); + } + + private _getHtml(): string { + return ` + + + + + + +

🐾 OpenClaw

+

Your AI-powered development companion

+
+ + +
+ + + +`; + } +} diff --git a/apps/extension/src/panels/setup.ts b/apps/extension/src/panels/setup.ts new file mode 100644 index 00000000..ee51e9d6 --- /dev/null +++ b/apps/extension/src/panels/setup.ts @@ -0,0 +1,114 @@ +import * as vscode from 'vscode'; +import * as cp from 'child_process'; +import * as os from 'os'; + +interface CheckResult { + name: string; + found: boolean; + version?: string; +} + +function checkCommand(cmd: string, versionFlag = '--version'): CheckResult { + try { + const out = cp.execSync(`${cmd} ${versionFlag}`, { timeout: 5000 }).toString().trim(); + return { name: cmd, found: true, version: out.split('\n')[0] }; + } catch { + return { name: cmd, found: false }; + } +} + +export class SetupPanel { + public static currentPanel: SetupPanel | undefined; + private readonly _panel: vscode.WebviewPanel; + private _disposables: vscode.Disposable[] = []; + + private constructor(panel: vscode.WebviewPanel) { + this._panel = panel; + this._update(); + this._panel.onDidDispose(() => this.dispose(), null, this._disposables); + this._panel.webview.onDidReceiveMessage(msg => { + if (msg.command === 'refresh') { this._update(); } + }, null, this._disposables); + } + + public static createOrShow(extensionUri: vscode.Uri) { + if (SetupPanel.currentPanel) { + SetupPanel.currentPanel._panel.reveal(); + return; + } + const panel = vscode.window.createWebviewPanel( + 'openclawSetup', 'OpenClaw Setup', vscode.ViewColumn.One, { enableScripts: true } + ); + SetupPanel.currentPanel = new SetupPanel(panel); + } + + public dispose() { + SetupPanel.currentPanel = undefined; + this._panel.dispose(); + this._disposables.forEach(d => d.dispose()); + } + + private _update() { + const platform = `${os.type()} ${os.release()} (${os.arch()})`; + const checks = [ + checkCommand('git'), + checkCommand('node'), + checkCommand('npm'), + checkCommand('docker'), + ]; + this._panel.webview.html = this._getHtml(platform, checks); + } + + private _getHtml(platform: string, checks: CheckResult[]): string { + const rows = checks.map(c => ` + + ${c.found ? '✅' : '❌'} + ${c.name} + ${c.found ? c.version : 'Not found'} + `).join(''); + + const allGood = checks.filter(c => ['git', 'node'].includes(c.name)).every(c => c.found); + + return ` + + + + + + +

🔧 OpenClaw Local Setup

+

Detected: ${platform}

+ +

Prerequisites

+ ${rows}
+ + ${allGood + ? '
✅ Core prerequisites met! You can proceed with OpenClaw setup.
' + : '
⚠️ Please install missing prerequisites (git, node) before continuing.
'} + +
+ + + + +`; + } +} diff --git a/apps/extension/src/panels/status.ts b/apps/extension/src/panels/status.ts new file mode 100644 index 00000000..44575d67 --- /dev/null +++ b/apps/extension/src/panels/status.ts @@ -0,0 +1,92 @@ +import * as vscode from 'vscode'; +import * as cp from 'child_process'; + +export class StatusPanel { + public static currentPanel: StatusPanel | undefined; + private readonly _panel: vscode.WebviewPanel; + private _disposables: vscode.Disposable[] = []; + + private constructor(panel: vscode.WebviewPanel) { + this._panel = panel; + this._update(); + this._panel.onDidDispose(() => this.dispose(), null, this._disposables); + this._panel.webview.onDidReceiveMessage(msg => { + if (msg.command === 'refresh') { this._update(); } + }, null, this._disposables); + } + + public static createOrShow(extensionUri: vscode.Uri) { + if (StatusPanel.currentPanel) { + StatusPanel.currentPanel._panel.reveal(); + return; + } + const panel = vscode.window.createWebviewPanel( + 'openclawStatus', 'OpenClaw Status', vscode.ViewColumn.One, { enableScripts: true } + ); + StatusPanel.currentPanel = new StatusPanel(panel); + } + + public dispose() { + StatusPanel.currentPanel = undefined; + this._panel.dispose(); + this._disposables.forEach(d => d.dispose()); + } + + private _update() { + let running = false; + let details = 'OpenClaw gateway not detected.'; + + try { + const out = cp.execSync('openclaw gateway status', { timeout: 5000 }).toString().trim(); + running = out.toLowerCase().includes('running'); + details = out; + } catch { + // gateway not installed or not running + } + + this._panel.webview.html = this._getHtml(running, details); + } + + private _getHtml(running: boolean, details: string): string { + return ` + + + + + + +

📡 OpenClaw Status

+

+ + ${running ? 'Running' : 'Not Running'} +

+
${details}
+ + + +`; + } +} diff --git a/apps/extension/tsconfig.json b/apps/extension/tsconfig.json new file mode 100644 index 00000000..882d1007 --- /dev/null +++ b/apps/extension/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "lib": ["ES2020"], + "sourceMap": true, + "rootDir": "src", + "strict": true + }, + "exclude": ["node_modules", "out"] +} diff --git a/apps/wrapper/assets/.gitkeep b/apps/wrapper/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/wrapper/extensions/.gitkeep b/apps/wrapper/extensions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/wrapper/package.json b/apps/wrapper/package.json new file mode 100644 index 00000000..5519b5ac --- /dev/null +++ b/apps/wrapper/package.json @@ -0,0 +1,52 @@ +{ + "name": "occode-wrapper", + "version": "0.1.0", + "description": "OCcode Electron wrapper — downloads and launches portable VSCodium", + "main": "src/main.js", + "scripts": { + "start": "electron .", + "build": "electron-builder", + "build:win": "electron-builder --win", + "build:mac": "electron-builder --mac", + "build:linux": "electron-builder --linux" + }, + "build": { + "appId": "com.openclaw.occode", + "productName": "OCcode", + "directories": { + "output": "dist" + }, + "files": [ + "src/**/*", + "assets/**/*" + ], + "extraResources": [ + { + "from": "extensions/", + "to": "extensions/", + "filter": ["**/*.vsix"] + } + ], + "win": { + "target": ["nsis", "portable"], + "icon": "assets/icon.ico" + }, + "mac": { + "target": ["dmg"], + "icon": "assets/icon.icns", + "category": "public.app-category.developer-tools" + }, + "linux": { + "target": ["AppImage", "deb"], + "icon": "assets/icon.png", + "category": "Development" + } + }, + "dependencies": { + "electron-log": "^5.0.0" + }, + "devDependencies": { + "electron": "^30.0.0", + "electron-builder": "^24.0.0" + } +} diff --git a/apps/wrapper/src/download.js b/apps/wrapper/src/download.js new file mode 100644 index 00000000..434d6c77 --- /dev/null +++ b/apps/wrapper/src/download.js @@ -0,0 +1,81 @@ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const { execSync } = require('child_process'); +const https = require('https'); + +const PLATFORM_MAP = { + win32: { os: 'win32', arch: 'x64', ext: 'zip', dir: 'VSCodium-win32-x64' }, + darwin: { os: 'darwin', arch: 'x64', ext: 'zip', dir: 'VSCodium-darwin-x64' }, + linux: { os: 'linux', arch: 'x64', ext: 'tar.gz', dir: 'VSCodium-linux-x64' }, +}; + +function getPlatformInfo() { + const p = PLATFORM_MAP[process.platform]; + if (!p) throw new Error(`Unsupported platform: ${process.platform}`); + // Handle Apple Silicon + if (process.platform === 'darwin' && process.arch === 'arm64') { + return { ...p, arch: 'arm64', dir: 'VSCodium-darwin-arm64' }; + } + return p; +} + +function buildDownloadUrl(version) { + const p = getPlatformInfo(); + const filename = `VSCodium-${p.os}-${p.arch}-${version}.${p.ext}`; + return `https://github.com/VSCodium/vscodium/releases/download/${version}/${filename}`; +} + +function downloadFile(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + const get = (u) => { + https.get(u, { headers: { 'User-Agent': 'OCcode' } }, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + return get(res.headers.location); + } + if (res.statusCode !== 200) { + return reject(new Error(`Download failed: HTTP ${res.statusCode}`)); + } + res.pipe(file); + file.on('finish', () => { file.close(); resolve(); }); + }).on('error', reject); + }; + get(url); + }); +} + +async function downloadVSCodium(version, destDir) { + fs.mkdirSync(destDir, { recursive: true }); + const url = buildDownloadUrl(version); + const p = getPlatformInfo(); + const archivePath = path.join(os.tmpdir(), `vscodium.${p.ext}`); + + await downloadFile(url, archivePath); + + // Extract + if (p.ext === 'zip') { + if (process.platform === 'win32') { + execSync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force"`); + } else { + execSync(`unzip -o "${archivePath}" -d "${destDir}"`); + } + } else { + execSync(`tar -xzf "${archivePath}" -C "${destDir}"`); + } + + fs.unlinkSync(archivePath); +} + +function getVSCodiumBinary(vscodeDir) { + switch (process.platform) { + case 'win32': + return path.join(vscodeDir, 'bin', 'codium.cmd'); + case 'darwin': + return path.join(vscodeDir, 'VSCodium.app', 'Contents', 'Resources', 'app', 'bin', 'codium'); + default: + return path.join(vscodeDir, 'bin', 'codium'); + } +} + +module.exports = { downloadVSCodium, getVSCodiumBinary, getPlatformInfo }; diff --git a/apps/wrapper/src/main.js b/apps/wrapper/src/main.js new file mode 100644 index 00000000..62e6b630 --- /dev/null +++ b/apps/wrapper/src/main.js @@ -0,0 +1,70 @@ +const { app, BrowserWindow, dialog } = require('electron'); +const path = require('path'); +const { downloadVSCodium, getVSCodiumBinary } = require('./download'); +const { installExtension, setDefaults, launchVSCodium } = require('./setup'); + +const APP_NAME = 'OCcode'; +const VSCODIUM_VERSION = '1.96.4.25027'; +const OCCODE_DIR = path.join(require('os').homedir(), '.occode'); +const VSCODE_DIR = path.join(OCCODE_DIR, 'vscode'); + +let mainWindow; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 520, + height: 380, + title: APP_NAME, + resizable: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + }, + }); + mainWindow.loadFile(path.join(__dirname, 'splash.html')); + mainWindow.setMenuBarVisibility(false); +} + +function sendStatus(msg) { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.executeJavaScript( + `document.getElementById('status').textContent = ${JSON.stringify(msg)}` + ); + } +} + +async function bootstrap() { + try { + sendStatus('Checking VSCodium installation…'); + const binary = getVSCodiumBinary(VSCODE_DIR); + + const fs = require('fs'); + if (!fs.existsSync(binary)) { + sendStatus('Downloading VSCodium…'); + await downloadVSCodium(VSCODIUM_VERSION, VSCODE_DIR); + } + + sendStatus('Installing OpenClaw extension…'); + await installExtension(binary, OCCODE_DIR); + + sendStatus('Setting defaults…'); + await setDefaults(OCCODE_DIR); + + sendStatus('Launching editor…'); + await launchVSCodium(binary, OCCODE_DIR); + + // Close wrapper after launching editor + setTimeout(() => app.quit(), 2000); + } catch (err) { + dialog.showErrorBox('OCcode Error', err.message); + app.quit(); + } +} + +app.setName(APP_NAME); +app.whenReady().then(() => { + createWindow(); + bootstrap(); +}); + +app.on('window-all-closed', () => app.quit()); diff --git a/apps/wrapper/src/setup.js b/apps/wrapper/src/setup.js new file mode 100644 index 00000000..314fe276 --- /dev/null +++ b/apps/wrapper/src/setup.js @@ -0,0 +1,72 @@ +const fs = require('fs'); +const path = require('path'); +const { execFile, spawn } = require('child_process'); +const { promisify } = require('util'); + +const execFileAsync = promisify(execFile); + +const DEFAULT_SETTINGS = { + "workbench.startupEditor": "none", + "workbench.colorTheme": "Default Dark Modern", + "workbench.tips.enabled": false, + "extensions.autoUpdate": true, + "telemetry.telemetryLevel": "off", +}; + +async function installExtension(codiumBinary, occodeDir) { + // Look for bundled .vsix files in the app's resources or local extensions/ dir + const searchPaths = [ + path.join(__dirname, '..', 'extensions'), + path.join(process.resourcesPath || '', 'extensions'), + ]; + + for (const dir of searchPaths) { + if (!fs.existsSync(dir)) continue; + const vsixFiles = fs.readdirSync(dir).filter(f => f.endsWith('.vsix')); + for (const vsix of vsixFiles) { + const vsixPath = path.join(dir, vsix); + const userDataDir = path.join(occodeDir, 'user-data'); + try { + await execFileAsync(codiumBinary, [ + '--install-extension', vsixPath, + '--user-data-dir', userDataDir, + '--force', + ], { timeout: 60000 }); + } catch (err) { + console.warn(`Failed to install ${vsix}:`, err.message); + } + } + } +} + +async function setDefaults(occodeDir) { + const userDataDir = path.join(occodeDir, 'user-data'); + const settingsDir = path.join(userDataDir, 'User'); + const settingsFile = path.join(settingsDir, 'settings.json'); + + fs.mkdirSync(settingsDir, { recursive: true }); + + // Merge with existing settings if any + let existing = {}; + if (fs.existsSync(settingsFile)) { + try { + existing = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); + } catch {} + } + + const merged = { ...DEFAULT_SETTINGS, ...existing }; + fs.writeFileSync(settingsFile, JSON.stringify(merged, null, 2)); +} + +async function launchVSCodium(codiumBinary, occodeDir) { + const userDataDir = path.join(occodeDir, 'user-data'); + const child = spawn(codiumBinary, [ + '--user-data-dir', userDataDir, + ], { + detached: true, + stdio: 'ignore', + }); + child.unref(); +} + +module.exports = { installExtension, setDefaults, launchVSCodium }; diff --git a/apps/wrapper/src/splash.html b/apps/wrapper/src/splash.html new file mode 100644 index 00000000..e69a2554 --- /dev/null +++ b/apps/wrapper/src/splash.html @@ -0,0 +1,43 @@ + + + + + OCcode + + + + +
Powered by OpenClaw
+
+
Starting…
+ + diff --git a/package.json b/package.json new file mode 100644 index 00000000..c4884ed5 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "occode", + "version": "0.1.0", + "private": true, + "description": "OCcode — branded cross-platform IDE wrapper with OpenClaw extension", + "workspaces": [ + "apps/wrapper", + "apps/extension" + ], + "scripts": { + "wrapper": "npm run --workspace=apps/wrapper start", + "ext:compile": "npm run --workspace=apps/extension compile", + "ext:package": "npm run --workspace=apps/extension package" + } +}