feat: add OpenClaw CLI command and install flow (#25)
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
* Registers all IPC handlers for main-renderer communication
|
||||
*/
|
||||
import { ipcMain, BrowserWindow, shell, dialog, app } from 'electron';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { GatewayManager } from '../gateway/manager';
|
||||
import { ClawHubService, ClawHubSearchParams, ClawHubInstallParams, ClawHubUninstallParams } from '../gateway/clawhub';
|
||||
import {
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
type ProviderConfig,
|
||||
} from '../utils/secure-storage';
|
||||
import { getOpenClawStatus, getOpenClawDir } from '../utils/paths';
|
||||
import { getOpenClawCliCommand, installOpenClawCliMac } from '../utils/openclaw-cli';
|
||||
import { getSetting } from '../utils/store';
|
||||
import { saveProviderKeyToOpenClaw, setOpenClawDefaultModel } from '../utils/openclaw-auth';
|
||||
import { logger } from '../utils/logger';
|
||||
@@ -500,6 +502,27 @@ function registerOpenClawHandlers(): void {
|
||||
return getOpenClawDir();
|
||||
});
|
||||
|
||||
// Get a shell command to run OpenClaw CLI without modifying PATH
|
||||
ipcMain.handle('openclaw:getCliCommand', () => {
|
||||
try {
|
||||
const status = getOpenClawStatus();
|
||||
if (!status.packageExists) {
|
||||
return { success: false, error: `OpenClaw package not found at: ${status.dir}` };
|
||||
}
|
||||
if (!existsSync(status.entryPath)) {
|
||||
return { success: false, error: `OpenClaw entry script not found at: ${status.entryPath}` };
|
||||
}
|
||||
return { success: true, command: getOpenClawCliCommand() };
|
||||
} catch (error) {
|
||||
return { success: false, error: String(error) };
|
||||
}
|
||||
});
|
||||
|
||||
// Install a system-wide openclaw command on macOS (requires admin prompt)
|
||||
ipcMain.handle('openclaw:installCliMac', async () => {
|
||||
return installOpenClawCliMac();
|
||||
});
|
||||
|
||||
// ==================== Channel Configuration Handlers ====================
|
||||
|
||||
// Save channel configuration
|
||||
|
||||
@@ -113,6 +113,8 @@ const electronAPI = {
|
||||
'log:listFiles',
|
||||
// OpenClaw extras
|
||||
'openclaw:getDir',
|
||||
'openclaw:getCliCommand',
|
||||
'openclaw:installCliMac',
|
||||
];
|
||||
|
||||
if (validChannels.includes(channel)) {
|
||||
|
||||
93
electron/utils/openclaw-cli.ts
Normal file
93
electron/utils/openclaw-cli.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* OpenClaw CLI utilities
|
||||
*/
|
||||
import { app } from 'electron';
|
||||
import { chmodSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { getOpenClawDir, getOpenClawEntryPath } from './paths';
|
||||
import { logger } from './logger';
|
||||
|
||||
function escapeForDoubleQuotes(value: string): string {
|
||||
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
function quoteForPosix(value: string): string {
|
||||
return `"${escapeForDoubleQuotes(value)}"`;
|
||||
}
|
||||
|
||||
function quoteForPowerShell(value: string): string {
|
||||
return `'${value.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
export function getOpenClawCliCommand(): string {
|
||||
const entryPath = getOpenClawEntryPath();
|
||||
const platform = process.platform;
|
||||
|
||||
if (platform === 'darwin') {
|
||||
const localBinPath = join(homedir(), '.local', 'bin', 'openclaw');
|
||||
if (existsSync(localBinPath)) {
|
||||
return quoteForPosix(localBinPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!app.isPackaged) {
|
||||
const openclawDir = getOpenClawDir();
|
||||
const nodeModulesDir = dirname(openclawDir);
|
||||
const binName = platform === 'win32' ? 'openclaw.cmd' : 'openclaw';
|
||||
const binPath = join(nodeModulesDir, '.bin', binName);
|
||||
|
||||
if (existsSync(binPath)) {
|
||||
if (platform === 'win32') {
|
||||
return `& ${quoteForPowerShell(binPath)}`;
|
||||
}
|
||||
return quoteForPosix(binPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (app.isPackaged) {
|
||||
const execPath = process.execPath;
|
||||
if (platform === 'win32') {
|
||||
return `$env:ELECTRON_RUN_AS_NODE=1; & ${quoteForPowerShell(execPath)} ${quoteForPowerShell(entryPath)}`;
|
||||
}
|
||||
return `ELECTRON_RUN_AS_NODE=1 ${quoteForPosix(execPath)} ${quoteForPosix(entryPath)}`;
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
return `node ${quoteForPowerShell(entryPath)}`;
|
||||
}
|
||||
|
||||
return `node ${quoteForPosix(entryPath)}`;
|
||||
}
|
||||
|
||||
export async function installOpenClawCliMac(): Promise<{ success: boolean; path?: string; error?: string }>
|
||||
{
|
||||
if (process.platform !== 'darwin') {
|
||||
return { success: false, error: 'Install is only supported on macOS.' };
|
||||
}
|
||||
|
||||
const entryPath = getOpenClawEntryPath();
|
||||
if (!existsSync(entryPath)) {
|
||||
return { success: false, error: `OpenClaw entry not found at: ${entryPath}` };
|
||||
}
|
||||
|
||||
const execPath = process.execPath;
|
||||
const targetDir = join(homedir(), '.local', 'bin');
|
||||
const target = join(targetDir, 'openclaw');
|
||||
|
||||
try {
|
||||
const script = [
|
||||
'#!/bin/sh',
|
||||
`ELECTRON_RUN_AS_NODE=1 "${escapeForDoubleQuotes(execPath)}" "${escapeForDoubleQuotes(entryPath)}" "$@"`,
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
writeFileSync(target, script, { mode: 0o755 });
|
||||
chmodSync(target, 0o755);
|
||||
return { success: true, path: target };
|
||||
} catch (error) {
|
||||
logger.error('Failed to install OpenClaw CLI:', error);
|
||||
return { success: false, error: String(error) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user