klypix-mcp 1.49.0 → 1.49.2
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/A2A.md +2 -0
- package/FORMAT.md +24 -2
- package/README.md +84 -29
- package/bin/klypix-mcp.mjs +2 -2
- package/bin/klypix-worker.mjs +7 -0
- package/package.json +1 -1
- package/src/klypix-format.mjs +19 -4
package/A2A.md
CHANGED
|
@@ -46,11 +46,13 @@ the skills below.
|
|
|
46
46
|
|---|---|---|
|
|
47
47
|
| `make_board` | Create a new `.klypix` from cards + connections | a `.klypix` **FilePart** + summary |
|
|
48
48
|
| `remember` | Append cards/decisions to an existing canvas (positions preserved) | the updated `.klypix` |
|
|
49
|
+
| `learn_skill` | Capture a reusable how-to / gotcha as a 🛠 skill card — resurfaces every session | the updated `.klypix` |
|
|
49
50
|
| `recall` | Search card text/titles/`#tags` across the vault | matching cards (text) |
|
|
50
51
|
| `read_canvas` | Read one canvas (cards, graph, `[[links]]`) + its images | markdown + image FileParts |
|
|
51
52
|
| `list_canvases` | List canvases with counts | text |
|
|
52
53
|
| `brain_insights` | Hubs / orphans / stale questions in a brain | text |
|
|
53
54
|
| `search_all_brains` | Cross-project memory search (semantic + lexical) | text |
|
|
55
|
+
| `brain_connect` | Find and draw related-but-unlinked cards (proposes before it applies) | text |
|
|
54
56
|
|
|
55
57
|
## Talk to it (JSON-RPC 2.0)
|
|
56
58
|
|
package/FORMAT.md
CHANGED
|
@@ -47,7 +47,8 @@ writer guarantees the content-addressed `images/ files/ thumbs/` split.
|
|
|
47
47
|
"updatedAt": "ISO-8601",
|
|
48
48
|
"title": "My board",
|
|
49
49
|
"stats": { "itemCount": 12, "assetCount": 3, "totalBytes": 0 },
|
|
50
|
-
"sync": { "enabled": false, "lastSyncRev": null, "lastSyncAt": null, "deviceId": "dev_…" }
|
|
50
|
+
"sync": { "enabled": false, "lastSyncRev": null, "lastSyncAt": null, "deviceId": "dev_…" },
|
|
51
|
+
"cloud": { "blobId": "702ecfe0-…", "linkedAt": "ISO-8601", "linkedBy": "Sara Ali" }
|
|
51
52
|
}
|
|
52
53
|
```
|
|
53
54
|
|
|
@@ -65,6 +66,13 @@ writer guarantees the content-addressed `images/ files/ thumbs/` split.
|
|
|
65
66
|
`items/`. Never use `stats` as an integrity check.
|
|
66
67
|
- `sync` records whether the file is opted into the app's cloud sync; it carries no
|
|
67
68
|
content and no credentials.
|
|
69
|
+
- **`cloud`** — optional and additive; written by the KLYPIX app when a brain is
|
|
70
|
+
shared, naming the encrypted cloud copy this file pairs with. It exists so the
|
|
71
|
+
mapping **travels inside the file**: clone the repo, or email the `.klypix`, and the
|
|
72
|
+
copy that arrives already knows its cloud twin — no server lookup, works offline.
|
|
73
|
+
**The encryption key is never here.** The `blobId` alone reaches nothing: the cloud
|
|
74
|
+
copy is ciphertext, and the key travels only in a share link's URL fragment. A
|
|
75
|
+
reader that does not care about cloud sync should ignore this object.
|
|
68
76
|
|
|
69
77
|
## `canvas.json`
|
|
70
78
|
|
|
@@ -115,13 +123,27 @@ strings: `text`, `box`, `image`, `file`, `container`, `approval`, `link`,
|
|
|
115
123
|
"border": true,
|
|
116
124
|
"heading": false,
|
|
117
125
|
"createdBy": "agent",
|
|
118
|
-
"createdVia": "claude-code"
|
|
126
|
+
"createdVia": "claude-code",
|
|
127
|
+
"author": "Sara Ali",
|
|
128
|
+
"updatedAt": 1785500000000
|
|
119
129
|
}
|
|
120
130
|
```
|
|
121
131
|
|
|
122
132
|
`createdBy` (`user` | `agent`) and the optional `createdVia` (which agent/channel
|
|
123
133
|
captured it) are the provenance bits the brain surfaces as badges and lenses.
|
|
124
134
|
|
|
135
|
+
The optional **`author`** answers the question a team actually asks: `createdBy` says
|
|
136
|
+
*what* wrote a card, `author` says *whose*. It is resolved from `git config user.name`
|
|
137
|
+
so brain attribution matches commit attribution with no configuration (override with
|
|
138
|
+
`KLYPIX_AUTHOR`; falls back to the OS user; simply absent if neither resolves).
|
|
139
|
+
|
|
140
|
+
**`updatedAt` is VOLATILE — never treat it as content.** It is restamped by the act of
|
|
141
|
+
writing, so two copies of an identical card differ in it. Any comparison that decides
|
|
142
|
+
"did this card change" must strip `updatedAt` (and the derived `zIndex` in
|
|
143
|
+
`positions`) before comparing, or a plain re-save reads as an edit. The merge engine
|
|
144
|
+
learned this the hard way: a byte-compare spawned conflict twins for cards nobody
|
|
145
|
+
touched. `mergeBrains` exports `sameMeaning(a, b)` as the single correct comparison.
|
|
146
|
+
|
|
125
147
|
## Bytes: when they are embedded vs referenced
|
|
126
148
|
|
|
127
149
|
**Embedded** — the bytes live inside the ZIP under `assets/`, and the item JSON
|
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ npx klypix-mcp conformance
|
|
|
79
79
|
|
|
80
80
|
It runs in a temporary fixture and touches nothing else. It checks tool discovery, task memory,
|
|
81
81
|
truthful peer reporting, overlap surfacing, proactive logging, and in-band delivery of a peer note.
|
|
82
|
-
It verifies
|
|
82
|
+
It verifies 12 required coordination behaviours — not the 18 tools, and not the retrieval engine.
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
@@ -133,9 +133,9 @@ content.
|
|
|
133
133
|
npx klypix-mcp link --check # audits without writing; exits non-zero on drift
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
>
|
|
137
|
-
>
|
|
138
|
-
>
|
|
136
|
+
> Either form works, and both are safe in CI: `npx -p klypix-mcp klypix-link --check` used to
|
|
137
|
+
> drop `--check` and write anyway — fixed, and locked by `test/cli-args.mjs`, which asserts the
|
|
138
|
+
> standalone bin and the dispatcher parse arguments identically.
|
|
139
139
|
|
|
140
140
|
**Give a project a brain** by dropping a `brain.klypix` into it — the
|
|
141
141
|
[KLYPIX app](https://klypix.com) does it in one click (*Save canvas as project brain*), or
|
|
@@ -292,12 +292,43 @@ Apache-2.0 and work with no app installed. The app's interface is available in E
|
|
|
292
292
|
|
|
293
293
|
## Git and concurrency
|
|
294
294
|
|
|
295
|
-
One file in your repo, committed with your code — versioned, branchable, portable.
|
|
295
|
+
One file in your repo, committed with your code — versioned, branchable, portable. So two
|
|
296
|
+
developers already share one brain the way they share code: clone, branch, pull.
|
|
296
297
|
|
|
297
|
-
Be precise about what git does
|
|
298
|
-
`Bin 1308328 -> 1309005 bytes
|
|
299
|
-
all-or-nothing take-ours or take-theirs
|
|
300
|
-
|
|
298
|
+
Be precise about what git does on its own: `brain.klypix` is a binary ZIP. Git shows
|
|
299
|
+
`Bin 1308328 -> 1309005 bytes` and produces zero line diffs, so out of the box a conflict on it is
|
|
300
|
+
an all-or-nothing take-ours or take-theirs, and a reviewer sees nothing. **Card-level merge safety
|
|
301
|
+
comes from the KLYPIX engine** — but since 1.48.0 you can hand that engine to git and read its
|
|
302
|
+
output in a PR:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
npx klypix-mcp git-driver install # once per clone, in any repo
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
That registers a merge driver for `*.klypix` (a per-machine git config line plus a `.gitattributes`
|
|
309
|
+
rule you commit) and provisions the engine it needs. When two people change the brain and one
|
|
310
|
+
pulls, git calls the engine instead of stopping: new cards from both sides are kept, a card only
|
|
311
|
+
one side edited takes that edit, and a card edited differently on both sides keeps **both**
|
|
312
|
+
versions — the second as a linked twin, never a silent overwrite. Before returning, the merge
|
|
313
|
+
asserts it still contains every surviving card from both sides and refuses rather than hand back a
|
|
314
|
+
result that lost one.
|
|
315
|
+
|
|
316
|
+
The honest boundary: a machine that has not run `git-driver install` simply gets the old binary
|
|
317
|
+
conflict — safe degradation, not corruption — and git keeps both parents of every merge, so even a
|
|
318
|
+
merge you dislike is reconstructable. It is a merge *on pull*, not live sync.
|
|
319
|
+
|
|
320
|
+
For review, two commands turn a binary blob into something a human can read:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npx klypix-mcp diff main # card-level: what was added / updated / removed
|
|
324
|
+
npx klypix-mcp pr-brief origin/main # the brain cards that reference this PR's changed files
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`diff` compares meaning rather than bytes (a re-save restamps timestamps; that is not a change).
|
|
328
|
+
`pr-brief` matches a card's `#file-…` evidence anchors against the changed paths, so a reviewer
|
|
329
|
+
sees the decisions already recorded about the code in front of them. `examples/github/brain-pr.yml`
|
|
330
|
+
wires both into a sticky pull-request comment using nothing but the checkout and the default
|
|
331
|
+
`GITHUB_TOKEN` — no KLYPIX service in the path.
|
|
301
332
|
|
|
302
333
|
Concurrent sessions serialize behind a capture lock, and each write is a temp file plus an atomic
|
|
303
334
|
rename, so a crash mid-write leaves the previous good file intact. The lock is advisory with a
|
|
@@ -307,6 +338,24 @@ was judged worse — but it is a real limit, not a guarantee.
|
|
|
307
338
|
|
|
308
339
|
---
|
|
309
340
|
|
|
341
|
+
## The command line
|
|
342
|
+
|
|
343
|
+
The MCP verbs below are what agents call. These are what **you** call:
|
|
344
|
+
|
|
345
|
+
| Command | What it does |
|
|
346
|
+
|---|---|
|
|
347
|
+
| `npx klypix-mcp init` | Seed a starter `brain.klypix` here and print an MCP config |
|
|
348
|
+
| `npx klypix-mcp install` | Install the engine + Claude Code hooks on this machine (see Quick start) |
|
|
349
|
+
| `npx klypix-mcp link` | Wire this project for Cursor, Cline, Windsurf, Copilot, Gemini CLI, Aider (`--check` audits) |
|
|
350
|
+
| `npx klypix-mcp doctor` | One verdict: version, hosts, live sessions, tool count, drift. Exits non-zero — usable as a CI gate |
|
|
351
|
+
| `npx klypix-mcp conformance` | Launch two real MCP clients against this build and verify coordination behaviour |
|
|
352
|
+
| `npx klypix-mcp git-driver` | Register the lossless `.klypix` merge driver for a repo (`status` to check) |
|
|
353
|
+
| `npx klypix-mcp diff [ref]` | Card-level brain diff against a git ref, as markdown |
|
|
354
|
+
| `npx klypix-mcp pr-brief [ref]` | Brain cards referencing the files changed since a ref, as markdown |
|
|
355
|
+
| `npx klypix-mcp garden-code` | Print the human approval code `brain_garden` requires |
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
310
359
|
## The 18 verbs
|
|
311
360
|
|
|
312
361
|
| Tool | What it does |
|
|
@@ -445,7 +494,10 @@ Read this section before you build on any of it.
|
|
|
445
494
|
|
|
446
495
|
- **Coordination is machine-local and OS-user-local.** The presence lane is a file in your home
|
|
447
496
|
directory. Two developers on two machines do not see each other's sessions, peers, overlaps or
|
|
448
|
-
messages.
|
|
497
|
+
messages. This package ships the cross-machine presence *core* (`./presence-relay` — versioned
|
|
498
|
+
metadata-only frames, a symmetric default-off consent gate, loop prevention and message dedup),
|
|
499
|
+
but no transport: carrying frames between machines is the desktop app's job. With `klypix-mcp`
|
|
500
|
+
alone, coordination is machine-local.
|
|
449
501
|
- **Overlap matching is exact-path, and both sides must declare.** A session that never declares
|
|
450
502
|
its expected files is invisible to overlap detection, and `src/auth/token.ts` does not match a
|
|
451
503
|
rename or a parent directory.
|
|
@@ -453,10 +505,9 @@ Read this section before you build on any of it.
|
|
|
453
505
|
`blocking`; the mechanism is not.
|
|
454
506
|
- **Codex has no automatic capture**, with or without `--codex-hooks`. The Codex hook never writes
|
|
455
507
|
the brain.
|
|
456
|
-
- **
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
them yet.
|
|
508
|
+
- **Uninstall does not remove per-project files.** `npx klypix-mcp uninstall` handles the
|
|
509
|
+
machine-global install; the 14 files `link` wrote into each project are listed by
|
|
510
|
+
`npx klypix-mcp link --check` and removed by `uninstall unlink` **per project**, one at a time.
|
|
460
511
|
- **Drift detection is single-host and opt-in per card.** It needs an `ev:` anchor written by the
|
|
461
512
|
card's author, and it runs only in the Claude Code hook path — the MCP tools do not compute
|
|
462
513
|
freshness.
|
|
@@ -468,8 +519,10 @@ Read this section before you build on any of it.
|
|
|
468
519
|
- **A fresh `npx klypix-mcp install` gets lexical retrieval.** The optional on-device model is
|
|
469
520
|
deliberately not installed.
|
|
470
521
|
- **The capture lock is fail-open** past ~3.6 seconds of contention (see *Git and concurrency*).
|
|
471
|
-
-
|
|
472
|
-
|
|
522
|
+
- **`test/` is not in the published tarball.** Run the suite from a clone. The publish workflow
|
|
523
|
+
*does* gate on it — a `gate` job runs `npm ci`, asserts the test chain is intact, runs `npm test`,
|
|
524
|
+
validates the version/tag, and checks the packed tarball; `publish` declares `needs: gate`, so a
|
|
525
|
+
red gate means npm never sees a tarball.
|
|
473
526
|
- **`canvas_view`'s MCP Apps UI has never been verified on a real Apps host.**
|
|
474
527
|
|
|
475
528
|
## Numbers and methodology
|
|
@@ -477,9 +530,9 @@ Read this section before you build on any of it.
|
|
|
477
530
|
Every number here is measured on our own project brain. Nothing below is published, benchmarked or
|
|
478
531
|
independently validated.
|
|
479
532
|
|
|
480
|
-
- **Dogfood scale.** KLYPIX itself is built with its own brain: **1,
|
|
533
|
+
- **Dogfood scale.** KLYPIX itself is built with its own brain: **1,645 cards and 1,521
|
|
481
534
|
connections**, written by multiple concurrent agent sessions, receipts in the file. Current as of
|
|
482
|
-
2026-
|
|
535
|
+
2026-08-01.
|
|
483
536
|
- **Recall.** 73% of past decisions recovered with one search round, 55% brief-only, 0% cold.
|
|
484
537
|
Caveat that travels with it: n=20, our own brain, self-authored questions, LLM-judged.
|
|
485
538
|
- **Ranker.** recall@5 of the true source card went **15% → 40%** across two upgrades (n=20 frozen
|
|
@@ -487,31 +540,33 @@ independently validated.
|
|
|
487
540
|
experiment that *regressed* — contextual prefixes on short cards — is recorded next to the wins.
|
|
488
541
|
- **What we do not publish.** No download count: this package's own 24-hour auto-updater generates
|
|
489
542
|
most of it, so it is not a user count. No adoption, team or customer figures. No brief-token
|
|
490
|
-
figure — the last one was measured at ~600 cards and is stale at 1,
|
|
543
|
+
figure — the last one was measured at ~600 cards and is stale at 1,645.
|
|
491
544
|
- **The eval harness is not in this repo.** It lives in the private KLYPIX desktop repository. The
|
|
492
545
|
numbers above are ours to defend, not yours to reproduce from here — treat them accordingly.
|
|
493
546
|
|
|
494
547
|
## Uninstall
|
|
495
548
|
|
|
496
|
-
There is no uninstall command yet. To remove Klypix by hand:
|
|
497
|
-
|
|
498
549
|
```bash
|
|
499
|
-
|
|
500
|
-
# then
|
|
501
|
-
|
|
502
|
-
npx klypix-mcp link --check # lists the 14 managed files in a project, so you know what to delete
|
|
550
|
+
npx klypix-mcp uninstall --check # full inventory — writes nothing
|
|
551
|
+
npx klypix-mcp uninstall # asks, then removes the machine-global install
|
|
552
|
+
npx klypix-mcp uninstall unlink # run inside a project: removes the files `link` wrote there
|
|
503
553
|
```
|
|
504
554
|
|
|
555
|
+
It strips only KLYPIX's own entries — every other hook and setting in
|
|
556
|
+
`~/.claude/settings.json` stays — backs up each file it edits, and **never deletes a `.klypix`**.
|
|
557
|
+
`--yes` skips the prompt for scripted removal.
|
|
558
|
+
|
|
505
559
|
Your `brain.klypix` is yours — it is a plain ZIP and stays readable with or without this package.
|
|
506
560
|
|
|
507
561
|
## Contributing
|
|
508
562
|
|
|
509
563
|
Issues and pull requests: [github.com/dahshanlabs/klypix-mcp](https://github.com/dahshanlabs/klypix-mcp).
|
|
510
564
|
|
|
511
|
-
The repository carries
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
565
|
+
The repository carries 38 test files, 34 of them in the `npm test` chain, covering the presence
|
|
566
|
+
lane and its cross-machine relay, the Context Gateway, supervisor hot-swap, auto-update, retrieval
|
|
567
|
+
quality, decay, challenge, lenses, the format guard, the git tools (including a real `git merge`
|
|
568
|
+
through the merge driver), uninstall, and conformance. Run them with `npm test` from a clone — they
|
|
569
|
+
are not in the published tarball, though the publish workflow does run them as a gate. There is a known intermittent Windows `EPERM` flake on rename in
|
|
515
570
|
`test/mcp-supervisor.mjs`.
|
|
516
571
|
|
|
517
572
|
## Why this exists
|
package/bin/klypix-mcp.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const PKG_VERSION = (() => {
|
|
|
19
19
|
}
|
|
20
20
|
})();
|
|
21
21
|
|
|
22
|
-
const DIRECT = new Set(['install', 'link', 'doctor', 'conformance', 'garden-code', 'init', 'git-driver', 'diff', 'pr-brief']);
|
|
22
|
+
const DIRECT = new Set(['install', 'link', 'doctor', 'conformance', 'garden-code', 'init', 'git-driver', 'diff', 'pr-brief', 'uninstall']);
|
|
23
23
|
|
|
24
24
|
const USAGE = [
|
|
25
25
|
`klypix-mcp ${PKG_VERSION} — shared project brain + MCP coordination server.`,
|
|
@@ -31,12 +31,12 @@ const USAGE = [
|
|
|
31
31
|
' conformance [--json] launch two real MCP clients against this build',
|
|
32
32
|
' init seed a starter ./brain.klypix + print an MCP config',
|
|
33
33
|
' garden-code [brain] print the human approval code for brain_garden',
|
|
34
|
+
' uninstall [--check|--yes|unlink] remove this install from the machine (--check inventories first; never deletes a .klypix)',
|
|
34
35
|
' git-driver [install|status] [repo] register the lossless .klypix merge driver for a repo (zero-command teams)',
|
|
35
36
|
' diff [ref] [--brain <path>] readable brain diff vs a git ref (default HEAD) — markdown to stdout',
|
|
36
37
|
' pr-brief [baseRef] [--brain <path>] brain decisions touching the files changed since baseRef — PR-comment markdown',
|
|
37
38
|
'',
|
|
38
39
|
'With no verb (or any --flag, e.g. --vault <dir>) it runs as an MCP stdio server.',
|
|
39
|
-
'There is no uninstall command — removal is manual (see README).',
|
|
40
40
|
].join('\n');
|
|
41
41
|
|
|
42
42
|
const verb = process.argv[2];
|
package/bin/klypix-worker.mjs
CHANGED
|
@@ -110,6 +110,13 @@ await runVerb('git-driver', './klypix-git-driver.mjs');
|
|
|
110
110
|
await runVerb('diff', './klypix-diff.mjs');
|
|
111
111
|
await runVerb('pr-brief', './klypix-pr-brief.mjs');
|
|
112
112
|
|
|
113
|
+
// `npx klypix-mcp uninstall` — the machine-global removal tool. `--check` prints a full
|
|
114
|
+
// inventory and writes nothing; every write is backed up; a `.klypix` brain is never
|
|
115
|
+
// touched. Wired into the dispatcher 2026-08-01: it shipped as a bin-only verb, so
|
|
116
|
+
// `npx klypix-mcp uninstall` answered "unknown command" while the README (and this
|
|
117
|
+
// CLI's own --help) said no uninstall existed at all.
|
|
118
|
+
await runVerb('uninstall', './klypix-uninstall.mjs');
|
|
119
|
+
|
|
113
120
|
// `npx klypix-mcp garden-code` — the HUMAN half of the garden approval gate.
|
|
114
121
|
// brain_garden's apply requires an 8-char code derived from the exact dormant
|
|
115
122
|
// candidate set + day; the agent is deliberately never shown it. The human runs
|
package/package.json
CHANGED
package/src/klypix-format.mjs
CHANGED
|
@@ -126,12 +126,27 @@ export async function parseKlypix(buffer) {
|
|
|
126
126
|
// (merge, arrange, capture) a structure it does not understand. The merge
|
|
127
127
|
// driver inherits this automatically: the throw exits it non-zero, which
|
|
128
128
|
// degrades to a normal manual git conflict.
|
|
129
|
+
// Both dimensions, or the guard has a hole: `version` is the CONTAINER shape and
|
|
130
|
+
// `schemaVersion` is the DOCUMENT (item/connection) shape, and they move
|
|
131
|
+
// independently. The desktop codec already refused a future value of either
|
|
132
|
+
// (klypixFormatV4.ts assertManifestReadable); checking only `version` here left
|
|
133
|
+
// {version:4, schemaVersion:5} parsed by this engine and refused by the app —
|
|
134
|
+
// the asymmetric case, where merge/git-driver/capture would happily write back a
|
|
135
|
+
// document they did not understand. Found by the 2026-08-01 doc audit.
|
|
129
136
|
const KLYPIX_FORMAT_CEILING = 4;
|
|
130
|
-
if (manifest && manifest.format === 'klypix'
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
if (manifest && manifest.format === 'klypix') {
|
|
138
|
+
const layout = Number(manifest.version);
|
|
139
|
+
const schema = Number(manifest.schemaVersion);
|
|
140
|
+
const tooNew = Math.max(
|
|
141
|
+
Number.isFinite(layout) ? layout : 0,
|
|
142
|
+
Number.isFinite(schema) ? schema : 0,
|
|
134
143
|
);
|
|
144
|
+
if (tooNew > KLYPIX_FORMAT_CEILING) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`This .klypix was saved by a newer format (v${tooNew}); this engine reads up to v${KLYPIX_FORMAT_CEILING}. ` +
|
|
147
|
+
'Update KLYPIX / klypix-mcp instead of parsing it — a blind read could damage it.'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
135
150
|
}
|
|
136
151
|
const canvas = JSON.parse(canvasRaw);
|
|
137
152
|
// v4 manifests are {format:"klypix", version:4}; positions presence is the
|