166 lines
4.7 KiB
JavaScript
Executable File
166 lines
4.7 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import { cpSync, existsSync, mkdtempSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { basename, dirname, join, resolve } from "node:path";
|
|
import { spawn } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import { startEditorServer } from "../lib/editor-server.js";
|
|
|
|
const SKILL_NAME = "open-kimi-ppt";
|
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const sourceDirectory = join(packageRoot, "skills", SKILL_NAME);
|
|
|
|
function printHelp(command) {
|
|
if (command === "serve") {
|
|
console.log(`Start the local PPTD editor and exporter.
|
|
|
|
Usage:
|
|
open-kimi-ppt-skills serve [options]
|
|
|
|
Options:
|
|
--port <number> HTTP port (default: 55173)
|
|
--open Open the editor in the default browser
|
|
-h, --help Show this help
|
|
`);
|
|
return;
|
|
}
|
|
|
|
console.log(`Install ${SKILL_NAME} for your AI coding agent or start its local editor.
|
|
|
|
Usage:
|
|
open-kimi-ppt-skills [install] [options]
|
|
open-kimi-ppt-skills serve [options]
|
|
|
|
Install options:
|
|
--target <directory> Skills directory (default: ~/.agents/skills)
|
|
--force Replace an existing installation
|
|
|
|
Run "open-kimi-ppt-skills serve --help" for server options.
|
|
`);
|
|
}
|
|
|
|
function parseArguments(arguments_) {
|
|
const args = [...arguments_];
|
|
const command = args[0] === "install" || args[0] === "serve" ? args.shift() : "install";
|
|
const options = command === "serve"
|
|
? { command, open: false, port: 55173 }
|
|
: { command, force: false, target: undefined };
|
|
|
|
while (args.length > 0) {
|
|
const argument = args.shift();
|
|
|
|
if (command === "install" && argument === "--force") {
|
|
options.force = true;
|
|
continue;
|
|
}
|
|
|
|
if (command === "install" && argument === "--target") {
|
|
const target = args.shift();
|
|
if (!target || target.startsWith("-")) {
|
|
throw new Error("--target requires a directory");
|
|
}
|
|
options.target = resolve(target);
|
|
continue;
|
|
}
|
|
|
|
if (command === "serve" && argument === "--port") {
|
|
const port = Number(args.shift());
|
|
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
throw new Error("--port must be an integer between 1 and 65535");
|
|
}
|
|
options.port = port;
|
|
continue;
|
|
}
|
|
|
|
if (command === "serve" && argument === "--open") {
|
|
options.open = true;
|
|
continue;
|
|
}
|
|
|
|
if (argument === "--help" || argument === "-h") {
|
|
options.help = true;
|
|
continue;
|
|
}
|
|
|
|
throw new Error(`unknown argument: ${argument}`);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
function openBrowser(url) {
|
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
child.on("error", (error) => console.warn(`Could not open the browser: ${error.message}`));
|
|
child.unref();
|
|
}
|
|
|
|
function defaultSkillsDirectory() {
|
|
return join(homedir(), ".agents", "skills");
|
|
}
|
|
|
|
function installSkill({ force, target }) {
|
|
if (!existsSync(join(sourceDirectory, "SKILL.md"))) {
|
|
throw new Error(`packaged skill is incomplete: ${sourceDirectory}`);
|
|
}
|
|
|
|
const skillsDirectory = target ?? defaultSkillsDirectory();
|
|
const destination = join(skillsDirectory, SKILL_NAME);
|
|
|
|
if (existsSync(destination) && !force) {
|
|
throw new Error(
|
|
`${destination} already exists; rerun with --force to replace it`,
|
|
);
|
|
}
|
|
|
|
mkdirSync(skillsDirectory, { recursive: true });
|
|
const stagingRoot = mkdtempSync(join(skillsDirectory, `.${SKILL_NAME}-`));
|
|
const stagedSkill = join(stagingRoot, SKILL_NAME);
|
|
|
|
try {
|
|
cpSync(sourceDirectory, stagedSkill, {
|
|
recursive: true,
|
|
filter: (source) => ![".DS_Store", "_user_meta.json"].includes(basename(source)),
|
|
});
|
|
|
|
if (force) {
|
|
rmSync(destination, { recursive: true, force: true });
|
|
}
|
|
|
|
renameSync(stagedSkill, destination);
|
|
} finally {
|
|
rmSync(stagingRoot, { recursive: true, force: true });
|
|
}
|
|
|
|
console.log(`Installed ${SKILL_NAME} to ${destination}`);
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseArguments(process.argv.slice(2));
|
|
if (options.help) {
|
|
printHelp(options.command);
|
|
return;
|
|
}
|
|
|
|
if (options.command === "install") {
|
|
installSkill(options);
|
|
return;
|
|
}
|
|
|
|
const { server, url } = await startEditorServer({ port: options.port });
|
|
console.log(`Open Kimi PPT editor is running at ${url}`);
|
|
console.log("Press Ctrl+C to stop the server.");
|
|
if (options.open) openBrowser(url);
|
|
|
|
const shutdown = () => server.close(() => process.exit(0));
|
|
process.once("SIGINT", shutdown);
|
|
process.once("SIGTERM", shutdown);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(`Error: ${error.message}`);
|
|
process.exitCode = 1;
|
|
});
|