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 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 login # sign in (opens your browser)
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 login Sign in / sign up
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.2",
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/workosAuth.ts
201
- async function json(res) {
202
- const text = await res.text();
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(body)
257
+ body: JSON.stringify({ email })
264
258
  });
265
- const data = await json(res);
266
- if (!res.ok && !data.registration_id) {
267
- throw new Error(`register failed: ${data.error ?? `HTTP ${res.status}`}`);
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 pollClaim(tokenEndpoint, claimToken, f, nowMs = Date.now()) {
281
- const res = await f(tokenEndpoint, {
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/x-www-form-urlencoded" },
284
- body: new URLSearchParams({ grant_type: CLAIM_GRANT, claim_token: claimToken }).toString()
267
+ headers: { "content-type": "application/json" },
268
+ body: JSON.stringify({ email, code })
285
269
  });
286
- const data = await json(res);
287
- if (res.ok && data.access_token)
288
- return { status: "done", tokens: tokensFromResponse(data, nowMs) };
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, Badge } from "@inkjs/ui";
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: "connecting" } : { t: "email" });
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 start = (email) => {
395
- if (started.current)
396
- return;
397
- started.current = true;
398
- setPhase({ t: "connecting" });
399
- login(apiBase, fetch, {
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
- start(initialEmail);
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
- }, [phase]);
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
- start(email);
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 === "connecting" && /* @__PURE__ */ jsx(Spinner, {
471
- label: "Connecting to vibegroup…"
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 === "waiting" && /* @__PURE__ */ jsxs(Box, {
389
+ phase.t === "code" && /* @__PURE__ */ jsxs(Box, {
477
390
  flexDirection: "column",
478
- gap: 1,
479
391
  children: [
480
- /* @__PURE__ */ jsxs(Box, {
481
- borderStyle: "round",
482
- borderColor: color.accent,
483
- paddingX: 1,
484
- flexDirection: "column",
392
+ /* @__PURE__ */ jsxs(Text, {
485
393
  children: [
486
- /* @__PURE__ */ jsx(Text, {
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.url
397
+ children: phase.email
499
398
  }),
500
- /* @__PURE__ */ jsx(Text, {
501
- dimColor: true,
502
- children: "(the code goes into that page — not back here)"
503
- })
399
+ ":"
504
400
  ]
505
401
  }),
506
- /* @__PURE__ */ jsx(Spinner, {
507
- label: "Waiting for you to confirm in the browser…"
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 { fileURLToPath } from "node:url";
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 json2 = JSON.stringify(mergeManagedSettings(readManagedSettings()), null, 2);
579
- const script = `mkdir -p "${dirname2(path)}" && cat > "${path}"`;
580
- const r = spawnSync("sudo", ["sh", "-c", script], { input: json2, stdio: ["pipe", "inherit", "inherit"] });
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 (!commandExists("claude")) {
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
- spawnSync("claude", ["plugin", "marketplace", "add", packageRoot()], { stdio: "inherit" });
596
- spawnSync("claude", ["plugin", "install", "vibegroup@vibegroup"], { stdio: "inherit" });
597
- if (!pluginInstalled()) {
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 spawnSync2 } from "node:child_process";
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 = spawnSync2(cmd, args, { stdio: "inherit", env: env ? { ...process.env, ...env } : process.env });
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.2",
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",
@@ -1,37 +1,29 @@
1
1
  ---
2
- description: Set up vibegroup — authenticate by following auth.md, create/join a team, launch a channel session.
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. **You (the agent) follow the auth.md protocol yourself** do NOT run `vibegroup login` (that's the human/terminal path). Use AskUserQuestion for choices; keep steps terse.
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 — Authenticate (follow auth.md, `service_auth`)
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. Register on their behalf:
14
+ 2. Send a code to that email:
17
15
  ```bash
18
- curl -s -X POST https://api.vibegroup.sh/agent/identity \
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
- Capture `claim_token`, `claim.user_code`, `claim.verification_uri`, and `claim.interval` from the JSON.
23
- 3. Surface to the user, in one message:
24
- > Open this link, sign in to vibegroup (it verifies your email), and enter the code **<user_code>**:
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/oauth2/token \
30
- -H 'content-type: application/x-www-form-urlencoded' \
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":"authorization_pending"}` → keep polling. `{"error":"expired_token"}` re-register (step 2). On success the response has `access_token` + `identity_assertion`.
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>" } }
@@ -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
- }