agent-reader 1.0.0 → 1.1.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/README.md CHANGED
@@ -29,6 +29,24 @@ Agent Reader 解决这三个问题:**一条命令,输出即交付**。
29
29
  | 图片 → 幻灯片 | 全屏播放、键盘翻页、自动轮播、缩略图导航 |
30
30
  | 幻灯片 → PDF | 每张图片一页,适合存档分享 |
31
31
 
32
+ ## 幻灯片快速上手
33
+
34
+ 如果你有一组图片要现场展示,直接用这一条命令:
35
+
36
+ ```bash
37
+ agent-reader slides ./my-images/
38
+ ```
39
+
40
+ 常用选项:
41
+
42
+ ```bash
43
+ agent-reader slides ./my-images/ --auto 5
44
+ agent-reader slides ./my-images/ --format pdf
45
+ ```
46
+
47
+ 支持格式:`png`、`jpg`、`jpeg`、`gif`、`svg`、`webp`。
48
+ 图片按自然顺序排序(例如 `1`、`2`、`10`,不是 `1`、`10`、`2`)。
49
+
32
50
  ## 两种使用方式
33
51
 
34
52
  **CLI** — 开发者和 Agent 通过命令行调用
@@ -93,7 +111,6 @@ agent-reader render 你的文件.md
93
111
  ```bash
94
112
  agent-reader render 你的文件.md --theme light # 亮色(默认)
95
113
  agent-reader render 你的文件.md --theme dark # 暗色
96
- agent-reader render 你的文件.md --theme print # 打印友好
97
114
  ```
98
115
 
99
116
  ### 导出 Word
@@ -211,3 +228,49 @@ Claude Desktop 配置(`claude_desktop_config.json`):
211
228
  | Node.js 18+ | 是 | 运行环境 |
212
229
  | Puppeteer | 是 | PDF 导出(安装时自动下载 Chromium) |
213
230
  | Pandoc | 否 | Word 导出更好看(没有会自动降级为纯 JS 方案) |
