@ztffn/presentation-generator-plugin 1.1.2 → 1.1.4
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.
|
@@ -4,6 +4,7 @@ description: Translates approved slide outlines into complete graph JSON with no
|
|
|
4
4
|
tools:
|
|
5
5
|
- Read
|
|
6
6
|
- Write
|
|
7
|
+
- Bash
|
|
7
8
|
skills:
|
|
8
9
|
- presentation-generator/system/node-schema
|
|
9
10
|
- presentation-generator/system/edge-conventions
|
|
@@ -28,34 +29,79 @@ You are a design and JSON generation specialist. Your job is to translate an app
|
|
|
28
29
|
5. Run the edge validation checklist before finalizing
|
|
29
30
|
6. Write the complete JSON to `_temp/presentation-draft.json`
|
|
30
31
|
|
|
32
|
+
## Field Mapping
|
|
33
|
+
|
|
34
|
+
When translating from the outline to `data` fields, use **exactly** these SlideNodeData field names — no others:
|
|
35
|
+
|
|
36
|
+
| Outline field | SlideNodeData field | Notes |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `### Slide Title` | `label` | Slide heading shown in graph editor and at top of slide |
|
|
39
|
+
| `**Key message:**` + `**Content:**` | `content` | Combine into markdown: key message as first line or `##` heading, then bullets/body. Use `\n\n` between paragraphs. |
|
|
40
|
+
| `**Speaker notes:**` | `notes` | Verbatim from outline. Never put this in `content`. |
|
|
41
|
+
|
|
42
|
+
**DO NOT use these field names** — they are not part of the schema and will be silently ignored by the renderer:
|
|
43
|
+
`headline`, `subheadline`, `bullets`, `speakerNote`, `speakerNotes`, `visualHint`, `theme`, `title`, `body`, `text`, `background`, `showLogo`, `keyMessage`, `claim`, `hook`, `description`, `summary`
|
|
44
|
+
|
|
45
|
+
## Edge Handle Rules
|
|
46
|
+
|
|
47
|
+
**WRONG** — these handle values will break navigation:
|
|
48
|
+
```json
|
|
49
|
+
{ "sourceHandle": "right", "targetHandle": "left" }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**CORRECT** — source handles prefix `s-`, target handles prefix `t-`:
|
|
53
|
+
```json
|
|
54
|
+
{ "sourceHandle": "s-right", "targetHandle": "t-left" }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
All 8 valid handle IDs: `s-right`, `s-left`, `s-top`, `s-bottom`, `t-right`, `t-left`, `t-top`, `t-bottom`
|
|
58
|
+
|
|
31
59
|
## Rules
|
|
32
60
|
|
|
33
61
|
- **Translation only**: Do not alter content, order, or structure from the outline. Your role is visual treatment and JSON compilation.
|
|
34
62
|
- Every node must have `type: "huma"`, `style: { width: 180, height: 70 }`, and `measured: { width: 180, height: 70 }`
|
|
35
63
|
- Every node ID must be a kebab-case slug
|
|
36
64
|
- Every forward edge must have a paired return edge
|
|
37
|
-
- All handle IDs must be from the valid set of 8
|
|
65
|
+
- All handle IDs must be from the valid set of 8 listed above
|
|
38
66
|
- For background images, use Unsplash URLs with `?w=1920&q=80`
|
|
39
67
|
- For video, use placeholder strings: `"PLACEHOLDER: [description]"`
|
|
40
68
|
- List all slides with video placeholders at the end of output
|
|
41
69
|
|
|
42
70
|
## Output
|
|
43
71
|
|
|
44
|
-
|
|
72
|
+
1. Derive the presentation slug from the deck title in `_temp/presentation-outline.md`: lowercase kebab-case, strip special characters. Example: "Calora Investor Pitch" → `calora-investor-pitch`.
|
|
73
|
+
2. Create the directory `presentations/{slug}/` if it does not exist.
|
|
74
|
+
3. Write the JSON to `presentations/{slug}/{slug}.json`:
|
|
45
75
|
|
|
46
76
|
```json
|
|
47
77
|
{
|
|
48
78
|
"meta": {
|
|
49
|
-
"name": "<deck title from _temp/presentation-outline.md>"
|
|
79
|
+
"name": "<deck title from _temp/presentation-outline.md verbatim>"
|
|
50
80
|
},
|
|
51
81
|
"nodes": [ ... ],
|
|
52
82
|
"edges": [ ... ]
|
|
53
83
|
}
|
|
54
84
|
```
|
|
55
85
|
|
|
56
|
-
|
|
86
|
+
4. Run `scripts/validate_draft.py` using Bash. This is a Python script — you must execute it, not read it. Reading the JSON file is not validation.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python3 scripts/validate_draft.py presentations/{slug}/{slug}.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If the working directory is different, use the absolute path to the script.
|
|
93
|
+
|
|
94
|
+
The script prints either `OK Presentation JSON is valid` (exit 0) or a list of `ERROR` lines (exit 1).
|
|
95
|
+
|
|
96
|
+
If there are any `ERROR` lines:
|
|
97
|
+
- Fix every error directly in the JSON file
|
|
98
|
+
- Run the script again against the same file
|
|
99
|
+
- Repeat until the script prints `OK Presentation JSON is valid`
|
|
100
|
+
|
|
101
|
+
**Your completion message must include the exact terminal output of the script.** A Read tool call on the JSON file is not validation and does not count. If the script output is not shown, the task is not complete.
|
|
57
102
|
|
|
58
|
-
After
|
|
103
|
+
After a clean validation pass, output a summary listing:
|
|
104
|
+
- Output path (e.g. `presentations/calora-investor-pitch/calora-investor-pitch.json`)
|
|
59
105
|
- Total node count
|
|
60
106
|
- Total edge count
|
|
61
107
|
- Any slides with video placeholders requiring manual upload
|
package/package.json
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
validate_draft.py — Validates a presentation draft JSON before import.
|
|
4
|
+
Checks node schema correctness, edge handle IDs, bidirectional pairs, and duplicates.
|
|
5
|
+
Intended to be run by the design agent after writing the draft, before reporting complete.
|
|
6
|
+
Usage: python3 validate_draft.py <path-to-draft.json>
|
|
7
|
+
Exit: 0 if valid, 1 if errors found.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Derived from SlideNodeData interface in src/types/presentation.ts.
|
|
15
|
+
# Any field not in this set is unknown to the renderer and will be silently ignored.
|
|
16
|
+
ALLOWED_DATA_FIELDS = {
|
|
17
|
+
"label", "topic", "content", "notes", "type", "centered", "layout",
|
|
18
|
+
"lightText", "brandFont", "showBranding", "brandingText",
|
|
19
|
+
"backgroundImage", "backgroundImageFit", "backgroundImageOverlay",
|
|
20
|
+
"backgroundVideo", "backgroundVideoFit", "backgroundVideoLoop",
|
|
21
|
+
"inlineVideoControls", "inlineVideoAutoplay", "inlineVideoLoop",
|
|
22
|
+
"scene", "chart", "charts",
|
|
23
|
+
"sceneGroup", "focus",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
VALID_SOURCE_HANDLES = {"s-right", "s-left", "s-top", "s-bottom"}
|
|
27
|
+
VALID_TARGET_HANDLES = {"t-right", "t-left", "t-top", "t-bottom"}
|
|
28
|
+
BARE_HANDLES = {"right", "left", "top", "bottom"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate(path):
|
|
32
|
+
errors = []
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
data = json.loads(Path(path).read_text())
|
|
36
|
+
except FileNotFoundError:
|
|
37
|
+
print(f"ERROR File not found: {path}")
|
|
38
|
+
return False
|
|
39
|
+
except json.JSONDecodeError as e:
|
|
40
|
+
print(f"ERROR Invalid JSON: {e}")
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# --- meta ---
|
|
44
|
+
meta = data.get("meta")
|
|
45
|
+
if not isinstance(meta, dict):
|
|
46
|
+
errors.append("meta: missing or not an object")
|
|
47
|
+
elif not str(meta.get("name", "")).strip():
|
|
48
|
+
errors.append("meta.name: missing or empty — must be the deck title")
|
|
49
|
+
|
|
50
|
+
# --- nodes ---
|
|
51
|
+
nodes = data.get("nodes")
|
|
52
|
+
node_ids = set()
|
|
53
|
+
|
|
54
|
+
if not isinstance(nodes, list) or len(nodes) == 0:
|
|
55
|
+
errors.append("nodes: missing or empty array")
|
|
56
|
+
else:
|
|
57
|
+
for i, node in enumerate(nodes):
|
|
58
|
+
nid = node.get("id", f"[index {i}]")
|
|
59
|
+
p = f"node '{nid}'"
|
|
60
|
+
|
|
61
|
+
if nid in node_ids:
|
|
62
|
+
errors.append(f"{p}: duplicate node ID")
|
|
63
|
+
node_ids.add(nid)
|
|
64
|
+
|
|
65
|
+
if node.get("type") != "huma":
|
|
66
|
+
errors.append(f"{p}: type must be 'huma', got {node.get('type')!r}")
|
|
67
|
+
|
|
68
|
+
style = node.get("style") or {}
|
|
69
|
+
measured = node.get("measured") or {}
|
|
70
|
+
if style.get("width") != 180 or style.get("height") != 70:
|
|
71
|
+
errors.append(f"{p}: style must be {{width: 180, height: 70}}, got {style}")
|
|
72
|
+
if measured.get("width") != 180 or measured.get("height") != 70:
|
|
73
|
+
errors.append(f"{p}: measured must be {{width: 180, height: 70}}, got {measured}")
|
|
74
|
+
|
|
75
|
+
node_data = node.get("data")
|
|
76
|
+
if not isinstance(node_data, dict):
|
|
77
|
+
errors.append(f"{p}: data must be an object")
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if not str(node_data.get("label", "")).strip():
|
|
81
|
+
errors.append(f"{p}: data.label is missing or empty (this is the slide title)")
|
|
82
|
+
|
|
83
|
+
unknown = set(node_data.keys()) - ALLOWED_DATA_FIELDS
|
|
84
|
+
if unknown:
|
|
85
|
+
errors.append(
|
|
86
|
+
f"{p}: unknown field(s) in data: {', '.join(sorted(unknown))} — "
|
|
87
|
+
f"only SlideNodeData fields are valid (label, content, notes, topic, type, layout, ...)"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# --- edges ---
|
|
91
|
+
edges = data.get("edges")
|
|
92
|
+
edge_ids = set()
|
|
93
|
+
valid_edges = [] # (source, target, sourceHandle, targetHandle, id)
|
|
94
|
+
|
|
95
|
+
if not isinstance(edges, list):
|
|
96
|
+
errors.append("edges: missing or not an array")
|
|
97
|
+
else:
|
|
98
|
+
for i, edge in enumerate(edges):
|
|
99
|
+
eid = edge.get("id", f"[index {i}]")
|
|
100
|
+
p = f"edge '{eid}'"
|
|
101
|
+
|
|
102
|
+
if eid in edge_ids:
|
|
103
|
+
errors.append(f"{p}: duplicate edge ID")
|
|
104
|
+
edge_ids.add(eid)
|
|
105
|
+
|
|
106
|
+
src = edge.get("source")
|
|
107
|
+
tgt = edge.get("target")
|
|
108
|
+
sh = edge.get("sourceHandle")
|
|
109
|
+
th = edge.get("targetHandle")
|
|
110
|
+
|
|
111
|
+
if node_ids and src not in node_ids:
|
|
112
|
+
errors.append(f"{p}: source '{src}' does not reference a known node ID")
|
|
113
|
+
if node_ids and tgt not in node_ids:
|
|
114
|
+
errors.append(f"{p}: target '{tgt}' does not reference a known node ID")
|
|
115
|
+
|
|
116
|
+
sh_ok = True
|
|
117
|
+
if sh in BARE_HANDLES:
|
|
118
|
+
errors.append(f"{p}: sourceHandle '{sh}' — use 's-{sh}' (source handles require 's-' prefix)")
|
|
119
|
+
sh_ok = False
|
|
120
|
+
elif sh not in VALID_SOURCE_HANDLES:
|
|
121
|
+
errors.append(f"{p}: sourceHandle '{sh}' invalid — must be one of {sorted(VALID_SOURCE_HANDLES)}")
|
|
122
|
+
sh_ok = False
|
|
123
|
+
|
|
124
|
+
th_ok = True
|
|
125
|
+
if th in BARE_HANDLES:
|
|
126
|
+
errors.append(f"{p}: targetHandle '{th}' — use 't-{th}' (target handles require 't-' prefix)")
|
|
127
|
+
th_ok = False
|
|
128
|
+
elif th not in VALID_TARGET_HANDLES:
|
|
129
|
+
errors.append(f"{p}: targetHandle '{th}' invalid — must be one of {sorted(VALID_TARGET_HANDLES)}")
|
|
130
|
+
th_ok = False
|
|
131
|
+
|
|
132
|
+
if sh_ok and th_ok:
|
|
133
|
+
valid_edges.append((src, tgt, sh, th, eid))
|
|
134
|
+
|
|
135
|
+
# --- bidirectional pair check ---
|
|
136
|
+
edge_set = {(src, tgt, sh, th) for src, tgt, sh, th, _ in valid_edges}
|
|
137
|
+
for src, tgt, sh, th, eid in valid_edges:
|
|
138
|
+
ret_sh = "s-" + th[2:] # t-left -> s-left
|
|
139
|
+
ret_th = "t-" + sh[2:] # s-right -> t-right
|
|
140
|
+
if (tgt, src, ret_sh, ret_th) not in edge_set:
|
|
141
|
+
errors.append(
|
|
142
|
+
f"edge '{eid}' ({src} -[{sh}/{th}]-> {tgt}): "
|
|
143
|
+
f"missing return edge ({tgt} -[{ret_sh}/{ret_th}]-> {src})"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if not errors:
|
|
147
|
+
print("OK Presentation JSON is valid")
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
for e in errors:
|
|
151
|
+
print(f"ERROR {e}")
|
|
152
|
+
print()
|
|
153
|
+
print(f"FAILED {len(errors)} error(s) — fix all errors before importing")
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
if len(sys.argv) != 2:
|
|
159
|
+
print("Usage: python3 validate_draft.py <path-to-draft.json>")
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
ok = validate(sys.argv[1])
|
|
162
|
+
sys.exit(0 if ok else 1)
|
|
@@ -106,7 +106,7 @@ Call to Action: [headline]
|
|
|
106
106
|
Write the tree to `_temp/presentation-plan.md` and present it in the chat.
|
|
107
107
|
|
|
108
108
|
Wait for the user to respond:
|
|
109
|
-
- **"approved"** or similar → proceed to Phase 5
|
|
109
|
+
- **"approved"** or similar → derive the slug (lowercase kebab-case of the deck title, e.g. `calora-investor-pitch`) and proceed to Phase 5
|
|
110
110
|
- **change requests** → update the outline, re-render the tree, wait again
|
|
111
111
|
- **"start over"** → return to Phase 1
|
|
112
112
|
|
|
@@ -119,24 +119,28 @@ Invoke the `presentation-design` sub-agent:
|
|
|
119
119
|
```
|
|
120
120
|
Agent: presentation-design
|
|
121
121
|
Input: Reads _temp/presentation-outline.md
|
|
122
|
-
Output:
|
|
122
|
+
Output: presentations/{slug}/{slug}.json
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
The design agent translates each slide into a fully specified graph node, wires all edges with bidirectional pairs, and validates the result.
|
|
126
126
|
|
|
127
127
|
## Phase 6 — Delivery
|
|
128
128
|
|
|
129
|
-
After `
|
|
129
|
+
After `presentations/{slug}/{slug}.json` is written:
|
|
130
130
|
|
|
131
|
-
1.
|
|
132
|
-
|
|
131
|
+
1. Copy the three intermediate files into the presentation folder so everything is self-contained:
|
|
132
|
+
- `_temp/presentation-content-brief.json` → `presentations/{slug}/content-brief.json`
|
|
133
|
+
- `_temp/presentation-outline.md` → `presentations/{slug}/outline.md`
|
|
134
|
+
- `_temp/presentation-plan.md` → `presentations/{slug}/plan.md`
|
|
135
|
+
|
|
136
|
+
2. Confirm the output path and provide import instructions:
|
|
133
137
|
|
|
134
138
|
```
|
|
135
139
|
Import your presentation:
|
|
136
140
|
1. Open the graph editor at /present/plan
|
|
137
141
|
2. Click "New presentation"
|
|
138
142
|
3. Choose "Import JSON"
|
|
139
|
-
4. Select
|
|
143
|
+
4. Select presentations/{slug}/{slug}.json
|
|
140
144
|
```
|
|
141
145
|
|
|
142
146
|
3. If any slides have video placeholders, list them:
|
|
@@ -187,4 +191,7 @@ All intermediate and output files:
|
|
|
187
191
|
| `_temp/presentation-content-brief.json` | Content agent or orchestrator | Narrative agent |
|
|
188
192
|
| `_temp/presentation-outline.md` | Narrative agent | Design agent, orchestrator |
|
|
189
193
|
| `_temp/presentation-plan.md` | Orchestrator | User (approval) |
|
|
190
|
-
| `
|
|
194
|
+
| `presentations/{slug}/{slug}.json` | Design agent | User (import) |
|
|
195
|
+
| `presentations/{slug}/content-brief.json` | Orchestrator (Phase 6) | Reference |
|
|
196
|
+
| `presentations/{slug}/outline.md` | Orchestrator (Phase 6) | Reference |
|
|
197
|
+
| `presentations/{slug}/plan.md` | Orchestrator (Phase 6) | Reference |
|