portable-agent-layer 0.26.1 → 0.27.1

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.
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: create-pdf
3
- description: Convert markdown files into a styled PDF report using md-to-pdf. Use when creating a PDF from existing markdown files, combining markdown into a report, or converting .md to .pdf.
3
+ description: Convert markdown files into a styled PDF. Use when creating a PDF from existing markdown files, combining markdown into a report, or converting .md to .pdf.
4
4
  argument-hint: <file paths, glob pattern, or directory containing .md files>
5
5
  ---
6
6
 
7
7
  ## Overview
8
8
 
9
- Combine one or more markdown files into a single styled PDF using `bunx --bun md-to-pdf`. This skill handles concatenation, page breaks, styling, and conversion. It does NOT generate content use the `research` skill or other content-generation workflows first, then pipe the resulting markdown files into this skill.
9
+ Combine one or more markdown files into a single styled PDF. Pipeline: Markdown HTML PDF via a headless browser.
10
+
11
+ This skill handles concatenation, page breaks, styling, and conversion. It does NOT generate content — use the `research` skill or other content-generation workflows first, then pipe the resulting markdown files into this skill.
10
12
 
11
13
  ## Input
12
14
 
@@ -26,9 +28,11 @@ Determine the list of markdown files and their order:
26
28
  - If glob/directory: list and sort alphabetically (files prefixed with `00_` come first naturally)
27
29
  - Confirm the file list and order with the user if more than 5 files
28
30
 
29
- ### Step 2: Combine with Page Breaks
31
+ ### Step 2: Combine (only if multiple files)
32
+
33
+ For a single input file, skip this step and pass it directly to the tool in Step 3.
30
34
 
31
- Concatenate all files with page break dividers between them:
35
+ For multiple files, concatenate with page break dividers between them into a single temp markdown:
32
36
 
33
37
  ```bash
34
38
  cat \
@@ -37,83 +41,70 @@ cat \
37
41
  second_file.md \
38
42
  <(echo -e '\n\n<div style="page-break-before: always"></div>\n\n---\n\n') \
39
43
  third_file.md \
40
- ... \
41
- > /tmp/combined_raw.md
44
+ > /tmp/combined.md
42
45
  ```
43
46
 
44
47
  For many files, generate the cat command dynamically rather than typing each one.
45
48
 
46
- ### Step 3: Add PDF Frontmatter
49
+ ### Step 3: Render to PDF
47
50
 
48
- Prepend YAML frontmatter with styling, then append the combined content:
51
+ Invoke the skill tool. Flags:
52
+ - `--pdf <path>` — explicit output PDF path (default: `<input_basename>.pdf` next to the input)
53
+ - `--html <path>` — explicit intermediate HTML path (default: `<input_basename>.html` next to the input; kept for inspection/re-printing)
49
54
 
50
- ```bash
51
- cat > <output_name>.md << 'FRONTMATTER'
52
- ---
53
- pdf_options:
54
- format: A4
55
- margin: 25mm
56
- printBackground: true
57
- stylesheet: https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.css
58
- body_class: markdown-body
59
- css: |-
60
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 11px; line-height: 1.6; color: #1a1a1a; }
61
- h1 { font-size: 22px; border-bottom: 2px solid #333; padding-bottom: 8px; }
62
- h2 { font-size: 17px; }
63
- h3 { font-size: 14px; }
64
- table { border-collapse: collapse; width: 100%; margin: 12px 0; }
65
- th, td { border: 1px solid #ccc; padding: 6px 10px; text-align: left; font-size: 10px; }
66
- th { background: #f0f0f0; }
67
- blockquote { border-left: 3px solid #666; padding-left: 12px; color: #444; }
68
- hr { border: none; border-top: 1px solid #ccc; margin: 20px 0; }
69
- a { color: #0366d6; text-decoration: none; }
70
- ---
55
+ Single-file example:
71
56
 
72
- FRONTMATTER
73
- cat /tmp/combined_raw.md >> <output_name>.md
57
+ ```bash
58
+ bun ~/.pal/skills/create-pdf/tools/md-to-html-pdf.ts /path/to/report.md --pdf /path/to/report.pdf
74
59
  ```
75
60
 
76
- The user may request custom styling (font size, margins, colors). Override the defaults above accordingly.
77
-
78
- ### Step 4: Generate PDF
61
+ Multi-file example (after Step 2):
79
62
 
80
63
  ```bash
