beehaven 0.8.29 → 0.8.33

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 (45) hide show
  1. package/dist/agents-md-text.js +64 -0
  2. package/dist/browser-login.js +14 -4
  3. package/dist/build-channel.js +2 -2
  4. package/dist/claude-bridge.js +12 -9
  5. package/dist/clearly.js +4 -3
  6. package/dist/cli.js +26 -7
  7. package/dist/commands/agents-md.js +118 -0
  8. package/dist/commands/daemon-cmd.js +2 -0
  9. package/dist/commands/help.js +21 -0
  10. package/dist/commands/self-update.js +32 -5
  11. package/dist/commands/workspace.js +3 -3
  12. package/dist/daemon-client.js +10 -6
  13. package/dist/daemon-entry.js +1 -0
  14. package/dist/daemon-process.js +125 -0
  15. package/dist/daemon.js +131 -62
  16. package/dist/dashboard.js +118 -6
  17. package/dist/ipc-auth.js +3 -1
  18. package/dist/ipc-client.js +7 -6
  19. package/dist/ipc-path.js +92 -0
  20. package/dist/ipc-server.js +33 -33
  21. package/dist/npm-update.js +138 -0
  22. package/dist/setup-service.js +9 -4
  23. package/dist/verb-surface.js +1 -0
  24. package/dist/watcher.js +9 -23
  25. package/dist/windows-hide.js +77 -0
  26. package/package.json +1 -1
  27. package/scripts/sync-skills.mjs +6 -0
  28. package/skills/agent-doodle-space/SKILL.md +1 -1
  29. package/skills/agent-home/SKILL.md +1 -1
  30. package/skills/brand-identity/SKILL.md +22 -5
  31. package/skills/clearly-agent/SKILL.md +11 -4
  32. package/skills/clearly-canvas/SKILL.md +31 -5
  33. package/skills/clearly-canvas/authoring.md +40 -12
  34. package/skills/clearly-canvas/example.mjs +72 -69
  35. package/skills/clearly-docs/SKILL.md +151 -0
  36. package/skills/clearly-init/SKILL.md +135 -0
  37. package/skills/clearly-workflows/SKILL.md +90 -0
  38. package/skills/clearly-workspace/SKILL.md +1 -1
  39. package/skills/codebase-map/SKILL.md +1 -1
  40. package/skills/design-craft/SKILL.md +9 -6
  41. package/skills/index.json +45 -10
  42. package/skills/layout-systems/SKILL.md +14 -11
  43. package/skills/pair-on-canvas/SKILL.md +1 -1
  44. package/skills/sticker-pack/SKILL.md +132 -0
  45. package/skills/visualize/SKILL.md +188 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clearly-agent
3
- description: Work in a Clearly workspace AS an agent — sign in with an identity, resume a session, read your brief (what changed since you were last here, your recents, your unfiled drafts), and understand whose work you are creating. TRIGGER on the first `beehaven` call of any session, on "Not an agent" / "agent login" refusals, and when the user asks "who am I acting as", "what was I doing", "what have I made", "why can't the agent see this", "why is my work invisible", or "run this as a different agent". Read this BEFORE clearly-canvas or clearly-workspace — both need an identity and neither will work without one.
3
+ description: Needs a terminal with the beehaven CLI (Claude Code, Codex, Gemini CLI); in a chat with only the Clearly connector, say so and use the clearly_* tools instead. Work in a Clearly workspace AS an agent — sign in with an identity, resume a session, read your brief (what changed since you were last here, your recents, your unfiled drafts), and understand whose work you are creating. TRIGGER on the first `beehaven` call of any session, on "Not an agent" / "agent login" refusals, and when the user asks "who am I acting as", "what was I doing", "what have I made", "why can't the agent see this", "why is my work invisible", or "run this as a different agent". Read this BEFORE clearly-canvas or clearly-workspace — both need an identity and neither will work without one.
4
4
  ---
5
5
 
6
6
  # clearly-agent — who you are, and what that makes your work
@@ -216,6 +216,11 @@ governance. Each resource's delete cell is `none`, `own`, or `all`; `own` means
216
216
  `created_by: agent:<your-id>`. Missing or legacy author data is not yours. Permanent deletion is
217
217
  human-only; agent deletes use a recoverable archive, tombstone, or checkpoint.
218
218
 
219
+ Removing canvas layers is an **update**, including `canvas.delete`, block removal, and placed
220
+ design layers. You may remove layers from any visible canvas within your edit scope, regardless
221
+ of its author. The server requires an undo checkpoint first. Deleting or archiving the whole
222
+ canvas still follows delete-own/delete-all (CAN2-128).
223
+
219
224
  Only agents whose owner is currently a workspace owner or admin can use admin or delete-all. The
220
225
  server checks this on every call: if your owner is demoted, your scope drops to write/delete-own.
221
226
  After an owner is promoted again, a human must explicitly re-grant the higher scope.
@@ -228,15 +233,17 @@ owner/admin-owned agent can be granted it. Check yours before a destructive step
228
233
  beehaven call agent-capabilities-get '{}' # { scope, matrix, summary }
