vibegroup 0.1.0 → 0.1.2

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.
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "vibegroup",
3
+ "owner": {
4
+ "name": "Terry Cruz Melo",
5
+ "email": "terry@cruz.pe"
6
+ },
7
+ "description": "vibegroup: let your Claude Code agents talk to each other across repos, machines, and networks.",
8
+ "plugins": [
9
+ {
10
+ "name": "vibegroup",
11
+ "source": "./plugin",
12
+ "description": "Ask peer Claude Code agents what they're working on, over an end-to-end-encrypted relay. Answers come from their live session via Claude Code Channels.",
13
+ "homepage": "https://vibegroup.sh",
14
+ "license": "MIT",
15
+ "keywords": ["claude-code", "channel", "mcp", "agents", "collaboration", "relay"]
16
+ }
17
+ ]
18
+ }
package/dist/cli.js CHANGED
@@ -65,6 +65,67 @@ function isLoggedIn(tokens) {
65
65
  }
66
66
  var init_auth = () => {};
67
67
 
68
+ // package.json
69
+ var package_default;
70
+ var init_package = __esm(() => {
71
+ package_default = {
72
+ name: "vibegroup",
73
+ version: "0.1.2",
74
+ description: "Talk to your teammates' Claude Code agents — agent-to-agent collaboration for Claude Code over a shared channel.",
75
+ type: "module",
76
+ bin: {
77
+ vibegroup: "dist/cli.js"
78
+ },
79
+ files: [
80
+ "dist/cli.js",
81
+ "README.md",
82
+ "scripts/postinstall.mjs",
83
+ ".claude-plugin/marketplace.json",
84
+ "plugin/.claude-plugin",
85
+ "plugin/.mcp.json",
86
+ "plugin/dist",
87
+ "plugin/commands",
88
+ "plugin/hooks"
89
+ ],
90
+ engines: {
91
+ node: ">=18"
92
+ },
93
+ license: "UNLICENSED",
94
+ homepage: "https://vibegroup.sh",
95
+ bugs: {
96
+ url: "https://vibegroup.sh"
97
+ },
98
+ keywords: ["claude", "claude-code", "agents", "collaboration", "cli", "vibegroup"],
99
+ publishConfig: {
100
+ access: "public"
101
+ },
102
+ workspaces: ["packages/*", "plugin"],
103
+ scripts: {
104
+ "build:relay": "cd packages/relay && bun run build",
105
+ "build:plugin": "cd plugin && bun run build",
106
+ "build:cli": "bun run scripts/build-cli.ts",
107
+ "build:server": "cd packages/server && bun run build",
108
+ build: "bun run build:relay && bun run build:server && bun run build:plugin && bun run build:cli",
109
+ prepublishOnly: "bun run build:plugin && bun run build:cli",
110
+ postinstall: "node scripts/postinstall.mjs",
111
+ "test:cli": "bun test ./test/",
112
+ "test:protocol": "cd packages/protocol && bun test",
113
+ "test:relay": "cd packages/relay && bun test",
114
+ "test:server": "cd packages/server && bun test",
115
+ "test:plugin": "cd plugin && bun test",
116
+ test: "bun run test:cli && bun run test:protocol && bun run test:relay && bun run test:server && bun run test:plugin"
117
+ },
118
+ dependencies: {
119
+ "@inkjs/ui": "^2.0.0",
120
+ ink: "^7.1.0",
121
+ react: "^19.2.7"
122
+ },
123
+ devDependencies: {
124
+ "@types/react": "^19.2.17"
125
+ }
126
+ };
127
+ });
128
+
68
129
  // src/lib/allowlist.ts
