beehaven 0.8.24 → 0.8.29
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-state.js +19 -0
- package/dist/browser-login.js +283 -0
- package/dist/build-channel.js +2 -2
- package/dist/clearly.js +5 -4
- package/dist/cli.js +14 -266
- package/dist/commands/agent.js +186 -59
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/compose.js +2 -2
- package/dist/commands/help.js +2 -0
- package/dist/commands/self-update.js +2 -2
- package/dist/commands/workspace.js +30 -8
- package/dist/daemon.js +9 -9
- package/dist/folder-sync.js +1 -1
- package/dist/local-app-manager.js +1 -1
- package/dist/relay-presets.js +3 -4
- package/dist/setup-relay.js +20 -235
- package/package.json +1 -1
- package/skills/beehaven-cli/SKILL.md +12 -0
- package/skills/brand-identity/SKILL.md +15 -9
- package/skills/clearly-agent/SKILL.md +27 -3
- package/skills/clearly-canvas/SKILL.md +67 -7
- package/skills/clearly-canvas/authoring.md +27 -4
- package/skills/clearly-canvas/review.mjs +106 -12
- package/skills/design-craft/SKILL.md +5 -0
- package/skills/design-craft/references.md +95 -0
- package/skills/index.json +7 -6
package/dist/relay-presets.js
CHANGED
|
@@ -67,9 +67,8 @@ export function resolveRelaySelection(cfg) {
|
|
|
67
67
|
* `https://localhost:3000` (canvas.ts's `--dev` branch). They are not interchangeable: the apex
|
|
68
68
|
* 301s to www, so the apex spelling handed people a link with a redirect in it.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* is `www.clearly.sh`. Inventing a `staging.clearly.sh` here would be the dead-link bug again.
|
|
70
|
+
* Staging uses the local HTTPS frontend (Clearly AGENTS.md, 2026-10-08). Its workspace IDs
|
|
71
|
+
* belong to staging and must never be linked into the production frontend.
|
|
73
72
|
*
|
|
74
73
|
* ⚠ `custom` RETURNS undefined, DELIBERATELY. A custom relay says nothing about where its frontend
|
|
75
74
|
* is, and guessing production for it is how you hand someone a link into the wrong deployment.
|
|
@@ -78,10 +77,10 @@ export function resolveRelaySelection(cfg) {
|
|
|
78
77
|
export function appOriginForEnv(envName) {
|
|
79
78
|
switch (envName) {
|
|
80
79
|
case 'prod':
|
|
81
|
-
case 'staging':
|
|
82
80
|
return 'https://www.clearly.sh';
|
|
83
81
|
// `npm run dev` is `next dev --experimental-https`, so the local app is HTTPS, not HTTP.
|
|
84
82
|
case 'dev':
|
|
83
|
+
case 'staging':
|
|
85
84
|
return 'https://localhost:3000';
|
|
86
85
|
default:
|
|
87
86
|
return undefined;
|
package/dist/setup-relay.js
CHANGED
|
@@ -1,148 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// Beehaven Office - Relay Setup (Login)
|
|
4
|
-
//
|
|
4
|
+
// The daemon's interactive sign-in fallback, and logout.
|
|
5
5
|
// ============================================================================
|
|
6
|
-
import
|
|
7
|
-
import crypto from 'crypto';
|
|
8
|
-
import { createInterface } from 'readline';
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, chmodSync } from 'fs';
|
|
10
7
|
import { homedir } from 'os';
|
|
11
8
|
import { join } from 'path';
|
|
12
9
|
import { spawnSync } from 'child_process';
|
|
13
10
|
import { Relay, CLEARLY_RELAY_URL } from './relay.js';
|
|
14
|
-
import { resolveRelaySelection } from './relay-presets.js';
|
|
15
11
|
import { SESSION_KEYCHAIN_ACCOUNTS, KEYCHAIN_SERVICE } from './keychain.js';
|
|
16
|
-
import { withUtm } from './utm.js';
|
|
17
12
|
const CONFIG_DIR = join(homedir(), '.beehaven');
|
|
18
13
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
19
|
-
const AUTH_BASE_URL = 'https://clearly.sh/auth/beehaven';
|
|
20
|
-
const LOGIN_TIMEOUT_MS = 120_000; // 2 minutes
|
|
21
14
|
/**
|
|
22
|
-
* The
|
|
23
|
-
*
|
|
15
|
+
* The daemon calls this when it starts interactively with no token, or when its session has
|
|
16
|
+
* expired (daemon.ts). It then re-reads the config, so all it needs is credentials on disk.
|
|
17
|
+
*
|
|
18
|
+
* ⚠⚠ AWO-224 — THIS USED TO BE A SECOND, DIFFERENT SIGN-IN. It opened
|
|
19
|
+
* `https://www.clearly.sh/auth/beehaven?callback=…`, waited for a single `token` and wrote its own
|
|
20
|
+
* config. The web app no longer serves that route (404 on prod and, via the apex redirect, the
|
|
21
|
+
* same 404), so the fallback opened a dead page and timed out after two minutes. It now runs the
|
|
22
|
+
* exact flow `beehaven login` runs (`browser-login.ts`: `/cli/auth`, the keychain idToken slot
|
|
23
|
+
* the daemon reads, both dial-target keys) and nothing else — it must NOT restart the daemon,
|
|
24
|
+
* because it runs inside it.
|
|
24
25
|
*/
|
|
25
|
-
function readConfigFile() {
|
|
26
|
-
if (!existsSync(CONFIG_FILE))
|
|
27
|
-
return {};
|
|
28
|
-
try {
|
|
29
|
-
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return {};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function saveRelayConfig(token, endpoint) {
|
|
36
|
-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
37
|
-
let existing = {};
|
|
38
|
-
if (existsSync(CONFIG_FILE)) {
|
|
39
|
-
try {
|
|
40
|
-
existing = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.warn(`[config] Failed to parse ${CONFIG_FILE}:`, err.message);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
existing.token = token;
|
|
47
|
-
// ⚠ BOTH KEYS, as `beehaven env` writes them — `endpoint` is dialled, `relayUrl` is the alias
|
|
48
|
-
// other surfaces read. Updating one leaves the other naming a different environment.
|
|
49
|
-
existing.endpoint = endpoint;
|
|
50
|
-
existing.relayUrl = endpoint;
|
|
51
|
-
existing.tier = 'connected';
|
|
52
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
|
|
53
|
-
chmodSync(CONFIG_FILE, 0o600);
|
|
54
|
-
}
|
|
55
|
-
function prompt(question) {
|
|
56
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
rl.question(question, (answer) => {
|
|
59
|
-
rl.close();
|
|
60
|
-
resolve(answer.trim());
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Start a local HTTP server on a random port to receive the OAuth callback.
|
|
66
|
-
* Returns a promise for the token, plus helpers to get the port and close.
|
|
67
|
-
*/
|
|
68
|
-
function startCallbackServer() {
|
|
69
|
-
let resolveToken;
|
|
70
|
-
let rejectToken;
|
|
71
|
-
let serverRef;
|
|
72
|
-
let settled = false;
|
|
73
|
-
const oauthState = crypto.randomBytes(32).toString('hex');
|
|
74
|
-
const promise = new Promise((resolve, reject) => {
|
|
75
|
-
resolveToken = resolve;
|
|
76
|
-
rejectToken = reject;
|
|
77
|
-
serverRef = http.createServer((req, res) => {
|
|
78
|
-
if (settled)
|
|
79
|
-
return;
|
|
80
|
-
const url = new URL(req.url, `http://localhost`);
|
|
81
|
-
if (url.pathname === '/callback') {
|
|
82
|
-
// Validate OAuth state parameter to prevent CSRF (skip if server doesn't echo it back yet)
|
|
83
|
-
const returnedState = url.searchParams.get('state');
|
|
84
|
-
if (returnedState && returnedState !== oauthState) {
|
|
85
|
-
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
86
|
-
res.end('<!DOCTYPE html><html><body><h1>Invalid state parameter</h1></body></html>');
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const token = url.searchParams.get('token');
|
|
90
|
-
if (token) {
|
|
91
|
-
settled = true;
|
|
92
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
93
|
-
res.end([
|
|
94
|
-
'<!DOCTYPE html><html><head>',
|
|
95
|
-
'<meta charset="utf-8">',
|
|
96
|
-
'<title>Beehaven - Logged In</title>',
|
|
97
|
-
'<style>',
|
|
98
|
-
'body{font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#fef9ef}',
|
|
99
|
-
'.card{text-align:center;padding:48px;border-radius:16px;background:white;box-shadow:0 2px 12px rgba(0,0,0,0.08)}',
|
|
100
|
-
'h1{margin:0 0 8px;font-size:24px}',
|
|
101
|
-
'p{margin:0;color:#666;font-size:15px}',
|
|
102
|
-
'.bee{font-size:48px;margin-bottom:16px}',
|
|
103
|
-
'</style>',
|
|
104
|
-
'</head><body>',
|
|
105
|
-
'<div class="card">',
|
|
106
|
-
'<div class="bee">🐝</div>',
|
|
107
|
-
'<h1>Logged in!</h1>',
|
|
108
|
-
'<p>You can close this tab and return to your terminal.</p>',
|
|
109
|
-
'</div>',
|
|
110
|
-
'</body></html>',
|
|
111
|
-
].join(''));
|
|
112
|
-
serverRef.close();
|
|
113
|
-
resolve(token);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
117
|
-
res.end('<!DOCTYPE html><html><body><h1>Missing token</h1></body></html>');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
res.writeHead(404);
|
|
122
|
-
res.end();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
serverRef.listen(0, '127.0.0.1');
|
|
126
|
-
// Timeout after 2 minutes
|
|
127
|
-
const timer = setTimeout(() => {
|
|
128
|
-
if (!settled) {
|
|
129
|
-
settled = true;
|
|
130
|
-
serverRef.close();
|
|
131
|
-
reject(new Error('Login timed out after 2 minutes.'));
|
|
132
|
-
}
|
|
133
|
-
}, LOGIN_TIMEOUT_MS);
|
|
134
|
-
serverRef.on('close', () => clearTimeout(timer));
|
|
135
|
-
});
|
|
136
|
-
return {
|
|
137
|
-
promise,
|
|
138
|
-
getPort: () => serverRef.address()?.port || 0,
|
|
139
|
-
close: () => {
|
|
140
|
-
settled = true;
|
|
141
|
-
serverRef?.close();
|
|
142
|
-
},
|
|
143
|
-
state: oauthState,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
26
|
export async function login() {
|
|
147
27
|
console.log('');
|
|
148
28
|
console.log(' Clearly account required to use Beehaven.');
|
|
@@ -165,114 +45,19 @@ export async function login() {
|
|
|
165
45
|
console.log(' Session expired. Please log in again.');
|
|
166
46
|
console.log('');
|
|
167
47
|
}
|
|
168
|
-
// Start local callback server
|
|
169
|
-
const callbackServer = startCallbackServer();
|
|
170
|
-
// Small delay to let server bind
|
|
171
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
172
|
-
const port = callbackServer.getPort();
|
|
173
|
-
if (!port) {
|
|
174
|
-
console.log(' Failed to start auth server.');
|
|
175
|
-
callbackServer.close();
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const callbackUrl = `http://localhost:${port}/callback`;
|
|
179
|
-
const authUrl = withUtm(`${AUTH_BASE_URL}?callback=${encodeURIComponent(callbackUrl)}&state=${callbackServer.state}`);
|
|
180
|
-
// Try to open browser
|
|
181
|
-
let browserOpened = false;
|
|
182
|
-
try {
|
|
183
|
-
const openModule = await import('open');
|
|
184
|
-
await openModule.default(authUrl);
|
|
185
|
-
browserOpened = true;
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
browserOpened = false;
|
|
189
|
-
}
|
|
190
|
-
if (browserOpened) {
|
|
191
|
-
console.log(' Opening browser to sign in with Google...');
|
|
192
|
-
console.log('');
|
|
193
|
-
console.log(' Waiting for login... (press Ctrl+C to cancel)');
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// Fallback: show URL and offer manual token paste
|
|
197
|
-
console.log(' Could not open browser. Sign in manually:');
|
|
198
|
-
console.log('');
|
|
199
|
-
console.log(` 1. Open: ${authUrl}`);
|
|
200
|
-
console.log(' 2. Sign in with Google');
|
|
201
|
-
console.log('');
|
|
202
|
-
console.log(' Waiting for login callback...');
|
|
203
|
-
console.log(' Or paste your relay token below:');
|
|
204
|
-
console.log('');
|
|
205
|
-
// Race: callback server vs manual token paste
|
|
206
|
-
const manualToken = prompt(' Relay token: ').then((token) => {
|
|
207
|
-
if (token) {
|
|
208
|
-
callbackServer.close();
|
|
209
|
-
return token;
|
|
210
|
-
}
|
|
211
|
-
return null;
|
|
212
|
-
});
|
|
213
|
-
try {
|
|
214
|
-
const token = await Promise.race([
|
|
215
|
-
callbackServer.promise,
|
|
216
|
-
manualToken.then((t) => {
|
|
217
|
-
if (t)
|
|
218
|
-
return t;
|
|
219
|
-
return new Promise(() => { });
|
|
220
|
-
}),
|
|
221
|
-
]);
|
|
222
|
-
if (token) {
|
|
223
|
-
return await verifyAndSave(token);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
console.log(` ${err.message}`);
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Wait for callback
|
|
232
48
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
console.log(` ${
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function verifyAndSave(token) {
|
|
242
|
-
// Validate token format before sending to server
|
|
243
|
-
if (!token || !/^[a-zA-Z0-9_\-.]{20,}$/.test(token)) {
|
|
244
|
-
console.log(' Invalid token format.');
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
console.log('');
|
|
248
|
-
console.log(' Verifying...');
|
|
249
|
-
const relay = new Relay();
|
|
250
|
-
/**
|
|
251
|
-
* ⚠⚠ THE ACTIVE RELAY, NOT A HARDCODED PROD CONSTANT. Both of these were `CLEARLY_RELAY_URL`, so
|
|
252
|
-
* a staging or dev user who signed in here was VERIFIED against prod and then had `endpoint`
|
|
253
|
-
* OVERWRITTEN to prod — silently moving them to another environment as a side effect of logging
|
|
254
|
-
* in. `beehaven env staging` writes the target; a login must honour it, never redefine it.
|
|
255
|
-
*
|
|
256
|
-
* ⚠ Read from the file rather than `Relay.loadConfig()`: that returns null without a token, which
|
|
257
|
-
* is exactly the state a first login is in. `resolveRelaySelection` defaults to prod when the
|
|
258
|
-
* config names nothing, so the no-config case is unchanged.
|
|
259
|
-
*/
|
|
260
|
-
const dialUrl = resolveRelaySelection(readConfigFile()).url;
|
|
261
|
-
const profile = await relay.verifyToken(token, dialUrl);
|
|
262
|
-
if (profile) {
|
|
263
|
-
saveRelayConfig(token, dialUrl);
|
|
264
|
-
if (profile.displayName) {
|
|
265
|
-
console.log(` Logged in as ${profile.displayName} (${profile.subscriptionPlan})`);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
console.log(' Logged in.');
|
|
269
|
-
}
|
|
49
|
+
// ⚠ Imported lazily: the Mac app loads this file for `logout` alone and should not pull in
|
|
50
|
+
// the sign-in flow's dependencies to do it.
|
|
51
|
+
const { browserLogin } = await import('./browser-login.js');
|
|
52
|
+
const who = await browserLogin();
|
|
53
|
+
console.log(` Logged in as ${who.email ?? who.userId}.`);
|
|
270
54
|
console.log(' Config saved to ~/.beehaven/config.json');
|
|
271
55
|
console.log('');
|
|
272
56
|
return true;
|
|
273
57
|
}
|
|
274
|
-
|
|
275
|
-
console.log(
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.log(` ${err?.message ?? err}`);
|
|
60
|
+
console.log(' Run `beehaven login`, then start again.');
|
|
276
61
|
console.log('');
|
|
277
62
|
return false;
|
|
278
63
|
}
|
package/package.json
CHANGED
|
@@ -25,6 +25,18 @@ beehaven cd home # back to the default workspace
|
|
|
25
25
|
beehaven disconnect # end the session
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
Start discovery narrowly: `beehaven explain <action>` gives one schema; `beehaven actions
|
|
29
|
+
--search <term>` or `--category <name>` bounds the inventory. The default inventory is compact;
|
|
30
|
+
`--verbose` or `--format full --json` requests full detail. Save large JSON once and print a
|
|
31
|
+
small summary rather than repeating a call after output truncation.
|
|
32
|
+
|
|
33
|
+
For ticket text search use `ticket-list {text:"…",projectId?,fields?,limit?}` (`query` and `q`
|
|
34
|
+
are aliases). For a design setup, `design-start {title,projectId?,boardId?,idempotencyKey}` creates or
|
|
35
|
+
resumes the linked artifacts using the normal permissions. Preserve idempotencyKey across retries;
|
|
36
|
+
requestId is reserved by the transport.
|
|
37
|
+
For supported canvas mutations inspect actual `updated` IDs, `outcome`, ignored/rejected fields
|
|
38
|
+
and warnings. A navigation request reaching a socket is not a verified editor display.
|
|
39
|
+
|
|
28
40
|
Once `cd`'d, **every** verb auto-routes to that target: `actions`, `call`, `sql`, `ls`, `cat`, `tree`, `grep`, `watch`, `logs`, `workspace`, `ask`. Switch by `cd`-ing again.
|
|
29
41
|
|
|
30
42
|
```bash
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brand-identity
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
4
|
+
Design the requested logo or brand identity on a Clearly canvas —
|
|
5
5
|
brief, mark, wordmark, lockups and clear space, palette, type system,
|
|
6
6
|
applications, and a spec board someone else can build from. Use when asked
|
|
7
7
|
for a brand, a logo, an identity, a rebrand, a visual system, brand
|
|
@@ -13,10 +13,14 @@ description: >-
|
|
|
13
13
|
|
|
14
14
|
# brand-identity — a system, not a picture of a logo
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
Match the user's scope. For a logo concept, deliver the editable mark/wordmark, primary and
|
|
17
|
+
one-color use, a small-size check, and the requested application. A logo plus landing page does
|
|
18
|
+
not require merchandise, storefronts or a complete guideline deck. Add those when requested or
|
|
19
|
+
when a concrete use in the brief requires them.
|
|
20
|
+
|
|
21
|
+
For a complete identity request, the deliverable is a system someone else can build with:
|
|
22
|
+
symbol, wordmark, palette, typography, applications and guidelines. Sections 6–8 describe that
|
|
23
|
+
larger scope; they are optional for a narrow concept.
|
|
20
24
|
|
|
21
25
|
Read **`design-craft`** first or alongside — grid, type scale, colour roles and the studio
|
|
22
26
|
pass are assumed here.
|
|
@@ -93,7 +97,9 @@ good. If it does not work in flat black, the shape is wrong and the gradient is
|
|
|
93
97
|
- ⚠ **A wordmark is always tracked tighter than body text.** At display size, `letterSpacing`
|
|
94
98
|
−1 to −4. Then fix the specific pairs the font gets wrong (an uppercase A next to a V, a
|
|
95
99
|
T next to a lowercase o).
|
|
96
|
-
- **
|
|
100
|
+
- **For a full identity, define this fixed set.** For a concept, deliver the requested lockup
|
|
101
|
+
plus the mark-only and one-color forms needed for its smallest use. Additional lockups are
|
|
102
|
+
optional unless the brief asks for them:
|
|
97
103
|
|
|
98
104
|
```
|
|
99
105
|
primary mark + wordmark, horizontal ← the default
|
|
@@ -225,11 +231,11 @@ Run `design-craft`'s studio pass, plus these identity-specific ones:
|
|
|
225
231
|
```
|
|
226
232
|
□ Rendered the mark at 24px and LOOKED at it
|
|
227
233
|
□ Mark works in flat black, flat white, and knocked out
|
|
228
|
-
□ Every lockup has stated clear space and minimum size
|
|
234
|
+
□ Every delivered lockup has stated clear space and minimum size
|
|
229
235
|
□ Palette has a dark-ground variant and role sentences
|
|
230
236
|
□ Type scale has NUMBERS, not just face names
|
|
231
|
-
□
|
|
232
|
-
□ Misuse section exists
|
|
237
|
+
□ Requested applications at real proportions (4–6 for a full identity; the requested surface for a concept)
|
|
238
|
+
□ Misuse section exists for a full identity; a concise use note is enough for a logo concept
|
|
233
239
|
□ Put the mark beside the competitor named in UNLIKE — still distinct?
|
|
234
240
|
□ Can someone build a new asset from this board without asking a question?
|
|
235
241
|
```
|
|
@@ -75,6 +75,17 @@ the admitted agent in the workspace, and soul reads/updates report whether the d
|
|
|
75
75
|
wearing the drawing or a preset. If the capability line is absent, it is disabled for that
|
|
76
76
|
deployment; the existing companion and presets remain available.
|
|
77
77
|
|
|
78
|
+
## End with a handoff
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
beehaven agent handoff "what you did · what is next · anything blocked"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The handoff is the note your next login, or the next agent you hand the work to, reads first,
|
|
85
|
+
under **Last time you were here**. Write it for a colleague who has none of your context: what changed,
|
|
86
|
+
where it is (ticket keys, document titles), what is next and what is blocked. It ends the work session
|
|
87
|
+
and keeps your identity bound. Without one, the next brief can show only the session's label and counts.
|
|
88
|
+
|
|
78
89
|
⚠ **Do not call `agent-login` immediately after `beehaven agent login`.** The RPC is an alias of
|
|
79
90
|
`agent-session-start`; doing both creates two sessions. Call it directly only when deliberately starting
|
|
80
91
|
or resuming an additional workspace session.
|
|
@@ -118,8 +129,8 @@ item queued. Every comment/description write returns `mentions`, `agentsQueued`
|
|
|
118
129
|
`beehaven comment <ref>` takes any id, key or exact title and the surface's anchor:
|
|
119
130
|
`--quote` (document passage) · `--slide 3` (deck, required) · `--cell B4` / `--cell "Tab!B4"` (sheet) ·
|
|
120
131
|
`--column "In review"` (board) · `--pin <id>` or `--x/--y` (canvas) · `--reply <id>`. An anchor a
|
|
121
|
-
surface cannot carry is **refused**, never dropped. Over MCP: `
|
|
122
|
-
|
|
132
|
+
surface cannot carry is **refused**, never dropped. Over MCP: `clearly_comment { target, content,
|
|
133
|
+
fields }`, `clearly_read { type:"comment", target }`, and `target:"@me"` for mentions.
|
|
123
134
|
|
|
124
135
|
## Inbox — people and agents message you, and you message them
|
|
125
136
|
|
|
@@ -140,7 +151,7 @@ beehaven inbox who # every handle you can put in <
|
|
|
140
151
|
|
|
141
152
|
Over MCP, `type: "message"` on the same tools: `clearly_read { type: "message", target: "@inbox" }`
|
|
142
153
|
lists your threads (`box`, `query`), `target: "@people"` lists who you can message, and a thread id
|
|
143
|
-
opens it (marks read). `
|
|
154
|
+
opens it (marks read). `clearly_comment { type: "message", fields: { to: ["ada"] }, content }` starts a
|
|
144
155
|
thread; with `target: <threadId>` it replies. `clearly_edit { changes: { read | starred | muted } }`
|
|
145
156
|
changes your view; `clearly_delete` archives (`restore: true` brings it back).
|
|
146
157
|
|
|
@@ -179,6 +190,19 @@ task changes a product's UI, the composition comes FIRST: build the screen and i
|
|
|
179
190
|
`canvas.review` it, link it to the ticket (`work-link {ticketId, kind:"design", refKind:"composition",
|
|
180
191
|
refId}`), then implement. `clearly-canvas` §0 has the details.
|
|
181
192
|
|
|
193
|
+
No brand yet (`brand-tokens` answers `brand-not-found`) for a real company? Ask the person for their
|
|
194
|
+
website rather than inventing a palette, then:
|
|
195
|
+
|
|
196
|
+
1. `beehaven call brand-import '{"url":"theirsite.com","dryRun":true}'` — colours by role, faces, logos,
|
|
197
|
+
voice and shape, each with its reason in `provenance`. Tell the person what you read.
|
|
198
|
+
2. Ask what to call the brand (`suggestedName` is what the site calls itself — a suggestion, not a default).
|
|
199
|
+
3. `beehaven call brand-import '{"url":"theirsite.com","name":"<their answer>"}'` saves it (the read is
|
|
200
|
+
reused; the first brand becomes the workspace default). Without `name` nothing is saved:
|
|
201
|
+
`brand-name-required`.
|
|
202
|
+
|
|
203
|
+
`brandId` + `apply:["colors","type"]` refreshes an existing brand as a new version instead. Licensed faces
|
|
204
|
+
come back set in the closest free one — `faces` says which.
|
|
205
|
+
|
|
182
206
|
## When a write is refused
|
|
183
207
|
|
|
184
208
|
`agent-denied-create` / `-update` / `-delete` mean your **capability matrix** said no. It covers the
|
|
@@ -22,6 +22,10 @@ stored. A rendered image you have inspected is the evidence for visual quality.
|
|
|
22
22
|
For a small correction, keep the existing direction and make the smallest useful edit.
|
|
23
23
|
For a new composition or substantial redesign, use the workflow below.
|
|
24
24
|
|
|
25
|
+
Keep the requested scope. A landing concept with a logo needs its page layouts and a usable
|
|
26
|
+
logo; add application boards or a full guideline system only when the brief calls for them.
|
|
27
|
+
Read only relevant references. Save large tool responses once and print compact summaries.
|
|
28
|
+
|
|
25
29
|
## The studio bar — what "done" means, every time
|
|
26
30
|
|
|
27
31
|
Load **`design-craft`** before any new visual work: grid, type scale, colour roles,
|
|
@@ -33,8 +37,9 @@ A composition is done when every requested artboard meets all four conditions:
|
|
|
33
37
|
1. **`canvas.review` verdict is `clean` or `look`.** `fix` means a probable defect
|
|
34
38
|
remains: text colliding or covered, contrast under WCAG (4.5:1 body, 3:1 display),
|
|
35
39
|
type below the artboard's legible floor, missing media, text overflowing its box,
|
|
36
|
-
placeholder copy. Fix it.
|
|
37
|
-
`
|
|
40
|
+
placeholder copy. Fix it. For an intentional finding, use
|
|
41
|
+
`accept:[{check,ids:[node IDs],reason}]`. Accepted findings stay visible and return `look`.
|
|
42
|
+
Legacy `ignore` can suppress multiple checks and is not an unconditional pass.
|
|
38
43
|
2. **Every `look` finding was looked at**: type-size and palette sprawl, near-miss
|
|
39
44
|
alignment, copy against the trim, text over imagery. Act on it or say why not.
|
|
40
45
|
3. **You inspected the rendered PNG** against design-craft's studio pass, covering what no
|
|
@@ -54,8 +59,9 @@ build from them — redrawing a button the library already has is how designs dr
|
|
|
54
59
|
product they describe.
|
|
55
60
|
|
|
56
61
|
```sh
|
|
57
|
-
beehaven call brand-tokens '{}'
|
|
58
|
-
beehaven call
|
|
62
|
+
beehaven call brand-tokens '{"compact":true}' # roles without repeated CSS/prose
|
|
63
|
+
beehaven call brand-import '{"url":"theirsite.com","dryRun":true}' # no brand yet? read it off their website
|
|
64
|
+
beehaven call component-list '{"query":"<kind>","compact":true}' # IDs, dimensions and prop names
|
|
59
65
|
beehaven call component-instantiate '{"compositionId":"<id>","instances":[
|
|
60
66
|
{"id":"<component>","x":96,"y":200,"variant":"Light"},
|
|
61
67
|
{"id":"<component>","x":96,"y":280,"variant":"Light","props":{"label":"Save changes"}}]}'
|
|
@@ -68,8 +74,9 @@ beehaven call component-instantiate '{"compositionId":"<id>","instances":[
|
|
|
68
74
|
`component-create` (or `{ fromCompositionId, nodeId }` to save one you drew).
|
|
69
75
|
- Component node coordinates are relative to the component ROOT, even under an inner
|
|
70
76
|
frame — the opposite of `canvas.create-node`, where x/y follow `parentId`.
|
|
71
|
-
-
|
|
72
|
-
and
|
|
77
|
+
- For Clearly product UI, use its brand kit. For a separate client, use that client's kit
|
|
78
|
+
and supplied assets. A requested new client identity is scoped to that composition;
|
|
79
|
+
it does not change the workspace's default brand.
|
|
73
80
|
|
|
74
81
|
**UX design before implementation.** When the work is a change to a product's UI, the
|
|
75
82
|
composition is the design the code will answer to: show the real states (empty, loading,
|
|
@@ -107,6 +114,33 @@ beehaven call canvas-perceive '{"compositionId":"<id>","format":"json"}'
|
|
|
107
114
|
beehaven call canvas-catalog '{"query":"create-node"}'
|
|
108
115
|
```
|
|
109
116
|
|
|
117
|
+
A canvas is planned like a git repository: its own plan board, a WORK TICKET for the request, and
|
|
118
|
+
COMMITS — one ticket per coherent layer or section of a larger change.
|
|
119
|
+
`design-start {title, brandId?}` makes a PRIVATE DRAFT canvas, bound to the brand, with its private
|
|
120
|
+
plan board and the work ticket, and returns `{ compositionId, ticketId, ticketKey, boardId, brand }`.
|
|
121
|
+
Same title again returns the same draft. Nothing is shared until it is filed:
|
|
122
|
+
`design-file {compositionId, projectId | projectTitle}` moves the canvas — plan board and every
|
|
123
|
+
ticket with it — into the project the person chose and sends the work ticket to in-review. Ask which
|
|
124
|
+
project unless they named one; a close-but-different title answers `similar-projects` with
|
|
125
|
+
candidates. Repeat-safe.
|
|
126
|
+
Plan a larger change before drawing, the way you would split a big code change into commits:
|
|
127
|
+
`design-plan {compositionId, commits: ["Grid, type and colour styles", "Nav", "Hero", …]}` (drafts
|
|
128
|
+
and filed canvases alike) adds ordered commit tickets under the work ticket. `sections: true` uses
|
|
129
|
+
the landing-page set (set up frames and styles, nav bar, hero, highlights, offer, story, call to
|
|
130
|
+
action, footer, mobile, states); `phases: true` and a `brief` add the studio method (brief, research,
|
|
131
|
+
structure, direction, design, review) — load the `design-process` skill. Order commits so each
|
|
132
|
+
builds on the last and can be reviewed alone. Build ONE commit per canvas-act batch that ends with
|
|
133
|
+
`canvas.review` of the artboard and carries
|
|
134
|
+
`"ticket":{"id":"<key>","status":"done","resolution":"<what changed>"}` — it lands only if that
|
|
135
|
+
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:
|
|
137
|
+
see "Technique" in [authoring.md](authoring.md).
|
|
138
|
+
|
|
139
|
+
`composition-board-get {compositionId}` reads the plan back. `design-start` with a project, ticket or
|
|
140
|
+
`phases` still sets up planned work directly (kept for one release).
|
|
141
|
+
For an existing task, reuse its ticket/project. Check one unknown action with
|
|
142
|
+
`beehaven explain <action>` or a filtered catalog; do not download the entire registry.
|
|
143
|
+
|
|
110
144
|
Use the composition supplied by the user. If none is named, inspect recent titles and
|
|
111
145
|
context; create a clearly titled composition for new work instead of modifying an
|
|
112
146
|
arbitrary recent canvas. File shared work into its project.
|
|
@@ -188,6 +222,10 @@ For a single page, establish the complete hierarchy before polishing small detai
|
|
|
188
222
|
Use a batch for each coherent build or revision, with descriptive unique layer names.
|
|
189
223
|
Parent content to its artboard. Inspect every operation result and rollback status.
|
|
190
224
|
Save your builder and returned IDs so revisions are targeted and reproducible.
|
|
225
|
+
Check `ignoredFields`, `rejectedFields`, `warnings`, `outcome` and `canvasChanges` as well as
|
|
226
|
+
`ok/count`. `matched` is not `updated`. An unchanged or partly ignored request does not prove
|
|
227
|
+
the intended edit happened. Read `composition-detail {includeScene:true}` from its top-level
|
|
228
|
+
`nodes`; never treat a missing response field as an empty scene. Keep executed payloads immutable.
|
|
191
229
|
|
|
192
230
|
⚠ **Batch, never loop.** One `canvas-act` batch of hundreds of steps is one scene read,
|
|
193
231
|
persisted every 50 steps, and the workspace stays responsive for everyone in it while it
|
|
@@ -195,6 +233,15 @@ runs. The same steps sent as hundreds of separate calls each re-read the whole s
|
|
|
195
233
|
call fails with a dropped connection, read the scene (`composition-detail
|
|
196
234
|
{id, includeScene:true}`) before retrying: the work may have landed.
|
|
197
235
|
|
|
236
|
+
⚠ **One batch per canvas at a time.** A reply that times out ("outcome unknown") or drops does
|
|
237
|
+
not stop the batch — it is still running on the workspace. Resending at once used to start a
|
|
238
|
+
second, slow, unprotected batch and could half-apply both (CA-43). The workspace now queues a
|
|
239
|
+
second writer behind the first and answers `code: "canvas-busy"` (with the running batch's
|
|
240
|
+
progress) if it waits too long — nothing from that call was applied. Either way: read the scene,
|
|
241
|
+
then send only what is missing. A ~1,100-step build takes about 40 s; the CLI waits 180 s by
|
|
242
|
+
default and `--timeout <ms>` (or `BEEHAVEN_RPC_TIMEOUT_MS`) raises it. Never place component
|
|
243
|
+
instances after a build whose reply you did not see.
|
|
244
|
+
|
|
198
245
|
Keep review notes and status outside the delivered artwork. Use
|
|
199
246
|
[communications.md](communications.md) only when posting those blocks is useful.
|
|
200
247
|
|
|
@@ -216,6 +263,12 @@ It invokes `canvas.review` on one persisted snapshot and returns:
|
|
|
216
263
|
type and palette sprawl, near-miss edges, trim crowding, text over imagery;
|
|
217
264
|
- the placeholder/overflow audit, measured text fit, and a preview PNG.
|
|
218
265
|
|
|
266
|
+
For several artboards, write their IDs/names as a JSON array and use:
|
|
267
|
+
`node <skill-directory>/review.mjs --many <composition-id> <ids.json> <output-directory>`.
|
|
268
|
+
This uses `canvas.review-many`: one scene read, separate findings per artboard and compact
|
|
269
|
+
preview handles. Review only artboards changed since their verified snapshot. The helper versions
|
|
270
|
+
existing output paths so before/after evidence is retained.
|
|
271
|
+
|
|
219
272
|
The helper prints the verdict and every finding. Work until the verdict is `clean` or
|
|
220
273
|
`look`, as described in the studio bar above. If the installed worker lacks `canvas.review`,
|
|
221
274
|
use a real frame ID with `--perceive` for a preview-only check and run the text measurement
|
|
@@ -223,9 +276,16 @@ and audit separately. A failed or missing render means visual verification is in
|
|
|
223
276
|
|
|
224
277
|
```sh
|
|
225
278
|
# A deliberate finding — say why in the handoff:
|
|
226
|
-
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame>"],"
|
|
279
|
+
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame>"],"accept":[{"check":"text-over-image","ids":["<text-id>","<image-id>"],"reason":"Intentional type over this photograph; rendered contrast was inspected."}]}}'
|
|
227
280
|
```
|
|
228
281
|
|
|
282
|
+
Asset storage is a reference-aware pipeline: an SVG may have `svgRef` instead of inline markup.
|
|
283
|
+
Do not shrink or recreate it just because the inline field is absent. Replace it with
|
|
284
|
+
`canvas.update-nodes {ids, patch:{svg:<complete markup>}}`; references are managed by the action.
|
|
285
|
+
Use `canvas.fonts` before choosing an unfamiliar family/weight. Fallback metrics are not exact.
|
|
286
|
+
Use `canvas.editor-state {requireLive:true,includePixels:true}` to verify the responding editor.
|
|
287
|
+
An agent camera and a navigation delivery count do not prove the person's view was framed.
|
|
288
|
+
|
|
229
289
|
Inspect the actual image and identify concrete defects, not generic praise:
|
|
230
290
|
|
|
231
291
|
| Lens | What to judge |
|