@xmarts/genius-setup 1.4.0 → 1.6.0
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/bin/genius-setup.js +198 -114
- package/package.json +2 -2
package/bin/genius-setup.js
CHANGED
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @xmarts/genius-setup — one-command frictionless onboarding for Xmarts Genius.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* # Secret-free path (preferred — the setup link delivers no secret in the GET):
|
|
6
|
+
* GENIUS_SETUP_TOKEN=<setup-token> npx @xmarts/genius-setup [--base-url https://beesmart.digital]
|
|
7
|
+
*
|
|
8
|
+
* # Back-compat path (an already-resolved consultant Bearer):
|
|
9
|
+
* GENIUS_TOKEN=<bearer> npx @xmarts/genius-setup [--base-url ...] [--client "Acme"]
|
|
6
10
|
*
|
|
7
11
|
* Idempotent + cross-platform (incl. Windows). It:
|
|
8
|
-
* 1.
|
|
12
|
+
* 1. (if given GENIUS_SETUP_TOKEN) redeems the single-use setup token for the
|
|
13
|
+
* consultant Bearer via a TLS POST to /xma/genius/v1/setup/exchange — the
|
|
14
|
+
* Bearer never rides inside a GET-able bootstrap script;
|
|
15
|
+
* 2. writes ~/.genius/config.json (endpoints + token) and installs the per-turn
|
|
9
16
|
* inject hook (hooks/genius_inject.py) + the session-capture hook
|
|
10
17
|
* (hooks/genius_capture.py);
|
|
11
|
-
*
|
|
12
|
-
*
|
|
18
|
+
* 3. registers the beesmart_genius MCP server in ~/.claude.json;
|
|
19
|
+
* 4. wires per-turn injection (UserPromptSubmit) + SessionStart priming +
|
|
13
20
|
* SessionEnd/PreCompact/Stop capture into ~/.claude/settings.json — merged,
|
|
14
|
-
* never duplicated, with a timestamped backup first
|
|
21
|
+
* never duplicated, with a timestamped backup first;
|
|
22
|
+
* 5. installs/refreshes the Genius Protocol in ~/.claude/CLAUDE.md.
|
|
15
23
|
* After this, the consultant has Brain + capture + feedback flowing with zero
|
|
16
|
-
* further prompts
|
|
24
|
+
* further prompts, and the package self-updates daily.
|
|
17
25
|
*/
|
|
18
26
|
'use strict';
|
|
19
27
|
const fs = require('fs');
|
|
@@ -26,130 +34,206 @@ function arg(name, def) {
|
|
|
26
34
|
}
|
|
27
35
|
function log(msg) { process.stdout.write(msg + '\n'); }
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
const BASE = (arg('base-url', 'https://beesmart.digital')).replace(/\/+$/, '');
|
|
38
|
+
const CLIENT = arg('client', '');
|
|
39
|
+
|
|
40
|
+
// -----------------------------------------------------------------------------
|
|
41
|
+
// Token resolution (priority order):
|
|
42
|
+
// 1. GENIUS_TOKEN env — an already-resolved consultant Bearer (back-compat
|
|
43
|
+
// with links/commands minted before the exchange).
|
|
44
|
+
// 2. --token argv — same, but leaks to the process list (warn).
|
|
45
|
+
// 3. GENIUS_SETUP_TOKEN / — a single-use setup token we redeem ONCE, over TLS,
|
|
46
|
+
// --setup-token for the Bearer. This is the secret-free path: the
|
|
47
|
+
// Bearer is never embedded in a GET-able script.
|
|
48
|
+
// -----------------------------------------------------------------------------
|
|
31
49
|
let TOKEN = process.env.GENIUS_TOKEN || '';
|
|
32
50
|
if (!TOKEN) {
|
|
33
51
|
TOKEN = arg('token', '');
|
|
34
52
|
if (TOKEN) log('WARN: passing --token on the CLI leaks it to the process list / shell history. Prefer GENIUS_TOKEN=... npx @xmarts/genius-setup');
|
|
35
53
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
const SETUP_TOKEN = process.env.GENIUS_SETUP_TOKEN || arg('setup-token', '');
|
|
55
|
+
|
|
56
|
+
function exchangeSetupToken(setupToken, cb) {
|
|
57
|
+
// Redeem the single-use setup token for the consultant Bearer. The api_key
|
|
58
|
+
// crosses the wire exactly once, in this TLS POST body, to a caller holding
|
|
59
|
+
// the unguessable 256-bit token; the server consumes the token atomically.
|
|
60
|
+
let url;
|
|
61
|
+
try { url = new URL(BASE + '/xma/genius/v1/setup/exchange'); }
|
|
62
|
+
catch (e) { return cb(e); }
|
|
63
|
+
const mod = url.protocol === 'http:' ? require('http') : require('https');
|
|
64
|
+
// device_name labels this machine in the consultant's Devices tab (each
|
|
65
|
+
// machine gets its OWN revocable key). Best-effort; harmless if unavailable.
|
|
66
|
+
let deviceName = '';
|
|
67
|
+
try { deviceName = os.hostname() || ''; } catch (_) { deviceName = ''; }
|
|
68
|
+
const payload = JSON.stringify({
|
|
69
|
+
jsonrpc: '2.0', method: 'call', id: 1,
|
|
70
|
+
params: { setup_token: setupToken, device_name: deviceName },
|
|
71
|
+
});
|
|
72
|
+
const req = mod.request({
|
|
73
|
+
hostname: url.hostname,
|
|
74
|
+
port: url.port || (url.protocol === 'http:' ? 80 : 443),
|
|
75
|
+
path: url.pathname,
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
80
|
+
// Cloudflare in front of beesmart.digital 403s the default Node UA.
|
|
81
|
+
'User-Agent': 'XmartsGenius-Setup/1.6.0',
|
|
82
|
+
},
|
|
83
|
+
timeout: 15000,
|
|
84
|
+
}, (res) => {
|
|
85
|
+
let data = '';
|
|
86
|
+
res.on('data', (c) => { data += c; });
|
|
87
|
+
res.on('end', () => {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(data);
|
|
90
|
+
const result = parsed.result || parsed;
|
|
91
|
+
if (result && result.status === 'ok' && result.api_key) {
|
|
92
|
+
cb(null, result.api_key);
|
|
93
|
+
} else {
|
|
94
|
+
cb(new Error((result && result.message) || 'exchange_failed'));
|
|
95
|
+
}
|
|
96
|
+
} catch (e) { cb(e); }
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
req.on('error', (e) => cb(e));
|
|
100
|
+
req.on('timeout', () => { req.destroy(new Error('exchange_timeout')); });
|
|
101
|
+
req.write(payload);
|
|
102
|
+
req.end();
|
|
41
103
|
}
|
|
42
104
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
105
|
+
function runSetup(token) {
|
|
106
|
+
const HOME = os.homedir();
|
|
107
|
+
const GENIUS_DIR = path.join(HOME, '.genius');
|
|
108
|
+
const HOOKS_DIR = path.join(GENIUS_DIR, 'hooks');
|
|
109
|
+
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
47
110
|
|
|
48
|
-
function ensureDir(d) { fs.mkdirSync(d, { recursive: true }); }
|
|
49
|
-
function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return {}; } }
|
|
50
|
-
function backup(p) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
111
|
+
function ensureDir(d) { fs.mkdirSync(d, { recursive: true }); }
|
|
112
|
+
function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return {}; } }
|
|
113
|
+
function backup(p) {
|
|
114
|
+
if (fs.existsSync(p)) {
|
|
115
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
116
|
+
fs.copyFileSync(p, p + '.genius-bak-' + stamp);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function writeJson(p, obj, secret) {
|
|
120
|
+
// (#pkg-3) secret files (token-bearing) are written 0600, never world-readable.
|
|
121
|
+
ensureDir(path.dirname(p));
|
|
122
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2), secret ? { mode: 0o600 } : undefined);
|
|
123
|
+
if (secret) { try { fs.chmodSync(p, 0o600); } catch (_) {} }
|
|
54
124
|
}
|
|
55
|
-
}
|
|
56
|
-
function writeJson(p, obj, secret) {
|
|
57
|
-
// (#pkg-3) secret files (token-bearing) are written 0600, never world-readable.
|
|
58
|
-
ensureDir(path.dirname(p));
|
|
59
|
-
fs.writeFileSync(p, JSON.stringify(obj, null, 2), secret ? { mode: 0o600 } : undefined);
|
|
60
|
-
if (secret) { try { fs.chmodSync(p, 0o600); } catch (_) {} }
|
|
61
|
-
}
|
|
62
125
|
|
|
63
|
-
// 1) ~/.genius/config.json + hook
|
|
64
|
-
ensureDir(HOOKS_DIR);
|
|
65
|
-
const cfg = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
71
|
-
if (CLIENT) cfg.target_client = CLIENT;
|
|
72
|
-
writeJson(path.join(GENIUS_DIR, 'config.json'), cfg, true); // secret: 0600
|
|
126
|
+
// 1) ~/.genius/config.json + hook
|
|
127
|
+
ensureDir(HOOKS_DIR);
|
|
128
|
+
const cfg = {
|
|
129
|
+
base_url: BASE,
|
|
130
|
+
token: token,
|
|
131
|
+
capture_endpoint: BASE + '/xma/genius/v1/capture/transcript',
|
|
132
|
+
inject_endpoint: BASE + '/xma/genius/v1/inject',
|
|
133
|
+
};
|
|
134
|
+
if (CLIENT) cfg.target_client = CLIENT;
|
|
135
|
+
writeJson(path.join(GENIUS_DIR, 'config.json'), cfg, true); // secret: 0600
|
|
73
136
|
|
|
74
|
-
// Copy the hooks shipped alongside this script (capture + per-turn inject).
|
|
75
|
-
function copyHook(fname) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
137
|
+
// Copy the hooks shipped alongside this script (capture + per-turn inject).
|
|
138
|
+
function copyHook(fname) {
|
|
139
|
+
const src = path.join(__dirname, '..', 'hooks', fname);
|
|
140
|
+
const dst = path.join(HOOKS_DIR, fname);
|
|
141
|
+
try {
|
|
142
|
+
fs.copyFileSync(src, dst);
|
|
143
|
+
try { fs.chmodSync(dst, 0o755); } catch (_) {}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
log('WARN: could not copy hook from ' + src + ' (' + e.message + ')');
|
|
146
|
+
}
|
|
147
|
+
return dst;
|
|
83
148
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const hookDst = copyHook('genius_capture.py');
|
|
87
|
-
const injectDst = copyHook('genius_inject.py');
|
|
149
|
+
const hookDst = copyHook('genius_capture.py');
|
|
150
|
+
const injectDst = copyHook('genius_inject.py');
|
|
88
151
|
|
|
89
|
-
// 2) ~/.claude.json mcpServers (merge, no clobber)
|
|
90
|
-
const claudeJsonPath = path.join(HOME, '.claude.json');
|
|
91
|
-
const claudeJson = readJson(claudeJsonPath);
|
|
92
|
-
backup(claudeJsonPath);
|
|
93
|
-
claudeJson.mcpServers = claudeJson.mcpServers || {};
|
|
94
|
-
claudeJson.mcpServers['beesmart_genius'] = {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
writeJson(claudeJsonPath, claudeJson, true); // contains the Bearer -> 0600
|
|
152
|
+
// 2) ~/.claude.json mcpServers (merge, no clobber)
|
|
153
|
+
const claudeJsonPath = path.join(HOME, '.claude.json');
|
|
154
|
+
const claudeJson = readJson(claudeJsonPath);
|
|
155
|
+
backup(claudeJsonPath);
|
|
156
|
+
claudeJson.mcpServers = claudeJson.mcpServers || {};
|
|
157
|
+
claudeJson.mcpServers['beesmart_genius'] = {
|
|
158
|
+
type: 'http',
|
|
159
|
+
url: BASE + '/mcp/consultant',
|
|
160
|
+
headers: { Authorization: 'Bearer ' + token },
|
|
161
|
+
};
|
|
162
|
+
writeJson(claudeJsonPath, claudeJson, true); // contains the Bearer -> 0600
|
|
100
163
|
|
|
101
|
-
// 3) ~/.claude/settings.json hooks (merge by command, no dup)
|
|
102
|
-
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
|
|
103
|
-
const settings = readJson(settingsPath);
|
|
104
|
-
backup(settingsPath);
|
|
105
|
-
settings.hooks = settings.hooks || {};
|
|
106
|
-
function addHook(eventName, scriptPath) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
// 3) ~/.claude/settings.json hooks (merge by command, no dup)
|
|
165
|
+
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
|
|
166
|
+
const settings = readJson(settingsPath);
|
|
167
|
+
backup(settingsPath);
|
|
168
|
+
settings.hooks = settings.hooks || {};
|
|
169
|
+
function addHook(eventName, scriptPath) {
|
|
170
|
+
settings.hooks[eventName] = settings.hooks[eventName] || [];
|
|
171
|
+
const marker = path.basename(scriptPath);
|
|
172
|
+
const already = JSON.stringify(settings.hooks[eventName]).includes(marker);
|
|
173
|
+
if (!already) {
|
|
174
|
+
const cmd = `python3 ${JSON.stringify(scriptPath)} || true`;
|
|
175
|
+
settings.hooks[eventName].push({ hooks: [{ type: 'command', command: cmd }] });
|
|
176
|
+
}
|
|
113
177
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
writeJson(settingsPath, settings);
|
|
178
|
+
// Capture (transcript -> Brain) on session boundaries.
|
|
179
|
+
['SessionEnd', 'PreCompact', 'Stop'].forEach((e) => addHook(e, hookDst));
|
|
180
|
+
// Per-turn knowledge injection + session priming (consultant_access guarantee).
|
|
181
|
+
['UserPromptSubmit', 'SessionStart'].forEach((e) => addHook(e, injectDst));
|
|
182
|
+
writeJson(settingsPath, settings);
|
|
120
183
|
|
|
121
|
-
// 4) ~/.claude/CLAUDE.md — install/refresh the Genius Protocol between auto-managed
|
|
122
|
-
// markers (idempotent; never clobbers the consultant's surrounding content).
|
|
123
|
-
const BEGIN = '<!-- XMARTS-GENIUS-PROTOCOL:BEGIN (auto-managed by @xmarts/genius-setup) -->';
|
|
124
|
-
const END = '<!-- XMARTS-GENIUS-PROTOCOL:END -->';
|
|
125
|
-
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
184
|
+
// 4) ~/.claude/CLAUDE.md — install/refresh the Genius Protocol between auto-managed
|
|
185
|
+
// markers (idempotent; never clobbers the consultant's surrounding content).
|
|
186
|
+
const BEGIN = '<!-- XMARTS-GENIUS-PROTOCOL:BEGIN (auto-managed by @xmarts/genius-setup) -->';
|
|
187
|
+
const END = '<!-- XMARTS-GENIUS-PROTOCOL:END -->';
|
|
188
|
+
try {
|
|
189
|
+
ensureDir(CLAUDE_DIR);
|
|
190
|
+
const claudeMdPath = path.join(CLAUDE_DIR, 'CLAUDE.md');
|
|
191
|
+
let existing = '';
|
|
192
|
+
try { existing = fs.readFileSync(claudeMdPath, 'utf8'); } catch (_) {}
|
|
193
|
+
const proto = fs.readFileSync(path.join(__dirname, '..', 'CLAUDE_PROTOCOL.md'), 'utf8').trim();
|
|
194
|
+
const block = BEGIN + '\n' + proto + '\n' + END;
|
|
195
|
+
let out;
|
|
196
|
+
if (existing.includes(BEGIN) && existing.includes(END)) {
|
|
197
|
+
out = existing.slice(0, existing.indexOf(BEGIN)) + block +
|
|
198
|
+
existing.slice(existing.indexOf(END) + END.length);
|
|
199
|
+
} else {
|
|
200
|
+
out = (existing ? existing.replace(/\s+$/, '') + '\n\n' : '') + block + '\n';
|
|
201
|
+
}
|
|
202
|
+
fs.writeFileSync(claudeMdPath, out);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
log('WARN: could not write CLAUDE.md protocol (' + e.message + ')');
|
|
138
205
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
log('
|
|
206
|
+
|
|
207
|
+
log('');
|
|
208
|
+
log(' Xmarts Genius is set up.');
|
|
209
|
+
log(' - MCP server : beesmart_genius -> ' + BASE + '/mcp/consultant');
|
|
210
|
+
log(' - Capture hook : ' + hookDst + ' (SessionEnd / PreCompact / Stop)');
|
|
211
|
+
log(' - Inject hook : ' + injectDst + ' (UserPromptSubmit / SessionStart)');
|
|
212
|
+
log(' - Config : ' + path.join(GENIUS_DIR, 'config.json'));
|
|
213
|
+
log(' - Protocol : ' + path.join(CLAUDE_DIR, 'CLAUDE.md') + ' (auto-managed)');
|
|
214
|
+
if (CLIENT) log(' - Default client : ' + CLIENT);
|
|
215
|
+
log('');
|
|
216
|
+
log(' Restart Claude Code. Per-turn Brain injection + automatic capture + the Genius');
|
|
217
|
+
log(' protocol are now active. The package SELF-UPDATES daily in the background, so new');
|
|
218
|
+
log(' tools, skills and features arrive automatically — you never have to run this again.');
|
|
142
219
|
}
|
|
143
220
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
log('
|
|
155
|
-
|
|
221
|
+
// -----------------------------------------------------------------------------
|
|
222
|
+
// Entry point
|
|
223
|
+
// -----------------------------------------------------------------------------
|
|
224
|
+
if (TOKEN) {
|
|
225
|
+
// Already-resolved Bearer (back-compat path / self-update). No exchange.
|
|
226
|
+
runSetup(TOKEN);
|
|
227
|
+
} else if (SETUP_TOKEN) {
|
|
228
|
+
// Secret-free path: redeem the single-use setup token for the Bearer.
|
|
229
|
+
exchangeSetupToken(SETUP_TOKEN, (err, key) => {
|
|
230
|
+
if (err || !key) {
|
|
231
|
+
log('ERROR: el enlace de setup ya fue usado o expiró. Pide a tu admin de Xmarts que regenere el link de setup.');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
runSetup(key);
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
log('ERROR: falta el token. Ejecuta el link de setup de Genius (recomendado) o define GENIUS_TOKEN=<token>.');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmarts/genius-setup",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "One-command, self-updating onboarding for Xmarts Genius (Brain MCP + per-turn injection + automatic session/time capture + CLAUDE.md protocol). Installs once; self-updates daily so new tools/skills/features arrive with zero action.",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "One-command, self-updating onboarding for Xmarts Genius (Brain MCP + per-turn injection + automatic session/time capture + CLAUDE.md protocol). Secret-free onboarding via single-use setup-token exchange (no Bearer in the GET-able bootstrap). Installs once; self-updates daily so new tools/skills/features arrive with zero action.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"genius-setup": "bin/genius-setup.js"
|
|
7
7
|
},
|