vibegroup 0.1.8 → 0.1.10

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.
Files changed (3) hide show
  1. package/README.md +6 -9
  2. package/dist/cli.js +375 -108
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,21 +12,18 @@ When you and your teammates are each heads-down in your own repo on your own mac
12
12
  ## Install
13
13
 
14
14
  ```bash
15
- npx vibegroup install # registers the vibegroup plugin in Claude Code
15
+ npm i -g vibegroup # installs the `vibegroup` command (the Claude Code plugin ships inside it)
16
+ vibegroup init # one-time: registers the plugin, enables the channel, signs you in
16
17
  ```
17
18
 
18
- Or install the CLI globally:
19
-
20
- ```bash
21
- npm i -g vibegroup
22
- ```
19
+ That's the whole setup — `vibegroup init` installs the plugin for you. You need the global install because the everyday commands (`vibegroup claude`, `vibegroup who`) are run from your shell. (`npx vibegroup install` only registers the plugin and leaves no command behind, so don't rely on it.)
23
20
 
24
21
  ## Quickstart
25
22
 
26
23
  ```bash
27
- vibegroup init # one-time: install the plugin, enable the channel, sign in
28
24
  vibegroup team create acme --name Acme # create a team (or get invited to one)
29
25
  vibegroup claude --team acme # launch Claude Code wired to your team's room
26
+ vibegroup who --team acme # see who's in the room (people + their sessions)
30
27
  ```
31
28
 
32
29
  Inside the session, your agent can reach teammates' agents:
@@ -44,8 +41,8 @@ Inside the session, your agent can reach teammates' agents:
44
41
  ## Commands
45
42
 
