First Release of Claw3D (#11)

Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
Luke The Dev
2026-03-19 23:14:04 -05:00
committed by GitHub
parent 5ea96b2650
commit 4fa4f13558
431 changed files with 105438 additions and 14 deletions
+88
View File
@@ -0,0 +1,88 @@
import { constants as fsConstants, promises as fs } from "node:fs";
import { spawnSync } from "node:child_process";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, "..");
const uxAuditDir = path.join(repoRoot, "output", "playwright", "ux-audit");
const transientFiles = [
path.join(repoRoot, ".agent", "ux-audit.md"),
path.join(repoRoot, ".agent", "execplan-pending.md"),
];
async function ensureDir(dir) {
await fs.mkdir(dir, { recursive: true });
}
async function clearDirContents(dir) {
await ensureDir(dir);
const entries = await fs.readdir(dir);
await Promise.all(
entries.map((entry) =>
fs.rm(path.join(dir, entry), { recursive: true, force: true }),
),
);
}
async function removeIfPresent(filePath) {
try {
await fs.unlink(filePath);
} catch (error) {
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
return;
}
throw error;
}
}
function run(command, args) {
return spawnSync(command, args, { encoding: "utf8" });
}
async function stopPlaywrightSessions() {
const codeHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
const pwcli = path.join(codeHome, "skills", "playwright", "scripts", "playwright_cli.sh");
try {
await fs.access(pwcli, fsConstants.X_OK);
} catch {
return;
}
const result = run(pwcli, ["session-stop-all"]);
if (result.status === 0) return;
if (result.error) {
throw result.error;
}
}
function killPattern(pattern) {
const result = run("pkill", ["-f", pattern]);
if (result.status === 0 || result.status === 1) return;
if (result.error && result.error.code === "ENOENT") return;
if (result.error) throw result.error;
}
function cleanupPlaywrightProcesses() {
killPattern("ms-playwright/daemon");
killPattern("playwright/cli.js run-mcp-server");
killPattern("chrome-headless-shell");
killPattern("Google Chrome --headless");
killPattern("Chromium --headless");
}
async function main() {
await stopPlaywrightSessions();
cleanupPlaywrightProcesses();
await clearDirContents(uxAuditDir);
for (const transientFile of transientFiles) {
await removeIfPresent(transientFile);
}
console.log("cleanup:ux-artifacts complete");
}
main().catch((error) => {
console.error("cleanup:ux-artifacts failed");
console.error(error);
process.exit(1);
});
+82
View File
@@ -0,0 +1,82 @@
import { spawn } from "node:child_process";
import net from "node:net";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const getFreePort = async () => {
for (let i = 0; i < 30; i++) {
const port = 20000 + Math.floor(Math.random() * 20000);
const ok = await new Promise((resolve) => {
const server = net.createServer();
server.once("error", () => resolve(false));
server.listen(port, "127.0.0.1", () => {
server.close(() => resolve(true));
});
});
if (ok) return port;
}
throw new Error("Failed to find a free port for smoke test.");
};
const main = async () => {
const port = await getFreePort();
const url = `http://127.0.0.1:${port}/`;
const child = spawn(process.execPath, ["server/index.js", "--dev"], {
env: {
...process.env,
HOST: "127.0.0.1",
PORT: String(port),
},
stdio: ["ignore", "pipe", "pipe"],
});
const lines = [];
const pushLines = (chunk) => {
const text = String(chunk ?? "");
for (const line of text.split(/\r?\n/)) {
if (!line) continue;
lines.push(line);
if (lines.length > 80) lines.shift();
}
};
child.stdout.on("data", pushLines);
child.stderr.on("data", pushLines);
const deadline = Date.now() + 60_000;
let lastErr = null;
try {
while (Date.now() < deadline) {
if (child.exitCode !== null) {
throw new Error(`Dev server exited early with code ${child.exitCode}.`);
}
try {
const res = await fetch(url, { redirect: "manual" });
if (res.status >= 200 && res.status < 500) {
process.stdout.write(`OK ${res.status} ${url}\n`);
return;
}
lastErr = new Error(`Unexpected status ${res.status} for ${url}`);
} catch (err) {
lastErr = err;
}
await sleep(500);
}
throw new Error(
`Timed out waiting for dev server to respond at ${url}. Last error: ${lastErr?.message || "unknown"}`
);
} finally {
child.kill("SIGTERM");
await Promise.race([new Promise((r) => child.once("exit", r)), sleep(2000)]);
}
};
main().catch((err) => {
process.stderr.write(String(err?.stack || err) + "\n");
process.exitCode = 1;
});
+90
View File
@@ -0,0 +1,90 @@
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");
const readline = require("node:readline/promises");
const { resolveStudioSettingsPath } = require("../server/studio-settings");
const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
const parseArgs = (argv) => {
return {
force: argv.includes("--force"),
};
};
const tryReadGatewayTokenFromOpenclawCli = () => {
try {
const raw = execFileSync("openclaw", ["config", "get", "gateway.auth.token"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
const token = String(raw ?? "").trim();
return token || null;
} catch {
return null;
}
};
async function main() {
const args = parseArgs(process.argv.slice(2));
const settingsPath = resolveStudioSettingsPath(process.env);
const settingsDir = path.dirname(settingsPath);
if (fs.existsSync(settingsPath) && !args.force) {
console.error(
`Studio settings already exist at ${settingsPath}. Re-run with --force to overwrite.`
);
process.exitCode = 1;
return;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
try {
const urlAnswer = await rl.question(
`Upstream Gateway URL [${DEFAULT_GATEWAY_URL}]: `
);
const gatewayUrl = (urlAnswer || DEFAULT_GATEWAY_URL).trim();
if (!gatewayUrl) {
throw new Error("Gateway URL is required.");
}
const tokenDefault = tryReadGatewayTokenFromOpenclawCli();
const tokenPrompt = tokenDefault
? "Upstream Gateway Token [detected from openclaw]: "
: "Upstream Gateway Token: ";
const tokenAnswer = await rl.question(tokenPrompt);
const token = (tokenAnswer || tokenDefault || "").trim();
if (!token) {
throw new Error(
"Gateway token is required. Provide it, or install/openclaw so it can be auto-detected."
);
}
fs.mkdirSync(settingsDir, { recursive: true });
const next = {
version: 1,
gateway: {
url: gatewayUrl,
token,
},
};
fs.writeFileSync(settingsPath, JSON.stringify(next, null, 2), "utf8");
console.info(`Wrote Studio settings to ${settingsPath}.`);
} finally {
rl.close();
}
}
main().catch((err) => {
const msg = err instanceof Error ? err.message : String(err);
console.error(msg);
process.exitCode = 1;
});
+47
View File
@@ -0,0 +1,47 @@
import fs from "node:fs";
import path from "node:path";
const repoRoot = process.cwd();
const requestedSourcePath =
process.argv[2]?.trim() ||
process.env.OPENCLAW_GATEWAY_CLIENT_SOURCE?.trim() ||
process.env.OPENCLAW_UI_PATH?.trim() ||
"";
const sourcePath = requestedSourcePath
? path.resolve(requestedSourcePath)
: "";
const destPath = path.join(
repoRoot,
"src",
"lib",
"gateway",
"openclaw",
"GatewayBrowserClient.ts"
);
if (!sourcePath) {
console.error(
"Missing upstream gateway client source path. Provide it as `npm run sync:gateway-client -- /path/to/gateway.ts` or set OPENCLAW_GATEWAY_CLIENT_SOURCE."
);
process.exit(1);
}
if (!fs.existsSync(sourcePath)) {
console.error(`Missing upstream gateway client at ${sourcePath}.`);
process.exit(1);
}
let contents = fs.readFileSync(sourcePath, "utf8");
contents = contents
.replace(
/from "\.\.\/\.\.\/\.\.\/src\/gateway\/protocol\/client-info\.js";/g,
'from "./client-info";'
)
.replace(
/from "\.\.\/\.\.\/\.\.\/src\/gateway\/device-auth\.js";/g,
'from "./device-auth-payload";'
);
fs.mkdirSync(path.dirname(destPath), { recursive: true });
fs.writeFileSync(destPath, contents, "utf8");
console.log(`Synced gateway client to ${destPath}.`);