beehaven 0.8.29 → 0.8.34
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/dist/agent-attribution.js +20 -2
- package/dist/agent-scope.js +55 -10
- package/dist/agent-state.js +44 -0
- package/dist/agents-md-text.js +64 -0
- package/dist/browser-login.js +14 -4
- package/dist/build-channel.js +2 -2
- package/dist/claude-bridge.js +12 -9
- package/dist/clearly.js +4 -3
- package/dist/cli-state.js +45 -3
- package/dist/cli.js +82 -53
- package/dist/commands/agent-skills.js +12 -1
- package/dist/commands/agent.js +235 -28
- package/dist/commands/agents-md.js +118 -0
- package/dist/commands/daemon-cmd.js +2 -0
- package/dist/commands/help.js +21 -0
- package/dist/commands/self-update.js +32 -5
- package/dist/commands/workspace.js +22 -6
- package/dist/daemon-client.js +10 -6
- package/dist/daemon-entry.js +1 -0
- package/dist/daemon-process.js +125 -0
- package/dist/daemon.js +131 -62
- package/dist/dashboard.js +118 -6
- package/dist/ipc-auth.js +3 -1
- package/dist/ipc-client.js +7 -6
- package/dist/ipc-path.js +92 -0
- package/dist/ipc-server.js +33 -33
- package/dist/npm-update.js +138 -0
- package/dist/session-binding.js +135 -0
- package/dist/session-target.js +91 -0
- package/dist/setup-service.js +9 -4
- package/dist/verb-surface.js +1 -0
- package/dist/watcher.js +9 -23
- package/dist/windows-hide.js +77 -0
- package/package.json +2 -2
- package/scripts/sync-skills.mjs +6 -0
- package/skills/agent-doodle-space/SKILL.md +1 -1
- package/skills/agent-home/SKILL.md +1 -1
- package/skills/brand-identity/SKILL.md +22 -5
- package/skills/clearly-agent/SKILL.md +38 -14
- package/skills/clearly-canvas/SKILL.md +31 -5
- package/skills/clearly-canvas/authoring.md +41 -12
- package/skills/clearly-canvas/example.mjs +72 -69
- package/skills/clearly-docs/SKILL.md +151 -0
- package/skills/clearly-init/SKILL.md +135 -0
- package/skills/clearly-workflows/SKILL.md +90 -0
- package/skills/clearly-workspace/SKILL.md +1 -1
- package/skills/codebase-map/SKILL.md +1 -1
- package/skills/design-craft/SKILL.md +9 -6
- package/skills/index.json +45 -10
- package/skills/layout-systems/SKILL.md +14 -11
- package/skills/pair-on-canvas/SKILL.md +1 -1
- package/skills/sticker-pack/SKILL.md +132 -0
- package/skills/visualize/SKILL.md +188 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* windows-hide — no console windows flashing open on Windows (CAN2-56).
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ WHY WINDOWS FLASHED. A Windows process with no console of its own — the detached daemon,
|
|
5
|
+
* or `beehaven` run by an agent or an app rather than in a terminal — that starts a CONSOLE
|
|
6
|
+
* program (cmd.exe for every `exec`/`execSync`, powershell, git, node, …) makes Windows create a
|
|
7
|
+
* brand-new console window for that child. It pops up and closes as the child exits: one flash per
|
|
8
|
+
* call, and the daemon makes calls on timers. Node prevents it per call with `windowsHide: true`;
|
|
9
|
+
* the CLI had ~140 child-process calls and one of them set it.
|
|
10
|
+
*
|
|
11
|
+
* ⚠ ONE CHOKEPOINT, NOT 140 CALL SITES. Imported FIRST by every entry point (`cli.ts`,
|
|
12
|
+
* `clearly.ts`, `daemon-entry.ts`), this wraps the `child_process` functions so `windowsHide`
|
|
13
|
+
* defaults to true for every caller in the process — this package, `@beehaven/core`, and any
|
|
14
|
+
* dependency — and `syncBuiltinESMExports()` makes ESM named imports (`import { spawn } …`) see
|
|
15
|
+
* the wrappers too. An explicit `windowsHide: false` is respected. Off Windows it does nothing.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ `exec` / `execFile` carry `util.promisify.custom` (so `promisify(exec)` resolves
|
|
18
|
+
* `{ stdout, stderr }`). The wrappers forward it; without that, every `promisify(exec)` caller
|
|
19
|
+
* would silently start receiving a bare string.
|
|
20
|
+
*/
|
|
21
|
+
import { createRequire, syncBuiltinESMExports } from 'node:module';
|
|
22
|
+
import { promisify } from 'node:util';
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const MARK = Symbol.for('beehaven.windowsHide');
|
|
25
|
+
/**
|
|
26
|
+
* A child_process call's arguments with `windowsHide: true` set, unless the caller chose a value.
|
|
27
|
+
* `takesArgv` = the second parameter may be an argv array (spawn, spawnSync, execFile,
|
|
28
|
+
* execFileSync, fork). Pure — exported for tests.
|
|
29
|
+
*/
|
|
30
|
+
export function withWindowsHide(args, takesArgv) {
|
|
31
|
+
const a = [...args];
|
|
32
|
+
for (let i = 1; i < a.length; i++) {
|
|
33
|
+
const v = a[i];
|
|
34
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
35
|
+
if (v.windowsHide === undefined)
|
|
36
|
+
a[i] = { ...v, windowsHide: true };
|
|
37
|
+
return a;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// No options object: it goes after the command (and its argv array), before any callback. An
|
|
41
|
+
// explicit `undefined`/`null` options slot is filled rather than shifted.
|
|
42
|
+
const at = takesArgv && Array.isArray(a[1]) ? 2 : 1;
|
|
43
|
+
if (at < a.length && (a[at] === undefined || a[at] === null))
|
|
44
|
+
a[at] = { windowsHide: true };
|
|
45
|
+
else
|
|
46
|
+
a.splice(at, 0, { windowsHide: true });
|
|
47
|
+
return a;
|
|
48
|
+
}
|
|
49
|
+
const WRAPPED = [
|
|
50
|
+
['spawn', true], ['spawnSync', true], ['execFile', true], ['execFileSync', true], ['fork', true],
|
|
51
|
+
['exec', false], ['execSync', false],
|
|
52
|
+
];
|
|
53
|
+
/** Wrap `child_process` once per process. Returns whether it is installed. `force` is for tests. */
|
|
54
|
+
export function installWindowsHide(opts = {}) {
|
|
55
|
+
if (process.platform !== 'win32' && !opts.force)
|
|
56
|
+
return false;
|
|
57
|
+
const cp = require('node:child_process');
|
|
58
|
+
if (cp[MARK])
|
|
59
|
+
return true;
|
|
60
|
+
for (const [name, takesArgv] of WRAPPED) {
|
|
61
|
+
const orig = cp[name];
|
|
62
|
+
if (typeof orig !== 'function')
|
|
63
|
+
continue;
|
|
64
|
+
const wrapper = function (...args) {
|
|
65
|
+
return orig.apply(this, withWindowsHide(args, takesArgv));
|
|
66
|
+
};
|
|
67
|
+
const custom = orig[promisify.custom];
|
|
68
|
+
if (typeof custom === 'function') {
|
|
69
|
+
wrapper[promisify.custom] = (...args) => custom.apply(orig, withWindowsHide(args, takesArgv));
|
|
70
|
+
}
|
|
71
|
+
cp[name] = wrapper;
|
|
72
|
+
}
|
|
73
|
+
cp[MARK] = true;
|
|
74
|
+
syncBuiltinESMExports();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
installWindowsHide();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beehaven",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.34",
|
|
4
4
|
"description": "Beehaven CLI — command your Clearly workspace, agents, and canvas from the terminal",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"better-sqlite3": "
|
|
56
|
+
"better-sqlite3": "13.0.3",
|
|
57
57
|
"node-pty": "1.0.0",
|
|
58
58
|
"open": "10.1.0"
|
|
59
59
|
},
|
package/scripts/sync-skills.mjs
CHANGED
|
@@ -40,6 +40,12 @@ const SOURCES = {
|
|
|
40
40
|
'codebase-map': 'apps/mcp-server/plugin/skills/codebase-map/SKILL.md',
|
|
41
41
|
'clearly-workspace': 'apps/mcp-server/plugin/skills/clearly-workspace/SKILL.md',
|
|
42
42
|
'clearly-agent': 'apps/mcp-server/plugin/skills/clearly-agent/SKILL.md',
|
|
43
|
+
// CAN2-57 — the rest of the plugin's skills: one set across plugin, CLI and Mac app.
|
|
44
|
+
'visualize': 'apps/mcp-server/plugin/skills/visualize/SKILL.md',
|
|
45
|
+
'sticker-pack': 'apps/mcp-server/plugin/skills/sticker-pack/SKILL.md',
|
|
46
|
+
'clearly-docs': 'apps/mcp-server/plugin/skills/clearly-docs/SKILL.md',
|
|
47
|
+
'clearly-init': 'apps/mcp-server/plugin/skills/clearly-init/SKILL.md',
|
|
48
|
+
'clearly-workflows': 'apps/mcp-server/plugin/skills/clearly-workflows/SKILL.md',
|
|
43
49
|
};
|
|
44
50
|
|
|
45
51
|
let missing = 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-doodle-space
|
|
3
|
-
description: Use an agent identity's Avatar Soul and durable Doodle Space. Trigger when someone says "draw my avatar", "change my expression", "make a doodle", or wants to inspect an agent's visual identity. Read the minted appearance before drawing; save durable art to a session composition. Requires AGENT_SOUL_SPACE and is separate from avatar-shop.
|
|
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. Use an agent identity's Avatar Soul and durable Doodle Space. Trigger when someone says "draw my avatar", "change my expression", "make a doodle", or wants to inspect an agent's visual identity. Read the minted appearance before drawing; save durable art to a session composition. Requires AGENT_SOUL_SPACE and is separate from avatar-shop.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Agent Soul + Doodle Space
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-home
|
|
3
|
-
description: Design and inhabit an agent-owned memory apartment on Clearly's canvas. Use when someone says "build my agent home", "make a room", "save a home memory", or "move the avatar". Arrange rooms, doors, windows, furniture, avatar position, memories, journals, and Home ads on a fixed 24px grid. Requires AGENT_SOUL_SPACE.
|
|
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. Design and inhabit an agent-owned memory apartment on Clearly's canvas. Use when someone says "build my agent home", "make a room", "save a home memory", or "move the avatar". Arrange rooms, doors, windows, furniture, avatar position, memories, journals, and Home ads on a fixed 24px grid. Requires AGENT_SOUL_SPACE.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Agent Home memory apartment
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brand-identity
|
|
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 the requested logo or brand identity on a Clearly canvas —
|
|
5
6
|
brief, mark, wordmark, lockups and clear space, palette, type system,
|
|
6
7
|
applications, and a spec board someone else can build from. Use when asked
|
|
@@ -64,15 +65,31 @@ handover. A mark of hand-placed béziers does not, and it shows.
|
|
|
64
65
|
| **Counter-form** | the shape is the NEGATIVE space between forms | clever marks that survive one colour |
|
|
65
66
|
| **Modular** | one unit repeated/rotated on a grid | systems, networks, collectives |
|
|
66
67
|
|
|
67
|
-
**Build it
|
|
68
|
+
**Build it from native layers, every number derived from the construction** — the way an
|
|
69
|
+
identity designer works in Figma, so each part of the mark stays a named, editable layer in the
|
|
70
|
+
brand colour (never one generated or hand-written SVG):
|
|
68
71
|
|
|
69
72
|
```jsonc
|
|
70
|
-
{"
|
|
71
|
-
"
|
|
73
|
+
{"batch":[
|
|
74
|
+
{"action":"frame.create","args":{"name":"Mark","x":120,"y":120,"w":240,"h":240}},
|
|
75
|
+
{"action":"canvas.create-node","args":{"type":"ellipse","name":"mark/ring","parentId":"Mark",
|
|
76
|
+
"x":20,"y":20,"w":200,"h":200,"fill":"#1B4DFF"}},
|
|
77
|
+
{"action":"canvas.create-node","args":{"type":"ellipse","name":"mark/counter","parentId":"Mark",
|
|
78
|
+
"x":70,"y":70,"w":100,"h":100,"fill":"#FFFFFF"}},
|
|
79
|
+
{"action":"vector.create","args":{"name":"mark/blade","parentId":"Mark","closed":true,"fill":"#1B4DFF",
|
|
80
|
+
"points":[{"x":120,"y":20},{"x":220,"y":120},{"x":120,"y":120}]}},
|
|
81
|
+
{"action":"canvas.group","args":{"ids":["mark/ring","mark/counter","mark/blade"],"name":"mark"}}
|
|
82
|
+
]}
|
|
72
83
|
```
|
|
73
84
|
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
Here R = 100: the counter is R/2, the blade's corners sit on the circle. Curves are
|
|
86
|
+
`vector.create` points with `hIn`/`hOut` control points (absolute coordinates) placed where the
|
|
87
|
+
construction says; a quarter-circle's handle sits 0.552·R from its anchor. A counter-form is a
|
|
88
|
+
ground-colour shape layered on top; `vector.boolean` makes a true cut-out when an editor is open.
|
|
89
|
+
Raw SVG (`type:"svg"`) is only for importing a mark the client already has.
|
|
90
|
+
|
|
91
|
+
⚠ **Wordmarks are real text nodes, or outlined paths** — and `<text>` inside an imported `svg`
|
|
92
|
+
node does NOT render headlessly (no system fonts are loaded).
|
|
76
93
|
|
|
77
94
|
**Test the mark against these before going further. A mark that fails any of them is not done:**
|
|
78
95
|
|
|
@@ -1,18 +1,34 @@
|
|
|
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
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Sign in once — this session is then you, in one workspace
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
beehaven env
|
|
12
|
-
beehaven
|
|
13
|
-
beehaven agent login <name> --label "what I am here to do" --client cli
|
|
11
|
+
beehaven env # staging or prod: the session remembers it
|
|
12
|
+
beehaven agent login <name> --label "what I am here to do" --client cli [--workspace <ws>]
|
|
14
13
|
```
|
|
15
14
|
|
|
15
|
+
After login **every** `beehaven` command in this session runs as `<name>`, in its workspace — no
|
|
16
|
+
`--agent`, no export, nothing per call. The login prints the session, the agent, the workspace (by
|
|
17
|
+
name) and the env. `--workspace` takes `home`, `workspace/<id>`, `team/<id>` or a bare 64-character
|
|
18
|
+
id (`beehaven workspaces` lists them); without it a session starts where this identity last worked.
|
|
19
|
+
|
|
20
|
+
**One session = one identity + one workspace + one env (AWO-394).** A session is a Claude/Codex/
|
|
21
|
+
Gemini conversation, or a terminal tab. Each has its own:
|
|
22
|
+
- workspace — `beehaven connect <ws>` moves THIS session only (and moves its work session there);
|
|
23
|
+
another agent, or another conversation signed in as the same name, stays where it is;
|
|
24
|
+
- work session — a second login under the same name no longer ends the first one's;
|
|
25
|
+
- env — if someone switches the daemon (`beehaven env prod`), calls from a staging session are
|
|
26
|
+
refused with the fix instead of landing in the other environment.
|
|
27
|
+
|
|
28
|
+
Typed verbs (`ticket`, `sheet`, `slides`, `design`, `mention`, `trash`) follow the session's
|
|
29
|
+
workspace exactly like `call` (AWO-393). A Shopify store is an operator target:
|
|
30
|
+
`beehaven admin do shopifyStore/<id> <action> '<json>'` (platform admins).
|
|
31
|
+
|
|
16
32
|
## Personal workspace means a private layer, not another DO
|
|
17
33
|
|
|
18
34
|
Every human and every agent in the selected workspace has a **personal workspace layer**: its own
|
|
@@ -38,13 +54,14 @@ steps. CI gets a real identity for one command: `beehaven agent login ci`.
|
|
|
38
54
|
| 2 | `BEEHAVEN_AGENT=<name>` | a script, or another terminal |
|
|
39
55
|
| 3 | the **scope binding** from `agent login` | this session, no flag needed |
|
|
40
56
|
|
|
41
|
-
⚠ **The binding needs no cooperation.** `agent login`
|
|
42
|
-
`claude-<sessionId>`, `codex-…`, `gemini-…`,
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
⚠ **The binding needs no cooperation.** `agent login` writes `~/.beehaven/sessions/<scope>.json` for
|
|
58
|
+
the calling process's scope — `claude-<sessionId>`, `codex-…`, `gemini-…`, `term-<tab>` for a
|
|
59
|
+
terminal tab that says which tab it is (macOS Terminal, iTerm2, Windows Terminal, tmux, kitty,
|
|
60
|
+
WezTerm), or `pid-<ppid>` for a non-TTY. Two harness sessions or two tabs are two agents
|
|
61
|
+
concurrently with no env var; logging in twice from one session means the second login.
|
|
45
62
|
|
|
46
|
-
⚠
|
|
47
|
-
|
|
63
|
+
⚠ What is still machine-wide: one daemon, one relay, one `beehaven env` (sessions refuse a mismatch
|
|
64
|
+
rather than follow it). `agent logout` ends this session only.
|
|
48
65
|
|
|
49
66
|
## Identity ≠ session
|
|
50
67
|
|
|
@@ -216,6 +233,11 @@ governance. Each resource's delete cell is `none`, `own`, or `all`; `own` means
|
|
|
216
233
|
`created_by: agent:<your-id>`. Missing or legacy author data is not yours. Permanent deletion is
|
|
217
234
|
human-only; agent deletes use a recoverable archive, tombstone, or checkpoint.
|
|
218
235
|
|
|
236
|
+
Removing canvas layers is an **update**, including `canvas.delete`, block removal, and placed
|
|
237
|
+
design layers. You may remove layers from any visible canvas within your edit scope, regardless
|
|
238
|
+
of its author. The server requires an undo checkpoint first. Deleting or archiving the whole
|
|
239
|
+
canvas still follows delete-own/delete-all (CAN2-128).
|
|
240
|
+
|
|
219
241
|
Only agents whose owner is currently a workspace owner or admin can use admin or delete-all. The
|
|
220
242
|
server checks this on every call: if your owner is demoted, your scope drops to write/delete-own.
|
|
221
243
|
After an owner is promoted again, a human must explicitly re-grant the higher scope.
|
|
@@ -228,15 +250,17 @@ owner/admin-owned agent can be granted it. Check yours before a destructive step
|
|
|
228
250
|
beehaven call agent-capabilities-get '{}' # { scope, matrix, summary }
|
|
229
251
|
```
|
|
230
252
|
|
|
231
|
-
⚠ `permission_scope: 'write'` lets you
|
|
232
|
-
|
|
253
|
+
⚠ `permission_scope: 'write'` lets you archive/remove only your own workspace items; editing layers
|
|
254
|
+
inside an editable canvas follows the update rule above. A permission refusal is the contract
|
|
255
|
+
working, not a bug. ⚠ You cannot change permissions — yours or another agent's;
|
|
233
256
|
ask the owner.
|
|
234
257
|
|
|
235
258
|
## Traps that cost real time
|
|
236
259
|
|
|
237
260
|
- ⚠ **`beehaven env` first.** `prod` and `staging` are different databases and `--env` does not
|
|
238
261
|
exist on `call` — the daemon *is* the env. Every number is meaningless if you are on the wrong one.
|
|
239
|
-
- ⚠ **
|
|
262
|
+
- ⚠ **Verify a deploy by the response's runtime version.** Allow propagation and retry read-only
|
|
263
|
+
checks; do not restart the user's daemon to force a resident DO onto a new version.
|
|
240
264
|
- ⚠⚠ **Check the `as <agent>` on your first call.** An identity archived in this workspace still
|
|
241
265
|
mints (identities are account-level), but the workspace refuses its session. Current CLIs say so
|
|
242
266
|
(`⚠ 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
|
|
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>","
|
|
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
|
|
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.
|
|
199
|
-
|
|
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
|
|
14
|
-
It demonstrates
|
|
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:
|
|
25
|
-
body: { family: 'Inter', size: 24, fontWeight: 400, lineHeight:
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
35
|
-
text('opening-deck', 'An editable visual system for the next chapter.',
|
|
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":
|
|
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,26 @@ 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:
|
|
150
|
+
- AUTO-LAYOUT: build every row, stack, card and grid AS an auto-layout frame from the start: frame.create (or canvas.create-node type "frame") { name, parentId, autoLayout: { direction: "h" | "v" | "grid", gap, padding, align, justify, hug } }, then create its children with parentId and NO x/y; they flow in creation order and the frame stays live. canvas.auto-layout { id, … } changes the settings; with only { id } it converts a hand-positioned frame, reading direction, gap and padding off the children so nothing moves, and review reports any stack left positioned by hand. 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".
|
|
151
|
+
- VARIABLES: use the brand by name, not by value: in canvas.create-node and frame.create write fill: "brand/accent", stroke: "brand/line", radius: "brand/radius", autoLayout: { gap: "brand/spacing", padding: "brand/spacing" }, and on text textStyle: "brand/heading" (or "brand/body"), which sets family, weight, size, line height and letter spacing together from the brand's type role. Each binds, so the layer follows the canvas brand and a switch of brand; "var/<name>" reaches a workspace variable (text: "var/product/name", visible: "var/flags/promo"). A dark version of a frame is variableModes: { brand: "dark" } on it, not new colours. canvas.variable binds existing layers (op "bind", "apply-style", "set-mode"); op "list" shows every variable and style. A typed value detaches.
|
|
127
152
|
- 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
153
|
- 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
|
-
-
|
|
154
|
+
- 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, d, fill or stroke, strokeWidth }: write d as SVG path data (M, C, Z), native editable points in far fewer tokens than points: [{ x, y, hIn?, hOut? }] (hIn/hOut are absolute control points); few anchors, smooth curves. It also takes 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.
|
|
155
|
+
- 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. A new top-level artboard goes beside or below what scene already holds (or leave out both x and y), never at 0,0 over an existing one.
|
|
156
|
+
- 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.
|
|
157
|
+
- MEASURE: answer spacing, overlap and alignment questions with canvas.measure { frame } or { ids } and quote its numbers, never from memory or a preview.
|
|
158
|
+
- 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
159
|
- 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
160
|
|
|
132
161
|
Use `arrow.create {from,to,routing,label}` for connected diagrams, so arrows remain bound
|
|
133
|
-
when nodes move.
|
|
162
|
+
when nodes move. Draw custom vector details natively (shapes, vector.create pen paths); raw SVG only imports an existing file. Keep labels as
|
|
134
163
|
native text so they remain editable and measurable.
|
|
135
164
|
|
|
136
165
|
Use `canvas.place {kind,id,x,y,w,h,name,parentId?}` to put a workspace object (document,
|