Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

242 lines
6.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { constants } from "node:fs";
import {
access,
cp,
mkdir,
readdir,
readFile,
rm,
stat
} from "node:fs/promises";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const skillName = "tutti-ui-system";
const devCacheDirectoryName = ".tutti-ui-system-dev";
const companionFiles = ["AGENTS.md", "ui-system.md"];
const options = parseArgs(process.argv.slice(2));
if (options.help) {
printHelp();
process.exit(0);
}
const targetRoot = resolve(options.cwd, ".codex", "skills");
const targetDirectory = join(targetRoot, skillName);
const sourceRoot = await resolveSourceRoot(options.cwd);
const sourceDirectory = join(sourceRoot, "agent", skillName);
await assertReadableDirectory(sourceDirectory);
await Promise.all(
companionFiles.map((fileName) =>
assertReadableFile(join(sourceRoot, fileName))
)
);
await mkdir(targetRoot, { recursive: true });
if (await pathExists(targetDirectory)) {
const targetStats = await stat(targetDirectory);
if (!targetStats.isDirectory()) {
throw new Error(`Target exists and is not a directory: ${targetDirectory}`);
}
if (!options.force) {
if (await directoriesMatch(sourceRoot, sourceDirectory, targetDirectory)) {
console.log(
`tutti-ui-system skill already configured at ${targetDirectory}`
);
process.exit(0);
}
throw new Error(
`Target skill already exists with local changes: ${targetDirectory}\n` +
"Run with --force to replace it."
);
}
await rm(targetDirectory, { recursive: true, force: true });
}
await cp(sourceDirectory, targetDirectory, {
errorOnExist: false,
force: true,
recursive: true
});
await Promise.all(
companionFiles.map((fileName) =>
cp(join(sourceRoot, fileName), join(targetDirectory, fileName), {
errorOnExist: false,
force: true,
recursive: false
})
)
);
console.log(`Installed tutti-ui-system skill to ${targetDirectory}`);
console.log(
"Agents can now load it from .codex/skills/tutti-ui-system/SKILL.md"
);
function parseArgs(args) {
const parsed = {
cwd: process.cwd(),
force: false,
help: false
};
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--force") {
parsed.force = true;
continue;
}
if (arg === "--help" || arg === "-h") {
parsed.help = true;
continue;
}
if (arg === "--cwd") {
const cwd = args[index + 1];
if (!cwd) {
throw new Error("--cwd requires a directory path");
}
parsed.cwd = resolve(cwd);
index += 1;
continue;
}
throw new Error(`Unknown option: ${arg}`);
}
return parsed;
}
function printHelp() {
console.log(`Usage: tutti-ui-system-install-skill [options]
Copies the bundled Tutti UI System skill into the current repository.
Options:
--cwd <path> Repository root to configure. Defaults to the current directory.
--force Replace an existing .codex/skills/tutti-ui-system directory.
-h, --help Show this help message.`);
}
async function assertReadableDirectory(path) {
await access(path, constants.R_OK);
const pathStats = await stat(path);
if (!pathStats.isDirectory()) {
throw new Error(`Expected directory: ${path}`);
}
}
async function assertReadableFile(path) {
await access(path, constants.R_OK);
const pathStats = await stat(path);
if (!pathStats.isFile()) {
throw new Error(`Expected file: ${path}`);
}
}
async function pathExists(path) {
try {
await access(path, constants.F_OK);
return true;
} catch {
return false;
}
}
async function resolveSourceRoot(cwd) {
const devCacheRoot = resolve(cwd, devCacheDirectoryName);
const devCacheSkillDirectory = join(devCacheRoot, "agent", skillName);
if (
(await pathExists(devCacheSkillDirectory)) &&
(await pathExists(join(devCacheRoot, "AGENTS.md"))) &&
(await pathExists(join(devCacheRoot, "ui-system.md")))
) {
return devCacheRoot;
}
return packageRoot;
}
async function directoriesMatch(sourceRoot, sourceDirectory, rightDirectory) {
const sourceEntries = await listBundleFiles(sourceRoot, sourceDirectory);
const leftEntries = sourceEntries.map((entry) => entry.targetRelativePath);
const rightEntries = await listFiles(rightDirectory, rightDirectory);
if (leftEntries.length !== rightEntries.length) {
return false;
}
for (let index = 0; index < leftEntries.length; index += 1) {
if (leftEntries[index] !== rightEntries[index]) {
return false;
}
const leftFile = sourceEntries[index].sourceAbsolutePath;
const rightFile = join(rightDirectory, rightEntries[index]);
const [leftContent, rightContent] = await Promise.all([
readFile(leftFile),
readFile(rightFile)
]);
if (!leftContent.equals(rightContent)) {
return false;
}
}
return true;
}
async function listBundleFiles(sourceRoot, sourceDirectory) {
const skillFiles = await listFiles(sourceDirectory, sourceDirectory);
const companionEntries = companionFiles.map((fileName) => ({
sourceAbsolutePath: join(sourceRoot, fileName),
targetRelativePath: fileName
}));
return skillFiles
.map((relativePath) => ({
sourceAbsolutePath: join(sourceDirectory, relativePath),
targetRelativePath: relativePath
}))
.concat(companionEntries)
.sort((left, right) =>
left.targetRelativePath.localeCompare(right.targetRelativePath)
);
}
async function listFiles(rootDirectory, directory) {
const entries = await readdir(directory, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const absolutePath = join(directory, entry.name);
if (entry.isDirectory()) {
return listFiles(rootDirectory, absolutePath);
}
if (!entry.isFile()) {
return [];
}
return relative(rootDirectory, absolutePath);
})
);
return files.flat().sort((left, right) => left.localeCompare(right));
}