chore: 1.0.1 release
This commit is contained in:
@@ -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 目录)
|
||||
@@ -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`)
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
'<p:transition spd="fast" advClick="1"><p:fade/></p:transition>'
|
||||
)
|
||||
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))
|
||||
|
||||
@@ -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'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
|
||||
Reference in New Issue
Block a user