46
43
  ```
47
- vibegroup install Register the plugin in Claude Code (also: npx vibegroup install)
48
- vibegroup init [email] One-time setup: plugin + channel + sign-in
44
+ vibegroup init [email] One-time setup: plugin + channel + sign-in (start here)
45
+ vibegroup install Re-register just the plugin (init already does this)
49
46
  vibegroup login [email] Sign in / sign up
50
47
  vibegroup logout Clear the cached session
51
48
  vibegroup status Show your auth status
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.8",
73
+ version: "0.1.10",
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: {
@@ -134,6 +134,209 @@ var init_package = __esm(() => {
134
134
  };
135
135
  });
136
136
 
137
+ // src/ui/runner.ts
138
+ import { render } from "ink";
139
+ function runInk(make) {
140
+ return new Promise((resolve) => {
141
+ let done = false;
142
+ let instance;
143
+ const finish = (code) => {
144
+ if (done)
145
+ return;
146
+ done = true;
147
+ instance?.unmount();
148
+ resolve(code);
149
+ };
150
+ instance = render(make(finish));
151
+ instance.waitUntilExit().then(() => finish(130));
152
+ });
153
+ }
154
+ var init_runner = () => {};
155
+
156
+ // src/ui/theme.ts
157
+ var color;
158
+ var init_theme = __esm(() => {
159
+ color = {
160
+ brand: "magenta",
161
+ accent: "cyan",
162
+ ok: "green",
163
+ warn: "yellow",
164
+ err: "red",
165
+ dim: "gray"
166
+ };
167
+ });
168
+
169
+ // src/ui/Home.tsx
170
+ import { useState } from "react";
171
+ import { Box, Text, useInput } from "ink";
172
+ import { jsx, jsxs } from "react/jsx-runtime";
173
+ function Home({
174
+ identity,
175
+ version,
176
+ items = HOME_ITEMS,
177
+ onSelect
178
+ }) {
179
+ const [i, setI] = useState(0);
180
+ useInput((input, key) => {
181
+ if (input === "q" || key.escape || key.ctrl && input === "c")
182
+ return onSelect(null);
183
+ if (key.upArrow)
184
+ setI((s) => (s - 1 + items.length) % items.length);
185
+ if (key.downArrow)
186
+ setI((s) => (s + 1) % items.length);
187
+ if (key.return)
188
+ return onSelect(items[i].cmd);
189
+ });
190
+ const sel = items[i];
191
+ return /* @__PURE__ */ jsxs(Box, {
192
+ flexDirection: "column",
193
+ paddingX: 1,
194
+ paddingY: 1,
195
+ gap: 1,
196
+ children: [
197
+ /* @__PURE__ */ jsxs(Box, {
198
+ borderStyle: "round",
199
+ borderColor: color.brand,
200
+ paddingX: 1,
201
+ flexDirection: "column",
202
+ children: [
203
+ /* @__PURE__ */ jsxs(Text, {
204
+ children: [
205
+ /* @__PURE__ */ jsx(Text, {
206
+ color: color.brand,
207
+ bold: true,
208
+ children: "vibegroup"
209
+ }),
210
+ /* @__PURE__ */ jsx(Text, {
211
+ dimColor: true,
212
+ children: " talk to your teammates' Claude Code agents"
213
+ })
214
+ ]
215
+ }),
216
+ /* @__PURE__ */ jsxs(Text, {
217
+ dimColor: true,
218
+ children: [
219
+ identity ? `signed in as ${identity}` : "not signed in — start with init or login",
220
+ " · ",
221
+ "v",
222
+ version
223
+ ]
224
+ })
225
+ ]
226
+ }),
227
+ /* @__PURE__ */ jsx(Box, {
228
+ flexDirection: "column",
229
+ children: items.map((it, idx) => {
230
+ const active = idx === i;
231
+ return /* @__PURE__ */ jsxs(Box, {
232
+ children: [
233
+ /* @__PURE__ */ jsx(Text, {
234
+ color: active ? color.accent : undefined,
235
+ children: active ? "❯ " : " "
236
+ }),
237
+ /* @__PURE__ */ jsx(Box, {
238
+ width: 14,
239
+ children: /* @__PURE__ */ jsx(Text, {
240
+ bold: active,
241
+ color: active ? color.accent : undefined,
242
+ children: it.label
243
+ })
244
+ }),
245
+ /* @__PURE__ */ jsx(Text, {
246
+ dimColor: true,
247
+ children: it.desc
248
+ })
249
+ ]
250
+ }, it.cmd);
251
+ })
252
+ }),
253
+ /* @__PURE__ */ jsx(Box, {
254
+ borderStyle: "round",
255
+ borderColor: color.dim,
256
+ paddingX: 1,
257
+ children: /* @__PURE__ */ jsxs(Text, {
258
+ children: [
259
+ /* @__PURE__ */ jsxs(Text, {
260
+ dimColor: true,
261
+ children: [
262
+ sel.runnable ? "runs" : "type",
263
+ ": "
264
+ ]
265
+ }),
266
+ /* @__PURE__ */ jsx(Text, {
267
+ color: color.accent,
268
+ children: sel.usage
269
+ })
270
+ ]
271
+ })
272
+ }),
273
+ /* @__PURE__ */ jsxs(Text, {
274
+ dimColor: true,
275
+ children: [
276
+ "↑↓ navigate · ⏎ ",
277
+ sel.runnable ? "run" : "show command",
278
+ " · q quit"
279
+ ]
280
+ })
281
+ ]
282
+ });
283
+ }
284
+ var HOME_ITEMS;
285
+ var init_Home = __esm(() => {
286
+ init_theme();
287
+ HOME_ITEMS = [
288
+ { cmd: "init", label: "init", desc: "One-time setup: install plugin, enable channel, sign in", usage: "vibegroup init [email]", runnable: true },
289
+ { cmd: "who", label: "who", desc: "Live view of who's in a room (people + sessions)", usage: "vibegroup who --team <slug> [--room <name>]", runnable: false },
290
+ { cmd: "claude", label: "claude", desc: "Launch Claude Code wired to a room", usage: "vibegroup claude --team <slug> [--room <name>] [--session <label>]", runnable: false },
291
+ { cmd: "status", label: "status", desc: "Your auth + connection status", usage: "vibegroup status", runnable: true },
292
+ { cmd: "team", label: "team create", desc: "Create a team (a WorkOS org + a general room)", usage: "vibegroup team create <slug> [--name <name>]", runnable: false },
293
+ { cmd: "room", label: "room create", desc: "Add a room to a team", usage: "vibegroup room create <name> --team <slug>", runnable: false },
294
+ { cmd: "rooms", label: "rooms", desc: "List a team's rooms", usage: "vibegroup rooms --team <slug>", runnable: false },
295
+ { cmd: "invite", label: "invite", desc: "Invite someone to a team", usage: "vibegroup invite <email> --team <slug>", runnable: false },
296
+ { cmd: "login", label: "login", desc: "Sign in / sign up (email code)", usage: "vibegroup login [email]", runnable: true },
297
+ { cmd: "logout", label: "logout", desc: "Clear the cached session", usage: "vibegroup logout", runnable: true },
298
+ { cmd: "install", label: "install", desc: "Register the vibegroup plugin in Claude Code", usage: "vibegroup install", runnable: true }
299
+ ];
300
+ });
301
+
302
+ // src/commands/home.ts
303
+ var exports_home = {};
304
+ __export(exports_home, {
305
+ homeCommand: () => homeCommand
306
+ });
307
+ import { createElement } from "react";
308
+ async function homeCommand(env, dispatch, version, help) {
309
+ if (!process.stdout.isTTY) {
310
+ console.log(help);
311
+ return 0;
312
+ }
313
+ const identity = readAuth(env)?.user?.email ?? null;
314
+ let chosen = null;
315
+ await runInk((onExit) => createElement(Home, {
316
+ identity,
317
+ version,
318
+ onSelect: (cmd) => {
319
+ chosen = cmd;
320
+ onExit(0);
321
+ }
322
+ }));
323
+ if (!chosen)
324
+ return 0;
325
+ const item = HOME_ITEMS.find((it) => it.cmd === chosen);
326
+ if (item && !item.runnable) {
327
+ console.log(`
328
+ Run:
329
+ ${item.usage}`);
330
+ return 0;
331
+ }
332
+ return dispatch(chosen);
333
+ }
334
+ var init_home = __esm(() => {
335
+ init_runner();
336
+ init_Home();
337
+ init_auth();
338
+ });
339
+
137
340
  // src/lib/allowlist.ts
138
341
  function managedSettingsPath(plat = process.platform) {
139
342
  if (plat === "darwin")
@@ -193,6 +396,9 @@ import { dirname as dirname2, join as join2 } from "node:path";
193
396
  function packageRoot() {
194
397
  return join2(dirname2(fileURLToPath(import.meta.url)), "..");
195
398
  }
399
+ function runningViaNpx(scriptPath = fileURLToPath(import.meta.url)) {
400
+ return /[\\/]_npx[\\/]/.test(scriptPath);
401
+ }
196
402
  function claudeAvailable() {
197
403
  try {
198
404
  return spawnSync("claude", ["--version"], { stdio: "ignore" }).status === 0;
@@ -233,7 +439,18 @@ async function installCommand() {
233
439
  console.error(r.message);
234
440
  return 1;
235
441
  }
236
- console.log("\n✓ Plugin installed. Next: `vibegroup init` to enable the channel + sign in.");
442
+ console.log(`
443
+ ✓ Plugin installed.`);
444
+ if (runningViaNpx()) {
445
+ console.log("\nOne more step — the `vibegroup` command isn't on your PATH (you ran this via npx).");
446
+ console.log(`Install it so you can run the rest:
447
+ `);
448
+ console.log(" npm i -g vibegroup");
449
+ console.log(`
450
+ Then finish setup: vibegroup init`);
451
+ } else {
452
+ console.log("Next: `vibegroup init` to enable the channel + sign in.");
453
+ }
237
454
  return 0;
238
455
  }
239
456
  var init_install = __esm(() => {
@@ -266,25 +483,6 @@ var init_channel = __esm(() => {
266
483
  init_allowlist();
267
484
  });
268
485
 
269
- // src/ui/runner.ts
270
- import { render } from "ink";
271
- function runInk(make) {
272
- return new Promise((resolve) => {
273
- let done = false;
274
- let instance;
275
- const finish = (code) => {
276
- if (done)
277
- return;
278
- done = true;
279
- instance?.unmount();
280
- resolve(code);
281
- };
282
- instance = render(make(finish));
283
- instance.waitUntilExit().then(() => finish(130));
284
- });
285
- }
286
- var init_runner = () => {};
287
-
288
486
  // src/lib/emailAuth.ts
289
487
  async function startEmailLogin(apiBase, email, f) {
290
488
  const res = await f(`${apiBase}/auth/email/start`, {
@@ -328,30 +526,17 @@ function sessionFromTokens(tokens, email) {
328
526
  };
329
527
  }
330
528
 
331
- // src/ui/theme.ts
332
- var color;
333
- var init_theme = __esm(() => {
334
- color = {
335
- brand: "magenta",
336
- accent: "cyan",
337
- ok: "green",
338
- warn: "yellow",
339
- err: "red",
340
- dim: "gray"
341
- };
342
- });
343
-
344
529
  // src/ui/Login.tsx
345
- import { useEffect, useRef, useState } from "react";
346
- import { Box, Text } from "ink";
530
+ import { useEffect, useRef, useState as useState2 } from "react";
531
+ import { Box as Box2, Text as Text2 } from "ink";
347
532
  import { Spinner, StatusMessage, TextInput, Alert } from "@inkjs/ui";
348
- import { jsx, jsxs } from "react/jsx-runtime";
533
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
349
534
  function Login({
350
535
  apiBase,
351
536
  initialEmail,
352
537
  onExit
353
538
  }) {
354
- const [phase, setPhase] = useState(initialEmail ? { t: "sending", email: initialEmail } : { t: "email" });
539
+ const [phase, setPhase] = useState2(initialEmail ? { t: "sending", email: initialEmail } : { t: "email" });
355
540
  const started = useRef(false);
356
541
  const send = async (email) => {
357
542
  setPhase({ t: "sending", email });
@@ -380,33 +565,33 @@ function Login({
380
565
  send(initialEmail);
381
566
  }
382
567
  }, []);
383
- return /* @__PURE__ */ jsxs(Box, {
568
+ return /* @__PURE__ */ jsxs2(Box2, {
384
569
  flexDirection: "column",
385
570
  gap: 1,
386
571
  paddingY: 1,
387
572
  children: [
388
- /* @__PURE__ */ jsxs(Text, {
573
+ /* @__PURE__ */ jsxs2(Text2, {
389
574
  children: [
390
- /* @__PURE__ */ jsx(Text, {
575
+ /* @__PURE__ */ jsx2(Text2, {
391
576
  color: color.brand,
392
577
  bold: true,
393
578
  children: "vibegroup"
394
579
  }),
395
- /* @__PURE__ */ jsx(Text, {
580
+ /* @__PURE__ */ jsx2(Text2, {
396
581
  dimColor: true,
397
582
  children: " · sign in"
398
583
  })
399
584
  ]
400
585
  }),
401
- phase.t === "email" && /* @__PURE__ */ jsxs(Box, {
586
+ phase.t === "email" && /* @__PURE__ */ jsxs2(Box2, {
402
587
  flexDirection: "column",
403
588
  children: [
404
- /* @__PURE__ */ jsx(Text, {
589
+ /* @__PURE__ */ jsx2(Text2, {
405
590
  children: "Email to sign in with:"
406
591
  }),
407
- /* @__PURE__ */ jsx(Box, {
592
+ /* @__PURE__ */ jsx2(Box2, {
408
593
  marginTop: 1,
409
- children: /* @__PURE__ */ jsx(TextInput, {
594
+ children: /* @__PURE__ */ jsx2(TextInput, {
410
595
  placeholder: "you@company.com",
411
596
  onSubmit: (value) => {
412
597
  const email = value.trim().toLowerCase();
@@ -419,25 +604,25 @@ function Login({
419
604
  })
420
605
  ]
421
606
  }),
422
- phase.t === "sending" && /* @__PURE__ */ jsx(Spinner, {
607
+ phase.t === "sending" && /* @__PURE__ */ jsx2(Spinner, {
423
608
  label: `Sending a code to ${phase.email}…`
424
609
  }),
425
- phase.t === "code" && /* @__PURE__ */ jsxs(Box, {
610
+ phase.t === "code" && /* @__PURE__ */ jsxs2(Box2, {
426
611
  flexDirection: "column",
427
612
  children: [
428
- /* @__PURE__ */ jsxs(Text, {
613
+ /* @__PURE__ */ jsxs2(Text2, {
429
614
  children: [
430
615
  "Enter the 6-digit code sent to ",
431
- /* @__PURE__ */ jsx(Text, {
616
+ /* @__PURE__ */ jsx2(Text2, {
432
617
  color: color.accent,
433
618
  children: phase.email
434
619
  }),
435
620
  ":"
436
621
  ]
437
622
  }),
438
- /* @__PURE__ */ jsx(Box, {
623
+ /* @__PURE__ */ jsx2(Box2, {
439
624
  marginTop: 1,
440
- children: /* @__PURE__ */ jsx(TextInput, {
625
+ children: /* @__PURE__ */ jsx2(TextInput, {
441
626
  placeholder: "123456",
442
627
  onSubmit: (value) => {
443
628
  const code = value.trim();
@@ -446,9 +631,9 @@ function Login({
446
631
  }
447
632
  })
448
633
  }),
449
- phase.error && /* @__PURE__ */ jsx(Box, {
634
+ phase.error && /* @__PURE__ */ jsx2(Box2, {
450
635
  marginTop: 1,
451
- children: /* @__PURE__ */ jsxs(Text, {
636
+ children: /* @__PURE__ */ jsxs2(Text2, {
452
637
  color: color.err,
453
638
  children: [
454
639
  phase.error,
@@ -458,10 +643,10 @@ function Login({
458
643
  })
459
644
  ]
460
645
  }),
461
- phase.t === "verifying" && /* @__PURE__ */ jsx(Spinner, {
646
+ phase.t === "verifying" && /* @__PURE__ */ jsx2(Spinner, {
462
647
  label: "Verifying…"
463
648
  }),
464
- phase.t === "done" && /* @__PURE__ */ jsxs(StatusMessage, {
649
+ phase.t === "done" && /* @__PURE__ */ jsxs2(StatusMessage, {
465
650
  variant: "success",
466
651
  children: [
467
652
  "Signed in as ",
@@ -469,7 +654,7 @@ function Login({
469
654
  ". You're good to go!"
470
655
  ]
471
656
  }),
472
- phase.t === "error" && /* @__PURE__ */ jsx(Alert, {
657
+ phase.t === "error" && /* @__PURE__ */ jsx2(Alert, {
473
658
  variant: "error",
474
659
  children: phase.message
475
660
  })
@@ -486,10 +671,10 @@ var exports_login = {};
486
671
  __export(exports_login, {
487
672
  loginCommand: () => loginCommand
488
673
  });
489
- import { createElement } from "react";
674
+ import { createElement as createElement2 } from "react";
490
675
  async function loginCommand(rest, env = process.env) {
491
676
  const base = apiBase(env);
492
- return runInk((onExit) => createElement(Login, { apiBase: base, initialEmail: rest[0], onExit }));
677
+ return runInk((onExit) => createElement2(Login, { apiBase: base, initialEmail: rest[0], onExit }));
493
678
  }
494
679
  var init_login = __esm(() => {
495
680
  init_runner();
@@ -557,6 +742,60 @@ var init_init = __esm(() => {
557
742
  init_login();
558
743
  });
559
744
 
745
+ // src/lib/refresh.ts
746
+ function jwtExpMs(token) {
747
+ try {
748
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
749
+ return typeof payload.exp === "number" ? payload.exp * 1000 : null;
750
+ } catch {
751
+ return null;
752
+ }
753
+ }
754
+ function tokenExpired(token, now = Date.now(), skewMs = 30000) {
755
+ const exp = jwtExpMs(token);
756
+ return exp !== null && now >= exp - skewMs;
757
+ }
758
+ async function refreshAccessToken(apiBase2, assertion, f = fetch) {
759
+ const res = await f(`${apiBase2.replace(/\/+$/, "")}/oauth2/token`, {
760
+ method: "POST",
761
+ headers: { "content-type": "application/x-www-form-urlencoded" },
762
+ body: new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion }).toString()
763
+ });
764
+ if (!res.ok)
765
+ throw new Error(`token refresh failed (HTTP ${res.status})`);
766
+ const d = await res.json();
767
+ if (!d.access_token)
768
+ throw new Error("token refresh response missing access_token");
769
+ return { accessToken: d.access_token, expiresIn: d.expires_in ?? 3600, scope: d.scope };
770
+ }
771
+ async function getValidAccessToken(apiBase2, env = process.env, f = fetch, now = Date.now()) {
772
+ const auth = readAuth(env);
773
+ if (!auth?.accessToken)
774
+ return null;
775
+ if (!tokenExpired(auth.accessToken, now))
776
+ return auth.accessToken;
777
+ if (!auth.identityAssertion || tokenExpired(auth.identityAssertion, now, 0))
778
+ return null;
779
+ try {
780
+ const r = await refreshAccessToken(apiBase2, auth.identityAssertion, f);
781
+ const expMs = jwtExpMs(r.accessToken);
782
+ const next = {
783
+ ...auth,
784
+ accessToken: r.accessToken,
785
+ scope: r.scope ?? auth.scope,
786
+ accessTokenExpiresAt: expMs ? new Date(expMs).toISOString() : auth.accessTokenExpiresAt
787
+ };
788
+ writeAuth(next, env);
789
+ return r.accessToken;
790
+ } catch {
791
+ return null;
792
+ }
793
+ }
794
+ var JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
795
+ var init_refresh = __esm(() => {
796
+ init_auth();
797
+ });
798
+
560
799
  // src/lib/apiClient.ts
561
800
  async function call(opts, method, path, body) {
562
801
  const f = opts.fetchImpl ?? fetch;
@@ -607,20 +846,24 @@ __export(exports_team, {
607
846
  roomCommand: () => roomCommand,
608
847
  inviteCommand: () => inviteCommand
609
848
  });
610
- function client(env) {
611
- const auth = readAuth(env);
612
- if (!isLoggedIn(auth)) {
849
+ async function client(env) {
850
+ if (!isLoggedIn(readAuth(env))) {
613
851
  console.error("Not logged in — run `vibegroup login` first.");
614
852
  return null;
615
853
  }
616
- return { apiBase: apiBase(env), token: auth.accessToken };
854
+ const token = await getValidAccessToken(apiBase(env), env);
855
+ if (!token) {
856
+ console.error("Session expired — run `vibegroup login` again.");
857
+ return null;
858
+ }
859
+ return { apiBase: apiBase(env), token };
617
860
  }
618
861
  async function teamCommand(rest, flags, env) {
619
862
  if (rest[0] !== "create" || !rest[1]) {
620
863
  console.error("usage: vibegroup team create <slug> [--name <name>]");
621
864
  return 1;
622
865
  }
623
- const opts = client(env);
866
+ const opts = await client(env);
624
867
  if (!opts)
625
868
  return 1;
626
869
  try {
@@ -639,7 +882,7 @@ async function roomCommand(rest, flags, env) {
639
882
  console.error("usage: vibegroup room create <name> --team <slug>");
640
883
  return 1;
641
884
  }
642
- const opts = client(env);
885
+ const opts = await client(env);
643
886
  if (!opts)
644
887
  return 1;
645
888
  try {
@@ -658,7 +901,7 @@ async function roomsCommand(flags, env) {
658
901
  console.error("usage: vibegroup rooms --team <slug>");
659
902
  return 1;
660
903
  }
661
- const opts = client(env);
904
+ const opts = await client(env);
662
905
  if (!opts)
663
906
  return 1;
664
907
  try {
@@ -678,7 +921,7 @@ async function inviteCommand(rest, flags, env) {
678
921
  console.error("usage: vibegroup invite <email> --team <slug>");
679
922
  return 1;
680
923
  }
681
- const opts = client(env);
924
+ const opts = await client(env);
682
925
  if (!opts)
683
926
  return 1;
684
927
  try {
@@ -693,6 +936,7 @@ async function inviteCommand(rest, flags, env) {
693
936
  var str = (v) => typeof v === "string" ? v : undefined;
694
937
  var init_team = __esm(() => {
695
938
  init_auth();
939
+ init_refresh();
696
940
  init_apiClient();
697
941
  init_cli();
698
942
  });
@@ -729,10 +973,10 @@ function groupPeers(peers, selfMemberId) {
729
973
  var DEFAULT_RELAY_HTTP = "https://relay.vibegroup.sh";
730
974
 
731
975
  // src/ui/Who.tsx
732
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
733
- import { Box as Box2, Text as Text2, useInput } from "ink";
976
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
977
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
734
978
  import { Spinner as Spinner2 } from "@inkjs/ui";
735
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
979
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
736
980
  function ago(ms) {
737
981
  if (!ms)
738
982
  return "—";
@@ -749,19 +993,24 @@ function Who({
749
993
  relayHttp,
750
994
  team,
751
995
  room,
752
- token,
996
+ getToken,
753
997
  selfMemberId,
754
998
  onExit,
755
999
  intervalMs = 3000
756
1000
  }) {
757
- const [people, setPeople] = useState2(null);
758
- const [selected, setSelected] = useState2(0);
759
- const [error, setError] = useState2("");
760
- const [updated, setUpdated] = useState2(0);
1001
+ const [people, setPeople] = useState3(null);
1002
+ const [selected, setSelected] = useState3(0);
1003
+ const [error, setError] = useState3("");
1004
+ const [updated, setUpdated] = useState3(0);
761
1005
  const selRef = useRef2(0);
762
1006
  selRef.current = selected;
763
1007
  const refresh = async () => {
764
1008
  try {
1009
+ const token = await getToken();
1010
+ if (!token) {
1011
+ setError("Session expired — run `vibegroup login` again.");
1012
+ return;
1013
+ }
765
1014
  const peers = await fetchPresence(relayHttp, team, room, token, fetch);
766
1015
  const grouped = groupPeers(peers, selfMemberId);
767
1016
  setPeople(grouped);
@@ -777,7 +1026,7 @@ function Who({
777
1026
  const t = setInterval(() => void refresh(), intervalMs);
778
1027
  return () => clearInterval(t);
779
1028
  }, []);
780
- useInput((input, key) => {
1029
+ useInput2((input, key) => {
781
1030
  if (input === "q" || key.escape || key.ctrl && input === "c")
782
1031
  return onExit(0);
783
1032
  if (input === "r")
@@ -791,30 +1040,30 @@ function Who({
791
1040
  setSelected((s) => (s + 1) % n);
792
1041
  });
793
1042
  const sel = people && people.length > 0 ? people[Math.min(selected, people.length - 1)] : undefined;
794
- return /* @__PURE__ */ jsxs2(Box2, {
1043
+ return /* @__PURE__ */ jsxs3(Box3, {
795
1044
  flexDirection: "column",
796
1045
  paddingX: 1,
797
1046
  paddingY: 1,
798
1047
  gap: 1,
799
1048
  children: [
800
- /* @__PURE__ */ jsxs2(Box2, {
1049
+ /* @__PURE__ */ jsxs3(Box3, {
801
1050
  borderStyle: "round",
802
1051
  borderColor: color.brand,
803
1052
  paddingX: 1,
804
1053
  justifyContent: "space-between",
805
1054
  children: [
806
- /* @__PURE__ */ jsxs2(Text2, {
1055
+ /* @__PURE__ */ jsxs3(Text3, {
807
1056
  children: [
808
- /* @__PURE__ */ jsx2(Text2, {
1057
+ /* @__PURE__ */ jsx3(Text3, {
809
1058
  color: color.brand,
810
1059
  bold: true,
811
1060
  children: "vibegroup"
812
1061
  }),
813
- /* @__PURE__ */ jsx2(Text2, {
1062
+ /* @__PURE__ */ jsx3(Text3, {
814
1063
  dimColor: true,
815
1064
  children: " · "
816
1065
  }),
817
- /* @__PURE__ */ jsxs2(Text2, {
1066
+ /* @__PURE__ */ jsxs3(Text3, {
818
1067
  color: color.accent,
819
1068
  children: [
820
1069
  team,
@@ -824,50 +1073,50 @@ function Who({
824
1073
  })
825
1074
  ]
826
1075
  }),
827
- /* @__PURE__ */ jsx2(Text2, {
1076
+ /* @__PURE__ */ jsx3(Text3, {
828
1077
  dimColor: true,
829
1078
  children: people ? `${people.length} ${people.length === 1 ? "person" : "people"}` : ""
830
1079
  })
831
1080
  ]
832
1081
  }),
833
- !people && !error && /* @__PURE__ */ jsx2(Spinner2, {
1082
+ !people && !error && /* @__PURE__ */ jsx3(Spinner2, {
834
1083
  label: "Loading who's here…"
835
1084
  }),
836
- error && /* @__PURE__ */ jsxs2(Text2, {
1085
+ error && /* @__PURE__ */ jsxs3(Text3, {
837
1086
  color: color.err,
838
1087
  children: [
839
1088
  "Couldn't read presence: ",
840
1089
  error
841
1090
  ]
842
1091
  }),
843
- people && people.length === 0 && /* @__PURE__ */ jsx2(Text2, {
1092
+ people && people.length === 0 && /* @__PURE__ */ jsx3(Text3, {
844
1093
  dimColor: true,
845
1094
  children: "No one's in this room yet."
846
1095
  }),
847
- people && people.length > 0 && /* @__PURE__ */ jsx2(Box2, {
1096
+ people && people.length > 0 && /* @__PURE__ */ jsx3(Box3, {
848
1097
  flexDirection: "column",
849
1098
  children: people.map((p, i) => {
850
1099
  const active = i === selected;
851
1100
  const n = p.sessions.length;
852
- return /* @__PURE__ */ jsxs2(Box2, {
1101
+ return /* @__PURE__ */ jsxs3(Box3, {
853
1102
  children: [
854
- /* @__PURE__ */ jsx2(Text2, {
1103
+ /* @__PURE__ */ jsx3(Text3, {
855
1104
  color: active ? color.accent : undefined,
856
1105
  children: active ? "❯ " : " "
857
1106
  }),
858
1107
  dot(p.state),
859
- /* @__PURE__ */ jsxs2(Text2, {
1108
+ /* @__PURE__ */ jsxs3(Text3, {
860
1109
  bold: active,
861
1110
  children: [
862
1111
  " ",
863
1112
  p.name || "unknown"
864
1113
  ]
865
1114
  }),
866
- p.isYou && /* @__PURE__ */ jsx2(Text2, {
1115
+ p.isYou && /* @__PURE__ */ jsx3(Text3, {
867
1116
  color: color.dim,
868
1117
  children: " (you)"
869
1118
  }),
870
- /* @__PURE__ */ jsxs2(Text2, {
1119
+ /* @__PURE__ */ jsxs3(Text3, {
871
1120
  dimColor: true,
872
1121
  children: [
873
1122
  " ",
@@ -882,13 +1131,13 @@ function Who({
882
1131
  }, p.memberId || p.name);
883
1132
  })
884
1133
  }),
885
- sel && /* @__PURE__ */ jsxs2(Box2, {
1134
+ sel && /* @__PURE__ */ jsxs3(Box3, {
886
1135
  flexDirection: "column",
887
1136
  borderStyle: "round",
888
1137
  borderColor: color.dim,
889
1138
  paddingX: 1,
890
1139
  children: [
891
- /* @__PURE__ */ jsxs2(Text2, {
1140
+ /* @__PURE__ */ jsxs3(Text3, {
892
1141
  dimColor: true,
893
1142
  children: [
894
1143
  sel.name,
@@ -898,16 +1147,16 @@ function Who({
898
1147
  sel.sessions.length === 1 ? "session" : "sessions"
899
1148
  ]
900
1149
  }),
901
- sel.sessions.slice().sort((a, b) => b.lastSeen - a.lastSeen).map((s) => /* @__PURE__ */ jsxs2(Box2, {
1150
+ sel.sessions.slice().sort((a, b) => b.lastSeen - a.lastSeen).map((s) => /* @__PURE__ */ jsxs3(Box3, {
902
1151
  children: [
903
1152
  dot(s.state),
904
- /* @__PURE__ */ jsxs2(Text2, {
1153
+ /* @__PURE__ */ jsxs3(Text3, {
905
1154
  children: [
906
1155
  " ",
907
1156
  s.session || shortId(s.peerId)
908
1157
  ]
909
1158
  }),
910
- /* @__PURE__ */ jsxs2(Text2, {
1159
+ /* @__PURE__ */ jsxs3(Text3, {
911
1160
  dimColor: true,
912
1161
  children: [
913
1162
  " ",
@@ -922,7 +1171,7 @@ function Who({
922
1171
  }, s.peerId))
923
1172
  ]
924
1173
  }),
925
- /* @__PURE__ */ jsxs2(Text2, {
1174
+ /* @__PURE__ */ jsxs3(Text3, {
926
1175
  dimColor: true,
927
1176
  children: [
928
1177
  "↑↓ navigate · r refresh · q quit",
@@ -932,10 +1181,10 @@ function Who({
932
1181
  ]
933
1182
  });
934
1183
  }
935
- var dot = (state) => state === "available" ? /* @__PURE__ */ jsx2(Text2, {
1184
+ var dot = (state) => state === "available" ? /* @__PURE__ */ jsx3(Text3, {
936
1185
  color: color.ok,
937
1186
  children: "●"
938
- }) : /* @__PURE__ */ jsx2(Text2, {
1187
+ }) : /* @__PURE__ */ jsx3(Text3, {
939
1188
  color: color.dim,
940
1189
  children: "○"
941
1190
  }), shortId = (peerId) => peerId.split("#")[1]?.slice(0, 8) ?? peerId.slice(0, 8);
@@ -948,7 +1197,7 @@ var exports_who = {};
948
1197
  __export(exports_who, {
949
1198
  whoCommand: () => whoCommand
950
1199
  });
951
- import { createElement as createElement2 } from "react";
1200
+ import { createElement as createElement3 } from "react";
952
1201
  async function whoCommand(flags, env = process.env) {
953
1202
  const auth = readAuth(env);
954
1203
  if (!isLoggedIn(auth)) {
@@ -962,13 +1211,22 @@ async function whoCommand(flags, env = process.env) {
962
1211
  }
963
1212
  const room = str2(flags.room) ?? "general";
964
1213
  const relayHttp = env.VIBEGROUP_RELAY_HTTP ?? DEFAULT_RELAY_HTTP;
965
- return runInk((onExit) => createElement2(Who, { relayHttp, team, room, token: auth.accessToken, selfMemberId: auth.user?.id, onExit }));
1214
+ const apiBase2 = env.VIBEGROUP_API ?? "https://api.vibegroup.sh";
1215
+ return runInk((onExit) => createElement3(Who, {
1216
+ relayHttp,
1217
+ team,
1218
+ room,
1219
+ getToken: () => getValidAccessToken(apiBase2, env),
1220
+ selfMemberId: auth.user?.id,
1221
+ onExit
1222
+ }));
966
1223
  }
967
1224
  var str2 = (v) => typeof v === "string" ? v : undefined;
968
1225
  var init_who = __esm(() => {
969
1226
  init_runner();
970
1227
  init_Who();
971
1228
  init_auth();
1229
+ init_refresh();
972
1230
  });
973
1231
 
974
1232
  // src/lib/claudeLaunch.ts
@@ -1015,7 +1273,7 @@ function extractFlag(args, name) {
1015
1273
  }
1016
1274
  return { value, rest };
1017
1275
  }
1018
- function claudeCommand(args, env = process.env, launcher, channel = realChannelGate) {
1276
+ async function claudeCommand(args, env = process.env, launcher, channel = realChannelGate) {
1019
1277
  if (!isLoggedIn(readAuth(env))) {
1020
1278
  console.error("Not logged in — run `vibegroup login` first.");
1021
1279
  return 1;
@@ -1028,6 +1286,10 @@ function claudeCommand(args, env = process.env, launcher, channel = realChannelG
1028
1286
  console.error("No team selected — pass `--team <slug>` (the team whose room you want to join).");
1029
1287
  return 1;
1030
1288
  }
1289
+ if (!await getValidAccessToken(env.VIBEGROUP_API ?? DEFAULT_API_BASE, env)) {
1290
+ console.error("Session expired — run `vibegroup login` again.");
1291
+ return 1;
1292
+ }
1031
1293
  const session = sessionFlag && sessionFlag.length > 0 ? sessionFlag : basename(process.cwd());
1032
1294
  if (!dangerously && !channel.allowlisted()) {
1033
1295
  console.log("Enabling the vibegroup channel — one-time, needs admin. Enter your password if prompted.");
@@ -1048,6 +1310,7 @@ var DEFAULT_API_BASE = "https://api.vibegroup.sh", realChannelGate;
1048
1310
  var init_claudeCmd = __esm(() => {
1049
1311
  init_claudeLaunch();
1050
1312
  init_auth();
1313
+ init_refresh();
1051
1314
  init_channel();
1052
1315
  realChannelGate = { allowlisted: channelAllowlisted, enable: enableChannelWithSudo };
1053
1316
  });
@@ -1104,9 +1367,10 @@ async function run(argv, env = process.env) {
1104
1367
  }
1105
1368
  switch (command) {
1106
1369
  case "":
1107
- case "help":
1108
- console.log(HELP);
1109
- return 0;
1370
+ case "help": {
1371
+ const { homeCommand: homeCommand2 } = await Promise.resolve().then(() => (init_home(), exports_home));
1372
+ return homeCommand2(env, (cmd) => run([cmd], env), VERSION, HELP);
1373
+ }
1110
1374
  case "version":
1111
1375
  console.log(VERSION);
1112
1376
  return 0;
@@ -1170,11 +1434,14 @@ async function run(argv, env = process.env) {
1170
1434
  }
1171
1435
  var VERSION, DEFAULT_API_BASE2 = "https://api.vibegroup.sh", HELP = `vibegroup — talk to your teammates' Claude Code agents.
1172
1436
 
1437
+ Get started:
1438
+ npm i -g vibegroup then vibegroup init
1439
+
1173
1440
  Usage: vibegroup <command> [options]
1174
1441
 
1175
1442
  Commands:
1176
- install Register the vibegroup plugin in Claude Code (also: npx vibegroup install)
1177
- init [email] One-time setup: install the plugin, enable the channel, sign in
1443
+ init [email] One-time setup: install the plugin, enable the channel, sign in (start here)
1444
+ install Re-register just the plugin in Claude Code (init already does this)
1178
1445
  login [email] Sign in / sign up (opens your browser, verifies email)
1179
1446
  logout Clear the cached session
1180
1447
  status Show your auth + connection status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibegroup",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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": {