@webority/ensemble 0.2.3 → 0.3.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/ensemble.js +41 -12
- package/lib/core.js +36 -0
- package/package.json +1 -1
package/bin/ensemble.js
CHANGED
|
@@ -18,9 +18,42 @@ function parseArgs(argv) {
|
|
|
18
18
|
return args;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function reportSetup(wired) {
|
|
22
|
+
core.log('• wired session hooks: ' + (wired.join(', ') || '(no supported coding CLI found — run `ensemble hooks` after installing claude/codex/grok)'));
|
|
23
|
+
core.log('• installed auto-start + launched the runner');
|
|
24
|
+
core.log('\n✓ Done. This machine runs your Ensemble runner and its coding sessions now share the bus (scoped to your org).');
|
|
25
|
+
core.log(' Open a NEW terminal (so ENSEMBLE_MACHINE_TOKEN loads), then start Claude/Codex/Grok as usual.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Browser sign-in (device flow) — no token to copy. The recommended path.
|
|
29
|
+
async function login(args) {
|
|
30
|
+
const api = args.api || core.DEFAULT_API;
|
|
31
|
+
const name = args.name || os.hostname();
|
|
32
|
+
core.log('Ensemble — signing in "' + name + '" via ' + api);
|
|
33
|
+
core.log('• fetching runner + bus binaries');
|
|
34
|
+
const { runnerPath } = await core.ensureBinaries();
|
|
35
|
+
const grant = await core.startDevice(api, name);
|
|
36
|
+
core.log('\n Approve this machine in your browser:\n ' + grant.verificationUriComplete +
|
|
37
|
+
'\n Confirmation code: ' + grant.userCode + '\n');
|
|
38
|
+
core.openBrowser(grant.verificationUriComplete);
|
|
39
|
+
core.log('• waiting for approval …');
|
|
40
|
+
const deadline = Date.now() + grant.expiresIn * 1000;
|
|
41
|
+
let token = null;
|
|
42
|
+
while (Date.now() < deadline) {
|
|
43
|
+
await core.sleep((grant.interval || 5) * 1000);
|
|
44
|
+
const r = await core.pollDevice(api, grant.deviceCode);
|
|
45
|
+
if (r.status === 'approved') { token = r.token; break; }
|
|
46
|
+
if (r.status === 'denied') core.die('approval was denied in the browser.');
|
|
47
|
+
if (r.status === 'expired') core.die('the login request expired — run `ensemble login` again.');
|
|
48
|
+
}
|
|
49
|
+
if (!token) core.die('timed out waiting for approval — run `ensemble login` again.');
|
|
50
|
+
reportSetup(core.finishSetup(api, token, runnerPath));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Headless / CI path: enrol with a copy-pasted enrollment token.
|
|
21
54
|
async function enroll(args) {
|
|
22
55
|
const token = args.token || process.env.ENSEMBLE_ENROLL_TOKEN;
|
|
23
|
-
if (!token) core.die('missing --token <enrollment-token> — get it from your Ensemble portal → Connect Runner');
|
|
56
|
+
if (!token) core.die('missing --token <enrollment-token> — get it from your Ensemble portal → Connect Runner, or use `ensemble login` for browser sign-in');
|
|
24
57
|
const api = args.api || core.DEFAULT_API;
|
|
25
58
|
const name = args.name || os.hostname();
|
|
26
59
|
core.log('Ensemble — enrolling "' + name + '" with ' + api);
|
|
@@ -28,14 +61,7 @@ async function enroll(args) {
|
|
|
28
61
|
const { runnerPath } = await core.ensureBinaries();
|
|
29
62
|
core.log('• exchanging enrollment token for a per-machine token');
|
|
30
63
|
const machineToken = await core.enrollMachine(api, token, name);
|
|
31
|
-
core.
|
|
32
|
-
const wired = core.wireHooks();
|
|
33
|
-
core.log('• wired session hooks: ' + (wired.join(', ') || '(no supported coding CLI found — run `ensemble hooks` after installing claude/codex/grok)'));
|
|
34
|
-
core.log('• installing auto-start + launching the runner');
|
|
35
|
-
core.installAutostart(runnerPath);
|
|
36
|
-
core.startRunner(runnerPath);
|
|
37
|
-
core.log('\n✓ Enrolled. This machine runs your Ensemble runner and its coding sessions now share the bus (scoped to your org).');
|
|
38
|
-
core.log(' Open a NEW terminal (so ENSEMBLE_MACHINE_TOKEN loads), then start Claude/Codex/Grok as usual.');
|
|
64
|
+
reportSetup(core.finishSetup(api, machineToken, runnerPath));
|
|
39
65
|
}
|
|
40
66
|
|
|
41
67
|
async function status(args) {
|
|
@@ -57,13 +83,15 @@ function usage() {
|
|
|
57
83
|
core.log(
|
|
58
84
|
`ensemble — connect this machine to your Ensemble control plane
|
|
59
85
|
|
|
86
|
+
ensemble login [--api <url>] [--name <machine>]
|
|
87
|
+
sign in via your browser (recommended) — approve the machine, no token to copy
|
|
60
88
|
ensemble enroll --token <enrollment-token> [--api <url>] [--name <machine>]
|
|
61
|
-
|
|
89
|
+
headless / CI: enrol with an enrollment token from the portal
|
|
62
90
|
ensemble status show connection + your org's live session mailboxes
|
|
63
91
|
ensemble hooks (re)wire ensemble-bus into installed coding CLIs
|
|
64
92
|
ensemble version
|
|
65
93
|
|
|
66
|
-
|
|
94
|
+
Run \`ensemble login\` and approve the machine in your browser.`);
|
|
67
95
|
}
|
|
68
96
|
|
|
69
97
|
(async () => {
|
|
@@ -71,7 +99,8 @@ Get your enrollment token from the Ensemble portal → Connect Runner.`);
|
|
|
71
99
|
const cmd = argv[0];
|
|
72
100
|
const args = parseArgs(argv.slice(1));
|
|
73
101
|
try {
|
|
74
|
-
if (cmd === '
|
|
102
|
+
if (cmd === 'login') await login(args);
|
|
103
|
+
else if (cmd === 'enroll') await enroll(args);
|
|
75
104
|
else if (cmd === 'status') await status(args);
|
|
76
105
|
else if (cmd === 'hooks') { await core.ensureBinaries(); core.log('wired: ' + (core.wireHooks().join(', ') || 'none')); }
|
|
77
106
|
else if (cmd === 'version' || cmd === '--version' || cmd === '-v') core.log(require('../package.json').version);
|
package/lib/core.js
CHANGED
|
@@ -238,7 +238,43 @@ function startRunner(runnerPath) {
|
|
|
238
238
|
child.unref();
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
242
|
+
|
|
243
|
+
function openBrowser(url) {
|
|
244
|
+
const pf = platform();
|
|
245
|
+
try {
|
|
246
|
+
if (pf.os === 'windows') spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref();
|
|
247
|
+
else if (pf.os === 'mac') spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
248
|
+
else spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
249
|
+
} catch (e) { /* fall back to the printed URL */ }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// --- device-authorization login (RFC 8628): browser approve instead of a copy-pasted token ---
|
|
253
|
+
async function startDevice(api, machineName) {
|
|
254
|
+
const res = await request('POST', api.replace(/\/$/, '') + '/api/device/start',
|
|
255
|
+
{ 'Content-Type': 'application/json' }, JSON.stringify({ machineName }));
|
|
256
|
+
if (res.status !== 200) throw new Error('could not start login: HTTP ' + res.status + ' ' + res.body);
|
|
257
|
+
return JSON.parse(res.body);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function pollDevice(api, deviceCode) {
|
|
261
|
+
const res = await request('POST', api.replace(/\/$/, '') + '/api/device/poll',
|
|
262
|
+
{ 'Content-Type': 'application/json' }, JSON.stringify({ deviceCode }));
|
|
263
|
+
if (res.status !== 200) throw new Error('poll failed: HTTP ' + res.status);
|
|
264
|
+
return JSON.parse(res.body);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Shared post-token setup used by both `login` and `enroll`: write config, wire hooks, auto-start, run.
|
|
268
|
+
function finishSetup(api, machineToken, runnerPath) {
|
|
269
|
+
writeRunnerConfig(api, machineToken);
|
|
270
|
+
const wired = wireHooks();
|
|
271
|
+
installAutostart(runnerPath);
|
|
272
|
+
startRunner(runnerPath);
|
|
273
|
+
return wired;
|
|
274
|
+
}
|
|
275
|
+
|
|
241
276
|
module.exports = {
|
|
242
277
|
log, warn, die, platform, DEFAULT_API, ENSEMBLE_DIR, RUNNER_DIR, BIN_DIR,
|
|
243
278
|
ensureBinaries, enrollMachine, writeRunnerConfig, wireHooks, installAutostart, startRunner, request,
|
|
279
|
+
sleep, openBrowser, startDevice, pollDevice, finishSetup,
|
|
244
280
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/ensemble",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Connect this machine to your Ensemble control plane — runs the local agent runner and wires the ensemble-bus session bus so your coding sessions talk (per-org, isolated).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ensemble": "bin/ensemble.js"
|