wand-decks-kit 0.8.4 → 0.8.7
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/PROMPT.md +5 -3
- package/SKILL.md +38 -2
- package/package.json +1 -1
- package/wand.js +91 -1
- package/wand_kit.js +1 -1
package/PROMPT.md
CHANGED
|
@@ -421,13 +421,15 @@ DejaVu Sans / Carlito / Liberation Sans, never Geist. Consequences:
|
|
|
421
421
|
**Garbled digits in exported PDFs.** The reference PDFs show `$ǿǹT+` where the deck says `$60T+`.
|
|
422
422
|
Cause: Google Slides subsets the embedded Geist and writes a `ToUnicode` map offset by +457, so
|
|
423
423
|
digits decode to Latin Extended-B in the PDF text layer. **The `.pptx` is correct — do not "fix"
|
|
424
|
-
these strings.** Our
|
|
424
|
+
these strings.** Our own export is clean, and `wand.js ship` confirms it on every run —
|
|
425
|
+
the `text layer` line and the `pdf text` gate. To check a PDF by hand, use the portable
|
|
426
|
+
form (sandbox grep has no `-P`, so the old `grep -P` command errors there):
|
|
425
427
|
|
|
426
428
|
```bash
|
|
427
|
-
pdftotext
|
|
429
|
+
python3 -c "import subprocess;t=subprocess.run(['pdftotext','output.pdf','-'],capture_output=True,text=True).stdout;print([c for c in t if 0x01C9<=ord(c)<=0x024F] or 'clean')"
|
|
428
430
|
```
|
|
429
431
|
|
|
430
|
-
|
|
432
|
+
`clean` is a pass. Listed characters mean subsetted fonts got in — set `saveSubsetFonts="0"` and re-embed.
|
|
431
433
|
|
|
432
434
|
**Other:** the reference decks use `roundRect` with per-shape `adj`, so copying a card and resizing
|
|
433
435
|
it changes its corner radius — recompute. Icon glyphs are separate PNGs layered over tile shapes;
|
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Rebrand, merge, edit or author a PowerPoint deck on the Wand design
|
|
|
4
4
|
compatibility: Requires Node.js 18+ and code execution enabled in claude.ai (Settings > Features). No network access needed - dependencies are bundled. Python 3 is used to read a source .pptx and to embed fonts; without it the kit can still author and check a deck, but the output will render in Arial on machines without Geist.
|
|
5
5
|
license: Proprietary - Wand AI
|
|
6
6
|
metadata:
|
|
7
|
-
version: "0.7
|
|
7
|
+
version: "0.8.7"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
|
|
@@ -61,7 +61,16 @@ node wand.js doctor
|
|
|
61
61
|
|
|
62
62
|
If `node_modules` is somehow missing, `npm install` in this directory.
|
|
63
63
|
|
|
64
|
-
##
|
|
64
|
+
## Before any build: show the plan, then wait
|
|
65
|
+
|
|
66
|
+
Write the slide-by-slide mapping in the chat **before** writing a line of JSON:
|
|
67
|
+
`S07 (timeline, 6 phases) → phaseCards`, one line per slide, with the reason
|
|
68
|
+
where it is not obvious. Then build. A field run skipped this, built the wrong
|
|
69
|
+
thing, and paid a full second pass. This is not optional for a rebuild, a merge
|
|
70
|
+
or an adaptation; only "author from a brief" with fewer than eight slides may
|
|
71
|
+
go straight to the spec.
|
|
72
|
+
|
|
73
|
+
## The five jobs
|
|
65
74
|
|
|
66
75
|
### Rebrand a deck they attached
|
|
67
76
|
|
|
@@ -89,6 +98,24 @@ to cover — and it says so.
|
|
|
89
98
|
spec. Ask which deck leads if they have not said. Flag duplicates rather than
|
|
90
99
|
silently dropping them, and list every merge in the review note.
|
|
91
100
|
|
|
101
|
+
### Adapt a deck and keep its graphics
|
|
102
|
+
|
|
103
|
+
The ask sounds like a rebrand but is not: *"take our standard deck, drop in an
|
|
104
|
+
agenda, a team slide and a demo placeholder, keep everything else as is."* They
|
|
105
|
+
want the source's own slides and art preserved, with a few slides retexted or
|
|
106
|
+
added. **This kit re-authors from a spec; it cannot copy a source slide's
|
|
107
|
+
shapes.** Say that in one line, then edit the source `.pptx` directly with the
|
|
108
|
+
pptx tools — and still run this kit's gates on the result:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
node wand.js audit <edited-deck>.pptx # file, pdf text, renders + contact sheet
|
|
112
|
+
node wand.js audit <edited-deck>.pptx --dump work/visible_dump.txt # + coverage
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`audit` runs every gate that does not need a spec. A deck delivered without it
|
|
116
|
+
has been checked by nobody. Say which slides were lifted unchanged, which were
|
|
117
|
+
retexted, and that the source's fonts (not Geist) carry through.
|
|
118
|
+
|
|
92
119
|
### Edit a deck this kit built
|
|
93
120
|
|
|
94
121
|
```bash
|
|
@@ -139,6 +166,14 @@ repeatedly.
|
|
|
139
166
|
4. **Icons are named.** `node wand.js glyphs` lists all 75. Pick by meaning.
|
|
140
167
|
They recolour to the theme, so there is no polarity to get wrong.
|
|
141
168
|
5. **Never invent content.** If the source is ambiguous, ask.
|
|
169
|
+
6. **A FAIL in the gate table is not shippable.** Not "acceptable two-line
|
|
170
|
+
wraps", not "probably fine" — either fix it and re-run, or list each failing
|
|
171
|
+
item with its measurement in the review note and let the user decide. A
|
|
172
|
+
field run shipped on `FAIL fit + bounds` with a reassurance instead of a
|
|
173
|
+
list; the reassurance was not checked against anything.
|
|
174
|
+
7. **Verbatim applies to rebuilds, merges and lifted slides.** When the job is
|
|
175
|
+
to author or retext a slide, say which slides were retexted — the review
|
|
176
|
+
note separates *lifted verbatim* from *rewritten*.
|
|
142
177
|
|
|
143
178
|
## Useful commands
|
|
144
179
|
|
|
@@ -150,6 +185,7 @@ repeatedly.
|
|
|
150
185
|
| `node wand.js check <spec>` | fit in real Geist, bounds, assets |
|
|
151
186
|
| `node wand.js preview <spec>` | writes `preview/trace.js` for the browser preview |
|
|
152
187
|
| `node wand.js inspect <deck>` | parts, relationships, embedded fonts |
|
|
188
|
+
| `node wand.js audit <deck> [--dump f]` | every spec-free gate on any .pptx — use on decks edited outside the kit |
|
|
153
189
|
|
|
154
190
|
## Deliver
|
|
155
191
|
|
package/package.json
CHANGED
package/wand.js
CHANGED
|
@@ -56,6 +56,7 @@ function runPython(args) {
|
|
|
56
56
|
function doctor() {
|
|
57
57
|
const rows = [];
|
|
58
58
|
const py = python();
|
|
59
|
+
rows.push(["wand-decks-kit", require("./wand_kit").VERSION, "ok"]);
|
|
59
60
|
rows.push(["node", process.version, "ok"]);
|
|
60
61
|
rows.push(["python3", py ? execFileSync(py, ["-V"], { encoding: "utf8" }).trim() : "MISSING",
|
|
61
62
|
py ? "ok" : "extract and font embedding unavailable"]);
|
|
@@ -203,6 +204,24 @@ function ship(specPath, outArg) {
|
|
|
203
204
|
console.log(rule("7 file"));
|
|
204
205
|
const fileErrs = inspect(out);
|
|
205
206
|
|
|
207
|
+
// Garbled-digit check, automated. The manual command in PROMPT.md used
|
|
208
|
+
// grep -P, which the sandbox's grep does not have — a field run had to
|
|
209
|
+
// improvise around it. A gate nobody types cannot be mistyped.
|
|
210
|
+
let garbled = null;
|
|
211
|
+
if (has("pdftotext") && fs.existsSync(pdfOut)) {
|
|
212
|
+
const r = spawnSync("pdftotext", [pdfOut, "-"], { encoding: "utf8" });
|
|
213
|
+
if (r.status === 0) {
|
|
214
|
+
garbled = 0;
|
|
215
|
+
for (const c of r.stdout || "") {
|
|
216
|
+
const p = c.codePointAt(0);
|
|
217
|
+
if (p >= 0x01c9 && p <= 0x024f) garbled++;
|
|
218
|
+
}
|
|
219
|
+
console.log(garbled
|
|
220
|
+
? `text layer : ${garbled} garbled glyph(s) — subsetted fonts got in; set saveSubsetFonts="0" and re-embed`
|
|
221
|
+
: "text layer : digits decode clean");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
206
225
|
console.log(rule("8 visual"));
|
|
207
226
|
node([path.join(KIT, "build.js"), path.resolve(specPath), "--preview"]);
|
|
208
227
|
let shots = 0;
|
|
@@ -227,13 +246,78 @@ function ship(specPath, outArg) {
|
|
|
227
246
|
}
|
|
228
247
|
|
|
229
248
|
const gates = [["fit + bounds", bad === 0], ["coverage", cov === null ? null : cov === 0],
|
|
230
|
-
["file", fileErrs === 0], ["
|
|
249
|
+
["file", fileErrs === 0], ["pdf text", garbled === null ? null : garbled === 0],
|
|
250
|
+
["renders", shots > 0 ? true : null]];
|
|
231
251
|
console.log(rule("gates"));
|
|
232
252
|
for (const [n, ok] of gates) console.log(` ${ok === null ? "skip" : ok ? " ok " : "FAIL"} ${n}`);
|
|
233
253
|
console.log(`\n${rel(out)}${fs.existsSync(pdfOut) ? "\n" + rel(pdfOut) : ""}\n`);
|
|
234
254
|
return gates.some(([, ok]) => ok === false) ? 1 : 0;
|
|
235
255
|
}
|
|
236
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Every gate that does not need a spec, on any .pptx.
|
|
259
|
+
*
|
|
260
|
+
* A field run adapted a source deck by editing it directly — the right call,
|
|
261
|
+
* this kit cannot copy a source slide's shapes — and then delivered it with
|
|
262
|
+
* no gate but a generic validator, because `ship` wants a spec. Now the
|
|
263
|
+
* spec-free gates run on their own: file structure and fonts, the PDF text
|
|
264
|
+
* layer, a render of every slide with the contact sheet, and coverage when a
|
|
265
|
+
* verbatim dump is given. LibreOffice does the render, so fit is *not* judged
|
|
266
|
+
* here (§9 — DejaVu is wider than Geist); geometry, overlap and contrast are.
|
|
267
|
+
*/
|
|
268
|
+
function audit(file, dump) {
|
|
269
|
+
const work = path.join(path.dirname(file), "audit");
|
|
270
|
+
fs.mkdirSync(work, { recursive: true });
|
|
271
|
+
console.log(rule("1 file"));
|
|
272
|
+
const fileErrs = inspect(file);
|
|
273
|
+
|
|
274
|
+
console.log(rule("2 render"));
|
|
275
|
+
let shots = 0, garbled = null, pdf = null;
|
|
276
|
+
// on PATH in a sandbox; an app bundle on a Mac
|
|
277
|
+
const soffice = ["soffice", "libreoffice"].find((c) => has(c))
|
|
278
|
+
|| ["/Applications/LibreOffice.app/Contents/MacOS/soffice"].find((p) => fs.existsSync(p)) || null;
|
|
279
|
+
if (soffice) {
|
|
280
|
+
spawnSync(soffice, ["--headless", "--convert-to", "pdf", "--outdir", work, file], { stdio: "ignore" });
|
|
281
|
+
pdf = path.join(work, path.basename(file).replace(/\.pptx$/i, ".pdf"));
|
|
282
|
+
}
|
|
283
|
+
if (pdf && fs.existsSync(pdf) && has("pdftoppm")) {
|
|
284
|
+
const dir = path.join(work, "slides");
|
|
285
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
286
|
+
spawnSync("pdftoppm", ["-r", "96", "-png", pdf, path.join(dir, "slide")], { stdio: "ignore" });
|
|
287
|
+
shots = fs.readdirSync(dir).filter((f) => f.startsWith("slide")).length;
|
|
288
|
+
const small = fs.mkdtempSync(path.join(require("os").tmpdir(), "wand-sheet-"));
|
|
289
|
+
spawnSync("pdftoppm", ["-r", "26", "-png", pdf, path.join(small, "s")], { stdio: "ignore" });
|
|
290
|
+
const sheet = path.join(dir, "sheet.png");
|
|
291
|
+
runPython([path.join(KIT, "tools", "sheet.py"), small, sheet]);
|
|
292
|
+
fs.rmSync(small, { recursive: true, force: true });
|
|
293
|
+
console.log(`rendered : ${shots} slide(s) — open ${rel(sheet)} (LibreOffice: trust geometry,`);
|
|
294
|
+
console.log(` overlap and contrast; do NOT judge fit or weight from it)`);
|
|
295
|
+
if (has("pdftotext")) {
|
|
296
|
+
const r = spawnSync("pdftotext", [pdf, "-"], { encoding: "utf8" });
|
|
297
|
+
if (r.status === 0) {
|
|
298
|
+
garbled = 0;
|
|
299
|
+
for (const c of r.stdout || "") { const cp = c.codePointAt(0); if (cp >= 0x01c9 && cp <= 0x024f) garbled++; }
|
|
300
|
+
console.log(garbled ? `text layer : ${garbled} garbled glyph(s) — fonts are subsetted` : "text layer : digits decode clean");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
console.log(soffice ? "pdftoppm absent — no renders" : "no LibreOffice — no renders; open the deck and look at every slide");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log(rule("3 coverage"));
|
|
308
|
+
let cov = null;
|
|
309
|
+
if (dump && fs.existsSync(dump)) cov = runPython([path.join(KIT, "qa_coverage.py"), dump, file]);
|
|
310
|
+
else console.log("no dump given — skipped. Pass --dump work/visible_dump.txt from `rebuild` to check\n" +
|
|
311
|
+
"every source string survived the edit.");
|
|
312
|
+
|
|
313
|
+
const gates = [["file", fileErrs === 0], ["pdf text", garbled === null ? null : garbled === 0],
|
|
314
|
+
["renders", shots > 0 ? true : null], ["coverage", cov === null ? null : cov === 0]];
|
|
315
|
+
console.log(rule("gates"));
|
|
316
|
+
for (const [n, ok] of gates) console.log(` ${ok === null ? "skip" : ok ? " ok " : "FAIL"} ${n}`);
|
|
317
|
+
console.log(" (fit is not gated on a deck built outside the kit — say so in the review note)\n");
|
|
318
|
+
return gates.some(([, ok]) => ok === false) ? 1 : 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
237
321
|
/**
|
|
238
322
|
* File-level check, in-process. `validate.py` from the pptx skill is not in the
|
|
239
323
|
* zip and LibreOffice may not be installed, so the kit carries its own: the
|
|
@@ -396,6 +480,12 @@ to.
|
|
|
396
480
|
if (!positional[0]) die("usage: node wand.js inspect <deck.pptx>");
|
|
397
481
|
process.exit(inspect(path.resolve(positional[0])) ? 1 : 0);
|
|
398
482
|
|
|
483
|
+
case "audit": {
|
|
484
|
+
if (!positional[0]) die("usage: node wand.js audit <deck.pptx> [--dump visible_dump.txt]");
|
|
485
|
+
const di = rest.indexOf("--dump");
|
|
486
|
+
process.exit(audit(path.resolve(positional[0]), di >= 0 ? path.resolve(rest[di + 1]) : null));
|
|
487
|
+
}
|
|
488
|
+
|
|
399
489
|
case "ship":
|
|
400
490
|
if (!positional[0]) die("usage: node wand.js ship <spec.json> [out.pptx]");
|
|
401
491
|
process.exit(ship(positional[0], positional[1]));
|
package/wand_kit.js
CHANGED
|
@@ -999,7 +999,7 @@ function productMap(pres, { tag, titleText, subtitleText, bandRow, cards, split,
|
|
|
999
999
|
|
|
1000
1000
|
|
|
1001
1001
|
module.exports = {
|
|
1002
|
-
VERSION: "0.8.
|
|
1002
|
+
VERSION: "0.8.7",
|
|
1003
1003
|
T, F, COLS, ASSETS, radius, theme, autoSize, measure: M,
|
|
1004
1004
|
background, chrome, footer, title, subtitle, kicker, contentTop,
|
|
1005
1005
|
card, iconTile, pill,
|