vibegroup 0.1.2 → 0.1.4
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 +10 -2
- package/dist/cli.js +144 -235
- package/package.json +1 -3
- package/plugin/commands/init.md +12 -20
- package/scripts/postinstall.mjs +0 -38
package/README.md
CHANGED
|
@@ -11,6 +11,12 @@ When you and your teammates are each heads-down in your own repo on your own mac
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
+
```bash
|
|
15
|
+
npx vibegroup install # registers the vibegroup plugin in Claude Code
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install the CLI globally:
|
|
19
|
+
|
|
14
20
|
```bash
|
|
15
21
|
npm i -g vibegroup
|
|
16
22
|
```
|
|
@@ -18,7 +24,7 @@ npm i -g vibegroup
|
|
|
18
24
|
## Quickstart
|
|
19
25
|
|
|
20
26
|
```bash
|
|
21
|
-
vibegroup
|
|
27
|
+
vibegroup init # one-time: install the plugin, enable the channel, sign in
|
|
22
28
|
vibegroup team create acme --name Acme # create a team (or get invited to one)
|
|
23
29
|
vibegroup claude --team acme # launch Claude Code wired to your team's room
|
|
24
30
|
```
|
|
@@ -38,7 +44,9 @@ Inside the session, your agent can reach teammates' agents:
|
|
|
38
44
|
## Commands
|
|
39
45
|
|
|
40
46
|
```
|
|
41
|
-
vibegroup
|
|
47
|
+
vibegroup install Register the plugin in Claude Code (also: npx vibegroup install)
|
|
48
|
+
vibegroup init [email] One-time setup: plugin + channel + sign-in
|
|
49
|
+
vibegroup login [email] Sign in / sign up
|
|
42
50
|
vibegroup logout Clear the cached session
|
|
43
51
|
vibegroup status Show your auth status
|
|
44
52
|
vibegroup team create <slug> [--name] Create a team (+ a general room)
|
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,7 @@ var package_default;
|
|
|
70
70
|
var init_package = __esm(() => {
|
|
71
71
|
package_default = {
|
|
72
72
|
name: "vibegroup",
|
|
73
|
-
version: "0.1.
|
|
73
|
+
version: "0.1.4",
|
|
74
74
|
description: "Talk to your teammates' Claude Code agents — agent-to-agent collaboration for Claude Code over a shared channel.",
|
|
75
75
|
type: "module",
|
|
76
76
|
bin: {
|
|
@@ -79,7 +79,6 @@ var init_package = __esm(() => {
|
|
|
79
79
|
files: [
|
|
80
80
|
"dist/cli.js",
|
|
81
81
|
"README.md",
|
|
82
|
-
"scripts/postinstall.mjs",
|
|
83
82
|
".claude-plugin/marketplace.json",
|
|
84
83
|
"plugin/.claude-plugin",
|
|
85
84
|
"plugin/.mcp.json",
|
|
@@ -107,7 +106,6 @@ var init_package = __esm(() => {
|
|
|
107
106
|
"build:server": "cd packages/server && bun run build",
|
|
108
107
|
build: "bun run build:relay && bun run build:server && bun run build:plugin && bun run build:cli",
|
|
109
108
|
prepublishOnly: "bun run build:plugin && bun run build:cli",
|
|
110
|
-
postinstall: "node scripts/postinstall.mjs",
|
|
111
109
|
"test:cli": "bun test ./test/",
|
|
112
110
|
"test:protocol": "cd packages/protocol && bun test",
|
|
113
111
|
"test:relay": "cd packages/relay && bun test",
|
|
@@ -178,6 +176,60 @@ var init_allowlist2 = __esm(() => {
|
|
|
178
176
|
init_allowlist();
|
|
179
177
|
});
|
|
180
178
|
|
|
179
|
+
// src/lib/pluginInstall.ts
|
|
180
|
+
import { spawnSync } from "node:child_process";
|
|
181
|
+
import { fileURLToPath } from "node:url";
|
|
182
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
183
|
+
function packageRoot() {
|
|
184
|
+
return join2(dirname2(fileURLToPath(import.meta.url)), "..");
|
|
185
|
+
}
|
|
186
|
+
function claudeAvailable() {
|
|
187
|
+
try {
|
|
188
|
+
return spawnSync("claude", ["--version"], { stdio: "ignore" }).status === 0;
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function pluginInstalled() {
|
|
194
|
+
const r = spawnSync("claude", ["plugin", "list"], { encoding: "utf8" });
|
|
195
|
+
return r.status === 0 && /vibegroup@vibegroup/.test(r.stdout ?? "");
|
|
196
|
+
}
|
|
197
|
+
function installPlugin() {
|
|
198
|
+
if (!claudeAvailable()) {
|
|
199
|
+
return { ok: false, message: "Claude Code (`claude`) is not on your PATH. Install it first: https://docs.claude.com/claude-code" };
|
|
200
|
+
}
|
|
201
|
+
const root = packageRoot();
|
|
202
|
+
spawnSync("claude", ["plugin", "marketplace", "remove", "vibegroup"], { stdio: "ignore" });
|
|
203
|
+
const add = spawnSync("claude", ["plugin", "marketplace", "add", root], { stdio: "inherit" });
|
|
204
|
+
if (add.status !== 0)
|
|
205
|
+
return { ok: false, message: "failed to add the vibegroup marketplace" };
|
|
206
|
+
const inst = spawnSync("claude", ["plugin", "install", "vibegroup@vibegroup"], { stdio: "inherit" });
|
|
207
|
+
if (inst.status !== 0 && !pluginInstalled())
|
|
208
|
+
return { ok: false, message: "failed to install the vibegroup plugin" };
|
|
209
|
+
return { ok: true, message: "plugin installed" };
|
|
210
|
+
}
|
|
211
|
+
var init_pluginInstall = () => {};
|
|
212
|
+
|
|
213
|
+
// src/commands/install.ts
|
|
214
|
+
var exports_install = {};
|
|
215
|
+
__export(exports_install, {
|
|
216
|
+
installCommand: () => installCommand
|
|
217
|
+
});
|
|
218
|
+
async function installCommand() {
|
|
219
|
+
console.log(`Installing the vibegroup plugin into Claude Code…
|
|
220
|
+
`);
|
|
221
|
+
const r = installPlugin();
|
|
222
|
+
if (!r.ok) {
|
|
223
|
+
console.error(r.message);
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
console.log("\n✓ Plugin installed. Next: `vibegroup init` to enable the channel + sign in.");
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
var init_install = __esm(() => {
|
|
230
|
+
init_pluginInstall();
|
|
231
|
+
});
|
|
232
|
+
|
|
181
233
|
// src/ui/runner.ts
|
|
182
234
|
import { render } from "ink";
|
|
183
235
|
function runInk(make) {
|
|
@@ -197,144 +249,30 @@ function runInk(make) {
|
|
|
197
249
|
}
|
|
198
250
|
var init_runner = () => {};
|
|
199
251
|
|
|
200
|
-
// src/lib/
|
|
201
|
-
async function
|
|
202
|
-
const
|
|
203
|
-
try {
|
|
204
|
-
return text ? JSON.parse(text) : {};
|
|
205
|
-
} catch {
|
|
206
|
-
return {};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
function parseCeremony(c) {
|
|
210
|
-
if (!c || !c.user_code || !c.verification_uri)
|
|
211
|
-
return;
|
|
212
|
-
return {
|
|
213
|
-
userCode: c.user_code,
|
|
214
|
-
verificationUri: c.verification_uri,
|
|
215
|
-
interval: typeof c.interval === "number" ? c.interval : 5,
|
|
216
|
-
expiresIn: typeof c.expires_in === "number" ? c.expires_in : 600
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function tokensFromResponse(body, nowMs) {
|
|
220
|
-
const tokens = { accessToken: body.access_token };
|
|
221
|
-
if (typeof body.expires_in === "number") {
|
|
222
|
-
tokens.accessTokenExpiresAt = new Date(nowMs + body.expires_in * 1000).toISOString();
|
|
223
|
-
}
|
|
224
|
-
if (body.identity_assertion)
|
|
225
|
-
tokens.identityAssertion = body.identity_assertion;
|
|
226
|
-
if (body.assertion_expires)
|
|
227
|
-
tokens.assertionExpiresAt = body.assertion_expires;
|
|
228
|
-
if (body.scope)
|
|
229
|
-
tokens.scope = body.scope;
|
|
230
|
-
return tokens;
|
|
231
|
-
}
|
|
232
|
-
async function discover(apiBase, f) {
|
|
233
|
-
const base = apiBase.replace(/\/+$/, "");
|
|
234
|
-
const prmRes = await f(`${base}/.well-known/oauth-protected-resource`);
|
|
235
|
-
if (!prmRes.ok)
|
|
236
|
-
throw new Error(`discovery failed: protected-resource metadata HTTP ${prmRes.status}`);
|
|
237
|
-
const prm = await json(prmRes);
|
|
238
|
-
const as = (prm.authorization_servers ?? [])[0];
|
|
239
|
-
if (!as)
|
|
240
|
-
throw new Error("discovery failed: no authorization_servers in protected-resource metadata");
|
|
241
|
-
const asRes = await f(`${as.replace(/\/+$/, "")}/.well-known/oauth-authorization-server`);
|
|
242
|
-
if (!asRes.ok)
|
|
243
|
-
throw new Error(`discovery failed: authorization-server metadata HTTP ${asRes.status}`);
|
|
244
|
-
const meta = await json(asRes);
|
|
245
|
-
const agent = meta.agent_auth ?? {};
|
|
246
|
-
if (!meta.token_endpoint || !agent.identity_endpoint || !agent.claim_endpoint) {
|
|
247
|
-
throw new Error("discovery failed: authorization server is missing agent_auth endpoints");
|
|
248
|
-
}
|
|
249
|
-
return {
|
|
250
|
-
issuer: meta.issuer,
|
|
251
|
-
tokenEndpoint: meta.token_endpoint,
|
|
252
|
-
revocationEndpoint: meta.revocation_endpoint,
|
|
253
|
-
identityEndpoint: agent.identity_endpoint,
|
|
254
|
-
claimEndpoint: agent.claim_endpoint,
|
|
255
|
-
resource: prm.resource ?? meta.resource,
|
|
256
|
-
scopesSupported: prm.scopes_supported
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
async function register(ep, body, f) {
|
|
260
|
-
const res = await f(ep.identityEndpoint, {
|
|
252
|
+
// src/lib/emailAuth.ts
|
|
253
|
+
async function startEmailLogin(apiBase, email, f) {
|
|
254
|
+
const res = await f(`${apiBase}/auth/email/start`, {
|
|
261
255
|
method: "POST",
|
|
262
256
|
headers: { "content-type": "application/json" },
|
|
263
|
-
body: JSON.stringify(
|
|
257
|
+
body: JSON.stringify({ email })
|
|
264
258
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
throw new Error(
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
const err = await res.json().catch(() => ({}));
|
|
261
|
+
throw new Error(err.detail ?? "could not send the code");
|
|
268
262
|
}
|
|
269
|
-
return {
|
|
270
|
-
registrationId: data.registration_id,
|
|
271
|
-
registrationType: data.registration_type,
|
|
272
|
-
identityAssertion: data.identity_assertion,
|
|
273
|
-
assertionExpires: data.assertion_expires,
|
|
274
|
-
preClaimScopes: data.pre_claim_scopes,
|
|
275
|
-
postClaimScopes: data.post_claim_scopes,
|
|
276
|
-
claimToken: data.claim_token,
|
|
277
|
-
claim: parseCeremony(data.claim)
|
|
278
|
-
};
|
|
279
263
|
}
|
|
280
|
-
async function
|
|
281
|
-
const res = await f(
|
|
264
|
+
async function verifyEmailLogin(apiBase, email, code, f) {
|
|
265
|
+
const res = await f(`${apiBase}/auth/email/verify`, {
|
|
282
266
|
method: "POST",
|
|
283
|
-
headers: { "content-type": "application/
|
|
284
|
-
body:
|
|
267
|
+
headers: { "content-type": "application/json" },
|
|
268
|
+
body: JSON.stringify({ email, code })
|
|
285
269
|
});
|
|
286
|
-
const data = await json(
|
|
287
|
-
if (res.ok
|
|
288
|
-
|
|
289
|
-
switch (data.error) {
|
|
290
|
-
case "authorization_pending":
|
|
291
|
-
return { status: "pending" };
|
|
292
|
-
case "slow_down":
|
|
293
|
-
return { status: "slow_down" };
|
|
294
|
-
case "expired_token":
|
|
295
|
-
return { status: "expired" };
|
|
296
|
-
default:
|
|
297
|
-
throw new Error(`claim poll failed: ${data.error ?? `HTTP ${res.status}`}`);
|
|
270
|
+
const data = await res.json().catch(() => ({}));
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
throw new Error(data.error === "invalid_code" ? "that code is incorrect or expired" : data.detail ?? "verification failed");
|
|
298
273
|
}
|
|
274
|
+
return { accessToken: data.access_token ?? "", identityAssertion: data.identity_assertion, scope: data.scope };
|
|
299
275
|
}
|
|
300
|
-
var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim";
|
|
301
|
-
|
|
302
|
-
// src/lib/loginFlow.ts
|
|
303
|
-
async function login(apiBase, f, hooks) {
|
|
304
|
-
const emit = (e) => hooks.onEvent?.(e);
|
|
305
|
-
const sleep = hooks.sleep ?? defaultSleep;
|
|
306
|
-
const now = hooks.now ?? Date.now;
|
|
307
|
-
emit({ type: "discovering", apiBase });
|
|
308
|
-
const ep = await discover(apiBase, f);
|
|
309
|
-
emit({ type: "discovered", endpoints: ep });
|
|
310
|
-
const email = (await hooks.email())?.trim();
|
|
311
|
-
if (!email)
|
|
312
|
-
return null;
|
|
313
|
-
emit({ type: "registering", email });
|
|
314
|
-
const reg = await register(ep, { type: "service_auth", login_hint: email }, f);
|
|
315
|
-
if (!reg.claim || !reg.claimToken)
|
|
316
|
-
throw new Error("service_auth did not return a claim ceremony");
|
|
317
|
-
emit({ type: "claim", ceremony: reg.claim });
|
|
318
|
-
emit({ type: "polling" });
|
|
319
|
-
let interval = Math.max(1, reg.claim.interval);
|
|
320
|
-
const deadline = now() + (hooks.timeoutMs ?? 10 * 60 * 1000);
|
|
321
|
-
for (;; ) {
|
|
322
|
-
const r = await pollClaim(ep.tokenEndpoint, reg.claimToken, f, now());
|
|
323
|
-
if (r.status === "done") {
|
|
324
|
-
emit({ type: "done" });
|
|
325
|
-
return { tokens: r.tokens, email };
|
|
326
|
-
}
|
|
327
|
-
if (r.status === "expired")
|
|
328
|
-
throw new Error("the code expired — run `vibegroup login` again");
|
|
329
|
-
if (r.status === "slow_down")
|
|
330
|
-
interval += 5;
|
|
331
|
-
if (now() > deadline)
|
|
332
|
-
throw new Error("login timed out");
|
|
333
|
-
await sleep(interval * 1000);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
337
|
-
var init_loginFlow = () => {};
|
|
338
276
|
|
|
339
277
|
// src/ui/session.ts
|
|
340
278
|
function jwtClaim(jwt, key) {
|
|
@@ -354,17 +292,6 @@ function sessionFromTokens(tokens, email) {
|
|
|
354
292
|
};
|
|
355
293
|
}
|
|
356
294
|
|
|
357
|
-
// src/lib/openBrowser.ts
|
|
358
|
-
import { spawn } from "node:child_process";
|
|
359
|
-
function openBrowser(url) {
|
|
360
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
361
|
-
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
362
|
-
try {
|
|
363
|
-
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
364
|
-
} catch {}
|
|
365
|
-
}
|
|
366
|
-
var init_openBrowser = () => {};
|
|
367
|
-
|
|
368
295
|
// src/ui/theme.ts
|
|
369
296
|
var color;
|
|
370
297
|
var init_theme = __esm(() => {
|
|
@@ -381,53 +308,42 @@ var init_theme = __esm(() => {
|
|
|
381
308
|
// src/ui/Login.tsx
|
|
382
309
|
import { useEffect, useRef, useState } from "react";
|
|
383
310
|
import { Box, Text } from "ink";
|
|
384
|
-
import { Spinner, StatusMessage, TextInput, Alert
|
|
311
|
+
import { Spinner, StatusMessage, TextInput, Alert } from "@inkjs/ui";
|
|
385
312
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
386
313
|
function Login({
|
|
387
314
|
apiBase,
|
|
388
315
|
initialEmail,
|
|
389
316
|
onExit
|
|
390
317
|
}) {
|
|
391
|
-
const [phase, setPhase] = useState(initialEmail ? { t: "
|
|
392
|
-
const opened = useRef(false);
|
|
318
|
+
const [phase, setPhase] = useState(initialEmail ? { t: "sending", email: initialEmail } : { t: "email" });
|
|
393
319
|
const started = useRef(false);
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
email: async () => email,
|
|
401
|
-
onEvent: (e) => {
|
|
402
|
-
if (e.type === "registering")
|
|
403
|
-
setPhase({ t: "registering", email: e.email });
|
|
404
|
-
else if (e.type === "claim")
|
|
405
|
-
setPhase({ t: "waiting", code: e.ceremony.userCode, url: e.ceremony.verificationUri });
|
|
406
|
-
}
|
|
407
|
-
}).then((res) => {
|
|
408
|
-
if (!res) {
|
|
409
|
-
setPhase({ t: "error", message: "Login cancelled." });
|
|
410
|
-
setTimeout(() => onExit(1), 50);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
writeAuth(sessionFromTokens(res.tokens, res.email));
|
|
414
|
-
setPhase({ t: "done", email: res.email });
|
|
415
|
-
setTimeout(() => onExit(0), 500);
|
|
416
|
-
}).catch((err) => {
|
|
320
|
+
const send = async (email) => {
|
|
321
|
+
setPhase({ t: "sending", email });
|
|
322
|
+
try {
|
|
323
|
+
await startEmailLogin(apiBase, email, fetch);
|
|
324
|
+
setPhase({ t: "code", email });
|
|
325
|
+
} catch (err) {
|
|
417
326
|
setPhase({ t: "error", message: err?.message ?? String(err) });
|
|
418
327
|
setTimeout(() => onExit(1), 800);
|
|
419
|
-
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
const verify = async (email, code) => {
|
|
331
|
+
setPhase({ t: "verifying", email });
|
|
332
|
+
try {
|
|
333
|
+
const tokens = await verifyEmailLogin(apiBase, email, code, fetch);
|
|
334
|
+
writeAuth(sessionFromTokens(tokens, email));
|
|
335
|
+
setPhase({ t: "done", email });
|
|
336
|
+
setTimeout(() => onExit(0), 500);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
setPhase({ t: "code", email, error: err?.message ?? String(err) });
|
|
339
|
+
}
|
|
420
340
|
};
|
|
421
341
|
useEffect(() => {
|
|
422
|
-
if (initialEmail)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
useEffect(() => {
|
|
426
|
-
if (phase.t === "waiting" && !opened.current) {
|
|
427
|
-
opened.current = true;
|
|
428
|
-
openBrowser(phase.url);
|
|
342
|
+
if (initialEmail && !started.current) {
|
|
343
|
+
started.current = true;
|
|
344
|
+
send(initialEmail);
|
|
429
345
|
}
|
|
430
|
-
}, [
|
|
346
|
+
}, []);
|
|
431
347
|
return /* @__PURE__ */ jsxs(Box, {
|
|
432
348
|
flexDirection: "column",
|
|
433
349
|
gap: 1,
|
|
@@ -457,9 +373,9 @@ function Login({
|
|
|
457
373
|
children: /* @__PURE__ */ jsx(TextInput, {
|
|
458
374
|
placeholder: "you@company.com",
|
|
459
375
|
onSubmit: (value) => {
|
|
460
|
-
const email = value.trim();
|
|
461
|
-
if (email)
|
|
462
|
-
|
|
376
|
+
const email = value.trim().toLowerCase();
|
|
377
|
+
if (email.includes("@"))
|
|
378
|
+
send(email);
|
|
463
379
|
else
|
|
464
380
|
onExit(1);
|
|
465
381
|
}
|
|
@@ -467,47 +383,48 @@ function Login({
|
|
|
467
383
|
})
|
|
468
384
|
]
|
|
469
385
|
}),
|
|
470
|
-
phase.t === "
|
|
471
|
-
label:
|
|
472
|
-
}),
|
|
473
|
-
phase.t === "registering" && /* @__PURE__ */ jsx(Spinner, {
|
|
474
|
-
label: `Registering ${phase.email}…`
|
|
386
|
+
phase.t === "sending" && /* @__PURE__ */ jsx(Spinner, {
|
|
387
|
+
label: `Sending a code to ${phase.email}…`
|
|
475
388
|
}),
|
|
476
|
-
phase.t === "
|
|
389
|
+
phase.t === "code" && /* @__PURE__ */ jsxs(Box, {
|
|
477
390
|
flexDirection: "column",
|
|
478
|
-
gap: 1,
|
|
479
391
|
children: [
|
|
480
|
-
/* @__PURE__ */ jsxs(
|
|
481
|
-
borderStyle: "round",
|
|
482
|
-
borderColor: color.accent,
|
|
483
|
-
paddingX: 1,
|
|
484
|
-
flexDirection: "column",
|
|
392
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
485
393
|
children: [
|
|
486
|
-
|
|
487
|
-
children: "Open this link, sign in, and enter the code:"
|
|
488
|
-
}),
|
|
489
|
-
/* @__PURE__ */ jsx(Box, {
|
|
490
|
-
marginY: 1,
|
|
491
|
-
children: /* @__PURE__ */ jsx(Badge, {
|
|
492
|
-
color: "cyan",
|
|
493
|
-
children: phase.code
|
|
494
|
-
})
|
|
495
|
-
}),
|
|
394
|
+
"Enter the 6-digit code sent to ",
|
|
496
395
|
/* @__PURE__ */ jsx(Text, {
|
|
497
396
|
color: color.accent,
|
|
498
|
-
children: phase.
|
|
397
|
+
children: phase.email
|
|
499
398
|
}),
|
|
500
|
-
|
|
501
|
-
dimColor: true,
|
|
502
|
-
children: "(the code goes into that page — not back here)"
|
|
503
|
-
})
|
|
399
|
+
":"
|
|
504
400
|
]
|
|
505
401
|
}),
|
|
506
|
-
/* @__PURE__ */ jsx(
|
|
507
|
-
|
|
402
|
+
/* @__PURE__ */ jsx(Box, {
|
|
403
|
+
marginTop: 1,
|
|
404
|
+
children: /* @__PURE__ */ jsx(TextInput, {
|
|
405
|
+
placeholder: "123456",
|
|
406
|
+
onSubmit: (value) => {
|
|
407
|
+
const code = value.trim();
|
|
408
|
+
if (code)
|
|
409
|
+
verify(phase.email, code);
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
}),
|
|
413
|
+
phase.error && /* @__PURE__ */ jsx(Box, {
|
|
414
|
+
marginTop: 1,
|
|
415
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
416
|
+
color: color.err,
|
|
417
|
+
children: [
|
|
418
|
+
phase.error,
|
|
419
|
+
" — try again."
|
|
420
|
+
]
|
|
421
|
+
})
|
|
508
422
|
})
|
|
509
423
|
]
|
|
510
424
|
}),
|
|
425
|
+
phase.t === "verifying" && /* @__PURE__ */ jsx(Spinner, {
|
|
426
|
+
label: "Verifying…"
|
|
427
|
+
}),
|
|
511
428
|
phase.t === "done" && /* @__PURE__ */ jsxs(StatusMessage, {
|
|
512
429
|
variant: "success",
|
|
513
430
|
children: [
|
|
@@ -524,9 +441,7 @@ function Login({
|
|
|
524
441
|
});
|
|
525
442
|
}
|
|
526
443
|
var init_Login = __esm(() => {
|
|
527
|
-
init_loginFlow();
|
|
528
444
|
init_auth();
|
|
529
|
-
init_openBrowser();
|
|
530
445
|
init_theme();
|
|
531
446
|
});
|
|
532
447
|
|
|
@@ -551,20 +466,9 @@ var exports_init = {};
|
|
|
551
466
|
__export(exports_init, {
|
|
552
467
|
initCommand: () => initCommand
|
|
553
468
|
});
|
|
554
|
-
import { spawnSync } from "node:child_process";
|
|
469
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
555
470
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
556
|
-
import {
|
|
557
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
558
|
-
function packageRoot() {
|
|
559
|
-
return join2(dirname2(fileURLToPath(import.meta.url)), "..");
|
|
560
|
-
}
|
|
561
|
-
function commandExists(cmd) {
|
|
562
|
-
return spawnSync(cmd, ["--version"], { stdio: "ignore" }).status === 0;
|
|
563
|
-
}
|
|
564
|
-
function pluginInstalled() {
|
|
565
|
-
const r = spawnSync("claude", ["plugin", "list"], { encoding: "utf8" });
|
|
566
|
-
return r.status === 0 && /vibegroup@vibegroup/.test(r.stdout ?? "");
|
|
567
|
-
}
|
|
471
|
+
import { dirname as dirname3 } from "node:path";
|
|
568
472
|
function readManagedSettings() {
|
|
569
473
|
try {
|
|
570
474
|
const p = managedSettingsPath();
|
|
@@ -575,16 +479,16 @@ function readManagedSettings() {
|
|
|
575
479
|
}
|
|
576
480
|
function writeAllowlistWithSudo() {
|
|
577
481
|
const path = managedSettingsPath();
|
|
578
|
-
const
|
|
579
|
-
const script = `mkdir -p "${
|
|
580
|
-
const r =
|
|
482
|
+
const json = JSON.stringify(mergeManagedSettings(readManagedSettings()), null, 2);
|
|
483
|
+
const script = `mkdir -p "${dirname3(path)}" && cat > "${path}"`;
|
|
484
|
+
const r = spawnSync2("sudo", ["sh", "-c", script], { input: json, stdio: ["pipe", "inherit", "inherit"] });
|
|
581
485
|
return r.status === 0;
|
|
582
486
|
}
|
|
583
487
|
async function initCommand(rest, flags = {}, env = process.env) {
|
|
584
488
|
const dev = flags.dev === true;
|
|
585
489
|
console.log(`vibegroup setup
|
|
586
490
|
`);
|
|
587
|
-
if (!
|
|
491
|
+
if (!claudeAvailable()) {
|
|
588
492
|
console.error("Claude Code (`claude`) is not on your PATH. Install it first: https://docs.claude.com/claude-code");
|
|
589
493
|
return 1;
|
|
590
494
|
}
|
|
@@ -592,10 +496,9 @@ async function initCommand(rest, flags = {}, env = process.env) {
|
|
|
592
496
|
console.log("✓ Plugin already installed.");
|
|
593
497
|
} else {
|
|
594
498
|
console.log("• Installing the vibegroup plugin into Claude Code…");
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
console.error("Plugin install did not complete. Try manually: claude plugin install vibegroup@vibegroup");
|
|
499
|
+
const r = installPlugin();
|
|
500
|
+
if (!r.ok) {
|
|
501
|
+
console.error(r.message);
|
|
599
502
|
return 1;
|
|
600
503
|
}
|
|
601
504
|
console.log("✓ Plugin installed.");
|
|
@@ -632,6 +535,7 @@ async function initCommand(rest, flags = {}, env = process.env) {
|
|
|
632
535
|
var init_init = __esm(() => {
|
|
633
536
|
init_auth();
|
|
634
537
|
init_allowlist();
|
|
538
|
+
init_pluginInstall();
|
|
635
539
|
init_login();
|
|
636
540
|
});
|
|
637
541
|
|
|
@@ -753,7 +657,7 @@ var init_team = __esm(() => {
|
|
|
753
657
|
});
|
|
754
658
|
|
|
755
659
|
// src/lib/claudeLaunch.ts
|
|
756
|
-
import { spawnSync as
|
|
660
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
757
661
|
function buildClaudeArgs(opts = {}) {
|
|
758
662
|
const extra = opts.extraArgs ?? [];
|
|
759
663
|
return opts.dangerously ? ["--dangerously-load-development-channels", CHANNEL_SPEC, ...extra] : ["--channels", CHANNEL_SPEC, ...extra];
|
|
@@ -762,7 +666,7 @@ function launchClaude(opts = {}, launcher = defaultLauncher) {
|
|
|
762
666
|
return launcher("claude", buildClaudeArgs(opts), opts.env);
|
|
763
667
|
}
|
|
764
668
|
var CHANNEL_SPEC = "plugin:vibegroup@vibegroup", defaultLauncher = (cmd, args, env) => {
|
|
765
|
-
const r =
|
|
669
|
+
const r = spawnSync3(cmd, args, { stdio: "inherit", env: env ? { ...process.env, ...env } : process.env });
|
|
766
670
|
if (r.error)
|
|
767
671
|
throw r.error;
|
|
768
672
|
return r.status ?? 0;
|
|
@@ -893,6 +797,10 @@ async function run(argv, env = process.env) {
|
|
|
893
797
|
const { allowlistPathCommand: allowlistPathCommand2 } = await Promise.resolve().then(() => (init_allowlist2(), exports_allowlist));
|
|
894
798
|
return allowlistPathCommand2();
|
|
895
799
|
}
|
|
800
|
+
case "install": {
|
|
801
|
+
const { installCommand: installCommand2 } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
802
|
+
return installCommand2();
|
|
803
|
+
}
|
|
896
804
|
case "init": {
|
|
897
805
|
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
898
806
|
return initCommand2(rest, flags, env);
|
|
@@ -929,6 +837,7 @@ var VERSION, DEFAULT_API_BASE2 = "https://api.vibegroup.sh", HELP = `vibegroup
|
|
|
929
837
|
Usage: vibegroup <command> [options]
|
|
930
838
|
|
|
931
839
|
Commands:
|
|
840
|
+
install Register the vibegroup plugin in Claude Code (also: npx vibegroup install)
|
|
932
841
|
init [email] One-time setup: install the plugin, enable the channel, sign in
|
|
933
842
|
login [email] Sign in / sign up (opens your browser, verifies email)
|
|
934
843
|
logout Clear the cached session
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibegroup",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Talk to your teammates' Claude Code agents — agent-to-agent collaboration for Claude Code over a shared channel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/cli.js",
|
|
11
11
|
"README.md",
|
|
12
|
-
"scripts/postinstall.mjs",
|
|
13
12
|
".claude-plugin/marketplace.json",
|
|
14
13
|
"plugin/.claude-plugin",
|
|
15
14
|
"plugin/.mcp.json",
|
|
@@ -37,7 +36,6 @@
|
|
|
37
36
|
"build:server": "cd packages/server && bun run build",
|
|
38
37
|
"build": "bun run build:relay && bun run build:server && bun run build:plugin && bun run build:cli",
|
|
39
38
|
"prepublishOnly": "bun run build:plugin && bun run build:cli",
|
|
40
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
41
39
|
"test:cli": "bun test ./test/",
|
|
42
40
|
"test:protocol": "cd packages/protocol && bun test",
|
|
43
41
|
"test:relay": "cd packages/relay && bun test",
|
package/plugin/commands/init.md
CHANGED
|
@@ -1,37 +1,29 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Set up vibegroup —
|
|
2
|
+
description: Set up vibegroup — sign in with an email code, create/join a team, launch a channel session.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Onboard the user to vibegroup. **
|
|
5
|
+
Onboard the user to vibegroup. Sign-in is **passwordless email code** — you send a code, the user reads it from their inbox and pastes it to you. Do NOT run `vibegroup login` (that's the human/terminal path). Use AskUserQuestion for choices; keep steps terse.
|
|
6
6
|
|
|
7
7
|
## Phase 0 — Already authenticated?
|
|
8
8
|
|
|
9
9
|
Read `~/.claude/vibegroup/auth.json` (honor `CLAUDE_CONFIG_DIR`). If it has a non-empty `accessToken`, you're signed in — skip to Phase 2.
|
|
10
10
|
|
|
11
|
-
## Phase 1 —
|
|
12
|
-
|
|
13
|
-
vibegroup publishes its agent-auth instructions at `https://api.vibegroup.sh/auth.md`. Follow the **`service_auth` (user-claimed)** path — no anonymous:
|
|
11
|
+
## Phase 1 — Sign in (email code, no browser)
|
|
14
12
|
|
|
15
13
|
1. Ask the user which email to sign in with (e.g. `terry@cruz.pe`).
|
|
16
|
-
2.
|
|
14
|
+
2. Send a code to that email:
|
|
17
15
|
```bash
|
|
18
|
-
curl -s -X POST https://api.vibegroup.sh/
|
|
19
|
-
-H 'content-type: application/json'
|
|
20
|
-
-d '{"type":"service_auth","login_hint":"<email>"}'
|
|
16
|
+
curl -s -X POST https://api.vibegroup.sh/auth/email/start \
|
|
17
|
+
-H 'content-type: application/json' -d '{"email":"<email>"}'
|
|
21
18
|
```
|
|
22
|
-
|
|
23
|
-
3.
|
|
24
|
-
|
|
25
|
-
> <verification_uri>
|
|
26
|
-
> (the code goes into that page — not back to me)
|
|
27
|
-
4. Poll until they confirm — wait `interval` seconds (default 5) between calls, give up after ~10 min:
|
|
19
|
+
(`{"ok":true}` means it's on the way.)
|
|
20
|
+
3. Tell the user, in one message: *"I sent a 6-digit code to `<email>` — paste it here when it arrives."* Then wait for them to give you the code.
|
|
21
|
+
4. Verify the code (this proves they own the email + signs them in):
|
|
28
22
|
```bash
|
|
29
|
-
curl -s -X POST https://api.vibegroup.sh/
|
|
30
|
-
-H 'content-type: application/
|
|
31
|
-
--data-urlencode 'grant_type=urn:workos:agent-auth:grant-type:claim' \
|
|
32
|
-
--data-urlencode 'claim_token=<claim_token>'
|
|
23
|
+
curl -s -X POST https://api.vibegroup.sh/auth/email/verify \
|
|
24
|
+
-H 'content-type: application/json' -d '{"email":"<email>","code":"<code>"}'
|
|
33
25
|
```
|
|
34
|
-
`{"error":"
|
|
26
|
+
`{"error":"invalid_code"}` → tell them it was wrong/expired and ask again (re-send with step 2 if needed). On success the response has `access_token` + `identity_assertion`.
|
|
35
27
|
5. Persist the session so the channel + CLI pick it up — write `~/.claude/vibegroup/auth.json` (file mode 600):
|
|
36
28
|
```json
|
|
37
29
|
{ "accessToken": "<access_token>", "identityAssertion": "<identity_assertion>", "user": { "id": "<email>", "email": "<email>" } }
|
package/scripts/postinstall.mjs
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Best-effort: after `npm i -g vibegroup`, register the plugin into Claude Code so
|
|
2
|
-
// it's usable immediately. The channel allowlist (needs sudo) and sign-in still
|
|
3
|
-
// happen in `vibegroup init`. Never fails the install — only global installs act.
|
|
4
|
-
import { spawnSync } from 'node:child_process'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { dirname, join } from 'node:path'
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
if (process.env.npm_config_global !== 'true') process.exit(0)
|
|
10
|
-
|
|
11
|
-
const has = (cmd) => {
|
|
12
|
-
try {
|
|
13
|
-
return spawnSync(cmd, ['--version'], { stdio: 'ignore' }).status === 0
|
|
14
|
-
} catch {
|
|
15
|
-
return false
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (!has('claude')) {
|
|
20
|
-
console.log('\nvibegroup installed. Install Claude Code, then run `vibegroup init` to set up the plugin + channel.\n')
|
|
21
|
-
process.exit(0)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const list = spawnSync('claude', ['plugin', 'list'], { encoding: 'utf8' })
|
|
25
|
-
if (list.status === 0 && /vibegroup@vibegroup/.test(list.stdout ?? '')) {
|
|
26
|
-
console.log('\nvibegroup plugin already registered. Finish setup any time with `vibegroup init`.\n')
|
|
27
|
-
process.exit(0)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
31
|
-
console.log('\nRegistering the vibegroup plugin in Claude Code…')
|
|
32
|
-
spawnSync('claude', ['plugin', 'marketplace', 'add', root], { stdio: 'inherit' })
|
|
33
|
-
spawnSync('claude', ['plugin', 'install', 'vibegroup@vibegroup'], { stdio: 'inherit' })
|
|
34
|
-
console.log('\n✓ Plugin registered. Next: run `vibegroup init` (enables the channel + signs you in).\n')
|
|
35
|
-
} catch {
|
|
36
|
-
// never break the install
|
|
37
|
-
process.exit(0)
|
|
38
|
-
}
|