229
234
  ```
230
235
 
231
- ⚠ `permission_scope: 'write'` lets you delete only your own work. A refusal on another author's
232
- item is the contract working, not a bug. ⚠ You cannot change permissions — yours or another agent's;
236
+ ⚠ `permission_scope: 'write'` lets you archive/remove only your own workspace items; editing layers
237
+ inside an editable canvas follows the update rule above. A permission refusal is the contract
238
+ working, not a bug. ⚠ You cannot change permissions — yours or another agent's;
233
239
  ask the owner.
234
240
 
235
241
  ## Traps that cost real time
236
242
 
237
243
  - ⚠ **`beehaven env` first.** `prod` and `staging` are different databases and `--env` does not
238
244
  exist on `call` — the daemon *is* the env. Every number is meaningless if you are on the wrong one.
239
- - ⚠ **A deploy does not reach a resident DO.** `beehaven stop && sleep 15 && beehaven start --headless`.
245
+ - ⚠ **Verify a deploy by the response's runtime version.** Allow propagation and retry read-only
246
+ checks; do not restart the user's daemon to force a resident DO onto a new version.
240
247
  - ⚠⚠ **Check the `as <agent>` on your first call.** An identity archived in this workspace still
241
248
  mints (identities are account-level), but the workspace refuses its session. Current CLIs say so
242
249
  (`⚠ Signed in, but this workspace refused …`, exit 1) and then refuse every call as that
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: clearly-canvas
3
3
  description: >-
4
+ Needs a terminal with the beehaven CLI (Claude Code, Codex, Gemini CLI); in a chat with only the Clearly connector, say so and use the clearly_* tools instead.
4
5
  Design and revise editable compositions on a Clearly canvas using the beehaven
5
6
  CLI: posters, campaigns, decks, page mockups, diagrams, spatial reviews, and
6
7
  project plans built from LIVE workspace objects (boards, tickets, documents,
@@ -19,6 +20,13 @@ The deliverable is a composition that communicates a specific idea at its intend
19
20
  viewing size, with editable, well-named layers. Successful RPCs prove that nodes were
20
21
  stored. A rendered image you have inspected is the evidence for visual quality.
21
22
 
23
+ Use the same working method as a designer in Figma: artboards, named editable layers,
24
+ text styles, reusable components, auto-layout and constraints; explicit responsive states;
25
+ then inspect the pixels and refine. Read `authoring.md` and discover the current catalog
26
+ before assuming an operation is missing. Typography defaults to Auto; use `140%` or `24px`
27
+ for explicit line heights. Keep unsupported capabilities and unverified rendering visible
28
+ in the handoff; a familiar workflow does not prove every Figma feature is implemented.
29
+
22
30
  For a small correction, keep the existing direction and make the smallest useful edit.
23
31
  For a new composition or substantial redesign, use the workflow below.
24
32
 
@@ -32,7 +40,7 @@ Load **`design-craft`** before any new visual work: grid, type scale, colour rol
32
40
  spacing, optical correction, the studio pass. It ships beside this skill; `layout-systems`
33
41
  covers multi-artboard grids. This skill covers the canvas mechanics.
34
42
 
35
- A composition is done when every requested artboard meets all four conditions:
43
+ A composition is done when every requested artboard meets all five conditions:
36
44
 
37
45
  1. **`canvas.review` verdict is `clean` or `look`.** `fix` means a probable defect
38
46
  remains: text colliding or covered, contrast under WCAG (4.5:1 body, 3:1 display),
@@ -47,6 +55,12 @@ A composition is done when every requested artboard meets all four conditions:
47
55
  one accent, orphans, consistency.
48
56
  4. **The first read works at delivery size.** At thumbnail size, the thing that should
49
57
  win does win.
58
+ 5. **The document behaves as a design.** Inspect its frame tree: sections and UI containers
59
+ own their contents and background fills; rows, stacks and grids use live auto layout
60
+ with deliberate Hug/Fill/Fixed sizing. Resize a representative container and lengthen
61
+ its copy on a duplicate, then verify reflow and containment. Keep intentional artwork
62
+ positioned inside named artwork frames. Record these checks separately from visual QA;
63
+ `canvas.review` does not prove hierarchy or reflow.
50
64
 
51
65
  A new composition almost always needs at least one revision after its first render. If
52
66
  the pass finds nothing, look again at the thumbnail. If there is still nothing, say what
@@ -110,10 +124,19 @@ can read and edit artifact records, while canvas-specific actions use the CLI.
110
124
  beehaven env
111
125
  beehaven connect home
112
126
  beehaven agent login <name> --label "Design <artifact>" --client cli
113
- beehaven call canvas-perceive '{"compositionId":"<id>","format":"json"}'
127
+ beehaven call canvas-perceive '{"compositionId":"<id>","outline":true}'
114
128
  beehaven call canvas-catalog '{"query":"create-node"}'
115
129
  ```
116
130
 
