@clearlysh/beehaven 0.8.31 → 0.8.32
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/agents-md-text.js +64 -0
- package/dist/browser-login.js +6 -1
- package/dist/build-channel.js +2 -2
- package/dist/cli.js +18 -0
- package/dist/commands/agents-md.js +118 -0
- package/dist/commands/help.js +21 -0
- package/dist/daemon.js +24 -10
- package/dist/dashboard.js +118 -6
- package/dist/ipc-server.js +11 -8
- package/dist/verb-surface.js +1 -0
- package/package.json +1 -1
- package/skills/agent-doodle-space/SKILL.md +1 -1
- package/skills/agent-home/SKILL.md +1 -1
- package/skills/brand-identity/SKILL.md +1 -0
- package/skills/clearly-agent/SKILL.md +1 -1
- package/skills/clearly-canvas/SKILL.md +1 -0
- package/skills/clearly-workflows/SKILL.md +1 -0
- package/skills/clearly-workspace/SKILL.md +1 -1
- package/skills/codebase-map/SKILL.md +1 -1
- package/skills/index.json +11 -11
- package/skills/pair-on-canvas/SKILL.md +1 -1
- package/skills/sticker-pack/SKILL.md +1 -1
- package/skills/visualize/SKILL.md +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agents-md-text — the Clearly block for a PROJECT's AGENTS.md, as `beehaven agents-md` writes it
|
|
3
|
+
* (CAN2-66).
|
|
4
|
+
*
|
|
5
|
+
* ⚠⚠ A COPY, PINNED. The source of truth is the monorepo's `lib/connections/agentsMd.ts`, which the
|
|
6
|
+
* Integrations page, each agent's details and the MCP `clearly-agents-md` prompt render. This
|
|
7
|
+
* package cannot import it (it ships on its own), so the text is repeated here and the monorepo's
|
|
8
|
+
* `lib/__tests__/agentsMdCliParity.test.mjs` compares the two OUTPUTS, name argument included.
|
|
9
|
+
* Change both or the test fails — a stale copy does not render wrong, it instructs wrong.
|
|
10
|
+
*
|
|
11
|
+
* ⚠ NO IMPORTS, so that test can load this file directly.
|
|
12
|
+
*/
|
|
13
|
+
export const CLI_BLOCK_START = '<!-- clearly:cli:start -->';
|
|
14
|
+
export const CLI_BLOCK_END = '<!-- clearly:cli:end -->';
|
|
15
|
+
export const MCP_BLOCK_START = '<!-- clearly:mcp:start -->';
|
|
16
|
+
export const MCP_BLOCK_END = '<!-- clearly:mcp:end -->';
|
|
17
|
+
/** What goes in CLAUDE.md so Claude Code reads the AGENTS.md block too. */
|
|
18
|
+
export const CLAUDE_MD_IMPORT = '@AGENTS.md';
|
|
19
|
+
/** The block for agents that use the `beehaven` CLI. */
|
|
20
|
+
export function agentsMdCliBlock(agentName) {
|
|
21
|
+
const name = (agentName ?? '').trim();
|
|
22
|
+
const login = name
|
|
23
|
+
? `\`beehaven agent login ${name} --label "<what you are doing>"\` — you are ${name} in this workspace.`
|
|
24
|
+
: '`beehaven agent login <name> --label "<what you are doing>"`, using your own name (claude, codex, gemini).';
|
|
25
|
+
return [
|
|
26
|
+
CLI_BLOCK_START,
|
|
27
|
+
'<!-- managed-by: clearly. Run `beehaven agents-md` to update; edit outside this block. -->',
|
|
28
|
+
'',
|
|
29
|
+
'## Clearly workspace',
|
|
30
|
+
'',
|
|
31
|
+
'This project keeps its documents, plans, boards and tickets in Clearly. Use the `beehaven` CLI',
|
|
32
|
+
'for anything the team should see, not files in this repo.',
|
|
33
|
+
'',
|
|
34
|
+
`- Sign in once per session: ${login}`,
|
|
35
|
+
' If it says the daemon is not running: `beehaven start --headless`.',
|
|
36
|
+
'- Read the `clearly-agent` skill before anything else, and `clearly-canvas` before design work',
|
|
37
|
+
' (`beehaven skills install` puts them in place).',
|
|
38
|
+
'- `beehaven actions` lists what you can call; `beehaven call <action> \'<json>\'` calls it.',
|
|
39
|
+
'- Search before you create, file work in the project it belongs to, and end the session with',
|
|
40
|
+
' `beehaven agent handoff "what you did · what is next"` so the next one starts there.',
|
|
41
|
+
CLI_BLOCK_END,
|
|
42
|
+
].join('\n');
|
|
43
|
+
}
|
|
44
|
+
/** The block for agents connected over MCP (the `clearly` server). */
|
|
45
|
+
export function agentsMdMcpBlock() {
|
|
46
|
+
return [
|
|
47
|
+
MCP_BLOCK_START,
|
|
48
|
+
'<!-- managed-by: clearly-mcp. Re-run the `clearly-agents-md` prompt to update; edit outside this block. -->',
|
|
49
|
+
'',
|
|
50
|
+
'## Clearly workspace (connected over MCP as `clearly`)',
|
|
51
|
+
'',
|
|
52
|
+
'This project keeps its documents, plans, boards and tickets in Clearly. Use the `clearly_*`',
|
|
53
|
+
'tools, not files in this repo, for anything the team should see.',
|
|
54
|
+
'',
|
|
55
|
+
'- Start with `clearly_catalog` to see what exists; search with `clearly_grep` / `clearly_glob`.',
|
|
56
|
+
'- Read with `clearly_read`, make new work with `clearly_create`, change it with `clearly_edit`,',
|
|
57
|
+
' and discuss it with `clearly_comment`. They take a `target`: a path (`~/Q1/Plan.md`), a key',
|
|
58
|
+
' (`CLR-42`), an id or an exact title. The extension names the type (`.md` document, `.csv`',
|
|
59
|
+
' sheet, `.board.json` board); a trailing slash means a project.',
|
|
60
|
+
'- Search before you write, and write decisions back so the next session inherits them.',
|
|
61
|
+
'- If a job is not covered by a listed tool, say so rather than guessing an action name.',
|
|
62
|
+
MCP_BLOCK_END,
|
|
63
|
+
].join('\n');
|
|
64
|
+
}
|
package/dist/browser-login.js
CHANGED
|
@@ -265,7 +265,12 @@ export function persistBrowserLogin(captured) {
|
|
|
265
265
|
userId: captured.userId,
|
|
266
266
|
email: captured.email ?? null,
|
|
267
267
|
displayName: captured.displayName ?? null,
|
|
268
|
-
|
|
268
|
+
/**
|
|
269
|
+
* ⚠ SAY WHERE THE TOKEN REALLY IS (CAN2-65). This was always 'keychain', so on Windows —
|
|
270
|
+
* which has no keychain here — `whoami` reported a keychain that did not exist, and
|
|
271
|
+
* `Relay.loadConfig` / the refresh path shelled out to macOS `security` on every load.
|
|
272
|
+
*/
|
|
273
|
+
tokenStorage: keychain.available ? 'keychain' : 'file',
|
|
269
274
|
refreshToken: captured.refreshToken,
|
|
270
275
|
token: captured.idToken,
|
|
271
276
|
endpoint: dialUrl,
|
package/dist/build-channel.js
CHANGED
|
@@ -53,9 +53,9 @@ export const PUBLIC_BUILD = true;
|
|
|
53
53
|
* ⚠ REWRITTEN IN `dist/` AT BUILD TIME, like `PUBLIC_BUILD`. `unstamped` is what a source checkout
|
|
54
54
|
* carries and what `tsx`/`vitest` see, and it is honest: nothing has stamped them.
|
|
55
55
|
*/
|
|
56
|
-
export const BUILD_SHA = '
|
|
56
|
+
export const BUILD_SHA = '3152df6cf1d7-dirty';
|
|
57
57
|
/** ISO-8601 minute precision, UTC. Rewritten in `dist/` by `scripts/stamp-build-channel.mjs`. */
|
|
58
|
-
export const BUILD_TIME = '2026-10-
|
|
58
|
+
export const BUILD_TIME = '2026-10-13T08:55Z';
|
|
59
59
|
/**
|
|
60
60
|
* The build identity as one short string, for `--version`.
|
|
61
61
|
*
|
package/dist/cli.js
CHANGED
|
@@ -143,6 +143,7 @@ function printHelp() {
|
|
|
143
143
|
|
|
144
144
|
beehaven guide Agent-first setup and signup handoff
|
|
145
145
|
beehaven signup Create a free Clearly account
|
|
146
|
+
beehaven agents-md Add Clearly to this project's AGENTS.md
|
|
146
147
|
|
|
147
148
|
Personal workspace:
|
|
148
149
|
beehaven cd home
|
|
@@ -956,6 +957,11 @@ const AGENT_LOGIN_EXEMPT = new Set([
|
|
|
956
957
|
* would refuse the one person it is for — the agent whose user has not signed up yet (SGS-73).
|
|
957
958
|
*/
|
|
958
959
|
'handoff',
|
|
960
|
+
/**
|
|
961
|
+
* ⚠ `agents-md` WRITES A FILE IN THIS REPO AND NOTHING ELSE — it never opens a workspace, and it is
|
|
962
|
+
* part of setting a project up, which must not require the login it helps a teammate do (CAN2-66).
|
|
963
|
+
*/
|
|
964
|
+
'agents-md',
|
|
959
965
|
]);
|
|
960
966
|
/**
|
|
961
967
|
* ⚠ ALIAS → CANONICAL VERB, for the activity log's `source` column only.
|
|
@@ -1371,6 +1377,9 @@ async function run() {
|
|
|
1371
1377
|
state: { type: 'string' },
|
|
1372
1378
|
goal: { type: 'string' },
|
|
1373
1379
|
remove: { type: 'boolean', default: false },
|
|
1380
|
+
// `agents-md`: the MCP block instead of the CLI one; print it instead of writing it (CAN2-66).
|
|
1381
|
+
mcp: { type: 'boolean', default: false },
|
|
1382
|
+
print: { type: 'boolean', default: false },
|
|
1374
1383
|
/**
|
|
1375
1384
|
* ⚠⚠ `beehaven sheet` / `beehaven slides` — AND AN UNDECLARED FLAG IS A FATAL, NOT A NO-OP.
|
|
1376
1385
|
*
|
|
@@ -3145,6 +3154,15 @@ async function run() {
|
|
|
3145
3154
|
console.log(' (or quit and relaunch the Hive Mac app if it owns the daemon).');
|
|
3146
3155
|
break;
|
|
3147
3156
|
}
|
|
3157
|
+
case 'agents-md': {
|
|
3158
|
+
const { cmdAgentsMd } = await import('./commands/agents-md.js');
|
|
3159
|
+
process.exitCode = cmdAgentsMd(positionals[1], {
|
|
3160
|
+
mcp: !!values.mcp,
|
|
3161
|
+
print: !!values.print,
|
|
3162
|
+
remove: !!values.remove,
|
|
3163
|
+
});
|
|
3164
|
+
break;
|
|
3165
|
+
}
|
|
3148
3166
|
case 'handoff': {
|
|
3149
3167
|
const { cmdHandoff } = await import('./commands/handoff.js');
|
|
3150
3168
|
process.exitCode = await cmdHandoff(positionals[1], {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* beehaven agents-md — put the Clearly block in THIS project's AGENTS.md (CAN2-66).
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ TWO FILES, TWO AUDIENCES. `beehaven login` already writes a GLOBAL block into
|
|
5
|
+
* ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md and ~/.gemini/GEMINI.md (agent-instructions.ts), so every
|
|
6
|
+
* agent on THIS machine knows Clearly. A project's AGENTS.md is for everyone else who opens the
|
|
7
|
+
* repo — a teammate's Codex, a cloud agent, CI — and it is committed and shared. That is why this
|
|
8
|
+
* is its own command the person (or their agent, asked first) runs, not a side effect of install.
|
|
9
|
+
*
|
|
10
|
+
* ⚠ THE MARKERS ARE THE UPDATE MECHANISM, as on every other surface (lib/connections/agentsMd.ts):
|
|
11
|
+
* re-running replaces only what lies between our markers and leaves the rest byte for byte. The
|
|
12
|
+
* CLI and MCP blocks have their own markers, so a repo used both ways keeps both.
|
|
13
|
+
*
|
|
14
|
+
* ⚠ AGENTS.md IS THE FILE; CLAUDE.md POINTS AT IT with one `@AGENTS.md` line — one copy to keep
|
|
15
|
+
* current. An existing CLAUDE.md gains the line only if it lacks it; nothing else in it is touched.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
18
|
+
import { join, resolve } from 'path';
|
|
19
|
+
import { execFileSync } from 'child_process';
|
|
20
|
+
import { color } from '../spinner.js';
|
|
21
|
+
import { CLI_BLOCK_START, CLI_BLOCK_END, MCP_BLOCK_START, MCP_BLOCK_END, CLAUDE_MD_IMPORT, agentsMdCliBlock, agentsMdMcpBlock, } from '../agents-md-text.js';
|
|
22
|
+
/** Our block replaced between its markers, or appended after one blank line. Keeps CRLF files CRLF. */
|
|
23
|
+
export function spliceBlock(text, block, start, end) {
|
|
24
|
+
const eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
25
|
+
const b = block.split('\n').join(eol);
|
|
26
|
+
const i = text.indexOf(start);
|
|
27
|
+
const j = i >= 0 ? text.indexOf(end, i) : -1;
|
|
28
|
+
if (i >= 0 && j >= 0)
|
|
29
|
+
return text.slice(0, i) + b + text.slice(j + end.length);
|
|
30
|
+
if (!text.trim())
|
|
31
|
+
return b + eol;
|
|
32
|
+
return text.replace(/\s*$/, '') + eol + eol + b + eol;
|
|
33
|
+
}
|
|
34
|
+
/** Our block taken out, with the blank line that separated it. Everything else stays. */
|
|
35
|
+
export function removeBlock(text, start, end) {
|
|
36
|
+
const i = text.indexOf(start);
|
|
37
|
+
const j = i >= 0 ? text.indexOf(end, i) : -1;
|
|
38
|
+
if (i < 0 || j < 0)
|
|
39
|
+
return text;
|
|
40
|
+
const before = text.slice(0, i).replace(/(\r?\n)*$/, '');
|
|
41
|
+
const after = text.slice(j + end.length).replace(/^(\r?\n)*/, '');
|
|
42
|
+
const eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
43
|
+
if (!before)
|
|
44
|
+
return after;
|
|
45
|
+
return after ? before + eol + eol + after : before + eol;
|
|
46
|
+
}
|
|
47
|
+
/** The repository root when `dir` is inside a git work tree, else `dir` itself. */
|
|
48
|
+
export function projectRoot(dir) {
|
|
49
|
+
try {
|
|
50
|
+
const top = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
51
|
+
cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, timeout: 5_000,
|
|
52
|
+
}).trim();
|
|
53
|
+
return top || dir;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return dir;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Write (or remove) the block in `<root>/AGENTS.md` and make CLAUDE.md import it. */
|
|
60
|
+
export function writeProjectAgentsMd(opts) {
|
|
61
|
+
const root = projectRoot(resolve(opts.dir));
|
|
62
|
+
const [start, end] = opts.mcp ? [MCP_BLOCK_START, MCP_BLOCK_END] : [CLI_BLOCK_START, CLI_BLOCK_END];
|
|
63
|
+
const agentsPath = join(root, 'AGENTS.md');
|
|
64
|
+
const claudePath = join(root, 'CLAUDE.md');
|
|
65
|
+
const files = [];
|
|
66
|
+
const had = existsSync(agentsPath);
|
|
67
|
+
const before = had ? readFileSync(agentsPath, 'utf8') : '';
|
|
68
|
+
if (opts.remove) {
|
|
69
|
+
const next = removeBlock(before, start, end);
|
|
70
|
+
if (next !== before)
|
|
71
|
+
writeFileSync(agentsPath, next);
|
|
72
|
+
files.push({ path: agentsPath, change: next !== before ? 'removed' : 'absent' });
|
|
73
|
+
return { root, files };
|
|
74
|
+
}
|
|
75
|
+
const next = spliceBlock(before, opts.mcp ? agentsMdMcpBlock() : agentsMdCliBlock(), start, end);
|
|
76
|
+
if (next !== before)
|
|
77
|
+
writeFileSync(agentsPath, next);
|
|
78
|
+
files.push({ path: agentsPath, change: !had ? 'created' : next !== before ? 'updated' : 'current' });
|
|
79
|
+
const claude = existsSync(claudePath) ? readFileSync(claudePath, 'utf8') : null;
|
|
80
|
+
if (claude === null) {
|
|
81
|
+
writeFileSync(claudePath, CLAUDE_MD_IMPORT + '\n');
|
|
82
|
+
files.push({ path: claudePath, change: 'created' });
|
|
83
|
+
}
|
|
84
|
+
else if (!claude.split(/\r?\n/).some((l) => l.trim() === CLAUDE_MD_IMPORT)) {
|
|
85
|
+
const eol = claude.includes('\r\n') ? '\r\n' : '\n';
|
|
86
|
+
writeFileSync(claudePath, claude.replace(/\s*$/, '') + (claude.trim() ? eol + eol : '') + CLAUDE_MD_IMPORT + eol);
|
|
87
|
+
files.push({ path: claudePath, change: 'updated' });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
files.push({ path: claudePath, change: 'current' });
|
|
91
|
+
}
|
|
92
|
+
return { root, files };
|
|
93
|
+
}
|
|
94
|
+
export function cmdAgentsMd(dir, opts) {
|
|
95
|
+
if (opts.print) {
|
|
96
|
+
process.stdout.write((opts.mcp ? agentsMdMcpBlock() : agentsMdCliBlock()) + '\n');
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
let r;
|
|
100
|
+
try {
|
|
101
|
+
r = writeProjectAgentsMd({ dir: dir ?? process.cwd(), mcp: opts.mcp, remove: opts.remove });
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error(` ${color.red('✗')} ${err?.message ?? err}`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
console.log('');
|
|
108
|
+
for (const f of r.files) {
|
|
109
|
+
const mark = f.change === 'current' || f.change === 'absent' ? color.dim('·') : color.green('✓');
|
|
110
|
+
console.log(` ${mark} ${f.path} ${color.dim(f.change)}`);
|
|
111
|
+
}
|
|
112
|
+
const changed = r.files.some((f) => f.change !== 'current' && f.change !== 'absent');
|
|
113
|
+
if (changed && !opts.remove) {
|
|
114
|
+
console.log(color.dim(`\n Commit ${r.files.map((f) => f.path.slice(r.root.length + 1)).join(' and ')} so every agent that opens this project knows the workspace.`));
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
package/dist/commands/help.js
CHANGED
|
@@ -413,6 +413,27 @@ const PAGES = {
|
|
|
413
413
|
\`beehaven login\` to connect the CLI to it.`,
|
|
414
414
|
related: ['login', 'guide'],
|
|
415
415
|
},
|
|
416
|
+
'agents-md': {
|
|
417
|
+
synopsis: 'beehaven agents-md [dir] [--mcp] [--print] [--remove]',
|
|
418
|
+
description: `Add the Clearly block to this project's AGENTS.md, so every agent that opens the
|
|
419
|
+
repository — a teammate's Codex or Claude Code, a cloud agent, CI — knows the workspace and how
|
|
420
|
+
to reach it. Writes at the git root (or the directory given), and makes CLAUDE.md import it with
|
|
421
|
+
one \`@AGENTS.md\` line. Needs no login.
|
|
422
|
+
|
|
423
|
+
The block sits between Clearly's markers: run it again to update the block, and everything
|
|
424
|
+
else in the file stays as it was. \`beehaven login\` already teaches the agents on THIS machine
|
|
425
|
+
(their global instruction files); this is the shared, committed copy for everyone else.`,
|
|
426
|
+
flags: [
|
|
427
|
+
{ name: '--mcp', desc: 'Write the block for agents connected over MCP instead of the CLI' },
|
|
428
|
+
{ name: '--print', desc: 'Print the block and write nothing' },
|
|
429
|
+
{ name: '--remove', desc: 'Take the block out again' },
|
|
430
|
+
],
|
|
431
|
+
examples: [
|
|
432
|
+
{ cmd: 'beehaven agents-md', note: 'AGENTS.md + CLAUDE.md at the repository root' },
|
|
433
|
+
{ cmd: 'beehaven agents-md --mcp', note: 'for a team that connects over MCP' },
|
|
434
|
+
],
|
|
435
|
+
related: ['skills', 'login'],
|
|
436
|
+
},
|
|
416
437
|
handoff: {
|
|
417
438
|
synopsis: 'beehaven handoff [file.md|-] [--tasks tasks.md|json] [--csv data.csv]… [--title "…"] [--wait] [--json]',
|
|
418
439
|
description: `Give a person your work when they have no Clearly account yet. Needs no login.
|
package/dist/daemon.js
CHANGED
|
@@ -4116,7 +4116,7 @@ export async function startDaemon(opts) {
|
|
|
4116
4116
|
// so browser frontends become virtual IPC clients — same
|
|
4117
4117
|
// pipeline, app-rpc, broadcast fan-out as Claude's path.
|
|
4118
4118
|
dashboard.setAppBridge({
|
|
4119
|
-
createVirtualClient: (send, subs) => ipc.createVirtualClient(send, subs),
|
|
4119
|
+
createVirtualClient: (send, subs, scope) => ipc.createVirtualClient(send, subs, scope),
|
|
4120
4120
|
injectMessage: (id, msg) => ipc.injectMessage(id, msg),
|
|
4121
4121
|
releaseVirtualClient: (id) => ipc.releaseVirtualClient(id),
|
|
4122
4122
|
});
|
|
@@ -4141,15 +4141,29 @@ export async function startDaemon(opts) {
|
|
|
4141
4141
|
}
|
|
4142
4142
|
})();
|
|
4143
4143
|
// ── Dashboard (local observability UI) ──────────────────────────────
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4144
|
+
/**
|
|
4145
|
+
* ⚠⚠ A LOOPBACK HTTP SERVER IS OPEN TO EVERY USER AND PROGRAM ON THE MACHINE (CAN2-64), so a
|
|
4146
|
+
* daemon opens one only when something will use it. The Mac app's daemon does (the avatar UI
|
|
4147
|
+
* talks over /apps/ws); a HEADLESS daemon — the CLI on Windows and Linux, CI, servers — has no
|
|
4148
|
+
* window to show it in and, on a shared Windows machine, would hand every other signed-in user
|
|
4149
|
+
* a port into this one's daemon. It starts there only on request: BEEHAVEN_DASHBOARD=1,
|
|
4150
|
+
* `"dashboard": true` in ~/.beehaven/config.json, or developer mode.
|
|
4151
|
+
*/
|
|
4152
|
+
const headlessDaemon = process.env.BEEHAVEN_HEADLESS === '1' || process.env.BEEHAVEN_HEADLESS === 'true';
|
|
4153
|
+
const dashboardWanted = !headlessDaemon || developerMode || config.dashboard === true || process.env.BEEHAVEN_DASHBOARD === '1';
|
|
4154
|
+
if (!dashboardWanted) {
|
|
4155
|
+
console.log(' [dashboard] off in a headless daemon (set BEEHAVEN_DASHBOARD=1 to open it on 127.0.0.1)');
|
|
4156
|
+
}
|
|
4157
|
+
if (dashboardWanted)
|
|
4158
|
+
dashboard = new Dashboard({
|
|
4159
|
+
port: config.dashboardPort ?? 9800,
|
|
4160
|
+
host: '127.0.0.1',
|
|
4161
|
+
explorerTarget: `http://127.0.0.1:${config.devPort ?? 8787}`,
|
|
4162
|
+
explorerSecret: config.explorerSecret ?? config.adminSecret ?? '',
|
|
4163
|
+
projectDir: config.linkedProject ?? '',
|
|
4164
|
+
});
|
|
4151
4165
|
dashboard
|
|
4152
|
-
|
|
4166
|
+
?.start()
|
|
4153
4167
|
.then(() => {
|
|
4154
4168
|
console.log(` [dashboard] Beehaven Dashboard at ${dashboard.url}`);
|
|
4155
4169
|
})
|
|
@@ -4158,7 +4172,7 @@ export async function startDaemon(opts) {
|
|
|
4158
4172
|
});
|
|
4159
4173
|
// Forward daemon events to dashboard
|
|
4160
4174
|
watcher.on('event', (event) => {
|
|
4161
|
-
dashboard
|
|
4175
|
+
dashboard?.pushEvent({
|
|
4162
4176
|
type: event.hook_event_name || event.type || 'unknown',
|
|
4163
4177
|
timestamp: event.timestamp || Date.now(),
|
|
4164
4178
|
data: {
|
package/dist/dashboard.js
CHANGED
|
@@ -17,6 +17,59 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
17
17
|
import { join, dirname } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { WebSocketServer } from 'ws';
|
|
20
|
+
// ── Who may talk to this server (CAN2-64) ───────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* ⚠⚠ A LOOPBACK PORT IS NOT A PRIVATE CHANNEL. Every web page the person opens can dial
|
|
23
|
+
* `ws://127.0.0.1:9800` — WebSockets ignore CORS — and so can every program on the machine,
|
|
24
|
+
* another Windows or macOS user's included. `/apps/ws` used to hand EVERY connection a
|
|
25
|
+
* developer-scope IPC client with no check at all (a comment in ipc-server.ts claimed a session
|
|
26
|
+
* cookie and an origin check that did not exist), so any website could ask the daemon for the
|
|
27
|
+
* signed-in Firebase token, run `sql`/`shell`, or install a package. Proven against a live
|
|
28
|
+
* daemon on 2026-10-13 with `Origin: https://evil.example`. The rules now:
|
|
29
|
+
*
|
|
30
|
+
* 1. The Host header must name this loopback server — a DNS-rebound hostname is refused.
|
|
31
|
+
* 2. `/apps/ws`: a PAGE must come from this server (an installed hive's frontend) or be a
|
|
32
|
+
* `file://` page (the Mac app loads the avatar UI with `loadFile`; a web page cannot claim
|
|
33
|
+
* `file://`), and it gets CONSUMER scope. A PROGRAM (no Origin) starts unauthenticated and
|
|
34
|
+
* must present the daemon's IPC token with `{type:'auth'}` — the pipe's own boundary. Any
|
|
35
|
+
* other origin, `null` included (sandboxed iframes send it), is refused at the upgrade.
|
|
36
|
+
* 3. JSON routes answer CORS only to loopback origins (a dev server on localhost). Browser
|
|
37
|
+
* building modules stay `*`: clearly.sh imports that public code cross-origin.
|
|
38
|
+
*/
|
|
39
|
+
export function isLoopbackHostHeader(host, port) {
|
|
40
|
+
const h = (host ?? '').toLowerCase();
|
|
41
|
+
return h === `127.0.0.1:${port}` || h === `localhost:${port}` || h === `[::1]:${port}`;
|
|
42
|
+
}
|
|
43
|
+
/** `http://localhost:N`, `http://127.0.0.1:N` or `http://[::1]:N` — a page served on this machine. */
|
|
44
|
+
export function isLoopbackOrigin(origin) {
|
|
45
|
+
if (!origin)
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
const u = new URL(origin);
|
|
49
|
+
return u.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(u.hostname);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** A page served by THIS server. */
|
|
56
|
+
export function isSelfOrigin(origin, port) {
|
|
57
|
+
return (origin === `http://127.0.0.1:${port}` ||
|
|
58
|
+
origin === `http://localhost:${port}` ||
|
|
59
|
+
origin === `http://[::1]:${port}`);
|
|
60
|
+
}
|
|
61
|
+
/** How a `/apps/ws` connection starts: `consumer` scope, unauthenticated (`token`), or refused (null). */
|
|
62
|
+
export function appBridgeTrust(origin, port) {
|
|
63
|
+
if (!origin)
|
|
64
|
+
return 'token';
|
|
65
|
+
if (origin === 'file://' || isSelfOrigin(origin, port))
|
|
66
|
+
return 'consumer';
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/** `/ws` streams events and accepts console logs: loopback or file pages, or programs. */
|
|
70
|
+
export function metricsSocketAllowed(origin) {
|
|
71
|
+
return !origin || origin === 'file://' || isLoopbackOrigin(origin);
|
|
72
|
+
}
|
|
20
73
|
// ── Fallback HTML ───────────────────────────────────────────────────────────
|
|
21
74
|
const FALLBACK_HTML = `<!DOCTYPE html>
|
|
22
75
|
<html lang="en">
|
|
@@ -150,11 +203,28 @@ export class Dashboard {
|
|
|
150
203
|
// first WSS's listener — both listeners fire on every upgrade event.
|
|
151
204
|
// Instead we accept all upgrades and route by req.url in the
|
|
152
205
|
// connection handler.
|
|
153
|
-
|
|
206
|
+
//
|
|
207
|
+
// Every upgrade is judged BEFORE it is accepted (see "Who may talk to this server").
|
|
208
|
+
this._wss = new WebSocketServer({
|
|
209
|
+
server: this._server,
|
|
210
|
+
verifyClient: (info, done) => {
|
|
211
|
+
const req = info.req;
|
|
212
|
+
const url = req.url ?? '';
|
|
213
|
+
const origin = req.headers.origin;
|
|
214
|
+
if (!isLoopbackHostHeader(req.headers.host, this.port))
|
|
215
|
+
return done(false, 403, 'Forbidden');
|
|
216
|
+
if (url.startsWith('/apps/ws')) {
|
|
217
|
+
return appBridgeTrust(origin, this.port) ? done(true) : done(false, 403, 'Forbidden');
|
|
218
|
+
}
|
|
219
|
+
if (url.startsWith('/ws'))
|
|
220
|
+
return metricsSocketAllowed(origin) ? done(true) : done(false, 403, 'Forbidden');
|
|
221
|
+
return done(false, 404, 'Not Found');
|
|
222
|
+
},
|
|
223
|
+
});
|
|
154
224
|
this._wss.on('connection', (ws, req) => {
|
|
155
225
|
const url = req.url ?? '';
|
|
156
226
|
if (url.startsWith('/apps/ws')) {
|
|
157
|
-
this._handleAppWsConnection(ws);
|
|
227
|
+
this._handleAppWsConnection(ws, appBridgeTrust(req.headers.origin, this.port));
|
|
158
228
|
}
|
|
159
229
|
else if (url.startsWith('/ws')) {
|
|
160
230
|
this._handleWSConnection(ws, req);
|
|
@@ -255,8 +325,10 @@ export class Dashboard {
|
|
|
255
325
|
get url() {
|
|
256
326
|
return `http://${this._config.host}:${this._config.port}`;
|
|
257
327
|
}
|
|
328
|
+
/** The port actually bound (config 0 picks one), which the Host and Origin checks compare to. */
|
|
258
329
|
get port() {
|
|
259
|
-
|
|
330
|
+
const addr = this._server?.address();
|
|
331
|
+
return addr && typeof addr === 'object' ? addr.port : this._config.port;
|
|
260
332
|
}
|
|
261
333
|
get running() {
|
|
262
334
|
return this._server !== null;
|
|
@@ -268,6 +340,30 @@ export class Dashboard {
|
|
|
268
340
|
_handleHTTP(req, res) {
|
|
269
341
|
const url = req.url ?? '/';
|
|
270
342
|
const method = req.method ?? 'GET';
|
|
343
|
+
const origin = req.headers.origin;
|
|
344
|
+
// A DNS-rebound name pointing at 127.0.0.1 carries its own Host — refuse it on every route.
|
|
345
|
+
if (!isLoopbackHostHeader(req.headers.host, this.port)) {
|
|
346
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
347
|
+
res.end('Forbidden');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* ⚠ ONE CHOKEPOINT FOR CORS. The handlers below were written to answer `*`; every response now
|
|
352
|
+
* passes through here, which keeps `*` only for browser building modules (public code that
|
|
353
|
+
* clearly.sh imports cross-origin) and otherwise reflects a loopback origin or sends none.
|
|
354
|
+
*/
|
|
355
|
+
const publicModule = /^\/apps\/[^/]+\/buildings\/[^/]+\.js$/.test(url);
|
|
356
|
+
const allowOrigin = publicModule ? '*' : isLoopbackOrigin(origin) ? origin : null;
|
|
357
|
+
const writeHead = res.writeHead.bind(res);
|
|
358
|
+
res.writeHead = (status, ...rest) => {
|
|
359
|
+
const headers = rest.find((r) => r && typeof r === 'object');
|
|
360
|
+
if (headers && 'Access-Control-Allow-Origin' in headers) {
|
|
361
|
+
delete headers['Access-Control-Allow-Origin'];
|
|
362
|
+
if (allowOrigin)
|
|
363
|
+
Object.assign(headers, { 'Access-Control-Allow-Origin': allowOrigin, Vary: 'Origin' });
|
|
364
|
+
}
|
|
365
|
+
return writeHead(status, ...rest);
|
|
366
|
+
};
|
|
271
367
|
// Handle CORS preflight
|
|
272
368
|
if (method === 'OPTIONS') {
|
|
273
369
|
res.writeHead(204, {
|
|
@@ -280,8 +376,14 @@ export class Dashboard {
|
|
|
280
376
|
return;
|
|
281
377
|
}
|
|
282
378
|
try {
|
|
283
|
-
// Explorer API proxy — forward /api/explorer/* to the Worker
|
|
379
|
+
// Explorer API proxy — forward /api/explorer/* to the Worker. It adds the admin secret, so
|
|
380
|
+
// only this server's own explorer page (or a program, which sends no Origin) may use it.
|
|
284
381
|
if (url.startsWith('/api/explorer')) {
|
|
382
|
+
if (origin && !isSelfOrigin(origin, this.port)) {
|
|
383
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
384
|
+
res.end('Forbidden');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
285
387
|
this._proxyExplorer(req, res, url);
|
|
286
388
|
return;
|
|
287
389
|
}
|
|
@@ -350,6 +452,11 @@ export class Dashboard {
|
|
|
350
452
|
}
|
|
351
453
|
}
|
|
352
454
|
if (method === 'POST' && url === '/api/console') {
|
|
455
|
+
if (!metricsSocketAllowed(origin)) {
|
|
456
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
457
|
+
res.end('Forbidden');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
353
460
|
this._handleConsolePost(req, res);
|
|
354
461
|
return;
|
|
355
462
|
}
|
|
@@ -702,7 +809,11 @@ export class Dashboard {
|
|
|
702
809
|
: { installed: [], available: [] };
|
|
703
810
|
this._serveJSON(res, payload);
|
|
704
811
|
}
|
|
705
|
-
_handleAppWsConnection(ws) {
|
|
812
|
+
_handleAppWsConnection(ws, trust) {
|
|
813
|
+
if (!trust) {
|
|
814
|
+
ws.close(1008, 'forbidden');
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
706
817
|
if (!this._appBridge) {
|
|
707
818
|
ws.close(1011, 'app bridge not configured');
|
|
708
819
|
return;
|
|
@@ -720,7 +831,8 @@ export class Dashboard {
|
|
|
720
831
|
// Subscribe broadly by default — browser UIs usually want to see
|
|
721
832
|
// everything (pipeline:*, broadcast:*, etc.). Clients can tighten this
|
|
722
833
|
// by sending their own { type: 'subscribe', patterns: [...] } message.
|
|
723
|
-
|
|
834
|
+
// Consumer scope at most: a page never gets developer verbs (get-token, shell, sql, installs).
|
|
835
|
+
const virtId = bridge.createVirtualClient(send, ['*'], trust === 'consumer' ? 'consumer' : null);
|
|
724
836
|
ws.on('message', (raw) => {
|
|
725
837
|
let msg;
|
|
726
838
|
try {
|
package/dist/ipc-server.js
CHANGED
|
@@ -602,20 +602,23 @@ export class IpcServer extends EventEmitter {
|
|
|
602
602
|
// IPC bus so the same pipeline/app-rpc handlers serve both Claude and
|
|
603
603
|
// the human UI.
|
|
604
604
|
/** Register a virtual client and return its id. */
|
|
605
|
-
createVirtualClient(virtualSend, initialSubscriptions = ['*']) {
|
|
605
|
+
createVirtualClient(virtualSend, initialSubscriptions = ['*'], scope = null) {
|
|
606
606
|
const id = `virt_${++this.virtualClientCounter}`;
|
|
607
|
+
/**
|
|
608
|
+
* ⚠⚠ A VIRTUAL CLIENT IS ONLY AS TRUSTED AS WHOEVER IS ON THE OTHER END OF IT (CAN2-64).
|
|
609
|
+
* This used to be `authenticated: true, scope: 'developer'` for everyone, justified by "the
|
|
610
|
+
* dashboard bridge enforces its own auth (session cookie, origin check)". It enforced none,
|
|
611
|
+
* so any web page that opened ws://127.0.0.1:9800/apps/ws could call get-token. The bridge
|
|
612
|
+
* now judges the connection (dashboard.ts `appBridgeTrust`) and passes 'consumer' or null;
|
|
613
|
+
* a null client must authenticate with the IPC token, exactly like a socket client.
|
|
614
|
+
*/
|
|
607
615
|
const client = {
|
|
608
616
|
id,
|
|
609
617
|
virtualSend,
|
|
610
618
|
subscriptions: initialSubscriptions,
|
|
611
619
|
buffer: '',
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
// (session cookie, origin check) before creating the virtual client,
|
|
615
|
-
// so we hand them developer scope to mirror what a logged-in tray
|
|
616
|
-
// user can already do.
|
|
617
|
-
authenticated: true,
|
|
618
|
-
scope: 'developer',
|
|
620
|
+
authenticated: scope !== null,
|
|
621
|
+
scope,
|
|
619
622
|
};
|
|
620
623
|
this.clients.set(id, client);
|
|
621
624
|
// Mimic the on-connect status frame real clients receive so the bridge
|
package/dist/verb-surface.js
CHANGED
|
@@ -60,6 +60,7 @@ export const VERB_SURFACE = {
|
|
|
60
60
|
logout: { cls: 'STRUCTURAL', why: 'clears local credentials' },
|
|
61
61
|
signup: { cls: 'STRUCTURAL', why: 'opens a browser' },
|
|
62
62
|
handoff: { cls: 'STRUCTURAL', why: 'posts a local file to the anonymous door; no account, no workspace' },
|
|
63
|
+
'agents-md': { cls: 'STRUCTURAL', why: "writes the Clearly block into this repo's AGENTS.md; no workspace" },
|
|
63
64
|
whoami: { cls: 'STRUCTURAL', why: 'reads local credentials' },
|
|
64
65
|
start: { cls: 'STRUCTURAL', why: 'spawns the daemon `call` needs' },
|
|
65
66
|
stop: { cls: 'STRUCTURAL', why: 'daemon lifecycle' },
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clearly-workflows
|
|
3
3
|
description: >-
|
|
4
|
+
Best with the beehaven CLI (semantic search and write-back); with only the Clearly connector, search with clearly_grep and record decisions with clearly_create.
|
|
4
5
|
Use the Clearly workspace as a COMPANY BRAIN — search the org's accumulated
|
|
5
6
|
context (decisions, facts, prompts, docs), write conclusions back so the next
|
|
6
7
|
agent inherits them instead of re-deriving them, and inherit the team's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clearly-workspace
|
|
3
|
-
description: Understand and audit a Clearly workspace from the terminal — what it holds (documents, projects, boards, tickets, thoughts, files, agents), who authored what, and which known data defects are present. Run `survey.mjs` for the whole picture in one command. TRIGGER when the user asks "what is in this workspace", "how big is this workspace", "audit / survey / check the workspace", "what have my agents made", "is anything broken in here", "who created these", or before any cleanup, migration or backfill — the survey is what tells you whether a repair is needed and whether it worked. NOT for editing documents (use `document-*`) or drawing on a canvas (use clearly-canvas).
|
|
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. Understand and audit a Clearly workspace from the terminal — what it holds (documents, projects, boards, tickets, thoughts, files, agents), who authored what, and which known data defects are present. Run `survey.mjs` for the whole picture in one command. TRIGGER when the user asks "what is in this workspace", "how big is this workspace", "audit / survey / check the workspace", "what have my agents made", "is anything broken in here", "who created these", or before any cleanup, migration or backfill — the survey is what tells you whether a repair is needed and whether it worked. NOT for editing documents (use `document-*`) or drawing on a canvas (use clearly-canvas).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# clearly-workspace — know what you are standing in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codebase-map
|
|
3
|
-
description: Walk a code repository and draw a living ARCHITECTURE MAP of it on a Clearly spatial canvas — modules as frames (sized by LOC, colored by layer), key files as nodes, imports/dependencies as arrows — so the big pieces, how they depend on each other, and where the complexity sits all read at a glance. Load this when the user says "map / diagram / visualize this codebase / repo / architecture", "how is this project structured", "draw the dependency graph", "give me an architecture overview", or when you're onboarding to an unfamiliar repo and need to see its shape fast.
|
|
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. Walk a code repository and draw a living ARCHITECTURE MAP of it on a Clearly spatial canvas — modules as frames (sized by LOC, colored by layer), key files as nodes, imports/dependencies as arrows — so the big pieces, how they depend on each other, and where the complexity sits all read at a glance. Load this when the user says "map / diagram / visualize this codebase / repo / architecture", "how is this project structured", "draw the dependency graph", "give me an architecture overview", or when you're onboarding to an unfamiliar repo and need to see its shape fast.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
> **⚠ CANVAS WORK GOES THROUGH THE CLI, NOT AN MCP TOOL.** Anything written
|
package/skills/index.json
CHANGED
|
@@ -17,21 +17,21 @@
|
|
|
17
17
|
"project-plans.md",
|
|
18
18
|
"review.mjs"
|
|
19
19
|
],
|
|
20
|
-
"sha": "
|
|
20
|
+
"sha": "41327744a8cc0fe1"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"name": "agent-home",
|
|
24
24
|
"files": [
|
|
25
25
|
"SKILL.md"
|
|
26
26
|
],
|
|
27
|
-
"sha": "
|
|
27
|
+
"sha": "e1d18e7518ff4b65"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"name": "agent-doodle-space",
|
|
31
31
|
"files": [
|
|
32
32
|
"SKILL.md"
|
|
33
33
|
],
|
|
34
|
-
"sha": "
|
|
34
|
+
"sha": "1e1911e6d8784476"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"name": "design-craft",
|
|
@@ -53,21 +53,21 @@
|
|
|
53
53
|
"files": [
|
|
54
54
|
"SKILL.md"
|
|
55
55
|
],
|
|
56
|
-
"sha": "
|
|
56
|
+
"sha": "366139b11ff11b58"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"name": "pair-on-canvas",
|
|
60
60
|
"files": [
|
|
61
61
|
"SKILL.md"
|
|
62
62
|
],
|
|
63
|
-
"sha": "
|
|
63
|
+
"sha": "1d79147baa8c232d"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"name": "codebase-map",
|
|
67
67
|
"files": [
|
|
68
68
|
"SKILL.md"
|
|
69
69
|
],
|
|
70
|
-
"sha": "
|
|
70
|
+
"sha": "4a4af2229b68827d"
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
"name": "clearly-workspace",
|
|
@@ -75,28 +75,28 @@
|
|
|
75
75
|
"SKILL.md",
|
|
76
76
|
"survey.mjs"
|
|
77
77
|
],
|
|
78
|
-
"sha": "
|
|
78
|
+
"sha": "3a3e6fa6e3cf8717"
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
"name": "clearly-agent",
|
|
82
82
|
"files": [
|
|
83
83
|
"SKILL.md"
|
|
84
84
|
],
|
|
85
|
-
"sha": "
|
|
85
|
+
"sha": "1c6425a2143e7901"
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"name": "visualize",
|
|
89
89
|
"files": [
|
|
90
90
|
"SKILL.md"
|
|
91
91
|
],
|
|
92
|
-
"sha": "
|
|
92
|
+
"sha": "88bb6e94fb37c337"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"name": "sticker-pack",
|
|
96
96
|
"files": [
|
|
97
97
|
"SKILL.md"
|
|
98
98
|
],
|
|
99
|
-
"sha": "
|
|
99
|
+
"sha": "74af254817a134d6"
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
"name": "clearly-docs",
|
|
@@ -117,6 +117,6 @@
|
|
|
117
117
|
"files": [
|
|
118
118
|
"SKILL.md"
|
|
119
119
|
],
|
|
120
|
-
"sha": "
|
|
120
|
+
"sha": "b9622f21173bab0d"
|
|
121
121
|
}
|
|
122
122
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pair-on-canvas
|
|
3
|
-
description: Use a Clearly canvas as mission control for a coding task — read the spec the human placed on the board, do the work in the repo, and report progress, diffs, and next steps back as cards they steer by inking. Load this when the user says "work the task on my canvas", "take this from the board", "what's on my Clearly canvas", "pick up the spec", "let's pair on the canvas", or whenever a human is directing your coding work spatially instead of in a chat thread.
|
|
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 a Clearly canvas as mission control for a coding task — read the spec the human placed on the board, do the work in the repo, and report progress, diffs, and next steps back as cards they steer by inking. Load this when the user says "work the task on my canvas", "take this from the board", "what's on my Clearly canvas", "pick up the spec", "let's pair on the canvas", or whenever a human is directing your coding work spatially instead of in a chat thread.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
> **⚠ CANVAS WORK GOES THROUGH THE CLI, NOT AN MCP TOOL.** Anything written
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sticker-pack
|
|
3
|
-
description: Turn an idea into a printable, die-cut STICKER SHEET on a Clearly canvas — Clearly's own sticker-vending-machine pipeline surfaced as an agent skill. Generates a cohesive SET of transparent stickers, lays them out as a sheet (frame + gridded stickers + a vector kiss-cut cutline layer), and hands back a print-ready bundle. Load this when the user says "make me stickers / a sticker pack / a sticker sheet about X", "turn this into stickers", "design stickers for my brand / startup / cat", or "I want die-cut stickers". The fun, inherently-shareable corner of the canvas skill set.
|
|
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. Turn an idea into a printable, die-cut STICKER SHEET on a Clearly canvas — Clearly's own sticker-vending-machine pipeline surfaced as an agent skill. Generates a cohesive SET of transparent stickers, lays them out as a sheet (frame + gridded stickers + a vector kiss-cut cutline layer), and hands back a print-ready bundle. Load this when the user says "make me stickers / a sticker pack / a sticker sheet about X", "turn this into stickers", "design stickers for my brand / startup / cat", or "I want die-cut stickers". The fun, inherently-shareable corner of the canvas skill set.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
> **⚠ CANVAS AND STICKER WORK GO THROUGH THE CLI, NOT AN MCP TOOL.** Anything written
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: visualize
|
|
3
|
-
description: Turn any concept, answer, or described system into a clean DIAGRAM on a Clearly spatial canvas instead of a wall of text. Load this when the user asks to "diagram this", "draw it", "sketch it", "map it out", "visualize this",
|
|
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. Turn any concept, answer, or described system into a clean DIAGRAM on a Clearly spatial canvas instead of a wall of text. Load this when the user asks to "diagram this", "draw it", "sketch it", "map it out", "visualize this",
|
|
4
4
|
"explain visually", "show me how X works", "flowchart this", "make a diagram" — AND proactively whenever YOUR OWN answer would be clearer as a picture than a paragraph (a flow with branches, who-calls-whom, a schema, an architecture, options on axes). Picks the right diagram type for the content and builds it with the canvas primitives.
|
|
5
5
|
---
|
|
6
6
|
|