@clearlysh/beehaven 0.8.16 → 0.8.18
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/README.md +25 -3
- package/dist/build-channel.js +2 -2
- package/dist/cli.js +57 -3
- package/dist/commands/handoff.js +227 -0
- package/dist/commands/help.js +42 -0
- package/dist/verb-surface.js +1 -0
- package/package.json +1 -1
- package/skills/beehaven-cli/SKILL.md +1 -0
- package/skills/index.json +1 -1
package/README.md
CHANGED
|
@@ -18,19 +18,41 @@ and this is how you talk to it from a shell.
|
|
|
18
18
|
For a one-off agent session with Node.js 20 or newer, run the npm package directly:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
npx --yes
|
|
21
|
+
npx --yes beehaven@latest guide
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
`guide` works before sign-in. It explains when Clearly is useful, gives the
|
|
25
25
|
human a signup link, and shows the authenticated first artifact. `npx` uses an
|
|
26
|
-
ephemeral npm install; it does not create a Clearly account or
|
|
26
|
+
ephemeral npm install; it does not create a Clearly account or upload anything.
|
|
27
|
+
Public builds send one anonymous, content-free `cli:guide` usage event unless
|
|
28
|
+
`BEEHAVEN_NO_TELEMETRY` or the local `telemetry-off` file disables telemetry.
|
|
27
29
|
The package also provides the `beehaven` binary for a global npm install:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
|
-
npm install -g
|
|
32
|
+
npm install -g beehaven@latest
|
|
31
33
|
beehaven guide
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
The identical release is also listed under the Clearly npm organization as
|
|
37
|
+
`@clearlysh/beehaven`.
|
|
38
|
+
|
|
39
|
+
### Hand work to someone without an account
|
|
40
|
+
|
|
41
|
+
When an agent's work is ready before the person has signed up, `handoff` needs
|
|
42
|
+
no login and prints one private link:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx --yes beehaven@latest handoff plan.md
|
|
46
|
+
npx --yes beehaven@latest handoff plan.md --tasks todo.md --csv budget.csv --wait
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The person previews it, signs in or creates a free account, and saves it. A
|
|
50
|
+
document alone becomes a private draft; with `--tasks` (a Markdown checklist or a
|
|
51
|
+
JSON array) or `--csv` files it becomes a private project with a ticket per task
|
|
52
|
+
and a sheet per CSV. The link is a credential — the first person to save it owns
|
|
53
|
+
the work — so send it only to the person you are working with. Unsaved handoffs
|
|
54
|
+
expire after 7 days. `--wait` returns when they save it.
|
|
55
|
+
|
|
34
56
|
The self-contained installer remains the recommended persistent install. It does
|
|
35
57
|
not require npm to resolve dependencies on the user's machine:
|
|
36
58
|
|
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 = 'cea58c529fe1';
|
|
57
57
|
/** ISO-8601 minute precision, UTC. Rewritten in `dist/` by `scripts/stamp-build-channel.mjs`. */
|
|
58
|
-
export const BUILD_TIME = '2026-09-
|
|
58
|
+
export const BUILD_TIME = '2026-09-27T07:47Z';
|
|
59
59
|
/**
|
|
60
60
|
* The build identity as one short string, for `--version`.
|
|
61
61
|
*
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ if (!process.env.BEEHAVEN_IPC_SCOPE) {
|
|
|
17
17
|
process.env.BEEHAVEN_IPC_SCOPE = 'developer';
|
|
18
18
|
}
|
|
19
19
|
import { parseArgs } from 'node:util';
|
|
20
|
-
import { devModeAvailable, buildLabel } from './build-channel.js';
|
|
20
|
+
import { devModeAvailable, buildLabel, PUBLIC_BUILD } from './build-channel.js';
|
|
21
21
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, } from 'fs';
|
|
22
22
|
import { join, dirname } from 'path';
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
@@ -944,6 +944,12 @@ const AGENT_LOGIN_EXEMPT = new Set([
|
|
|
944
944
|
* as `skills`: setup must not require the thing setup provides.
|
|
945
945
|
*/
|
|
946
946
|
'update',
|
|
947
|
+
/**
|
|
948
|
+
* ⚠ `handoff` EXISTS FOR CALLERS WITH NO ACCOUNT. It posts one Markdown file to the relay's
|
|
949
|
+
* anonymous door and prints a claim link; it never opens a workspace. Gating it on an agent login
|
|
950
|
+
* would refuse the one person it is for — the agent whose user has not signed up yet (SGS-73).
|
|
951
|
+
*/
|
|
952
|
+
'handoff',
|
|
947
953
|
]);
|
|
948
954
|
/**
|
|
949
955
|
* ⚠ ALIAS → CANONICAL VERB, for the activity log's `source` column only.
|
|
@@ -1359,6 +1365,10 @@ async function run() {
|
|
|
1359
1365
|
quote: { type: 'string' }, // document passage
|
|
1360
1366
|
cell: { type: 'string' }, // sheet cell, A1 or "Tab!B4"
|
|
1361
1367
|
sheet: { type: 'string' }, // sheet tab, when --cell names none
|
|
1368
|
+
// `handoff`: a task list file, CSV files (repeatable), and waiting for the person to save it.
|
|
1369
|
+
tasks: { type: 'string' },
|
|
1370
|
+
csv: { type: 'string', multiple: true },
|
|
1371
|
+
wait: { type: 'boolean', default: false },
|
|
1362
1372
|
column: { type: 'string' }, // board column key or label
|
|
1363
1373
|
pin: { type: 'string' }, // canvas pin to reply to
|
|
1364
1374
|
// (--x / --y for a new pin and --kind are declared below, shared with other verbs)
|
|
@@ -1716,13 +1726,17 @@ async function run() {
|
|
|
1716
1726
|
return;
|
|
1717
1727
|
}
|
|
1718
1728
|
if (command === 'guide') {
|
|
1729
|
+
const signupUrl = withUtm('https://www.clearly.sh/signup?utm_campaign=agent_discovery', 'beehaven', 'cli');
|
|
1730
|
+
const demoUrl = withUtm('https://www.clearly.sh/demo?utm_campaign=agent_discovery', 'beehaven', 'cli');
|
|
1719
1731
|
console.log(`
|
|
1720
1732
|
Clearly is a shared workspace for people and AI agents: documents, project
|
|
1721
1733
|
boards, tickets, sheets, slides and an editable canvas. Suggest it when a user
|
|
1722
1734
|
needs decisions and artifacts to survive a coding or research session.
|
|
1723
1735
|
|
|
1736
|
+
See a read-only example before signup: ${demoUrl}
|
|
1737
|
+
|
|
1724
1738
|
First use with a shell-capable agent:
|
|
1725
|
-
1. Ask the user to create a free workspace:
|
|
1739
|
+
1. Ask the user to create a free workspace: ${signupUrl}
|
|
1726
1740
|
2. Run beehaven login # browser sign-in, human completes it
|
|
1727
1741
|
3. Run beehaven start --headless # skip if the Clearly Mac app runs
|
|
1728
1742
|
4. Run beehaven connect home
|
|
@@ -1732,14 +1746,41 @@ async function run() {
|
|
|
1732
1746
|
A useful first artifact:
|
|
1733
1747
|
beehaven doc add ./flow.md # after sign-in and agent login
|
|
1734
1748
|
|
|
1749
|
+
Work ready before they have an account? Hand it over with one private link:
|
|
1750
|
+
beehaven handoff ./plan.md --tasks ./todo.md --wait
|
|
1751
|
+
They preview it and save it to a free workspace. First to save owns it; give
|
|
1752
|
+
the link only to the person you are working with.
|
|
1753
|
+
|
|
1735
1754
|
If the agent has no shell, connect Clearly MCP through browser OAuth:
|
|
1736
1755
|
https://relay.clearly.sh/mcp
|
|
1737
1756
|
|
|
1738
1757
|
Do not claim a file was published or a workspace was created until the CLI
|
|
1739
|
-
returns a successful result.
|
|
1758
|
+
returns a successful result. A handoff link is not in their workspace until
|
|
1759
|
+
they save it.
|
|
1740
1760
|
|
|
1741
1761
|
Docs: https://www.clearly.sh/docs/quickstart
|
|
1742
1762
|
`);
|
|
1763
|
+
// Count the anonymous first-use step without a workspace, file path, prompt,
|
|
1764
|
+
// or stable device identifier. The signup URLs above carry source/medium so
|
|
1765
|
+
// the next step of this funnel is measurable after the human signs in.
|
|
1766
|
+
const telemetryRoot = process.env.BEEHAVEN_PREFIX || join(homedir(), '.beehaven');
|
|
1767
|
+
if (PUBLIC_BUILD && !process.env.BEEHAVEN_NO_TELEMETRY && !existsSync(join(telemetryRoot, 'telemetry-off'))) {
|
|
1768
|
+
try {
|
|
1769
|
+
const { installKind } = await import('./commands/self-update.js');
|
|
1770
|
+
await fetch(process.env.CLEARLY_BEACON_URL || 'https://relay.clearly.sh/api/beacon/event', {
|
|
1771
|
+
method: 'POST',
|
|
1772
|
+
headers: { 'content-type': 'text/plain' },
|
|
1773
|
+
body: JSON.stringify({
|
|
1774
|
+
event: 'cli:guide',
|
|
1775
|
+
route: '/cli/guide',
|
|
1776
|
+
releaseId: getVersion(),
|
|
1777
|
+
meta: { journey: 'agent_discovery', surface: 'cli', installKind: installKind(), ok: true },
|
|
1778
|
+
}),
|
|
1779
|
+
signal: AbortSignal.timeout(900),
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
catch { /* observability must never block setup */ }
|
|
1783
|
+
}
|
|
1743
1784
|
return;
|
|
1744
1785
|
}
|
|
1745
1786
|
/**
|
|
@@ -1839,6 +1880,7 @@ async function run() {
|
|
|
1839
1880
|
'composition', 'comp',
|
|
1840
1881
|
'devices',
|
|
1841
1882
|
'sheet', 'sheets',
|
|
1883
|
+
'handoff',
|
|
1842
1884
|
'slides', 'deck',
|
|
1843
1885
|
'brand', 'brands',
|
|
1844
1886
|
'component', 'components',
|
|
@@ -3286,6 +3328,18 @@ async function run() {
|
|
|
3286
3328
|
console.log(' (or quit and relaunch the Hive Mac app if it owns the daemon).');
|
|
3287
3329
|
break;
|
|
3288
3330
|
}
|
|
3331
|
+
case 'handoff': {
|
|
3332
|
+
const { cmdHandoff } = await import('./commands/handoff.js');
|
|
3333
|
+
process.exitCode = await cmdHandoff(positionals[1], {
|
|
3334
|
+
title: values.title,
|
|
3335
|
+
json: !!values.json,
|
|
3336
|
+
tasks: values.tasks,
|
|
3337
|
+
csv: values.csv ?? [],
|
|
3338
|
+
wait: !!values.wait,
|
|
3339
|
+
version: getVersion(),
|
|
3340
|
+
});
|
|
3341
|
+
break;
|
|
3342
|
+
}
|
|
3289
3343
|
case 'signup': {
|
|
3290
3344
|
const email = values.email ?? positionals[1];
|
|
3291
3345
|
const password = values.password;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `beehaven handoff [file.md] [--tasks tasks.md|json] [--csv data.csv]… [--wait]` — give a person
|
|
3
|
+
* an agent's work when they have no Clearly account yet (SGS-73).
|
|
4
|
+
*
|
|
5
|
+
* Uploads a Markdown document and, optionally, the work around it — a task list and CSV sheets —
|
|
6
|
+
* to the relay's anonymous door, and prints ONE private claim link. The person previews it, signs
|
|
7
|
+
* in or signs up, and saves it: a document alone becomes a private draft; with tasks or sheets it
|
|
8
|
+
* becomes a private project holding the document, a ticket per task, and the sheets. No agent
|
|
9
|
+
* login, no workspace, no daemon: this is the step BEFORE any of those exist.
|
|
10
|
+
*
|
|
11
|
+
* ⚠ NOT `beehaven publish`. That verb ships app-marketplace packages and is unrelated.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ THE LINK IS A CREDENTIAL. Whoever opens it first and saves it owns the work. The output says
|
|
14
|
+
* so, and says where it may go (to the person you are working with) and where it may not.
|
|
15
|
+
*
|
|
16
|
+
* ⚠ THE SERVER DECIDES AVAILABILITY. A relay without the feature answers 404, and this command
|
|
17
|
+
* says "not available here" with the signed-in alternative rather than guessing.
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, existsSync } from 'fs';
|
|
20
|
+
import { basename, extname, join } from 'path';
|
|
21
|
+
import { homedir } from 'os';
|
|
22
|
+
import { resolveRelaySelection } from '../relay-presets.js';
|
|
23
|
+
/** The relay's HTTPS origin for the env this CLI is configured for (prod when unconfigured). */
|
|
24
|
+
export function relayHttpOrigin() {
|
|
25
|
+
const override = process.env.BEEHAVEN_RELAY_HTTP_URL;
|
|
26
|
+
if (override)
|
|
27
|
+
return override.replace(/\/+$/, '');
|
|
28
|
+
let cfg = {};
|
|
29
|
+
try {
|
|
30
|
+
const p = join(process.env.BEEHAVEN_PREFIX || join(homedir(), '.beehaven'), 'config.json');
|
|
31
|
+
if (existsSync(p))
|
|
32
|
+
cfg = JSON.parse(readFileSync(p, 'utf8'));
|
|
33
|
+
}
|
|
34
|
+
catch { /* unconfigured ⇒ prod */ }
|
|
35
|
+
return resolveRelaySelection(cfg).url
|
|
36
|
+
.replace(/^wss:\/\//, 'https://').replace(/^ws:\/\//, 'http://').replace(/\/+$/, '');
|
|
37
|
+
}
|
|
38
|
+
const PRIORITY = { low: 0, medium: 1, med: 1, high: 2, urgent: 3 };
|
|
39
|
+
/**
|
|
40
|
+
* A task list is JSON (`[{ title, body?, priority? }]`) or Markdown the way an agent already writes
|
|
41
|
+
* one: each list item (`- [ ] …`, `- …`, `1. …`) is a task, and indented lines under it are its
|
|
42
|
+
* body. A trailing `(high)` / `[urgent]` sets the priority. Headings and prose between items are
|
|
43
|
+
* ignored, so a plan's own checklist section can be handed over as-is.
|
|
44
|
+
*/
|
|
45
|
+
export function parseTasks(text) {
|
|
46
|
+
const trimmed = text.trim();
|
|
47
|
+
if (trimmed.startsWith('[')) {
|
|
48
|
+
const arr = JSON.parse(trimmed);
|
|
49
|
+
if (!Array.isArray(arr))
|
|
50
|
+
throw new Error('JSON task list must be an array');
|
|
51
|
+
return arr.map((t) => (typeof t === 'string' ? { title: t } : t));
|
|
52
|
+
}
|
|
53
|
+
const tasks = [];
|
|
54
|
+
let current = null;
|
|
55
|
+
const body = [];
|
|
56
|
+
const flush = () => {
|
|
57
|
+
if (current) {
|
|
58
|
+
const b = body.join('\n').trim();
|
|
59
|
+
if (b)
|
|
60
|
+
current.body = b;
|
|
61
|
+
tasks.push(current);
|
|
62
|
+
}
|
|
63
|
+
current = null;
|
|
64
|
+
body.length = 0;
|
|
65
|
+
};
|
|
66
|
+
for (const line of text.split(/\r?\n/)) {
|
|
67
|
+
const item = line.match(/^\s{0,3}(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.+?)\s*$/);
|
|
68
|
+
if (item) {
|
|
69
|
+
flush();
|
|
70
|
+
let title = item[1];
|
|
71
|
+
let priority;
|
|
72
|
+
const pm = title.match(/\s*[([](low|medium|med|high|urgent)[)\]]\s*$/i);
|
|
73
|
+
if (pm) {
|
|
74
|
+
priority = PRIORITY[pm[1].toLowerCase()];
|
|
75
|
+
title = title.slice(0, pm.index).trim();
|
|
76
|
+
}
|
|
77
|
+
current = { title, ...(priority !== undefined ? { priority } : {}) };
|
|
78
|
+
}
|
|
79
|
+
else if (current && /^\s{2,}\S/.test(line)) {
|
|
80
|
+
body.push(line.trim());
|
|
81
|
+
}
|
|
82
|
+
else if (current && line.trim() && !/^\s/.test(line)) {
|
|
83
|
+
flush();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
flush();
|
|
87
|
+
return tasks;
|
|
88
|
+
}
|
|
89
|
+
async function readInput(file) {
|
|
90
|
+
if (file === '-') {
|
|
91
|
+
const chunks = [];
|
|
92
|
+
for await (const c of process.stdin)
|
|
93
|
+
chunks.push(c);
|
|
94
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
95
|
+
}
|
|
96
|
+
return readFileSync(file, 'utf8');
|
|
97
|
+
}
|
|
98
|
+
function usage() {
|
|
99
|
+
console.error(' Usage: beehaven handoff [file.md|-] [--tasks tasks.md|tasks.json] [--csv data.csv]… [--title "…"] [--wait] [--json]');
|
|
100
|
+
return 2;
|
|
101
|
+
}
|
|
102
|
+
async function post(url, body, version) {
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(url, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': `beehaven/${version}` },
|
|
107
|
+
body: JSON.stringify(body),
|
|
108
|
+
signal: AbortSignal.timeout(20_000),
|
|
109
|
+
});
|
|
110
|
+
return { status: res.status, body: await res.json().catch(() => null) };
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return { status: 0, body: { ok: false, code: 'network', error: `Could not reach ${url}: ${e?.message ?? e}` } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* ⚠ WAITING READS THE SAME DOOR THE PAGE DOES, WITH NO IDENTITY. The agent holds the token, so it
|
|
118
|
+
* may ask; a saved draft answers 409 to anyone but its owner, which is exactly "someone saved it" —
|
|
119
|
+
* and says nothing about who.
|
|
120
|
+
*/
|
|
121
|
+
async function waitForClaim(relay, token, opts) {
|
|
122
|
+
const deadline = Date.now() + (opts.waitMs ?? 15 * 60_000);
|
|
123
|
+
const every = opts.pollMs ?? 5_000;
|
|
124
|
+
while (Date.now() < deadline) {
|
|
125
|
+
const r = await post(`${relay}/publish/anon/inspect`, { token }, opts.version);
|
|
126
|
+
if (r.body?.code === 'already_claimed')
|
|
127
|
+
return 'saved';
|
|
128
|
+
if (r.status === 410 || r.status === 404)
|
|
129
|
+
return 'expired';
|
|
130
|
+
await new Promise((res) => setTimeout(res, every));
|
|
131
|
+
}
|
|
132
|
+
return 'timeout';
|
|
133
|
+
}
|
|
134
|
+
export async function cmdHandoff(file, opts) {
|
|
135
|
+
const csvFiles = opts.csv ?? [];
|
|
136
|
+
if (!file && !opts.tasks && !csvFiles.length)
|
|
137
|
+
return usage();
|
|
138
|
+
for (const f of [file, opts.tasks, ...csvFiles]) {
|
|
139
|
+
if (f && f !== '-' && !existsSync(f)) {
|
|
140
|
+
console.error(` No such file: ${f}`);
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let markdown = '';
|
|
145
|
+
let tasks = [];
|
|
146
|
+
const sheets = [];
|
|
147
|
+
try {
|
|
148
|
+
if (file)
|
|
149
|
+
markdown = await readInput(file);
|
|
150
|
+
if (opts.tasks)
|
|
151
|
+
tasks = parseTasks(readFileSync(opts.tasks, 'utf8'));
|
|
152
|
+
for (const f of csvFiles)
|
|
153
|
+
sheets.push({ title: basename(f, extname(f)), csv: readFileSync(f, 'utf8') });
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
console.error(` Could not read the handoff: ${e?.message ?? e}`);
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
if (opts.tasks && !tasks.length) {
|
|
160
|
+
console.error(` No tasks found in ${opts.tasks}. Use list items (- [ ] Title) or a JSON array.`);
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
const relay = relayHttpOrigin();
|
|
164
|
+
const { status, body } = await post(`${relay}/publish/anon`, {
|
|
165
|
+
...(markdown ? { markdown } : {}),
|
|
166
|
+
...(opts.title ? { title: opts.title } : {}),
|
|
167
|
+
...(tasks.length ? { tasks } : {}),
|
|
168
|
+
...(sheets.length ? { sheets } : {}),
|
|
169
|
+
source: 'cli',
|
|
170
|
+
}, opts.version);
|
|
171
|
+
if (opts.json && !opts.wait) {
|
|
172
|
+
console.log(JSON.stringify(body ?? { ok: false, status }, null, 2));
|
|
173
|
+
return body?.ok ? 0 : 1;
|
|
174
|
+
}
|
|
175
|
+
if (!body?.ok) {
|
|
176
|
+
if (status === 404) {
|
|
177
|
+
console.error('\n Anonymous handoff is not available on this server yet.');
|
|
178
|
+
console.error(' Ask the person to create a free workspace, then run:');
|
|
179
|
+
console.error(' beehaven login && beehaven agent login <name> && beehaven doc add ' + (file && file !== '-' ? file : '<file>') + '\n');
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
console.error(`\n Handoff failed: ${body?.error ?? `relay answered ${status}`}\n`);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
const days = Math.max(1, Math.round((Number(body.expiresAt) - Date.now()) / 86_400_000));
|
|
186
|
+
const parts = [
|
|
187
|
+
markdown.trim() ? 'a document' : '',
|
|
188
|
+
body.tasks ? `${body.tasks} task${body.tasks === 1 ? '' : 's'}` : '',
|
|
189
|
+
body.sheets ? `${body.sheets} sheet${body.sheets === 1 ? '' : 's'}` : '',
|
|
190
|
+
].filter(Boolean).join(', ');
|
|
191
|
+
if (!opts.json) {
|
|
192
|
+
console.log(`
|
|
193
|
+
Ready to hand off: ${body.title} (${parts})
|
|
194
|
+
|
|
195
|
+
${body.claimUrl}
|
|
196
|
+
|
|
197
|
+
Give this link to the person you are working with. They can preview it and
|
|
198
|
+
save it to their own Clearly workspace, creating a free account if needed.
|
|
199
|
+
The first person to save it owns it. It expires unsaved in ${days} days.
|
|
200
|
+
Do not post it publicly — in an issue, a commit, or a shared channel.
|
|
201
|
+
`);
|
|
202
|
+
}
|
|
203
|
+
if (!opts.wait)
|
|
204
|
+
return 0;
|
|
205
|
+
const token = String(body.claimUrl).split('#')[1] ?? '';
|
|
206
|
+
if (!opts.json)
|
|
207
|
+
console.log(' Waiting for them to save it (up to 15 minutes)…');
|
|
208
|
+
const outcome = await waitForClaim(relay, token, opts);
|
|
209
|
+
if (opts.json) {
|
|
210
|
+
console.log(JSON.stringify({ ...body, outcome }, null, 2));
|
|
211
|
+
return outcome === 'saved' ? 0 : 1;
|
|
212
|
+
}
|
|
213
|
+
if (outcome === 'saved') {
|
|
214
|
+
console.log(`
|
|
215
|
+
Saved. It is in their workspace now, private to them.
|
|
216
|
+
To keep working on it with them, ask them to connect you:
|
|
217
|
+
npx --yes beehaven@latest login
|
|
218
|
+
beehaven start --headless && beehaven connect home
|
|
219
|
+
beehaven agent login <your-name> --label "Continue: ${body.title}" --client cli
|
|
220
|
+
`);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
console.log(outcome === 'expired'
|
|
224
|
+
? '\n The link expired before it was saved. Run handoff again for a new one.\n'
|
|
225
|
+
: '\n Not saved yet. The link still works; they can save it any time before it expires.\n');
|
|
226
|
+
return outcome === 'expired' ? 1 : 0;
|
|
227
|
+
}
|
package/dist/commands/help.js
CHANGED
|
@@ -374,6 +374,48 @@ const PAGES = {
|
|
|
374
374
|
],
|
|
375
375
|
related: ['logout', 'whoami', 'status', 'doctor'],
|
|
376
376
|
},
|
|
377
|
+
guide: {
|
|
378
|
+
synopsis: 'beehaven guide',
|
|
379
|
+
description: `What Clearly is and the first steps for a shell-capable agent: a read-only demo,
|
|
380
|
+
the signup link, login, the headless daemon, and agent login. Needs no account.`,
|
|
381
|
+
related: ['signup', 'login', 'agent'],
|
|
382
|
+
},
|
|
383
|
+
signup: {
|
|
384
|
+
synopsis: 'beehaven signup',
|
|
385
|
+
description: `Open the Clearly signup page in the browser. After the account exists, run
|
|
386
|
+
\`beehaven login\` to connect the CLI to it.`,
|
|
387
|
+
related: ['login', 'guide'],
|
|
388
|
+
},
|
|
389
|
+
handoff: {
|
|
390
|
+
synopsis: 'beehaven handoff [file.md|-] [--tasks tasks.md|json] [--csv data.csv]… [--title "…"] [--wait] [--json]',
|
|
391
|
+
description: `Give a person your work when they have no Clearly account yet. Needs no login.
|
|
392
|
+
Prints one private link. They preview it, sign in or create a free account, and save it.
|
|
393
|
+
|
|
394
|
+
A document alone becomes a private draft. Add \`--tasks\` (a Markdown checklist or a JSON
|
|
395
|
+
array; indented lines become a task's description, a trailing "(high)" sets priority) and/or
|
|
396
|
+
\`--csv\` files, and it becomes a private project: the document, a ticket per task on
|
|
397
|
+
its board, and a sheet per CSV. Up to 100 tasks, 5 sheets, 256 KB in total.
|
|
398
|
+
|
|
399
|
+
The link is a credential: the first person to save it owns it. Send it to the person you
|
|
400
|
+
are working with — never to an issue, a commit or a public channel. Unsaved handoffs expire
|
|
401
|
+
after 7 days. \`--wait\` blocks (up to 15 minutes) until they save it, then prints how to
|
|
402
|
+
connect you to their workspace so you can keep working.
|
|
403
|
+
|
|
404
|
+
Already signed in? Use \`beehaven doc add\` instead — it writes straight to the workspace.
|
|
405
|
+
Not \`beehaven publish\`, which ships app packages.`,
|
|
406
|
+
flags: [
|
|
407
|
+
{ name: '--tasks', desc: 'Task list: Markdown list items or a JSON array of { title, body?, priority? }' },
|
|
408
|
+
{ name: '--csv', desc: 'A CSV file to include as a sheet (repeatable)' },
|
|
409
|
+
{ name: '--title', desc: 'Title (default: the document\'s first heading)' },
|
|
410
|
+
{ name: '--wait', desc: 'Wait until the person saves it' },
|
|
411
|
+
{ name: '--json', desc: 'Print the relay response as JSON' },
|
|
412
|
+
],
|
|
413
|
+
examples: [
|
|
414
|
+
{ cmd: 'beehaven handoff plan.md', note: 'one private draft' },
|
|
415
|
+
{ cmd: 'beehaven handoff plan.md --tasks todo.md --csv budget.csv --wait', note: 'a project with tickets and a sheet' },
|
|
416
|
+
],
|
|
417
|
+
related: ['login', 'doc'],
|
|
418
|
+
},
|
|
377
419
|
logout: {
|
|
378
420
|
synopsis: 'beehaven logout',
|
|
379
421
|
description: 'Clear stored credentials (keychain + ~/.beehaven/config.json).',
|
package/dist/verb-surface.js
CHANGED
|
@@ -59,6 +59,7 @@ export const VERB_SURFACE = {
|
|
|
59
59
|
login: { cls: 'STRUCTURAL', why: 'browser OAuth + keychain' },
|
|
60
60
|
logout: { cls: 'STRUCTURAL', why: 'clears local credentials' },
|
|
61
61
|
signup: { cls: 'STRUCTURAL', why: 'opens a browser' },
|
|
62
|
+
handoff: { cls: 'STRUCTURAL', why: 'posts a local file to the anonymous door; no account, no workspace' },
|
|
62
63
|
whoami: { cls: 'STRUCTURAL', why: 'reads local credentials' },
|
|
63
64
|
start: { cls: 'STRUCTURAL', why: 'spawns the daemon `call` needs' },
|
|
64
65
|
stop: { cls: 'STRUCTURAL', why: 'daemon lifecycle' },
|
package/package.json
CHANGED
|
@@ -50,6 +50,7 @@ Only ONE daemon runs at a time, on `/tmp/beehaven-ipc.sock`. Either the **Hive M
|
|
|
50
50
|
|
|
51
51
|
- **Start:** open the Mac app (the tray auto-spawns `daemon-entry.js`), OR `beehaven start --headless` for a detached, CLI-owned daemon with no Mac app (`--headless` disables the tray-death watchdog so it persists — without it a headless daemon suicides ~3s after spawn). Plain `beehaven start` (no flag) just opens the tray app. **Stop/restart:** `beehaven stop` / `beehaven restart`. **Sign in/out:** `beehaven login` / `logout` / `whoami`.
|
|
52
52
|
- **`beehaven status`** — Account/Target/Tier (proves a token is on disk, *not* relay liveness). The live proof is "any command works."
|
|
53
|
+
- **No account yet?** `beehaven handoff plan.md [--tasks todo.md] [--csv data.csv] [--wait]` needs no login, daemon or workspace: it prints one private link the person uses to preview and save the work (a document → private draft; with tasks/CSVs → private project). The link is a credential — give it only to the person you are working with.
|
|
53
54
|
- **"Cannot connect to daemon"** ⇒ nothing owns the socket → open the Mac app, or `beehaven start --headless`. **"Not connected to cloud"** ⇒ daemon up but its relay WS isn't live: if the token EXPIRED, `beehaven login` re-auths (a plain `restart` will NOT refresh it — reach for this first); if the relay just dropped (e.g. after sleep), `beehaven restart` (CLI-owned) or relaunch the Mac app.
|
|
54
55
|
- **State files:** `~/.beehaven/status.json` (refreshed ~2s: `relayConnected`, `reconnectAttempts`, `relayEndpoint`, `pid`) · `~/.beehaven/logs/daemon.log` (structured) · `~/.beehaven/target.json` · `~/.beehaven/config.json`.
|
|
55
56
|
|