beehaven 0.8.22 โ 0.8.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-state.js +19 -0
- package/dist/browser-login.js +283 -0
- package/dist/build-channel.js +2 -2
- package/dist/cli.js +83 -269
- package/dist/commands/agent-instructions.js +182 -0
- package/dist/commands/agent.js +191 -60
- package/dist/commands/canvas-image.js +57 -3
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/compose.js +2 -2
- package/dist/commands/daemon-cmd.js +32 -0
- package/dist/commands/help.js +2 -0
- package/dist/commands/self-update.js +2 -2
- package/dist/commands/workspace.js +24 -3
- package/dist/daemon.js +9 -9
- package/dist/folder-sync.js +1 -1
- package/dist/local-app-manager.js +1 -1
- package/dist/reserved-type.js +60 -0
- package/dist/setup-relay.js +20 -235
- package/package.json +1 -1
- package/scripts/sync-skills.mjs +1 -0
- package/skills/README.md +20 -18
- package/skills/brand-identity/SKILL.md +248 -0
- package/skills/clearly-agent/SKILL.md +28 -7
- package/skills/clearly-canvas/SKILL.md +48 -2
- package/skills/clearly-canvas/authoring.md +20 -9
- package/skills/design-craft/SKILL.md +4 -0
- package/skills/index.json +10 -3
package/dist/daemon.js
CHANGED
|
@@ -171,14 +171,14 @@ async function simulateCloudAppRpc(params) {
|
|
|
171
171
|
* Curated "available to install" app list served by /api/marketplace.
|
|
172
172
|
*
|
|
173
173
|
* This is a stub catalog. Once the cloud marketplace is live, the daemon
|
|
174
|
-
* will fetch from https://clearly.sh/apps/catalog.json instead of this
|
|
174
|
+
* will fetch from https://www.clearly.sh/apps/catalog.json instead of this
|
|
175
175
|
* hardcoded array. The installed-app set is subtracted at serve time so
|
|
176
176
|
* users don't see things they already have.
|
|
177
177
|
*
|
|
178
178
|
* Shapes roughly mirror AgentHiveCatalogEntry minus the live "registered"
|
|
179
179
|
* state โ a catalog entry is a promise of what installing would give you.
|
|
180
180
|
*/
|
|
181
|
-
const CATALOG_URL = 'https://clearly.sh/apps/catalog.json';
|
|
181
|
+
const CATALOG_URL = 'https://www.clearly.sh/apps/catalog.json';
|
|
182
182
|
const CATALOG_CACHE_MS = 5 * 60_000; // 5 min
|
|
183
183
|
let _catalogCache = null;
|
|
184
184
|
let _catalogFetchedAt = 0;
|
|
@@ -234,7 +234,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
234
234
|
tier: 'pro',
|
|
235
235
|
tags: ['commerce', 'cloud', 'api'],
|
|
236
236
|
kind: 'cloud',
|
|
237
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/shopify/shopify-0.1.0.hive' },
|
|
237
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/shopify/shopify-0.1.0.hive' },
|
|
238
238
|
frontend: { title: 'Shopify', icon: '๐๏ธ' },
|
|
239
239
|
},
|
|
240
240
|
{
|
|
@@ -245,7 +245,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
245
245
|
tier: 'free',
|
|
246
246
|
tags: ['creative', '3d', 'local'],
|
|
247
247
|
kind: 'local',
|
|
248
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/blender/blender-0.1.0.hive' },
|
|
248
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/blender/blender-0.1.0.hive' },
|
|
249
249
|
frontend: { title: 'Blender', icon: '๐ง' },
|
|
250
250
|
},
|
|
251
251
|
{
|
|
@@ -256,7 +256,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
256
256
|
tier: 'pro',
|
|
257
257
|
tags: ['design', 'cloud', 'api'],
|
|
258
258
|
kind: 'cloud',
|
|
259
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/figma/figma-0.1.0.hive' },
|
|
259
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/figma/figma-0.1.0.hive' },
|
|
260
260
|
frontend: { title: 'Figma', icon: '๐จ' },
|
|
261
261
|
},
|
|
262
262
|
{
|
|
@@ -267,7 +267,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
267
267
|
tier: 'pro',
|
|
268
268
|
tags: ['creative', 'audio', 'local'],
|
|
269
269
|
kind: 'local',
|
|
270
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/logic-pro/logic-pro-0.1.0.hive' },
|
|
270
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/logic-pro/logic-pro-0.1.0.hive' },
|
|
271
271
|
frontend: { title: 'Logic Pro', icon: '๐๏ธ' },
|
|
272
272
|
},
|
|
273
273
|
{
|
|
@@ -278,7 +278,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
278
278
|
tier: 'free',
|
|
279
279
|
tags: ['smart-home', 'local'],
|
|
280
280
|
kind: 'local',
|
|
281
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/hue/hue-0.1.0.hive' },
|
|
281
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/hue/hue-0.1.0.hive' },
|
|
282
282
|
frontend: { title: 'Philips Hue', icon: '๐ก' },
|
|
283
283
|
},
|
|
284
284
|
{
|
|
@@ -289,7 +289,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
289
289
|
tier: 'pro',
|
|
290
290
|
tags: ['productivity', 'cloud', 'api'],
|
|
291
291
|
kind: 'cloud',
|
|
292
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/jira/jira-0.1.0.hive' },
|
|
292
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/jira/jira-0.1.0.hive' },
|
|
293
293
|
frontend: { title: 'Jira', icon: '๐' },
|
|
294
294
|
},
|
|
295
295
|
{
|
|
@@ -707,7 +707,7 @@ let daemonAppManager = null;
|
|
|
707
707
|
// curl-only distribution โ the CLI is NOT published to npm. Updates fetch the
|
|
708
708
|
// versioned tarball from downloads.clearly.sh via the public installer script.
|
|
709
709
|
let announcedManagedUpdates = false;
|
|
710
|
-
const INSTALL_URL = process.env.BEEHAVEN_INSTALL_URL || 'https://clearly.sh/install.sh';
|
|
710
|
+
const INSTALL_URL = process.env.BEEHAVEN_INSTALL_URL || 'https://www.clearly.sh/install.sh';
|
|
711
711
|
/**
|
|
712
712
|
* โ THE SAME OVERRIDE ITS SIBLING ALREADY HAD. `min-version.ts` reads `BEEHAVEN_CLI_FEED` so the
|
|
713
713
|
* force-update gate can be exercised against a scratch channel; the auto-updater โ the half that
|
package/dist/folder-sync.js
CHANGED
|
@@ -77,7 +77,7 @@ export async function linkProject(relay, folderPath, brandId) {
|
|
|
77
77
|
}
|
|
78
78
|
if (projects.length === 0) {
|
|
79
79
|
console.error(' No projects found in your Clearly account.');
|
|
80
|
-
console.error(' Create a project at https://clearly.sh first.');
|
|
80
|
+
console.error(' Create a project at https://www.clearly.sh first.');
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
console.log('\n Available projects:\n');
|
|
@@ -97,7 +97,7 @@ export class LocalAppManager extends EventEmitter {
|
|
|
97
97
|
*/
|
|
98
98
|
templateVars() {
|
|
99
99
|
// `webBase` โ dev Mac app sets this via env; defaults to production.
|
|
100
|
-
const webBase = process.env.BEEHAVEN_WEB_BASE ?? process.env.CLEARLY_WEB_BASE ?? 'https://clearly.sh';
|
|
100
|
+
const webBase = process.env.BEEHAVEN_WEB_BASE ?? process.env.CLEARLY_WEB_BASE ?? 'https://www.clearly.sh';
|
|
101
101
|
// User identity โ the daemon stores these in config on sign-in
|
|
102
102
|
// install(), so they're available without decoding the JWT here.
|
|
103
103
|
const cfg = readDaemonConfigSafe();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reserved-type โ `type` is the WIRE'S action name, so a top-level `type` in a call's input is lost.
|
|
3
|
+
*
|
|
4
|
+
* โ โ SILENT DATA LOSS (PPO-190). The daemon sends `{ ...input, type: action }`. An agent that calls
|
|
5
|
+
* `ticket-create '{"title":"โฆ","type":"bug"}'` gets `ok: true` and a ticket that is NOT a bug: its
|
|
6
|
+
* `type` was overwritten by `"ticket-create"` before the workspace ever saw it. AGENTS.md has
|
|
7
|
+
* carried "use `issueType`, not `type`" as a warning, which is a rule every new agent has to be
|
|
8
|
+
* told and every one of them breaks once.
|
|
9
|
+
*
|
|
10
|
+
* The verbs already accept a non-colliding spelling (`issueType`, `linkType`, `proposalType`,
|
|
11
|
+
* `kind`). This moves `type` onto that spelling when the verb has one, and refuses with the right
|
|
12
|
+
* spelling when it does not โ either way, nothing is dropped in silence.
|
|
13
|
+
*/
|
|
14
|
+
/** Verb โ the field the workspace reads instead of `type`. */
|
|
15
|
+
export const TYPE_ALIASES = {
|
|
16
|
+
// Each entry is a verb whose handler reads this field (verified against apps/cloudflare/src).
|
|
17
|
+
'ticket-create': 'issueType',
|
|
18
|
+
'ticket-update': 'issueType',
|
|
19
|
+
'ticket-link-create': 'linkType',
|
|
20
|
+
'proposal-create': 'proposalType',
|
|
21
|
+
'composition-create': 'kind',
|
|
22
|
+
'record-thought': 'kind',
|
|
23
|
+
'thought-record': 'kind',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Reconcile a top-level `type` with the wire's use of the name. Pure.
|
|
27
|
+
*
|
|
28
|
+
* โ A `type` EQUAL TO THE ACTION is left alone โ that is a caller spelling the action twice, which
|
|
29
|
+
* the wire already agrees with. Nested `type`s (canvas nodes, `updates.type`) never collide and are
|
|
30
|
+
* never inspected.
|
|
31
|
+
*/
|
|
32
|
+
export function reconcileReservedType(action, input) {
|
|
33
|
+
if (!input || typeof input !== 'object' || !Object.prototype.hasOwnProperty.call(input, 'type'))
|
|
34
|
+
return { kind: 'none' };
|
|
35
|
+
const value = input.type;
|
|
36
|
+
if (value === undefined || value === action)
|
|
37
|
+
return { kind: 'none' };
|
|
38
|
+
const field = TYPE_ALIASES[action];
|
|
39
|
+
if (field) {
|
|
40
|
+
const { type: _dropped, ...rest } = input;
|
|
41
|
+
if (rest[field] !== undefined && rest[field] !== value) {
|
|
42
|
+
return {
|
|
43
|
+
kind: 'refused',
|
|
44
|
+
error: `both "type" (${JSON.stringify(value)}) and "${field}" (${JSON.stringify(rest[field])}) were given โ `
|
|
45
|
+
+ `"type" is the action name on the wire and would be dropped; pass only "${field}".`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
kind: 'moved',
|
|
50
|
+
field,
|
|
51
|
+
input: { ...rest, [field]: value },
|
|
52
|
+
note: `"type" is reserved for the action name on the wire โ sent as "${field}": ${JSON.stringify(value)}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
kind: 'refused',
|
|
57
|
+
error: `"type" is reserved for the action name on the wire, so ${action} would never see ${JSON.stringify(value)}. `
|
|
58
|
+
+ `Use the verb's own field (see: beehaven describe ${action}).`,
|
|
59
|
+
};
|
|
60
|
+
}
|
package/dist/setup-relay.js
CHANGED
|
@@ -1,148 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// Beehaven Office - Relay Setup (Login)
|
|
4
|
-
//
|
|
4
|
+
// The daemon's interactive sign-in fallback, and logout.
|
|
5
5
|
// ============================================================================
|
|
6
|
-
import
|
|
7
|
-
import crypto from 'crypto';
|
|
8
|
-
import { createInterface } from 'readline';
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, chmodSync } from 'fs';
|
|
10
7
|
import { homedir } from 'os';
|
|
11
8
|
import { join } from 'path';
|
|
12
9
|
import { spawnSync } from 'child_process';
|
|
13
10
|
import { Relay, CLEARLY_RELAY_URL } from './relay.js';
|
|
14
|
-
import { resolveRelaySelection } from './relay-presets.js';
|
|
15
11
|
import { SESSION_KEYCHAIN_ACCOUNTS, KEYCHAIN_SERVICE } from './keychain.js';
|
|
16
|
-
import { withUtm } from './utm.js';
|
|
17
12
|
const CONFIG_DIR = join(homedir(), '.beehaven');
|
|
18
13
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
19
|
-
const AUTH_BASE_URL = 'https://clearly.sh/auth/beehaven';
|
|
20
|
-
const LOGIN_TIMEOUT_MS = 120_000; // 2 minutes
|
|
21
14
|
/**
|
|
22
|
-
* The
|
|
23
|
-
*
|
|
15
|
+
* The daemon calls this when it starts interactively with no token, or when its session has
|
|
16
|
+
* expired (daemon.ts). It then re-reads the config, so all it needs is credentials on disk.
|
|
17
|
+
*
|
|
18
|
+
* โ โ AWO-224 โ THIS USED TO BE A SECOND, DIFFERENT SIGN-IN. It opened
|
|
19
|
+
* `https://www.clearly.sh/auth/beehaven?callback=โฆ`, waited for a single `token` and wrote its own
|
|
20
|
+
* config. The web app no longer serves that route (404 on prod and, via the apex redirect, the
|
|
21
|
+
* same 404), so the fallback opened a dead page and timed out after two minutes. It now runs the
|
|
22
|
+
* exact flow `beehaven login` runs (`browser-login.ts`: `/cli/auth`, the keychain idToken slot
|
|
23
|
+
* the daemon reads, both dial-target keys) and nothing else โ it must NOT restart the daemon,
|
|
24
|
+
* because it runs inside it.
|
|
24
25
|
*/
|
|
25
|
-
function readConfigFile() {
|
|
26
|
-
if (!existsSync(CONFIG_FILE))
|
|
27
|
-
return {};
|
|
28
|
-
try {
|
|
29
|
-
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return {};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function saveRelayConfig(token, endpoint) {
|
|
36
|
-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
37
|
-
let existing = {};
|
|
38
|
-
if (existsSync(CONFIG_FILE)) {
|
|
39
|
-
try {
|
|
40
|
-
existing = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.warn(`[config] Failed to parse ${CONFIG_FILE}:`, err.message);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
existing.token = token;
|
|
47
|
-
// โ BOTH KEYS, as `beehaven env` writes them โ `endpoint` is dialled, `relayUrl` is the alias
|
|
48
|
-
// other surfaces read. Updating one leaves the other naming a different environment.
|
|
49
|
-
existing.endpoint = endpoint;
|
|
50
|
-
existing.relayUrl = endpoint;
|
|
51
|
-
existing.tier = 'connected';
|
|
52
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
|
|
53
|
-
chmodSync(CONFIG_FILE, 0o600);
|
|
54
|
-
}
|
|
55
|
-
function prompt(question) {
|
|
56
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
rl.question(question, (answer) => {
|
|
59
|
-
rl.close();
|
|
60
|
-
resolve(answer.trim());
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Start a local HTTP server on a random port to receive the OAuth callback.
|
|
66
|
-
* Returns a promise for the token, plus helpers to get the port and close.
|
|
67
|
-
*/
|
|
68
|
-
function startCallbackServer() {
|
|
69
|
-
let resolveToken;
|
|
70
|
-
let rejectToken;
|
|
71
|
-
let serverRef;
|
|
72
|
-
let settled = false;
|
|
73
|
-
const oauthState = crypto.randomBytes(32).toString('hex');
|
|
74
|
-
const promise = new Promise((resolve, reject) => {
|
|
75
|
-
resolveToken = resolve;
|
|
76
|
-
rejectToken = reject;
|
|
77
|
-
serverRef = http.createServer((req, res) => {
|
|
78
|
-
if (settled)
|
|
79
|
-
return;
|
|
80
|
-
const url = new URL(req.url, `http://localhost`);
|
|
81
|
-
if (url.pathname === '/callback') {
|
|
82
|
-
// Validate OAuth state parameter to prevent CSRF (skip if server doesn't echo it back yet)
|
|
83
|
-
const returnedState = url.searchParams.get('state');
|
|
84
|
-
if (returnedState && returnedState !== oauthState) {
|
|
85
|
-
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
86
|
-
res.end('<!DOCTYPE html><html><body><h1>Invalid state parameter</h1></body></html>');
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const token = url.searchParams.get('token');
|
|
90
|
-
if (token) {
|
|
91
|
-
settled = true;
|
|
92
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
93
|
-
res.end([
|
|
94
|
-
'<!DOCTYPE html><html><head>',
|
|
95
|
-
'<meta charset="utf-8">',
|
|
96
|
-
'<title>Beehaven - Logged In</title>',
|
|
97
|
-
'<style>',
|
|
98
|
-
'body{font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#fef9ef}',
|
|
99
|
-
'.card{text-align:center;padding:48px;border-radius:16px;background:white;box-shadow:0 2px 12px rgba(0,0,0,0.08)}',
|
|
100
|
-
'h1{margin:0 0 8px;font-size:24px}',
|
|
101
|
-
'p{margin:0;color:#666;font-size:15px}',
|
|
102
|
-
'.bee{font-size:48px;margin-bottom:16px}',
|
|
103
|
-
'</style>',
|
|
104
|
-
'</head><body>',
|
|
105
|
-
'<div class="card">',
|
|
106
|
-
'<div class="bee">🐝</div>',
|
|
107
|
-
'<h1>Logged in!</h1>',
|
|
108
|
-
'<p>You can close this tab and return to your terminal.</p>',
|
|
109
|
-
'</div>',
|
|
110
|
-
'</body></html>',
|
|
111
|
-
].join(''));
|
|
112
|
-
serverRef.close();
|
|
113
|
-
resolve(token);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
117
|
-
res.end('<!DOCTYPE html><html><body><h1>Missing token</h1></body></html>');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
res.writeHead(404);
|
|
122
|
-
res.end();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
serverRef.listen(0, '127.0.0.1');
|
|
126
|
-
// Timeout after 2 minutes
|
|
127
|
-
const timer = setTimeout(() => {
|
|
128
|
-
if (!settled) {
|
|
129
|
-
settled = true;
|
|
130
|
-
serverRef.close();
|
|
131
|
-
reject(new Error('Login timed out after 2 minutes.'));
|
|
132
|
-
}
|
|
133
|
-
}, LOGIN_TIMEOUT_MS);
|
|
134
|
-
serverRef.on('close', () => clearTimeout(timer));
|
|
135
|
-
});
|
|
136
|
-
return {
|
|
137
|
-
promise,
|
|
138
|
-
getPort: () => serverRef.address()?.port || 0,
|
|
139
|
-
close: () => {
|
|
140
|
-
settled = true;
|
|
141
|
-
serverRef?.close();
|
|
142
|
-
},
|
|
143
|
-
state: oauthState,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
26
|
export async function login() {
|
|
147
27
|
console.log('');
|
|
148
28
|
console.log(' Clearly account required to use Beehaven.');
|
|
@@ -165,114 +45,19 @@ export async function login() {
|
|
|
165
45
|
console.log(' Session expired. Please log in again.');
|
|
166
46
|
console.log('');
|
|
167
47
|
}
|
|
168
|
-
// Start local callback server
|
|
169
|
-
const callbackServer = startCallbackServer();
|
|
170
|
-
// Small delay to let server bind
|
|
171
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
172
|
-
const port = callbackServer.getPort();
|
|
173
|
-
if (!port) {
|
|
174
|
-
console.log(' Failed to start auth server.');
|
|
175
|
-
callbackServer.close();
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const callbackUrl = `http://localhost:${port}/callback`;
|
|
179
|
-
const authUrl = withUtm(`${AUTH_BASE_URL}?callback=${encodeURIComponent(callbackUrl)}&state=${callbackServer.state}`);
|
|
180
|
-
// Try to open browser
|
|
181
|
-
let browserOpened = false;
|
|
182
|
-
try {
|
|
183
|
-
const openModule = await import('open');
|
|
184
|
-
await openModule.default(authUrl);
|
|
185
|
-
browserOpened = true;
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
browserOpened = false;
|
|
189
|
-
}
|
|
190
|
-
if (browserOpened) {
|
|
191
|
-
console.log(' Opening browser to sign in with Google...');
|
|
192
|
-
console.log('');
|
|
193
|
-
console.log(' Waiting for login... (press Ctrl+C to cancel)');
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// Fallback: show URL and offer manual token paste
|
|
197
|
-
console.log(' Could not open browser. Sign in manually:');
|
|
198
|
-
console.log('');
|
|
199
|
-
console.log(` 1. Open: ${authUrl}`);
|
|
200
|
-
console.log(' 2. Sign in with Google');
|
|
201
|
-
console.log('');
|
|
202
|
-
console.log(' Waiting for login callback...');
|
|
203
|
-
console.log(' Or paste your relay token below:');
|
|
204
|
-
console.log('');
|
|
205
|
-
// Race: callback server vs manual token paste
|
|
206
|
-
const manualToken = prompt(' Relay token: ').then((token) => {
|
|
207
|
-
if (token) {
|
|
208
|
-
callbackServer.close();
|
|
209
|
-
return token;
|
|
210
|
-
}
|
|
211
|
-
return null;
|
|
212
|
-
});
|
|
213
|
-
try {
|
|
214
|
-
const token = await Promise.race([
|
|
215
|
-
callbackServer.promise,
|
|
216
|
-
manualToken.then((t) => {
|
|
217
|
-
if (t)
|
|
218
|
-
return t;
|
|
219
|
-
return new Promise(() => { });
|
|
220
|
-
}),
|
|
221
|
-
]);
|
|
222
|
-
if (token) {
|
|
223
|
-
return await verifyAndSave(token);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
console.log(` ${err.message}`);
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Wait for callback
|
|
232
48
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
console.log(` ${
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function verifyAndSave(token) {
|
|
242
|
-
// Validate token format before sending to server
|
|
243
|
-
if (!token || !/^[a-zA-Z0-9_\-.]{20,}$/.test(token)) {
|
|
244
|
-
console.log(' Invalid token format.');
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
console.log('');
|
|
248
|
-
console.log(' Verifying...');
|
|
249
|
-
const relay = new Relay();
|
|
250
|
-
/**
|
|
251
|
-
* โ โ THE ACTIVE RELAY, NOT A HARDCODED PROD CONSTANT. Both of these were `CLEARLY_RELAY_URL`, so
|
|
252
|
-
* a staging or dev user who signed in here was VERIFIED against prod and then had `endpoint`
|
|
253
|
-
* OVERWRITTEN to prod โ silently moving them to another environment as a side effect of logging
|
|
254
|
-
* in. `beehaven env staging` writes the target; a login must honour it, never redefine it.
|
|
255
|
-
*
|
|
256
|
-
* โ Read from the file rather than `Relay.loadConfig()`: that returns null without a token, which
|
|
257
|
-
* is exactly the state a first login is in. `resolveRelaySelection` defaults to prod when the
|
|
258
|
-
* config names nothing, so the no-config case is unchanged.
|
|
259
|
-
*/
|
|
260
|
-
const dialUrl = resolveRelaySelection(readConfigFile()).url;
|
|
261
|
-
const profile = await relay.verifyToken(token, dialUrl);
|
|
262
|
-
if (profile) {
|
|
263
|
-
saveRelayConfig(token, dialUrl);
|
|
264
|
-
if (profile.displayName) {
|
|
265
|
-
console.log(` Logged in as ${profile.displayName} (${profile.subscriptionPlan})`);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
console.log(' Logged in.');
|
|
269
|
-
}
|
|
49
|
+
// โ Imported lazily: the Mac app loads this file for `logout` alone and should not pull in
|
|
50
|
+
// the sign-in flow's dependencies to do it.
|
|
51
|
+
const { browserLogin } = await import('./browser-login.js');
|
|
52
|
+
const who = await browserLogin();
|
|
53
|
+
console.log(` Logged in as ${who.email ?? who.userId}.`);
|
|
270
54
|
console.log(' Config saved to ~/.beehaven/config.json');
|
|
271
55
|
console.log('');
|
|
272
56
|
return true;
|
|
273
57
|
}
|
|
274
|
-
|
|
275
|
-
console.log(
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.log(` ${err?.message ?? err}`);
|
|
60
|
+
console.log(' Run `beehaven login`, then start again.');
|
|
276
61
|
console.log('');
|
|
277
62
|
return false;
|
|
278
63
|
}
|
package/package.json
CHANGED
package/scripts/sync-skills.mjs
CHANGED
|
@@ -35,6 +35,7 @@ const SOURCES = {
|
|
|
35
35
|
'agent-doodle-space':'apps/mcp-server/plugin/skills/agent-doodle-space/SKILL.md',
|
|
36
36
|
'design-craft': 'apps/mcp-server/plugin/skills/design-craft/SKILL.md',
|
|
37
37
|
'layout-systems': 'apps/mcp-server/plugin/skills/layout-systems/SKILL.md',
|
|
38
|
+
'brand-identity': 'apps/mcp-server/plugin/skills/brand-identity/SKILL.md',
|
|
38
39
|
'pair-on-canvas': 'apps/mcp-server/plugin/skills/pair-on-canvas/SKILL.md',
|
|
39
40
|
'codebase-map': 'apps/mcp-server/plugin/skills/codebase-map/SKILL.md',
|
|
40
41
|
'clearly-workspace': 'apps/mcp-server/plugin/skills/clearly-workspace/SKILL.md',
|
package/skills/README.md
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Skills shipped with the CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`clearly-hives/<hive>/skills/`).
|
|
3
|
+
The `beehaven` tarball carries the agent skills a CLI-only user needs โ the same set the Clearly
|
|
4
|
+
Mac app installs โ so `curl | sh` teaches an agent Clearly without the app.
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
with the CLI itself, not with any specific hive. Currently:
|
|
6
|
+
## Where each skill comes from
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
| Skill | Source of truth (edit HERE, never the copy) |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `beehaven-cli` | `clearly-hives/beehaven-cli/skills/SKILL.md` |
|
|
11
|
+
| `clearly-agent`, `clearly-canvas`, `clearly-workspace`, `design-craft`, `layout-systems`, `brand-identity`, `pair-on-canvas`, `codebase-map`, `agent-home`, `agent-doodle-space` | `apps/mcp-server/plugin/skills/<name>/` in the monorepo |
|
|
12
|
+
| `claude-shopper` | this folder โ hand-written, documents `beehaven shop` (the shop@0 protocol) |
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
`scripts/sync-skills.mjs` copies every monorepo source into `skills/<name>/` (`npm run sync:skills`,
|
|
15
|
+
and automatically at `prepack`) and writes `skills/index.json`. Its `SOURCES` table mirrors the
|
|
16
|
+
Mac app's `electron-builder*.yml` extraResources and `SHIPPED_SKILLS`
|
|
17
|
+
(`apps/electron/electron/agent-skills-installer.ts`); `apps/electron/scripts/verify-mcp-parity.mjs`
|
|
18
|
+
fails when the two sets disagree.
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
cp -R packages/cli/skills/claude-shopper ~/.claude/skills/beehaven-shopper
|
|
20
|
-
```
|
|
20
|
+
## Rules
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
- Edit the source, then run `npm run sync:skills`. A change made to a copy here is overwritten.
|
|
23
|
+
- Copies are generated, so they are listed in `.gitignore` (a second committed copy is a second copy
|
|
24
|
+
to drift). โ `agent-home`, `agent-doodle-space`, `clearly-agent` and `clearly-workspace` are still
|
|
25
|
+
tracked from before that rule; treat them as generated all the same.
|
|
26
|
+
- Only `README.md` and `claude-shopper/` are hand-written here.
|