| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- #!/usr/bin/env node
- import { spawnSync } from "node:child_process";
- import fs from "node:fs";
- import os from "node:os";
- import path from "node:path";
- import { fileURLToPath } from "node:url";
- const SOURCE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
- const INVOCATION_ROOT = path.resolve(process.cwd());
- const invocationRelativeToSource = path.relative(SOURCE_ROOT, INVOCATION_ROOT);
- const invokedFromSource = invocationRelativeToSource === ""
- || (!invocationRelativeToSource.startsWith("..") && !path.isAbsolute(invocationRelativeToSource));
- const WORKSPACE_ROOT = invokedFromSource ? path.dirname(SOURCE_ROOT) : INVOCATION_ROOT;
- const DEFAULT_TARGET = path.join(os.homedir(), ".claude", "plugins", "fmode-image-set");
- const WORKSPACE_TARGET = path.join(WORKSPACE_ROOT, ".claude", "skills", "fmode-image-set");
- const EXCLUDED = new Set(["node_modules", "生成结果", "memory", "outputs", ".tmp", ".git", ".env", ".env.local"]);
- EXCLUDED.add(".claude");
- EXCLUDED.add(".codex");
- function parseArgs(argv) {
- const command = argv[0] && !argv[0].startsWith("--") ? argv[0] : "install";
- const options = {
- command,
- target: command === "workspace" ? WORKSPACE_TARGET : DEFAULT_TARGET,
- workspace: command === "workspace",
- smoke: argv.includes("--smoke"),
- skipInstall: argv.includes("--skip-install"),
- force: argv.includes("--force"),
- };
- const targetIndex = argv.indexOf("--target");
- if (targetIndex >= 0 && argv[targetIndex + 1]) options.target = path.resolve(argv[targetIndex + 1]);
- return options;
- }
- function isInside(parent, candidate) {
- const relative = path.relative(path.resolve(parent), path.resolve(candidate));
- return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
- }
- function safeTarget(target, force) {
- return force
- || path.resolve(target) === path.resolve(DEFAULT_TARGET)
- || isInside(path.join(WORKSPACE_ROOT, ".claude", "plugins"), target)
- || isInside(path.join(WORKSPACE_ROOT, ".claude", "skills"), target);
- }
- function copyPackage(source, target) {
- fs.mkdirSync(target, { recursive: true });
- for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
- if (EXCLUDED.has(entry.name) || entry.name.endsWith(".tgz")) continue;
- const from = path.join(source, entry.name);
- const to = path.join(target, entry.name);
- if (entry.isDirectory()) copyPackage(from, to);
- else if (entry.isFile()) fs.copyFileSync(from, to);
- }
- }
- function run(command, args, cwd) {
- const useCmd = process.platform === "win32" && command === "npm";
- const executable = useCmd ? "cmd.exe" : command;
- const finalArgs = useCmd ? ["/d", "/s", "/c", "npm", ...args] : args;
- const child = spawnSync(executable, finalArgs, { cwd, stdio: "inherit", encoding: "utf8" });
- if (child.status !== 0) throw new Error(`${command} failed with exit ${child.status}`);
- }
- function readJsonMaybe(filePath) {
- if (!fs.existsSync(filePath)) return {};
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
- }
- function writeWorkspaceActivation(target) {
- const configPath = path.join(WORKSPACE_ROOT, ".mcp.json");
- const existing = readJsonMaybe(configPath);
- const config = {
- ...existing,
- mcpServers: {
- ...(existing.mcpServers || {}),
- "fmode-image-set": {
- command: "node",
- args: [path.join(target, "mcp", "src", "server.mjs")],
- cwd: target,
- },
- },
- };
- fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
- const packagedSkill = path.join(target, "skills", "fmode-image-set");
- copyPackage(packagedSkill, target);
- }
- function install(options) {
- if (fs.existsSync(options.target)) {
- if (!safeTarget(options.target, options.force)) {
- throw new Error(`Target exists; use --force only for an intentional custom replacement: ${options.target}`);
- }
- fs.rmSync(options.target, { recursive: true, force: true });
- }
- copyPackage(SOURCE_ROOT, options.target);
- if (options.workspace) writeWorkspaceActivation(options.target);
- if (!options.skipInstall) run("npm", ["install", "--omit=dev", "--ignore-scripts"], options.target);
- run(process.execPath, ["install.js", options.smoke ? "--smoke" : "--check", "--skip-install"], options.target);
- console.log(`Installed Fmode Image Set: ${options.target}`);
- if (options.workspace) console.log("Restart the Claude Code session so it can discover the new Skill and MCP tools.");
- }
- function main() {
- const options = parseArgs(process.argv.slice(2));
- if (options.command === "install" || options.command === "workspace") return install(options);
- if (options.command === "check") return run(process.execPath, ["install.js", "--check"], SOURCE_ROOT);
- if (options.command === "smoke") return run(process.execPath, ["install.js", "--smoke", "--skip-install"], SOURCE_ROOT);
- if (options.command === "path") return console.log(options.target);
- if (options.command === "run") {
- return run(process.execPath, ["mcp/src/tools/image-set-run.mjs", ...process.argv.slice(3)], SOURCE_ROOT);
- }
- console.log([
- "Usage:",
- " fmode-image-set install [--smoke]",
- " fmode-image-set workspace [--smoke]",
- " fmode-image-set check",
- " fmode-image-set smoke",
- " fmode-image-set run <start|execute|resume|status> [options]",
- " fmode-image-set path",
- ].join("\n"));
- }
- try {
- main();
- } catch (error) {
- console.error(`fmode-image-set failed: ${error.message}`);
- process.exit(1);
- }
|