69
130
  function managedSettingsPath(plat = process.platform) {
70
131
  if (plat === "darwin")
@@ -81,6 +142,12 @@ function mergeManagedSettings(existing) {
81
142
  next.allowedChannelPlugins = has ? current : [...current, VIBEGROUP_ENTRY];
82
143
  return next;
83
144
  }
145
+ function isAllowlisted(existing) {
146
+ if (!existing || existing.channelsEnabled !== true)
147
+ return false;
148
+ const current = Array.isArray(existing.allowedChannelPlugins) ? existing.allowedChannelPlugins : [];
149
+ return current.some((e) => e?.marketplace === "vibegroup" && e?.plugin === "vibegroup");
150
+ }
84
151
  var VIBEGROUP_ENTRY;
85
152
  var init_allowlist = __esm(() => {
86
153
  VIBEGROUP_ENTRY = { marketplace: "vibegroup", plugin: "vibegroup" };
@@ -111,6 +178,25 @@ var init_allowlist2 = __esm(() => {
111
178
  init_allowlist();
112
179
  });
113
180
 
181
+ // src/ui/runner.ts
182
+ import { render } from "ink";
183
+ function runInk(make) {
184
+ return new Promise((resolve) => {
185
+ let done = false;
186
+ let instance;
187
+ const finish = (code) => {
188
+ if (done)
189
+ return;
190
+ done = true;
191
+ instance?.unmount();
192
+ resolve(code);
193
+ };
194
+ instance = render(make(finish));
195
+ instance.waitUntilExit().then(() => finish(130));
196
+ });
197
+ }
198
+ var init_runner = () => {};
199
+
114
200
  // src/lib/workosAuth.ts
115
201
  async function json(res) {
116
202
  const text = await res.text();
@@ -191,18 +277,6 @@ async function register(ep, body, f) {
191
277
  claim: parseCeremony(data.claim)
192
278
  };
193
279
  }
194
- async function startClaim(ep, claimToken, email, f) {
195
- const res = await f(ep.claimEndpoint, {
196
- method: "POST",
197
- headers: { "content-type": "application/json" },
198
- body: JSON.stringify({ claim_token: claimToken, email })
199
- });
200
- const data = await json(res);
201
- const ceremony = parseCeremony(data.claim_attempt) ?? parseCeremony(data.claim);
202
- if (!ceremony)
203
- throw new Error(`claim start failed: ${data.error ?? `HTTP ${res.status}`}`);
204
- return ceremony;
205
- }
206
280
  async function pollClaim(tokenEndpoint, claimToken, f, nowMs = Date.now()) {
207
281
  const res = await f(tokenEndpoint, {
208
282
  method: "POST",
@@ -223,24 +297,7 @@ async function pollClaim(tokenEndpoint, claimToken, f, nowMs = Date.now()) {
223
297
  throw new Error(`claim poll failed: ${data.error ?? `HTTP ${res.status}`}`);
224
298
  }
225
299
  }
226
- async function exchangeAssertion(tokenEndpoint, assertion, resource, f, nowMs = Date.now()) {
227
- const params = new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion });
228
- if (resource)
229
- params.set("resource", resource);
230
- const res = await f(tokenEndpoint, {
231
- method: "POST",
232
- headers: { "content-type": "application/x-www-form-urlencoded" },
233
- body: params.toString()
234
- });
235
- const data = await json(res);
236
- if (!res.ok || !data.access_token)
237
- throw new Error(`assertion exchange failed: ${data.error ?? `HTTP ${res.status}`}`);
238
- const tokens = tokensFromResponse(data, nowMs);
239
- if (!tokens.identityAssertion)
240
- tokens.identityAssertion = assertion;
241
- return tokens;
242
- }
243
- var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim", JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
300
+ var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim";
244
301
 
245
302
  // src/lib/loginFlow.ts
246
303
  async function login(apiBase, f, hooks) {
@@ -250,116 +307,332 @@ async function login(apiBase, f, hooks) {
250
307
  emit({ type: "discovering", apiBase });
251
308
  const ep = await discover(apiBase, f);
252
309
  emit({ type: "discovered", endpoints: ep });
253
- if (!await hooks.consent({ resource: ep.resource, scopes: ep.scopesSupported }))
310
+ const email = (await hooks.email())?.trim();
311
+ if (!email)
254
312
  return null;
255
- emit({ type: "registering" });
256
- const reg = await register(ep, { type: "anonymous" }, f);
257
- if (!reg.identityAssertion)
258
- throw new Error("register did not return a pre-claim assertion");
259
- let tokens = await exchangeAssertion(ep.tokenEndpoint, reg.identityAssertion, ep.resource, f, now());
260
- emit({ type: "registered", scopes: reg.preClaimScopes });
261
- const email = hooks.emailFor ? await hooks.emailFor() : null;
262
- if (email && reg.claimToken) {
263
- const ceremony = await startClaim(ep, reg.claimToken, email, f);
264
- emit({ type: "claim", ceremony });
265
- if (hooks.waitForCode)
266
- await hooks.waitForCode();
267
- emit({ type: "polling" });
268
- let interval = Math.max(1, ceremony.interval);
269
- for (;; ) {
270
- const r = await pollClaim(ep.tokenEndpoint, reg.claimToken, f, now());
271
- if (r.status === "done") {
272
- tokens = r.tokens;
273
- emit({ type: "unlocked", scopes: reg.postClaimScopes });
274
- return { tokens, claimed: true };
275
- }
276
- if (r.status === "expired")
277
- break;
278
- if (r.status === "slow_down")
279
- interval += 5;
280
- await sleep(interval * 1000);
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 };
281
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);
282
334
  }
283
- return { tokens, claimed: false };
284
335
  }
285
336
  var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
286
337
  var init_loginFlow = () => {};
287
338
 
339
+ // src/ui/session.ts
340
+ function jwtClaim(jwt, key) {
341
+ try {
342
+ return JSON.parse(Buffer.from(jwt.split(".")[1], "base64url").toString("utf8"))[key];
343
+ } catch {
344
+ return;
345
+ }
346
+ }
347
+ function sessionFromTokens(tokens, email) {
348
+ const sub = jwtClaim(tokens.accessToken, "sub");
349
+ const exp = jwtClaim(tokens.accessToken, "exp");
350
+ return {
351
+ ...tokens,
352
+ user: { id: typeof sub === "string" ? sub : email, email },
353
+ accessTokenExpiresAt: typeof exp === "number" ? new Date(exp * 1000).toISOString() : tokens.accessTokenExpiresAt
354
+ };
355
+ }
356
+
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
+ // src/ui/theme.ts
369
+ var color;
370
+ var init_theme = __esm(() => {
371
+ color = {
372
+ brand: "magenta",
373
+ accent: "cyan",
374
+ ok: "green",
375
+ warn: "yellow",
376
+ err: "red",
377
+ dim: "gray"
378
+ };
379
+ });
380
+
381
+ // src/ui/Login.tsx
382
+ import { useEffect, useRef, useState } from "react";
383
+ import { Box, Text } from "ink";
384
+ import { Spinner, StatusMessage, TextInput, Alert, Badge } from "@inkjs/ui";
385
+ import { jsx, jsxs } from "react/jsx-runtime";
386
+ function Login({
387
+ apiBase,
388
+ initialEmail,
389
+ onExit
390
+ }) {
391
+ const [phase, setPhase] = useState(initialEmail ? { t: "connecting" } : { t: "email" });
392
+ const opened = useRef(false);
393
+ 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) => {
417
+ setPhase({ t: "error", message: err?.message ?? String(err) });
418
+ setTimeout(() => onExit(1), 800);
419
+ });
420
+ };
421
+ 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);
429
+ }
430
+ }, [phase]);
431
+ return /* @__PURE__ */ jsxs(Box, {
432
+ flexDirection: "column",
433
+ gap: 1,
434
+ paddingY: 1,
435
+ children: [
436
+ /* @__PURE__ */ jsxs(Text, {
437
+ children: [
438
+ /* @__PURE__ */ jsx(Text, {
439
+ color: color.brand,
440
+ bold: true,
441
+ children: "vibegroup"
442
+ }),
443
+ /* @__PURE__ */ jsx(Text, {
444
+ dimColor: true,
445
+ children: " · sign in"
446
+ })
447
+ ]
448
+ }),
449
+ phase.t === "email" && /* @__PURE__ */ jsxs(Box, {
450
+ flexDirection: "column",
451
+ children: [
452
+ /* @__PURE__ */ jsx(Text, {
453
+ children: "Email to sign in with:"
454
+ }),
455
+ /* @__PURE__ */ jsx(Box, {
456
+ marginTop: 1,
457
+ children: /* @__PURE__ */ jsx(TextInput, {
458
+ placeholder: "you@company.com",
459
+ onSubmit: (value) => {
460
+ const email = value.trim();
461
+ if (email)
462
+ start(email);
463
+ else
464
+ onExit(1);
465
+ }
466
+ })
467
+ })
468
+ ]
469
+ }),
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}…`
475
+ }),
476
+ phase.t === "waiting" && /* @__PURE__ */ jsxs(Box, {
477
+ flexDirection: "column",
478
+ gap: 1,
479
+ children: [
480
+ /* @__PURE__ */ jsxs(Box, {
481
+ borderStyle: "round",
482
+ borderColor: color.accent,
483
+ paddingX: 1,
484
+ flexDirection: "column",
485
+ 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
+ }),
496
+ /* @__PURE__ */ jsx(Text, {
497
+ color: color.accent,
498
+ children: phase.url
499
+ }),
500
+ /* @__PURE__ */ jsx(Text, {
501
+ dimColor: true,
502
+ children: "(the code goes into that page — not back here)"
503
+ })
504
+ ]
505
+ }),
506
+ /* @__PURE__ */ jsx(Spinner, {
507
+ label: "Waiting for you to confirm in the browser…"
508
+ })
509
+ ]
510
+ }),
511
+ phase.t === "done" && /* @__PURE__ */ jsxs(StatusMessage, {
512
+ variant: "success",
513
+ children: [
514
+ "Signed in as ",
515
+ phase.email,
516
+ ". You're good to go!"
517
+ ]
518
+ }),
519
+ phase.t === "error" && /* @__PURE__ */ jsx(Alert, {
520
+ variant: "error",
521
+ children: phase.message
522
+ })
523
+ ]
524
+ });
525
+ }
526
+ var init_Login = __esm(() => {
527
+ init_loginFlow();
528
+ init_auth();
529
+ init_openBrowser();
530
+ init_theme();
531
+ });
532
+
288
533
  // src/commands/login.ts
289
534
  var exports_login = {};
290
535
  __export(exports_login, {
291
536
  loginCommand: () => loginCommand
292
537
  });
293
- import { createInterface } from "node:readline/promises";
294
- async function ask(question) {
295
- const rl = createInterface({ input: process.stdin, output: process.stdout });
296
- try {
297
- return (await rl.question(question)).trim();
298
- } finally {
299
- rl.close();
300
- }
538
+ import { createElement } from "react";
539
+ async function loginCommand(rest, env = process.env) {
540
+ const base = apiBase(env);
541
+ return runInk((onExit) => createElement(Login, { apiBase: base, initialEmail: rest[0], onExit }));
301
542
  }
302
- async function confirm(question) {
303
- return /^y(es)?$/i.test(await ask(`${question} [y/N] `));
304
- }
305
- function render(e) {
306
- switch (e.type) {
307
- case "discovering":
308
- console.log(`
309
- Checking ${e.apiBase}/auth.md for instructions`);
310
- break;
311
- case "discovered":
312
- console.log(" └ Found agent_auth · anonymous registration supported · claim to unlock full access");
313
- break;
314
- case "registering":
315
- console.log("• Registering with vibegroup");
316
- break;
317
- case "registered":
318
- console.log(" 200 OK · registered, acting with limited access");
319
- break;
320
- case "claim":
321
- console.log(`
322
- To unlock your account, sign in at vibegroup and enter this code: ${e.ceremony.userCode}`);
323
- console.log(`${e.ceremony.verificationUri} — the code goes in there, not back to me.`);
324
- break;
325
- case "polling":
326
- console.log(`
327
- Waiting for you to confirm…`);
328
- break;
329
- case "unlocked":
330
- console.log(" └ Unlocked · full access");
331
- break;
332
- }
543
+ var init_login = __esm(() => {
544
+ init_runner();
545
+ init_Login();
546
+ init_cli();
547
+ });
548
+
549
+ // src/commands/init.ts
550
+ var exports_init = {};
551
+ __export(exports_init, {
552
+ initCommand: () => initCommand
553
+ });
554
+ import { spawnSync } from "node:child_process";
555
+ 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)), "..");
333
560
  }
334
- async function loginCommand(env = process.env) {
335
- const base = apiBase(env);
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
+ }
568
+ function readManagedSettings() {
336
569
  try {
337
- const result = await login(base, fetch, {
338
- consent: ({ resource }) => confirm(`Allow agent to register for vibegroup (${resource}) on your behalf?`),
339
- emailFor: async () => {
340
- const email = await ask("Email to link your account (leave blank to stay anonymous): ");
341
- return email || null;
342
- },
343
- onEvent: render
344
- });
345
- if (!result) {
346
- console.log("Cancelled nothing was registered.");
570
+ const p = managedSettingsPath();
571
+ if (existsSync3(p))
572
+ return JSON.parse(readFileSync3(p, "utf8"));
573
+ } catch {}
574
+ return null;
575
+ }
576
+ function writeAllowlistWithSudo() {
577
+ 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"] });
581
+ return r.status === 0;
582
+ }
583
+ async function initCommand(rest, flags = {}, env = process.env) {
584
+ const dev = flags.dev === true;
585
+ console.log(`vibegroup setup
586
+ `);
587
+ if (!commandExists("claude")) {
588
+ console.error("Claude Code (`claude`) is not on your PATH. Install it first: https://docs.claude.com/claude-code");
589
+ return 1;
590
+ }
591
+ if (pluginInstalled()) {
592
+ console.log("✓ Plugin already installed.");
593
+ } else {
594
+ 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");
347
599
  return 1;
348
600
  }
349
- writeAuth(result.tokens, env);
350
- console.log(result.claimed ? `
351
- Logged in. You're good to go!` : "\nRegistered with limited access. Run `vibegroup login` again anytime to link your account and unlock full access.");
352
- return 0;
353
- } catch (err) {
354
- console.error(`
355
- login failed: ${err.message}`);
356
- return 1;
601
+ console.log("✓ Plugin installed.");
357
602
  }
603
+ if (dev) {
604
+ console.log("• Dev mode: skipping the channel allowlist (launch with `vibegroup claude --dev`).");
605
+ } else if (isAllowlisted(readManagedSettings())) {
606
+ console.log("✓ Channel already enabled.");
607
+ } else {
608
+ console.log(`
609
+ • Enabling the vibegroup channel needs admin once (Claude Code gates channel plugins).`);
610
+ console.log(` Writing ${managedSettingsPath()} — enter your password if prompted.`);
611
+ if (!writeAllowlistWithSudo()) {
612
+ console.error(" Could not write managed settings. Retry, or run `vibegroup init --dev` to skip it and use `vibegroup claude --dev`.");
613
+ return 1;
614
+ }
615
+ console.log("✓ Channel enabled.");
616
+ }
617
+ if (isLoggedIn(readAuth(env))) {
618
+ console.log("✓ Already signed in.");
619
+ } else {
620
+ console.log(`
621
+ • Sign in to vibegroup:`);
622
+ const code = await loginCommand(rest, env);
623
+ if (code !== 0)
624
+ return code;
625
+ }
626
+ console.log(`
627
+ ✓ Setup complete. Next:`);
628
+ console.log(' vibegroup team create <slug> --name "<name>" # if you need a team');
629
+ console.log(" vibegroup claude --team <slug> # launch a channel session");
630
+ return 0;
358
631
  }