131
+ Read a canvas the way you read a repository: `canvas-perceive {outline:true}` is `tree` (artboards,
132
+ sections, layer counts, first copy, EMPTY sections — a few hundred bytes); `{frame:"<id or name>"}`
133
+ opens one frame like a file (its layers indented, positions relative to their parent);
134
+ `{find:"Start free"}` is `grep` (copy, layer names or `#hex`, with each match's frame path). Use
135
+ `format:"json"` only when you need every field of every node. Every `canvas-act` write returns
136
+ `diagnostics` — a lint of the artboards it touched (text overflow, collisions, covered text,
137
+ contrast) — treat them like compiler errors and fix them in the next batch. `canvas.review` is still
138
+ the pixel check and the ticket gate.
139
+
117
140
  A canvas is planned like a git repository: its own plan board, a WORK TICKET for the request, and
118
141
  COMMITS — one ticket per coherent layer or section of a larger change.
119
142
  `design-start {title, brandId?}` makes a PRIVATE DRAFT canvas, bound to the brand, with its private
@@ -133,7 +156,7 @@ builds on the last and can be reviewed alone. Build ONE commit per canvas-act ba
133
156
  `canvas.review` of the artboard and carries
134
157
  `"ticket":{"id":"<key>","status":"done","resolution":"<what changed>"}` — it lands only if that
135
158
  review is clean or look; `result.ticket` says whether it did, and the ticket logs the canvas
136
- revision and the layers the commit changed. Auto-layout, gradients, shadows and SVG placeholders:
159
+ revision and the layers the commit changed. Auto-layout, gradients, shadows, drawing and placeholders:
137
160
  see "Technique" in [authoring.md](authoring.md).
138
161
 
139
162
  `composition-board-get {compositionId}` reads the plan back. `design-start` with a project, ticket or
@@ -195,8 +218,11 @@ hand-author hundreds of coordinates in a chat message.
195
218
  Check contrast on the actual ground. Use the user's real brand assets.
196
219
  - Choose relevant imagery, a meaningful diagram, a product view or a deliberate
197
220
  typographic treatment. Decorative blobs and random stock photos do not prove a
198
- product claim. Native SVG/paths suit diagrams and vector art; raster generation
199
- suits imagery when an image tool is available.
221
+ product claim. Draw marks, icons, illustrations and diagrams yourself from native
222
+ layers (shapes, vector.create pen paths, text, groups), the way a designer works in
223
+ Figma, so every part stays named and editable. Raster generation suits photographic
224
+ imagery when an image tool is available. Raw SVG (an svg node, or a generated SVG)
225
+ is for importing an existing file or a requested SVG, never the default.
200
226
  - Define repeated components once. Vary composition where the content changes.
201
227
  A deck of identical cards has little rhythm even when every card aligns.
202
228
  - Keep important copy and layers editable. Do not flatten the whole composition
@@ -10,8 +10,8 @@ Use a local builder and pass JSON through `@file` or stdin. Do not interpolate c
10
10
  SVG or code into shell strings. Here is a minimal native-node pattern; replace its
11
11
  visual decisions with the brief's system:
12
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
13
+ For desktop, mobile and dark examples, inspect [example.mjs](example.mjs).
14
+ It demonstrates nested flow containers, Hug/Fill sizing and framed artwork; it writes a payload
15
15
  without making remote calls. Run `node <skill-directory>/example.mjs <composition-id>`.
16
16
  Use its construction approach, not its fictional brand or aesthetic, for other briefs.
17
17
 
@@ -21,18 +21,22 @@ const compositionId = process.argv[2];
21
21
  const grid = { w: 1440, h: 900, margin: 88, gap: 24 };
22
22
  const color = { paper: '#F3F0E8', ink: '#172824', accent: '#CB482F' };
23
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 },
24
+ display: { family: 'Inter', size: 88, fontWeight: 700, lineHeight: '105%', letterSpacing: -2 },
25
+ body: { family: 'Inter', size: 24, fontWeight: 400, lineHeight: '140%' },
26
26
  };
27
27
  const batch = [];
28
28
  const add = (action, args) => batch.push({ action, args });
29
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,
30
+ add('frame.create', { name: 'hero-copy', parentId: '01-opening', w: 740, h: 400, fill: 'transparent' });
31
+ const text = (name, copy, role) => add('canvas.create-node', {
32
+ name, type: 'text', parentId: 'hero-copy', w: 740,
32
33
  text: copy, fill: color.ink, ...type[role],
33
34
  });
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');
35
+ text('opening-title', 'Ideas take shape here.', 'display');
36
+ text('opening-deck', 'An editable visual system for the next chapter.', 'body');
37
+ add('canvas.update-nodes', { ids: ['opening-title', 'opening-deck'], patch: { layoutSizingH: 'fill' } });
38
+ add('canvas.auto-layout', { id: 'hero-copy', direction: 'v', gap: 24, sizingH: 'fixed', sizingV: 'hug' });
39
+ add('canvas.auto-layout', { id: '01-opening', direction: 'v', gap: 64, padding: grid.margin, sizingH: 'fixed', sizingV: 'hug' });
36
40
  // Continue with the brief's central visual, supporting evidence and footer.
37
41
  writeFileSync('/tmp/canvas-build.json', JSON.stringify({ compositionId, batch }));
