beehaven 0.8.16

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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/dist/__tests__/_cli-switch.js +105 -0
  4. package/dist/__tests__/agent-wrap-probe.js +136 -0
  5. package/dist/agent-attribution.js +192 -0
  6. package/dist/agent-identity-daemon.js +135 -0
  7. package/dist/agent-onboard.js +156 -0
  8. package/dist/agent-scope.js +79 -0
  9. package/dist/agent-sessions.js +370 -0
  10. package/dist/agent-state.js +482 -0
  11. package/dist/app-manifest.js +221 -0
  12. package/dist/app-signer.js +54 -0
  13. package/dist/app-signing.js +127 -0
  14. package/dist/audio-play.js +98 -0
  15. package/dist/avatar-protocol.js +25 -0
  16. package/dist/avatar-route.js +79 -0
  17. package/dist/bee/cloud-proxy.js +130 -0
  18. package/dist/blocked-actions.js +120 -0
  19. package/dist/build-channel.js +101 -0
  20. package/dist/buildings/daemon-workspace.js +475 -0
  21. package/dist/claude-bridge.js +310 -0
  22. package/dist/claude-credential-store.js +633 -0
  23. package/dist/clearly-sync.js +518 -0
  24. package/dist/clearly.js +616 -0
  25. package/dist/cli-state.js +334 -0
  26. package/dist/cli.js +6973 -0
  27. package/dist/commands/agent-skills.js +302 -0
  28. package/dist/commands/agent.js +1310 -0
  29. package/dist/commands/app.js +1123 -0
  30. package/dist/commands/auth.js +171 -0
  31. package/dist/commands/building-shell.js +392 -0
  32. package/dist/commands/canvas.js +932 -0
  33. package/dist/commands/claude.js +218 -0
  34. package/dist/commands/compliance.js +387 -0
  35. package/dist/commands/compose.js +507 -0
  36. package/dist/commands/composition.js +95 -0
  37. package/dist/commands/connect-session.js +409 -0
  38. package/dist/commands/conversations.js +160 -0
  39. package/dist/commands/costs.js +443 -0
  40. package/dist/commands/d1.js +253 -0
  41. package/dist/commands/daemon-cmd.js +162 -0
  42. package/dist/commands/deploy.js +164 -0
  43. package/dist/commands/design.js +617 -0
  44. package/dist/commands/dev-generate.js +125 -0
  45. package/dist/commands/dev.js +109 -0
  46. package/dist/commands/devices.js +135 -0
  47. package/dist/commands/explain-router.js +24 -0
  48. package/dist/commands/explain.js +882 -0
  49. package/dist/commands/explore.js +264 -0
  50. package/dist/commands/help.js +2418 -0
  51. package/dist/commands/init.js +408 -0
  52. package/dist/commands/install.js +129 -0
  53. package/dist/commands/library.js +229 -0
  54. package/dist/commands/marketplace.js +138 -0
  55. package/dist/commands/mcp-install.js +476 -0
  56. package/dist/commands/mention.js +257 -0
  57. package/dist/commands/omni.js +529 -0
  58. package/dist/commands/secrets.js +323 -0
  59. package/dist/commands/self-update.js +206 -0
  60. package/dist/commands/sheet.js +264 -0
  61. package/dist/commands/shell-output.js +309 -0
  62. package/dist/commands/shell.js +1048 -0
  63. package/dist/commands/shop.js +368 -0
  64. package/dist/commands/skills.js +311 -0
  65. package/dist/commands/slides.js +418 -0
  66. package/dist/commands/swarm.js +341 -0
  67. package/dist/commands/ticket.js +1301 -0
  68. package/dist/commands/tool.js +312 -0
  69. package/dist/commands/trash.js +106 -0
  70. package/dist/commands/treehouse.js +120 -0
  71. package/dist/commands/user.js +240 -0
  72. package/dist/commands/voice-tts.js +319 -0
  73. package/dist/commands/voice.js +166 -0
  74. package/dist/commands/watch-git.js +401 -0
  75. package/dist/commands/webhook.js +159 -0
  76. package/dist/commands/workspace.js +3354 -0
  77. package/dist/composition-repos.js +91 -0
  78. package/dist/daemon-avatar.js +130 -0
  79. package/dist/daemon-claude-code-auto-install.js +52 -0
  80. package/dist/daemon-client.js +363 -0
  81. package/dist/daemon-codex.js +349 -0
  82. package/dist/daemon-entry.js +101 -0
  83. package/dist/daemon-fetch-allowlist.js +194 -0
  84. package/dist/daemon-gemini.js +224 -0
  85. package/dist/daemon-mcp-clearly.js +353 -0
  86. package/dist/daemon-mcp-sandbox.js +127 -0
  87. package/dist/daemon-omni-company.js +338 -0
  88. package/dist/daemon-omni-hooks.js +375 -0
  89. package/dist/daemon-omni-identity.js +229 -0
  90. package/dist/daemon-omni-pipeline.js +251 -0
  91. package/dist/daemon-omni-policies.js +346 -0
  92. package/dist/daemon-omni-principal.js +580 -0
  93. package/dist/daemon-omni-tool-gate.js +116 -0
  94. package/dist/daemon-omni.js +1442 -0
  95. package/dist/daemon-path-transform.js +120 -0
  96. package/dist/daemon-write-sandbox.js +191 -0
  97. package/dist/daemon.js +4471 -0
  98. package/dist/dashboard.js +846 -0
  99. package/dist/dep-installer.js +286 -0
  100. package/dist/dev-mode.js +67 -0
  101. package/dist/doc-indexer.js +169 -0
  102. package/dist/e2e-crypto.js +94 -0
  103. package/dist/feature-flags.js +154 -0
  104. package/dist/file-browser.js +88 -0
  105. package/dist/first-party-hives.js +211 -0
  106. package/dist/folder-sync.js +226 -0
  107. package/dist/health.js +56 -0
  108. package/dist/ipc-auth.js +299 -0
  109. package/dist/ipc-client.js +495 -0
  110. package/dist/ipc-server.js +684 -0
  111. package/dist/keychain.js +224 -0
  112. package/dist/lib/admin-client.js +73 -0
  113. package/dist/lib/mobile-build-parser.js +156 -0
  114. package/dist/local-app-manager.js +660 -0
  115. package/dist/log-redaction.js +89 -0
  116. package/dist/logger.js +108 -0
  117. package/dist/manifest.js +143 -0
  118. package/dist/marketplace.js +793 -0
  119. package/dist/min-version-copy.js +51 -0
  120. package/dist/min-version.js +214 -0
  121. package/dist/public-surface.js +23 -0
  122. package/dist/relay-presets.js +89 -0
  123. package/dist/relay.js +2723 -0
  124. package/dist/repl.js +619 -0
  125. package/dist/setup-relay.js +363 -0
  126. package/dist/setup-service.js +536 -0
  127. package/dist/shared/bee-memory/index.js +6 -0
  128. package/dist/shared/bee-memory/types.js +682 -0
  129. package/dist/shared/claude-code/daemon-types.js +112 -0
  130. package/dist/shared/claude-code/index.js +2 -0
  131. package/dist/shared/claude-code/types.js +1 -0
  132. package/dist/shared/status/schema.js +133 -0
  133. package/dist/shell-session.js +198 -0
  134. package/dist/speak-lock.js +97 -0
  135. package/dist/spinner.js +57 -0
  136. package/dist/telemetry.js +166 -0
  137. package/dist/terminal/block-parser.js +207 -0
  138. package/dist/terminal/shell-integration.js +108 -0
  139. package/dist/transcript-tailer.js +215 -0
  140. package/dist/tts-qwen3.js +547 -0
  141. package/dist/types.js +4 -0
  142. package/dist/utm.js +38 -0
  143. package/dist/verb-surface.js +331 -0
  144. package/dist/version-utils.js +85 -0
  145. package/dist/watcher.js +207 -0
  146. package/package.json +88 -0
  147. package/python/qwen3_tts.py +230 -0
  148. package/scripts/stamp-build-channel.mjs +115 -0
  149. package/scripts/sync-installed.mjs +159 -0
  150. package/scripts/sync-skills.mjs +112 -0
  151. package/skills/README.md +24 -0
  152. package/skills/agent-doodle-space/SKILL.md +118 -0
  153. package/skills/agent-home/SKILL.md +98 -0
  154. package/skills/beehaven-cli/SKILL.md +144 -0
  155. package/skills/claude-shopper/README.md +90 -0
  156. package/skills/claude-shopper/SKILL.md +151 -0
  157. package/skills/clearly-agent/SKILL.md +183 -0
  158. package/skills/clearly-canvas/SKILL.md +222 -0
  159. package/skills/clearly-canvas/authoring.md +160 -0
  160. package/skills/clearly-canvas/communications.md +32 -0
  161. package/skills/clearly-canvas/composition-patterns.md +84 -0
  162. package/skills/clearly-canvas/example.mjs +78 -0
  163. package/skills/clearly-canvas/project-plans.md +156 -0
  164. package/skills/clearly-canvas/review.mjs +68 -0
  165. package/skills/clearly-workspace/SKILL.md +79 -0
  166. package/skills/clearly-workspace/survey.mjs +101 -0
  167. package/skills/codebase-map/SKILL.md +142 -0
  168. package/skills/design-craft/SKILL.md +300 -0
  169. package/skills/index.json +79 -0
  170. package/skills/layout-systems/SKILL.md +184 -0
  171. package/skills/pair-on-canvas/SKILL.md +92 -0
