@clearlysh/beehaven 0.8.29 → 0.8.31

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/watcher.js CHANGED
@@ -1,18 +1,19 @@
1
1
  // ============================================================================
2
2
  // Beehaven Office - Claude Code Event Watcher
3
- // Primary: Unix domain socket HTTP server at /tmp/beehaven.sock
3
+ // Primary: HTTP server on a local socket — /tmp/beehaven.sock, or a named pipe on Windows (ipc-path.ts)
4
4
  // Fallback: Watches the JSONL events file written by the hook script
5
5
  // ============================================================================
6
6
  // chokidar is imported lazily so a missing bundle (e.g. when the daemon ships
7
7
  // inside an Electron app whose node_modules layout doesn't include chokidar)
8
8
  // degrades the file-watcher to a no-op rather than crashing the whole daemon.
9
9
  // The primary Unix-socket path does not depend on chokidar.
10
- import { createReadStream, existsSync, writeFileSync, statSync, renameSync, unlinkSync, chmodSync, watch as fsWatch, } from 'fs';
10
+ import { createReadStream, existsSync, writeFileSync, statSync, renameSync, chmodSync, watch as fsWatch, } from 'fs';
11
11
  import { createInterface } from 'readline';
12
12
  import { createServer } from 'http';
13
13
  import { EventEmitter } from 'events';
14
- const EVENTS_FILE = '/tmp/beehaven-events.jsonl';
15
- const SOCKET_PATH = '/tmp/beehaven.sock';
14
+ import { watcherSocketPath, eventsFilePath, clearStaleSocket, restrictSocketToOwner } from './ipc-path.js';
15
+ const EVENTS_FILE = eventsFilePath();
16
+ const SOCKET_PATH = watcherSocketPath();
16
17
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
17
18
  export { SOCKET_PATH };
