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.
- package/assets/skills/consulting-report/SKILL.md +115 -0
- package/assets/skills/consulting-report/demo/content/current-state.md +33 -0
- package/assets/skills/consulting-report/demo/content/executive-summary.md +19 -0
- package/assets/skills/consulting-report/demo/content/report-data.ts +101 -0
- package/assets/skills/consulting-report/demo/diagrams/.gitkeep +0 -0
- package/assets/skills/consulting-report/template/README.md +28 -0
- package/assets/skills/consulting-report/template/content/executive-summary.md +19 -0
- package/assets/skills/consulting-report/template/content/report-data.ts +59 -0
- package/assets/skills/consulting-report/template/diagrams/.gitkeep +0 -0
- package/assets/skills/consulting-report/tools/generate-pdf.ts +508 -0
- package/assets/skills/consulting-report/tools/scaffold.ts +74 -0
- package/assets/skills/create-pdf/SKILL.md +33 -42
- package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +91 -0
- package/assets/skills/think/SKILL.md +70 -7
- package/package.json +4 -2
- package/src/cli/index.ts +43 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-pdf
|
|
3
|
-
description: Convert markdown files into a styled 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
|
|
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
|
|
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
|
-
|
|
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:
|
|
49
|
+
### Step 3: Render to PDF
|
|
47
50
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
### Step 4: Generate PDF
|
|
61
|
+
Multi-file example (after Step 2):
|
|
79
62
|
|
|
80
63
|
```bash
|
|
81
|
-
|
|
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
|
-
|
|
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 <
|
|
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-
|
|
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
|
|
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
|
-
-
|
|
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.
|
|
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");
|