@@ -0,0 +1,160 @@
1
+ # Authoring contracts
2
+
3
+ Use `beehaven call canvas-catalog '{"query":"<action>"}'` for current arguments and
4
+ `headlessActions`. The CLI calls the deployed worker; changing local source does
5
+ not add a capability to that worker.
6
+
7
+ ## Generate payloads, then submit
8
+
9
+ Use a local builder and pass JSON through `@file` or stdin. Do not interpolate copy,
10
+ SVG or code into shell strings. Here is a minimal native-node pattern; replace its
11
+ visual decisions with the brief's system:
12
+
13
+ For a complete editable three-artboard example, inspect [example.mjs](example.mjs).
14
+ It demonstrates tokens, reusable helpers and varied compositions; it writes a payload
15
+ without making remote calls. Run `node <skill-directory>/example.mjs <composition-id>`.
16
+ Use its construction approach, not its fictional brand or aesthetic, for other briefs.
17
+
18
+ ```js
19
+ import { writeFileSync } from 'node:fs';
20
+ const compositionId = process.argv[2];
21
+ const grid = { w: 1440, h: 900, margin: 88, gap: 24 };
22
+ const color = { paper: '#F3F0E8', ink: '#172824', accent: '#CB482F' };
23
+ const type = {
24
+ display: { family: 'Inter', size: 88, fontWeight: 700, lineHeight: 1.05, letterSpacing: -2 },
25
+ body: { family: 'Inter', size: 24, fontWeight: 400, lineHeight: 1.4 },
26
+ };
27
+ const batch = [];
28
+ const add = (action, args) => batch.push({ action, args });
29
+ add('frame.create', { name: '01-opening', x: 0, y: 0, w: grid.w, h: grid.h, fill: color.paper });
30
+ const text = (name, copy, x, y, w, role) => add('canvas.create-node', {
31
+ name, type: 'text', parentId: '01-opening', x, y, w,
32
+ text: copy, fill: color.ink, ...type[role],
33
+ });
34
+ text('opening-title', 'Ideas take\nshape here.', grid.margin, 120, 740, 'display');
35
+ text('opening-deck', 'An editable visual system for the next chapter.', grid.margin, 360, 580, 'body');
36
+ // Continue with the brief's central visual, supporting evidence and footer.
37
+ writeFileSync('/tmp/canvas-build.json', JSON.stringify({ compositionId, batch }));
38
+ ```
39
+
40
+ ```sh
41
+ beehaven call composition-create '{"title":"Project — concept","projectId":"<project-id>"}'
42
+ node build.mjs <returned-id>
43
+ beehaven call canvas-act @/tmp/canvas-build.json > /tmp/canvas-build-result.json
44
+ ```
45
+
46
+ Check `ok`, each `results[].ok`, `count`, `skipped` and `rolledBack`. A failed headless
47
+ batch may roll back its scene writes. Re-read the scene before deciding what to retry;
48
+ do not blindly resend only skipped operations. Mixed live/headless actions may have
49
+ different rollback behavior. Use deterministic layer names and returned IDs to avoid
50
+ duplicate content during retries.
51
+
52
+ ## Node shape and coordinates
53
+
54
+ Fields are flat: `{name,type,x,y,w,h,text,size,family,fontWeight,fill,...}`.
55
+ Use `text` for copy, `size` for type size, numeric `fontWeight`, and `w`/`h` for bounds.
56
+ Create normalizes hex colours; raw stored scenes use RGBA arrays in 0..1.
57
+
58
+ - `frame.create` and `canvas.create-node` with `parentId` take coordinates **relative
59
+ to that parent**. Create the frame first. Names can resolve earlier nodes in a batch.
60
+ - Persisted nodes and perception bounds use **world coordinates**. Raw scene replacement
61
+ through `composition-patch`/MCP must use world coordinates, even with `parentId`.
62
+ - `canvas.update-nodes` geometry is world geometry. For group moves/resizes use the
63
+ catalog's frame/arrangement/scale actions; do not assume a patch reflows children.
64
+ - Build backgrounds before foregrounds. Use `canvas.order` to change stacking.
65
+ - Name nodes uniquely; use returned IDs where names collide. Parent content to its
66
+ frame so movement, selection, clipping and reviews can treat it as one artifact.
67
+ - Give an intended stroke both `stroke` and `strokeWidth`. Zero explicitly removes it.
68
+
69
+ ## Typography
70
+
71
+ For prose, set `w` and omit `h` to enable auto-height. Omit both axes only for short
72
+ single-line labels whose width should hug content. A fixed box sets that axis explicitly;
73
+ it does not guarantee text will clip safely. Headless auto-height is an estimate from
74
+ stored text and font metrics, not browser layout. Reserve enough room for measured lines,
75
+ then inspect the rendered result in the editor.
76
+
77
+ ```json
78
+ {"compositionId":"<id>","action":"canvas.measure-text","args":{
79
+ "text":"Ideas take shape here.","family":"Inter","size":88,"fontWeight":700,
80
+ "letterSpacing":-2,"lineHeight":1.05,"maxWidth":740}}
81
+ ```
82
+
83
+ Use measured `height`/`lineCount` to place the next block. Measure the exact copy, font,
84
+ weight, tracking and width you will render. Inspect final line breaks visually. Changing
85
+ the headline width or copy is often better than shrinking every role to fit.
86
+
87
+ `font` reports the requested family/weight and resolved file. `exact:false` means a
88
+ fallback or an estimate; it is not a proof of browser fit. Bundled faces include Inter
89
+ 400/700, JetBrains Mono 400, Geist Mono 400, Montserrat 500 and Google Sans Flex's default
90
+ face. Other weights/families need live verification. Custom font appearance can differ
91
+ from the headless preview. The renderer's supported fonts are not a design-style mandate.
92
+
93
+ ## Layout, reuse and graphics
94
+
95
+ Discover `canvas.arrange`, frame auto-layout, constraints, `canvas.scale`, components
96
+ and tokens when relevant. Constraints reflow children on frame resize; they do not turn
97
+ a static canvas into a responsive website. Design explicit mobile artboards when the
98
+ deliverable requires them, with content order and hierarchy reconsidered for the width.
99
+
100
+ Use `arrow.create {from,to,routing,label}` for connected diagrams, so arrows remain bound
101
+ when nodes move. Use native shapes and SVG for custom vector details. Keep labels as
102
+ native text so they remain editable and measurable.
103
+
104
+ Use `canvas.place {kind,id,x,y,w,h,name,parentId?}` to put a workspace object (document,
105
+ deck, sheet, board, ticket, project, site or file) on the canvas as a live card. With
106
+ `parentId`, `x/y` are relative to that frame, like `canvas.create-node`. See
107
+ [project-plans.md](project-plans.md).
108
+ Use `canvas.list-sources`/`canvas.place-source` for library assets. A native image
109
+ node accepts `src`. Upload local media using `canvas-upload-image` (discover its current
110
+ schema), then place its returned URL. Inspect the actual asset before using it. Prefer
111
+ provided, reusable or purpose-made imagery to random URL placeholders. Label temporary
112
+ assets in the handoff and replace them when final assets are part of the request.
113
+
114
+ Current headless rendering can resolve images, including workspace media. A blank image
115
+ can indicate fetch, decoding or budget failure; check its source and inspect a live view.
116
+ Do not treat a stored `src` as proof that the intended crop rendered.
117
+
118
+ For an interactive HTML prototype, use `canvas-add-artifact` with self-contained HTML.
119
+ Choose it when interactions are the deliverable; use native nodes for editable visual
120
+ design. Verify the returned artifact in a browser at realistic viewport sizes.
121
+
122
+ ## Review and scoped perception
123
+
124
+ ```sh
125
+ beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame-id>"]}}' > /tmp/review.json
126
+ beehaven call canvas-perceive '{"compositionId":"<id>","includePixels":true,"fitNodeIds":["<frame-id>"],"pixelScale":1}' > /tmp/perceive.json
127
+ ```
128
+
129
+ Use the bundled `review.mjs` to decode the PNG and keep base64 out of the conversation.
130
+ It does not edit the composition. Read `previewError`, audit findings, font fallbacks and
131
+ `typography.omitted`; missing checks are not passes. Review each artboard individually.
132
+ For a long page, also request `canvas-perceive` with a `region:{x,y,w,h}` detail crop.
133
+ The preview has a pixel-size cap; increasing `pixelScale` cannot bypass it.
134
+
135
+ `fitNodeIds`/`region` plus pixels uses an agent-owned persisted view (`live:false` is
136
+ expected). Bare perception can return the user's live view. Use `format:"json"` for a
137
+ full inventory: text format limits the list, and text previews are truncated. Perception
138
+ is not a lossless scene serializer. Use `composition-detail {id,includeScene:true}` when
139
+ you need the full scene and its revision.
140
+
141
+ ## Whole-scene writes and MCP
142
+
143
+ CLI and MCP can both create or replace a whole scene. For a scripted large build,
144
+ `composition-patch {id,expectedCanvasNodesRev,patch:{canvasNodes:[...]}}` is available.
145
+ Read the authoritative scene first, preserve unrelated nodes and fields, and pass its
146
+ revision. On a conflict, re-read and merge your intended changes. Never rebuild a scene
147
+ from the abbreviated perceive report or remove a revision check just to force a write.
148
+
149
+ MCP's artifact tools are split by operation:
150
+
151
+ - `clearly_catalog {type:"composition"}` exposes the current schemas.
152
+ - `clearly_read` returns nodes and `canvasNodesRev`.
153
+ - `clearly_write` creates a composition or replaces its full node array; use
154
+ `expectedRev` from the read for replacement.
155
+ - `clearly_edit` patches existing nodes with `changes:{ids,patch}`.
156
+
157
+ Canvas action dispatch and targeted pixels use the CLI. Do not invent
158
+ `clearly_canvas_act`/`clearly_canvas_perceive` tools. If only MCP is available, use the
159
+ artifact schemas for supported edits and state that pixel review remains unverified.
160
+ Do not claim the design was visually checked from scene JSON alone.
@@ -0,0 +1,32 @@
1
+ # Canvas updates and handoff
2
+
3
+ Use communication blocks when the canvas is the user's working surface. Keep them
4
+ outside the final artboards and reuse a status block instead of adding one per tool call.
5
+
6
+ ```sh
7
+ beehaven call canvas-add-claude-comm '{"compositionId":"<id>","kind":"status","title":"Designing the campaign","active":true,"statusLines":["Establishing the visual direction"]}'
8
+ ```
9
+
10
+ Capture `blockId`, then pass it on subsequent updates. Close with `active:false`.
11
+ Use `kind:"build-result"` for a code build (with the actual URL or install command),
12
+ `kind:"result"` for design work, and `kind:"narration"` for a concise explanation.
13
+ Discover optional fields through `canvas:actions`.
14
+
15
+ `canvas.add-diff {diff,title,frame:true}` makes a spatial code review from a real diff.
16
+ Pass it through a JSON file. Keep code review separate from delivered artwork.
17
+
18
+ `canvas-add-brand-snapshot` displays a brand reference. It does not change that brand.
19
+ Use real brand assets; do not redraw a company's logo from memory. A request to design
20
+ an artifact does not inherently authorize changing shared brand settings.
21
+
22
+ Use normal chat for a question or approval when one is actually required. Do not invent
23
+ `canvas-add-claude-template` or `brand-propose-update` actions.
24
+
25
+ ```sh
26
+ beehaven call agent-nav-open '{"kind":"composition","id":"<id>"}'
27
+ ```
28
+
29
+ This records your location and can navigate connected clients. `delivered:0` means no
30
+ client received navigation; it does not invalidate the stored composition. Give the
31
+ user the title and composition link on the intended environment. Do not claim a browser
32
+ opened merely because a message was delivered.
@@ -0,0 +1,84 @@
1
+ # Composition choices by deliverable
2
+
3
+ Numbers below are useful starting ranges in canvas pixels, not fixed standards. Scale
4
+ them for the actual viewing distance, content and brand. Never impose a universal
5
+ “premium” style on every brief.
6
+
7
+ ## Choose structure by the content
8
+
9
+ | Content asks for | A structure worth trying | What makes it specific |
10
+ |---|---|---|
11
+ | One strong claim | Large statement and one piece of evidence | Size contrast and the evidence's visual form |
12
+ | A product or object | Deliberately cropped focal image with quiet annotations | Crop, lighting, material and scale |
13
+ | Before/after or alternatives | Matched comparison fields, possibly 50/50 | Shared scale, baseline and meaningful differences |
14
+ | A story over time | Sequence, timeline or repeated visual motif | Change across panels, controlled rhythm |
15
+ | Many related facts | Editorial grid or carefully structured table | Grouping, direct labels, alignment and legibility |
16
+ | A process/system | Spatial diagram with meaningful relationships | Direction, proximity, grouping and labeled connectors |
17
+
18
+ Sketch visual mass before detail. Locate the primary read, supporting read and quiet
19
+ space. Use asymmetry for tension when it helps, and symmetry for comparison or calm.
20
+ Empty space should reinforce grouping and emphasis; excessive empty space can weaken
21
+ information just as crowding can.
22
+
23
+ ## Poster, cover or campaign
24
+
25
+ For 1080×1350, try 64–96 margins, 96–180 display, 24–36 supporting type, 18–22 detail.
26
+ View a 150–250px-wide thumbnail to judge the primary read, then inspect the full image.
27
+ The idea might be typographic, photographic, illustrative or mixed. Make the central
28
+ visual recognisable and relevant. A repeated abstract ornament is not automatically
29
+ a brand asset.
30
+
31
+ A campaign shares a typographic voice, colour logic, grid and image treatment. Vary
32
+ the focal scale or arrangement to suit each message. Design other aspect ratios as
33
+ adaptations, moving hierarchy and crop; do not stretch or blindly crop the first board.
34
+ Check the target platform's current overlays/safe zones when final publication matters.
35
+
36
+ ## Presentation
37
+
38
+ For 1920×1080, try 96–120 margins, 64–96 headlines, 28–36 main text and 20–24 labels.
39
+ Write the argument as the title. Choose evidence the audience can grasp at presentation
40
+ distance. Move supporting detail into notes or a document when it obscures the argument.
41
+
42
+ Plan the whole sequence: opening promise, evidence, explanation and conclusion. Vary
43
+ the visual function: opening image, decisive statistic, diagram, comparison, detail,
44
+ closing. Keep title baselines, folios and text roles consistent. Do not force an equal
45
+ number of cards onto every slide. Review a contact sheet for rhythm and each slide for fit.
46
+
47
+ ## Page or app mockup
48
+
49
+ For 1440 desktop, try 80–112 outer margins, 64–88 hero headline and 18–24 supporting
50
+ text. For 390 mobile, try 20–24 margins, 36–48 headline and 16–18 body, then measure.
51
+ These values are starting points, not a substitute for testing actual copy.
52
+
53
+ Design a complete reading path: navigation, proposition, evidence, next action and
54
+ relevant follow-up content. Use an actual product visual where the page claims product
55
+ value. Distinguish interface controls from decorative editorial elements. Include useful
56
+ empty/error/loading states when they belong to the requested UI.
57
+
58
+ Mobile needs its own content order, wrapping, crop and touch affordances. A static
59
+ desktop artboard scaled down is not a mobile design. If delivering implementation,
60
+ verify keyboard access and responsive behavior in the real browser as well.
61
+
62
+ ## Editorial and information design
63
+
64
+ Begin with reading order and density. Align related labels and values, give numbers
65
+ a consistent unit/precision, and use direct chart annotations. Let the amount of content
66
+ determine the number of columns. Body measure often works around 45–75 characters;
67
+ captions and labels can be shorter. Check actual line breaks rather than calculating
68
+ character count alone.
69
+
70
+ Use rules and backgrounds where they clarify grouping. Remove boxes that merely frame
71
+ every paragraph. A chart, map or diagram should carry information, with honest scales
72
+ and visible sources when supplied. Never invent evidence to make a layout look complete.
73
+
74
+ ## A useful critique names the fix
75
+
76
+ Weak: “Make it more polished.”
77
+
78
+ Useful: “At thumbnail size the orange badge wins before the title. Reduce its area and
79
+ move it beside the caption. The product image needs a larger crop to explain the claim.
80
+ The second paragraph wraps into the footer; shorten its measure only after reserving
81
+ more vertical space, or edit the copy.”
82
+
83
+ Judge the artifact against its audience and brief. A quiet museum catalogue, dense
84
+ operations dashboard and vivid music poster should not converge on the same aesthetic.
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /** Example of a reusable native-node builder. Writes JSON only; it never calls a workspace. */
3
+ import { writeFileSync } from 'node:fs';
4
+
5
+ const [compositionId, output = '/tmp/fieldwork-build.json'] = process.argv.slice(2);
6
+ if (!compositionId) throw new Error('Usage: node example.mjs <composition-id> [output.json]');
7
+ const grid = { w: 1440, h: 900, margin: 88, gutter: 80 };
8
+ const ink = '#172824', paper = '#F2F0E7', orange = '#C84A2E', muted = '#58685E';
9
+ const batch = [];
10
+ const op = (action, args) => batch.push({ action, args });
11
+ const frame = (name, index, fill) => op('frame.create', {
12
+ id: name, name, x: index * (grid.w + grid.gutter), y: 0, w: grid.w, h: grid.h, fill,
13
+ });
14
+ const node = (parentId, name, type, x, y, w, h, props) => op('canvas.create-node', {
15
+ id: `${parentId}-${name}`, name: `${parentId}-${name}`, parentId, type, x, y, w, h, ...props,
16
+ });
17
+ const text = (p, name, copy, x, y, w, size = 24, fill = ink, weight = 400, extra = {}) =>
18
+ node(p, name, 'text', x, y, w, undefined, { text: copy, size, family: 'Inter', fontWeight: weight,
19
+ lineHeight: 1.12, fill, ...extra });
20
+ const rect = (p, name, x, y, w, h, fill, extra = {}) => node(p, name, 'rect', x, y, w, h, { fill, ...extra });
21
+ const circle = (p, name, x, y, d, fill, extra = {}) => node(p, name, 'ellipse', x, y, d, d, { fill, ...extra });
22
+ const rule = (p, y, color) => rect(p, `rule-${y}`, grid.margin, y, grid.w - grid.margin * 2, 1, color);
23
+ const masthead = (p, index, color) => {
24
+ text(p, 'brand', 'FIELDWORK', grid.margin, 52, 400, 20, color, 700, { letterSpacing: 2 });
25
+ text(p, 'series', 'DESIGN NOTES / VOL. 01', 912, 54, 440, 14, color, 400, { letterSpacing: 1 });
26
+ text(p, 'folio', 'STUDIO PRACTICE', grid.margin, 828, 900, 14, color, 400, { letterSpacing: 1 });
27
+ text(p, 'page', String(index).padStart(2, '0'), 1304, 828, 48, 14, color, 400, { align: 'right' });
28
+ };
29
+
30
+ // Brief: show how one simple visual idea becomes a coherent editorial system.
31
+ // Hierarchy: statement → geometric proof → compact practical explanation.
32
+ // Motif: two fields meet to make a third; repeated with a different function on each slide.
33
+ const cover = 'fieldwork-01';
34
+ frame(cover, 0, paper); masthead(cover, 1, ink); rule(cover, 104, ink);
35
+ text(cover, 'eyebrow', 'A NOTE ON MAKING', 88, 168, 560, 16, orange, 700, { letterSpacing: 2 });
36
+ text(cover, 'title', 'Make room\nfor the idea.', 80, 228, 810, 108, ink, 700, { letterSpacing: -4, lineHeight: 1.02 });
37
+ text(cover, 'intro', 'Good design begins with a point of view.\nThe system gives it room to grow.', 88, 538, 740, 25, muted);
38
+ circle(cover, 'disc', 977, 233, 350, orange);
39
+ rect(cover, 'field', 916, 385, 270, 270, ink);
40
+ rect(cover, 'intersection', 977, 385, 209, 198, paper, { radius: 0 });
41
+ text(cover, 'motif-caption', '01 / TWO FIELDS, ONE IDEA', 920, 696, 420, 14, muted, 400, { letterSpacing: 1 });
42
+ rule(cover, 782, ink);
43
+
44
+ const method = 'fieldwork-02';
45
+ frame(method, 1, ink); masthead(method, 2, paper); rule(method, 104, '#607369');
46
+ text(method, 'title', 'One idea.\nMany expressions.', 80, 164, 1120, 92, paper, 700, { letterSpacing: -3, lineHeight: 1.04 });
47
+ text(method, 'intro', 'Keep the visual logic.\nChange what the content needs.', 952, 380, 400, 24, '#C7CFC4');
48
+ const concepts = [
49
+ { x: 88, title: '01 / EMPHASIS', body: 'Let one thing lead.', kind: 0 },
50
+ { x: 520, title: '02 / RELATIONSHIP', body: 'Make the connection visible.', kind: 1 },
51
+ { x: 952, title: '03 / RHYTHM', body: 'Repeat with a reason.', kind: 2 },
52
+ ];
53
+ for (const c of concepts) {
54
+ const y = 518;
55
+ if (c.kind === 0) { circle(method, 'emphasis', c.x, y, 152, orange); circle(method, 'quiet', c.x + 192, y + 92, 60, paper); }
56
+ if (c.kind === 1) { rect(method, 'relation-field', c.x, y + 20, 148, 120, paper); circle(method, 'relation-disc', c.x + 80, y, 152, orange); }
57
+ if (c.kind === 2) for (let i = 0; i < 4; i++) rect(method, `rhythm-${i}`, c.x + i * 78, y + i * 20, 48, 152 - i * 20, i === 3 ? orange : paper);
58
+ text(method, `label-${c.kind}`, c.title, c.x, 714, 380, 16, paper, 700, { letterSpacing: 1 });
59
+ text(method, `body-${c.kind}`, c.body, c.x, 748, 380, 20, '#C7CFC4');
60
+ }
61
+
62
+ const detail = 'fieldwork-03';
63
+ frame(detail, 2, orange); masthead(detail, 3, paper); rule(detail, 104, paper);
64
+ text(detail, 'title', 'The work is\nin the details.', 80, 168, 1100, 106, paper, 700, { letterSpacing: -4, lineHeight: 1.03 });
65
+ text(detail, 'intro', 'A composition is finished when the details\nserve the idea — at every scale.', 88, 436, 850, 28, paper);
66
+ rule(detail, 566, paper);
67
+ const details = [
68
+ ['TYPE', 'Measure the words.\nGive them a voice.'],
69
+ ['SPACE', 'Group what belongs.\nLet the rest breathe.'],
70
+ ['REVIEW', 'Look at the result.\nMake the next decision.'],
71
+ ];
72
+ details.forEach(([label, body], i) => {
73
+ const x = grid.margin + i * 432;
74
+ text(detail, `label-${i}`, label, x, 610, 380, 16, paper, 700, { letterSpacing: 2 });
75
+ text(detail, `body-${i}`, body, x, 662, 390, 28, paper);
76
+ });
77
+ writeFileSync(output, JSON.stringify({ compositionId, batch }, null, 2) + '\n');
78
+ process.stdout.write(`${output}: ${batch.length} operations\n`);
@@ -0,0 +1,156 @@
1
+ # Project plans on a canvas — the real work, laid out
2
+
3
+ Use this when asked to plan, kick off, roadmap or organise work on a canvas: "make a
4
+ project plan", "lay out the launch", "put the roadmap on a board". The deliverable is
5
+ a composition where every card is a **live workspace object** — the project, its board
6
+ and tickets, the PRD, the checklist sheet, the review deck, the site — arranged so a
7
+ person understands the plan at a glance and can open any piece of it.
8
+
9
+ Do not draw a board out of rectangles or paste a document's text into a text node.
10
+ `canvas.place` stores `{refKind, refId}`, so the card *is* the row: edit the ticket
11
+ anywhere and the card updates; open the card and you are in the real editor.
12
+
13
+ ## 1. Anchor the plan in a project
14
+
15
+ ```sh
16
+ beehaven call project-list '{"limit":50}' # reuse the project that owns the work
17
+ beehaven call project-create '{"title":"Launch — Q4","description":"…"}'
18
+ beehaven call board-list '{"projectId":"<project>"}' # a project usually has a default board
19
+ ```
20
+
21
+ File everything you create into that project (`projectId`). Unfiled agent work is
22
+ private to you; filing is how the team sees it.
23
+
24
+ ## 2. Create the objects — each with its own verb
25
+
26
+ | Primitive | Create | Exact-shape traps |
27
+ |---|---|---|
28
+ | Document (PRD, brief) | `document-create {title, markdown, projectId}` | `markdown` or `html`, **never** `content` (dropped silently → an empty, unsearchable doc). `ticketId` + `artifactKind:"prd"` files it on a ticket. |
29
+ | Sheet (checklist, budget) | `sheet-create {title, columns:[…], rows:[[…]], projectId}` | `rows` are arrays of cells; `columns` is the header row. |
30
+ | Slides (review deck) | `deck-create {title, projectId, slides:[{title, bullets:[…]}]}` | Build the whole deck in one call; `layout:"title"` for an opener. |
31
+ | Board | `board-create {projectId, title, columns?}` | Needs `projectId` (or `personal:true`). Cards on a board **are tickets**. |
32
+ | Ticket | `ticket-create {projectId, boardId, title, body, issueType, status}` | `issueType`, not `type`; `body`, not `description` — both fail silently. |
33
+ | Site (one page) | `compose-upload-init` → `file-teleport-inline` (`composition:true`, key `compositions/<id>/build/index.html`) → `compose-upload-finalize` | All three steps, or the site is empty. For a page that only lives on this canvas, `canvas.add-artifact {html}` is simpler. |
34
+
35
+ Keep every returned `id`. Tickets are placed by **row id**, not by key.
36
+
37
+ ## 3. Lay it out with a builder, then place everything in one batch
38
+
39
+ ```sh
40
+ beehaven call composition-create '{"title":"Launch — Q4 plan","projectId":"<project>"}'
41
+ ```
42
+
43
+ Think in reading order. A header states the goal. Then two lanes: *why and what* (the
44
+ project, PRD, tickets, board) and *how and ship* (the checklist sheet, review deck, site).
45
+ Connect only cards that depend on each other. A plan is a composition like any other, so
46
+ it meets the studio bar in SKILL.md: one grid, a few type roles, one left edge, and a
47
+ `canvas.review` verdict before you hand it over.
48
+
49
+ Build it **inside one frame** named `plan`. That frame is what `canvas.review` inspects,
50
+ what a person moves as one piece, and what gives the page its margin.
51
+
52
+ ```js
53
+ // plan.mjs — node plan.mjs <compositionId> > /tmp/plan.json
54
+ const [compositionId] = process.argv.slice(2);
55
+ const ids = { project: '…', prd: '…', board: '…', tickets: ['…', '…'], sheet: '…', deck: '…', site: '…' };
56
+ // The system: margin, gutter (room for an arrow label), three type roles, two inks, one ground.
57
+ const M = 96, G = 80;
58
+ const type = { title: { size: 72, fontWeight: 700, letterSpacing: -1.5 }, lead: { size: 24 }, label: { size: 16, fontWeight: 600 } }; // a plan is read zoomed out: labels ≥ 1% of the board
59
+ const ink = { strong: '#16150f', muted: '#6b6760' }; // both ≥ 4.5:1 on the ground below
60
+ const size = { card: [340, 440], wide: [720, 440], ticket: [340, 210], deck: [560, 315], site: [420, 440] };
61
+ const row1 = [size.card, size.card, size.card, size.wide].reduce((w, [cw]) => w + cw + G, -G);
62
+ const W = M * 2 + row1, LANE1 = 300, CARDS1 = LANE1 + 36, LANE2 = CARDS1 + 440 + G, CARDS2 = LANE2 + 36;
63
+ const H = CARDS2 + 440 + M;
64
+
65
+ const batch = [];
66
+ const add = (action, args) => batch.push({ action, args });
67
+ // Coordinates below are RELATIVE to the plan frame (parentId), for text and cards alike.
68
+ const text = (name, copy, x, y, w, role, fill = ink.strong) => add('canvas.create-node', { name, type: 'text', parentId: 'plan', x, y, w, text: copy, fill, ...type[role] });
69
+ const place = (kind, id, name, x, y, [w, h]) => add('canvas.place', { kind, id, name, parentId: 'plan', x, y, w, h });
70
+
71
+ add('frame.create', { name: 'plan', x: 0, y: 0, w: W, h: H, fill: '#f6f5f1' });
72
+ text('plan-title', 'Launch — Q4', M, M, 1400, 'title');
73
+ text('plan-goal', 'Ship the new onboarding to every workspace by Nov 30', M, M + 104, 1400, 'lead', ink.muted);
74
+
75
+ text('lane-1', 'Why and what', M, LANE1, 400, 'label', ink.muted);
76
+ let x = M;
77
+ place('project', ids.project, 'project', x, CARDS1, size.card); x += 340 + G;
78
+ place('document', ids.prd, 'prd', x, CARDS1, size.card); x += 340 + G;
79
+ place('ticket', ids.tickets[0], 'ticket-1', x, CARDS1, size.ticket);
80
+ place('ticket', ids.tickets[1], 'ticket-2', x, CARDS1 + 230, size.ticket); x += 340 + G;
81
+ place('board', ids.board, 'board', x, CARDS1, size.wide);
82
+
83
+ text('lane-2', 'How and ship', M, LANE2, 400, 'label', ink.muted);
84
+ x = M;
85
+ place('sheet', ids.sheet, 'checklist', x, CARDS2, size.wide); x += 720 + G;
86
+ place('deck', ids.deck, 'review-deck', x, CARDS2, size.deck); x += 560 + G;
87
+ place('site', ids.site, 'status-site', x, CARDS2, size.site);
88
+
89
+ // Connect NEIGHBOURS. An arrow is drawn straight through anything between its ends.
90
+ add('arrow.create', { from: 'prd', to: 'ticket-1', label: 'scopes' });
91
+ add('arrow.create', { from: 'review-deck', to: 'status-site', label: 'ships' });
92
+ console.log(JSON.stringify({ compositionId, batch }));
93
+ ```
94
+
95
+ ```sh
96
+ node plan.mjs <composition-id> > /tmp/plan.json
97
+ beehaven call canvas-act @/tmp/plan.json > /tmp/plan-result.json # check ok, results[].ok, skipped
98
+ ```
99
+
100
+ - With `parentId` (a frame id **or** name), `canvas.place` takes `x/y` relative to that
101
+ frame, exactly like `canvas.create-node`, and refuses a parent it cannot find. Results come
102
+ back in world coordinates.
103
+ - Give explicit sizes. Unsized cards are born at their kind's size: board and sheet 720×440,
104
+ deck 560×315, document/project 340×440, ticket 340×210, site 420×440. A board shows up to
105
+ five lanes; a sheet its first rows.
106
+ - A multi-page document places as a frame of pages; its result `id` is that frame.
107
+ - `arrow.create {from,to}` resolves ids **or** layer names, and refuses one that matches
108
+ nothing. Read the error rather than retrying; the name you placed with is the handle.
109
+ - Leave ~80px between connected cards so the label fits.
110
+
111
+ ## 4. Review it like any composition
112
+
113
+ ```sh
114
+ node <skill-directory>/review.mjs <composition-id> plan /tmp/plan-review/plan
115
+ ```
116
+
117
+ Work until the verdict is `clean` or `look` (see the studio bar in SKILL.md), then open the
118
+ PNG. Check that each card shows its content:
119
+
120
+ | Card | Should show | If not |
121
+ |---|---|---|
122
+ | Board | Lanes named after its columns, ticket cards in each | Empty lanes → no tickets on that board (check `boardId`) |
123
+ | Sheet | A table: header row, then rows | One run-on line → an older worker; re-check after deploy |
124
+ | Deck | Numbered slide titles | "Untitled slide" → slides were created without titles |
125
+ | Any | Its content | "no longer exists" → wrong id or kind; "Loading…" in a headless render → unresolved kind |
126
+
127
+ Then judge it as a page: does the goal win at thumbnail size, do the lanes read as two
128
+ groups, does every card share the left edge and gutter? Fix the layout (cramped lanes,
129
+ unreadable arrows, a stray card off the grid) before you finish.
130
+
131
+ ## 5. You are visible while you build
132
+
133
+ When someone has the composition open, you appear in its collaborator stack with your
134
+ name, your face and a live activity line ("Placing board · Q4 roadmap"). Your pointer
135
+ travels to each thing a step creates or edits, a click plays where it lands, and the
136
+ new node is briefly outlined in your colour. While someone is watching, each step is
137
+ paced by a fraction of a second (capped per call) so the build streams in under the
138
+ pointer. Nobody watching means no pacing.
139
+
140
+ - **Order the batch in reading order** — header, then each lane left to right — so the
141
+ build reads as the plan unfolding rather than cards popping up at random.
142
+ - **Name every layer** (`name`). The activity line uses titles and names.
143
+ - Prefer **one batch per coherent section** over one call per node or one enormous
144
+ call: each call's pacing is capped, and a section is what a watcher follows.
145
+ - Presence only ever reflects real steps. Do not issue no-op edits to "show activity".
146
+ - Presence is not a status report. For a long task, keep one `canvas-add-claude-comm`
147
+ status block updated as well (see [communications.md](communications.md)).
148
+
149
+ ## 6. Hand it over
150
+
151
+ ```sh
152
+ beehaven call agent-nav-open '{"kind":"composition","id":"<composition-id>"}'
153
+ ```
154
+
155
+ Report the composition title and link, what each card is, and anything left to do
156
+ (unfilled tickets, placeholder copy). Everything should be filed in the project.
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ /** Save review pixels outside the model context. No shell interpolation, no workspace mutations. */
3
+ import { execFileSync } from 'node:child_process';
4
+ import { mkdirSync, writeFileSync } from 'node:fs';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ export function rpc(action, payload) {
9
+ const stdout = execFileSync('beehaven', ['call', action, '-'], {
10
+ input: JSON.stringify(payload), encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
11
+ stdio: ['pipe', 'pipe', 'pipe'], timeout: 120_000,
12
+ });
13
+ const result = JSON.parse(stdout);
14
+ if (result.ok === false || result.error || result.results?.some((r) => r.ok === false)) {
15
+ throw new Error(result.error || result.results.find((r) => !r.ok)?.error || 'Canvas request failed');
16
+ }
17
+ return result;
18
+ }
19
+
20
+ export function saveReview(report, prefix) {
21
+ const preview = report.preview;
22
+ if (!preview?.dataUrl?.startsWith('data:image/png;base64,')) {
23
+ throw new Error(report.previewError || 'No PNG returned. Visual verification is incomplete.');
24
+ }
25
+ const bytes = Buffer.from(preview.dataUrl.slice('data:image/png;base64,'.length), 'base64');
26
+ if (!bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) throw new Error('Invalid PNG response');
27
+ const base = resolve(prefix);
28
+ mkdirSync(dirname(base), { recursive: true });
29
+ const { dataUrl, url, ...metadata } = preview;
30
+ const saved = { ...report, preview: { ...metadata, path: `${base}.png` } };
31
+ writeFileSync(`${base}.png`, bytes);
32
+ writeFileSync(`${base}.json`, JSON.stringify(saved, null, 2) + '\n');
33
+ const craft = report.craft?.findings ?? [];
34
+ return { png: `${base}.png`, report: `${base}.json`,
35
+ // `fix` = a probable defect remains: fix it, or pass its check/id in `ignore` with a reason.
36
+ // `look` = only things worth a second look. `clean` = nothing mechanical found — NOT "good".
37
+ verdict: report.verdict ?? 'unknown', blocking: report.blocking,
38
+ craft: craft.map((f) => `${f.severity} ${f.check}: ${f.detail}`),
39
+ nodes: report.scope?.nodeCount, findings: report.audit?.total, textChecked: report.typography?.checked,
40
+ textOmitted: report.typography?.omitted, visualReviewRequired: true,
41
+ note: report.verdict === 'fix'
42
+ ? 'Fix every warn (or justify it with ignore), then review again. Then open the PNG and critique it.'
43
+ : 'Open the PNG with your image-viewing tool, critique it against the studio bar, revise and render again.' };
44
+ }
45
+
46
+ export function main(argv = process.argv.slice(2)) {
47
+ const legacy = argv.includes('--perceive');
48
+ const args = argv.filter((a) => a !== '--perceive');
49
+ if (args.length !== 3) throw new Error('Usage: node review.mjs <compositionId> <frame-id-or-name> <output-prefix> [--perceive]\n--perceive: older workers, preview only; pass a frame ID.');
50
+ const [compositionId, id, prefix] = args;
51
+ let report;
52
+ if (legacy) {
53
+ const response = rpc('canvas-perceive', { compositionId, includePixels: true, fitNodeIds: [id], pixelScale: 1 });
54
+ const objects = response.room?.contents?.objects || [];
55
+ if (!objects.some((n) => n.id === id)) throw new Error('Frame ID absent from perception; refusing an unverified crop.');
56
+ report = { preview: response.room?.pixels, visualReviewRequired: true,
57
+ limitations: ['Preview-only compatibility mode. Text metrics and scoped audit were not run.'] };
58
+ } else {
59
+ const response = rpc('canvas-act', { compositionId, action: 'canvas.review', args: { ids: [id], pixelScale: 1 } });
60
+ report = response.results?.[0]?.value;
61
+ if (!report?.scope) throw new Error('canvas.review unavailable. Check canvas-catalog; use --perceive for an explicitly preview-only check on an older worker.');
62
+ }
63
+ process.stdout.write(JSON.stringify(saveReview(report, prefix), null, 2) + '\n');
64
+ }
65
+
66
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
67
+ try { main(); } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
68
+ }