18
19
  export class ClaudeWatcher extends EventEmitter {
@@ -32,14 +33,8 @@ export class ClaudeWatcher extends EventEmitter {
32
33
  // ── Unix Domain Socket ───────────────────────────────────────────────
33
34
  /** Start HTTP server on Unix domain socket for direct event delivery */
34
35
  startSocket() {
35
- // Remove stale socket file
36
- try {
37
- if (existsSync(SOCKET_PATH))
38
- unlinkSync(SOCKET_PATH);
39
- }
40
- catch {
41
- /* may not exist */
42
- }
36
+ // Remove a stale socket file (a Windows named pipe leaves none)
37
+ clearStaleSocket(SOCKET_PATH);
43
38
  this.socketServer = createServer((req, res) => {
44
39
  if (req.method !== 'POST' || req.url !== '/event') {
45
40
  res.writeHead(404);
@@ -71,10 +66,7 @@ export class ClaudeWatcher extends EventEmitter {
71
66
  // during startup). A post-bind chmod achieves the same security
72
67
  // without the race.
73
68
  this.socketServer.listen(SOCKET_PATH, () => {
74
- try {
75
- chmodSync(SOCKET_PATH, 0o600);
76
- }
77
- catch { }
69
+ restrictSocketToOwner(SOCKET_PATH); // no-op for a named pipe
78
70
  console.log(`[watcher] Socket listening on ${SOCKET_PATH}`);
79
71
  });
80
72
  }
@@ -84,13 +76,7 @@ export class ClaudeWatcher extends EventEmitter {
84
76
  this.socketServer.close();
85
77
  this.socketServer = null;
86
78
  }
87
- try {
88
- if (existsSync(SOCKET_PATH))
89
- unlinkSync(SOCKET_PATH);
90
- }
91
- catch {
92
- /* may already be gone */
93
- }
79
+ clearStaleSocket(SOCKET_PATH);
94
80
  }
95
81
  // ── File Watcher (fallback) ──────────────────────────────────────────
96
82
  /** Start file watcher as fallback for when socket isn't available */
@@ -0,0 +1,77 @@
1
+ /**
2
+ * windows-hide — no console windows flashing open on Windows (CAN2-56).
3
+ *
4
+ * ⚠⚠ WHY WINDOWS FLASHED. A Windows process with no console of its own — the detached daemon,
5
+ * or `beehaven` run by an agent or an app rather than in a terminal — that starts a CONSOLE
6
+ * program (cmd.exe for every `exec`/`execSync`, powershell, git, node, …) makes Windows create a
7
+ * brand-new console window for that child. It pops up and closes as the child exits: one flash per
8
+ * call, and the daemon makes calls on timers. Node prevents it per call with `windowsHide: true`;
9
+ * the CLI had ~140 child-process calls and one of them set it.
10
+ *
11
+ * ⚠ ONE CHOKEPOINT, NOT 140 CALL SITES. Imported FIRST by every entry point (`cli.ts`,
12
+ * `clearly.ts`, `daemon-entry.ts`), this wraps the `child_process` functions so `windowsHide`
13
+ * defaults to true for every caller in the process — this package, `@beehaven/core`, and any
14
+ * dependency — and `syncBuiltinESMExports()` makes ESM named imports (`import { spawn } …`) see
15
+ * the wrappers too. An explicit `windowsHide: false` is respected. Off Windows it does nothing.
16
+ *
17
+ * ⚠ `exec` / `execFile` carry `util.promisify.custom` (so `promisify(exec)` resolves
18
+ * `{ stdout, stderr }`). The wrappers forward it; without that, every `promisify(exec)` caller
19
+ * would silently start receiving a bare string.
20
+ */
21
+ import { createRequire, syncBuiltinESMExports } from 'node:module';
22
+ import { promisify } from 'node:util';
23
+ const require = createRequire(import.meta.url);
24
+ const MARK = Symbol.for('beehaven.windowsHide');
25
+ /**
26
+ * A child_process call's arguments with `windowsHide: true` set, unless the caller chose a value.
27
+ * `takesArgv` = the second parameter may be an argv array (spawn, spawnSync, execFile,
28
+ * execFileSync, fork). Pure — exported for tests.
29
+ */
30
+ export function withWindowsHide(args, takesArgv) {
31
+ const a = [...args];
32
+ for (let i = 1; i < a.length; i++) {
33
+ const v = a[i];
34
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
35
+ if (v.windowsHide === undefined)
36
+ a[i] = { ...v, windowsHide: true };
37
+ return a;
38
+ }
39
+ }
40
+ // No options object: it goes after the command (and its argv array), before any callback. An
41
+ // explicit `undefined`/`null` options slot is filled rather than shifted.
42
+ const at = takesArgv && Array.isArray(a[1]) ? 2 : 1;
43
+ if (at < a.length && (a[at] === undefined || a[at] === null))
44
+ a[at] = { windowsHide: true };
45
+ else
46
+ a.splice(at, 0, { windowsHide: true });
47
+ return a;
48
+ }
49
+ const WRAPPED = [
50
+ ['spawn', true], ['spawnSync', true], ['execFile', true], ['execFileSync', true], ['fork', true],
51
+ ['exec', false], ['execSync', false],
52
+ ];
53
+ /** Wrap `child_process` once per process. Returns whether it is installed. `force` is for tests. */
54
+ export function installWindowsHide(opts = {}) {
55
+ if (process.platform !== 'win32' && !opts.force)
56
+ return false;
57
+ const cp = require('node:child_process');
58
+ if (cp[MARK])
59
+ return true;
60
+ for (const [name, takesArgv] of WRAPPED) {
61
+ const orig = cp[name];
62
+ if (typeof orig !== 'function')
63
+ continue;
64
+ const wrapper = function (...args) {
65
+ return orig.apply(this, withWindowsHide(args, takesArgv));
66
+ };
67
+ const custom = orig[promisify.custom];
68
+ if (typeof custom === 'function') {
69
+ wrapper[promisify.custom] = (...args) => custom.apply(orig, withWindowsHide(args, takesArgv));
70
+ }
71
+ cp[name] = wrapper;
72
+ }
73
+ cp[MARK] = true;
74
+ syncBuiltinESMExports();
75
+ return true;
76
+ }
77
+ installWindowsHide();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clearlysh/beehaven",
3
- "version": "0.8.29",
3
+ "version": "0.8.31",
4
4
  "description": "Beehaven CLI — command your Clearly workspace, agents, and canvas from the terminal",
5
5
  "private": false,
6
6
  "type": "module",
@@ -40,6 +40,12 @@ const SOURCES = {
40
40
  'codebase-map': 'apps/mcp-server/plugin/skills/codebase-map/SKILL.md',
41
41
  'clearly-workspace': 'apps/mcp-server/plugin/skills/clearly-workspace/SKILL.md',
42
42
  'clearly-agent': 'apps/mcp-server/plugin/skills/clearly-agent/SKILL.md',
43
+ // CAN2-57 — the rest of the plugin's skills: one set across plugin, CLI and Mac app.
44
+ 'visualize': 'apps/mcp-server/plugin/skills/visualize/SKILL.md',
45
+ 'sticker-pack': 'apps/mcp-server/plugin/skills/sticker-pack/SKILL.md',
46
+ 'clearly-docs': 'apps/mcp-server/plugin/skills/clearly-docs/SKILL.md',
47
+ 'clearly-init': 'apps/mcp-server/plugin/skills/clearly-init/SKILL.md',
48
+ 'clearly-workflows': 'apps/mcp-server/plugin/skills/clearly-workflows/SKILL.md',
43
49
  };
44
50
 
45
51
  let missing = 0;
@@ -64,15 +64,31 @@ handover. A mark of hand-placed béziers does not, and it shows.
64
64
  | **Counter-form** | the shape is the NEGATIVE space between forms | clever marks that survive one colour |
65
65
  | **Modular** | one unit repeated/rotated on a grid | systems, networks, collectives |
66
66
 
67
- **Build it as an `svg` node with a stated viewBox and derived numbers:**
67
+ **Build it from native layers, every number derived from the construction** — the way an
68
+ identity designer works in Figma, so each part of the mark stays a named, editable layer in the
69
+ brand colour (never one generated or hand-written SVG):
68
70
 
69
71
  ```jsonc
70
- {"action":"canvas.create-node","args":{"name":"mark","type":"svg","x":120,"y":120,"w":240,"h":240,
71
- "svg":"<svg viewBox='0 0 240 240' xmlns='http://www.w3.org/2000/svg'>…</svg>"}}
72
+ {"batch":[
73
+ {"action":"frame.create","args":{"name":"Mark","x":120,"y":120,"w":240,"h":240}},
74
+ {"action":"canvas.create-node","args":{"type":"ellipse","name":"mark/ring","parentId":"Mark",
75
+ "x":20,"y":20,"w":200,"h":200,"fill":"#1B4DFF"}},
76
+ {"action":"canvas.create-node","args":{"type":"ellipse","name":"mark/counter","parentId":"Mark",
77
+ "x":70,"y":70,"w":100,"h":100,"fill":"#FFFFFF"}},
78
+ {"action":"vector.create","args":{"name":"mark/blade","parentId":"Mark","closed":true,"fill":"#1B4DFF",
79
+ "points":[{"x":120,"y":20},{"x":220,"y":120},{"x":120,"y":120}]}},
80
+ {"action":"canvas.group","args":{"ids":["mark/ring","mark/counter","mark/blade"],"name":"mark"}}
81
+ ]}
72
82
  ```
73
83
 
74
- ⚠ **`<text>` inside an `svg` node does NOT render headlessly** — no system fonts are loaded.
75
- Wordmarks are real text nodes, or outlined paths. Never live `<text>` in the mark.
84
+ Here R = 100: the counter is R/2, the blade's corners sit on the circle. Curves are
85
+ `vector.create` points with `hIn`/`hOut` control points (absolute coordinates) placed where the
86
+ construction says; a quarter-circle's handle sits 0.552·R from its anchor. A counter-form is a
87
+ ground-colour shape layered on top; `vector.boolean` makes a true cut-out when an editor is open.
88
+ Raw SVG (`type:"svg"`) is only for importing a mark the client already has.
89
+
90
+ ⚠ **Wordmarks are real text nodes, or outlined paths** — and `<text>` inside an imported `svg`
91
+ node does NOT render headlessly (no system fonts are loaded).
76
92
 
77
93
  **Test the mark against these before going further. A mark that fails any of them is not done:**
78
94
 
@@ -110,10 +110,19 @@ can read and edit artifact records, while canvas-specific actions use the CLI.
110
110
  beehaven env
111
111
  beehaven connect home
112
112
  beehaven agent login <name> --label "Design <artifact>" --client cli
113
- beehaven call canvas-perceive '{"compositionId":"<id>","format":"json"}'
113
+ beehaven call canvas-perceive '{"compositionId":"<id>","outline":true}'
114
114
  beehaven call canvas-catalog '{"query":"create-node"}'
115
115
  ```
116
116
 
117
+ Read a canvas the way you read a repository: `canvas-perceive {outline:true}` is `tree` (artboards,
118
+ sections, layer counts, first copy, EMPTY sections — a few hundred bytes); `{frame:"<id or name>"}`
119
+ opens one frame like a file (its layers indented, positions relative to their parent);
120
+ `{find:"Start free"}` is `grep` (copy, layer names or `#hex`, with each match's frame path). Use
121
+ `format:"json"` only when you need every field of every node. Every `canvas-act` write returns
122
+ `diagnostics` — a lint of the artboards it touched (text overflow, collisions, covered text,
123
+ contrast) — treat them like compiler errors and fix them in the next batch. `canvas.review` is still
124
+ the pixel check and the ticket gate.
125
+
117
126
  A canvas is planned like a git repository: its own plan board, a WORK TICKET for the request, and
118
127
  COMMITS — one ticket per coherent layer or section of a larger change.
119
128
  `design-start {title, brandId?}` makes a PRIVATE DRAFT canvas, bound to the brand, with its private
@@ -133,7 +142,7 @@ builds on the last and can be reviewed alone. Build ONE commit per canvas-act ba
133
142
  `canvas.review` of the artboard and carries
134
143
  `"ticket":{"id":"<key>","status":"done","resolution":"<what changed>"}` — it lands only if that
135
144
  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:
145
+ revision and the layers the commit changed. Auto-layout, gradients, shadows, drawing and placeholders:
137
146
  see "Technique" in [authoring.md](authoring.md).
138
147
 
139
148
  `composition-board-get {compositionId}` reads the plan back. `design-start` with a project, ticket or
@@ -195,8 +204,11 @@ hand-author hundreds of coordinates in a chat message.
195
204
  Check contrast on the actual ground. Use the user's real brand assets.
196
205
  - Choose relevant imagery, a meaningful diagram, a product view or a deliberate
197
206
  typographic treatment. Decorative blobs and random stock photos do not prove a
198
- product claim. Native SVG/paths suit diagrams and vector art; raster generation
199
- suits imagery when an image tool is available.
207
+ product claim. Draw marks, icons, illustrations and diagrams yourself from native
208
+ layers (shapes, vector.create pen paths, text, groups), the way a designer works in
209
+ Figma, so every part stays named and editable. Raster generation suits photographic
210
+ imagery when an image tool is available. Raw SVG (an svg node, or a generated SVG)
211
+ is for importing an existing file or a requested SVG, never the default.
200
212
  - Define repeated components once. Vary composition where the content changes.
201
213
  A deck of identical cards has little rhythm even when every card aligns.
202
214
  - Keep important copy and layers editable. Do not flatten the whole composition
@@ -123,14 +123,15 @@ deliverable requires them, with content order and hierarchy reconsidered for the
123
123
 
124
124
  The same rules the in-app assistant works to (canvas-catalog returns them as `technique`).
125
125
 
126
- - AUTO-LAYOUT: rows and stacks (nav links, button groups, card rows, form fields) are a frame whose children you create in reading order, then canvas.auto-layout { id, direction, gap, padding, align, justify, hug }. hug:false keeps the frame size, which a full-width bar needs with justify:"space-between"; set a child to layoutSizingH/V "fill" | "hug" | "fixed" with canvas.update-nodes.
126
+ - AUTO-LAYOUT: rows and stacks (nav links, button groups, card rows, form fields) are a frame whose children you create in reading order, then canvas.auto-layout { id, direction, gap, padding, align, justify, hug }. hug:false keeps the frame size, which a full-width bar needs with justify:"space-between"; set a child to layoutSizingH/V "fill" | "hug" | "fixed" with canvas.update-nodes. The frame stays live: create more children into it (parentId) and they flow in; layoutPositioning:"absolute" takes one out of the flow.
127
127
  - GRADIENTS: gradient { kind, angle, stops: [{ offset, color }] } replaces the solid fill. angle is the direction the colour travels: 0 = left to right, 90 = top to bottom, 45 = top-left to bottom-right (not CSS degrees). Use two or three stops from the brand palette, and keep text off a busy gradient or check its contrast in canvas.review.
128
128
  - SHADOWS AND BLUR: effects on any node. A soft card shadow is { type: "drop-shadow", x: 0, y: 4, blur: 16, spread: 0, color: "#0000001F" }; frosted glass is { type: "background-blur", blur: 16 }. Never fake a shadow with an extra rectangle.
129
- - SVG PLACEHOLDERS: until real imagery exists, draw it as an svg node at its final size in a muted tone from the brand palette, never a flat accent block, e.g. for a 400×240 slot svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 240"><rect width="400" height="240" rx="12" fill="#EDE6DF"/><circle cx="170" cy="96" r="14" fill="#CDBFB2"/><path d="M150 168l40-48 30 34 22-20 40 34z" fill="#CDBFB2"/></svg>' — <svg viewBox="0 0 W H"> always matches the node w and h. Put a label in its own text node and name the svg "<section> image placeholder". Hand-write only simple marks like this (placeholders, dividers, UI icons); logos, illustrations and finished artwork come from svg-make.
129
+ - DRAWING: build every mark, icon, illustration and diagram shape from native layers, as a designer does in Figma, never as one generated SVG file. Primitives are canvas.create-node rect and ellipse; curves are pen paths, vector.create { name, parentId, points: [{ x, y, hIn?: { x, y }, hOut?: { x, y } }], closed, fill or stroke, strokeWidth }, where hIn/hOut are absolute control points: use few anchors with smooth handles, and refine with vector.read, vector.vertex.move and vector.handle.set. Round caps and joins: canvas.update-nodes patch { strokeCap: "round", strokeJoin: "round" }. Work back to front: ground, big silhouettes in flat brand colours, then features, then details and highlights; check the silhouette in canvas.review before detailing. Name layers by part ("fox/ear-left"), group each figure with canvas.group { ids, name } and set overlap with canvas.order { ids, op }. Make a cut-out by layering a ground-colour shape on top; vector.boolean { op } on selected paths needs an open editor. Keep one stroke weight per drawing. Photographic or painterly imagery is a placed raster. Raw SVG stays supported but is never the default: when an SVG is asked for, write the markup yourself as an svg node, canvas.create-node { type: "svg", name, w, h, svg } with <svg viewBox="0 0 W H"> matching w and h, or import existing markup the same way; never an SVG generation pipeline.
130
+ - PLACEHOLDERS: until real imagery exists, draw a native placeholder at the final size in a muted brand tone, never a flat accent block: a rect with the slot radius plus a simple motif (an ellipse sun, a vector.create mountain), grouped with canvas.group and named "<section> image placeholder", with any label in its own text node.
130
131
  - SECTIONS: one frame per page section, named exactly as its ticket says, inside the page artboard. Build a section in one canvas-act batch that ends with canvas.review of the artboard and carries ticket: { id: "<its key>", status: "done", resolution: "<what changed>" }. The ticket moves only when that review is clean or look; result.ticket says whether it did. Then start the next section.
131
132
 
132
133
  Use `arrow.create {from,to,routing,label}` for connected diagrams, so arrows remain bound
133
- when nodes move. Use native shapes and SVG for custom vector details. Keep labels as
134
+ when nodes move. Draw custom vector details natively (shapes, vector.create pen paths); raw SVG only imports an existing file. Keep labels as
134
135
  native text so they remain editable and measurable.
135
136
 
136
137
  Use `canvas.place {kind,id,x,y,w,h,name,parentId?}` to put a workspace object (document,
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: clearly-docs
3
+ description: >-
4
+ Read, search, edit and recover a company's documents in a Clearly workspace.
5
+ Use the seven-tool MCP filesystem for ordinary work; use the `beehaven` CLI
6
+ only for document history, diffs, and restore. Load this whenever the answer
7
+ depends on what the company has written down, or when you are about to write
8
+ something down for it. Triggers: "what do our docs say", "find the doc about
9
+ X", "update the spec", "write this up", "add it to the docs", "has this
10
+ changed", "what did they edit", "search the wiki", "company knowledge", "our
11
+ documentation", "the PRD", "the runbook".
12
+ ---
13
+
14
+ > **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may expose them
15
+ > with a server prefix — e.g. `mcp__plugin_clearly_clearly__clearly_read`. **Match by suffix,
16
+ > not by exact name**: a skill written against the bare name resolves to nothing otherwise, and
17
+ > the failure looks like "the tool doesn't exist" rather than "the name is decorated".
18
+ >
19
+ > **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
20
+ > session — these skills still LIST when it isn't, so you find out by firing a dead call.
21
+ > Authorise it (`/mcp`, or `claude mcp`).
22
+
23
+ ## What this is
24
+
25
+ The workspace is a **filesystem**, not a chat surface with a search box.
26
+
27
+ ```
28
+ ~/ every document
29
+ ~/clearly.md what is settled for the whole workspace
30
+ ~/<Folder>/ one directory per project
31
+ ~/<Folder>/clearly.md what is settled for that folder
32
+ ~/.archive/ put away — hidden, readable, read-only, still searched
33
+ ```
34
+
35
+ Folders are projects. Documents are `.md` files named `<KEY> <Title>.md` — e.g.
36
+ `CLR-42 Pricing.md`. The key is an address: `cat ~/CLR-42.md` works from anywhere, and so does
37
+ the full name and the bare title.
38
+
39
+ ## The loop
40
+
41
+ **Find it, then open it.** `clearly_glob` answers "what is it called", `clearly_grep` answers
42
+ "where is this mentioned", and `clearly_read` opens what either one returned.
43
+
44
+ ```
45
+ clearly_glob { pattern: "~/**/*.md" }
46
+ clearly_grep { pattern: "pricing", limit: 5 }
47
+ clearly_read { target: "~/Prepress/CLR-42 Pricing.md" }
48
+ ```
49
+
50
+ ⚠ **`clearly_grep` takes a REGEX, unparsed** — `|`, `(`, `$` and quotes mean what they mean in a
51
+ regular expression. Nothing shells out, so nothing eats them as syntax first.
52
+
53
+ ⚠ **Archived documents are excluded from both.** When any matched, the result carries
54
+ `archivedHidden` saying how many — mention that rather than reporting a clean miss. Search the
55
+ archive by naming it: `clearly_grep { pattern: …, path: "~/.archive" }`.
56
+
57
+ **Structural questions — a date range, a null body, counts — have no keyword to search for**, so
58
+ they go to SQL over the workspace's own tables. That is a CLI job, not an MCP tool:
59
+
60
+ ```
61
+ beehaven call bash '{"command":"sql SELECT title, updated_at FROM notes WHERE project_id IS NULL LIMIT 20"}'
62
+ ```
63
+
64
+ Read-only, one statement, credential columns redacted.
65
+
66
+ ## ⚠⚠ Read before you write, and check what changed
67
+
68
+ This is the part that makes documentation stay true, and it is the part agents skip.
69
+
70
+ The MCP read/edit/replace tools cover the current body. Document history is a separate CLI surface:
71
+
72
+ - **`beehaven call document-status '{"limit":20}'`** lists recent changes. `mine: true` is the
73
+ certain signal that the active agent made the latest change. `by: "unknown"` means the log has
74
+ no author; it may be a browser edit or an older unstamped agent write, so do not claim a person
75
+ made it.
76
+ - **`beehaven call document-diff '{"id":"<documentId>"}'`** follows up with added and removed
77
+ lines. Pass a `versionId` from status to compare against a specific checkpoint.
78
+
79
+ Run status when you resume a conversation, and whenever they say something implying the document
80
+ has moved — *"I rewrote that"*, *"take another look"*.
81
+
82
+ ⚠ **Their edit wins, always.** If a document now disagrees with something you concluded
83
+ earlier, your conclusion is the stale one. You are keeping their documents, not defending your
84
+ own record. Never quietly restore what they took out.
85
+
86
+ ## Editing
87
+
88
+ **`clearly_edit` (find/replace) is the default. `clearly_replace` REPLACES the whole document.**
89
+
90
+ To change one line of a 12,000-character document, `replace` makes you reproduce all 12,000 — and
91
+ anything you fail to retype is gone with no error. It is also a lost-update machine: it writes
92
+ whatever you last read, overwriting anything the human changed in between.
93
+
94
+ - `clearly_edit` requires the `find` text to match **exactly once**. Ambiguity is REFUSED with a
95
+ count, never guessed — an agent that edits the first of three matches has changed something
96
+ nobody looked at.
97
+ - Several independent document edits can be sent in one tool call with `batch`, but each batch
98
+ row is still one exact `{ target, find, replace, all? }` operation. There is no `multi_edit`
99
+ field and the batch is not transactional; inspect the per-item results and retry only failures.
100
+ - `clearly_replace` does not accept `expected_content` or a commit-message field. If you must replace
101
+ a whole existing document, re-read it immediately before writing and verify the returned body.
102
+ Prefer `clearly_edit` whenever a precise passage change can express the job.
103
+
104
+ ## Comments and @mentions — say it beside the work, not instead of it
105
+
106
+ A comment is ABOUT something; an edit changes it. When you have a question or a note for a person,
107
+ leave it where they will see it, on the thing itself:
108
+
109
+ ```
110
+ clearly_comment { target: "CLR-42", content: "Is this still the Q3 number? @sam" }
111
+ clearly_comment { target: "<sheetId>", content: "Double-counts refunds", fields: { cell: "Plan!D12" } }
112
+ clearly_comment { target: "<deckId>", content: "Tighten this headline", fields: { slide: 3 } }
113
+ clearly_read { type: "comment", target: "SS-41" } # the thread, on any item
114
+ clearly_read { type: "comment", target: "@me" } # where YOU were @mentioned
115
+ clearly_edit { type: "comment", target: "<mentionId>", changes: { acknowledged: true } }
116
+ ```
117
+
118
+ `target` is the item — a document, sheet, deck, ticket, board, thought or canvas, by id, key or exact
119
+ title. Anchor with `fields.quote` (document), `slide` (deck, required), `cell` (sheet), `column`
120
+ (board) or `pinId` / `x`,`y` (canvas); an anchor the item cannot carry is refused, never dropped.
121
+
122
+ `@handle` mentions a person (notified) or an agent (queued in its inbox). Get real handles with
123
+ `clearly_read { type: "comment", target, includeMentionables: true }`. The write's result lists
124
+ `mentions`, and `unmatchedMentions` for any handle that reached **no one** — say so, do not assume.
125
+ Acknowledge a mention once you have acted on it, or it comes back in every session.
126
+
127
+ ## `clearly.md` — what is settled
128
+
129
+ Not a document like the others. It holds what this place IS (the work, the constraints, the
130
+ vocabulary, the decisions that stopped moving) and **how this person wants to be worked with** —
131
+ length, tone, format, and what irritates them, which is said once in passing and worth more than
132
+ anything they praise.
133
+
134
+ Fold a conclusion into it once it has stopped moving. Reach for `edit`, never `replace`.
135
+
136
+ ## Report what MOVED
137
+
138
+ *"You added a paragraph about the 12-hour window to Pricing and removed the line about three
139
+ credits"* is a report. *"I've updated the note"* is not — it costs a turn and says nothing they
140
+ could not have assumed.
141
+
142
+ ## What not to do
143
+
144
+ - **Do not tidy unasked.** A document that looks redundant or superseded is still theirs. If two
145
+ say the same thing, say so and let them choose.
146
+ - **`delete` archives, it does not erase** — the document moves to `~/.archive/`, stays readable
147
+ and comes back. Report it as **archived**; "deleted" makes someone believe something is gone
148
+ that is not.
149
+ - **Do not invent a media line.** `![caption](url)` renders a photo, audio or video by file
150
+ extension. You cannot upload, so never write one pointing at a URL you guessed — it renders
151
+ broken, which reads as their photo being lost. Move existing lines; never fabricate one.
@@ -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.