38
42
  ```
@@ -57,6 +61,13 @@ Keep executed payloads and returned-ID manifests separate from regenerated plans
57
61
 
58
62
  ## Node shape and coordinates
59
63
 
64
+ Choose ownership before coordinates: page → section → row/stack/grid → content. Put
65
+ the container's background, border and radius on its frame. Auto layout owns positions
66
+ in content flow; use Hug for content height and Fill for flexible width. Keep deliberate
67
+ overlays and illustrations inside artwork frames. A native editable layer alone does
68
+ not establish a usable document structure. Frames also make scoped perception cheaper:
69
+ read the outline and the section you are editing instead of a full scene dump.
70
+
60
71
  Fields are flat: `{name,type,x,y,w,h,text,size,family,fontWeight,fill,...}`.
61
72
  Use `text` for copy, `size` for type size, numeric `fontWeight`, and `w`/`h` for bounds.
62
73
  Create normalizes hex colours; raw stored scenes use RGBA arrays in 0..1.
@@ -90,6 +101,14 @@ not a missing node. Replacing `svg` clears and rebuilds its reference automatica
90
101
 
91
102
  ## Typography
92
103
 
104
+ Line height defaults to **Auto**, from the font itself. Use `lineHeight:"140%"` for a
105
+ percentage of font size or `lineHeight:"24px"` for fixed leading. Numeric values in old
106
+ scenes remain multipliers (`1.4` = `140%`); new designs should carry explicit units.
107
+ Changing font size scales % leading and leaves px leading fixed. Paragraph spacing is
108
+ `paragraphSpacing` in px after a hard newline, never a soft wrap. In a fixed-height
109
+ box, `vAlign:"middle"` centers the line boxes; `"bottom"` anchors their bottom. Do not
110
+ compensate with spaces, empty lines or hand-tuned y offsets.
111
+
93
112
  For prose, set `w` and omit `h` to enable auto-height. Omit both axes only for short
94
113
  single-line labels whose width should hug content. A fixed box sets that axis explicitly;
95
114
  it does not guarantee text will clip safely. Headless auto-height is an estimate from
@@ -99,7 +118,7 @@ then inspect the rendered result in the editor.
99
118
  ```json
