@liustack/pptwise 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/README.zh-CN.md +136 -0
- package/cordis.patch.yml +5 -0
- package/dist/chunk-3ZUKISTY.js +114 -0
- package/dist/chunk-3ZUKISTY.js.map +1 -0
- package/dist/chunk-M35M4QUC.js +1167 -0
- package/dist/chunk-M35M4QUC.js.map +1 -0
- package/dist/chunk-VUOLBHD7.js +19 -0
- package/dist/chunk-VUOLBHD7.js.map +1 -0
- package/dist/chunk-WL5KWYKS.js +49762 -0
- package/dist/chunk-WL5KWYKS.js.map +1 -0
- package/dist/cli.js +4753 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +4224 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +7 -0
- package/dist/node.js +11 -0
- package/dist/node.js.map +1 -0
- package/dist/pixel-audit-H5K6JK3X.js +218 -0
- package/dist/pixel-audit-H5K6JK3X.js.map +1 -0
- package/dist/registry-C0GJH7ZT.d.ts +46 -0
- package/dsh/client.js +1398 -0
- package/dsh/index.js +141 -0
- package/dsh/preview-tool.js +1931 -0
- package/dsh/spawnHidden.js +109 -0
- package/package.json +113 -0
- package/skills/pptwise/SKILL.md +100 -0
- package/skills/pptwise/SKILL.zh-CN.md +102 -0
- package/skills/pptwise/references/branding.md +18 -0
- package/skills/pptwise/references/branding.zh-CN.md +21 -0
- package/skills/pptwise/references/components.md +35 -0
- package/skills/pptwise/references/components.zh-CN.md +40 -0
- package/skills/pptwise/references/density.md +17 -0
- package/skills/pptwise/references/density.zh-CN.md +22 -0
- package/skills/pptwise/references/images.md +42 -0
- package/skills/pptwise/references/images.zh-CN.md +47 -0
- package/skills/pptwise/references/layouts.md +37 -0
- package/skills/pptwise/references/layouts.zh-CN.md +42 -0
- package/skills/pptwise/references/spec.md +107 -0
- package/skills/pptwise/references/spec.zh-CN.md +112 -0
- package/skills/pptwise/references/validate.md +82 -0
- package/skills/pptwise/references/validate.zh-CN.md +87 -0
- package/skills/pptwise/scripts/run.ps1 +192 -0
- package/skills/pptwise/scripts/run.sh +229 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leon Liu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<p align="center"><img src="assets/banner.png" alt="pptwise — make your deck in minutes, not hours" width="100%"></p>
|
|
2
|
+
|
|
3
|
+
<h1 align="center">pptwise</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center"><b>Make your deck in minutes, not hours.</b></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">🥇 <b>The FIRST deck-generation plugin for DeepSeek Harness (dsh)</b> 🥇</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="./README.zh-CN.md">简体中文</a> ·
|
|
11
|
+
<a href="./INSTALL.md">Install (hand it to your AI)</a> ·
|
|
12
|
+
<a href="./docs/cli.md">Commands</a> ·
|
|
13
|
+
<a href="./docs/ir.md">IR</a> ·
|
|
14
|
+
<a href="./docs/themes.md">Themes</a> ·
|
|
15
|
+
<a href="./skills/pptwise/SKILL.md">Agent skill</a> ·
|
|
16
|
+
<a href="https://github.com/liustack/modlens">ModLens (vision)</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="https://x.com/liustack"><img src="https://img.shields.io/badge/follow-%40liustack-black?style=flat-square&logo=x&logoColor=white" alt="Follow @liustack on X"></a>
|
|
21
|
+
<a href="https://www.npmjs.com/package/@liustack/pptwise"><img src="https://img.shields.io/npm/v/@liustack/pptwise?style=flat-square&label=npm&color=cb3837" alt="npm"></a>
|
|
22
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@liustack/pptwise?style=flat-square" alt="Node.js"></a>
|
|
23
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License"></a>
|
|
24
|
+
<img src="https://img.shields.io/badge/Not%20backed%20by-Y%20Combinator-FF6600?style=flat-square&logo=ycombinator&logoColor=white" alt="Not backed by Y Combinator">
|
|
25
|
+
<img src="https://img.shields.io/badge/no%20API%20key-to%20render-4c1?style=flat-square" alt="No API key to render">
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
## Talk to us
|
|
29
|
+
|
|
30
|
+
Issues are welcome any time. [Open one](https://github.com/liustack/pptwise/issues/new/choose), or follow **[@liustack](https://x.com/liustack)** on X. Share what you made with pptwise, which harness you use, and what the next release should solve. New releases land there first.
|
|
31
|
+
|
|
32
|
+
## Highlights
|
|
33
|
+
|
|
34
|
+
**⚡ Tell your AI what to cover, get the deck.** You bring the content, the engine handles layout, color, type size, and spacing. The same content renders the same deck every time, so there is nothing to redo and no luck involved.
|
|
35
|
+
|
|
36
|
+
**✏️ A real deck, not a picture of one.** Every heading, bullet, and chart bar opens in PowerPoint for you to retype and restyle. Chart and table figures are the exception: to change the numbers, have your AI rebuild that page. 24 ready-made styles, and you can pull the colors and fonts out of a deck your company already uses.
|
|
37
|
+
|
|
38
|
+
**🔌 Installs into the agent you already use.** One command puts pptwise into DeepSeek Harness, Claude Code, or any agent that reads a skill folder (Codex and friends), and it knows how to build a deck the moment it lands.
|
|
39
|
+
|
|
40
|
+
**🔁 Revisions without describing everything again.** One command opens a live preview in your browser. Tell your AI what to change in plain words — the page refreshes itself as each revision lands.
|
|
41
|
+
|
|
42
|
+
**🔒 No account, no API key to render, no network at render time.** Install it and it works. Node 22.19+ or Bun is all you need on the machine. Optional stock-photo search uses the user's own Pexels key.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
**Step 1, hand it to your AI.** Send it this line:
|
|
47
|
+
|
|
48
|
+
> Install the pptwise deck skill following https://raw.githubusercontent.com/liustack/pptwise/main/INSTALL.md, then run the health check and tell me the result.
|
|
49
|
+
|
|
50
|
+
There is no step 2. Your AI puts the skill folder where your harness reads it, and the skill brings its own version-pinned launcher, so there is no CLI to install by hand. pptwise renders a PPTX entirely locally: no API key, no account, nothing to configure for render. Optional stock-photo search needs the user's own Pexels key. The only prerequisite is Node 22.19+ (or Bun).
|
|
51
|
+
|
|
52
|
+
**On DeepSeek Harness, it is one command instead.** pptwise is a native DSH plugin there, not a skill folder:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/pptwise@0.22.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Name the version. Without it, the install quietly lands on an older release and you miss the newest features. `npm view @liustack/pptwise version` prints the current one. The plugin card shows up as "pptwise", registers the deck-generation skill, and carries the CLI inside its own package. Uninstalling removes the skill with no residue.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
An IR is one JSON file describing the whole deck. Write a minimal one, then run the validate → render → preview loop:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cat > deck.json <<'EOF'
|
|
66
|
+
{
|
|
67
|
+
"filename": "hello.pptx",
|
|
68
|
+
"theme": { "id": "consulting" },
|
|
69
|
+
"slides": [
|
|
70
|
+
{ "type": "cover", "heading": "Hello pptwise", "subheading": "A first deck in ten minutes" },
|
|
71
|
+
{ "type": "content", "heading": "Why it works", "components": [
|
|
72
|
+
{ "type": "bullets", "items": ["Semantic IR in", "Native DrawingML out", "Every shape stays editable"] } ] },
|
|
73
|
+
{ "type": "ending", "heading": "Thanks" }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
EOF
|
|
77
|
+
pptwise validate deck.json # → OK — 3 slides, theme "consulting"
|
|
78
|
+
pptwise render deck.json -o out/hello.pptx # → wrote out/hello.pptx (3 slides, ~24 KB)
|
|
79
|
+
pptwise render deck.json -o out/tech.pptx --theme tech # same deck, different theme
|
|
80
|
+
pptwise preview deck.json -o out/svgs # SVG per slide, for a visual self-check
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
One shape rule: `cover`/`chapter`/`ending` slides are heading + subheading only, components live on `content` slides. `validate` says exactly this if you mix them up.
|
|
84
|
+
|
|
85
|
+
No install at all also works: `npx -y @liustack/pptwise validate deck.json`. In a source checkout, `node dist/cli.js` replaces `pptwise`, and `examples/` has ready-made IR files to try.
|
|
86
|
+
|
|
87
|
+
The commands you will reach for most:
|
|
88
|
+
|
|
89
|
+
| Command | Does |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `validate <target>` | Check the IR, with page numbers on every error |
|
|
92
|
+
| `render <target> [-o <out.pptx>] [--theme <id>]` | Render a `.pptx`. Omit `-o` to write `.pptwise/<deck>/<deck>.pptx` |
|
|
93
|
+
| `preview <target> [-o <dir>] [--html]` | One SVG per slide, plus a self-contained review page. Omit `-o` to write `.pptwise/<deck>/` |
|
|
94
|
+
| `serve <target>` | Live preview that reloads on every change |
|
|
95
|
+
| `audit <target>` | Geometry review: overflow, out-of-bounds, low contrast, overlap |
|
|
96
|
+
| `themes` | List the built-in themes |
|
|
97
|
+
| `doctor` | Check the install: runtime, skill copies, optional capabilities, self-test render |
|
|
98
|
+
|
|
99
|
+
Full reference: [`docs/cli.md`](./docs/cli.md).
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
| Doc | Read it when |
|
|
104
|
+
| :-- | :-- |
|
|
105
|
+
| [Install guide](./INSTALL.md) | Handing installation to an agent or checking prerequisites |
|
|
106
|
+
| [Agent skill](./skills/pptwise/SKILL.md) | Learning the workflow pptwise teaches an agent |
|
|
107
|
+
| [CLI manual](./docs/cli.md) | Looking up commands, flags, audits, previews, and health checks |
|
|
108
|
+
| [IR reference](./docs/ir.md) | Writing a deck, slide, component, or narrative in JSON |
|
|
109
|
+
| [Themes](./docs/themes.md) | Picking a built-in theme or extracting your own brand |
|
|
110
|
+
| [Core concepts](./docs/concepts.md) | Understanding themes, layouts, components, narratives, and capacity |
|
|
111
|
+
| [Architecture](./docs/architecture.md) | Working on the render chain or adding a theme, layout, or component |
|
|
112
|
+
| [Deck projects](./docs/deck-projects.md) | Building a multi-file deck with locked specs, assets, and live review |
|
|
113
|
+
| [Layout selection and seed](./docs/selection-and-seed.md) | Explaining why a layout was picked or keeping revisions stable |
|
|
114
|
+
| [Contrast system](./docs/contrast-system.md) | Debugging text color, painted backgrounds, or contrast findings |
|
|
115
|
+
| [Designing themes](./docs/designing-themes.md) | Drawing a theme redesign that can actually compile to PPTX |
|
|
116
|
+
| [Testing](./docs/testing.md) | Running the right gate, inspecting snapshots, or changing exported XML |
|
|
117
|
+
| [Internal API](./docs/internal-api.md) | Understanding why the JavaScript internals carry no semver promise |
|
|
118
|
+
| [Release guide](./docs/releasing.md) | Preparing and publishing an npm release |
|
|
119
|
+
| [CHANGELOG](./CHANGELOG.md) | Finding what changed in a version |
|
|
120
|
+
|
|
121
|
+
## Credits
|
|
122
|
+
|
|
123
|
+
Icon primitives are extracted from [lucide](https://lucide.dev) (ISC License). pptwise itself was extracted from a production AI-deck-generation system and CJK-typography-tuned (full-width punctuation width, Chinese line breaking, a Chinese-first font stack, explicit east-asian font-slot declarations) from day one.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[MIT](./LICENSE)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<p align="center"><img src="assets/banner.png" alt="pptwise:PPT 不用等,马上就好" width="100%"></p>
|
|
2
|
+
|
|
3
|
+
<h1 align="center">pptwise</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center"><b>PPT 不用等,马上就好。</b></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">🥇 <b>全网第一个 DeepSeek Harness PPT 生成插件</b> 🥇</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="./README.md">English</a> ·
|
|
11
|
+
<a href="./INSTALL.md">安装(转发给你的 AI)</a> ·
|
|
12
|
+
<a href="./docs/cli.zh-CN.md">命令</a> ·
|
|
13
|
+
<a href="./docs/ir.zh-CN.md">IR</a> ·
|
|
14
|
+
<a href="./docs/themes.zh-CN.md">主题</a> ·
|
|
15
|
+
<a href="./skills/pptwise/SKILL.zh-CN.md">Agent skill</a> ·
|
|
16
|
+
<a href="https://github.com/liustack/modlens">ModLens(视觉)</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="https://x.com/liustack"><img src="https://img.shields.io/badge/follow-%40liustack-black?style=flat-square&logo=x&logoColor=white" alt="Follow @liustack on X"></a>
|
|
21
|
+
<a href="https://www.npmjs.com/package/@liustack/pptwise"><img src="https://img.shields.io/npm/v/@liustack/pptwise?style=flat-square&label=npm&color=cb3837" alt="npm"></a>
|
|
22
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@liustack/pptwise?style=flat-square" alt="Node.js"></a>
|
|
23
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License"></a>
|
|
24
|
+
<img src="https://img.shields.io/badge/Not%20backed%20by-Y%20Combinator-FF6600?style=flat-square&logo=ycombinator&logoColor=white" alt="Not backed by Y Combinator">
|
|
25
|
+
<img src="https://img.shields.io/badge/no%20API%20key-to%20render-4c1?style=flat-square" alt="No API key to render">
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
## 交流
|
|
29
|
+
|
|
30
|
+
欢迎随时提 [issue](https://github.com/liustack/pptwise/issues/new/choose)。也欢迎在 X 关注 **[@liustack](https://x.com/liustack)**,聊聊你用 pptwise 做了什么、在哪个 harness 上运行,以及下一版最该解决什么。新版本也会第一时间在那里发布。
|
|
31
|
+
|
|
32
|
+
## 亮点
|
|
33
|
+
|
|
34
|
+
**⚡ 跟 AI 说一句,PPT 就好了。** 你只管说要讲什么,版面、配色、字号、间距全由引擎排好。同一份内容做十遍是同一份,不用一遍遍重来碰运气。
|
|
35
|
+
|
|
36
|
+
**✏️ 交出来的是真 PPT,不是一张图。** 每个标题、每条要点、每根柱子都能在 PowerPoint 里点开改字改色。图表和表格里的数字是例外,换数字让 AI 重做一版。24 套现成风格,也能把你公司现有 PPT 里的配色和字体抽出来直接用。
|
|
37
|
+
|
|
38
|
+
**🔌 装进你正在用的 agent。** 一条命令装进 DeepSeek Harness、Claude Code,或任何读 skill 文件夹的 agent(Codex 等),装完就会用。
|
|
39
|
+
|
|
40
|
+
**🔁 改稿不用重新描述一遍。** 一条命令打开实时预览网页,用一句话告诉 AI 要改哪里,改完网页自动刷新。
|
|
41
|
+
|
|
42
|
+
**🔒 不用注册、渲染不用配 key、渲染时不联网。** 装好就能用,电脑上有 Node 22.19+ 或 Bun 就行。可选的图库搜索用用户自己的 Pexels key。
|
|
43
|
+
|
|
44
|
+
## 安装
|
|
45
|
+
|
|
46
|
+
**第一步,交给你的 AI。** 把这行话发给它:
|
|
47
|
+
|
|
48
|
+
> 按照 https://raw.githubusercontent.com/liustack/pptwise/main/INSTALL.md 安装 pptwise deck 技能,装完跑一遍健康检查,把结果告诉我。
|
|
49
|
+
|
|
50
|
+
没有第二步。你的 AI 会把 skill 文件夹放到你这个 harness 读取的位置,skill 自带钉死版本的启动器,不需要你手动装 CLI。pptwise 在本地渲染 PPTX:渲染不要 API key、不用注册、无需配置。可选的图库搜索需要用户自己的 Pexels key。唯一前置是 Node 22.19+(或 Bun)。
|
|
51
|
+
|
|
52
|
+
**在 DeepSeek Harness 上换成一条命令。** 那里 pptwise 是原生 DSH 插件,不走 skill 文件夹:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/pptwise@0.22.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
版本号要点名。不点名的话,安装会静默落到一个更旧的版本,拿不到最新能力。`npm view @liustack/pptwise version` 可查当前版本。插件卡片显示为「pptwise」,把整套生成流程的 skill 注册进 DSH 技能系统,驱动的 CLI 就在插件包自己里面。卸载即移除,不留残余。
|
|
59
|
+
|
|
60
|
+
## 快速开始
|
|
61
|
+
|
|
62
|
+
IR 就是一份描述整份 PPT 内容的 JSON 文件。写一个最小的,跑一遍 validate → render → preview 回路:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cat > deck.json <<'EOF'
|
|
66
|
+
{
|
|
67
|
+
"filename": "hello.pptx",
|
|
68
|
+
"theme": { "id": "consulting" },
|
|
69
|
+
"slides": [
|
|
70
|
+
{ "type": "cover", "heading": "Hello pptwise", "subheading": "A first deck in ten minutes" },
|
|
71
|
+
{ "type": "content", "heading": "Why it works", "components": [
|
|
72
|
+
{ "type": "bullets", "items": ["Semantic IR in", "Native DrawingML out", "Every shape stays editable"] } ] },
|
|
73
|
+
{ "type": "ending", "heading": "Thanks" }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
EOF
|
|
77
|
+
pptwise validate deck.json # → OK — 3 slides, theme "consulting"
|
|
78
|
+
pptwise render deck.json -o out/hello.pptx # → wrote out/hello.pptx (3 slides, ~24 KB)
|
|
79
|
+
pptwise render deck.json -o out/tech.pptx --theme tech # 同一份 deck,换个主题
|
|
80
|
+
pptwise preview deck.json -o out/svgs # 每页一张 SVG,供人工目检
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
只有一条形状规则:`cover`/`chapter`/`ending` 页只有 heading + subheading,组件都放在 `content` 页上。写混了 `validate` 会原话告诉你。
|
|
84
|
+
|
|
85
|
+
不想安装也行:`npx -y @liustack/pptwise validate deck.json`。源码仓库里则用 `node dist/cli.js` 代替 `pptwise`,`examples/` 下有现成的 IR 文件可以直接试。
|
|
86
|
+
|
|
87
|
+
最常用的几条命令:
|
|
88
|
+
|
|
89
|
+
| 命令 | 作用 |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `validate <target>` | 校验 IR,每条报错都带页码 |
|
|
92
|
+
| `render <target> [-o <out.pptx>] [--theme <id>]` | 渲染出 `.pptx`。省略 `-o` 则写到 `.pptwise/<deck>/<deck>.pptx` |
|
|
93
|
+
| `preview <target> [-o <dir>] [--html]` | 每页一张 SVG,外加一个自包含的审阅页。省略 `-o` 则写到 `.pptwise/<deck>/` |
|
|
94
|
+
| `serve <target>` | 随改动自动刷新的实时预览 |
|
|
95
|
+
| `audit <target>` | 几何审查:溢出、越界、低对比度、重叠 |
|
|
96
|
+
| `themes` | 列出 24 套内置主题(24 个 id) |
|
|
97
|
+
| `doctor` | 体检这套安装:运行时、skill 副本、可选能力、自检渲染 |
|
|
98
|
+
|
|
99
|
+
完整命令表见 [`docs/cli.zh-CN.md`](./docs/cli.zh-CN.md)。
|
|
100
|
+
|
|
101
|
+
## 文档
|
|
102
|
+
|
|
103
|
+
| 文档 | 适用场景 |
|
|
104
|
+
| :-- | :-- |
|
|
105
|
+
| [安装手册](./INSTALL.md) | 把安装交给 agent,或检查运行前提 |
|
|
106
|
+
| [Agent skill](./skills/pptwise/SKILL.zh-CN.md) | 了解 pptwise 教给 agent 的完整工作流 |
|
|
107
|
+
| [CLI 手册](./docs/cli.zh-CN.md) | 查询命令、参数、审查、预览与健康检查 |
|
|
108
|
+
| [IR 参考](./docs/ir.zh-CN.md) | 用 JSON 编写 deck、页面、组件与叙事 |
|
|
109
|
+
| [主题](./docs/themes.zh-CN.md) | 挑选内置主题,或从自家 PPT 提取品牌 |
|
|
110
|
+
| [核心概念](./docs/concepts.md) | 理解主题、版式、组件、叙事与容量模型 |
|
|
111
|
+
| [架构](./docs/architecture.md) | 修改渲染链,或新增主题、版式与组件 |
|
|
112
|
+
| [Deck 项目](./docs/deck-projects.md) | 用锁定 spec、页面文件、素材与实时审阅制作复杂 PPT |
|
|
113
|
+
| [版式选型与 seed](./docs/selection-and-seed.md) | 排查版式为何被选中,或保持多次修订稳定 |
|
|
114
|
+
| [对比度系统](./docs/contrast-system.md) | 排查文字颜色、自绘背景与低对比度问题 |
|
|
115
|
+
| [测试](./docs/testing.md) | 选择验证命令、检查快照,或修改导出 XML |
|
|
116
|
+
| [内部 API](./docs/internal-api.md) | 了解 JavaScript 内部模块为何不承诺 semver 稳定性 |
|
|
117
|
+
| [发布手册](./docs/releasing.md) | 准备并发布 npm 版本 |
|
|
118
|
+
| [更新日志](./CHANGELOG.md) | 查询各版本的变化 |
|
|
119
|
+
|
|
120
|
+
## 关注「liustack」
|
|
121
|
+
|
|
122
|
+
关注微信公众号「liustack」:AI 创业机会、独立开发见解、AI 实战与工具,第一时间推送。微信扫码,或搜一搜「liustack」:
|
|
123
|
+
|
|
124
|
+
<p align="center">
|
|
125
|
+
<img src="assets/wechat-qrcode.png" width="420" alt="微信公众号 liustack" />
|
|
126
|
+
</p>
|
|
127
|
+
|
|
128
|
+
⭐ 如果 pptwise 对你有用,请给[项目](https://github.com/liustack/pptwise)一个 star,并在 X 关注 **[@liustack](https://x.com/liustack)**。这是让更多开发者找到它最直接的方式。
|
|
129
|
+
|
|
130
|
+
## 致谢
|
|
131
|
+
|
|
132
|
+
图标原语抽取自 [lucide](https://lucide.dev)(ISC License)。pptwise 本身从一套生产环境的 AI 出 PPT 系统中抽取而来,从第一天起就针对 CJK 排版做了优化(全角标点宽度、中文换行、雅黑优先字体栈、显式东亚字体槽声明)。
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
[MIT](./LICENSE)
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findRemoteAssetRef,
|
|
3
|
+
installPlatform
|
|
4
|
+
} from "./chunk-VUOLBHD7.js";
|
|
5
|
+
|
|
6
|
+
// src/platform/node.ts
|
|
7
|
+
import { DOMParser as LinkedomDOMParser } from "linkedom";
|
|
8
|
+
|
|
9
|
+
// src/cli/proxy-fetch.ts
|
|
10
|
+
import { EnvHttpProxyAgent, fetch as undiciFetch } from "undici";
|
|
11
|
+
var CONNECT_CODES = /* @__PURE__ */ new Set([
|
|
12
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
13
|
+
"ECONNREFUSED",
|
|
14
|
+
"ECONNRESET",
|
|
15
|
+
"ENOTFOUND",
|
|
16
|
+
"EHOSTUNREACH",
|
|
17
|
+
"ENETUNREACH",
|
|
18
|
+
"ETIMEDOUT"
|
|
19
|
+
]);
|
|
20
|
+
function hasProxyEnv(env) {
|
|
21
|
+
return Boolean(env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy);
|
|
22
|
+
}
|
|
23
|
+
function connectFailureHint(error, url) {
|
|
24
|
+
const cause = error instanceof Error ? error.cause : void 0;
|
|
25
|
+
if (!cause?.code || !CONNECT_CODES.has(cause.code)) return null;
|
|
26
|
+
let host;
|
|
27
|
+
try {
|
|
28
|
+
host = new URL(url).host;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return `Could not connect to ${host} (${cause.code}). The request never reached the network. If this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY.`;
|
|
33
|
+
}
|
|
34
|
+
function requestUrl(input) {
|
|
35
|
+
if (typeof input === "string") return input;
|
|
36
|
+
if (input instanceof URL) return input.href;
|
|
37
|
+
return input.url;
|
|
38
|
+
}
|
|
39
|
+
async function proxyFetch(input, init) {
|
|
40
|
+
if (!hasProxyEnv(process.env)) {
|
|
41
|
+
return await fetch(input, init);
|
|
42
|
+
}
|
|
43
|
+
const dispatcher = new EnvHttpProxyAgent();
|
|
44
|
+
try {
|
|
45
|
+
const response = await undiciFetch(requestUrl(input), {
|
|
46
|
+
...init,
|
|
47
|
+
dispatcher
|
|
48
|
+
});
|
|
49
|
+
const buffered = Buffer.from(await response.arrayBuffer());
|
|
50
|
+
await dispatcher.close();
|
|
51
|
+
return new Response(buffered, {
|
|
52
|
+
status: response.status,
|
|
53
|
+
statusText: response.statusText,
|
|
54
|
+
headers: response.headers
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
await dispatcher.close().catch(() => {
|
|
58
|
+
});
|
|
59
|
+
const hint = connectFailureHint(error, requestUrl(input));
|
|
60
|
+
throw hint ? new Error(hint, { cause: error }) : error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/platform/node.ts
|
|
65
|
+
function isMissingModuleError(e) {
|
|
66
|
+
const err = e;
|
|
67
|
+
return err?.code === "ERR_MODULE_NOT_FOUND" || /Cannot find/.test(err?.message ?? "");
|
|
68
|
+
}
|
|
69
|
+
async function loadSharp(missingDepContext) {
|
|
70
|
+
try {
|
|
71
|
+
return (await import("sharp")).default;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
if (isMissingModuleError(e)) {
|
|
74
|
+
throw new Error(`${missingDepContext} requires the optional dependency "sharp" (npm i sharp)`);
|
|
75
|
+
}
|
|
76
|
+
throw e;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function recodeWithSharp(dataUrl) {
|
|
80
|
+
const sharpMod = await loadSharp("Re-encoding non-PNG/JPEG/GIF images");
|
|
81
|
+
const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
|
|
82
|
+
const png = await sharpMod(Buffer.from(base64, "base64")).png().toBuffer();
|
|
83
|
+
return `data:image/png;base64,${png.toString("base64")}`;
|
|
84
|
+
}
|
|
85
|
+
async function rasterizeWithSharp(svgMarkup, width, height) {
|
|
86
|
+
const remoteRef = findRemoteAssetRef(svgMarkup);
|
|
87
|
+
if (remoteRef) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`rasterizeSvg: refusing to rasterize an SVG that references a remote image (${remoteRef}) \u2014 only data-URI (or other local) assets are supported, pptwise audit never makes a network request`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const sharpMod = await loadSharp("Pixel-contrast auditing (--pixels)");
|
|
93
|
+
const { data, info } = await sharpMod(Buffer.from(svgMarkup, "utf-8")).resize(width, height, { fit: "fill" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
94
|
+
return {
|
|
95
|
+
width: info.width,
|
|
96
|
+
height: info.height,
|
|
97
|
+
data: new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function installNodePlatform() {
|
|
101
|
+
installPlatform({
|
|
102
|
+
domParser: LinkedomDOMParser,
|
|
103
|
+
recodeImageToPng: recodeWithSharp,
|
|
104
|
+
rasterizeSvg: rasterizeWithSharp,
|
|
105
|
+
fetch: proxyFetch
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
proxyFetch,
|
|
111
|
+
isMissingModuleError,
|
|
112
|
+
installNodePlatform
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=chunk-3ZUKISTY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/platform/node.ts","../src/cli/proxy-fetch.ts"],"sourcesContent":["import { DOMParser as LinkedomDOMParser } from \"linkedom\"\nimport { proxyFetch } from \"../cli/proxy-fetch\"\nimport { findRemoteAssetRef, installPlatform, type RasterizedImage } from \"./registry\"\nimport type * as Sharp from \"sharp\"\n\n/**\n * `true` when `e` looks like Node's own \"module not found\" shape for a\n * missing optional dependency — split out from `loadSharp` below as its own\n * pure predicate so it's directly unit-testable against synthetic error\n * objects (`node.test.ts`). Simulating a genuinely missing `sharp` package\n * through `vi.mock(\"sharp\", ...)` doesn't work for this: vitest wraps *any*\n * exception a mock factory raises (sync throw or a rejected promise alike)\n * in its own generic \"There was an error when mocking a module\" message\n * (confirmed empirically while building this test, not assumed) rather than\n * letting `await import(\"sharp\")` observe the original error — so the\n * classification logic is what gets tested directly here, while the\n * surrounding `try { await import(...) }` wiring is exercised for real by\n * every other test in this repo that successfully loads the real, installed\n * `sharp` package through this exact path.\n */\nexport function isMissingModuleError(e: unknown): boolean {\n const err = e as NodeJS.ErrnoException\n return err?.code === \"ERR_MODULE_NOT_FOUND\" || /Cannot find/.test(err?.message ?? \"\")\n}\n\n/**\n * Both Sharp-needing paths (this one and `recodeWithSharp` below) hit the\n * exact same \"optional dependency not installed\" failure mode and must\n * report it the same explicit way — spec §4.3's own requirement (\"如果用户\n * 显式传入 --pixels 但 Sharp 不可用,命令必须失败并说明缺失依赖\"). Threading\n * a caller-specific sentence through one shared thrower keeps that contract\n * in one place instead of two copies drifting apart.\n */\nasync function loadSharp(missingDepContext: string): Promise<typeof Sharp.default> {\n try {\n return (await import(\"sharp\")).default as unknown as typeof Sharp.default\n } catch (e) {\n if (isMissingModuleError(e)) {\n throw new Error(`${missingDepContext} requires the optional dependency \"sharp\" (npm i sharp)`)\n }\n throw e\n }\n}\n\nasync function recodeWithSharp(dataUrl: string): Promise<string> {\n const sharpMod = await loadSharp(\"Re-encoding non-PNG/JPEG/GIF images\")\n const base64 = dataUrl.slice(dataUrl.indexOf(\",\") + 1)\n const png = await sharpMod(Buffer.from(base64, \"base64\")).png().toBuffer()\n return `data:image/png;base64,${png.toString(\"base64\")}`\n}\n\n/**\n * Node's `rasterizeSvg` (audit-v2 phase B, spec §4.3/§11.9): Sharp stays the\n * pre-authorized default (already an optional dependency, zero new\n * dependency to add) — the escape clause to `@resvg/resvg-js` only fires if\n * a real render out of this repo's own SVG subset comes back visibly wrong,\n * which `node-rasterize.test.ts`'s probe suite exists to catch. Missing\n * Sharp is an explicit failure (`loadSharp` above), never a silent skip —\n * the pixel audit's own \"未检查≠通过\" contract extended to a missing\n * platform capability, spec §11.7's \"契约层\".\n *\n * `findRemoteAssetRef` guards this the same way it guards the browser\n * implementation (`browser.ts`) — see that function's own doc comment for\n * why the guard lives once, shared, rather than trusting every caller to\n * pre-filter: a Sharp/librsvg fetch of a remote `href` would be a real\n * network request from inside `pptwise audit`, which spec §3.1/§7 promise\n * never happens.\n *\n * No explicit `density` — an empirically-verified default (this task's own\n * probe): this renderer's root `<svg>` carries a `viewBox=\"0 0 1280 720\"`\n * with no `width`/`height` attributes, and Sharp/libvips already resolves\n * that to a 1280×720 intrinsic size with no density hint at all (confirmed\n * directly — adding an explicit `density: 96` to match this renderer's own\n * 96-px/in convention (`constants.ts`) actually *overshoots* to 1707×960,\n * since Sharp's default SVG density is 72, not 96, and only applies when a\n * physical unit is present for it to scale). `.resize(width, height, {fit:\n * \"fill\"})` is kept anyway as a defensive guarantee of the exact requested\n * output size regardless of the source SVG's own sizing, not because it\n * currently changes anything.\n */\nasync function rasterizeWithSharp(svgMarkup: string, width: number, height: number): Promise<RasterizedImage> {\n const remoteRef = findRemoteAssetRef(svgMarkup)\n if (remoteRef) {\n throw new Error(\n `rasterizeSvg: refusing to rasterize an SVG that references a remote image (${remoteRef}) — only data-URI (or other local) assets are supported, pptwise audit never makes a network request`,\n )\n }\n const sharpMod = await loadSharp(\"Pixel-contrast auditing (--pixels)\")\n const { data, info } = await sharpMod(Buffer.from(svgMarkup, \"utf-8\"))\n .resize(width, height, { fit: \"fill\" })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n return {\n width: info.width,\n height: info.height,\n data: new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength),\n }\n}\n\n/** Wire Node implementations (linkedom DOM, sharp image re-encode + SVG\n * rasterize) into the SDK. */\nexport function installNodePlatform(): void {\n installPlatform({\n domParser: LinkedomDOMParser as unknown as typeof DOMParser,\n recodeImageToPng: recodeWithSharp,\n rasterizeSvg: rasterizeWithSharp,\n fetch: proxyFetch,\n })\n}\n","/**\n * Proxy-aware fetch for gallery API requests (issue #20). Node's fetch\n * ignores HTTP_PROXY/HTTPS_PROXY. Dispatcher and fetch both come from\n * undici. The dispatcher is closed after the body is buffered so a\n * keep-alive pool cannot pin the CLI process (#23).\n */\nimport { EnvHttpProxyAgent, fetch as undiciFetch } from \"undici\"\n\nconst CONNECT_CODES = new Set([\n \"UND_ERR_CONNECT_TIMEOUT\",\n \"ECONNREFUSED\",\n \"ECONNRESET\",\n \"ENOTFOUND\",\n \"EHOSTUNREACH\",\n \"ENETUNREACH\",\n \"ETIMEDOUT\",\n])\n\nfunction hasProxyEnv(env: NodeJS.ProcessEnv): boolean {\n return Boolean(env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy)\n}\n\nexport function connectFailureHint(error: unknown, url: string): string | null {\n const cause = error instanceof Error ? (error.cause as { code?: string } | undefined) : undefined\n if (!cause?.code || !CONNECT_CODES.has(cause.code)) return null\n let host: string\n try {\n host = new URL(url).host\n } catch {\n return null\n }\n return (\n `Could not connect to ${host} (${cause.code}). The request never reached the network. ` +\n \"If this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY.\"\n )\n}\n\nfunction requestUrl(input: RequestInfo | URL): string {\n if (typeof input === \"string\") return input\n if (input instanceof URL) return input.href\n return input.url\n}\n\nexport async function proxyFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n if (!hasProxyEnv(process.env)) {\n return await fetch(input, init)\n }\n const dispatcher = new EnvHttpProxyAgent()\n try {\n const response = await undiciFetch(requestUrl(input), {\n ...(init as Parameters<typeof undiciFetch>[1]),\n dispatcher,\n })\n const buffered = Buffer.from(await response.arrayBuffer())\n await dispatcher.close()\n return new Response(buffered, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers as HeadersInit,\n })\n } catch (error) {\n await dispatcher.close().catch(() => {})\n const hint = connectFailureHint(error, requestUrl(input))\n throw hint ? new Error(hint, { cause: error }) : error\n }\n}\n"],"mappings":"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","names":[]}
|