chore: 1.0.1 release

This commit is contained in:
binaryify
2026-08-06 12:36:09 +08:00
parent 37d2272f55
commit 4fca104a44
7 changed files with 191 additions and 14 deletions
+30
View File
@@ -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 目录)
+30
View File
@@ -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`)
+11
View File
@@ -8,9 +8,19 @@ import { fileURLToPath } from "node:url";
import { startEditorServer } from "../lib/editor-server.js"; import { startEditorServer } from "../lib/editor-server.js";
const SKILL_NAME = "open-kimi-ppt"; const SKILL_NAME = "open-kimi-ppt";
const MIN_NODE_MAJOR = 18;
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const sourceDirectory = join(packageRoot, "skills", SKILL_NAME); 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) { function printHelp(command) {
if (command === "serve") { if (command === "serve") {
console.log(`Start the local PPTD editor and exporter. console.log(`Start the local PPTD editor and exporter.
@@ -138,6 +148,7 @@ function installSkill({ force, target }) {
} }
async function main() { async function main() {
assertNodeVersion();
const options = parseArguments(process.argv.slice(2)); const options = parseArguments(process.argv.slice(2));
if (options.help) { if (options.help) {
printHelp(options.command); printHelp(options.command);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "open-kimi-ppt-skills", "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.", "description": "Unofficial reverse-engineered Kimi Slides skill for AI coding agents with local PPTD editing and PPTX export.",
"license": "MIT", "license": "MIT",
"author": "binaryify", "author": "binaryify",
+11 -2
View File
@@ -18,6 +18,15 @@ The .pptd format is a simplified abstraction layer over OOXML that follows basic
## PPT production workflow ## 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 ### 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. 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. 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. Existing output files are not overwritten unless `--force` is passed.
7. Local export requirements and boundaries: 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; - 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 `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; - 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; - 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; - 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; - local PNG/JPEG/GIF/SVG files inside the PPTD project are supplied to the iframe as data URLs;
+71 -9
View File
@@ -28,14 +28,6 @@ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple 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 SKILL_DIR = Path(__file__).resolve().parent.parent
HOST_TEMPLATE = Path(__file__).with_name("export_host.html") HOST_TEMPLATE = Path(__file__).with_name("export_host.html")
IMAGE_MIME = { IMAGE_MIME = {
@@ -54,6 +46,8 @@ FADE_TRANSITION_XML = (
'<p:transition spd="fast" advClick="1"><p:fade/></p:transition>' '<p:transition spd="fast" advClick="1"><p:fade/></p:transition>'
) )
MIN_AGENT_BROWSER_VERSION = (0, 33, 2) 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): class ExportError(RuntimeError):
@@ -69,6 +63,31 @@ def log(message: str) -> None:
print(f"[open-kimi-ppt] {message}", file=sys.stderr, flush=True) 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]: def parse_version(output: str) -> Tuple[int, int, int]:
match = re.search(r"(\d+)\.(\d+)\.(\d+)\b", output) match = re.search(r"(\d+)\.(\d+)\.(\d+)\b", output)
if not match: 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()) 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]: def read_agent_browser_version(executable: str) -> Tuple[int, int, int]:
process = subprocess.run( process = subprocess.run(
[executable, "--version"], [executable, "--version"],
@@ -89,7 +115,42 @@ def read_agent_browser_version(executable: str) -> Tuple[int, int, int]:
return parse_version(process.stdout) 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: def ensure_agent_browser() -> str:
ensure_nodejs()
executable = shutil.which("agent-browser") executable = shutil.which("agent-browser")
version = read_agent_browser_version(executable) if executable else None version = read_agent_browser_version(executable) if executable else None
if version is not None and version >= MIN_AGENT_BROWSER_VERSION: if version is not None and version >= MIN_AGENT_BROWSER_VERSION:
@@ -100,7 +161,8 @@ def ensure_agent_browser() -> str:
if not npm: if not npm:
reason = "not installed" if version is None else ".".join(map(str, version)) reason = "not installed" if version is None else ".".join(map(str, version))
raise ExportError( 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)) current = "not installed" if version is None else ".".join(map(str, version))
+37 -2
View File
@@ -22,17 +22,52 @@ class ExportPptxTests(unittest.TestCase):
@patch.object(MODULE.subprocess, "run") @patch.object(MODULE.subprocess, "run")
@patch.object(MODULE.shutil, "which") @patch.object(MODULE.shutil, "which")
def test_old_agent_browser_is_upgraded(self, which, run): 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 = [ 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, "agent-browser 0.17.1\n"),
MODULE.subprocess.CompletedProcess([], 0, "changed 1 package\n"), MODULE.subprocess.CompletedProcess([], 0, "changed 1 package\n"),
MODULE.subprocess.CompletedProcess([], 0, "agent-browser 0.33.2\n"), MODULE.subprocess.CompletedProcess([], 0, "agent-browser 0.33.2\n"),
] ]
self.assertEqual(MODULE.ensure_agent_browser(), "/bin/agent-browser") 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" "/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): def test_fade_is_inserted_before_timing(self):
source = ( source = (
b'<?xml version="1.0" encoding="UTF-8"?>' b'<?xml version="1.0" encoding="UTF-8"?>'