231
+
232
+ ## 云环境部署
233
+
234
+ 在 Docker/CI 中,Agent Reader 会自动检测环境并在需要时为 Puppeteer 关闭沙盒参数(`--no-sandbox` 等)。
235
+
236
+ 手动覆盖方式:
237
+
238
+ ```bash
239
+ # auto | on | off
240
+ AGENT_READER_SANDBOX=off agent-reader export report.md --format pdf
241
+ ```
242
+
243
+ Docker 环境建议预装 Chromium 依赖(示例):
244
+
245
+ ```bash
246
+ apt-get update && apt-get install -y chromium-browser
247
+ ```
248
+
249
+ ## FAQ:没有 Pandoc 怎么办?
250
+
251
+ - 不装 Pandoc 也能导出 DOCX,只是排版会使用基础模式
252
+ - 装 Pandoc 后,代码块和复杂表格的效果更稳定
253
+
254
+ 常见安装命令:
255
+
256
+ ```bash
257
+ # macOS
258
+ brew install pandoc
259
+
260
+ # Linux
261
+ apt-get install pandoc
262
+
263
+ # Windows
264
+ winget install pandoc
265
+ # or: choco install pandoc
266
+ # or: scoop install pandoc
267
+ ```
268
+
269
+ ## Puppeteer 进阶安装
270
+
271
+ 如果你已经有可用浏览器,想减少安装体积:
272
+
273
+ ```bash
274
+ PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install -g agent-reader
275
+ PUPPETEER_EXECUTABLE_PATH=/path/to/chrome agent-reader export report.md --format pdf
276
+ ```
package/SKILL.md ADDED
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: agent_reader
3
+ description: 把 Markdown 渲染成漂亮网页、导出 Word/PDF、图片做幻灯片。专为 AI Agent 输出设计。
4
+ ---
5
+
6
+ # Agent Reader Skill
7
+
8
+ ## 能力
9
+
10
+ - Markdown 渲染为可阅读网页(目录、代码高亮、表格样式)
11
+ - 导出 PDF / DOCX 文档
12
+ - 图片目录生成幻灯片并支持导出 PDF
13
+
14
+ ## 触发条件
15
+
16
+ - 用户要求把 Markdown 输出整理成可交付文档
17
+ - 用户需要导出 Word 或 PDF
18
+ - 用户希望把图片集合用于展示或汇报
19
+
20
+ ## CLI 调用
21
+
22
+ ```bash
23
+ agent-reader render report.md
24
+ agent-reader export report.md --format pdf
25
+ agent-reader export report.md --format docx
26
+ agent-reader slides ./images --auto 5
27
+ ```
28
+
29
+ ## MCP 调用
30
+
31
+ - `render_markdown`
32
+ - `export_document`
33
+ - `create_slideshow`
34
+ - `open_file`
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ npm install -g agent-reader
40
+ ```
@@ -16,7 +16,7 @@ const program = new Command();
16
16
  program
17
17
  .name('agent-reader')
18
18
  .description('AI Agent output beautifier and slideshow generator')
19
- .version('0.1.0');
19
+ .version('1.1.0');
20
20
 
21
21
  setupCommonCommandOptions(
22
22
  program
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: agent_reader
3
+ description: 把 Markdown 渲染成漂亮网页、导出 Word/PDF、图片做幻灯片。专为 AI Agent 输出设计。
4
+ ---
5
+
6
+ # Agent Reader Skill
7
+
8
+ ## 能力
9
+
10
+ - Markdown 渲染为可阅读网页(目录、代码高亮、表格样式)
11
+ - 导出 PDF / DOCX 文档
12
+ - 图片目录生成幻灯片并支持导出 PDF
13
+
14
+ ## 触发条件
15
+
16
+ - 用户要求把 Markdown 输出整理成可交付文档
17
+ - 用户需要导出 Word 或 PDF
18
+ - 用户希望把图片集合用于展示或汇报
19
+
20
+ ## CLI 调用
21
+
22
+ ```bash
23
+ agent-reader render report.md
24
+ agent-reader export report.md --format pdf
25
+ agent-reader export report.md --format docx
26
+ agent-reader slides ./images --auto 5
27
+ ```
28
+
29
+ ## MCP 调用
30
+
31
+ - `render_markdown`
32
+ - `export_document`
33
+ - `create_slideshow`
34
+ - `open_file`
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ npm install -g agent-reader
40
+ ```
@@ -0,0 +1,197 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "agent-reader-openclaw-skill-schema",
4
+ "type": "object",
5
+ "tools": {
6
+ "render_markdown": {
7
+ "description": "Render markdown text into styled HTML",
8
+ "input": {
9
+ "type": "object",
10
+ "properties": {
11
+ "content": {
12
+ "type": "string",
13
+ "description": "Markdown source content"
14
+ },
15
+ "source_path": {
16
+ "type": "string",
17
+ "description": "Source markdown path for relative images; relative resources must stay under source directory."
18
+ },
19
+ "theme": {
20
+ "type": "string",
21
+ "description": "Theme name"
22
+ },
23
+ "auto_open": {
24
+ "type": "boolean",
25
+ "description": "Open output automatically (ignored in MCP)"
26
+ },
27
+ "return_content": {
28
+ "type": "boolean",
29
+ "description": "Return inline HTML content directly"
30
+ }
31
+ },
32
+ "required": [
33
+ "content"
34
+ ],
35
+ "additionalProperties": false
36
+ },
37
+ "output": {
38
+ "type": "object",
39
+ "properties": {
40
+ "html_path": {
41
+ "type": "string"
42
+ },
43
+ "content_data": {
44
+ "type": "string"
45
+ },
46
+ "format": {
47
+ "type": "string",
48
+ "enum": [
49
+ "html"
50
+ ]
51
+ },
52
+ "size": {
53
+ "type": "number"
54
+ },
55
+ "warnings": {
56
+ "type": "array",
57
+ "items": {
58
+ "type": "string"
59
+ }
60
+ }
61
+ },
62
+ "required": [
63
+ "format",
64
+ "size",
65
+ "warnings"
66
+ ],
67
+ "additionalProperties": true
68
+ }
69
+ },
70
+ "export_document": {
71
+ "description": "Export markdown text into PDF or DOCX",
72
+ "input": {
73
+ "type": "object",
74
+ "properties": {
75
+ "content": {
76
+ "type": "string",
77
+ "description": "Markdown source content"
78
+ },
79
+ "source_path": {
80
+ "type": "string",
81
+ "description": "Source markdown path for relative images; relative resources must stay under source directory."
82
+ },
83
+ "format": {
84
+ "type": "string",
85
+ "enum": [
86
+ "pdf",
87
+ "docx"
88
+ ],
89
+ "description": "Export format"
90
+ },
91
+ "return_content": {
92
+ "type": "boolean",
93
+ "description": "Return file bytes as base64"
94
+ }
95
+ },
96
+ "required": [
97
+ "content",
98
+ "format"
99
+ ],
100
+ "additionalProperties": false
101
+ },
102
+ "output": {
103
+ "type": "object",
104
+ "properties": {
105
+ "file_path": {
106
+ "type": "string"
107
+ },
108
+ "content_data": {
109
+ "type": "string"
110
+ },
111
+ "format": {
112
+ "type": "string",
113
+ "enum": [
114
+ "pdf",
115
+ "docx"
116
+ ]
117
+ },
118
+ "size": {
119
+ "type": "number"
120
+ },
121
+ "warnings": {
122
+ "type": "array",
123
+ "items": {
124
+ "type": "string"
125
+ }
126
+ }
127
+ },
128
+ "required": [
129
+ "format",
130
+ "size",
131
+ "warnings"
132
+ ],
133
+ "additionalProperties": true
134
+ }
135
+ },
136
+ "create_slideshow": {
137
+ "description": "Create slideshow HTML from an image directory",
138
+ "input": {
139
+ "type": "object",
140
+ "properties": {
141
+ "image_dir": {
142
+ "type": "string",
143
+ "description": "Absolute or relative image directory path"
144
+ },
145
+ "auto_play": {
146
+ "type": "number",
147
+ "description": "Autoplay interval in seconds"
148
+ },
149
+ "auto_open": {
150
+ "type": "boolean",
151
+ "description": "Open output automatically (ignored in MCP)"
152
+ },
153
+ "return_content": {
154
+ "type": "boolean",
155
+ "description": "Return inline HTML content directly"
156
+ }
157
+ },
158
+ "required": [
159
+ "image_dir"
160
+ ],
161
+ "additionalProperties": false
162
+ },
163
+ "output": {
164
+ "type": "object",
165
+ "properties": {
166
+ "html_path": {
167
+ "type": "string"
168
+ },
169
+ "content_data": {
170
+ "type": "string"
171
+ },
172
+ "format": {
173
+ "type": "string",
174
+ "enum": [
175
+ "html"
176
+ ]
177
+ },
178
+ "size": {
179
+ "type": "number"
180
+ },
181
+ "warnings": {
182
+ "type": "array",
183
+ "items": {
184
+ "type": "string"
185
+ }
186
+ }
187
+ },
188
+ "required": [
189
+ "format",
190
+ "size",
191
+ "warnings"
192
+ ],
193
+ "additionalProperties": true
194
+ }
195
+ }
196
+ }
197
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-reader",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI Agent 的文档美化引擎 — 一键把 Markdown 变成漂亮网页、Word、PDF 和幻灯片",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -4,7 +4,14 @@ import open from 'open';
4
4
  import { execa } from 'execa';
5
5
  import { renderMarkdown } from '../core/renderer.js';
6
6
  import { createSlideshow } from '../core/slideshow.js';
7
- import { checkPandoc, checkPuppeteer, exportDOCX, exportDOCXFromHTML, exportPDF } from '../core/exporter.js';
7
+ import {
8
+ checkPandoc,
9
+ checkPuppeteer,
10
+ exportDOCX,
11
+ exportDOCXFromHTML,
12
+ exportPDF,
13
+ resolveSandboxMode,
14
+ } from '../core/exporter.js';
8
15
  import { openTarget } from '../core/opener.js';
9
16
  import { cleanOldOutputs, createOutputDir } from '../utils/output.js';
10
17
  import { createLogger } from '../utils/logger.js';
@@ -86,6 +93,10 @@ function dedupeWarnings(warnings) {
86
93
  return [...new Set((warnings || []).filter(Boolean))];
87
94
  }
88
95
 
96
+ function resolveSandboxOption(options = {}) {
97
+ return resolveSandboxMode(options.sandbox, process.env);
98
+ }
99
+
89
100
  async function buildPrintHtml(imageDir) {
90
101
  const absDir = path.resolve(imageDir);
91
102
  const entries = await fs.readdir(absDir, { withFileTypes: true });
@@ -135,9 +146,11 @@ async function maybeServeOutput(options, outputDir, targetPath, logger, jsonOnly
135
146
  return null;
136
147
  }
137
148
 
149
+ const sandbox = resolveSandboxOption(options);
138
150
  const serverHandle = await startStaticServer(outputDir, {
139
151
  host: '127.0.0.1',
140
152
  port: Number(options.port || 3000),
153
+ sandbox,
141
154
  });
142
155
 
143
156
  const rel = path.relative(outputDir, targetPath).split(path.sep).join('/');
@@ -222,6 +235,7 @@ export async function exportCommand(file, options) {
222
235
 
223
236
  try {
224
237
  const format = normalizeFormat(options.format);
238
+ const sandbox = resolveSandboxOption(options);
225
239
  const input = await resolveMarkdownInput(file, options, mode.logger);
226
240
  const outputDir = await createOutputDir(input.name, options.outDir);
227
241
  const warnings = [];
@@ -249,6 +263,7 @@ export async function exportCommand(file, options) {
249
263
  outDir: outputDir,
250
264
  fileName: `${input.name}.pdf`,
251
265
  htmlPath,
266
+ sandbox,
252
267
  });
253
268
  warnings.push(...pdf.warnings);
254
269
  targetPath = pdf.pdfPath;
@@ -322,6 +337,7 @@ export async function slidesCommand(dir, options) {
322
337
  await fs.writeFile(printHtmlPath, result.printHtml, 'utf8');
323
338
 
324
339
  const format = String(options.format || '').toLowerCase();
340
+ const sandbox = resolveSandboxOption(options);
325
341
  const dirName = path.basename(inputDir);
326
342
 
327
343
  if (format === 'pdf') {
@@ -331,6 +347,7 @@ export async function slidesCommand(dir, options) {
331
347
  outDir: outputDir,
332
348
  fileName: `${dirName}.pdf`,
333
349
  htmlPath: printHtmlPath,
350
+ sandbox,
334
351
  });
335
352
 
336
353
  const pdfWarnings = dedupeWarnings([...result.warnings, ...(pdf.warnings || [])]);
@@ -398,6 +415,7 @@ export async function openCommand(target, options) {
398
415
  throw new Error('missing target path');
399
416
  }
400
417
 
418
+ const sandbox = resolveSandboxOption(options);
401
419
  const preferences = await loadPreferences();
402
420
  const requestedMode = normalizeOpenMode(options.as || options.mode || 'auto', 'auto');
403
421
 
@@ -410,6 +428,7 @@ export async function openCommand(target, options) {
410
428
  pageSize: options.pageSize || 'A4',
411
429
  fetchRemote: options.fetchRemote !== false,
412
430
  returnContent: false,
431
+ sandbox,
413
432
  });
414
433
 
415
434
  const canServe = result.format === 'html' && result.path;
@@ -598,5 +617,6 @@ export function setupCommonCommandOptions(command) {
598
617
  .option('--inline-all', 'inline all assets to base64')
599
618
  .option('--no-fetch-remote', 'disable remote image fetching')
600
619
  .option('--serve', 'serve generated files via local HTTP server')
601
- .option('--port <port>', 'port for --serve mode', '3000');
620
+ .option('--port <port>', 'port for --serve mode', '3000')
621
+ .option('--sandbox <mode>', 'Puppeteer sandbox mode: auto|on|off');
602
622
  }
@@ -3,6 +3,7 @@ import dns from 'node:dns/promises';
3
3
  import { promises as fs } from 'node:fs';
4
4
  import net from 'node:net';
5
5
  import path from 'node:path';
6
+ import { assertWithinBase } from '../utils/pathGuard.js';
6
7
 
7
8
  const LOCAL_IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp']);
8
9
  const DEFAULT_MAX_INLINE_BYTES = 200 * 1024;
@@ -241,6 +242,13 @@ async function processLocalImage(src, {
241
242
  }
242
243
 
243
244
  const resolvedPath = isAbsoluteFile ? cleanSrc : path.resolve(baseDir, cleanSrc);
245
+ if (!isAbsoluteFile) {
246
+ const withinBase = await assertWithinBase(resolvedPath, baseDir);
247
+ if (!withinBase) {
248
+ warnings.push(`path traversal blocked: ${cleanSrc}`);
249
+ return cleanSrc;
250
+ }
251
+ }
244
252
 
245
253
  let fileBuffer;
246
254
  try {