81
- bunx --bun md-to-pdf <output_name>.md
64
+ bun ~/.pal/skills/create-pdf/tools/md-to-html-pdf.ts /tmp/combined.md --pdf /path/to/report.pdf --html /path/to/report.html
82
65
  ```
83
66
 
84
- ### Step 5: Verify and Report
67
+ The tool writes the self-contained HTML (inline CSS, UTF-8) and the PDF, and prints both paths + sizes on stdout.
68
+
69
+ ### Step 4: Verify and Report
85
70
 
86
71
  ```bash
87
- ls -lh <output_name>.pdf
72
+ ls -lh <output>.pdf
88
73
  ```
89
74
 
90
75
  Report the file path and size to the user.
91
76
 
77
+ ## Styling
78
+
79
+ Default styling (A4, 25mm margins, GitHub-ish look, table-friendly, page-break-aware headings) is baked into the tool. To customize, edit `tools/md-to-html-pdf.ts` — the `css` string and the `page.pdf({...})` options live side-by-side so fonts, colors, margins, or header/footer templates can be tuned in one place.
80
+
81
+ For header/footer templates (page numbers, client name, CONFIDENTIAL markings, etc.), extend `page.pdf` with `displayHeaderFooter`, `headerTemplate`, and `footerTemplate` — do NOT combine with CSS `@page` margin-box rules, which duplicate.
82
+
92
83
  ## Translation Variant
93
84
 
94
85
  If the user asks to translate markdown files and then create a PDF:
95
86
 
96
- 1. Spawn **parallel subagents** to translate files — batch 2-4 files per agent, all agents in a **single message**
87
+ 1. Spawn **parallel subagents** to translate files — batch 2-4 files per agent, all agents in a **single message** (skip parallelism when there is only one file; a single coherent translator is faster and more consistent)
97
88
  2. Each agent reads the source file, translates all text content to the target language, and writes to `<original_name>_<lang>.md`
98
89
  3. Rules for translation agents:
99
90
  - Keep all markdown formatting, links, and structure identical
100
91
  - Keep source/link titles in their original language; translate surrounding text
101
92
  - Translate naturally, not word-for-word; use proper domain terminology
102
- 4. After all agents complete, run Steps 2-5 above on the translated files
93
+ 4. After all agents complete, run Steps 2-4 above on the translated files
103
94
  5. Output filename gets a `_<lang>` suffix (e.g., `report_hu.pdf`)
104
95
 
105
96
  **Batch sizing for translation agents:**
106
97
 
107
98
  | Files | Agents | Files per agent |
108
99
  |-------|--------|-----------------|
109
- | 1-4 | 1-2 | 2 each |
100
+ | 1 | 0 | done inline |
101
+ | 2-4 | 1-2 | 2 each |
110
102
  | 5-10 | 3-4 | 2-3 each |
111
103
  | 11+ | 5 | 3-4 each |
112
104
 
113
105
  ## Important
114
106
 
115
- - Use `bunx --bun md-to-pdf` (NOT npx) for PDF conversion
116
107
  - This skill only converts — it does not research or generate content
117
108
  - Individual markdown files are preserved alongside the PDF for future editing
118
- - If `md-to-pdf` is not installed, `bunx` will auto-install it on first run
109
+ - The intermediate HTML is also preserved open it in a browser to inspect or Print-to-PDF manually
119
110
  - Always verify the PDF exists before reporting success
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env bun
2
+ // create-pdf skill tool: Markdown -> HTML (marked, GFM) -> PDF (Playwright).
3
+ // Self-contained HTML: all CSS inlined, no CDN at render time.
4
+ //
5
+ // Usage:
6
+ // bun ~/.pal/skills/create-pdf/tools/md-to-html-pdf.ts <input.md> [--html <out.html>] [--pdf <out.pdf>]
7
+
8
+ import { readFile, stat, writeFile } from "node:fs/promises";
9
+ import { basename, dirname, extname, resolve } from "node:path";
10
+ import { marked } from "marked";
11
+ import { chromium } from "playwright";
12
+
13
+ const args = process.argv.slice(2);
14
+ if (args.length === 0) {
15
+ console.error("usage: md-to-html-pdf.ts <input.md> [--html <out>] [--pdf <out>]");
16
+ process.exit(1);
17
+ }
18
+
19
+ const input = resolve(args[0]);
20
+ let htmlOut = "";
21
+ let pdfOut = "";
22
+ for (let i = 1; i < args.length; i++) {
23
+ if (args[i] === "--html") htmlOut = resolve(args[++i]);
24
+ else if (args[i] === "--pdf") pdfOut = resolve(args[++i]);
25
+ }
26
+ const stem = basename(input, extname(input));
27
+ const dir = dirname(input);
28
+ if (!htmlOut) htmlOut = resolve(dir, `${stem}.html`);
29
+ if (!pdfOut) pdfOut = resolve(dir, `${stem}.pdf`);
30
+
31
+ const md = await readFile(input, "utf8");
32
+ marked.setOptions({ gfm: true, breaks: false });
33
+ const body = await marked.parse(md);
34
+
35
+ const css = `
36
+ html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
37
+ body {
38
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
39
+ font-size: 11px; line-height: 1.6; color: #1a1a1a; margin: 0;
40
+ }
41
+ h1 { font-size: 22px; border-bottom: 2px solid #333; padding-bottom: 8px; margin-top: 0; }
42
+ h2 { font-size: 17px; margin-top: 1.6em; }
43
+ h3 { font-size: 14px; }
44
+ h1, h2, h3, h4 { page-break-after: avoid; }
45
+ p, li { orphans: 3; widows: 3; }
46
+ hr { border: none; border-top: 1px solid #ccc; margin: 20px 0; }
47
+ a { color: #0366d6; text-decoration: none; }
48
+ blockquote { border-left: 3px solid #666; padding-left: 12px; color: #444; margin: 12px 0; }
49
+ code { background: #f4f4f4; padding: 1px 4px; border-radius: 3px; font-size: 10px; }
50
+ pre { background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto; }
51
+ pre code { background: transparent; padding: 0; }
52
+ table { border-collapse: collapse; width: 100%; margin: 12px 0; page-break-inside: avoid; }
53
+ th, td { border: 1px solid #ccc; padding: 6px 10px; text-align: left; font-size: 10px; vertical-align: top; }
54
+ th { background: #f0f0f0; }
55
+ tr { page-break-inside: avoid; }
56
+ ul, ol { padding-left: 1.4em; }
57
+ `;
58
+
59
+ const html = `<!doctype html>
60
+ <html>
61
+ <head>
62
+ <meta charset="utf-8">
63
+ <title>${stem}</title>
64
+ <style>${css}</style>
65
+ </head>
66
+ <body>
67
+ ${body}
68
+ </body>
69
+ </html>
70
+ `;
71
+
72
+ await writeFile(htmlOut, html, "utf8");
73
+
74
+ const browser = await chromium.launch();
75
+ try {
76
+ const page = await browser.newPage();
77
+ await page.goto(`file://${htmlOut}`, { waitUntil: "networkidle" });
78
+ await page.pdf({
79
+ path: pdfOut,
80
+ format: "A4",
81
+ margin: { top: "25mm", right: "25mm", bottom: "25mm", left: "25mm" },
82
+ printBackground: true,
83
+ preferCSSPageSize: false,
84
+ });
85
+ } finally {
86
+ await browser.close();
87
+ }
88
+
89
+ const [htmlStat, pdfStat] = await Promise.all([stat(htmlOut), stat(pdfOut)]);
90
+ console.log(`HTML: ${htmlOut} (${(htmlStat.size / 1024).toFixed(1)} KB)`);
91
+ console.log(`PDF: ${pdfOut} (${(pdfStat.size / 1024).toFixed(1)} KB)`);
@@ -10,10 +10,10 @@ Route $ARGUMENTS to the right thinking mode based on intent. Detect the mode fro
10
10
 
11
11
  | Intent signals | Mode | How to invoke |
12
12
  |---------------|------|---------------|
13
- | decompose, root cause, fundamental, challenge assumptions, first principles | **First Principles** | Use the Skill tool to invoke `first-principles` with $ARGUMENTS |
14
- | debate, weigh options, multiple viewpoints, perspectives, deliberate | **Council** | Use the Skill tool to invoke `council` with $ARGUMENTS |
15
- | brainstorm, creative, divergent, ideas, what if, possibilities | **Creative** | Follow the Creative steps below with $ARGUMENTS |
16
- | think through, analyze, explore deeply, examine from angles | **Deep Analysis** | Follow the Deep Analysis steps below with $ARGUMENTS |
13
+ | decompose, root cause, fundamental, challenge assumptions, first principles, why is X happening | **First Principles** | Use the Skill tool to invoke `first-principles` with $ARGUMENTS |
14
+ | debate, weigh options, multiple viewpoints, perspectives, deliberate, should I, which is better | **Council** | Use the Skill tool to invoke `council` with $ARGUMENTS |
15
+ | brainstorm, creative, divergent, ideas, what if, possibilities, how could we, make it better | **Creative** | Follow the Creative steps below with $ARGUMENTS |
16
+ | think through, analyze, explore deeply, examine from angles, tradeoffs, what are the implications | **Deep Analysis** | Follow the Deep Analysis steps below with $ARGUMENTS |
17
17
 
18
18
  If intent is ambiguous, default to **Deep Analysis**.
19
19
 
@@ -39,9 +39,72 @@ Divergent ideation — quantity and variety over polish.
39
39
 
40
40
  Multi-angle exploration for complex topics.
41
41
 
42
- 1. **Frame the question** precisely
42
+ 1. **Frame the question** precisely — what is actually being asked?
43
43
  2. **Technical** — mechanics, constraints, and trade-offs
44
- 3. **Practical** — what does this look like in practice? What's the effort?
44
+ 3. **Practical** — what does this look like in practice? What's the effort and friction?
45
45
  4. **Strategic** — how does this fit the bigger picture? What does it enable or block?
46
- 5. **Tensions** — where do the angles disagree?
46
+ 5. **Tensions** — where do the angles disagree? What can't be optimized simultaneously?
47
47
  6. **Synthesis** — what the analysis reveals that wasn't obvious at the surface
48
+
49
+ ---
50
+
51
+ ## Examples
52
+
53
+ **Example 1 → First Principles**
54
+ ```
55
+ User: "why is our test suite so slow?"
56
+ → Intent: root cause, decompose
57
+ → Invoke first-principles: "Why is the test suite slow?"
58
+ → Break down: what makes tests slow? → I/O, parallelism, fixtures, network calls
59
+ → Challenge each assumption: are all these tests necessary? Is the slowness uniform?
60
+ ```
61
+
62
+ **Example 2 → Council**
63
+ ```
64
+ User: "should we use a monorepo or separate repos?"
65
+ → Intent: debate, weigh options
66
+ → Invoke council with the question
67
+ → Multiple perspectives argue in parallel, then synthesize
68
+ ```
69
+
70
+ **Example 3 → Creative**
71
+ ```
72
+ User: "how could we make onboarding more engaging?"
73
+ → Intent: brainstorm, possibilities
74
+ → Restate: "How do we make first-run onboarding feel less like a form?"
75
+ → Obvious: wizard steps, progress bar, skip option
76
+ → Wild: game mechanic for first task, video from the AI, co-pilot mode for first session
77
+ → Diamond pick: co-pilot mode — high novelty, directly addresses the friction
78
+ ```
79
+
80
+ **Example 4 → Deep Analysis**
81
+ ```
82
+ User: "what are the tradeoffs of server-side rendering?"
83
+ → Intent: explore tradeoffs, implications
84
+ → Technical: hydration cost, TTFB vs TTI, caching strategies
85
+ → Practical: team expertise, deployment complexity, SEO requirements
86
+ → Strategic: enables progressive enhancement, constrains client-side interactivity
87
+ → Tensions: performance vs interactivity, simplicity vs capability
88
+ → Synthesis: SSR is a distribution of complexity, not a removal of it
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Anti-patterns
94
+
95
+ - **Don't ask which mode to use.** Detect from context and commit. The routing table is the decision — use it.
96
+ - **Don't use Deep Analysis as the default for everything.** "Should I do X or Y?" is Council. "Why is X broken?" is First Principles. Reserve Deep Analysis for genuine multi-angle exploration.
97
+ - **Don't produce shallow Creative output.** Wild ideas must genuinely challenge constraints — listing 5 obvious variations is not creative thinking.
98
+ - **Don't skip the Tensions section in Deep Analysis.** If you can't find a tension, you haven't analyzed deeply enough.
99
+ - **Don't combine modes in one response.** Pick one and commit. Mixing modes produces unfocused output.
100
+ - **Don't over-explain the routing.** One line is enough: "This is a root-cause question — First Principles." Then do the work.
101
+
102
+ ---
103
+
104
+ ## Rules
105
+
106
+ - **Mode detection is mandatory** — classify before starting, state it in one line
107
+ - **First Principles and Council are delegated** — always invoke those skills rather than reimplementing them
108
+ - **Creative and Deep Analysis run inline** — follow the steps above directly
109
+ - **Tensions are non-negotiable in Deep Analysis** — name at least one real conflict between the angles
110
+ - **Wild ideas in Creative must be genuinely unconventional** — if a PM would have thought of it in 30 seconds, it's not wild enough
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.26.1",
3
+ "version": "0.27.1",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -76,6 +76,8 @@
76
76
  },
77
77
  "dependencies": {
78
78
  "@clack/prompts": "^1.1.0",
79
- "adm-zip": "^0.5.16"
79
+ "adm-zip": "^0.5.16",
80
+ "marked": "^15.0.0",
81
+ "playwright": "^1.49.0"
80
82
  }
81
83
  }
package/src/cli/index.ts CHANGED
@@ -338,6 +338,24 @@ function checkCopilotInstructionsPresent(): boolean {
338
338
  return existsSync(resolve(platform.copilotDir(), "copilot-instructions.md"));
339
339
  }
340
340
 
341
+ function playwrightBrowsersPath(): string {
342
+ if (process.env.PLAYWRIGHT_BROWSERS_PATH) return process.env.PLAYWRIGHT_BROWSERS_PATH;
343
+ const home = homedir();
344
+ if (process.platform === "darwin") return resolve(home, "Library/Caches/ms-playwright");
345
+ if (process.platform === "win32") return resolve(home, "AppData/Local/ms-playwright");
346
+ return resolve(home, ".cache/ms-playwright");
347
+ }
348
+
349
+ function checkPlaywrightChromium(): boolean {
350
+ const base = playwrightBrowsersPath();
351
+ if (!existsSync(base)) return false;
352
+ try {
353
+ return readdirSync(base).some((f) => f.startsWith("chromium-"));
354
+ } catch {
355
+ return false;
356
+ }
357
+ }
358
+
341
359
  function checkHookHealth(home: string): HookHealth {
342
360
  const logPath = resolve(home, "memory", "state", "debug.log");
343
361
 
@@ -509,6 +527,13 @@ function doctor(silent = false): DoctorResult {
509
527
  ? ok("Dependencies installed")
510
528
  : fail("Dependencies missing — run 'pal cli install'");
511
529
 
530
+ // Playwright Chromium (required by create-pdf + consulting-report skills)
531
+ checkPlaywrightChromium()
532
+ ? ok("Playwright Chromium installed")
533
+ : fail(
534
+ "Playwright Chromium — not found (run 'pal cli install' or 'bunx playwright install chromium')"
535
+ );
536
+
512
537
  // Hook registration (per installed agent)
513
538
  if (claude.available) {
514
539
  checkClaudeHooksRegistered()
@@ -607,6 +632,24 @@ async function install(targets: Targets) {
607
632
  log.warn("bun install failed — continuing anyway, but hooks may not work");
608
633
  }
609
634
 
635
+ // Fetch the Chromium build Playwright uses for PDF rendering (create-pdf skill).
636
+ // Idempotent — skipped if already cached. Skipped entirely under PAL_SKIP_BROWSER_INSTALL=1
637
+ // (used by tests to avoid a ~150MB download on every run).
638
+ // Uses `bun x` (not `bunx`) for Windows compatibility — bunx resolves unreliably under cmd.exe.
639
+ if (process.env.PAL_SKIP_BROWSER_INSTALL !== "1") {
640
+ log.info("Installing Playwright Chromium...");
641
+ const pw = spawnSync("bun", ["x", "playwright", "install", "chromium"], {
642
+ cwd: pkg,
643
+ stdio: "inherit",
644
+ shell: true,
645
+ });
646
+ if (pw.status !== 0) {
647
+ log.warn(
648
+ `playwright install chromium failed (exit ${pw.status}) — create-pdf and consulting-report skills won't work. Retry manually: bun x playwright install chromium`
649
+ );
650
+ }
651
+ }
652
+
610
653
  // Scaffold TELOS + PAL settings, then prompt for missing identity
611
654
  const { scaffoldTelos, scaffoldPalSettings } = await import("../targets/lib");
612
655
  const { promptIdentity } = await import("./setup-identity");