beehaven 0.8.27 → 0.8.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +6 -4
- package/dist/cli.js +27 -8
- 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 -5
- 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/relay-presets.js +3 -4
- 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 +1 -1
- 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/beehaven-cli/SKILL.md +12 -0
- package/skills/brand-identity/SKILL.md +37 -14
- package/skills/clearly-agent/SKILL.md +24 -4
- package/skills/clearly-canvas/SKILL.md +88 -11
- package/skills/clearly-canvas/authoring.md +61 -10
- package/skills/clearly-canvas/example.mjs +72 -69
- package/skills/clearly-canvas/review.mjs +106 -12
- 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 +14 -6
- package/skills/design-craft/references.md +95 -0
- package/skills/index.json +48 -12
- 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
|
@@ -1,78 +1,81 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
2
|
+
/** Three editable page states. Writes JSON only; it never calls a workspace. */
|
|
3
3
|
import { writeFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
const [compositionId, output = '/tmp/fieldwork-build.json'] = process.argv.slice(2);
|
|
6
6
|
if (!compositionId) throw new Error('Usage: node example.mjs <composition-id> [output.json]');
|
|
7
|
-
const grid = { w: 1440, h: 900, margin: 88, gutter: 80 };
|
|
8
|
-
const ink = '#172824', paper = '#F2F0E7', orange = '#C84A2E', muted = '#58685E';
|
|
9
7
|
const batch = [];
|
|
10
8
|
const op = (action, args) => batch.push({ action, args });
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const rule = (p, y, color) => rect(p, `rule-${y}`, grid.margin, y, grid.w - grid.margin * 2, 1, color);
|
|
23
|
-
const masthead = (p, index, color) => {
|
|
24
|
-
text(p, 'brand', 'FIELDWORK', grid.margin, 52, 400, 20, color, 700, { letterSpacing: 2 });
|
|
25
|
-
text(p, 'series', 'DESIGN NOTES / VOL. 01', 912, 54, 440, 14, color, 400, { letterSpacing: 1 });
|
|
26
|
-
text(p, 'folio', 'STUDIO PRACTICE', grid.margin, 828, 900, 14, color, 400, { letterSpacing: 1 });
|
|
27
|
-
text(p, 'page', String(index).padStart(2, '0'), 1304, 828, 48, 14, color, 400, { align: 'right' });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Brief: show how one simple visual idea becomes a coherent editorial system.
|
|
31
|
-
// Hierarchy: statement → geometric proof → compact practical explanation.
|
|
32
|
-
// Motif: two fields meet to make a third; repeated with a different function on each slide.
|
|
33
|
-
const cover = 'fieldwork-01';
|
|
34
|
-
frame(cover, 0, paper); masthead(cover, 1, ink); rule(cover, 104, ink);
|
|
35
|
-
text(cover, 'eyebrow', 'A NOTE ON MAKING', 88, 168, 560, 16, orange, 700, { letterSpacing: 2 });
|
|
36
|
-
text(cover, 'title', 'Make room\nfor the idea.', 80, 228, 810, 108, ink, 700, { letterSpacing: -4, lineHeight: 1.02 });
|
|
37
|
-
text(cover, 'intro', 'Good design begins with a point of view.\nThe system gives it room to grow.', 88, 538, 740, 25, muted);
|
|
38
|
-
circle(cover, 'disc', 977, 233, 350, orange);
|
|
39
|
-
rect(cover, 'field', 916, 385, 270, 270, ink);
|
|
40
|
-
rect(cover, 'intersection', 977, 385, 209, 198, paper, { radius: 0 });
|
|
41
|
-
text(cover, 'motif-caption', '01 / TWO FIELDS, ONE IDEA', 920, 696, 420, 14, muted, 400, { letterSpacing: 1 });
|
|
42
|
-
rule(cover, 782, ink);
|
|
43
|
-
|
|
44
|
-
const method = 'fieldwork-02';
|
|
45
|
-
frame(method, 1, ink); masthead(method, 2, paper); rule(method, 104, '#607369');
|
|
46
|
-
text(method, 'title', 'One idea.\nMany expressions.', 80, 164, 1120, 92, paper, 700, { letterSpacing: -3, lineHeight: 1.04 });
|
|
47
|
-
text(method, 'intro', 'Keep the visual logic.\nChange what the content needs.', 952, 380, 400, 24, '#C7CFC4');
|
|
48
|
-
const concepts = [
|
|
49
|
-
{ x: 88, title: '01 / EMPHASIS', body: 'Let one thing lead.', kind: 0 },
|
|
50
|
-
{ x: 520, title: '02 / RELATIONSHIP', body: 'Make the connection visible.', kind: 1 },
|
|
51
|
-
{ x: 952, title: '03 / RHYTHM', body: 'Repeat with a reason.', kind: 2 },
|
|
52
|
-
];
|
|
53
|
-
for (const c of concepts) {
|
|
54
|
-
const y = 518;
|
|
55
|
-
if (c.kind === 0) { circle(method, 'emphasis', c.x, y, 152, orange); circle(method, 'quiet', c.x + 192, y + 92, 60, paper); }
|
|
56
|
-
if (c.kind === 1) { rect(method, 'relation-field', c.x, y + 20, 148, 120, paper); circle(method, 'relation-disc', c.x + 80, y, 152, orange); }
|
|
57
|
-
if (c.kind === 2) for (let i = 0; i < 4; i++) rect(method, `rhythm-${i}`, c.x + i * 78, y + i * 20, 48, 152 - i * 20, i === 3 ? orange : paper);
|
|
58
|
-
text(method, `label-${c.kind}`, c.title, c.x, 714, 380, 16, paper, 700, { letterSpacing: 1 });
|
|
59
|
-
text(method, `body-${c.kind}`, c.body, c.x, 748, 380, 20, '#C7CFC4');
|
|
9
|
+
const fillWidth = id => op('canvas.update-nodes', { ids: [id], patch: { layoutSizingH: 'fill' } });
|
|
10
|
+
// Page, section, row, card and button share this container contract.
|
|
11
|
+
function flow(id, parentId, box, layout, children) {
|
|
12
|
+
// Enable flow before adding children so reading order is creation order.
|
|
13
|
+
op('frame.create', { id, name: id, parentId, fill: 'transparent', ...box,
|
|
14
|
+
autoLayout: { direction: 'v', gap: 24, padding: 0, ...layout },
|
|
15
|
+
layoutSizingH: layout.sizingH ?? 'fixed', layoutSizingV: layout.sizingV ?? 'hug' });
|
|
16
|
+
children(id);
|
|
17
|
+
op('canvas.auto-layout', { id, direction: 'v', gap: 24, padding: 0,
|
|
18
|
+
sizingH: 'fixed', sizingV: 'hug', ...layout });
|
|
19
|
+
return id;
|
|
60
20
|
}
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
21
|
+
function text(parentId, name, copy, w, size, fill, extra = {}) {
|
|
22
|
+
const id = parentId + '/' + name;
|
|
23
|
+
op('canvas.create-node', { id, name, parentId, type: 'text', w, text: copy,
|
|
24
|
+
size, family: 'Inter', fill, lineHeight: 'auto', ...extra });
|
|
25
|
+
fillWidth(id);
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
function page(id, x, mobile = false, dark = false) {
|
|
29
|
+
const w = mobile ? 390 : 1440, pad = mobile ? 24 : 80, inner = w - pad * 2;
|
|
30
|
+
const ink = dark ? '#F2F0E7' : '#172824', ground = dark ? '#172824' : '#F2F0E7';
|
|
31
|
+
flow(id, undefined, { x, y: 0, w, h: 1000, fill: ground }, { gap: 64, padding: pad }, p => {
|
|
32
|
+
flow(p + '/navigation', p, { w: inner, h: 48 }, { direction: 'h', align: 'center', justify: 'space-between' }, nav => {
|
|
33
|
+
text(nav, 'brand', 'FIELDWORK', inner - 140, 20, ink, { fontWeight: 700 });
|
|
34
|
+
text(nav, 'edition', 'STUDIO NOTES', 140, 14, ink);
|
|
35
|
+
op('canvas.update-nodes', { ids: [nav + '/edition'], patch: { layoutSizingH: 'fixed' } });
|
|
36
|
+
});
|
|
37
|
+
fillWidth(p + '/navigation');
|
|
38
|
+
flow(p + '/hero', p, { w: inner, h: 420 }, { direction: mobile ? 'v' : 'h', gap: 48 }, hero => {
|
|
39
|
+
const copyWidth = mobile ? inner : 740;
|
|
40
|
+
flow(hero + '/copy', hero, { w: copyWidth, h: 400 }, { gap: 24 }, copy => {
|
|
41
|
+
text(copy, 'eyebrow', 'A NOTE ON MAKING', copyWidth, 14, ink, { letterSpacing: 1.5 });
|
|
42
|
+
text(copy, 'title', 'Make room for the idea.', copyWidth, mobile ? 48 : 88, ink,
|
|
43
|
+
{ fontWeight: 700, letterSpacing: -2, lineHeight: '105%' });
|
|
44
|
+
text(copy, 'description', 'A shared direction. An editable system. Room for the next idea.', copyWidth, 22, ink, { lineHeight: '145%' });
|
|
45
|
+
flow(copy + '/action', copy, { w: 240, h: 56, fill: ink, radius: 8 },
|
|
46
|
+
{ direction: 'h', padding: 16, align: 'center' }, button => {
|
|
47
|
+
text(button, 'label', 'Explore the studio', 208, 16, ground, { align: 'center' });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
fillWidth(hero + '/copy');
|
|
51
|
+
// Art-directed coordinates belong inside an owning artwork frame.
|
|
52
|
+
const art = hero + '/artwork', aw = mobile ? inner : 400;
|
|
53
|
+
op('frame.create', { id: art, name: 'Artwork / two fields', parentId: hero, w: aw, h: 320, fill: '#C84A2E' });
|
|
54
|
+
op('canvas.create-node', { id: art + '/disc', name: 'Disc', type: 'ellipse', parentId: art, x: 24, y: 24, w: 220, h: 220, fill: '#F2F0E7' });
|
|
55
|
+
op('canvas.create-node', { id: art + '/field', name: 'Field', type: 'rect', parentId: art, x: 116, y: 136, w: 196, h: 160, fill: '#172824' });
|
|
56
|
+
});
|
|
57
|
+
fillWidth(p + '/hero');
|
|
58
|
+
flow(p + '/features', p, { w: inner, h: 220 }, { direction: 'grid', columns: mobile ? 1 : 3,
|
|
59
|
+
rows: mobile ? 3 : 1, rowSizes: mobile ? ['hug', 'hug', 'hug'] : ['hug'], gap: 24, rowGap: 24 }, grid => {
|
|
60
|
+
for (const [name, title, body] of [
|
|
61
|
+
['type', 'Give ideas a voice.', 'Type scales and wraps with the available space.'],
|
|
62
|
+
['space', 'Keep room to grow.', 'Content owns its height. Related layers move together.'],
|
|
63
|
+
['review', 'Make the next edit.', 'Resize a card and extend its copy to check the system.'],
|
|
64
|
+
]) {
|
|
65
|
+
const card = grid + '/' + name, cw = mobile ? inner : (inner - 48) / 3;
|
|
66
|
+
flow(card, grid, { w: cw, h: 180, fill: dark ? '#294337' : '#FFFFFF', radius: 12 }, { gap: 16, padding: 24 }, c => {
|
|
67
|
+
text(c, 'title', title, cw - 48, 24, ink, { fontWeight: 600 });
|
|
68
|
+
text(c, 'body', body, cw - 48, 18, ink, { lineHeight: '145%' });
|
|
69
|
+
});
|
|
70
|
+
fillWidth(card);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
fillWidth(p + '/features');
|
|
74
|
+
text(p, 'footer', 'FIELDWORK / A SYSTEM READY FOR CHANGE', inner, 14, ink, { letterSpacing: 1 });
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
page('01-desktop', 0);
|
|
78
|
+
page('02-mobile', 1520, true);
|
|
79
|
+
page('03-dark', 1990, false, true);
|
|
77
80
|
writeFileSync(output, JSON.stringify({ compositionId, batch }, null, 2) + '\n');
|
|
78
|
-
process.stdout.write(
|
|
81
|
+
process.stdout.write(output + ': ' + batch.length + ' operations\n');
|
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Save review pixels outside the model context. No shell interpolation, no workspace mutations. */
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import {
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
5
6
|
import { dirname, resolve } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
|
-
export function
|
|
9
|
+
export function assertReceipt(result, { requireChange = false } = {}) {
|
|
10
|
+
if (result?.ok === false || result?.error || result?.results?.some(r => r.ok === false)) {
|
|
11
|
+
throw new Error(result.error || result.results?.find(r => !r.ok)?.error || 'Canvas request failed');
|
|
12
|
+
}
|
|
13
|
+
for (const row of result?.results ?? [result]) {
|
|
14
|
+
const value = row.value ?? row;
|
|
15
|
+
if (value.ignoredFields?.length || value.rejectedFields?.length) {
|
|
16
|
+
throw new Error(`Fields were not applied: ${[...(value.ignoredFields ?? []), ...(value.rejectedFields ?? [])].join(', ')}. Some work may have landed; inspect the saved scene before retrying.`);
|
|
17
|
+
}
|
|
18
|
+
if (requireChange) {
|
|
19
|
+
const delta = value.canvasChanges;
|
|
20
|
+
const changed = ['changed', 'partial'].includes(value.outcome) || (Array.isArray(delta) ? delta.length > 0 : delta && typeof delta === 'object'
|
|
21
|
+
? Number(delta.added || 0) + Number(delta.changed || 0) + Number(delta.removed || 0) > 0
|
|
22
|
+
: value.placed === true || /\.create(-node)?$/.test(String(row.action || '')) && !!value.id && !value.duplicate);
|
|
23
|
+
if (!changed) throw new Error('No nodes changed, or the receipt does not prove a change. Inspect the saved scene; do not treat a match as an update.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function rpc(action, payload, options) {
|
|
9
30
|
const stdout = execFileSync('beehaven', ['call', action, '-'], {
|
|
10
31
|
input: JSON.stringify(payload), encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
|
11
32
|
stdio: ['pipe', 'pipe', 'pipe'], timeout: 120_000,
|
|
12
33
|
});
|
|
13
34
|
const result = JSON.parse(stdout);
|
|
14
|
-
|
|
15
|
-
|
|
35
|
+
return assertReceipt(result, options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sceneNodes(result) {
|
|
39
|
+
if (!Array.isArray(result?.nodes)) throw new Error('Expected composition-detail includeScene:true to return top-level nodes. Refusing to interpret a missing field as an empty canvas.');
|
|
40
|
+
return result.nodes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Resume creates only. Existing layers are preserved, including the person's later edits. */
|
|
44
|
+
export function resumeCanvasPlan(plan, snapshot, idManifest = {}) {
|
|
45
|
+
const nodes = sceneNodes(snapshot), byId = new Map(nodes.map(n => [n.id, n]));
|
|
46
|
+
const names = new Map();
|
|
47
|
+
for (const n of nodes) { const list = names.get(n.name) ?? []; list.push(n); names.set(n.name, list); }
|
|
48
|
+
const roots = new Map(), batch = [], skipped = [];
|
|
49
|
+
for (const step of plan.batch ?? []) {
|
|
50
|
+
const args = { ...(step.args ?? {}) };
|
|
51
|
+
if (!['frame.create', 'canvas.create-node'].includes(step.action)) { skipped.push({ action: step.action, reason: 'Resume preserves existing mutations; revise explicitly.' }); continue; }
|
|
52
|
+
const type = step.action === 'frame.create' ? 'frame' : args.type;
|
|
53
|
+
const pinned = args.id || idManifest[args.name];
|
|
54
|
+
const matches = names.get(args.name) ?? [];
|
|
55
|
+
if (!pinned && matches.length > 1) throw new Error(`Ambiguous layer name ${args.name}; use its recorded ID.`);
|
|
56
|
+
const existing = pinned ? byId.get(pinned) : matches[0];
|
|
57
|
+
if (existing) {
|
|
58
|
+
if (existing.type !== type) throw new Error(`Layer ${args.name} has type ${existing.type}, expected ${type}. No automatic replacement.`);
|
|
59
|
+
roots.set(args.name, existing.id); skipped.push({ id: existing.id, name: args.name, reason: 'Already exists; preserved.' }); continue;
|
|
60
|
+
}
|
|
61
|
+
args.id = pinned || `plan-${createHash('sha256').update(`${plan.compositionId}:${args.name}`).digest('hex').slice(0, 24)}`;
|
|
62
|
+
if (args.parentId && roots.has(args.parentId)) args.parentId = roots.get(args.parentId);
|
|
63
|
+
roots.set(args.name, args.id);
|
|
64
|
+
batch.push({ ...step, args });
|
|
16
65
|
}
|
|
17
|
-
return
|
|
66
|
+
return { compositionId: plan.compositionId, expectedCanvasNodesRev: snapshot.canvasNodesRev, batch, skipped };
|
|
18
67
|
}
|
|
19
68
|
|
|
20
69
|
export function saveReview(report, prefix) {
|
|
@@ -24,7 +73,21 @@ export function saveReview(report, prefix) {
|
|
|
24
73
|
}
|
|
25
74
|
const bytes = Buffer.from(preview.dataUrl.slice('data:image/png;base64,'.length), 'base64');
|
|
26
75
|
if (!bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) throw new Error('Invalid PNG response');
|
|
27
|
-
|
|
76
|
+
if (bytes.length < 45 || bytes.readUInt32BE(8) !== 13 || bytes.subarray(12,16).toString() !== 'IHDR'
|
|
77
|
+
|| !bytes.readUInt32BE(16) || !bytes.readUInt32BE(20)) throw new Error('Invalid PNG response: missing image header/body');
|
|
78
|
+
let offset = 8, imageData = false, end = false;
|
|
79
|
+
while (offset + 12 <= bytes.length) {
|
|
80
|
+
const length = bytes.readUInt32BE(offset), tag = bytes.subarray(offset+4,offset+8).toString();
|
|
81
|
+
if (offset + length + 12 > bytes.length) throw new Error('Invalid PNG response: truncated image chunk');
|
|
82
|
+
if (tag === 'IDAT' && length > 0) imageData = true;
|
|
83
|
+
offset += length + 12;
|
|
84
|
+
if (tag === 'IEND') { end = length === 0; break; }
|
|
85
|
+
}
|
|
86
|
+
if (!imageData || !end) throw new Error('Invalid PNG response: missing image data/end');
|
|
87
|
+
let base = resolve(prefix);
|
|
88
|
+
const original = base;
|
|
89
|
+
let version = 0;
|
|
90
|
+
while (existsSync(`${base}.png`) || existsSync(`${base}.json`)) base = `${original}-r${report.canvasNodesRev ?? 'unknown'}-${++version}`;
|
|
28
91
|
mkdirSync(dirname(base), { recursive: true });
|
|
29
92
|
const { dataUrl, url, ...metadata } = preview;
|
|
30
93
|
const saved = { ...report, preview: { ...metadata, path: `${base}.png` } };
|
|
@@ -34,16 +97,47 @@ export function saveReview(report, prefix) {
|
|
|
34
97
|
return { png: `${base}.png`, report: `${base}.json`,
|
|
35
98
|
// `fix` = a probable defect remains: fix it, or pass its check/id in `ignore` with a reason.
|
|
36
99
|
// `look` = only things worth a second look. `clean` = nothing mechanical found — NOT "good".
|
|
37
|
-
verdict: report.verdict ?? 'unknown',
|
|
100
|
+
verdict: report.verdict ?? 'unknown', acceptanceStatus: report.acceptanceStatus,
|
|
101
|
+
acceptedFindings: report.acceptedFindings ?? [], blocking: report.blocking,
|
|
38
102
|
craft: craft.map((f) => `${f.severity} ${f.check}: ${f.detail}`),
|
|
103
|
+
audit: (report.audit?.violations ?? []).map(v => ({check:v.kind,layer:v.name || v.id,detail:v.detail || v.message || v.reason || `${v.kind} on ${v.name || v.id}`})),
|
|
104
|
+
scopeWarnings: report.scopeWarnings ?? [],
|
|
105
|
+
assets: (report.assets ?? []).filter(a => a.status !== 'ready'),
|
|
106
|
+
typography: (report.typography?.text ?? []).filter(t => t.issue || t.horizontalOverflow > 1 || !t.autoHeight && t.verticalOverflow > 1 || t.exact === false)
|
|
107
|
+
.map(t => ({layer:t.name || t.id,issue:t.issue,horizontalOverflow:t.horizontalOverflow,verticalOverflow:t.verticalOverflow,
|
|
108
|
+
requestedFont:t.font?.requestedFamily,requestedWeight:t.font?.requestedWeight,resolvedFile:t.font?.file,exact:t.exact})),
|
|
39
109
|
nodes: report.scope?.nodeCount, findings: report.audit?.total, textChecked: report.typography?.checked,
|
|
40
110
|
textOmitted: report.typography?.omitted, visualReviewRequired: true,
|
|
41
111
|
note: report.verdict === 'fix'
|
|
42
|
-
? 'Fix every warn (or
|
|
112
|
+
? 'Fix every warn (or accept an exact finding with a reason), then review changed artboards. Then open the PNG and critique it.'
|
|
43
113
|
: 'Open the PNG with your image-viewing tool, critique it against the studio bar, revise and render again.' };
|
|
44
114
|
}
|
|
45
115
|
|
|
46
|
-
export function
|
|
116
|
+
export async function saveReviewPixels(report, prefix, fetchImage = fetch) {
|
|
117
|
+
if (report.preview?.dataUrl) return saveReview(report, prefix);
|
|
118
|
+
const url = report.preview?.url;
|
|
119
|
+
if (!url || !/^https?:\/\//.test(url)) throw new Error(report.previewError || 'No downloadable preview. Visual verification is incomplete.');
|
|
120
|
+
const response = await fetchImage(url);
|
|
121
|
+
if (!response.ok) throw new Error(`Preview download failed (HTTP ${response.status}); request a fresh review.`);
|
|
122
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
123
|
+
return saveReview({ ...report, preview: { ...report.preview, dataUrl: `data:image/png;base64,${bytes.toString('base64')}` } }, prefix);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
127
|
+
if (argv.includes('--many')) {
|
|
128
|
+
const args = argv.filter(a => a !== '--many');
|
|
129
|
+
if (args.length !== 3) throw new Error('Usage: node review.mjs --many <compositionId> <artboard-ids.json> <output-directory>');
|
|
130
|
+
const [compositionId, manifest, directory] = args;
|
|
131
|
+
const ids = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
132
|
+
if (!Array.isArray(ids) || !ids.length) throw new Error('The manifest must be a non-empty artboard ID/name array.');
|
|
133
|
+
const response = rpc('canvas-act', { compositionId, action: 'canvas.review-many', args: { ids, preview: 'files' } });
|
|
134
|
+
const reports = response.results?.[0]?.value?.reviews;
|
|
135
|
+
if (!Array.isArray(reports) || reports.length !== ids.length) throw new Error('Incomplete review-many result; do not claim all artboards passed.');
|
|
136
|
+
const saved = [];
|
|
137
|
+
for (let i = 0; i < reports.length; i++) saved.push(await saveReviewPixels(reports[i], resolve(directory, `${String(i + 1).padStart(2, '0')}-${String(ids[i]).replace(/[^a-z0-9_-]/gi, '-')}`)));
|
|
138
|
+
process.stdout.write(JSON.stringify(saved, null, 2) + '\n');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
47
141
|
const legacy = argv.includes('--perceive');
|
|
48
142
|
const args = argv.filter((a) => a !== '--perceive');
|
|
49
143
|
if (args.length !== 3) throw new Error('Usage: node review.mjs <compositionId> <frame-id-or-name> <output-prefix> [--perceive]\n--perceive: older workers, preview only; pass a frame ID.');
|
|
@@ -60,9 +154,9 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
60
154
|
report = response.results?.[0]?.value;
|
|
61
155
|
if (!report?.scope) throw new Error('canvas.review unavailable. Check canvas-catalog; use --perceive for an explicitly preview-only check on an older worker.');
|
|
62
156
|
}
|
|
63
|
-
process.stdout.write(JSON.stringify(
|
|
157
|
+
process.stdout.write(JSON.stringify(await saveReviewPixels(report, prefix), null, 2) + '\n');
|
|
64
158
|
}
|
|
65
159
|
|
|
66
|
-
if (process.argv[1] &&
|
|
67
|
-
|
|
160
|
+
if (process.argv[1] && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
161
|
+
main().catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; });
|
|
68
162
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearly-docs
|
|
3
|
+
description: >-
|
|
4
|
+
Read, search, edit and recover a company's documents in a Clearly workspace.
|
|
5
|
+
Use the seven-tool MCP filesystem for ordinary work; use the `beehaven` CLI
|
|
6
|
+
only for document history, diffs, and restore. Load this whenever the answer
|
|
7
|
+
depends on what the company has written down, or when you are about to write
|
|
8
|
+
something down for it. Triggers: "what do our docs say", "find the doc about
|
|
9
|
+
X", "update the spec", "write this up", "add it to the docs", "has this
|
|
10
|
+
changed", "what did they edit", "search the wiki", "company knowledge", "our
|
|
11
|
+
documentation", "the PRD", "the runbook".
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
> **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may expose them
|
|
15
|
+
> with a server prefix — e.g. `mcp__plugin_clearly_clearly__clearly_read`. **Match by suffix,
|
|
16
|
+
> not by exact name**: a skill written against the bare name resolves to nothing otherwise, and
|
|
17
|
+
> the failure looks like "the tool doesn't exist" rather than "the name is decorated".
|
|
18
|
+
>
|
|
19
|
+
> **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
|
|
20
|
+
> session — these skills still LIST when it isn't, so you find out by firing a dead call.
|
|
21
|
+
> Authorise it (`/mcp`, or `claude mcp`).
|
|
22
|
+
|
|
23
|
+
## What this is
|
|
24
|
+
|
|
25
|
+
The workspace is a **filesystem**, not a chat surface with a search box.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
~/ every document
|
|
29
|
+
~/clearly.md what is settled for the whole workspace
|
|
30
|
+
~/<Folder>/ one directory per project
|
|
31
|
+
~/<Folder>/clearly.md what is settled for that folder
|
|
32
|
+
~/.archive/ put away — hidden, readable, read-only, still searched
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Folders are projects. Documents are `.md` files named `<KEY> <Title>.md` — e.g.
|
|
36
|
+
`CLR-42 Pricing.md`. The key is an address: `cat ~/CLR-42.md` works from anywhere, and so does
|
|
37
|
+
the full name and the bare title.
|
|
38
|
+
|
|
39
|
+
## The loop
|
|
40
|
+
|
|
41
|
+
**Find it, then open it.** `clearly_glob` answers "what is it called", `clearly_grep` answers
|
|
42
|
+
"where is this mentioned", and `clearly_read` opens what either one returned.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
clearly_glob { pattern: "~/**/*.md" }
|
|
46
|
+
clearly_grep { pattern: "pricing", limit: 5 }
|
|
47
|
+
clearly_read { target: "~/Prepress/CLR-42 Pricing.md" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
⚠ **`clearly_grep` takes a REGEX, unparsed** — `|`, `(`, `$` and quotes mean what they mean in a
|
|
51
|
+
regular expression. Nothing shells out, so nothing eats them as syntax first.
|
|
52
|
+
|
|
53
|
+
⚠ **Archived documents are excluded from both.** When any matched, the result carries
|
|
54
|
+
`archivedHidden` saying how many — mention that rather than reporting a clean miss. Search the
|
|
55
|
+
archive by naming it: `clearly_grep { pattern: …, path: "~/.archive" }`.
|
|
56
|
+
|
|
57
|
+
**Structural questions — a date range, a null body, counts — have no keyword to search for**, so
|
|
58
|
+
they go to SQL over the workspace's own tables. That is a CLI job, not an MCP tool:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
beehaven call bash '{"command":"sql SELECT title, updated_at FROM notes WHERE project_id IS NULL LIMIT 20"}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Read-only, one statement, credential columns redacted.
|
|
65
|
+
|
|
66
|
+
## ⚠⚠ Read before you write, and check what changed
|
|
67
|
+
|
|
68
|
+
This is the part that makes documentation stay true, and it is the part agents skip.
|
|
69
|
+
|
|
70
|
+
The MCP read/edit/replace tools cover the current body. Document history is a separate CLI surface:
|
|
71
|
+
|
|
72
|
+
- **`beehaven call document-status '{"limit":20}'`** lists recent changes. `mine: true` is the
|
|
73
|
+
certain signal that the active agent made the latest change. `by: "unknown"` means the log has
|
|
74
|
+
no author; it may be a browser edit or an older unstamped agent write, so do not claim a person
|
|
75
|
+
made it.
|
|
76
|
+
- **`beehaven call document-diff '{"id":"<documentId>"}'`** follows up with added and removed
|
|
77
|
+
lines. Pass a `versionId` from status to compare against a specific checkpoint.
|
|
78
|
+
|
|
79
|
+
Run status when you resume a conversation, and whenever they say something implying the document
|
|
80
|
+
has moved — *"I rewrote that"*, *"take another look"*.
|
|
81
|
+
|
|
82
|
+
⚠ **Their edit wins, always.** If a document now disagrees with something you concluded
|
|
83
|
+
earlier, your conclusion is the stale one. You are keeping their documents, not defending your
|
|
84
|
+
own record. Never quietly restore what they took out.
|
|
85
|
+
|
|
86
|
+
## Editing
|
|
87
|
+
|
|
88
|
+
**`clearly_edit` (find/replace) is the default. `clearly_replace` REPLACES the whole document.**
|
|
89
|
+
|
|
90
|
+
To change one line of a 12,000-character document, `replace` makes you reproduce all 12,000 — and
|
|
91
|
+
anything you fail to retype is gone with no error. It is also a lost-update machine: it writes
|
|
92
|
+
whatever you last read, overwriting anything the human changed in between.
|
|
93
|
+
|
|
94
|
+
- `clearly_edit` requires the `find` text to match **exactly once**. Ambiguity is REFUSED with a
|
|
95
|
+
count, never guessed — an agent that edits the first of three matches has changed something
|
|
96
|
+
nobody looked at.
|
|
97
|
+
- Several independent document edits can be sent in one tool call with `batch`, but each batch
|
|
98
|
+
row is still one exact `{ target, find, replace, all? }` operation. There is no `multi_edit`
|
|
99
|
+
field and the batch is not transactional; inspect the per-item results and retry only failures.
|
|
100
|
+
- `clearly_replace` does not accept `expected_content` or a commit-message field. If you must replace
|
|
101
|
+
a whole existing document, re-read it immediately before writing and verify the returned body.
|
|
102
|
+
Prefer `clearly_edit` whenever a precise passage change can express the job.
|
|
103
|
+
|
|
104
|
+
## Comments and @mentions — say it beside the work, not instead of it
|
|
105
|
+
|
|
106
|
+
A comment is ABOUT something; an edit changes it. When you have a question or a note for a person,
|
|
107
|
+
leave it where they will see it, on the thing itself:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
clearly_comment { target: "CLR-42", content: "Is this still the Q3 number? @sam" }
|
|
111
|
+
clearly_comment { target: "<sheetId>", content: "Double-counts refunds", fields: { cell: "Plan!D12" } }
|
|
112
|
+
clearly_comment { target: "<deckId>", content: "Tighten this headline", fields: { slide: 3 } }
|
|
113
|
+
clearly_read { type: "comment", target: "SS-41" } # the thread, on any item
|
|
114
|
+
clearly_read { type: "comment", target: "@me" } # where YOU were @mentioned
|
|
115
|
+
clearly_edit { type: "comment", target: "<mentionId>", changes: { acknowledged: true } }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`target` is the item — a document, sheet, deck, ticket, board, thought or canvas, by id, key or exact
|
|
119
|
+
title. Anchor with `fields.quote` (document), `slide` (deck, required), `cell` (sheet), `column`
|
|
120
|
+
(board) or `pinId` / `x`,`y` (canvas); an anchor the item cannot carry is refused, never dropped.
|
|
121
|
+
|
|
122
|
+
`@handle` mentions a person (notified) or an agent (queued in its inbox). Get real handles with
|
|
123
|
+
`clearly_read { type: "comment", target, includeMentionables: true }`. The write's result lists
|
|
124
|
+
`mentions`, and `unmatchedMentions` for any handle that reached **no one** — say so, do not assume.
|
|
125
|
+
Acknowledge a mention once you have acted on it, or it comes back in every session.
|
|
126
|
+
|
|
127
|
+
## `clearly.md` — what is settled
|
|
128
|
+
|
|
129
|
+
Not a document like the others. It holds what this place IS (the work, the constraints, the
|
|
130
|
+
vocabulary, the decisions that stopped moving) and **how this person wants to be worked with** —
|
|
131
|
+
length, tone, format, and what irritates them, which is said once in passing and worth more than
|
|
132
|
+
anything they praise.
|
|
133
|
+
|
|
134
|
+
Fold a conclusion into it once it has stopped moving. Reach for `edit`, never `replace`.
|
|
135
|
+
|
|
136
|
+
## Report what MOVED
|
|
137
|
+
|
|
138
|
+
*"You added a paragraph about the 12-hour window to Pricing and removed the line about three
|
|
139
|
+
credits"* is a report. *"I've updated the note"* is not — it costs a turn and says nothing they
|
|
140
|
+
could not have assumed.
|
|
141
|
+
|
|
142
|
+
## What not to do
|
|
143
|
+
|
|
144
|
+
- **Do not tidy unasked.** A document that looks redundant or superseded is still theirs. If two
|
|
145
|
+
say the same thing, say so and let them choose.
|
|
146
|
+
- **`delete` archives, it does not erase** — the document moves to `~/.archive/`, stays readable
|
|
147
|
+
and comes back. Report it as **archived**; "deleted" makes someone believe something is gone
|
|
148
|
+
that is not.
|
|
149
|
+
- **Do not invent a media line.** `` renders a photo, audio or video by file
|
|
150
|
+
extension. You cannot upload, so never write one pointing at a URL you guessed — it renders
|
|
151
|
+
broken, which reads as their photo being lost. Move existing lines; never fabricate one.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearly-init
|
|
3
|
+
description: >-
|
|
4
|
+
Connect a Clearly workspace to Claude Code or Codex and prove both the MCP and
|
|
5
|
+
CLI paths work. Prefer browser OAuth for remote HTTP clients; when an agent-bound
|
|
6
|
+
bearer credential is required, mint and install it with the active `beehaven`
|
|
7
|
+
identity. Use FIRST whenever a Clearly tool is refused for auth, returns nothing
|
|
8
|
+
on a workspace that should have content, or the user has never connected.
|
|
9
|
+
Triggers: "connect Clearly", "set up Clearly", "sign in to Clearly",
|
|
10
|
+
"authenticate", "install the MCP", "add the MCP token", "it says unauthorized",
|
|
11
|
+
"the tools are not showing up", "/mcp shows nothing", "which workspace am I in".
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
> **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may
|
|
15
|
+
> expose them with a server prefix — e.g.
|
|
16
|
+
> `mcp__plugin_clearly_clearly-staging__clearly_read`. **Match by suffix, not by
|
|
17
|
+
> exact name**: a skill written against the bare name resolves to nothing otherwise, and
|
|
18
|
+
> the failure looks like "the tool doesn't exist" rather than "the name is decorated".
|
|
19
|
+
>
|
|
20
|
+
> **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
|
|
21
|
+
> session — note that these skills still LIST when it isn't, so you find out by firing a
|
|
22
|
+
> dead call. Authorise it (`/mcp`, or `claude mcp`), or if you have a shell, use the
|
|
23
|
+
> `beehaven` CLI and its own `clearly-canvas` skill instead.
|
|
24
|
+
|
|
25
|
+
# `/clearly:init` — connect a Clearly workspace to Claude Code or Codex
|
|
26
|
+
|
|
27
|
+
Run this once per machine. The plugin's `.mcp.json` points at
|
|
28
|
+
`https://relay.clearly.sh/mcp`; complete authentication and verify the connection before doing
|
|
29
|
+
workspace work.
|
|
30
|
+
|
|
31
|
+
## Steps
|
|
32
|
+
|
|
33
|
+
### 1. Authenticate (OAuth — browser sign-in)
|
|
34
|
+
|
|
35
|
+
The plugin already wires the MCP server. Prefer browser OAuth:
|
|
36
|
+
|
|
37
|
+
- **In Claude Code (recommended)**: run `/mcp`, select `clearly`, and choose **Authenticate**. A browser window opens for sign-in; approve and it closes itself. No token.
|
|
38
|
+
- **From the shell**: `claude mcp login clearly` — same browser sign-in flow.
|
|
39
|
+
- **In Codex**: `codex mcp login clearly`. If the server was added outside the plugin first, run
|
|
40
|
+
`codex mcp add clearly --url https://relay.clearly.sh/mcp`, then log in.
|
|
41
|
+
|
|
42
|
+
The sign-in grants scoped access (`rpc:read` for search and read; add `rpc:write` to let the
|
|
43
|
+
agent write documents and create boards or tickets). Sign out or revoke with the client's MCP
|
|
44
|
+
logout command, or from Settings → **Developers** in the app.
|
|
45
|
+
|
|
46
|
+
### Agent-bound token path (CLI and automation)
|
|
47
|
+
|
|
48
|
+
Use this when the client cannot complete OAuth or when the credential must act as a specific agent
|
|
49
|
+
identity. Sign in and select the identity first:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
beehaven agent login <agent-name>
|
|
53
|
+
beehaven connect home
|
|
54
|
+
beehaven agent whoami
|
|
55
|
+
beehaven mcp install --client codex
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The installer writes through the client's own config command and never prints the token. Restart
|
|
59
|
+
the client afterward. A static token normally carries `rpc:read` and `rpc:write`; `agent:ask` is
|
|
60
|
+
OAuth-only and `rpc:admin` is reserved for configured platform admins. Never paste a bearer token
|
|
61
|
+
into a document, ticket, canvas, log, or chat response.
|
|
62
|
+
|
|
63
|
+
> **Working in more than one workspace?** A client stores one credential per server entry, so two
|
|
64
|
+
> workspaces behind a single entry collide. Use `https://relay.clearly.sh/mcp/w/<workspaceId>` as a
|
|
65
|
+
> second entry — see [the MCP docs](https://clearly.sh/docs/mcp).
|
|
66
|
+
|
|
67
|
+
### 2. Verify
|
|
68
|
+
|
|
69
|
+
Run `/mcp` in Claude Code, or `codex mcp list` in Codex. You should see `clearly` authenticated
|
|
70
|
+
with **7 tools**:
|
|
71
|
+
|
|
72
|
+
**Seven MCP tools, split by what they DO rather than by what they act on.** The four CRUD tools
|
|
73
|
+
cover every artifact type — document, canvas, sheet, deck, board, ticket, project — so the tool fixes
|
|
74
|
+
whether it reads or writes and `type` says what it touches.
|
|
75
|
+
|
|
76
|
+
- **`clearly_catalog`** — optional, lazy machine-readable schemas. Use `{ type, operation }` before a type-specific write/edit; omit both for the compact capability overview.
|
|
77
|
+
- **`clearly_read` / `clearly_create` / `clearly_edit` / `clearly_replace` / `clearly_delete`** — open one, make a new one, change part of one, overwrite one's whole body, archive one. Each takes a uniform same-operation `batch`.
|
|
78
|
+
- **`clearly_comment`** — comment on any item (with @mentions), or send and reply to messages.
|
|
79
|
+
- **`clearly_grep` / `clearly_glob`** — find by content, find by name. Both span notes and code.
|
|
80
|
+
|
|
81
|
+
Addressed by `target`: a path (`~/Q1/Plan.md`), a key (`CLR-42`) or an id. Folders are projects,
|
|
82
|
+
documents are `.md`, canvases are `.scene.json` — the extension names the type, and you pass `type`
|
|
83
|
+
when it does not.
|
|
84
|
+
|
|
85
|
+
**Everything else is the `beehaven` CLI**, by action name — the canvas, the Company Brain, skills,
|
|
86
|
+
scheduling, stickers, billing. `beehaven actions` lists them; `beehaven call <action> '<json>'` runs
|
|
87
|
+
one:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
beehaven call context-search '{"query":"per-seat billing","scope":"org"}'
|
|
91
|
+
beehaven call canvas-act '{"compositionId":"c_…","batch":[…]}'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
⚠ **That split is deliberate, not a gap.** A single tool that runs whatever action the caller names
|
|
95
|
+
spans safe and unsafe operations at once — a connector-directory rejection criterion, and one the
|
|
96
|
+
criteria explicitly refuse to let a description paper over. So the generic door is the shell, where
|
|
97
|
+
Claude Code already lives and where every call is attributed to your agent.
|
|
98
|
+
|
|
99
|
+
> ⚠ **Everything requires authentication.** There is no anonymous surface: as of 2026-09-11 both
|
|
100
|
+
> `tools/list` and `tools/call` resolve a credential first, and every credential is bound to an
|
|
101
|
+
> agent. A 401 carries an OAuth challenge, so a capable client can recover from it by itself —
|
|
102
|
+
> but a caller with no token sees no catalog, by design.
|
|
103
|
+
|
|
104
|
+
## Install the CLI — the other half of the surface
|
|
105
|
+
|
|
106
|
+
The seven MCP tools cover reading, writing and searching artifacts. **Everything else — the canvas,
|
|
107
|
+
the Company Brain, skills, stickers — is reached with `beehaven`**, and the skills in this plugin
|
|
108
|
+
assume it is on PATH. Claude Code has a shell, so this is one command:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl -fsSL https://clearly.sh/install.sh | sh
|
|
112
|
+
beehaven login # once, opens a browser
|
|
113
|
+
beehaven status # daemon + relay, both should read UP
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
⚠ **`beehaven: command not found` is the failure to expect**, and it does not look like a missing
|
|
117
|
+
install — it looks like the skill being wrong. If a `beehaven call …` line fails that way, install
|
|
118
|
+
it rather than reaching for an MCP tool that does not exist.
|
|
119
|
+
|
|
120
|
+
⚠ **Do not restart the user's daemon to fix something.** It is theirs; `beehaven status` tells you
|
|
121
|
+
what is wrong and the message says which command fixes it.
|
|
122
|
+
|
|
123
|
+
Then load the usage skill: it teaches the Company Brain + self-prompting loops — see `clearly-workflows`.
|
|
124
|
+
|
|
125
|
+
A quick functional test:
|
|
126
|
+
> "Map my Clearly workspace's brain using the clearly MCP."
|
|
127
|
+
|
|
128
|
+
If Claude calls `clearly_catalog` and gets back the artifact types with their capabilities, the connection is live. It is a connection check and schema lookup, not a required preflight for ordinary reads/deletes.
|
|
129
|
+
|
|
130
|
+
## Troubleshooting
|
|
131
|
+
|
|
132
|
+
- **`Missing scope "rpc:write"`** — the sign-in didn't grant write access. Run `claude mcp logout clearly`, then re-authenticate (`/mcp` → **Authenticate**) and approve the write scope.
|
|
133
|
+
- **`Not authenticated` / calls rejected** — the session expired or was revoked. Re-authenticate with `claude mcp login clearly` (or `/mcp` → **Authenticate**).
|
|
134
|
+
- **`clearly` shows in `/mcp` but as unauthenticated** — the endpoint is preconfigured by the plugin; you still need to complete the browser sign-in. Select `clearly` → **Authenticate**.
|
|
135
|
+
- **`Network error reaching ...`** — relay.clearly.sh might be down (rare); check https://clearly.sh status page.
|