359
- var init_login = __esm(() => {
360
- init_loginFlow();
632
+ var init_init = __esm(() => {
361
633
  init_auth();
362
- init_cli();
634
+ init_allowlist();
635
+ init_login();
363
636
  });
364
637
 
365
638
  // src/lib/apiClient.ts
@@ -480,7 +753,7 @@ var init_team = __esm(() => {
480
753
  });
481
754
 
482
755
  // src/lib/claudeLaunch.ts
483
- import { spawnSync } from "node:child_process";
756
+ import { spawnSync as spawnSync2 } from "node:child_process";
484
757
  function buildClaudeArgs(opts = {}) {
485
758
  const extra = opts.extraArgs ?? [];
486
759
  return opts.dangerously ? ["--dangerously-load-development-channels", CHANNEL_SPEC, ...extra] : ["--channels", CHANNEL_SPEC, ...extra];
@@ -489,7 +762,7 @@ function launchClaude(opts = {}, launcher = defaultLauncher) {
489
762
  return launcher("claude", buildClaudeArgs(opts), opts.env);
490
763
  }
491
764
  var CHANNEL_SPEC = "plugin:vibegroup@vibegroup", defaultLauncher = (cmd, args, env) => {
492
- const r = spawnSync(cmd, args, { stdio: "inherit", env: env ? { ...process.env, ...env } : process.env });
765
+ const r = spawnSync2(cmd, args, { stdio: "inherit", env: env ? { ...process.env, ...env } : process.env });
493
766
  if (r.error)
494
767
  throw r.error;
495
768
  return r.status ?? 0;
@@ -620,9 +893,13 @@ async function run(argv, env = process.env) {
620
893
  const { allowlistPathCommand: allowlistPathCommand2 } = await Promise.resolve().then(() => (init_allowlist2(), exports_allowlist));
621
894
  return allowlistPathCommand2();
622
895
  }
896
+ case "init": {
897
+ const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), exports_init));
898
+ return initCommand2(rest, flags, env);
899
+ }
623
900
  case "login": {
624
901
  const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
625
- return loginCommand2(env);
902
+ return loginCommand2(rest, env);
626
903
  }
627
904
  case "team": {
628
905
  const { teamCommand: teamCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
@@ -647,12 +924,13 @@ async function run(argv, env = process.env) {
647
924
  return 1;
648
925
  }
649
926
  }
650
- var VERSION = "0.1.0", DEFAULT_API_BASE2 = "https://api.vibegroup.sh", HELP = `vibegroup — talk to your teammates' Claude Code agents.
927
+ var VERSION, DEFAULT_API_BASE2 = "https://api.vibegroup.sh", HELP = `vibegroup — talk to your teammates' Claude Code agents.
651
928
 
652
929
  Usage: vibegroup <command> [options]
653
930
 
654
931
  Commands:
655
- login Sign in / sign up (opens your browser)
932
+ init [email] One-time setup: install the plugin, enable the channel, sign in
933
+ login [email] Sign in / sign up (opens your browser, verifies email)
656
934
  logout Clear the cached session
657
935
  status Show your auth + connection status
658
936
  team create <slug> [--name] Create a team (a WorkOS org + a general room)
@@ -665,6 +943,8 @@ Commands:
665
943
  Run a command with --help for more.`;
666
944
  var init_cli = __esm(() => {
667
945
  init_auth();
946
+ init_package();
947
+ VERSION = package_default.version;
668
948
  });
669
949
 
670
950
  // src/bin.ts