feat(hands): restructure Hands UI with Chinese localization
Major changes: - Add HandList.tsx component for left sidebar - Add HandTaskPanel.tsx for middle content area - Restructure Sidebar tabs: 分身/HANDS/Workflow - Remove Hands tab from RightPanel - Localize all UI text to Chinese - Archive legacy OpenClaw documentation - Add Hands integration lessons document - Update feature checklist with new components UI improvements: - Left sidebar now shows Hands list with status icons - Middle area shows selected Hand's tasks and results - Consistent styling with Tailwind CSS - Chinese status labels and buttons Documentation: - Create docs/archive/openclaw-legacy/ for old docs - Add docs/knowledge-base/hands-integration-lessons.md - Update docs/knowledge-base/feature-checklist.md - Update docs/knowledge-base/README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
167
desktop/scripts/prepare-openclaw-runtime.mjs
Normal file
167
desktop/scripts/prepare-openclaw-runtime.mjs
Normal file
@@ -0,0 +1,167 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const desktopRoot = path.resolve(__dirname, '..');
|
||||
const outputDir = path.join(desktopRoot, 'src-tauri', 'resources', 'openclaw-runtime');
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
|
||||
function log(message) {
|
||||
console.log(`[prepare-openclaw-runtime] ${message}`);
|
||||
}
|
||||
|
||||
function readFirstExistingPath(commandNames) {
|
||||
for (const commandName of commandNames) {
|
||||
try {
|
||||
const stdout = execFileSync('where.exe', [commandName], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
const firstMatch = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean);
|
||||
if (firstMatch) {
|
||||
return firstMatch;
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureFileExists(filePath, label) {
|
||||
if (!filePath || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
||||
throw new Error(`${label} 不存在:${filePath || '(empty)'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDirExists(dirPath, label) {
|
||||
if (!dirPath || !fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
||||
throw new Error(`${label} 不存在:${dirPath || '(empty)'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveOpenClawBin() {
|
||||
const override = process.env.OPENCLAW_BIN;
|
||||
if (override) {
|
||||
return path.resolve(override);
|
||||
}
|
||||
|
||||
const resolved = readFirstExistingPath(['openclaw.cmd', 'openclaw']);
|
||||
if (!resolved) {
|
||||
throw new Error('未找到 openclaw 入口。请先安装 OpenClaw,或设置 OPENCLAW_BIN。');
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function resolvePackageDir(openclawBinPath) {
|
||||
const override = process.env.OPENCLAW_PACKAGE_DIR;
|
||||
if (override) {
|
||||
return path.resolve(override);
|
||||
}
|
||||
|
||||
return path.join(path.dirname(openclawBinPath), 'node_modules', 'openclaw');
|
||||
}
|
||||
|
||||
function resolveNodeExe(openclawBinPath) {
|
||||
const override = process.env.OPENCLAW_NODE_EXE;
|
||||
if (override) {
|
||||
return path.resolve(override);
|
||||
}
|
||||
|
||||
const bundledNode = path.join(path.dirname(openclawBinPath), 'node.exe');
|
||||
if (fs.existsSync(bundledNode)) {
|
||||
return bundledNode;
|
||||
}
|
||||
|
||||
const resolved = readFirstExistingPath(['node.exe', 'node']);
|
||||
if (!resolved) {
|
||||
throw new Error('未找到 node.exe。请先安装 Node.js,或设置 OPENCLAW_NODE_EXE。');
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function cleanOutputDirectory(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of fs.readdirSync(dirPath)) {
|
||||
fs.rmSync(path.join(dirPath, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function writeCmdLauncher(dirPath) {
|
||||
const launcher = [
|
||||
'@ECHO off',
|
||||
'SETLOCAL',
|
||||
'SET "_prog=%~dp0\\node.exe"',
|
||||
'"%_prog%" "%~dp0\\node_modules\\openclaw\\openclaw.mjs" %*',
|
||||
'',
|
||||
].join('\r\n');
|
||||
|
||||
fs.writeFileSync(path.join(dirPath, 'openclaw.cmd'), launcher, 'utf8');
|
||||
}
|
||||
|
||||
function stageRuntime() {
|
||||
const openclawBinPath = resolveOpenClawBin();
|
||||
const packageDir = resolvePackageDir(openclawBinPath);
|
||||
const nodeExePath = resolveNodeExe(openclawBinPath);
|
||||
const packageJsonPath = path.join(packageDir, 'package.json');
|
||||
const entryPath = path.join(packageDir, 'openclaw.mjs');
|
||||
|
||||
ensureFileExists(openclawBinPath, 'OpenClaw 入口');
|
||||
ensureDirExists(packageDir, 'OpenClaw 包目录');
|
||||
ensureFileExists(packageJsonPath, 'OpenClaw package.json');
|
||||
ensureFileExists(entryPath, 'OpenClaw 入口脚本');
|
||||
ensureFileExists(nodeExePath, 'Node.js 可执行文件');
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
const destinationPackageDir = path.join(outputDir, 'node_modules', 'openclaw');
|
||||
const manifest = {
|
||||
source: {
|
||||
openclawBinPath,
|
||||
packageDir,
|
||||
nodeExePath,
|
||||
},
|
||||
stagedAt: new Date().toISOString(),
|
||||
version: packageJson.version ?? null,
|
||||
};
|
||||
|
||||
log(`OpenClaw version: ${packageJson.version || 'unknown'}`);
|
||||
log(`Source bin: ${openclawBinPath}`);
|
||||
log(`Source package: ${packageDir}`);
|
||||
log(`Source node.exe: ${nodeExePath}`);
|
||||
log(`Target dir: ${outputDir}`);
|
||||
|
||||
if (dryRun) {
|
||||
log('Dry run 完成,未写入任何文件。');
|
||||
return;
|
||||
}
|
||||
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
cleanOutputDirectory(outputDir);
|
||||
fs.mkdirSync(path.join(outputDir, 'node_modules'), { recursive: true });
|
||||
fs.copyFileSync(nodeExePath, path.join(outputDir, 'node.exe'));
|
||||
fs.cpSync(packageDir, destinationPackageDir, { recursive: true, force: true });
|
||||
writeCmdLauncher(outputDir);
|
||||
fs.writeFileSync(path.join(outputDir, 'runtime-manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
||||
|
||||
log('OpenClaw runtime 已写入 src-tauri/resources/openclaw-runtime');
|
||||
}
|
||||
|
||||
try {
|
||||
stageRuntime();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(`[prepare-openclaw-runtime] ${message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user