100
119
  {"compositionId":"<id>","action":"canvas.measure-text","args":{
101
120
  "text":"Ideas take shape here.","family":"Inter","size":88,"fontWeight":700,
102
- "letterSpacing":-2,"lineHeight":1.05,"maxWidth":740}}
121
+ "letterSpacing":-2,"lineHeight":"105%","maxWidth":740}}
103
122
  ```
104
123
 
105
124
  Use measured `height`/`lineCount` to place the next block. Measure the exact copy, font,
@@ -121,16 +140,25 @@ deliverable requires them, with content order and hierarchy reconsidered for the
121
140
 
122
141
  ## Technique
123
142
 
143
+ - CONTAINERS: page sections, cards, sidebars and toolbars are named frames that own their content and background fill. Use live auto-layout for content rows, stacks and grids, with explicit Hug/Fill/Fixed sizing; position overlays and artwork deliberately within their owning frames. A rectangle behind sibling text is not a container. Verify the frame tree, resize a representative container and lengthen its copy on a duplicate before completion; record reflow checks separately from pixel review.
144
+ - FIGMA WORKFLOW: approach this canvas as a designer working in Figma. Read the brand and component library; compose artboards from named editable frames, text, shapes and component instances; use auto-layout, constraints, styles and variables; create deliberate desktop/mobile and light/dark states; inspect at delivery size, iterate, then review the final edit. Discover the current canvas-catalog before assuming a familiar Figma operation is unavailable. Its headlessActions identify what runs without an open editor; live-only actions need an editor. Report an unsupported capability accurately rather than inventing an operation or claiming universal feature parity.
145
+
146
+ - TYPOGRAPHY: lineHeight defaults to "auto", using the resolved font metrics. Author percentages as "140%" and absolute leading as "24px"; legacy numeric 1.4 still means 140%. Percentage leading follows font size; px leading stays fixed until the Scale tool scales the artwork. paragraphSpacing is px between hard-newline paragraphs, never soft wraps. For Auto width omit w/h; for Auto height set w and omit h; for a fixed box set w/h. Explicit layoutSizingH/V "fixed" or "fill" overrides legacy autoWidth/autoHeight. align is left|center|right|justify; vAlign is top|middle|bottom and applies to fixed-height boxes. Measure exact copy/font/weight/width using canvas.measure-text, then inspect the pixels; do not center text with spaces or arbitrary y offsets.
147
+
124
148
  The same rules the in-app assistant works to (canvas-catalog returns them as `technique`).
125
149
 
126
- - AUTO-LAYOUT: rows and stacks (nav links, button groups, card rows, form fields) are a frame whose children you create in reading order, then canvas.auto-layout { id, direction, gap, padding, align, justify, hug }. hug:false keeps the frame size, which a full-width bar needs with justify:"space-between"; set a child to layoutSizingH/V "fill" | "hug" | "fixed" with canvas.update-nodes.
150
+ - AUTO-LAYOUT: rows, stacks and grids are a frame whose children you create in reading order, then canvas.auto-layout { id, direction: "h" | "v" | "grid", gap, padding, align, justify, hug }. hug:false keeps the frame size; justify "space-between" | "space-around" | "space-evenly" are the Auto gaps; align:"baseline" lines up text in a row. Grids take columns, rows, columnGap, rowGap, columnSizes / rowSizes ("fill", "2fr", "hug", px). wrap:true flows into new rows; give Fill cards a minW so they wrap where you mean. Child settings go on the child via canvas.update-nodes: layoutSizingH/V "fill" | "hug" | "fixed", minW / maxW / minH / maxH, gridColumn / gridRow / gridColumnSpan, layoutPositioning:"absolute". The frame stays live: children created into it (parentId) flow in. Spacing variables: canvas.variable { op: "bind", ids, field, name }.
127
151
  - GRADIENTS: gradient { kind, angle, stops: [{ offset, color }] } replaces the solid fill. angle is the direction the colour travels: 0 = left to right, 90 = top to bottom, 45 = top-left to bottom-right (not CSS degrees). Use two or three stops from the brand palette, and keep text off a busy gradient or check its contrast in canvas.review.
128
152
  - SHADOWS AND BLUR: effects on any node. A soft card shadow is { type: "drop-shadow", x: 0, y: 4, blur: 16, spread: 0, color: "#0000001F" }; frosted glass is { type: "background-blur", blur: 16 }. Never fake a shadow with an extra rectangle.
129
- - SVG PLACEHOLDERS: until real imagery exists, draw it as an svg node at its final size in a muted tone from the brand palette, never a flat accent block, e.g. for a 400×240 slot svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 240"><rect width="400" height="240" rx="12" fill="#EDE6DF"/><circle cx="170" cy="96" r="14" fill="#CDBFB2"/><path d="M150 168l40-48 30 34 22-20 40 34z" fill="#CDBFB2"/></svg>' — <svg viewBox="0 0 W H"> always matches the node w and h. Put a label in its own text node and name the svg "<section> image placeholder". Hand-write only simple marks like this (placeholders, dividers, UI icons); logos, illustrations and finished artwork come from svg-make.
153
+ - DRAWING: build every mark, icon, illustration and diagram shape from native layers, as a designer does in Figma, never as one generated SVG file. Primitives are canvas.create-node rect and ellipse; curves are pen paths, vector.create { name, parentId, points: [{ x, y, hIn?: { x, y }, hOut?: { x, y } }], closed, fill or stroke, strokeWidth }, where hIn/hOut are absolute control points: use few anchors with smooth handles. It also takes d (SVG path data, converted to native editable points) or shape: petal|circle|polygon|star|arc|spiral with cx, cy and sizes; strokeCap/strokeJoin apply. Reshape in place with canvas.update-nodes { ids, patch: { points | d | shape } }. Draw a repeated part once: canvas.repeat { id, count, around | cx, cy } turns one petal into a flower; canvas.rotate { ids, angle, cx, cy }; canvas.boolean { ids, op: "union" | "subtract" | "intersect" | "exclude" } (exact, editable operands). Work back to front: ground, big silhouettes in flat brand colours, then features, then details and highlights; check the silhouette in canvas.review before detailing. Name layers by part ("fox/ear-left"), group each figure with canvas.group { ids, name } and set overlap with canvas.order { ids, op }. Keep one stroke weight per drawing. Photographic or painterly imagery is a placed raster. Raw SVG stays supported but is never the default: when an SVG is asked for, write the markup yourself as an svg node, canvas.create-node { type: "svg", name, w, h, svg } with <svg viewBox="0 0 W H"> matching w and h, or import existing markup the same way; never an SVG generation pipeline.
154
+ - COMPUTED GEOMETRY: shapes from math (radial petals, spirals, grids) are computed and drawn in ONE call: canvas-act { compositionId, script } runs JavaScript with geo.petal/circle/arc/star/polygon/spiral/smooth/rotate/radial/grid/bounds (degrees, 0 = 3 o'clock, clockwise) and a read-only scene list, then applies the { action, args } steps it returns. Never copy numbers from one tool result into another call.
155
+ - SHEETS: several concepts or variants go on canvas.sheet { name, title, subtitle, cards or count, cols, cardW, cardH }: a live wrap grid of card frames that own their captions, so rows never overlap. Draw into a card with parentId = its id.
156
+ - MEASURE: answer spacing, overlap and alignment questions with canvas.measure { frame } or { ids } and quote its numbers, never from memory or a preview.
157
+ - PLACEHOLDERS: until real imagery exists, draw a native placeholder at the final size in a muted brand tone, never a flat accent block: a rect with the slot radius plus a simple motif (an ellipse sun, a vector.create mountain), grouped with canvas.group and named "<section> image placeholder", with any label in its own text node.
130
158
  - SECTIONS: one frame per page section, named exactly as its ticket says, inside the page artboard. Build a section in one canvas-act batch that ends with canvas.review of the artboard and carries ticket: { id: "<its key>", status: "done", resolution: "<what changed>" }. The ticket moves only when that review is clean or look; result.ticket says whether it did. Then start the next section.
131
159
 
132
160
  Use `arrow.create {from,to,routing,label}` for connected diagrams, so arrows remain bound
133
- when nodes move. Use native shapes and SVG for custom vector details. Keep labels as
161
+ when nodes move. Draw custom vector details natively (shapes, vector.create pen paths); raw SVG only imports an existing file. Keep labels as
134
162
  native text so they remain editable and measurable.
135
163
 
136
164
  Use `canvas.place {kind,id,x,y,w,h,name,parentId?}` to put a workspace object (document,
@@ -1,78 +1,81 @@
1
1
  #!/usr/bin/env node
2
- /** Example of a reusable native-node builder. Writes JSON only; it never calls a workspace. */
2
+ /** Three editable page states. Writes JSON only; it never calls a workspace. */
3
3
  import { writeFileSync } from 'node:fs';
4
4
 
5
5
  const [compositionId, output = '/tmp/fieldwork-build.json'] = process.argv.slice(2);
6
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
7
  const batch = [];
10
8
  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');
9
+ const fillWidth = id => op('canvas.update-nodes', { ids: [id], patch: { layoutSizingH: 'fill' } });
10
+ // Page, section, row, card and button share this container contract.
11
+ function flow(id, parentId, box, layout, children) {
12
+ // Enable flow before adding children so reading order is creation order.
13
+ op('frame.create', { id, name: id, parentId, fill: 'transparent', ...box,
14
+ autoLayout: { direction: 'v', gap: 24, padding: 0, ...layout },
15
+ layoutSizingH: layout.sizingH ?? 'fixed', layoutSizingV: layout.sizingV ?? 'hug' });
16
+ children(id);
17
+ op('canvas.auto-layout', { id, direction: 'v', gap: 24, padding: 0,
18
+ sizingH: 'fixed', sizingV: 'hug', ...layout });
19
+ return id;
60
20
  }
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
- });
21
+ function text(parentId, name, copy, w, size, fill, extra = {}) {
22
+ const id = parentId + '/' + name;
23
+ op('canvas.create-node', { id, name, parentId, type: 'text', w, text: copy,
24
+ size, family: 'Inter', fill, lineHeight: 'auto', ...extra });
25
+ fillWidth(id);
26
+ return id;
27
+ }
28
+ function page(id, x, mobile = false, dark = false) {
29
+ const w = mobile ? 390 : 1440, pad = mobile ? 24 : 80, inner = w - pad * 2;
30
+ const ink = dark ? '#F2F0E7' : '#172824', ground = dark ? '#172824' : '#F2F0E7';
31
+ flow(id, undefined, { x, y: 0, w, h: 1000, fill: ground }, { gap: 64, padding: pad }, p => {
32
+ flow(p + '/navigation', p, { w: inner, h: 48 }, { direction: 'h', align: 'center', justify: 'space-between' }, nav => {
33
+ text(nav, 'brand', 'FIELDWORK', inner - 140, 20, ink, { fontWeight: 700 });
34
+ text(nav, 'edition', 'STUDIO NOTES', 140, 14, ink);
35
+ op('canvas.update-nodes', { ids: [nav + '/edition'], patch: { layoutSizingH: 'fixed' } });
36
+ });
37
+ fillWidth(p + '/navigation');
38
+ flow(p + '/hero', p, { w: inner, h: 420 }, { direction: mobile ? 'v' : 'h', gap: 48 }, hero => {
39
+ const copyWidth = mobile ? inner : 740;
40
+ flow(hero + '/copy', hero, { w: copyWidth, h: 400 }, { gap: 24 }, copy => {
41
+ text(copy, 'eyebrow', 'A NOTE ON MAKING', copyWidth, 14, ink, { letterSpacing: 1.5 });
42
+ text(copy, 'title', 'Make room for the idea.', copyWidth, mobile ? 48 : 88, ink,
43
+ { fontWeight: 700, letterSpacing: -2, lineHeight: '105%' });
44
+ text(copy, 'description', 'A shared direction. An editable system. Room for the next idea.', copyWidth, 22, ink, { lineHeight: '145%' });
45
+ flow(copy + '/action', copy, { w: 240, h: 56, fill: ink, radius: 8 },
46
+ { direction: 'h', padding: 16, align: 'center' }, button => {
47
+ text(button, 'label', 'Explore the studio', 208, 16, ground, { align: 'center' });
48
+ });
49
+ });
50
+ fillWidth(hero + '/copy');
51
+ // Art-directed coordinates belong inside an owning artwork frame.
52
+ const art = hero + '/artwork', aw = mobile ? inner : 400;
53
+ op('frame.create', { id: art, name: 'Artwork / two fields', parentId: hero, w: aw, h: 320, fill: '#C84A2E' });
54
+ op('canvas.create-node', { id: art + '/disc', name: 'Disc', type: 'ellipse', parentId: art, x: 24, y: 24, w: 220, h: 220, fill: '#F2F0E7' });
55
+ op('canvas.create-node', { id: art + '/field', name: 'Field', type: 'rect', parentId: art, x: 116, y: 136, w: 196, h: 160, fill: '#172824' });
56
+ });
57
+ fillWidth(p + '/hero');
58
+ flow(p + '/features', p, { w: inner, h: 220 }, { direction: 'grid', columns: mobile ? 1 : 3,
59
+ rows: mobile ? 3 : 1, rowSizes: mobile ? ['hug', 'hug', 'hug'] : ['hug'], gap: 24, rowGap: 24 }, grid => {
60
+ for (const [name, title, body] of [
61
+ ['type', 'Give ideas a voice.', 'Type scales and wraps with the available space.'],
62
+ ['space', 'Keep room to grow.', 'Content owns its height. Related layers move together.'],
63
+ ['review', 'Make the next edit.', 'Resize a card and extend its copy to check the system.'],
64
+ ]) {
65
+ const card = grid + '/' + name, cw = mobile ? inner : (inner - 48) / 3;
66
+ flow(card, grid, { w: cw, h: 180, fill: dark ? '#294337' : '#FFFFFF', radius: 12 }, { gap: 16, padding: 24 }, c => {
67
+ text(c, 'title', title, cw - 48, 24, ink, { fontWeight: 600 });
68
+ text(c, 'body', body, cw - 48, 18, ink, { lineHeight: '145%' });
69
+ });
70
+ fillWidth(card);
71
+ }
72
+ });
73
+ fillWidth(p + '/features');
74
+ text(p, 'footer', 'FIELDWORK / A SYSTEM READY FOR CHANGE', inner, 14, ink, { letterSpacing: 1 });
75
+ });
76
+ }
77
+ page('01-desktop', 0);
78
+ page('02-mobile', 1520, true);
79
+ page('03-dark', 1990, false, true);
77
80
  writeFileSync(output, JSON.stringify({ compositionId, batch }, null, 2) + '\n');
78
- process.stdout.write(`${output}: ${batch.length} operations\n`);
81
+ process.stdout.write(output + ': ' + batch.length + ' operations\n');
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: clearly-docs
3
+ description: >-
4
+ Read, search, edit and recover a company's documents in a Clearly workspace.
5
+ Use the seven-tool MCP filesystem for ordinary work; use the `beehaven` CLI
6
+ only for document history, diffs, and restore. Load this whenever the answer
7
+ depends on what the company has written down, or when you are about to write
8
+ something down for it. Triggers: "what do our docs say", "find the doc about
9
+ X", "update the spec", "write this up", "add it to the docs", "has this
10
+ changed", "what did they edit", "search the wiki", "company knowledge", "our
11
+ documentation", "the PRD", "the runbook".
12
+ ---
13
+
14
+ > **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may expose them
15
+ > with a server prefix — e.g. `mcp__plugin_clearly_clearly__clearly_read`. **Match by suffix,
16
+ > not by exact name**: a skill written against the bare name resolves to nothing otherwise, and
17
+ > the failure looks like "the tool doesn't exist" rather than "the name is decorated".
18
+ >
19
+ > **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
20
+ > session — these skills still LIST when it isn't, so you find out by firing a dead call.
21
+ > Authorise it (`/mcp`, or `claude mcp`).
22
+
23
+ ## What this is
24
+
25
+ The workspace is a **filesystem**, not a chat surface with a search box.
26
+
27
+ ```
28
+ ~/ every document
29
+ ~/clearly.md what is settled for the whole workspace
30
+ ~/<Folder>/ one directory per project
31
+ ~/<Folder>/clearly.md what is settled for that folder
32
+ ~/.archive/ put away — hidden, readable, read-only, still searched
33
+ ```
34
+
35
+ Folders are projects. Documents are `.md` files named `<KEY> <Title>.md` — e.g.
36
+ `CLR-42 Pricing.md`. The key is an address: `cat ~/CLR-42.md` works from anywhere, and so does
37
+ the full name and the bare title.
38
+
39
+ ## The loop
40
+
41
+ **Find it, then open it.** `clearly_glob` answers "what is it called", `clearly_grep` answers
42
+ "where is this mentioned", and `clearly_read` opens what either one returned.
43
+
44
+ ```
45
+ clearly_glob { pattern: "~/**/*.md" }
46
+ clearly_grep { pattern: "pricing", limit: 5 }
47
+ clearly_read { target: "~/Prepress/CLR-42 Pricing.md" }
48
+ ```
49
+
50
+ ⚠ **`clearly_grep` takes a REGEX, unparsed** — `|`, `(`, `$` and quotes mean what they mean in a
51
+ regular expression. Nothing shells out, so nothing eats them as syntax first.
52
+
53
+ ⚠ **Archived documents are excluded from both.** When any matched, the result carries
54
+ `archivedHidden` saying how many — mention that rather than reporting a clean miss. Search the
55
+ archive by naming it: `clearly_grep { pattern: …, path: "~/.archive" }`.
56
+
57
+ **Structural questions — a date range, a null body, counts — have no keyword to search for**, so
58
+ they go to SQL over the workspace's own tables. That is a CLI job, not an MCP tool:
59
+
60
+ ```
61
+ beehaven call bash '{"command":"sql SELECT title, updated_at FROM notes WHERE project_id IS NULL LIMIT 20"}'
62
+ ```
63
+
64
+ Read-only, one statement, credential columns redacted.
65
+
66
+ ## ⚠⚠ Read before you write, and check what changed
67
+
68
+ This is the part that makes documentation stay true, and it is the part agents skip.
69
+
70
+ The MCP read/edit/replace tools cover the current body. Document history is a separate CLI surface:
71
+
72
+ - **`beehaven call document-status '{"limit":20}'`** lists recent changes. `mine: true` is the
73
+ certain signal that the active agent made the latest change. `by: "unknown"` means the log has
74
+ no author; it may be a browser edit or an older unstamped agent write, so do not claim a person
75
+ made it.
76
+ - **`beehaven call document-diff '{"id":"<documentId>"}'`** follows up with added and removed
77
+ lines. Pass a `versionId` from status to compare against a specific checkpoint.
78
+
79
+ Run status when you resume a conversation, and whenever they say something implying the document
80
+ has moved — *"I rewrote that"*, *"take another look"*.
81
+
82
+ ⚠ **Their edit wins, always.** If a document now disagrees with something you concluded
83
+ earlier, your conclusion is the stale one. You are keeping their documents, not defending your
84
+ own record. Never quietly restore what they took out.
85
+
86
+ ## Editing
87
+
88
+ **`clearly_edit` (find/replace) is the default. `clearly_replace` REPLACES the whole document.**
89
+
90
+ To change one line of a 12,000-character document, `replace` makes you reproduce all 12,000 — and
91
+ anything you fail to retype is gone with no error. It is also a lost-update machine: it writes
92
+ whatever you last read, overwriting anything the human changed in between.
93
+
94
+ - `clearly_edit` requires the `find` text to match **exactly once**. Ambiguity is REFUSED with a
95
+ count, never guessed — an agent that edits the first of three matches has changed something
96
+ nobody looked at.
97
+ - Several independent document edits can be sent in one tool call with `batch`, but each batch
98
+ row is still one exact `{ target, find, replace, all? }` operation. There is no `multi_edit`
99
+ field and the batch is not transactional; inspect the per-item results and retry only failures.
100
+ - `clearly_replace` does not accept `expected_content` or a commit-message field. If you must replace
101
+ a whole existing document, re-read it immediately before writing and verify the returned body.
102
+ Prefer `clearly_edit` whenever a precise passage change can express the job.
103
+
104
+ ## Comments and @mentions — say it beside the work, not instead of it
105
+
106
+ A comment is ABOUT something; an edit changes it. When you have a question or a note for a person,
107
+ leave it where they will see it, on the thing itself:
108
+
109
+ ```
110
+ clearly_comment { target: "CLR-42", content: "Is this still the Q3 number? @sam" }
111
+ clearly_comment { target: "<sheetId>", content: "Double-counts refunds", fields: { cell: "Plan!D12" } }
112
+ clearly_comment { target: "<deckId>", content: "Tighten this headline", fields: { slide: 3 } }
113
+ clearly_read { type: "comment", target: "SS-41" } # the thread, on any item
114
+ clearly_read { type: "comment", target: "@me" } # where YOU were @mentioned
115
+ clearly_edit { type: "comment", target: "<mentionId>", changes: { acknowledged: true } }
116
+ ```
117
+
118
+ `target` is the item — a document, sheet, deck, ticket, board, thought or canvas, by id, key or exact
119
+ title. Anchor with `fields.quote` (document), `slide` (deck, required), `cell` (sheet), `column`
120
+ (board) or `pinId` / `x`,`y` (canvas); an anchor the item cannot carry is refused, never dropped.
121
+
122
+ `@handle` mentions a person (notified) or an agent (queued in its inbox). Get real handles with
123
+ `clearly_read { type: "comment", target, includeMentionables: true }`. The write's result lists
124
+ `mentions`, and `unmatchedMentions` for any handle that reached **no one** — say so, do not assume.
125
+ Acknowledge a mention once you have acted on it, or it comes back in every session.
126
+
127
+ ## `clearly.md` — what is settled
128
+
129
+ Not a document like the others. It holds what this place IS (the work, the constraints, the
130
+ vocabulary, the decisions that stopped moving) and **how this person wants to be worked with** —
131
+ length, tone, format, and what irritates them, which is said once in passing and worth more than
132
+ anything they praise.
133
+
134
+ Fold a conclusion into it once it has stopped moving. Reach for `edit`, never `replace`.
135
+
136
+ ## Report what MOVED
137
+
138
+ *"You added a paragraph about the 12-hour window to Pricing and removed the line about three
139
+ credits"* is a report. *"I've updated the note"* is not — it costs a turn and says nothing they
140
+ could not have assumed.
141
+
142
+ ## What not to do
143
+
144
+ - **Do not tidy unasked.** A document that looks redundant or superseded is still theirs. If two
145
+ say the same thing, say so and let them choose.
146
+ - **`delete` archives, it does not erase** — the document moves to `~/.archive/`, stays readable
147
+ and comes back. Report it as **archived**; "deleted" makes someone believe something is gone
148
+ that is not.
149
+ - **Do not invent a media line.** `![caption](url)` renders a photo, audio or video by file
150
+ extension. You cannot upload, so never write one pointing at a URL you guessed — it renders
151
+ broken, which reads as their photo being lost. Move existing lines; never fabricate one.