portable-agent-layer 0.55.1 → 0.56.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.
|
@@ -76,9 +76,19 @@ Report the file path and size to the user.
|
|
|
76
76
|
|
|
77
77
|
## Styling
|
|
78
78
|
|
|
79
|
-
Default styling (A4, 25mm margins, GitHub-ish look, table-friendly, page-break-aware headings) is baked into the tool.
|
|
79
|
+
Default styling (A4, 25mm margins, GitHub-ish look, table-friendly, page-break-aware headings) is baked into the tool. Margins and header/footer are configurable per-render via flags — no source edits needed:
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
- `--margin <css>` — page margin on all four sides (default `25mm`). E.g. `--margin 12mm` for a denser one-pager.
|
|
82
|
+
- `--header <html|file>` / `--footer <html|file>` — running header/footer on every page. The value is either an inline HTML string or a path to an HTML file. Templates may use Playwright's injected classes: `pageNumber`, `totalPages`, `date`, `title`, `url`.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node --experimental-strip-types ~/.pal/skills/create-pdf/tools/md-to-html-pdf.ts report.md --pdf report.pdf \
|
|
86
|
+
--margin 18mm \
|
|
87
|
+
--header '<div style="font-size:9px;width:100%;text-align:center;color:#888">CONFIDENTIAL</div>' \
|
|
88
|
+
--footer '<div style="font-size:9px;width:100%;text-align:right;padding-right:12mm;color:#888"><span class="pageNumber"></span>/<span class="totalPages"></span></div>'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Header/footer templates **need an explicit `font-size`** (Playwright defaults them to 0) and render *inside* the page margin — widen `--margin` so they have room. Do NOT also add CSS `@page` margin-box rules; they duplicate. For deeper changes (fonts, base CSS), edit the `css` string in `tools/md-to-html-pdf.ts`.
|
|
82
92
|
|
|
83
93
|
## Translation Variant
|
|
84
94
|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// pipe handling doesn't complete the CDP handshake.
|
|
8
8
|
//
|
|
9
9
|
// Usage:
|
|
10
|
-
// node --experimental-strip-types ~/.pal/skills/create-pdf/tools/md-to-html-pdf.ts <input.md>
|
|
10
|
+
// node --experimental-strip-types ~/.pal/skills/create-pdf/tools/md-to-html-pdf.ts <input.md> \
|
|
11
|
+
// [--html <out.html>] [--pdf <out.pdf>] [--margin <css>] [--header <html|file>] [--footer <html|file>]
|
|
12
|
+
// --margin defaults to 25mm (all sides). --header/--footer accept inline HTML or a file path.
|
|
11
13
|
|
|
12
14
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
13
15
|
import { basename, dirname, extname, resolve } from "node:path";
|
|
@@ -17,21 +19,44 @@ import { chromium } from "playwright";
|
|
|
17
19
|
|
|
18
20
|
const args = process.argv.slice(2);
|
|
19
21
|
if (args.length === 0) {
|
|
20
|
-
console.error(
|
|
22
|
+
console.error(
|
|
23
|
+
"usage: md-to-html-pdf.ts <input.md> [--html <out>] [--pdf <out>] [--margin <css>] [--header <html|file>] [--footer <html|file>]"
|
|
24
|
+
);
|
|
21
25
|
process.exit(1);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
const input = resolve(args[0]);
|
|
25
29
|
let htmlOut = "";
|
|
26
30
|
let pdfOut = "";
|
|
31
|
+
let margin = "25mm";
|
|
32
|
+
let headerArg = "";
|
|
33
|
+
let footerArg = "";
|
|
27
34
|
for (let i = 1; i < args.length; i++) {
|
|
28
35
|
if (args[i] === "--html") htmlOut = resolve(args[++i]);
|
|
29
36
|
else if (args[i] === "--pdf") pdfOut = resolve(args[++i]);
|
|
37
|
+
else if (args[i] === "--margin") margin = args[++i];
|
|
38
|
+
else if (args[i] === "--header") headerArg = args[++i];
|
|
39
|
+
else if (args[i] === "--footer") footerArg = args[++i];
|
|
30
40
|
}
|
|
31
41
|
const stem = basename(input, extname(input));
|
|
32
42
|
const dir = dirname(input);
|
|
33
|
-
htmlOut
|
|
34
|
-
pdfOut
|
|
43
|
+
htmlOut ||= resolve(dir, `${stem}.html`);
|
|
44
|
+
pdfOut ||= resolve(dir, `${stem}.pdf`);
|
|
45
|
+
|
|
46
|
+
// --header/--footer take inline HTML or a path to an HTML file. Playwright renders
|
|
47
|
+
// them inside the page margin, so widen --margin when you use them. Templates may use
|
|
48
|
+
// Playwright's injected classes: pageNumber, totalPages, date, title, url.
|
|
49
|
+
async function resolveTemplate(value: string): Promise<string> {
|
|
50
|
+
if (!value) return "";
|
|
51
|
+
try {
|
|
52
|
+
return await readFile(resolve(value), "utf8");
|
|
53
|
+
} catch {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const headerTemplate = await resolveTemplate(headerArg);
|
|
58
|
+
const footerTemplate = await resolveTemplate(footerArg);
|
|
59
|
+
const displayHeaderFooter = Boolean(headerTemplate || footerTemplate);
|
|
35
60
|
|
|
36
61
|
const md = await readFile(input, "utf8");
|
|
37
62
|
marked.setOptions({ gfm: true, breaks: false });
|
|
@@ -83,9 +108,12 @@ try {
|
|
|
83
108
|
await page.pdf({
|
|
84
109
|
path: pdfOut,
|
|
85
110
|
format: "A4",
|
|
86
|
-
margin: { top:
|
|
111
|
+
margin: { top: margin, right: margin, bottom: margin, left: margin },
|
|
87
112
|
printBackground: true,
|
|
88
113
|
preferCSSPageSize: false,
|
|
114
|
+
displayHeaderFooter,
|
|
115
|
+
headerTemplate: headerTemplate || "<span></span>",
|
|
116
|
+
footerTemplate: footerTemplate || "<span></span>",
|
|
89
117
|
});
|
|
90
118
|
} finally {
|
|
91
119
|
await browser.close();
|
package/package.json
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Used by SecurityValidator.ts (Claude Code) and the opencode plugin.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { lstatSync } from "node:fs";
|
|
7
|
+
|
|
6
8
|
/** Dangerous command patterns — always blocked */
|
|
7
9
|
const BLOCKED_COMMANDS: [RegExp, string][] = [
|
|
8
10
|
[/rm\s+-rf\s+[/~]/, "Recursive delete of root or home"],
|
|
@@ -67,7 +69,8 @@ const PROTECTED_PATHS: RegExp[] = [
|
|
|
67
69
|
/\.gnupg\//,
|
|
68
70
|
// Claude Code auto-memory — PAL owns memory; writes here indicate wrong system is being used
|
|
69
71
|
/\.claude\/projects\/[^/]+\/memory\//,
|
|
70
|
-
PAL_INSTALLED_DIRS_RE
|
|
72
|
+
// PAL_INSTALLED_DIRS_RE is enforced by the dedicated branch in checkFilePath
|
|
73
|
+
// (which exempts personal skill dirs); keeping it here would re-block them.
|
|
71
74
|
// Derived from HOOK_MANAGED_FILES — scoped to managed roots only
|
|
72
75
|
...HOOK_MANAGED_FILES.map(
|
|
73
76
|
(name) =>
|
|
@@ -120,6 +123,22 @@ export function checkBashCommand(cmd: string): string | null {
|
|
|
120
123
|
return null;
|
|
121
124
|
}
|
|
122
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Shipped skills are symlinks into the PAL repo (engine-managed); personal
|
|
128
|
+
* skills are real dirs authored in place. A not-yet-created skill dir is also
|
|
129
|
+
* personal (scaffolding). So: symlink → shipped/protected, otherwise → personal.
|
|
130
|
+
*/
|
|
131
|
+
function isShippedSkillPath(normalized: string): boolean {
|
|
132
|
+
const m = /\.pal\/skills\/([^/]+)/.exec(normalized);
|
|
133
|
+
if (!m) return false;
|
|
134
|
+
const skillRoot = normalized.slice(0, m.index + m[0].length);
|
|
135
|
+
try {
|
|
136
|
+
return lstatSync(skillRoot).isSymbolicLink();
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
/** Check a file path against protected patterns. Returns a reason string or null. */
|
|
124
143
|
export function checkFilePath(filePath: string): string | null {
|
|
125
144
|
const normalized = filePath.replaceAll("\\", "/");
|
|
@@ -141,7 +160,10 @@ export function checkFilePath(filePath: string): string | null {
|
|
|
141
160
|
if (PAL_INSTALLED_DIRS_RE.test(normalized)) {
|
|
142
161
|
const match = new RegExp(/\.pal[/\\](docs|skills|tools)/).exec(normalized);
|
|
143
162
|
const dir = match ? match[1] : "docs/skills/tools";
|
|
144
|
-
|
|
163
|
+
const isPersonalSkill = dir === "skills" && !isShippedSkillPath(normalized);
|
|
164
|
+
if (!isPersonalSkill) {
|
|
165
|
+
return `~/.pal/${dir}/ is managed by 'pal install' — edit the source in the PAL repo instead`;
|
|
166
|
+
}
|
|
145
167
|
}
|
|
146
168
|
// Check remaining system-protected paths
|
|
147
169
|
if (PROTECTED_PATHS.some((pattern) => pattern.test(filePath))) {
|