diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4f74ab4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# 更新日志 + +[简体中文](CHANGELOG.md) | [English](CHANGELOG_EN.md) + +本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 + +## [1.0.1] - 2026-08-06 + +### 新增 + +- Skill 工作流增加 **step0 本地前置检测**:生成前检查 Node.js 18+、npm/npx、python3,并提示需要 Chromium 系浏览器 +- 导出脚本在启动时检测 **Node.js 18+** 与 **npm**;缺失或版本过低时给出明确安装指引 +- CLI(`open-kimi-ppt-skills`)启动时校验 Node.js 主版本 ≥ 18 +- 缺失 **PyYAML** 时自动 `pip install --user pyyaml`(与 Pillow / websocket-client 行为一致) + +### 文档 + +- 补充多 Agent / 多模型实测截图(ChatGPT·Codex + 5.6 Luna、Reasonix + DeepSeek、WorkBuddy 等) +- 安装说明改为「自动 / 手动二选一」,并补充 Windows 路径说明 +- README 结构与示例图更新 + +## [1.0.0] - 2026-08-05 + +### 新增 + +- 首次发布 `open-kimi-ppt-skills` +- PPTD 生成 / 编辑 / 复刻,默认同时交付可编辑 PPTD 项目与 PPTX 成品 +- 浏览器侧导出 PPTX(嵌字体、淡入淡出切换),导出前可选多模态视觉质检 +- 本地在线 PPTD 编辑器(`npx open-kimi-ppt-skills serve`) +- CLI 安装 Skill 到 `~/.agents/skills`(可用 `--target` 指定其他 Agent 目录) diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md new file mode 100644 index 0000000..42eed10 --- /dev/null +++ b/CHANGELOG_EN.md @@ -0,0 +1,30 @@ +# Changelog + +[简体中文](CHANGELOG.md) | [English](CHANGELOG_EN.md) + +This project follows [Semantic Versioning](https://semver.org/). + +## [1.0.1] - 2026-08-06 + +### Added + +- Skill workflow **step0 prerequisite check**: verify Node.js 18+, npm/npx, and python3 before generation; note that a Chromium-based browser is required for export +- Export scripts check **Node.js 18+** and **npm** at startup, with clear install guidance when missing or too old +- CLI (`open-kimi-ppt-skills`) refuses to start when the Node.js major version is below 18 +- Auto-install **PyYAML** via `pip install --user pyyaml` when missing (same pattern as Pillow / websocket-client) + +### Docs + +- Added multi-agent / multi-model example screenshots (ChatGPT·Codex + 5.6 Luna, Reasonix + DeepSeek, WorkBuddy, and more) +- Clarified install as “automatic or manual — pick one”, with Windows path notes +- Updated README structure and example images + +## [1.0.0] - 2026-08-05 + +### Added + +- Initial release of `open-kimi-ppt-skills` +- PPTD create / edit / replicate, delivering both an editable PPTD project and a PPTX by default +- Browser-side PPTX export (embedded fonts, fade transitions) with optional multimodal visual QA before export +- Local in-browser PPTD editor (`npx open-kimi-ppt-skills serve`) +- CLI to install the skill into `~/.agents/skills` (or another agent directory via `--target`) diff --git a/bin/open-kimi-ppt-skills.js b/bin/open-kimi-ppt-skills.js index 42478c6..27f2d04 100755 --- a/bin/open-kimi-ppt-skills.js +++ b/bin/open-kimi-ppt-skills.js @@ -8,9 +8,19 @@ import { fileURLToPath } from "node:url"; import { startEditorServer } from "../lib/editor-server.js"; const SKILL_NAME = "open-kimi-ppt"; +const MIN_NODE_MAJOR = 18; const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const sourceDirectory = join(packageRoot, "skills", SKILL_NAME); +function assertNodeVersion() { + const major = Number.parseInt(process.versions.node.split(".")[0], 10); + if (!Number.isInteger(major) || major < MIN_NODE_MAJOR) { + throw new Error( + `Node.js ${MIN_NODE_MAJOR}+ is required; found ${process.version}. Install from https://nodejs.org`, + ); + } +} + function printHelp(command) { if (command === "serve") { console.log(`Start the local PPTD editor and exporter. @@ -138,6 +148,7 @@ function installSkill({ force, target }) { } async function main() { + assertNodeVersion(); const options = parseArguments(process.argv.slice(2)); if (options.help) { printHelp(options.command); diff --git a/package.json b/package.json index a314116..7d317b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-kimi-ppt-skills", - "version": "1.0.0", + "version": "1.0.1", "description": "Unofficial reverse-engineered Kimi Slides skill for AI coding agents with local PPTD editing and PPTX export.", "license": "MIT", "author": "binaryify", diff --git a/skills/open-kimi-ppt/SKILL.md b/skills/open-kimi-ppt/SKILL.md index 423de62..3c2b12c 100644 --- a/skills/open-kimi-ppt/SKILL.md +++ b/skills/open-kimi-ppt/SKILL.md @@ -18,6 +18,15 @@ The .pptd format is a simplified abstraction layer over OOXML that follows basic ## PPT production workflow +### step0. Check local prerequisites +Default delivery includes PPTX export (and optional `npx open-kimi-ppt-skills serve`), which need a local toolchain. **Before generating**, verify: + +1. **Node.js 18+**: run `node --version`. If `node` is missing or the major version is below 18, **stop immediately**, tell the user to install Node.js 18+ from https://nodejs.org (or their OS package manager), and do not continue with PPTX export / `npx` until it is available. Only continue with PPTD-only output when the user explicitly opts out of PPTX. +2. **npm / npx**: run `npm --version`. They ship with Node.js; if missing, treat Node.js as not installed correctly and guide the user to reinstall/fix PATH. +3. **python3**: run `python3 --version` (on Windows, `python` may be the correct command). Needed for `export_pptx.py` / `export_images.py`. +4. **Chrome / Chromium / Edge**: needed by `agent-browser` for PPTX export and visual QA. If export later fails with a browser-launch error, ask the user to install a Chromium-based browser. +5. Soft deps are auto-handled by the scripts when missing: **PyYAML**, **agent-browser** (≥0.33.2 via npm), and for image QA **Pillow** + **websocket-client**. Network access to `www.kimi.com` and `statics.moonshot.cn` is still required at export time. + ### step1. Read the context thoroughly Read **all files uploaded by the user**, the provided URLs, and the pptd format guide `reference/pptd.md` to fully understand the user's requirements. @@ -144,8 +153,8 @@ When generating a PPT, adopt different production approaches for different user A project directory may be passed instead of the manifest only when it contains exactly one `.pptd` file. Existing output files are not overwritten unless `--force` is passed. 7. Local export requirements and boundaries: - - requires `python3`, PyYAML, `npm`, `agent-browser`, Chrome/Chromium, and network access to `www.kimi.com` plus `statics.moonshot.cn`; the image-based visual QA step additionally requires Pillow, auto-installed with `pip --user` when missing; - - before browser export, `export_pptx.py` checks `agent-browser --version`; when it is missing or below `0.33.2`, it installs `agent-browser@latest` globally with npm, then verifies the resulting version before continuing; + - requires **Node.js 18+** (`node` / `npm` / `npx`), `python3`, a Chromium-based browser, and network access to `www.kimi.com` plus `statics.moonshot.cn`; + - before browser export, `export_pptx.py` checks Node.js 18+ and `npm`, then checks `agent-browser --version`; when `agent-browser` is missing or below `0.33.2`, it installs `agent-browser@latest` globally with npm; **PyYAML** is auto-installed with `pip --user` when missing; the image-based visual QA step additionally auto-installs Pillow and websocket-client the same way; - the PPTD document itself is provided to the public editor iframe through the localhost SDK bridge, not uploaded to a server-side PPTX conversion endpoint; - remote images, icons, or fonts referenced by the deck may still be fetched from their respective hosts; - local PNG/JPEG/GIF/SVG files inside the PPTD project are supplied to the iframe as data URLs; diff --git a/skills/open-kimi-ppt/scripts/export_pptx.py b/skills/open-kimi-ppt/scripts/export_pptx.py index 2459752..b0c33ab 100755 --- a/skills/open-kimi-ppt/scripts/export_pptx.py +++ b/skills/open-kimi-ppt/scripts/export_pptx.py @@ -28,14 +28,6 @@ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple -try: - import yaml -except ImportError as exc: # pragma: no cover - environment diagnostic - raise SystemExit( - "PyYAML is required. Install it with: python3 -m pip install --user pyyaml" - ) from exc - - SKILL_DIR = Path(__file__).resolve().parent.parent HOST_TEMPLATE = Path(__file__).with_name("export_host.html") IMAGE_MIME = { @@ -54,6 +46,8 @@ FADE_TRANSITION_XML = ( '' ) MIN_AGENT_BROWSER_VERSION = (0, 33, 2) +MIN_NODE_MAJOR = 18 +NODE_INSTALL_HINT = "Install Node.js 18+ from https://nodejs.org, then retry." class ExportError(RuntimeError): @@ -69,6 +63,31 @@ def log(message: str) -> None: print(f"[open-kimi-ppt] {message}", file=sys.stderr, flush=True) +def ensure_pyyaml() -> Any: + try: + import yaml + except ImportError: + log("PyYAML is required; installing pyyaml with pip --user") + process = subprocess.run( + [sys.executable, "-m", "pip", "install", "--user", "pyyaml"], + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=300, + ) + if process.returncode != 0: + raise ExportError( + "failed to install PyYAML with pip --user:\n" + f"{process.stdout[-2000:]}\n" + "Install it manually with: python3 -m pip install --user pyyaml" + ) + import yaml + return yaml + + +yaml = ensure_pyyaml() + + def parse_version(output: str) -> Tuple[int, int, int]: match = re.search(r"(\d+)\.(\d+)\.(\d+)\b", output) if not match: @@ -76,6 +95,13 @@ def parse_version(output: str) -> Tuple[int, int, int]: return tuple(int(part) for part in match.groups()) +def parse_node_version(output: str) -> Tuple[int, int, int]: + match = re.search(r"v?(\d+)\.(\d+)\.(\d+)\b", output) + if not match: + raise ExportError(f"could not parse Node.js version from: {output.strip()}") + return tuple(int(part) for part in match.groups()) + + def read_agent_browser_version(executable: str) -> Tuple[int, int, int]: process = subprocess.run( [executable, "--version"], @@ -89,7 +115,42 @@ def read_agent_browser_version(executable: str) -> Tuple[int, int, int]: return parse_version(process.stdout) +def ensure_nodejs() -> str: + executable = shutil.which("node") + if not executable: + raise ExportError(f"Node.js is not installed or not on PATH. {NODE_INSTALL_HINT}") + + process = subprocess.run( + [executable, "--version"], + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=30, + ) + if process.returncode != 0: + raise ExportError(f"node --version failed:\n{process.stdout[-2000:]}") + + version = parse_node_version(process.stdout) + if version[0] < MIN_NODE_MAJOR: + raise ExportError( + f"Node.js {MIN_NODE_MAJOR}+ is required; found " + f"{'.'.join(map(str, version))} ({process.stdout.strip()}). {NODE_INSTALL_HINT}" + ) + + npm = shutil.which("npm") + if not npm: + raise ExportError( + "npm is not installed or not on PATH. " + f"npm ships with Node.js. {NODE_INSTALL_HINT}" + ) + + log(f"Node.js version: {'.'.join(map(str, version))}") + return executable + + def ensure_agent_browser() -> str: + ensure_nodejs() + executable = shutil.which("agent-browser") version = read_agent_browser_version(executable) if executable else None if version is not None and version >= MIN_AGENT_BROWSER_VERSION: @@ -100,7 +161,8 @@ def ensure_agent_browser() -> str: if not npm: reason = "not installed" if version is None else ".".join(map(str, version)) raise ExportError( - f"agent-browser {reason}; npm is required to install agent-browser@latest" + f"agent-browser {reason}; npm is required to install agent-browser@latest. " + f"npm ships with Node.js. {NODE_INSTALL_HINT}" ) current = "not installed" if version is None else ".".join(map(str, version)) diff --git a/skills/open-kimi-ppt/tests/test_export_pptx.py b/skills/open-kimi-ppt/tests/test_export_pptx.py index e332a1d..c239392 100755 --- a/skills/open-kimi-ppt/tests/test_export_pptx.py +++ b/skills/open-kimi-ppt/tests/test_export_pptx.py @@ -22,17 +22,52 @@ class ExportPptxTests(unittest.TestCase): @patch.object(MODULE.subprocess, "run") @patch.object(MODULE.shutil, "which") def test_old_agent_browser_is_upgraded(self, which, run): - which.side_effect = ["/bin/agent-browser", "/bin/npm", "/bin/agent-browser"] + which.side_effect = [ + "/bin/node", + "/bin/npm", + "/bin/agent-browser", + "/bin/npm", + "/bin/agent-browser", + ] run.side_effect = [ + MODULE.subprocess.CompletedProcess([], 0, "v22.11.0\n"), MODULE.subprocess.CompletedProcess([], 0, "agent-browser 0.17.1\n"), MODULE.subprocess.CompletedProcess([], 0, "changed 1 package\n"), MODULE.subprocess.CompletedProcess([], 0, "agent-browser 0.33.2\n"), ] self.assertEqual(MODULE.ensure_agent_browser(), "/bin/agent-browser") - self.assertEqual(run.call_args_list[1].args[0], [ + self.assertEqual(run.call_args_list[2].args[0], [ "/bin/npm", "install", "-g", "agent-browser@latest" ]) + @patch.object(MODULE.subprocess, "run") + @patch.object(MODULE.shutil, "which") + def test_missing_nodejs_raises_clear_error(self, which, run): + which.return_value = None + with self.assertRaisesRegex(MODULE.ExportError, "Node.js is not installed"): + MODULE.ensure_nodejs() + run.assert_not_called() + + @patch.object(MODULE.subprocess, "run") + @patch.object(MODULE.shutil, "which") + def test_old_nodejs_raises_clear_error(self, which, run): + which.return_value = "/bin/node" + run.return_value = MODULE.subprocess.CompletedProcess([], 0, "v16.20.2\n") + with self.assertRaisesRegex(MODULE.ExportError, "Node.js 18\\+ is required"): + MODULE.ensure_nodejs() + + @patch.object(MODULE.subprocess, "run") + @patch.object(MODULE.shutil, "which") + def test_missing_npm_raises_clear_error(self, which, run): + which.side_effect = ["/bin/node", None] + run.return_value = MODULE.subprocess.CompletedProcess([], 0, "v22.11.0\n") + with self.assertRaisesRegex(MODULE.ExportError, "npm is not installed"): + MODULE.ensure_nodejs() + + def test_parse_node_version(self): + self.assertEqual(MODULE.parse_node_version("v22.11.0"), (22, 11, 0)) + self.assertEqual(MODULE.parse_node_version("18.20.4"), (18, 20, 4)) + def test_fade_is_inserted_before_timing(self): source = ( b''