T029: seed solution mm-pdf-report
This commit is contained in:
parent
83cda680d2
commit
430061c0ff
8 changed files with 608 additions and 0 deletions
26
solutions/mm-pdf-report/listing.json
Normal file
26
solutions/mm-pdf-report/listing.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"schema_version": "m2.listing.v1",
|
||||
"listing_id": "lst_mm-pdf-report",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"solution_version": "1.0.0",
|
||||
"inventory_type": "solution",
|
||||
"name": "MM PDF Report Generator",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown.",
|
||||
"category": "document-generation",
|
||||
"keywords": ["pdf", "branding", "markdown", "reports", "decks"],
|
||||
"price": {
|
||||
"amount": 40,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"evidence_summary": "Skill in production use on the m2 host since 2026-04-21; two real generated PDF/HTML pairs on disk (session-2026-04-20.pdf, spark-cluster-old/fabric-setup.pdf) confirm the pipeline works end-to-end.",
|
||||
"tenant_visibility": ["*"],
|
||||
"stats": {
|
||||
"installs": 0,
|
||||
"proposals_shown": 0,
|
||||
"proposals_accepted": 0
|
||||
},
|
||||
"status": "draft",
|
||||
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||
}
|
||||
89
solutions/mm-pdf-report/payload/SKILL.md
Normal file
89
solutions/mm-pdf-report/payload/SKILL.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
name: mm-pdf
|
||||
description: Generate beautifully styled Machine.Machine branded PDFs and slide decks from Markdown. Dark navy aesthetic, cyan accents, professional A4 layout. Uses a headless-Chrome render container (any container with Node + Chrome, e.g. a "*-m2o" agent-desktop image) or a local WeasyPrint fallback. Produces PDF + HTML output.
|
||||
---
|
||||
|
||||
# mm-pdf
|
||||
|
||||
Generate MM-branded documents from Markdown content.
|
||||
|
||||
## Design system
|
||||
|
||||
| Token | Value | Use |
|
||||
|-------|-------|-----|
|
||||
| Cover background | `#0a0e1a` | Dark navy |
|
||||
| Primary accent | `#4bb8d4` | Cyan — headings, kickers, borders |
|
||||
| Section banners | `#3d4550` | Slate grey |
|
||||
| Purple sidebar | `#6b46c1` | Alt cover variant |
|
||||
| Body text | `#e2e8f0` | Light grey on dark bg |
|
||||
| Body background | `#111827` | Dark page bg |
|
||||
| Code/data blocks | `#1e2533` | Slightly lighter navy |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate PDF from markdown
|
||||
mm-pdf generate input.md [--out output.pdf] [--style dark|purple|light]
|
||||
|
||||
# Generate slide deck (Marp)
|
||||
mm-pdf slides input.md [--out output.pdf]
|
||||
|
||||
# With explicit container (auto-discovery picks the first "*m2o*" container if omitted)
|
||||
mm-pdf generate input.md --container <your-render-container-name>
|
||||
```
|
||||
|
||||
## Pipeline
|
||||
|
||||
1. **MD → HTML** — a small Node script (via `marked`) renders Markdown into full MM-styled
|
||||
HTML. Extracts YAML frontmatter (`title`, `subtitle`, `date`, `client`) for the cover
|
||||
page. Strips the first `# H1` to use as title if no frontmatter is present.
|
||||
2. **HTML → PDF** — Chrome headless renders the HTML to PDF (`--print-to-pdf`).
|
||||
|
||||
Both outputs are saved alongside the input file.
|
||||
|
||||
## Output
|
||||
|
||||
- `<name>.html` — standalone styled HTML (preview, shareable)
|
||||
- `<name>.pdf` — print-ready PDF
|
||||
|
||||
## Styles
|
||||
|
||||
| Style | Cover | Use case |
|
||||
|-------|-------|----------|
|
||||
| `dark` | Navy `#0a0e1a` + cyan accent | Technical specs, deployment docs |
|
||||
| `purple` | Purple sidebar `#6b46c1` | Partner decks |
|
||||
| `light` | White + navy header | Client-facing docs |
|
||||
|
||||
`dark.css` and `purple.css` ship in `templates/`. `light` is documented for forward
|
||||
compatibility but has no template in this bundle yet — add `templates/light.css` locally
|
||||
if you need it (it will be picked up automatically; `generate.sh` falls back to `dark.css`
|
||||
for any missing style file).
|
||||
|
||||
## Components (in Markdown)
|
||||
|
||||
```markdown
|
||||
<!-- KPI card row -->
|
||||
<div class="kpi-row">
|
||||
<div class="kpi">**€5K** setup</div>
|
||||
<div class="kpi">**€2K**/mo</div>
|
||||
<div class="kpi">**8 weeks** pilot</div>
|
||||
</div>
|
||||
|
||||
<!-- Section kicker -->
|
||||
<span class="kicker">DEPLOYMENT ARCHITECTURE</span>
|
||||
|
||||
<!-- Warning/callout -->
|
||||
<div class="callout">⚠️ Open item: integration path TBD</div>
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker, with a running container that has Node.js + Google Chrome (headless) installed
|
||||
and reachable via `docker exec`/`docker cp` from the host running this skill. Any
|
||||
container name is fine — `scripts/generate.sh` auto-discovers the first container whose
|
||||
name matches `nasr-m2o|m2o`, or accepts `--container <name>` to target any other
|
||||
container that meets the Node+Chrome requirement.
|
||||
- `docker` CLI available on the host invoking `mm-pdf`.
|
||||
|
||||
No absolute host paths, hostnames, or client-identifying data are baked into this skill —
|
||||
`--container` and the input Markdown path are the only machine-specific inputs at run time.
|
||||
145
solutions/mm-pdf-report/payload/scripts/generate.sh
Executable file
145
solutions/mm-pdf-report/payload/scripts/generate.sh
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env bash
|
||||
# mm-pdf generate — render Markdown → HTML → PDF using nasr-m2o container
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
STYLE="${MM_PDF_STYLE:-dark}"
|
||||
OUT=""
|
||||
OUT_HTML=""
|
||||
CONTAINER=""
|
||||
INPUT=""
|
||||
|
||||
usage() {
|
||||
echo "Usage: mm-pdf generate <input.md> [--out output.pdf] [--style dark|purple|light] [--container NAME]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--out) OUT="$2"; shift 2 ;;
|
||||
--style) STYLE="$2"; shift 2 ;;
|
||||
--container) CONTAINER="$2"; shift 2 ;;
|
||||
--help|-h) usage ;;
|
||||
*) INPUT="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$INPUT" ]] && usage
|
||||
[[ ! -f "$INPUT" ]] && { echo "Error: $INPUT not found"; exit 1; }
|
||||
|
||||
INPUT_ABS="$(realpath "$INPUT")"
|
||||
BASENAME="${INPUT_ABS%.md}"
|
||||
OUT="${OUT:-${BASENAME}.pdf}"
|
||||
OUT_ABS="$(realpath -m "$OUT")"
|
||||
OUT_HTML_ABS="${OUT_ABS%.pdf}.html"
|
||||
|
||||
# Auto-discover container
|
||||
if [[ -z "$CONTAINER" ]]; then
|
||||
CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "nasr-m2o|m2o" | head -1)
|
||||
[[ -z "$CONTAINER" ]] && { echo "Error: no m2o container running. Start nasr-m2o or pass --container"; exit 1; }
|
||||
fi
|
||||
echo "Using container: $CONTAINER"
|
||||
|
||||
# Copy files into container
|
||||
TMP="/tmp/mm-pdf-$$"
|
||||
docker exec "$CONTAINER" mkdir -p "$TMP"
|
||||
docker cp "$INPUT_ABS" "$CONTAINER:$TMP/input.md"
|
||||
|
||||
if [[ -f "$SKILL_DIR/templates/${STYLE}.css" ]]; then
|
||||
docker cp "$SKILL_DIR/templates/${STYLE}.css" "$CONTAINER:$TMP/style.css"
|
||||
else
|
||||
docker cp "$SKILL_DIR/templates/dark.css" "$CONTAINER:$TMP/style.css"
|
||||
fi
|
||||
|
||||
# Step 1: Markdown → styled HTML (via Node/marked)
|
||||
echo "Step 1: Converting Markdown → HTML..."
|
||||
docker exec "$CONTAINER" bash -c "
|
||||
cd $TMP
|
||||
# Install marked locally in tmp dir so require() can find it
|
||||
[ -d node_modules/marked ] || npm install marked --prefix . -q 2>/dev/null
|
||||
|
||||
node - <<'JSEOF'
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { marked } = require('marked');
|
||||
|
||||
const mdRaw = fs.readFileSync('input.md', 'utf8');
|
||||
const css = fs.readFileSync('style.css', 'utf8');
|
||||
|
||||
// Strip YAML frontmatter
|
||||
let body = mdRaw;
|
||||
const fm = {};
|
||||
const fmMatch = mdRaw.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
||||
if (fmMatch) {
|
||||
fmMatch[1].split('\n').forEach(line => {
|
||||
const i = line.indexOf(':');
|
||||
if (i > 0) fm[line.slice(0,i).trim().toLowerCase()] = line.slice(i+1).trim();
|
||||
});
|
||||
body = mdRaw.slice(fmMatch[0].length);
|
||||
}
|
||||
|
||||
// Extract first H1 as title
|
||||
let title = fm.title || '';
|
||||
if (!title) {
|
||||
const h1 = body.match(/^#\s+(.+)$/m);
|
||||
if (h1) { title = h1[1].trim(); body = body.replace(h1[0], ''); }
|
||||
}
|
||||
title = title || 'Machine.Machine';
|
||||
const subtitle = fm.subtitle || fm.description || '';
|
||||
const dateLine = [fm.client, fm.date].filter(Boolean).join(' \u00a0·\u00a0 ');
|
||||
|
||||
marked.setOptions({ gfm: true, breaks: false });
|
||||
const contentHtml = marked.parse(body);
|
||||
|
||||
const coverHtml = \`<div class=\"cover\">
|
||||
<div class=\"logo\">MACHINE.MACHINE</div>
|
||||
<div>
|
||||
<h1 class=\"title\">\${title}</h1>
|
||||
\${subtitle ? '<div class=\"subtitle\">' + subtitle + '</div>' : ''}
|
||||
</div>
|
||||
\${dateLine ? '<div class=\"meta\">' + dateLine + '</div>' : ''}
|
||||
</div>\`;
|
||||
|
||||
const html = \`<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<meta charset=\"utf-8\">
|
||||
<title>\${title}</title>
|
||||
<style>\${css}</style>
|
||||
</head>
|
||||
<body>
|
||||
\${coverHtml}
|
||||
<div class=\"body-content\">
|
||||
\${contentHtml}
|
||||
</div>
|
||||
</body>
|
||||
</html>\`;
|
||||
|
||||
fs.writeFileSync('output.html', html);
|
||||
console.log('HTML written:', html.length, 'bytes');
|
||||
JSEOF
|
||||
"
|
||||
|
||||
echo "Step 2: Rendering HTML → PDF..."
|
||||
docker exec "$CONTAINER" bash -c "
|
||||
cd $TMP
|
||||
google-chrome \
|
||||
--headless \
|
||||
--no-sandbox \
|
||||
--disable-gpu \
|
||||
--disable-web-security \
|
||||
--print-to-pdf=output.pdf \
|
||||
--print-to-pdf-no-header \
|
||||
--run-all-compositor-stages-before-draw \
|
||||
--virtual-time-budget=5000 \
|
||||
'file://$TMP/output.html'
|
||||
"
|
||||
|
||||
# Copy both outputs back
|
||||
docker cp "$CONTAINER:$TMP/output.html" "$OUT_HTML_ABS"
|
||||
docker cp "$CONTAINER:$TMP/output.pdf" "$OUT_ABS"
|
||||
docker exec "$CONTAINER" rm -rf "$TMP"
|
||||
|
||||
echo ""
|
||||
echo "HTML: $OUT_HTML_ABS ($(du -h "$OUT_HTML_ABS" | cut -f1))"
|
||||
echo "PDF: $OUT_ABS ($(du -h "$OUT_ABS" | cut -f1))"
|
||||
236
solutions/mm-pdf-report/payload/templates/dark.css
Normal file
236
solutions/mm-pdf-report/payload/templates/dark.css
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/* MM Dark Design System — WeasyPrint / Marp compatible */
|
||||
|
||||
:root {
|
||||
--cover-bg: #0a0e1a;
|
||||
--accent: #4bb8d4;
|
||||
--section-bg: #3d4550;
|
||||
--body-bg: #111827;
|
||||
--body-text: #e2e8f0;
|
||||
--muted: #94a3b8;
|
||||
--code-bg: #1e2533;
|
||||
--border: #2d3748;
|
||||
--white: #ffffff;
|
||||
}
|
||||
|
||||
/* --- PAGE --- */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
line-height: 1.6;
|
||||
color: var(--body-text);
|
||||
background: var(--body-bg);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.body-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* --- COVER PAGE --- */
|
||||
.cover {
|
||||
background: var(--cover-bg);
|
||||
min-height: 100vh;
|
||||
padding: 52px 52px 40px 52px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.cover .logo {
|
||||
font-size: 11pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.cover .title {
|
||||
font-size: 28pt;
|
||||
font-weight: 800;
|
||||
color: var(--white);
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cover .subtitle {
|
||||
font-size: 13pt;
|
||||
color: var(--accent);
|
||||
margin-top: 8px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.cover .meta {
|
||||
font-size: 9pt;
|
||||
color: var(--muted);
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.cover .version-badge {
|
||||
display: inline-block;
|
||||
background: var(--accent);
|
||||
color: var(--cover-bg);
|
||||
font-size: 8pt;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 3px;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
/* --- SECTION BANNERS --- */
|
||||
.section-banner, h2 {
|
||||
background: var(--section-bg);
|
||||
color: var(--white);
|
||||
padding: 8px 16px;
|
||||
margin: 24px -18mm 16px -18mm;
|
||||
font-size: 10pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* --- KICKER --- */
|
||||
.kicker {
|
||||
display: block;
|
||||
font-size: 8pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* --- HEADINGS --- */
|
||||
h1 {
|
||||
font-size: 20pt;
|
||||
font-weight: 800;
|
||||
color: var(--white);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 11pt;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
margin: 16px 0 6px 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 10pt;
|
||||
font-weight: 600;
|
||||
color: var(--white);
|
||||
margin: 12px 0 4px 0;
|
||||
}
|
||||
|
||||
/* --- KPI CARDS --- */
|
||||
.kpi-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.kpi {
|
||||
flex: 1;
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-top: 3px solid var(--accent);
|
||||
padding: 12px 14px;
|
||||
border-radius: 4px;
|
||||
font-size: 9pt;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.kpi strong {
|
||||
display: block;
|
||||
font-size: 16pt;
|
||||
font-weight: 800;
|
||||
color: var(--white);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* --- TABLES --- */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--section-bg);
|
||||
color: var(--white);
|
||||
padding: 7px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--body-text);
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
|
||||
/* --- CODE BLOCKS --- */
|
||||
pre, code {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
font-size: 8.5pt;
|
||||
color: #a5d6a7;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 12px 14px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code { padding: 1px 5px; }
|
||||
|
||||
/* --- CALLOUTS --- */
|
||||
.callout {
|
||||
background: rgba(75, 184, 212, 0.08);
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 10px 14px;
|
||||
margin: 12px 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 9pt;
|
||||
color: var(--body-text);
|
||||
}
|
||||
|
||||
.callout.warn {
|
||||
border-left-color: #f6a623;
|
||||
background: rgba(246, 166, 35, 0.06);
|
||||
}
|
||||
|
||||
.callout.danger {
|
||||
border-left-color: #e53e3e;
|
||||
background: rgba(229, 62, 62, 0.06);
|
||||
}
|
||||
|
||||
/* --- FOOTER --- */
|
||||
@page {
|
||||
@bottom-center {
|
||||
content: "Machine.Machine — Confidential";
|
||||
font-size: 7.5pt;
|
||||
color: #4a5568;
|
||||
}
|
||||
@bottom-right {
|
||||
content: counter(page);
|
||||
font-size: 7.5pt;
|
||||
color: #4a5568;
|
||||
}
|
||||
}
|
||||
32
solutions/mm-pdf-report/payload/templates/purple.css
Normal file
32
solutions/mm-pdf-report/payload/templates/purple.css
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* MM Purple Sidebar — Nasr reference style */
|
||||
@import url("dark.css");
|
||||
|
||||
:root {
|
||||
--cover-bg: #1a0533;
|
||||
--accent: #9f7aea;
|
||||
--sidebar-bg: #6b46c1;
|
||||
}
|
||||
|
||||
.cover {
|
||||
background: var(--cover-bg);
|
||||
padding-left: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 6px 1fr;
|
||||
}
|
||||
|
||||
.cover::before {
|
||||
content: '';
|
||||
background: var(--sidebar-bg);
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.cover-content {
|
||||
grid-column: 2;
|
||||
padding: 52px 52px 40px 36px;
|
||||
}
|
||||
|
||||
.cover .logo { color: var(--accent); }
|
||||
.cover .subtitle { color: var(--accent); }
|
||||
h3 { color: var(--accent); }
|
||||
.kpi { border-top-color: var(--accent); }
|
||||
.callout { border-left-color: var(--accent); }
|
||||
17
solutions/mm-pdf-report/recipe.yaml
Normal file
17
solutions/mm-pdf-report/recipe.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# mm-pdf-report install recipe (apply-adapter.md bundle layout, local adapter v1)
|
||||
# Places the mm-pdf skill into the buyer's Claude Code skills directory.
|
||||
targets:
|
||||
- src: SKILL.md
|
||||
dest: ~/.claude/skills/mm-pdf/SKILL.md
|
||||
- src: scripts/generate.sh
|
||||
dest: ~/.claude/skills/mm-pdf/scripts/generate.sh
|
||||
- src: templates/dark.css
|
||||
dest: ~/.claude/skills/mm-pdf/templates/dark.css
|
||||
- src: templates/purple.css
|
||||
dest: ~/.claude/skills/mm-pdf/templates/purple.css
|
||||
checks:
|
||||
- cmd: test -f ~/.claude/skills/mm-pdf/SKILL.md
|
||||
expect_exit: 0
|
||||
- cmd: test -x ~/.claude/skills/mm-pdf/scripts/generate.sh
|
||||
expect_exit: 0
|
||||
entrypoint: "Ask your agent to use the mm-pdf skill, or run: ~/.claude/skills/mm-pdf/scripts/generate.sh <input.md> --style dark"
|
||||
63
solutions/mm-pdf-report/solution.json
Normal file
63
solutions/mm-pdf-report/solution.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"schema_version": "m2.solution.v1",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"name": "MM PDF Report Generator",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown.",
|
||||
"description": "Installs the mm-pdf skill: a Markdown-to-branded-PDF/HTML pipeline (dark navy aesthetic, cyan accents, A4 layout) built on a Node+marked HTML render step and headless-Chrome print-to-pdf inside a Docker container. Ships three artifacts: SKILL.md, scripts/generate.sh, and templates/{dark,purple}.css. content_hash is computed over payload/ + recipe.yaml only (not solution.json itself, to avoid the self-referential hash problem of hashing a file that contains its own hash): sha256 of `tar --sort=name --mtime='@0' --owner=0 --group=0 --numeric-owner -czf - payload recipe.yaml` run from this bundle's root.",
|
||||
"intent": "Give an operator a working branded-document pipeline (proposal PDFs, spec sheets, decks) without building one from scratch.",
|
||||
"behavior": {
|
||||
"skill_ref": "payload/SKILL.md"
|
||||
},
|
||||
"tools": [
|
||||
{ "name": "docker", "kind": "cli", "required": true }
|
||||
],
|
||||
"runtime": {
|
||||
"surfaces": ["claude-code-skill"]
|
||||
},
|
||||
"permissions": [
|
||||
{ "action": "write", "target": "~/.claude/skills/mm-pdf/" },
|
||||
{ "action": "exec", "target": "docker exec/docker cp against a caller-selected container" }
|
||||
],
|
||||
"deployment": {
|
||||
"recipe_ref": "recipe.yaml",
|
||||
"entrypoint": "Ask your agent to use the mm-pdf skill, or run: ~/.claude/skills/mm-pdf/scripts/generate.sh <input.md> --style dark",
|
||||
"verify_command": "test -f ~/.claude/skills/mm-pdf/SKILL.md"
|
||||
},
|
||||
"applicability": {
|
||||
"image_classes": ["primus", "agent-latest"],
|
||||
"roles": ["operator", "desktop-agent"],
|
||||
"tool_requirements": ["docker"]
|
||||
},
|
||||
"tenant_scope": "m2-core",
|
||||
"evidence": [
|
||||
{
|
||||
"source": "/home/m2/.claude/skills/mm-pdf/ (SKILL.md, scripts/generate.sh, templates/dark.css, templates/purple.css) — the proven skill this bundle packages, present on the m2 host since 2026-04-21 (SKILL.md mtime 2026-04-21T00:05:01+02:00, templates/dark.css mtime 2026-04-21T00:04, scripts/generate.sh mtime 2026-04-21T00:09)",
|
||||
"machine": "m2",
|
||||
"excerpt": "SKILL.md frontmatter: 'Generate beautifully styled Machine.Machine branded PDFs and slide decks from Markdown. Dark navy aesthetic, cyan accents, professional A4 layout.'"
|
||||
},
|
||||
{
|
||||
"source": "/home/m2/session-2026-04-20.pdf — real output of this pipeline: 4-page A4 PDF, PDF metadata Title 'output.html' (the pipeline's fixed intermediate filename), Creator 'HeadlessChrome/145.0.0.0' (Chrome print-to-pdf, matches scripts/generate.sh Step 2), CreationDate 2026-04-20T22:56:51+02:00; pdftotext shows the 'MACHINE.MACHINE' cover logo and MM design-system markup",
|
||||
"machine": "m2",
|
||||
"excerpt": "pdfinfo: Title=output.html; Creator=Mozilla/5.0 ... HeadlessChrome/145.0.0.0 ...; Producer=Skia/PDF m145; Pages=4; page size 594.96x841.92pts (A4)"
|
||||
},
|
||||
{
|
||||
"source": "/home/m2/spark-cluster-old/fabric-setup.pdf + fabric-setup.html — second real generated pair, 4-page A4 PDF titled 'DGX Spark Cluster — Fabric Setup Reference', same HeadlessChrome/145.0.0.0 print-to-pdf signature, CreationDate 2026-04-21T00:09:40+02:00 (12 seconds before this SKILL.md's own mtime, i.e. produced by the exact skill version in this bundle)",
|
||||
"machine": "m2",
|
||||
"excerpt": "pdftotext footer: 'Machine.Machine — Confidential' page-number footer matches templates/dark.css `@bottom-center`/`@bottom-right` rules"
|
||||
}
|
||||
],
|
||||
"content_hash": "sha256:aaee0b1a894efafabddd1bd95c5023c915a6fcd05007776ad921d420f38ea916",
|
||||
"price": {
|
||||
"amount": 40,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"license": {
|
||||
"terms": "Perpetual use on operator-owned machines for the buyer's own document generation; no redistribution of the skill files as a standalone product.",
|
||||
"major_version_coverage": true
|
||||
},
|
||||
"revenue_split": {
|
||||
"platform_pct": 10
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue