chore: Also format scripts and skills.

This commit is contained in:
cpojer
2026-01-31 21:21:09 +09:00
parent a767c584c7
commit 76b5208b11
95 changed files with 2250 additions and 1239 deletions

View File

@@ -13,8 +13,7 @@ type RunResult = {
usage?: Usage;
};
const DEFAULT_PROMPT =
"Reply with a single word: ok. No punctuation or extra text.";
const DEFAULT_PROMPT = "Reply with a single word: ok. No punctuation or extra text.";
const DEFAULT_RUNS = 10;
function parseArg(flag: string): string | undefined {
@@ -65,9 +64,7 @@ async function runModel(opts: {
);
const durationMs = Date.now() - started;
results.push({ durationMs, usage: res.usage });
console.log(
`${opts.label} run ${i + 1}/${opts.runs}: ${durationMs}ms`,
);
console.log(`${opts.label} run ${i + 1}/${opts.runs}: ${durationMs}ms`);
}
return results;
}
@@ -85,10 +82,8 @@ async function main(): Promise<void> {
throw new Error("Missing MINIMAX_API_KEY in environment.");
}
const minimaxBaseUrl =
process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
const minimaxModelId =
process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1";
const minimaxBaseUrl = process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
const minimaxModelId = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1";
const minimaxModel: Model<"openai-completions"> = {
id: minimaxModelId,
@@ -135,9 +130,7 @@ async function main(): Promise<void> {
console.log("");
console.log("Summary (ms):");
for (const row of summary) {
console.log(
`${row.label.padEnd(7)} median=${row.med} min=${row.min} max=${row.max}`,
);
console.log(`${row.label.padEnd(7)} median=${row.med} min=${row.min} max=${row.max}`);
}
}

View File

@@ -5,27 +5,18 @@ import { fileURLToPath, pathToFileURL } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
export function getA2uiPaths(env = process.env) {
const srcDir =
env.OPENCLAW_A2UI_SRC_DIR ?? path.join(repoRoot, "src", "canvas-host", "a2ui");
const outDir =
env.OPENCLAW_A2UI_OUT_DIR ?? path.join(repoRoot, "dist", "canvas-host", "a2ui");
const srcDir = env.OPENCLAW_A2UI_SRC_DIR ?? path.join(repoRoot, "src", "canvas-host", "a2ui");
const outDir = env.OPENCLAW_A2UI_OUT_DIR ?? path.join(repoRoot, "dist", "canvas-host", "a2ui");
return { srcDir, outDir };
}
export async function copyA2uiAssets({
srcDir,
outDir,
}: {
srcDir: string;
outDir: string;
}) {
export async function copyA2uiAssets({ srcDir, outDir }: { srcDir: string; outDir: string }) {
const skipMissing = process.env.OPENCLAW_A2UI_SKIP_MISSING === "1";
try {
await fs.stat(path.join(srcDir, "index.html"));
await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
} catch (err) {
const message =
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
const message = 'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
if (skipMissing) {
console.warn(`${message} Skipping copy (OPENCLAW_A2UI_SKIP_MISSING=1).`);
return;

View File

@@ -3,19 +3,19 @@
* Copy HOOK.md files from src/hooks/bundled to dist/hooks/bundled
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, '..');
const projectRoot = path.resolve(__dirname, "..");
const srcBundled = path.join(projectRoot, 'src', 'hooks', 'bundled');
const distBundled = path.join(projectRoot, 'dist', 'hooks', 'bundled');
const srcBundled = path.join(projectRoot, "src", "hooks", "bundled");
const distBundled = path.join(projectRoot, "dist", "hooks", "bundled");
function copyHookMetadata() {
if (!fs.existsSync(srcBundled)) {
console.warn('[copy-hook-metadata] Source directory not found:', srcBundled);
console.warn("[copy-hook-metadata] Source directory not found:", srcBundled);
return;
}
@@ -31,8 +31,8 @@ function copyHookMetadata() {
const hookName = entry.name;
const srcHookDir = path.join(srcBundled, hookName);
const distHookDir = path.join(distBundled, hookName);
const srcHookMd = path.join(srcHookDir, 'HOOK.md');
const distHookMd = path.join(distHookDir, 'HOOK.md');
const srcHookMd = path.join(srcHookDir, "HOOK.md");
const distHookMd = path.join(distHookDir, "HOOK.md");
if (!fs.existsSync(srcHookMd)) {
console.warn(`[copy-hook-metadata] No HOOK.md found for ${hookName}`);
@@ -47,7 +47,7 @@ function copyHookMetadata() {
console.log(`[copy-hook-metadata] Copied ${hookName}/HOOK.md`);
}
console.log('[copy-hook-metadata] Done');
console.log("[copy-hook-metadata] Done");
}
copyHookMetadata();

View File

@@ -44,9 +44,9 @@ const parseArgs = (): Args => {
const loadAuthProfiles = (agentId: string) => {
const stateRoot =
process.env.OPENCLAW_STATE_DIR?.trim() ||
process.env.CLAWDBOT_STATE_DIR?.trim() ||
path.join(os.homedir(), ".openclaw");
process.env.OPENCLAW_STATE_DIR?.trim() ||
process.env.CLAWDBOT_STATE_DIR?.trim() ||
path.join(os.homedir(), ".openclaw");
const authPath = path.join(stateRoot, "agents", agentId, "agent", "auth-profiles.json");
if (!fs.existsSync(authPath)) throw new Error(`Missing: ${authPath}`);
const store = JSON.parse(fs.readFileSync(authPath, "utf8")) as {
@@ -99,8 +99,7 @@ const readClaudeCliKeychain = (): {
if (!oauth || typeof oauth !== "object") return null;
const accessToken = oauth.accessToken;
if (typeof accessToken !== "string" || !accessToken.trim()) return null;
const expiresAt =
typeof oauth.expiresAt === "number" ? oauth.expiresAt : undefined;
const expiresAt = typeof oauth.expiresAt === "number" ? oauth.expiresAt : undefined;
const scopes = Array.isArray(oauth.scopes)
? oauth.scopes.filter((v): v is string => typeof v === "string")
: undefined;
@@ -120,11 +119,11 @@ const chromeServiceNameForPath = (cookiePath: string): string => {
const readKeychainPassword = (service: string): string | null => {
try {
const out = execFileSync(
"security",
["find-generic-password", "-w", "-s", service],
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 },
);
const out = execFileSync("security", ["find-generic-password", "-w", "-s", service], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
timeout: 5000,
});
const pw = out.trim();
return pw ? pw : null;
} catch {
@@ -317,15 +316,16 @@ const main = async () => {
process.env.CLAUDE_AI_SESSION_KEY?.trim() ||
process.env.CLAUDE_WEB_SESSION_KEY?.trim() ||
findClaudeSessionKey()?.sessionKey;
const source =
opts.sessionKey
? "--session-key"
: process.env.CLAUDE_AI_SESSION_KEY || process.env.CLAUDE_WEB_SESSION_KEY
? "env"
: findClaudeSessionKey()?.source ?? "auto";
const source = opts.sessionKey
? "--session-key"
: process.env.CLAUDE_AI_SESSION_KEY || process.env.CLAUDE_WEB_SESSION_KEY
? "env"
: (findClaudeSessionKey()?.source ?? "auto");
if (!sessionKey) {
console.log("Claude web: no sessionKey found (try --session-key or export CLAUDE_AI_SESSION_KEY)");
console.log(
"Claude web: no sessionKey found (try --session-key or export CLAUDE_AI_SESSION_KEY)",
);
return;
}

View File

@@ -1,26 +1,26 @@
#!/usr/bin/env node
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
process.stdout.on('error', (error) => {
if (error?.code === 'EPIPE') {
process.stdout.on("error", (error) => {
if (error?.code === "EPIPE") {
process.exit(0);
}
throw error;
});
const DOCS_DIR = join(process.cwd(), 'docs');
const DOCS_DIR = join(process.cwd(), "docs");
if (!existsSync(DOCS_DIR)) {
console.error('docs:list: missing docs directory. Run from repo root.');
console.error("docs:list: missing docs directory. Run from repo root.");
process.exit(1);
}
if (!statSync(DOCS_DIR).isDirectory()) {
console.error('docs:list: docs path is not a directory.');
console.error("docs:list: docs path is not a directory.");
process.exit(1);
}
const EXCLUDED_DIRS = new Set(['archive', 'research']);
const EXCLUDED_DIRS = new Set(["archive", "research"]);
/**
* @param {unknown[]} values
@@ -49,7 +49,7 @@ function walkMarkdownFiles(dir, base = dir) {
const entries = readdirSync(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (entry.name.startsWith('.')) {
if (entry.name.startsWith(".")) {
continue;
}
const fullPath = join(dir, entry.name);
@@ -58,7 +58,7 @@ function walkMarkdownFiles(dir, base = dir) {
continue;
}
files.push(...walkMarkdownFiles(fullPath, base));
} else if (entry.isFile() && entry.name.endsWith('.md')) {
} else if (entry.isFile() && entry.name.endsWith(".md")) {
files.push(relative(base, fullPath));
}
}
@@ -70,19 +70,19 @@ function walkMarkdownFiles(dir, base = dir) {
* @returns {{ summary: string | null; readWhen: string[]; error?: string }}
*/
function extractMetadata(fullPath) {
const content = readFileSync(fullPath, 'utf8');
const content = readFileSync(fullPath, "utf8");
if (!content.startsWith('---')) {
return { summary: null, readWhen: [], error: 'missing front matter' };
if (!content.startsWith("---")) {
return { summary: null, readWhen: [], error: "missing front matter" };
}
const endIndex = content.indexOf('\n---', 3);
const endIndex = content.indexOf("\n---", 3);
if (endIndex === -1) {
return { summary: null, readWhen: [], error: 'unterminated front matter' };
return { summary: null, readWhen: [], error: "unterminated front matter" };
}
const frontMatter = content.slice(3, endIndex).trim();
const lines = frontMatter.split('\n');
const lines = frontMatter.split("\n");
let summaryLine = null;
const readWhen = [];
@@ -91,16 +91,16 @@ function extractMetadata(fullPath) {
for (const rawLine of lines) {
const line = rawLine.trim();
if (line.startsWith('summary:')) {
if (line.startsWith("summary:")) {
summaryLine = line;
collectingField = null;
continue;
}
if (line.startsWith('read_when:')) {
collectingField = 'read_when';
const inline = line.slice('read_when:'.length).trim();
if (inline.startsWith('[') && inline.endsWith(']')) {
if (line.startsWith("read_when:")) {
collectingField = "read_when";
const inline = line.slice("read_when:".length).trim();
if (inline.startsWith("[") && inline.endsWith("]")) {
try {
const parsed = JSON.parse(inline.replace(/'/g, '"'));
if (Array.isArray(parsed)) {
@@ -113,13 +113,13 @@ function extractMetadata(fullPath) {
continue;
}
if (collectingField === 'read_when') {
if (line.startsWith('- ')) {
if (collectingField === "read_when") {
if (line.startsWith("- ")) {
const hint = line.slice(2).trim();
if (hint) {
readWhen.push(hint);
}
} else if (line === '') {
} else if (line === "") {
// allow blank lines inside the list
} else {
collectingField = null;
@@ -128,23 +128,23 @@ function extractMetadata(fullPath) {
}
if (!summaryLine) {
return { summary: null, readWhen, error: 'summary key missing' };
return { summary: null, readWhen, error: "summary key missing" };
}
const summaryValue = summaryLine.slice('summary:'.length).trim();
const summaryValue = summaryLine.slice("summary:".length).trim();
const normalized = summaryValue
.replace(/^['"]|['"]$/g, '')
.replace(/\s+/g, ' ')
.replace(/^['"]|['"]$/g, "")
.replace(/\s+/g, " ")
.trim();
if (!normalized) {
return { summary: null, readWhen, error: 'summary is empty' };
return { summary: null, readWhen, error: "summary is empty" };
}
return { summary: normalized, readWhen };
}
console.log('Listing all markdown files in docs folder:');
console.log("Listing all markdown files in docs folder:");
const markdownFiles = walkMarkdownFiles(DOCS_DIR);
@@ -154,14 +154,14 @@ for (const relativePath of markdownFiles) {
if (summary) {
console.log(`${relativePath} - ${summary}`);
if (readWhen.length > 0) {
console.log(` Read when: ${readWhen.join('; ')}`);
console.log(` Read when: ${readWhen.join("; ")}`);
}
} else {
const reason = error ? ` - [${error}]` : '';
const reason = error ? ` - [${error}]` : "";
console.log(`${relativePath}${reason}`);
}
}
console.log(
'\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above (React hooks, cache directives, database work, tests, etc.), read that doc before coding, and suggest new coverage when it is missing.'
'\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above (React hooks, cache directives, database work, tests, etc.), read that doc before coding, and suggest new coverage when it is missing.',
);

View File

@@ -88,9 +88,7 @@ async function run() {
localError = error instanceof Error ? error.message : String(error);
}
console.log(
`local: ${localStatus} len=${localText.length} title=${truncate(localTitle, 80)}`
);
console.log(`local: ${localStatus} len=${localText.length} title=${truncate(localTitle, 80)}`);
if (localError) console.log(`local error: ${localError}`);
if (localText) console.log(`local sample: ${truncate(localText)}`);
@@ -111,7 +109,7 @@ async function run() {
`firecrawl: ok len=${firecrawl.text.length} title=${truncate(
firecrawl.title ?? "",
80,
)} status=${firecrawl.status ?? "n/a"}`
)} status=${firecrawl.status ?? "n/a"}`,
);
if (firecrawl.warning) console.log(`firecrawl warning: ${firecrawl.warning}`);
if (firecrawl.text) console.log(`firecrawl sample: ${truncate(firecrawl.text)}`);

View File

@@ -3,16 +3,7 @@ import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const OXFMT_EXTENSIONS = new Set([
".cjs",
".js",
".json",
".jsonc",
".jsx",
".mjs",
".ts",
".tsx",
]);
const OXFMT_EXTENSIONS = new Set([".cjs", ".js", ".json", ".jsonc", ".jsx", ".mjs", ".ts", ".tsx"]);
function getRepoRoot() {
const here = path.dirname(fileURLToPath(import.meta.url));
@@ -40,9 +31,10 @@ function normalizeGitPath(filePath) {
function filterOxfmtTargets(paths) {
return paths
.map(normalizeGitPath)
.filter((filePath) =>
(filePath.startsWith("src/") || filePath.startsWith("test/")) &&
OXFMT_EXTENSIONS.has(path.posix.extname(filePath)),
.filter(
(filePath) =>
(filePath.startsWith("src/") || filePath.startsWith("test/")) &&
OXFMT_EXTENSIONS.has(path.posix.extname(filePath)),
);
}
@@ -94,13 +86,10 @@ function stageFiles(repoRoot, files) {
function main() {
const repoRoot = getRepoRoot();
const staged = getGitPaths([
"diff",
"--cached",
"--name-only",
"-z",
"--diff-filter=ACMR",
], repoRoot);
const staged = getGitPaths(
["diff", "--cached", "--name-only", "-z", "--diff-filter=ACMR"],
repoRoot,
);
const targets = filterOxfmtTargets(staged);
if (targets.length === 0) return;

View File

@@ -1,36 +1,34 @@
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { setupGitHooks } from './setup-git-hooks.js';
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { setupGitHooks } from "./setup-git-hooks.js";
function detectPackageManager(ua = process.env.npm_config_user_agent ?? '') {
function detectPackageManager(ua = process.env.npm_config_user_agent ?? "") {
// Examples:
// - "pnpm/10.23.0 npm/? node/v22.21.1 darwin arm64"
// - "npm/10.9.4 node/v22.12.0 linux x64"
// - "bun/1.2.2"
const normalized = String(ua).trim();
if (normalized.startsWith('pnpm/')) return 'pnpm';
if (normalized.startsWith('bun/')) return 'bun';
if (normalized.startsWith('npm/')) return 'npm';
if (normalized.startsWith('yarn/')) return 'yarn';
return 'unknown';
if (normalized.startsWith("pnpm/")) return "pnpm";
if (normalized.startsWith("bun/")) return "bun";
if (normalized.startsWith("npm/")) return "npm";
if (normalized.startsWith("yarn/")) return "yarn";
return "unknown";
}
function shouldApplyPnpmPatchedDependenciesFallback(
pm = detectPackageManager(),
) {
function shouldApplyPnpmPatchedDependenciesFallback(pm = detectPackageManager()) {
// pnpm already applies pnpm.patchedDependencies itself; re-applying would fail.
return pm !== 'pnpm';
return pm !== "pnpm";
}
function getRepoRoot() {
const here = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(here, '..');
return path.resolve(here, "..");
}
function ensureExecutable(targetPath) {
if (process.platform === 'win32') return;
if (process.platform === "win32") return;
if (!fs.existsSync(targetPath)) return;
try {
const mode = fs.statSync(targetPath).mode & 0o777;
@@ -42,32 +40,32 @@ function ensureExecutable(targetPath) {
}
function hasGit(repoRoot) {
const result = spawnSync('git', ['--version'], {
const result = spawnSync("git", ["--version"], {
cwd: repoRoot,
stdio: 'ignore',
stdio: "ignore",
});
return result.status === 0;
}
function extractPackageName(key) {
if (key.startsWith('@')) {
const idx = key.indexOf('@', 1);
if (key.startsWith("@")) {
const idx = key.indexOf("@", 1);
if (idx === -1) return key;
return key.slice(0, idx);
}
const idx = key.lastIndexOf('@');
const idx = key.lastIndexOf("@");
if (idx <= 0) return key;
return key.slice(0, idx);
}
function stripPrefix(p) {
if (p.startsWith('a/') || p.startsWith('b/')) return p.slice(2);
if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
return p;
}
function parseRange(segment) {
// segment: "-12,5" or "+7"
const [startRaw, countRaw] = segment.slice(1).split(',');
const [startRaw, countRaw] = segment.slice(1).split(",");
const start = Number.parseInt(startRaw, 10);
const count = countRaw ? Number.parseInt(countRaw, 10) : 1;
if (Number.isNaN(start) || Number.isNaN(count)) {
@@ -77,12 +75,12 @@ function parseRange(segment) {
}
function parsePatch(patchText) {
const lines = patchText.split('\n');
const lines = patchText.split("\n");
const files = [];
let i = 0;
while (i < lines.length) {
if (!lines[i].startsWith('diff --git ')) {
if (!lines[i].startsWith("diff --git ")) {
i += 1;
continue;
}
@@ -91,22 +89,20 @@ function parsePatch(patchText) {
i += 1;
// Skip index line(s)
while (i < lines.length && lines[i].startsWith('index ')) i += 1;
while (i < lines.length && lines[i].startsWith("index ")) i += 1;
if (i < lines.length && lines[i].startsWith('--- ')) {
if (i < lines.length && lines[i].startsWith("--- ")) {
file.oldPath = stripPrefix(lines[i].slice(4).trim());
i += 1;
}
if (i < lines.length && lines[i].startsWith('+++ ')) {
if (i < lines.length && lines[i].startsWith("+++ ")) {
file.newPath = stripPrefix(lines[i].slice(4).trim());
i += 1;
}
while (i < lines.length && lines[i].startsWith('@@')) {
while (i < lines.length && lines[i].startsWith("@@")) {
const header = lines[i];
const match = /^@@\s+(-\d+(?:,\d+)?)\s+(\+\d+(?:,\d+)?)\s+@@/.exec(
header,
);
const match = /^@@\s+(-\d+(?:,\d+)?)\s+(\+\d+(?:,\d+)?)\s+@@/.exec(header);
if (!match) throw new Error(`invalid hunk header: ${header}`);
const oldRange = parseRange(match[1]);
const newRange = parseRange(match[2]);
@@ -115,12 +111,12 @@ function parsePatch(patchText) {
const hunkLines = [];
while (i < lines.length) {
const line = lines[i];
if (line.startsWith('@@') || line.startsWith('diff --git ')) break;
if (line === '') {
if (line.startsWith("@@") || line.startsWith("diff --git ")) break;
if (line === "") {
i += 1;
continue;
}
if (line.startsWith('\\ No newline at end of file')) {
if (line.startsWith("\\ No newline at end of file")) {
i += 1;
continue;
}
@@ -149,16 +145,16 @@ function readFileLines(targetPath) {
if (!fs.existsSync(targetPath)) {
throw new Error(`target file missing: ${targetPath}`);
}
const raw = fs.readFileSync(targetPath, 'utf-8');
const hasTrailingNewline = raw.endsWith('\n');
const parts = raw.split('\n');
const raw = fs.readFileSync(targetPath, "utf-8");
const hasTrailingNewline = raw.endsWith("\n");
const parts = raw.split("\n");
if (hasTrailingNewline) parts.pop();
return { lines: parts, hasTrailingNewline };
}
function writeFileLines(targetPath, lines, hadTrailingNewline) {
const content = lines.join('\n') + (hadTrailingNewline ? '\n' : '');
fs.writeFileSync(targetPath, content, 'utf-8');
const content = lines.join("\n") + (hadTrailingNewline ? "\n" : "");
fs.writeFileSync(targetPath, content, "utf-8");
}
function applyHunk(lines, hunk, offset) {
@@ -166,7 +162,7 @@ function applyHunk(lines, hunk, offset) {
const expected = [];
for (const raw of hunk.lines) {
const marker = raw[0];
if (marker === ' ' || marker === '+') {
if (marker === " " || marker === "+") {
expected.push(raw.slice(1));
}
}
@@ -187,21 +183,21 @@ function applyHunk(lines, hunk, offset) {
for (const raw of hunk.lines) {
const marker = raw[0];
const text = raw.slice(1);
if (marker === ' ') {
if (marker === " ") {
if (lines[cursor] !== text) {
throw new Error(
`context mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? '<eof>'}"`,
`context mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? "<eof>"}"`,
);
}
cursor += 1;
} else if (marker === '-') {
} else if (marker === "-") {
if (lines[cursor] !== text) {
throw new Error(
`delete mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? '<eof>'}"`,
`delete mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? "<eof>"}"`,
);
}
lines.splice(cursor, 1);
} else if (marker === '+') {
} else if (marker === "+") {
lines.splice(cursor, 0, text);
cursor += 1;
} else {
@@ -214,11 +210,11 @@ function applyHunk(lines, hunk, offset) {
}
function applyPatchToFile(targetDir, filePatch) {
if (filePatch.newPath === '/dev/null') {
if (filePatch.newPath === "/dev/null") {
// deletion not needed for our patches
return;
}
const relPath = stripPrefix(filePatch.newPath ?? filePatch.oldPath ?? '');
const relPath = stripPrefix(filePatch.newPath ?? filePatch.oldPath ?? "");
const targetPath = path.join(targetDir, relPath);
const { lines, hasTrailingNewline } = readFileLines(targetPath);
@@ -232,10 +228,7 @@ function applyPatchToFile(targetDir, filePatch) {
function applyPatchSet({ patchText, targetDir }) {
let resolvedTarget = path.resolve(targetDir);
if (
!fs.existsSync(resolvedTarget) ||
!fs.statSync(resolvedTarget).isDirectory()
) {
if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isDirectory()) {
console.warn(`[postinstall] skip missing target: ${resolvedTarget}`);
return;
}
@@ -254,7 +247,7 @@ function applyPatchFile({ patchPath, targetDir }) {
if (!fs.existsSync(absPatchPath)) {
throw new Error(`missing patch: ${absPatchPath}`);
}
const patchText = fs.readFileSync(absPatchPath, 'utf-8');
const patchText = fs.readFileSync(absPatchPath, "utf-8");
applyPatchSet({ patchText, targetDir });
}
@@ -262,20 +255,20 @@ function trySetupCompletion(repoRoot) {
// Skip in CI or if explicitly disabled
if (process.env.CI || process.env.OPENCLAW_SKIP_COMPLETION_SETUP) return;
const binPath = path.join(repoRoot, 'openclaw.mjs');
const binPath = path.join(repoRoot, "openclaw.mjs");
if (!fs.existsSync(binPath)) return;
// In development, dist might not exist yet during postinstall
const distEntry = path.join(repoRoot, 'dist', 'index.js');
const distEntry = path.join(repoRoot, "dist", "index.js");
if (!fs.existsSync(distEntry)) return;
try {
// Run with OPENCLAW_SKIP_POSTINSTALL to avoid any weird recursion,
// though distinct from this script.
spawnSync(process.execPath, [binPath, 'completion', '--install', '--yes'], {
spawnSync(process.execPath, [binPath, "completion", "--install", "--yes"], {
cwd: repoRoot,
stdio: 'inherit',
env: { ...process.env, OPENCLAW_SKIP_POSTINSTALL: '1' },
stdio: "inherit",
env: { ...process.env, OPENCLAW_SKIP_POSTINSTALL: "1" },
});
} catch (err) {
// Ignore errors to not break install
@@ -286,7 +279,7 @@ function main() {
const repoRoot = getRepoRoot();
process.chdir(repoRoot);
ensureExecutable(path.join(repoRoot, 'dist', '/entry.js'));
ensureExecutable(path.join(repoRoot, "dist", "/entry.js"));
setupGitHooks({ repoRoot });
trySetupCompletion(repoRoot);
@@ -294,18 +287,18 @@ function main() {
return;
}
const pkgPath = path.join(repoRoot, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const pkgPath = path.join(repoRoot, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
const patched = pkg?.pnpm?.patchedDependencies ?? {};
// Bun does not support pnpm.patchedDependencies. Apply these patch files to
// node_modules packages as a best-effort compatibility layer.
for (const [key, relPatchPath] of Object.entries(patched)) {
if (typeof relPatchPath !== 'string' || !relPatchPath.trim()) continue;
if (typeof relPatchPath !== "string" || !relPatchPath.trim()) continue;
const pkgName = extractPackageName(String(key));
if (!pkgName) continue;
applyPatchFile({
targetDir: path.join('node_modules', ...pkgName.split('/')),
targetDir: path.join("node_modules", ...pkgName.split("/")),
patchPath: relPatchPath,
});
}
@@ -313,10 +306,10 @@ function main() {
try {
const skip =
process.env.OPENCLAW_SKIP_POSTINSTALL === '1' ||
process.env.CLAWDBOT_SKIP_POSTINSTALL === '1' ||
process.env.VITEST === 'true' ||
process.env.NODE_ENV === 'test';
process.env.OPENCLAW_SKIP_POSTINSTALL === "1" ||
process.env.CLAWDBOT_SKIP_POSTINSTALL === "1" ||
process.env.VITEST === "true" ||
process.env.NODE_ENV === "test";
if (!skip) {
main();

View File

@@ -1,11 +1,7 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
ErrorCodes,
PROTOCOL_VERSION,
ProtocolSchemas,
} from "../src/gateway/protocol/schema.js";
import { ErrorCodes, PROTOCOL_VERSION, ProtocolSchemas } from "../src/gateway/protocol/schema.js";
type JsonSchema = {
type?: string | string[];
@@ -19,14 +15,7 @@ type JsonSchema = {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, "..");
const outPaths = [
path.join(
repoRoot,
"apps",
"macos",
"Sources",
"OpenClawProtocol",
"GatewayModels.swift",
),
path.join(repoRoot, "apps", "macos", "Sources", "OpenClawProtocol", "GatewayModels.swift"),
path.join(
repoRoot,
"apps",
@@ -38,7 +27,9 @@ const outPaths = [
),
];
const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by hand\nimport Foundation\n\npublic let GATEWAY_PROTOCOL_VERSION = ${PROTOCOL_VERSION}\n\npublic enum ErrorCode: String, Codable, Sendable {\n${Object.values(ErrorCodes)
const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by hand\nimport Foundation\n\npublic let GATEWAY_PROTOCOL_VERSION = ${PROTOCOL_VERSION}\n\npublic enum ErrorCode: String, Codable, Sendable {\n${Object.values(
ErrorCodes,
)
.map((c) => ` case ${camelCase(c)} = "${c}"`)
.join("\n")}\n}\n`;
@@ -133,25 +124,27 @@ function emitStruct(name: string, schema: JsonSchema): string {
codingKeys.push(` case ${propName}`);
}
}
lines.push("\n public init(\n" +
Object.entries(props)
.map(([key, prop]) => {
const propName = safeName(key);
const req = required.has(key);
return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`;
})
.join(",\n") +
"\n ) {\n" +
Object.entries(props)
.map(([key]) => {
const propName = safeName(key);
return ` self.${propName} = ${propName}`;
})
.join("\n") +
"\n }\n" +
" private enum CodingKeys: String, CodingKey {\n" +
codingKeys.join("\n") +
"\n }\n}");
lines.push(
"\n public init(\n" +
Object.entries(props)
.map(([key, prop]) => {
const propName = safeName(key);
const req = required.has(key);
return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`;
})
.join(",\n") +
"\n ) {\n" +
Object.entries(props)
.map(([key]) => {
const propName = safeName(key);
return ` self.${propName} = ${propName}`;
})
.join("\n") +
"\n }\n" +
" private enum CodingKeys: String, CodingKey {\n" +
codingKeys.join("\n") +
"\n }\n}",
);
lines.push("");
return lines.join("\n");
}
@@ -209,9 +202,7 @@ function emitGatewayFrame(): string {
}
async function generate() {
const definitions = Object.entries(ProtocolSchemas) as Array<
[string, JsonSchema]
>;
const definitions = Object.entries(ProtocolSchemas) as Array<[string, JsonSchema]>;
for (const [name, schema] of definitions) {
schemaNameByObject.set(schema as object, name);

View File

@@ -7,11 +7,7 @@ import { join, resolve } from "node:path";
type PackFile = { path: string };
type PackResult = { files?: PackFile[] };
const requiredPaths = [
"dist/discord/send.js",
"dist/hooks/gmail.js",
"dist/whatsapp/normalize.js",
];
const requiredPaths = ["dist/discord/send.js", "dist/hooks/gmail.js", "dist/whatsapp/normalize.js"];
const forbiddenPrefixes = ["dist/OpenClaw.app/"];
type PackageJson = {

View File

@@ -1,24 +1,21 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
const args = process.argv.slice(2);
const env = { ...process.env };
const cwd = process.cwd();
const compilerOverride = env.OPENCLAW_TS_COMPILER ?? env.CLAWDBOT_TS_COMPILER;
const compiler = compilerOverride === 'tsc' ? 'tsc' : 'tsgo';
const projectArgs = ['--project', 'tsconfig.json'];
const compiler = compilerOverride === "tsc" ? "tsc" : "tsgo";
const projectArgs = ["--project", "tsconfig.json"];
const distRoot = path.join(cwd, 'dist');
const distEntry = path.join(distRoot, '/entry.js');
const buildStampPath = path.join(distRoot, '.buildstamp');
const srcRoot = path.join(cwd, 'src');
const configFiles = [
path.join(cwd, 'tsconfig.json'),
path.join(cwd, 'package.json'),
];
const distRoot = path.join(cwd, "dist");
const distEntry = path.join(distRoot, "/entry.js");
const buildStampPath = path.join(distRoot, ".buildstamp");
const srcRoot = path.join(cwd, "src");
const configFiles = [path.join(cwd, "tsconfig.json"), path.join(cwd, "package.json")];
const statMtime = (filePath) => {
try {
@@ -30,10 +27,10 @@ const statMtime = (filePath) => {
const isExcludedSource = (filePath) => {
const relativePath = path.relative(srcRoot, filePath);
if (relativePath.startsWith('..')) return false;
if (relativePath.startsWith("..")) return false;
return (
relativePath.endsWith('.test.ts') ||
relativePath.endsWith('.test.tsx') ||
relativePath.endsWith(".test.ts") ||
relativePath.endsWith(".test.tsx") ||
relativePath.endsWith(`test-helpers.ts`)
);
};
@@ -69,7 +66,7 @@ const findLatestMtime = (dirPath, shouldSkip) => {
};
const shouldBuild = () => {
if (env.OPENCLAW_FORCE_BUILD === '1') return true;
if (env.OPENCLAW_FORCE_BUILD === "1") return true;
const stampMtime = statMtime(buildStampPath);
if (stampMtime == null) return true;
if (statMtime(distEntry) == null) return true;
@@ -85,18 +82,18 @@ const shouldBuild = () => {
};
const logRunner = (message) => {
if (env.OPENCLAW_RUNNER_LOG === '0') return;
if (env.OPENCLAW_RUNNER_LOG === "0") return;
process.stderr.write(`[openclaw] ${message}\n`);
};
const runNode = () => {
const nodeProcess = spawn(process.execPath, ['openclaw.mjs', ...args], {
const nodeProcess = spawn(process.execPath, ["openclaw.mjs", ...args], {
cwd,
env,
stdio: 'inherit',
stdio: "inherit",
});
nodeProcess.on('exit', (exitCode, exitSignal) => {
nodeProcess.on("exit", (exitCode, exitSignal) => {
if (exitSignal) {
process.exit(1);
}
@@ -110,29 +107,25 @@ const writeBuildStamp = () => {
fs.writeFileSync(buildStampPath, `${Date.now()}\n`);
} catch (error) {
// Best-effort stamp; still allow the runner to start.
logRunner(
`Failed to write build stamp: ${error?.message ?? 'unknown error'}`,
);
logRunner(`Failed to write build stamp: ${error?.message ?? "unknown error"}`);
}
};
if (!shouldBuild()) {
runNode();
} else {
logRunner('Building TypeScript (dist is stale).');
const pnpmArgs = ['exec', compiler, ...projectArgs];
const buildCmd = process.platform === 'win32' ? 'cmd.exe' : 'pnpm';
logRunner("Building TypeScript (dist is stale).");
const pnpmArgs = ["exec", compiler, ...projectArgs];
const buildCmd = process.platform === "win32" ? "cmd.exe" : "pnpm";
const buildArgs =
process.platform === 'win32'
? ['/d', '/s', '/c', 'pnpm', ...pnpmArgs]
: pnpmArgs;
process.platform === "win32" ? ["/d", "/s", "/c", "pnpm", ...pnpmArgs] : pnpmArgs;
const build = spawn(buildCmd, buildArgs, {
cwd,
env,
stdio: 'inherit',
stdio: "inherit",
});
build.on('exit', (code, signal) => {
build.on("exit", (code, signal) => {
if (signal) {
process.exit(1);
}

View File

@@ -31,9 +31,7 @@ insert.run("c", vec([0.2, 0.2, 0, 0]));
const query = vec([1, 0, 0, 0]);
const rows = db
.prepare(
"SELECT id, vec_distance_cosine(embedding, ?) AS dist FROM v ORDER BY dist ASC"
)
.prepare("SELECT id, vec_distance_cosine(embedding, ?) AS dist FROM v ORDER BY dist ASC")
.all(query);
console.log("sqlite-vec ok");

View File

@@ -36,16 +36,7 @@ for (const label of missing) {
const color = pickColor(label);
execFileSync(
"gh",
[
"api",
"-X",
"POST",
`repos/${repo}/labels`,
"-f",
`name=${label}`,
"-f",
`color=${color}`,
],
["api", "-X", "POST", `repos/${repo}/labels`, "-f", `name=${label}`, "-f", `color=${color}`],
{ stdio: "inherit" },
);
console.log(`Created label: ${label}`);
@@ -97,11 +88,9 @@ function resolveRepo(): string {
}
function fetchExistingLabels(repo: string): Map<string, RepoLabel> {
const raw = execFileSync(
"gh",
["api", `repos/${repo}/labels?per_page=100`, "--paginate"],
{ encoding: "utf8" },
);
const raw = execFileSync("gh", ["api", `repos/${repo}/labels?per_page=100`, "--paginate"], {
encoding: "utf8",
});
const labels = JSON.parse(raw) as RepoLabel[];
return new Map(labels.map((label) => [label.name, label]));
}

View File

@@ -49,9 +49,7 @@ function replaceBlockLines(
}
function renderKimiK2Ids(prefix: string) {
return MOONSHOT_KIMI_K2_MODELS.map(
(model) => `- \`${prefix}${model.id}\``,
);
return MOONSHOT_KIMI_K2_MODELS.map((model) => `- \`${prefix}${model.id}\``);
}
function renderMoonshotAliases() {
@@ -85,10 +83,7 @@ function renderMoonshotModels() {
async function syncMoonshotDocs() {
const moonshotDoc = path.join(repoRoot, "docs/providers/moonshot.md");
const conceptsDoc = path.join(
repoRoot,
"docs/concepts/model-providers.md",
);
const conceptsDoc = path.join(repoRoot, "docs/concepts/model-providers.md");
let moonshotText = await readFile(moonshotDoc, "utf8");
moonshotText = replaceBlockLines(

View File

@@ -16,7 +16,9 @@ if (!targetVersion) {
}
const extensionsDir = resolve("extensions");
const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory());
const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
entry.isDirectory(),
);
const updated: string[] = [];
const changelogged: string[] = [];
@@ -67,5 +69,5 @@ for (const dir of dirs) {
}
console.log(
`Synced plugin versions to ${targetVersion}. Updated: ${updated.length}. Changelogged: ${changelogged.length}. Skipped: ${skipped.length}.`
`Synced plugin versions to ${targetVersion}. Updated: ${updated.length}. Changelogged: ${changelogged.length}. Skipped: ${skipped.length}.`,
);

View File

@@ -24,10 +24,17 @@ const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macO
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
const isWindowsCi = isCI && isWindows;
const shardOverride = Number.parseInt(process.env.OPENCLAW_TEST_SHARDS ?? "", 10);
const shardCount = isWindowsCi ? (Number.isFinite(shardOverride) && shardOverride > 1 ? shardOverride : 2) : 1;
const windowsCiArgs = isWindowsCi ? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"] : [];
const shardCount = isWindowsCi
? Number.isFinite(shardOverride) && shardOverride > 1
? shardOverride
: 2
: 1;
const windowsCiArgs = isWindowsCi
? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"]
: [];
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
const resolvedOverride =
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway");
const serialRuns = isWindowsCi ? runs : runs.filter((entry) => entry.name === "gateway");
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));

View File

@@ -11,9 +11,7 @@ const uiDir = path.join(repoRoot, "ui");
function usage() {
// keep this tiny; it's invoked from npm scripts too
process.stderr.write(
"Usage: node scripts/ui.js <install|dev|build|test> [...args]\n",
);
process.stderr.write("Usage: node scripts/ui.js <install|dev|build|test> [...args]\n");
}
function which(cmd) {
@@ -24,9 +22,7 @@ function which(cmd) {
.filter(Boolean);
const extensions =
process.platform === "win32"
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM")
.split(";")
.filter(Boolean)
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)
: [""];
for (const entry of paths) {
for (const ext of extensions) {
@@ -122,11 +118,8 @@ if (action === "install") run(runner.cmd, ["install", ...rest]);
else {
if (!depsInstalled(action === "test" ? "test" : "build")) {
const installEnv =
action === "build"
? { ...process.env, NODE_ENV: "production" }
: process.env;
const installArgs =
action === "build" ? ["install", "--prod"] : ["install"];
action === "build" ? { ...process.env, NODE_ENV: "production" } : process.env;
const installArgs = action === "build" ? ["install", "--prod"] : ["install"];
runSync(runner.cmd, installArgs, installEnv);
}
run(runner.cmd, ["run", script, ...rest]);

View File

@@ -329,7 +329,7 @@ function resolveLogin(
email: string | null,
apiByLogin: Map<string, User>,
nameToLogin: Record<string, string>,
emailToLogin: Record<string, string>
emailToLogin: Record<string, string>,
): string | null {
if (email && emailToLogin[email]) {
return emailToLogin[email];
@@ -379,7 +379,7 @@ function resolveLogin(
function guessLoginFromEmailName(
name: string,
email: string,
apiByLogin: Map<string, User>
apiByLogin: Map<string, User>,
): string | null {
const local = email.split("@", 1)[0]?.trim();
if (!local) {
@@ -410,7 +410,7 @@ function normalizeIdentifier(value: string): string {
}
function parseReadmeEntries(
content: string
content: string,
): Array<{ display: string; html_url: string; avatar_url: string }> {
const start = content.indexOf('<p align="left">');
const end = content.indexOf("</p>", start);
@@ -458,7 +458,11 @@ function fallbackHref(value: string): string {
return encoded ? `https://github.com/search?q=${encoded}` : "https://github.com";
}
function pickDisplay(baseName: string | null | undefined, login: string, existing?: string): string {
function pickDisplay(
baseName: string | null | undefined,
login: string,
existing?: string,
): string {
const key = login.toLowerCase();
if (displayName[key]) {
return displayName[key];

View File

@@ -42,7 +42,4 @@ const buildInfo = {
};
fs.mkdirSync(distDir, { recursive: true });
fs.writeFileSync(
path.join(distDir, "build-info.json"),
`${JSON.stringify(buildInfo, null, 2)}\n`,
);
fs.writeFileSync(path.join(distDir, "build-info.json"), `${JSON.stringify(buildInfo, null, 2)}\n`);

View File

@@ -20,9 +20,7 @@ function pickAnthropicEnv(): { type: "oauth" | "api"; value: string } | null {
}
function pickZaiKey(): string | null {
return (
process.env.ZAI_API_KEY?.trim() ?? process.env.Z_AI_API_KEY?.trim() ?? null
);
return process.env.ZAI_API_KEY?.trim() ?? process.env.Z_AI_API_KEY?.trim() ?? null;
}
async function runCommand(
@@ -74,9 +72,7 @@ async function main() {
process.exit(1);
}
const baseDir = await fs.mkdtemp(
path.join(os.tmpdir(), "openclaw-zai-fallback-"),
);
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-zai-fallback-"));
const stateDir = path.join(baseDir, "state");
const configPath = path.join(baseDir, "openclaw.json");
await fs.mkdir(stateDir, { recursive: true });
@@ -130,28 +126,14 @@ async function main() {
"Then use the read tool to display the file contents. Reply with just the file contents.";
const run1 = await runCommand(
"run1",
[
"openclaw",
"agent",
"--local",
"--session-id",
sessionId,
"--message",
toolPrompt,
],
["openclaw", "agent", "--local", "--session-id", sessionId, "--message", toolPrompt],
envValidAnthropic,
);
if (run1.code !== 0) {
process.exit(run1.code ?? 1);
}
const sessionFile = path.join(
stateDir,
"agents",
"main",
"sessions",
`${sessionId}.jsonl`,
);
const sessionFile = path.join(stateDir, "agents", "main", "sessions", `${sessionId}.jsonl`);
const transcript = await fs.readFile(sessionFile, "utf8").catch(() => "");
if (!transcript.includes('"toolResult"')) {
console.warn("Warning: no toolResult entries detected in session history.");
@@ -162,15 +144,7 @@ async function main() {
"What is the content of zai-fallback-tool.txt? Reply with just the contents.";
const run2 = await runCommand(
"run2",
[
"openclaw",
"agent",
"--local",
"--session-id",
sessionId,
"--message",
followupPrompt,
],
["openclaw", "agent", "--local", "--session-id", sessionId, "--message", followupPrompt],
envInvalidAnthropic,
);