vibegroup 0.1.8 → 0.1.9

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 (2) hide show
  1. package/dist/cli.js +355 -105
  2. package/package.json +1 -1
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.9",
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")
@@ -266,25 +469,6 @@ var init_channel = __esm(() => {
266
469
  init_allowlist();
267
470
  });
268
471
 
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
472
  // src/lib/emailAuth.ts
289
473
  async function startEmailLogin(apiBase, email, f) {
290
474
  const res = await f(`${apiBase}/auth/email/start`, {
@@ -328,30 +512,17 @@ function sessionFromTokens(tokens, email) {
328
512
  };
329
513
  }
330
514
 
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
515
  // src/ui/Login.tsx
345
- import { useEffect, useRef, useState } from "react";
346
- import { Box, Text } from "ink";
516
+ import { useEffect, useRef, useState as useState2 } from "react";
517
+ import { Box as Box2, Text as Text2 } from "ink";
347
518
  import { Spinner, StatusMessage, TextInput, Alert } from "@inkjs/ui";
348
- import { jsx, jsxs } from "react/jsx-runtime";
519
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
349
520
  function Login({
350
521
  apiBase,
351
522
  initialEmail,
352
523
  onExit
353
524
  }) {
354
- const [phase, setPhase] = useState(initialEmail ? { t: "sending", email: initialEmail } : { t: "email" });
525
+ const [phase, setPhase] = useState2(initialEmail ? { t: "sending", email: initialEmail } : { t: "email" });
355
526
  const started = useRef(false);
356
527
  const send = async (email) => {
357
528
  setPhase({ t: "sending", email });
@@ -380,33 +551,33 @@ function Login({
380
551
  send(initialEmail);
381
552
  }
382
553
  }, []);
383
- return /* @__PURE__ */ jsxs(Box, {
554
+ return /* @__PURE__ */ jsxs2(Box2, {
384
555
  flexDirection: "column",
385
556
  gap: 1,
386
557
  paddingY: 1,
387
558
  children: [
388
- /* @__PURE__ */ jsxs(Text, {
559
+ /* @__PURE__ */ jsxs2(Text2, {
389
560
  children: [
390
- /* @__PURE__ */ jsx(Text, {
561
+ /* @__PURE__ */ jsx2(Text2, {
391
562
  color: color.brand,
392
563
  bold: true,
393
564
  children: "vibegroup"
394
565
  }),
395
- /* @__PURE__ */ jsx(Text, {
566
+ /* @__PURE__ */ jsx2(Text2, {
396
567
  dimColor: true,
397
568
  children: " · sign in"
398
569
  })
399
570
  ]
400
571
  }),
401
- phase.t === "email" && /* @__PURE__ */ jsxs(Box, {
572
+ phase.t === "email" && /* @__PURE__ */ jsxs2(Box2, {
402
573
  flexDirection: "column",
403
574
  children: [
404
- /* @__PURE__ */ jsx(Text, {
575
+ /* @__PURE__ */ jsx2(Text2, {
405
576
  children: "Email to sign in with:"
406
577
  }),
407
- /* @__PURE__ */ jsx(Box, {
578
+ /* @__PURE__ */ jsx2(Box2, {
408
579
  marginTop: 1,
409
- children: /* @__PURE__ */ jsx(TextInput, {
580
+ children: /* @__PURE__ */ jsx2(TextInput, {
410
581
  placeholder: "you@company.com",
411
582
  onSubmit: (value) => {
412
583
  const email = value.trim().toLowerCase();
@@ -419,25 +590,25 @@ function Login({
419
590
  })
420
591
  ]
421
592
  }),
422
- phase.t === "sending" && /* @__PURE__ */ jsx(Spinner, {
593
+ phase.t === "sending" && /* @__PURE__ */ jsx2(Spinner, {
423
594
  label: `Sending a code to ${phase.email}…`
424
595
  }),
425
- phase.t === "code" && /* @__PURE__ */ jsxs(Box, {
596
+ phase.t === "code" && /* @__PURE__ */ jsxs2(Box2, {
426
597
  flexDirection: "column",
427
598
  children: [
428
- /* @__PURE__ */ jsxs(Text, {
599
+ /* @__PURE__ */ jsxs2(Text2, {
429
600
  children: [
430
601
  "Enter the 6-digit code sent to ",
431
- /* @__PURE__ */ jsx(Text, {
602
+ /* @__PURE__ */ jsx2(Text2, {
432
603
  color: color.accent,
433
604
  children: phase.email
434
605
  }),
435
606
  ":"
436
607
  ]
437
608
  }),
438
- /* @__PURE__ */ jsx(Box, {
609
+ /* @__PURE__ */ jsx2(Box2, {
439
610
  marginTop: 1,
440
- children: /* @__PURE__ */ jsx(TextInput, {
611
+ children: /* @__PURE__ */ jsx2(TextInput, {
441
612
  placeholder: "123456",
442
613
  onSubmit: (value) => {
443
614
  const code = value.trim();
@@ -446,9 +617,9 @@ function Login({
446
617
  }
447
618
  })
448
619
  }),
449
- phase.error && /* @__PURE__ */ jsx(Box, {
620
+ phase.error && /* @__PURE__ */ jsx2(Box2, {
450
621
  marginTop: 1,
451
- children: /* @__PURE__ */ jsxs(Text, {
622
+ children: /* @__PURE__ */ jsxs2(Text2, {
452
623
  color: color.err,
453
624
  children: [
454
625
  phase.error,
@@ -458,10 +629,10 @@ function Login({
458
629
  })
459
630
  ]
460
631
  }),
461
- phase.t === "verifying" && /* @__PURE__ */ jsx(Spinner, {
632
+ phase.t === "verifying" && /* @__PURE__ */ jsx2(Spinner, {
462
633
  label: "Verifying…"
463
634
  }),
464
- phase.t === "done" && /* @__PURE__ */ jsxs(StatusMessage, {
635
+ phase.t === "done" && /* @__PURE__ */ jsxs2(StatusMessage, {
465
636
  variant: "success",
466
637
  children: [
467
638
  "Signed in as ",
@@ -469,7 +640,7 @@ function Login({
469
640
  ". You're good to go!"
470
641
  ]
471
642
  }),
472
- phase.t === "error" && /* @__PURE__ */ jsx(Alert, {
643
+ phase.t === "error" && /* @__PURE__ */ jsx2(Alert, {
473
644
  variant: "error",
474
645
  children: phase.message
475
646
  })
@@ -486,10 +657,10 @@ var exports_login = {};
486
657
  __export(exports_login, {
487
658
  loginCommand: () => loginCommand
488
659
  });
489
- import { createElement } from "react";
660
+ import { createElement as createElement2 } from "react";
490
661
  async function loginCommand(rest, env = process.env) {
491
662
  const base = apiBase(env);
492
- return runInk((onExit) => createElement(Login, { apiBase: base, initialEmail: rest[0], onExit }));
663
+ return runInk((onExit) => createElement2(Login, { apiBase: base, initialEmail: rest[0], onExit }));
493
664
  }
494
665
  var init_login = __esm(() => {
495
666
  init_runner();
@@ -557,6 +728,60 @@ var init_init = __esm(() => {
557
728
  init_login();
558
729
  });
559
730
 
731
+ // src/lib/refresh.ts
732
+ function jwtExpMs(token) {
733
+ try {
734
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
735
+ return typeof payload.exp === "number" ? payload.exp * 1000 : null;
736
+ } catch {
737
+ return null;
738
+ }
739
+ }
740
+ function tokenExpired(token, now = Date.now(), skewMs = 30000) {
741
+ const exp = jwtExpMs(token);
742
+ return exp !== null && now >= exp - skewMs;
743
+ }
744
+ async function refreshAccessToken(apiBase2, assertion, f = fetch) {
745
+ const res = await f(`${apiBase2.replace(/\/+$/, "")}/oauth2/token`, {
746
+ method: "POST",
747
+ headers: { "content-type": "application/x-www-form-urlencoded" },
748
+ body: new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion }).toString()
749
+ });
750
+ if (!res.ok)
751
+ throw new Error(`token refresh failed (HTTP ${res.status})`);
752
+ const d = await res.json();
753
+ if (!d.access_token)
754
+ throw new Error("token refresh response missing access_token");
755
+ return { accessToken: d.access_token, expiresIn: d.expires_in ?? 3600, scope: d.scope };
756
+ }
757
+ async function getValidAccessToken(apiBase2, env = process.env, f = fetch, now = Date.now()) {
758
+ const auth = readAuth(env);
759
+ if (!auth?.accessToken)
760
+ return null;
761
+ if (!tokenExpired(auth.accessToken, now))
762
+ return auth.accessToken;
763
+ if (!auth.identityAssertion || tokenExpired(auth.identityAssertion, now, 0))
764
+ return null;
765
+ try {
766
+ const r = await refreshAccessToken(apiBase2, auth.identityAssertion, f);
767
+ const expMs = jwtExpMs(r.accessToken);
768
+ const next = {
769
+ ...auth,
770
+ accessToken: r.accessToken,
771
+ scope: r.scope ?? auth.scope,
772
+ accessTokenExpiresAt: expMs ? new Date(expMs).toISOString() : auth.accessTokenExpiresAt
773
+ };
774
+ writeAuth(next, env);
775
+ return r.accessToken;
776
+ } catch {
777
+ return null;
778
+ }
779
+ }
780
+ var JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
781
+ var init_refresh = __esm(() => {
782
+ init_auth();
783
+ });
784
+
560
785
  // src/lib/apiClient.ts
561
786
  async function call(opts, method, path, body) {
562
787
  const f = opts.fetchImpl ?? fetch;
@@ -607,20 +832,24 @@ __export(exports_team, {
607
832
  roomCommand: () => roomCommand,
608
833
  inviteCommand: () => inviteCommand
609
834
  });
610
- function client(env) {
611
- const auth = readAuth(env);
612
- if (!isLoggedIn(auth)) {
835
+ async function client(env) {
836
+ if (!isLoggedIn(readAuth(env))) {
613
837
  console.error("Not logged in — run `vibegroup login` first.");
614
838
  return null;
615
839
  }
616
- return { apiBase: apiBase(env), token: auth.accessToken };
840
+ const token = await getValidAccessToken(apiBase(env), env);
841
+ if (!token) {
842
+ console.error("Session expired — run `vibegroup login` again.");
843
+ return null;
844
+ }
845
+ return { apiBase: apiBase(env), token };
617
846
  }
618
847
  async function teamCommand(rest, flags, env) {
619
848
  if (rest[0] !== "create" || !rest[1]) {
620
849
  console.error("usage: vibegroup team create <slug> [--name <name>]");
621
850
  return 1;
622
851
  }
623
- const opts = client(env);
852
+ const opts = await client(env);
624
853
  if (!opts)
625
854
  return 1;
626
855
  try {
@@ -639,7 +868,7 @@ async function roomCommand(rest, flags, env) {
639
868
  console.error("usage: vibegroup room create <name> --team <slug>");
640
869
  return 1;
641
870
  }
642
- const opts = client(env);
871
+ const opts = await client(env);
643
872
  if (!opts)
644
873
  return 1;
645
874
  try {
@@ -658,7 +887,7 @@ async function roomsCommand(flags, env) {
658
887
  console.error("usage: vibegroup rooms --team <slug>");
659
888
  return 1;
660
889
  }
661
- const opts = client(env);
890
+ const opts = await client(env);
662
891
  if (!opts)
663
892
  return 1;
664
893
  try {
@@ -678,7 +907,7 @@ async function inviteCommand(rest, flags, env) {
678
907
  console.error("usage: vibegroup invite <email> --team <slug>");
679
908
  return 1;
680
909
  }
681
- const opts = client(env);
910
+ const opts = await client(env);
682
911
  if (!opts)
683
912
  return 1;
684
913
  try {
@@ -693,6 +922,7 @@ async function inviteCommand(rest, flags, env) {
693
922
  var str = (v) => typeof v === "string" ? v : undefined;
694
923
  var init_team = __esm(() => {
695
924
  init_auth();
925
+ init_refresh();
696
926
  init_apiClient();
697
927
  init_cli();
698
928
  });
@@ -729,10 +959,10 @@ function groupPeers(peers, selfMemberId) {
729
959
  var DEFAULT_RELAY_HTTP = "https://relay.vibegroup.sh";
730
960
 
731
961
  // 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";
962
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
963
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
734
964
  import { Spinner as Spinner2 } from "@inkjs/ui";
735
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
965
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
736
966
  function ago(ms) {
737
967
  if (!ms)
738
968
  return "—";
@@ -749,19 +979,24 @@ function Who({
749
979
  relayHttp,
750
980
  team,
751
981
  room,
752
- token,
982
+ getToken,
753
983
  selfMemberId,
754
984
  onExit,
755
985
  intervalMs = 3000
756
986
  }) {
757
- const [people, setPeople] = useState2(null);
758
- const [selected, setSelected] = useState2(0);
759
- const [error, setError] = useState2("");
760
- const [updated, setUpdated] = useState2(0);
987
+ const [people, setPeople] = useState3(null);
988
+ const [selected, setSelected] = useState3(0);
989
+ const [error, setError] = useState3("");
990
+ const [updated, setUpdated] = useState3(0);
761
991
  const selRef = useRef2(0);
762
992
  selRef.current = selected;
763
993
  const refresh = async () => {
764
994
  try {
995
+ const token = await getToken();
996
+ if (!token) {
997
+ setError("Session expired — run `vibegroup login` again.");
998
+ return;
999
+ }
765
1000
  const peers = await fetchPresence(relayHttp, team, room, token, fetch);
766
1001
  const grouped = groupPeers(peers, selfMemberId);
767
1002
  setPeople(grouped);
@@ -777,7 +1012,7 @@ function Who({
777
1012
  const t = setInterval(() => void refresh(), intervalMs);
778
1013
  return () => clearInterval(t);
779
1014
  }, []);
780
- useInput((input, key) => {
1015
+ useInput2((input, key) => {
781
1016
  if (input === "q" || key.escape || key.ctrl && input === "c")
782
1017
  return onExit(0);
783
1018
  if (input === "r")
@@ -791,30 +1026,30 @@ function Who({
791
1026
  setSelected((s) => (s + 1) % n);
792
1027
  });
793
1028
  const sel = people && people.length > 0 ? people[Math.min(selected, people.length - 1)] : undefined;
794
- return /* @__PURE__ */ jsxs2(Box2, {
1029
+ return /* @__PURE__ */ jsxs3(Box3, {
795
1030
  flexDirection: "column",
796
1031
  paddingX: 1,
797
1032
  paddingY: 1,
798
1033
  gap: 1,
799
1034
  children: [
800
- /* @__PURE__ */ jsxs2(Box2, {
1035
+ /* @__PURE__ */ jsxs3(Box3, {
801
1036
  borderStyle: "round",
802
1037
  borderColor: color.brand,
803
1038
  paddingX: 1,
804
1039
  justifyContent: "space-between",
805
1040
  children: [
806
- /* @__PURE__ */ jsxs2(Text2, {
1041
+ /* @__PURE__ */ jsxs3(Text3, {
807
1042
  children: [
808
- /* @__PURE__ */ jsx2(Text2, {
1043
+ /* @__PURE__ */ jsx3(Text3, {
809
1044
  color: color.brand,
810
1045
  bold: true,
811
1046
  children: "vibegroup"
812
1047
  }),
813
- /* @__PURE__ */ jsx2(Text2, {
1048
+ /* @__PURE__ */ jsx3(Text3, {
814
1049
  dimColor: true,
815
1050
  children: " · "
816
1051
  }),
817
- /* @__PURE__ */ jsxs2(Text2, {
1052
+ /* @__PURE__ */ jsxs3(Text3, {
818
1053
  color: color.accent,
819
1054
  children: [
820
1055
  team,
@@ -824,50 +1059,50 @@ function Who({
824
1059
  })
825
1060
  ]
826
1061
  }),
827
- /* @__PURE__ */ jsx2(Text2, {
1062
+ /* @__PURE__ */ jsx3(Text3, {
828
1063
  dimColor: true,
829
1064
  children: people ? `${people.length} ${people.length === 1 ? "person" : "people"}` : ""
830
1065
  })
831
1066
  ]
832
1067
  }),
833
- !people && !error && /* @__PURE__ */ jsx2(Spinner2, {
1068
+ !people && !error && /* @__PURE__ */ jsx3(Spinner2, {
834
1069
  label: "Loading who's here…"
835
1070
  }),
836
- error && /* @__PURE__ */ jsxs2(Text2, {
1071
+ error && /* @__PURE__ */ jsxs3(Text3, {
837
1072
  color: color.err,
838
1073
  children: [
839
1074
  "Couldn't read presence: ",
840
1075
  error
841
1076
  ]
842
1077
  }),
843
- people && people.length === 0 && /* @__PURE__ */ jsx2(Text2, {
1078
+ people && people.length === 0 && /* @__PURE__ */ jsx3(Text3, {
844
1079
  dimColor: true,
845
1080
  children: "No one's in this room yet."
846
1081
  }),
847
- people && people.length > 0 && /* @__PURE__ */ jsx2(Box2, {
1082
+ people && people.length > 0 && /* @__PURE__ */ jsx3(Box3, {
848
1083
  flexDirection: "column",
849
1084
  children: people.map((p, i) => {
850
1085
  const active = i === selected;
851
1086
  const n = p.sessions.length;
852
- return /* @__PURE__ */ jsxs2(Box2, {
1087
+ return /* @__PURE__ */ jsxs3(Box3, {
853
1088
  children: [
854
- /* @__PURE__ */ jsx2(Text2, {
1089
+ /* @__PURE__ */ jsx3(Text3, {
855
1090
  color: active ? color.accent : undefined,
856
1091
  children: active ? "❯ " : " "
857
1092
  }),
858
1093
  dot(p.state),
859
- /* @__PURE__ */ jsxs2(Text2, {
1094
+ /* @__PURE__ */ jsxs3(Text3, {
860
1095
  bold: active,
861
1096
  children: [
862
1097
  " ",
863
1098
  p.name || "unknown"
864
1099
  ]
865
1100
  }),
866
- p.isYou && /* @__PURE__ */ jsx2(Text2, {
1101
+ p.isYou && /* @__PURE__ */ jsx3(Text3, {
867
1102
  color: color.dim,
868
1103
  children: " (you)"
869
1104
  }),
870
- /* @__PURE__ */ jsxs2(Text2, {
1105
+ /* @__PURE__ */ jsxs3(Text3, {
871
1106
  dimColor: true,
872
1107
  children: [
873
1108
  " ",
@@ -882,13 +1117,13 @@ function Who({
882
1117
  }, p.memberId || p.name);
883
1118
  })
884
1119
  }),
885
- sel && /* @__PURE__ */ jsxs2(Box2, {
1120
+ sel && /* @__PURE__ */ jsxs3(Box3, {
886
1121
  flexDirection: "column",
887
1122
  borderStyle: "round",
888
1123
  borderColor: color.dim,
889
1124
  paddingX: 1,
890
1125
  children: [
891
- /* @__PURE__ */ jsxs2(Text2, {
1126
+ /* @__PURE__ */ jsxs3(Text3, {
892
1127
  dimColor: true,
893
1128
  children: [
894
1129
  sel.name,
@@ -898,16 +1133,16 @@ function Who({
898
1133
  sel.sessions.length === 1 ? "session" : "sessions"
899
1134
  ]
900
1135
  }),
901
- sel.sessions.slice().sort((a, b) => b.lastSeen - a.lastSeen).map((s) => /* @__PURE__ */ jsxs2(Box2, {
1136
+ sel.sessions.slice().sort((a, b) => b.lastSeen - a.lastSeen).map((s) => /* @__PURE__ */ jsxs3(Box3, {
902
1137
  children: [
903
1138
  dot(s.state),
904
- /* @__PURE__ */ jsxs2(Text2, {
1139
+ /* @__PURE__ */ jsxs3(Text3, {
905
1140
  children: [
906
1141
  " ",
907
1142
  s.session || shortId(s.peerId)
908
1143
  ]
909
1144
  }),
910
- /* @__PURE__ */ jsxs2(Text2, {
1145
+ /* @__PURE__ */ jsxs3(Text3, {
911
1146
  dimColor: true,
912
1147
  children: [
913
1148
  " ",
@@ -922,7 +1157,7 @@ function Who({
922
1157
  }, s.peerId))
923
1158
  ]
924
1159
  }),
925
- /* @__PURE__ */ jsxs2(Text2, {
1160
+ /* @__PURE__ */ jsxs3(Text3, {
926
1161
  dimColor: true,
927
1162
  children: [
928
1163
  "↑↓ navigate · r refresh · q quit",
@@ -932,10 +1167,10 @@ function Who({
932
1167
  ]
933
1168
  });
934
1169
  }
935
- var dot = (state) => state === "available" ? /* @__PURE__ */ jsx2(Text2, {
1170
+ var dot = (state) => state === "available" ? /* @__PURE__ */ jsx3(Text3, {
936
1171
  color: color.ok,
937
1172
  children: "●"
938
- }) : /* @__PURE__ */ jsx2(Text2, {
1173
+ }) : /* @__PURE__ */ jsx3(Text3, {
939
1174
  color: color.dim,
940
1175
  children: "○"
941
1176
  }), shortId = (peerId) => peerId.split("#")[1]?.slice(0, 8) ?? peerId.slice(0, 8);
@@ -948,7 +1183,7 @@ var exports_who = {};
948
1183
  __export(exports_who, {
949
1184
  whoCommand: () => whoCommand
950
1185
  });
951
- import { createElement as createElement2 } from "react";
1186
+ import { createElement as createElement3 } from "react";
952
1187
  async function whoCommand(flags, env = process.env) {
953
1188
  const auth = readAuth(env);
954
1189
  if (!isLoggedIn(auth)) {
@@ -962,13 +1197,22 @@ async function whoCommand(flags, env = process.env) {
962
1197
  }
963
1198
  const room = str2(flags.room) ?? "general";
964
1199
  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 }));
1200
+ const apiBase2 = env.VIBEGROUP_API ?? "https://api.vibegroup.sh";
1201
+ return runInk((onExit) => createElement3(Who, {
1202
+ relayHttp,
1203
+ team,
1204
+ room,
1205
+ getToken: () => getValidAccessToken(apiBase2, env),
1206
+ selfMemberId: auth.user?.id,
1207
+ onExit
1208
+ }));
966
1209
  }
967
1210
  var str2 = (v) => typeof v === "string" ? v : undefined;
968
1211
  var init_who = __esm(() => {
969
1212
  init_runner();
970
1213
  init_Who();
971
1214
  init_auth();
1215
+ init_refresh();
972
1216
  });
973
1217
 
974
1218
  // src/lib/claudeLaunch.ts
@@ -1015,7 +1259,7 @@ function extractFlag(args, name) {
1015
1259
  }
1016
1260
  return { value, rest };
1017
1261
  }
1018
- function claudeCommand(args, env = process.env, launcher, channel = realChannelGate) {
1262
+ async function claudeCommand(args, env = process.env, launcher, channel = realChannelGate) {
1019
1263
  if (!isLoggedIn(readAuth(env))) {
1020
1264
  console.error("Not logged in — run `vibegroup login` first.");
1021
1265
  return 1;
@@ -1028,6 +1272,10 @@ function claudeCommand(args, env = process.env, launcher, channel = realChannelG
1028
1272
  console.error("No team selected — pass `--team <slug>` (the team whose room you want to join).");
1029
1273
  return 1;
1030
1274
  }
1275
+ if (!await getValidAccessToken(env.VIBEGROUP_API ?? DEFAULT_API_BASE, env)) {
1276
+ console.error("Session expired — run `vibegroup login` again.");
1277
+ return 1;
1278
+ }
1031
1279
  const session = sessionFlag && sessionFlag.length > 0 ? sessionFlag : basename(process.cwd());
1032
1280
  if (!dangerously && !channel.allowlisted()) {
1033
1281
  console.log("Enabling the vibegroup channel — one-time, needs admin. Enter your password if prompted.");
@@ -1048,6 +1296,7 @@ var DEFAULT_API_BASE = "https://api.vibegroup.sh", realChannelGate;
1048
1296
  var init_claudeCmd = __esm(() => {
1049
1297
  init_claudeLaunch();
1050
1298
  init_auth();
1299
+ init_refresh();
1051
1300
  init_channel();
1052
1301
  realChannelGate = { allowlisted: channelAllowlisted, enable: enableChannelWithSudo };
1053
1302
  });
@@ -1104,9 +1353,10 @@ async function run(argv, env = process.env) {
1104
1353
  }
1105
1354
  switch (command) {
1106
1355
  case "":
1107
- case "help":
1108
- console.log(HELP);
1109
- return 0;
1356
+ case "help": {
1357
+ const { homeCommand: homeCommand2 } = await Promise.resolve().then(() => (init_home(), exports_home));
1358
+ return homeCommand2(env, (cmd) => run([cmd], env), VERSION, HELP);
1359
+ }
1110
1360
  case "version":
1111
1361
  console.log(VERSION);
1112
1362
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibegroup",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
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": {