diff --git a/README.md b/README.md index 030a8ad6..1020105c 100644 --- a/README.md +++ b/README.md @@ -155,18 +155,3 @@ Made with [contrib.rocks](https://contrib.rocks).

-## Sponsors - -OpenClaw Code is made possible by the generous support of our sponsors. - -

πŸ’Ž Diamond Sponsor

- -

- - MoltPod β€” Diamond Sponsor - -

- ---- - -Want to sponsor OpenClaw Code and support open-source AI? Reach out at [team@mba.sh](mailto:team@mba.sh). diff --git a/apps/editor/extensions/openclaw/src/extension.ts b/apps/editor/extensions/openclaw/src/extension.ts index ce4ba52d..5bb7adc7 100644 --- a/apps/editor/extensions/openclaw/src/extension.ts +++ b/apps/editor/extensions/openclaw/src/extension.ts @@ -140,7 +140,11 @@ async function openOpenClawFolder(): Promise { // Only open the workspace if OpenClaw is already installed. const openclawPath = path.join(os.homedir(), '.openclaw'); if (!fs.existsSync(openclawPath)) { - return; // OpenClaw not installed yet β€” nothing to open. + // OpenClaw not installed β€” clean up any stale workspace file so VS Code + // doesn't keep opening the workspace with a missing folder on next restart. + const staleWorkspaceFile = path.join(occPath, WORKSPACE_FILENAME); + try { if (fs.existsSync(staleWorkspaceFile)) { fs.unlinkSync(staleWorkspaceFile); } } catch { /* non-fatal */ } + return; } // Workspace file lives in ~/.occ, points at ~/.openclaw as the folder. diff --git a/apps/editor/extensions/openclaw/src/panels/home.ts b/apps/editor/extensions/openclaw/src/panels/home.ts index 3b451d92..bcad8849 100644 --- a/apps/editor/extensions/openclaw/src/panels/home.ts +++ b/apps/editor/extensions/openclaw/src/panels/home.ts @@ -8,6 +8,20 @@ import * as path from 'path'; type GatewayStatus = 'checking' | 'running' | 'stopped' | 'starting' | 'stopping' | 'restarting' | 'errored' | 'ai-fixing'; +// ── OCC Legacy model constants ──────────────────────────────────────────────── +const OCC_LEGACY_MODEL_ID = 'occ-legacy'; +const OCC_LEGACY_MODEL_NAME = 'occ-legacy'; +const OCC_LEGACY_BASE_URL = 'https://occ.mba.sh/v1'; +const OCC_LEGACY_API = 'openai-completions'; +const OCC_LEGACY_COST = { + input: 0.0000006, + output: 0.000003, + cacheRead: 0.0000001, + cacheWrite: 0, +}; +const OCC_LEGACY_CONTEXT_WINDOW = 262144; +const OCC_LEGACY_MAX_TOKENS = 262144; + /** * Resolves the directory where OpenClaw stores its workspace files * (AGENTS.md, IDENTITY.md, USER.md, TOOLS.md, MEMORY.md, SOUL.md, HEARTBEAT.md). @@ -251,7 +265,7 @@ export class HomePanel { post({ type: 'installState', state: 'running' }); const env = panel._buildExecEnv(); - const isPermError = (s: string) => /EACCES|permission denied|EPERM|not permitted/i.test(s); + const isPermError = (s: string) => /EACCES|permission denied|EPERM|not permitted|Need sudo access|needs to be an Administrator/i.test(s); // Spawn a command silently and stream output to the panel. const runCaptured = (cmd: string, args: string[], opts: cp.SpawnOptions = {}): Promise<{ code: number }> => @@ -349,11 +363,79 @@ export class HomePanel { } } tee('\nnpm install did not succeed β€” trying full installer script...\n'); + } else if (platform !== 'win32') { + + // ── Unix: npm not found β€” silent Node.js install (no terminal) ────────── + // Step A: try nvm (no sudo, no password needed) + const nvmSh = path.join(os.homedir(), '.nvm', 'nvm.sh'); + if (fs.existsSync(nvmSh)) { + tee('nvm detected β€” installing Node.js LTS...\n'); + const nvmR = await runCaptured('bash', ['-c', + `. "${nvmSh}" && nvm install --lts && nvm use --lts && npm install -g openclaw` + ]); + if (nvmR.code === 0) { await fixOpenclawPermissions(); await succeed(); return; } + tee('nvm install failed β€” falling back to system install...\n'); + } + + // Step B: collect password once, cache sudo for all subsequent steps + tee('\nNode.js is required. Your password is needed once to install it.\n'); + const sudoOk = await cacheSudo('Enter your password to install Node.js'); + if (!sudoOk) { tee('Incorrect password or cancelled.\n'); failCancelled(); return; } + + if (platform === 'darwin') { + // macOS: download official Node.js universal pkg, install silently with cached sudo + const nodeVersion = '20.18.2'; + const pkgUrl = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}.pkg`; + const pkgPath = `/tmp/.occ-node-${nodeVersion}.pkg`; + tee(`Downloading Node.js v${nodeVersion}...\n`); + const dlR = await runCaptured('curl', ['-fsSL', pkgUrl, '-o', pkgPath]); + if (dlR.code !== 0) { try { fs.unlinkSync(pkgPath); } catch {} await fail(); return; } + tee('Installing Node.js (this may take a moment)...\n'); + const instR = await runCaptured('sudo', ['-n', 'installer', '-pkg', pkgPath, '-target', '/']); + try { fs.unlinkSync(pkgPath); } catch { /* non-fatal */ } + if (instR.code !== 0) { await fail(); return; } + } else { + // Linux: detect package manager and install Node.js LTS via nodesource + const hasCmdSync = (cmd: string): boolean => { + try { cp.execSync(`which ${cmd}`, { env, stdio: 'ignore' }); return true; } catch { return false; } + }; + tee('Installing Node.js via package manager...\n'); + let pkgResult: { code: number } | undefined; + if (hasCmdSync('apt-get')) { + pkgResult = await runCaptured('sudo', ['-n', 'bash', '-c', + 'curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt-get install -y nodejs' + ]); + } else if (hasCmdSync('dnf')) { + pkgResult = await runCaptured('sudo', ['-n', 'bash', '-c', + 'curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash - && dnf install -y nodejs' + ]); + } else if (hasCmdSync('yum')) { + pkgResult = await runCaptured('sudo', ['-n', 'bash', '-c', + 'curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash - && yum install -y nodejs' + ]); + } + if (!pkgResult) { tee('No supported package manager found (tried apt-get, dnf, yum).\n'); await fail(); return; } + if (pkgResult.code !== 0) { await fail(); return; } + } + + // Step C: npm is now installed β€” find it and install openclaw + tee('Installing OpenClaw...\n'); + const npmCandidates = ['/usr/local/bin/npm', '/usr/bin/npm']; + const npmBin = npmCandidates.find(p => fs.existsSync(p)) ?? 'npm'; + const npmR1 = await runCaptured(npmBin, ['install', '-g', 'openclaw']); + if (npmR1.code === 0) { await fixOpenclawPermissions(); await succeed(); return; } + // Global prefix dir may be root-owned β€” retry with cached sudo + if (isPermError(fullLog)) { + const npmR2 = await runCaptured('sudo', ['-n', npmBin, 'install', '-g', 'openclaw']); + if (npmR2.code === 0) { await fixOpenclawPermissions(); await succeed(); return; } + } + await fail(); return; + } else { tee('npm not found β€” running full installer script...\n'); } - // ── Step 2: full install script, captured (no terminal) ─────────────────── + // ── Step 2: full install script ── (npm found but failed, or Windows no npm) if (platform === 'win32') { tee('Running PowerShell installer...\n'); const psArgs = [ @@ -364,13 +446,13 @@ export class HomePanel { const r = await runCaptured('powershell', psArgs, { windowsHide: true } as cp.SpawnOptions); if (r.code === 0) { await succeed(); return; } } else { + // npm was found but install failed β€” try the openclaw installer script tee('Running install script...\n'); const r1 = await runCaptured('bash', ['-c', 'curl -fsSL https://openclaw.ai/install.sh | bash']); if (r1.code === 0) { await fixOpenclawPermissions(); await succeed(); return; } - // Permission error β†’ ask for sudo, run installer under sudo, then fix permissions if (isPermError(fullLog)) { tee('\nPermission error in installer β€” elevated access required.\n'); const ok = await cacheSudo('Enter your system password to complete installation'); @@ -932,6 +1014,37 @@ export class HomePanel { JSON.stringify({ tier: 'free', grantedAt: new Date().toISOString(), limitUsd: 1.00 }), ); } catch { /* non-fatal */ } + // Patch openclaw.json to inject correct cost/context metadata for occ-legacy. + // openclaw onboard writes the model with null/zero values; we fix them here. + try { + const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json'); + const raw = fs.readFileSync(configPath, 'utf-8'); + const cfg = JSON.parse(raw) as Record; + // Recursively find any model object with id === OCC_LEGACY_MODEL_ID and patch it. + const patchModel = (obj: unknown): boolean => { + if (!obj || typeof obj !== 'object') return false; + if (Array.isArray(obj)) { + for (const item of obj) { + if (patchModel(item)) return true; + } + return false; + } + const o = obj as Record; + if (o['id'] === OCC_LEGACY_MODEL_ID) { + o['name'] = OCC_LEGACY_MODEL_NAME; + o['reasoning'] = false; + o['input'] = ['text']; + o['cost'] = { ...OCC_LEGACY_COST }; + o['contextWindow'] = OCC_LEGACY_CONTEXT_WINDOW; + o['maxTokens'] = OCC_LEGACY_MAX_TOKENS; + return true; + } + for (const v of Object.values(o)) { patchModel(v); } + return false; + }; + patchModel(cfg); + fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2)); + } catch { /* non-fatal β€” openclaw.json may not exist yet */ } } setTimeout(() => { HomePanel.refresh(); @@ -994,13 +1107,9 @@ export class HomePanel { private async _runUninstall(): Promise { const post = (msg: object) => { try { this._panel.webview.postMessage(msg); } catch {} }; - const home = os.homedir(); post({ type: 'uninstallLog', text: 'Handing off to AI for uninstall…\n', done: true, ok: true }); - // IMPORTANT: workspace file deletion must happen AFTER MoltPilot is activated β€” - // deleting the open .code-workspace file can trigger a VS Code window reload - // which would cancel any pending setTimeout callbacks. setTimeout(() => { post({ type: 'uninstallDone' }); vscode.commands.executeCommand( @@ -1009,31 +1118,10 @@ export class HomePanel { 'agent', ); this._schedulePostUninstallClose(); - - // Remove ~/.openclaw from the VS Code workspace Explorer sidebar. - // Done here (after MoltPilot fires) so workspace changes don't trigger - // a window reload that would cancel this callback before it executes. - try { - const openclawUri = vscode.Uri.file(path.join(home, '.openclaw')); - const folders = vscode.workspace.workspaceFolders ?? []; - const idx = folders.findIndex(f => f.uri.fsPath === openclawUri.fsPath); - if (idx !== -1) { - vscode.workspace.updateWorkspaceFolders(idx, 1); - } - } catch { /* non-fatal */ } - - // Delete the .code-workspace file so the folder doesn't come back on next launch. - // Use a small extra delay so VS Code settles after the updateWorkspaceFolders call - // before we remove the workspace file (avoids "workspace file missing" prompts). - setTimeout(() => { - try { - const wsFile = path.join(home, '.occ', 'My OpenClaw Workspace.code-workspace'); - if (fs.existsSync(wsFile)) { fs.unlinkSync(wsFile); } - } catch { /* non-fatal */ } - }, 800); - - // Reload the panel so it shows the "not installed" state - setTimeout(() => HomePanel.refresh(), 1200); + // Workspace cleanup (removing ~/.openclaw folder and .code-workspace file) is handled + // by openOpenClawFolder() on the next startup β€” manipulating workspace state here + // triggers VS Code to modify/reload the workspace, which can cause the extension to + // re-activate and open a duplicate OCC Home tab. }, 1200); } @@ -2321,6 +2409,7 @@ The binary is already downloaded β€” do NOT re-download or compile anything.`; border: 1px solid #2b2b2b; border-radius: 8px; padding: 6px clamp(10px, 3vw, 16px); + margin-top: 20px; margin-bottom: 10px; font-size: clamp(11px, 2.5vw, 12px); } @@ -2983,7 +3072,8 @@ The binary is already downloaded β€” do NOT re-download or compile anything.`; ${isInstalled ? `
- {/* Global community */} + {/* MoltPilot AI Assistant */} +
+
+
+ {/* Header */} + +
+ + + + + + Built-in AI Guide +
+

+ Meet MoltPilot +

+ +
+ + {/* Chat bubbles β€” centered */} + +
+
+ {/* User bubble 1 */} + +
+

How do I connect my Telegram to OpenClaw?

+
+
+ + {/* MoltPilot reply 1 */} + +
+

MoltPilot

+

I'll walk you through it! First, open @BotFather on Telegram and create a new bot. Once you have the token, I'll configure everything for you.

+
+
+ + {/* User bubble 2 */} + +
+

Done! Here's the token.

+
+
+ + {/* MoltPilot reply 2 */} + +
+

MoltPilot

+

Connected! Your OpenClaw agent is now live on Telegram. Try sending it a message β€” it'll respond instantly.

+
+
+ + {/* Typing indicator */} + +
+
+ + + +
+
+
+
+ +
+
+
+ + {/* Global community */}

@@ -648,60 +781,79 @@ export default function Home({ downloadUrls = FALLBACK_URLS }: { downloadUrls?:

- {/* Sponsors */} -
-

Our Sponsors

-

- Backed by the best -

-

- OpenClaw Code is made possible by the generous support of our sponsors. -

- - {/* Diamond Sponsor */} -
-

- πŸ’Ž Diamond Sponsor -

-
- - + {/* NemoClaw Enterprise */} +
+
+
+
- {/* Become a Sponsor CTA */} -
-

- Want to sponsor OpenClaw Code and support open-source AI? -

- + - - - - - Become a sponsor β€” team@mba.sh - + {/* Jensen as full-bleed background */} +
+ NemoClaw + {/* Dark overlays for text readability */} +
+
+
+ + {/* Accent glow */} +
+ + {/* Text overlay */} +
+
+ +
+ + + + Now Available +
+

+ NemoClaw. +

+

+ Enterprise-grade OpenClaw agents powered by NVIDIA NeMo Guardrails. + Safety, compliance, and scale β€” built in from day one. +

+ + + + + Read the Docs + +
+
+
+
- {/* CTA */} + {/* CTA */}
@@ -728,13 +880,18 @@ export default function Home({ downloadUrls = FALLBACK_URLS }: { downloadUrls?: Download for {platformLabels[platform]}
- - Also available for{" "} - {platform === "windows" ? "macOS" : "Windows"} - + + Also available for + {otherPlatforms.map((p, i) => ( + + {i > 0 && Β·} + + {platformIcons[p]} + {platformLabels[p]} + + + ))} +
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 886c8cda..0a1436b8 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -5,6 +5,7 @@ const REPO = "damoahdominic/occ"; const FALLBACK: DownloadUrls = { windows: `https://github.com/${REPO}/releases/latest`, macos: `https://github.com/${REPO}/releases/latest`, + linux: `https://github.com/${REPO}/releases/latest`, }; async function getDownloadUrls(): Promise { @@ -29,6 +30,9 @@ async function getDownloadUrls(): Promise { const mac = assets.find( (a) => a.name.includes("darwin") && a.name.endsWith(".zip") ); + const lin = assets.find( + (a) => a.name.includes("linux") && (a.name.endsWith(".deb") || a.name.endsWith(".AppImage") || a.name.endsWith(".tar.gz")) + ); const tagUrl = tag ? `https://github.com/${REPO}/releases/tag/${tag}` @@ -37,6 +41,7 @@ async function getDownloadUrls(): Promise { return { windows: win?.browser_download_url ?? tagUrl, macos: mac?.browser_download_url ?? tagUrl, + linux: lin?.browser_download_url ?? tagUrl, }; } catch { return FALLBACK; diff --git a/apps/web/src/components/ui/resizable-navbar.tsx b/apps/web/src/components/ui/resizable-navbar.tsx index d0029a64..21764644 100644 --- a/apps/web/src/components/ui/resizable-navbar.tsx +++ b/apps/web/src/components/ui/resizable-navbar.tsx @@ -269,7 +269,7 @@ export const NavbarLogo = () => { className="relative z-20 mr-4 flex items-center space-x-2.5 px-2 py-1" > OCCode - OCCode + OCCode ); }; diff --git a/package-lock.json b/package-lock.json index b93c694d..8d7c1164 100644 --- a/package-lock.json +++ b/package-lock.json @@ -945,6 +945,23 @@ "resolved": "packages/control-center", "link": true }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2169,6 +2186,55 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/point-in-polygon-hao": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/point-in-polygon-hao/-/point-in-polygon-hao-1.2.4.tgz", @@ -2534,72 +2600,6 @@ "react": "19.2.3", "react-dom": "19.2.3" } - }, - "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "playwright": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "playwright-core": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } } } }