agent-reader 1.1.0 → 1.1.2

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
@@ -232,6 +232,7 @@ Claude Desktop 配置(`claude_desktop_config.json`):
232
232
  ## 云环境部署
233
233
 
234
234
  在 Docker/CI 中,Agent Reader 会自动检测环境并在需要时为 Puppeteer 关闭沙盒参数(`--no-sandbox` 等)。
235
+ 在部分非标准云环境里,`auto` 模式首次失败时也会自动重试一次 `no-sandbox`,减少手动排障。
235
236
 
236
237
  手动覆盖方式:
237
238
 
@@ -4,6 +4,7 @@ import {
4
4
  cleanCommand,
5
5
  doctorCommand,
6
6
  exportCommand,
7
+ mcpStubCommand,
7
8
  openCommand,
8
9
  renderCommand,
9
10
  runCommandSafely,
@@ -16,7 +17,7 @@ const program = new Command();
16
17
  program
17
18
  .name('agent-reader')
18
19
  .description('AI Agent output beautifier and slideshow generator')
19
- .version('1.1.0');
20
+ .version('1.1.2');
20
21
 
21
22
  setupCommonCommandOptions(
22
23
  program
@@ -80,4 +81,9 @@ program
80
81
  .option('--days <days>', 'max age in days', '7')
81
82
  .action((options) => runCommandSafely(() => cleanCommand(options)));
82
83
 
84
+ program
85
+ .command('mcp')
86
+ .description('Start MCP server on stdio')
87
+ .action(() => runCommandSafely(() => mcpStubCommand()));
88
+
83
89
  await program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-reader",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI Agent 的文档美化引擎 — 一键把 Markdown 变成漂亮网页、Word、PDF 和幻灯片",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
3
4
  import open from 'open';
4
5
  import { execa } from 'execa';
5
6
  import { renderMarkdown } from '../core/renderer.js';
@@ -49,6 +50,9 @@ function normalizeMode(options) {
49
50
  }
50
51
 
51
52
  async function readAllFromStdin() {
53
+ if (process.stdin.isTTY) {
54
+ throw new Error('stdin is a terminal — pipe content or use a file path instead (e.g. echo "# hi" | agent-reader render --stdin)');
55
+ }
52
56
  const chunks = [];
53
57
  for await (const chunk of process.stdin) {
54
58
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -591,7 +595,9 @@ export async function cleanCommand(options) {
591
595
  }
592
596
 
593
597
  export async function mcpStubCommand() {
594
- const result = await execa('node', ['src/mcp/server.js'], { stdio: 'inherit' });
598
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
599
+ const serverPath = path.resolve(__dirname, '..', 'mcp', 'server.js');
600
+ const result = await execa('node', [serverPath], { stdio: 'inherit' });
595
601
  return result;
596
602
  }
597
603
 
@@ -690,30 +690,15 @@ export function createPandocFallbackWarnings(platform = process.platform) {
690
690
  ];
691
691
  }
692
692
 
693
- export async function exportPDF(html, options = {}) {
694
- const {
695
- pageSize = 'A4',
696
- landscape = false,
697
- outDir = os.tmpdir(),
698
- fileName = 'output.pdf',
699
- htmlPath,
700
- sandbox,
701
- } = options;
702
-
703
- let puppeteer;
704
- try {
705
- const mod = await import('puppeteer');
706
- puppeteer = mod.default || mod;
707
- } catch {
708
- throw new Error('Puppeteer is not installed. Install optional dependency: npm install puppeteer');
709
- }
710
-
711
- const warnings = [];
712
- const pdfPath = path.join(path.resolve(outDir), fileName);
713
- const resolvedSandboxMode = resolveSandboxMode(sandbox);
714
- const launchArgs = landscape ? ['--allow-file-access-from-files'] : [];
715
- launchArgs.push(...getSandboxArgs(resolvedSandboxMode));
716
-
693
+ async function renderPdfWithPuppeteer({
694
+ puppeteer,
695
+ launchArgs,
696
+ html,
697
+ htmlPath,
698
+ pageSize,
699
+ landscape,
700
+ pdfPath,
701
+ }) {
717
702
  const browser = await puppeteer.launch({
718
703
  headless: true,
719
704
  args: launchArgs,
@@ -723,14 +708,24 @@ export async function exportPDF(html, options = {}) {
723
708
 
724
709
  if (htmlPath) {
725
710
  await page.goto(pathToFileURL(path.resolve(htmlPath)).toString(), {
726
- waitUntil: 'networkidle0',
711
+ waitUntil: 'domcontentloaded',
727
712
  });
728
713
  } else {
729
714
  await page.setContent(html, {
730
- waitUntil: 'networkidle0',
715
+ waitUntil: 'domcontentloaded',
731
716
  });
732
717
  }
733
718
 
719
+ // Wait for images and fonts to finish loading before generating PDF.
720
+ await page.evaluate(() =>
721
+ Promise.all([
722
+ document.fonts?.ready,
723
+ ...Array.from(document.images).map((img) =>
724
+ img.complete ? Promise.resolve() : new Promise((r) => { img.onload = r; img.onerror = r; }),
725
+ ),
726
+ ]),
727
+ );
728
+
734
729
  await page.addStyleTag({
735
730
  content: `
736
731
  @page {
@@ -798,7 +793,72 @@ blockquote { break-inside: avoid; }`,
798
793
  tagged: true,
799
794
  });
800
795
  } finally {
801
- await browser.close();
796
+ await browser.close().catch(() => {});
797
+ }
798
+ }
799
+
800
+ export async function exportPDF(html, options = {}) {
801
+ const {
802
+ pageSize = 'A4',
803
+ landscape = false,
804
+ outDir = os.tmpdir(),
805
+ fileName = 'output.pdf',
806
+ htmlPath,
807
+ sandbox,
808
+ puppeteerInstance,
809
+ sandboxRuntime,
810
+ } = options;
811
+
812
+ let puppeteer = puppeteerInstance;
813
+ try {
814
+ if (!puppeteer) {
815
+ const mod = await import('puppeteer');
816
+ puppeteer = mod.default || mod;
817
+ }
818
+ } catch {
819
+ throw new Error('Puppeteer is not installed. Install optional dependency: npm install puppeteer');
820
+ }
821
+
822
+ const warnings = [];
823
+ const pdfPath = path.join(path.resolve(outDir), fileName);
824
+ const resolvedSandboxMode = resolveSandboxMode(sandbox);
825
+ const baseArgs = landscape ? ['--allow-file-access-from-files'] : [];
826
+ const primaryArgs = [...baseArgs, ...getSandboxArgs(resolvedSandboxMode, sandboxRuntime)];
827
+ const canRetryWithNoSandbox = resolvedSandboxMode === 'auto'
828
+ && !primaryArgs.includes('--no-sandbox');
829
+
830
+ try {
831
+ await renderPdfWithPuppeteer({
832
+ puppeteer,
833
+ launchArgs: primaryArgs,
834
+ html,
835
+ htmlPath,
836
+ pageSize,
837
+ landscape,
838
+ pdfPath,
839
+ });
840
+ } catch (primaryError) {
841
+ if (!canRetryWithNoSandbox) {
842
+ throw primaryError;
843
+ }
844
+
845
+ const fallbackArgs = [...baseArgs, ...SANDBOX_DISABLED_ARGS];
846
+ try {
847
+ await renderPdfWithPuppeteer({
848
+ puppeteer,
849
+ launchArgs: fallbackArgs,
850
+ html,
851
+ htmlPath,
852
+ pageSize,
853
+ landscape,
854
+ pdfPath,
855
+ });
856
+ warnings.push('sandbox_auto_retry_off');
857
+ } catch (fallbackError) {
858
+ throw new Error(
859
+ `PDF export failed in auto mode and fallback off mode: ${fallbackError.message} (auto error: ${primaryError.message})`,
860
+ );
861
+ }
802
862
  }
803
863
 
804
864
  const stat = await fs.stat(pdfPath);
package/src/mcp/server.js CHANGED
@@ -72,7 +72,7 @@ async function saveHtmlResult(html, outputDir, name = 'output') {
72
72
 
73
73
  const server = new McpServer({
74
74
  name: 'agent-reader',
75
- version: '1.1.0',
75
+ version: '1.1.2',
76
76
  });
77
77
 
78
78
  server.registerTool(