vibegroup 0.1.7 → 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.
- package/dist/cli.js +605 -72
- package/package.json +1 -1
- package/plugin/commands/vibegroup.md +2 -2
- package/plugin/dist/channel.js +10 -9
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,7 @@ var package_default;
|
|
|
70
70
|
var init_package = __esm(() => {
|
|
71
71
|
package_default = {
|
|
72
72
|
name: "vibegroup",
|
|
73
|
-
version: "0.1.
|
|
73
|
+
version: "0.1.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] =
|
|
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__ */
|
|
554
|
+
return /* @__PURE__ */ jsxs2(Box2, {
|
|
384
555
|
flexDirection: "column",
|
|
385
556
|
gap: 1,
|
|
386
557
|
paddingY: 1,
|
|
387
558
|
children: [
|
|
388
|
-
/* @__PURE__ */
|
|
559
|
+
/* @__PURE__ */ jsxs2(Text2, {
|
|
389
560
|
children: [
|
|
390
|
-
/* @__PURE__ */
|
|
561
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
391
562
|
color: color.brand,
|
|
392
563
|
bold: true,
|
|
393
564
|
children: "vibegroup"
|
|
394
565
|
}),
|
|
395
|
-
/* @__PURE__ */
|
|
566
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
396
567
|
dimColor: true,
|
|
397
568
|
children: " · sign in"
|
|
398
569
|
})
|
|
399
570
|
]
|
|
400
571
|
}),
|
|
401
|
-
phase.t === "email" && /* @__PURE__ */
|
|
572
|
+
phase.t === "email" && /* @__PURE__ */ jsxs2(Box2, {
|
|
402
573
|
flexDirection: "column",
|
|
403
574
|
children: [
|
|
404
|
-
/* @__PURE__ */
|
|
575
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
405
576
|
children: "Email to sign in with:"
|
|
406
577
|
}),
|
|
407
|
-
/* @__PURE__ */
|
|
578
|
+
/* @__PURE__ */ jsx2(Box2, {
|
|
408
579
|
marginTop: 1,
|
|
409
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
593
|
+
phase.t === "sending" && /* @__PURE__ */ jsx2(Spinner, {
|
|
423
594
|
label: `Sending a code to ${phase.email}…`
|
|
424
595
|
}),
|
|
425
|
-
phase.t === "code" && /* @__PURE__ */
|
|
596
|
+
phase.t === "code" && /* @__PURE__ */ jsxs2(Box2, {
|
|
426
597
|
flexDirection: "column",
|
|
427
598
|
children: [
|
|
428
|
-
/* @__PURE__ */
|
|
599
|
+
/* @__PURE__ */ jsxs2(Text2, {
|
|
429
600
|
children: [
|
|
430
601
|
"Enter the 6-digit code sent to ",
|
|
431
|
-
/* @__PURE__ */
|
|
602
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
432
603
|
color: color.accent,
|
|
433
604
|
children: phase.email
|
|
434
605
|
}),
|
|
435
606
|
":"
|
|
436
607
|
]
|
|
437
608
|
}),
|
|
438
|
-
/* @__PURE__ */
|
|
609
|
+
/* @__PURE__ */ jsx2(Box2, {
|
|
439
610
|
marginTop: 1,
|
|
440
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
620
|
+
phase.error && /* @__PURE__ */ jsx2(Box2, {
|
|
450
621
|
marginTop: 1,
|
|
451
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
632
|
+
phase.t === "verifying" && /* @__PURE__ */ jsx2(Spinner, {
|
|
462
633
|
label: "Verifying…"
|
|
463
634
|
}),
|
|
464
|
-
phase.t === "done" && /* @__PURE__ */
|
|
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__ */
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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,10 +922,299 @@ 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
|
});
|
|
699
929
|
|
|
930
|
+
// src/lib/presence.ts
|
|
931
|
+
async function fetchPresence(relayHttp, team, room, token, f = fetch) {
|
|
932
|
+
const url = `${relayHttp.replace(/\/+$/, "")}/presence?team=${encodeURIComponent(team)}&room=${encodeURIComponent(room)}`;
|
|
933
|
+
const res = await f(url, { headers: { authorization: `Bearer ${token}` } });
|
|
934
|
+
if (!res.ok)
|
|
935
|
+
throw new Error(`presence request failed (HTTP ${res.status})`);
|
|
936
|
+
const data = await res.json();
|
|
937
|
+
return data.peers ?? [];
|
|
938
|
+
}
|
|
939
|
+
function groupPeers(peers, selfMemberId) {
|
|
940
|
+
const byMember = new Map;
|
|
941
|
+
for (const p of peers) {
|
|
942
|
+
const key = p.memberId || p.peerId;
|
|
943
|
+
let g = byMember.get(key);
|
|
944
|
+
if (!g) {
|
|
945
|
+
g = { name: p.name, memberId: p.memberId ?? "", sessions: [], state: "offline", lastSeen: 0, isYou: false };
|
|
946
|
+
byMember.set(key, g);
|
|
947
|
+
}
|
|
948
|
+
g.sessions.push(p);
|
|
949
|
+
g.lastSeen = Math.max(g.lastSeen, p.lastSeen);
|
|
950
|
+
if (p.state === "available")
|
|
951
|
+
g.state = "available";
|
|
952
|
+
if (p.name && (!g.name || g.name === g.memberId))
|
|
953
|
+
g.name = p.name;
|
|
954
|
+
if (selfMemberId && p.memberId === selfMemberId)
|
|
955
|
+
g.isYou = true;
|
|
956
|
+
}
|
|
957
|
+
return [...byMember.values()].sort((a, b) => b.lastSeen - a.lastSeen);
|
|
958
|
+
}
|
|
959
|
+
var DEFAULT_RELAY_HTTP = "https://relay.vibegroup.sh";
|
|
960
|
+
|
|
961
|
+
// src/ui/Who.tsx
|
|
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";
|
|
964
|
+
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
965
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
966
|
+
function ago(ms) {
|
|
967
|
+
if (!ms)
|
|
968
|
+
return "—";
|
|
969
|
+
const s = Math.max(0, Math.round((Date.now() - ms) / 1000));
|
|
970
|
+
if (s < 5)
|
|
971
|
+
return "now";
|
|
972
|
+
if (s < 60)
|
|
973
|
+
return `${s}s ago`;
|
|
974
|
+
if (s < 3600)
|
|
975
|
+
return `${Math.round(s / 60)}m ago`;
|
|
976
|
+
return `${Math.round(s / 3600)}h ago`;
|
|
977
|
+
}
|
|
978
|
+
function Who({
|
|
979
|
+
relayHttp,
|
|
980
|
+
team,
|
|
981
|
+
room,
|
|
982
|
+
getToken,
|
|
983
|
+
selfMemberId,
|
|
984
|
+
onExit,
|
|
985
|
+
intervalMs = 3000
|
|
986
|
+
}) {
|
|
987
|
+
const [people, setPeople] = useState3(null);
|
|
988
|
+
const [selected, setSelected] = useState3(0);
|
|
989
|
+
const [error, setError] = useState3("");
|
|
990
|
+
const [updated, setUpdated] = useState3(0);
|
|
991
|
+
const selRef = useRef2(0);
|
|
992
|
+
selRef.current = selected;
|
|
993
|
+
const refresh = async () => {
|
|
994
|
+
try {
|
|
995
|
+
const token = await getToken();
|
|
996
|
+
if (!token) {
|
|
997
|
+
setError("Session expired — run `vibegroup login` again.");
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const peers = await fetchPresence(relayHttp, team, room, token, fetch);
|
|
1001
|
+
const grouped = groupPeers(peers, selfMemberId);
|
|
1002
|
+
setPeople(grouped);
|
|
1003
|
+
setSelected((s) => Math.min(s, Math.max(0, grouped.length - 1)));
|
|
1004
|
+
setUpdated(Date.now());
|
|
1005
|
+
setError("");
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
setError(e?.message ?? String(e));
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
useEffect2(() => {
|
|
1011
|
+
refresh();
|
|
1012
|
+
const t = setInterval(() => void refresh(), intervalMs);
|
|
1013
|
+
return () => clearInterval(t);
|
|
1014
|
+
}, []);
|
|
1015
|
+
useInput2((input, key) => {
|
|
1016
|
+
if (input === "q" || key.escape || key.ctrl && input === "c")
|
|
1017
|
+
return onExit(0);
|
|
1018
|
+
if (input === "r")
|
|
1019
|
+
return void refresh();
|
|
1020
|
+
const n = people?.length ?? 0;
|
|
1021
|
+
if (n === 0)
|
|
1022
|
+
return;
|
|
1023
|
+
if (key.upArrow)
|
|
1024
|
+
setSelected((s) => (s - 1 + n) % n);
|
|
1025
|
+
if (key.downArrow)
|
|
1026
|
+
setSelected((s) => (s + 1) % n);
|
|
1027
|
+
});
|
|
1028
|
+
const sel = people && people.length > 0 ? people[Math.min(selected, people.length - 1)] : undefined;
|
|
1029
|
+
return /* @__PURE__ */ jsxs3(Box3, {
|
|
1030
|
+
flexDirection: "column",
|
|
1031
|
+
paddingX: 1,
|
|
1032
|
+
paddingY: 1,
|
|
1033
|
+
gap: 1,
|
|
1034
|
+
children: [
|
|
1035
|
+
/* @__PURE__ */ jsxs3(Box3, {
|
|
1036
|
+
borderStyle: "round",
|
|
1037
|
+
borderColor: color.brand,
|
|
1038
|
+
paddingX: 1,
|
|
1039
|
+
justifyContent: "space-between",
|
|
1040
|
+
children: [
|
|
1041
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1042
|
+
children: [
|
|
1043
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
1044
|
+
color: color.brand,
|
|
1045
|
+
bold: true,
|
|
1046
|
+
children: "vibegroup"
|
|
1047
|
+
}),
|
|
1048
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
1049
|
+
dimColor: true,
|
|
1050
|
+
children: " · "
|
|
1051
|
+
}),
|
|
1052
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1053
|
+
color: color.accent,
|
|
1054
|
+
children: [
|
|
1055
|
+
team,
|
|
1056
|
+
"/",
|
|
1057
|
+
room
|
|
1058
|
+
]
|
|
1059
|
+
})
|
|
1060
|
+
]
|
|
1061
|
+
}),
|
|
1062
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
1063
|
+
dimColor: true,
|
|
1064
|
+
children: people ? `${people.length} ${people.length === 1 ? "person" : "people"}` : ""
|
|
1065
|
+
})
|
|
1066
|
+
]
|
|
1067
|
+
}),
|
|
1068
|
+
!people && !error && /* @__PURE__ */ jsx3(Spinner2, {
|
|
1069
|
+
label: "Loading who's here…"
|
|
1070
|
+
}),
|
|
1071
|
+
error && /* @__PURE__ */ jsxs3(Text3, {
|
|
1072
|
+
color: color.err,
|
|
1073
|
+
children: [
|
|
1074
|
+
"Couldn't read presence: ",
|
|
1075
|
+
error
|
|
1076
|
+
]
|
|
1077
|
+
}),
|
|
1078
|
+
people && people.length === 0 && /* @__PURE__ */ jsx3(Text3, {
|
|
1079
|
+
dimColor: true,
|
|
1080
|
+
children: "No one's in this room yet."
|
|
1081
|
+
}),
|
|
1082
|
+
people && people.length > 0 && /* @__PURE__ */ jsx3(Box3, {
|
|
1083
|
+
flexDirection: "column",
|
|
1084
|
+
children: people.map((p, i) => {
|
|
1085
|
+
const active = i === selected;
|
|
1086
|
+
const n = p.sessions.length;
|
|
1087
|
+
return /* @__PURE__ */ jsxs3(Box3, {
|
|
1088
|
+
children: [
|
|
1089
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
1090
|
+
color: active ? color.accent : undefined,
|
|
1091
|
+
children: active ? "❯ " : " "
|
|
1092
|
+
}),
|
|
1093
|
+
dot(p.state),
|
|
1094
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1095
|
+
bold: active,
|
|
1096
|
+
children: [
|
|
1097
|
+
" ",
|
|
1098
|
+
p.name || "unknown"
|
|
1099
|
+
]
|
|
1100
|
+
}),
|
|
1101
|
+
p.isYou && /* @__PURE__ */ jsx3(Text3, {
|
|
1102
|
+
color: color.dim,
|
|
1103
|
+
children: " (you)"
|
|
1104
|
+
}),
|
|
1105
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1106
|
+
dimColor: true,
|
|
1107
|
+
children: [
|
|
1108
|
+
" ",
|
|
1109
|
+
n,
|
|
1110
|
+
" ",
|
|
1111
|
+
n === 1 ? "session" : "sessions",
|
|
1112
|
+
" · ",
|
|
1113
|
+
ago(p.lastSeen)
|
|
1114
|
+
]
|
|
1115
|
+
})
|
|
1116
|
+
]
|
|
1117
|
+
}, p.memberId || p.name);
|
|
1118
|
+
})
|
|
1119
|
+
}),
|
|
1120
|
+
sel && /* @__PURE__ */ jsxs3(Box3, {
|
|
1121
|
+
flexDirection: "column",
|
|
1122
|
+
borderStyle: "round",
|
|
1123
|
+
borderColor: color.dim,
|
|
1124
|
+
paddingX: 1,
|
|
1125
|
+
children: [
|
|
1126
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1127
|
+
dimColor: true,
|
|
1128
|
+
children: [
|
|
1129
|
+
sel.name,
|
|
1130
|
+
" · ",
|
|
1131
|
+
sel.sessions.length,
|
|
1132
|
+
" ",
|
|
1133
|
+
sel.sessions.length === 1 ? "session" : "sessions"
|
|
1134
|
+
]
|
|
1135
|
+
}),
|
|
1136
|
+
sel.sessions.slice().sort((a, b) => b.lastSeen - a.lastSeen).map((s) => /* @__PURE__ */ jsxs3(Box3, {
|
|
1137
|
+
children: [
|
|
1138
|
+
dot(s.state),
|
|
1139
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1140
|
+
children: [
|
|
1141
|
+
" ",
|
|
1142
|
+
s.session || shortId(s.peerId)
|
|
1143
|
+
]
|
|
1144
|
+
}),
|
|
1145
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1146
|
+
dimColor: true,
|
|
1147
|
+
children: [
|
|
1148
|
+
" ",
|
|
1149
|
+
s.state,
|
|
1150
|
+
" · ",
|
|
1151
|
+
ago(s.lastSeen),
|
|
1152
|
+
" · #",
|
|
1153
|
+
shortId(s.peerId)
|
|
1154
|
+
]
|
|
1155
|
+
})
|
|
1156
|
+
]
|
|
1157
|
+
}, s.peerId))
|
|
1158
|
+
]
|
|
1159
|
+
}),
|
|
1160
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
1161
|
+
dimColor: true,
|
|
1162
|
+
children: [
|
|
1163
|
+
"↑↓ navigate · r refresh · q quit",
|
|
1164
|
+
updated ? ` · updated ${ago(updated)}` : ""
|
|
1165
|
+
]
|
|
1166
|
+
})
|
|
1167
|
+
]
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
var dot = (state) => state === "available" ? /* @__PURE__ */ jsx3(Text3, {
|
|
1171
|
+
color: color.ok,
|
|
1172
|
+
children: "●"
|
|
1173
|
+
}) : /* @__PURE__ */ jsx3(Text3, {
|
|
1174
|
+
color: color.dim,
|
|
1175
|
+
children: "○"
|
|
1176
|
+
}), shortId = (peerId) => peerId.split("#")[1]?.slice(0, 8) ?? peerId.slice(0, 8);
|
|
1177
|
+
var init_Who = __esm(() => {
|
|
1178
|
+
init_theme();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
// src/commands/who.ts
|
|
1182
|
+
var exports_who = {};
|
|
1183
|
+
__export(exports_who, {
|
|
1184
|
+
whoCommand: () => whoCommand
|
|
1185
|
+
});
|
|
1186
|
+
import { createElement as createElement3 } from "react";
|
|
1187
|
+
async function whoCommand(flags, env = process.env) {
|
|
1188
|
+
const auth = readAuth(env);
|
|
1189
|
+
if (!isLoggedIn(auth)) {
|
|
1190
|
+
console.error("Not logged in — run `vibegroup login` first.");
|
|
1191
|
+
return 1;
|
|
1192
|
+
}
|
|
1193
|
+
const team = str2(flags.team);
|
|
1194
|
+
if (!team) {
|
|
1195
|
+
console.error("usage: vibegroup who --team <slug> [--room <name>]");
|
|
1196
|
+
return 1;
|
|
1197
|
+
}
|
|
1198
|
+
const room = str2(flags.room) ?? "general";
|
|
1199
|
+
const relayHttp = env.VIBEGROUP_RELAY_HTTP ?? DEFAULT_RELAY_HTTP;
|
|
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
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
var str2 = (v) => typeof v === "string" ? v : undefined;
|
|
1211
|
+
var init_who = __esm(() => {
|
|
1212
|
+
init_runner();
|
|
1213
|
+
init_Who();
|
|
1214
|
+
init_auth();
|
|
1215
|
+
init_refresh();
|
|
1216
|
+
});
|
|
1217
|
+
|
|
700
1218
|
// src/lib/claudeLaunch.ts
|
|
701
1219
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
702
1220
|
function buildClaudeArgs(opts = {}) {
|
|
@@ -719,6 +1237,7 @@ var exports_claudeCmd = {};
|
|
|
719
1237
|
__export(exports_claudeCmd, {
|
|
720
1238
|
claudeCommand: () => claudeCommand
|
|
721
1239
|
});
|
|
1240
|
+
import { basename } from "node:path";
|
|
722
1241
|
function extractFlag(args, name) {
|
|
723
1242
|
const rest = [];
|
|
724
1243
|
let value;
|
|
@@ -740,18 +1259,24 @@ function extractFlag(args, name) {
|
|
|
740
1259
|
}
|
|
741
1260
|
return { value, rest };
|
|
742
1261
|
}
|
|
743
|
-
function claudeCommand(args, env = process.env, launcher, channel = realChannelGate) {
|
|
1262
|
+
async function claudeCommand(args, env = process.env, launcher, channel = realChannelGate) {
|
|
744
1263
|
if (!isLoggedIn(readAuth(env))) {
|
|
745
1264
|
console.error("Not logged in — run `vibegroup login` first.");
|
|
746
1265
|
return 1;
|
|
747
1266
|
}
|
|
748
1267
|
const dangerously = args.includes("--dev");
|
|
749
1268
|
const { value: team, rest: afterTeam } = extractFlag(args.filter((a) => a !== "--dev"), "team");
|
|
750
|
-
const { value: room, rest:
|
|
1269
|
+
const { value: room, rest: afterRoom } = extractFlag(afterTeam, "room");
|
|
1270
|
+
const { value: sessionFlag, rest: extra } = extractFlag(afterRoom, "session");
|
|
751
1271
|
if (!team) {
|
|
752
1272
|
console.error("No team selected — pass `--team <slug>` (the team whose room you want to join).");
|
|
753
1273
|
return 1;
|
|
754
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
|
+
}
|
|
1279
|
+
const session = sessionFlag && sessionFlag.length > 0 ? sessionFlag : basename(process.cwd());
|
|
755
1280
|
if (!dangerously && !channel.allowlisted()) {
|
|
756
1281
|
console.log("Enabling the vibegroup channel — one-time, needs admin. Enter your password if prompted.");
|
|
757
1282
|
if (!channel.enable()) {
|
|
@@ -762,6 +1287,7 @@ function claudeCommand(args, env = process.env, launcher, channel = realChannelG
|
|
|
762
1287
|
const vg = {
|
|
763
1288
|
VIBEGROUP_TEAM: team,
|
|
764
1289
|
VIBEGROUP_ROOM: room && room.length > 0 ? room : "general",
|
|
1290
|
+
VIBEGROUP_SESSION: session,
|
|
765
1291
|
VIBEGROUP_API: env.VIBEGROUP_API ?? DEFAULT_API_BASE
|
|
766
1292
|
};
|
|
767
1293
|
return launchClaude({ extraArgs: extra, dangerously, env: vg }, launcher);
|
|
@@ -770,6 +1296,7 @@ var DEFAULT_API_BASE = "https://api.vibegroup.sh", realChannelGate;
|
|
|
770
1296
|
var init_claudeCmd = __esm(() => {
|
|
771
1297
|
init_claudeLaunch();
|
|
772
1298
|
init_auth();
|
|
1299
|
+
init_refresh();
|
|
773
1300
|
init_channel();
|
|
774
1301
|
realChannelGate = { allowlisted: channelAllowlisted, enable: enableChannelWithSudo };
|
|
775
1302
|
});
|
|
@@ -826,9 +1353,10 @@ async function run(argv, env = process.env) {
|
|
|
826
1353
|
}
|
|
827
1354
|
switch (command) {
|
|
828
1355
|
case "":
|
|
829
|
-
case "help":
|
|
830
|
-
|
|
831
|
-
return
|
|
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
|
+
}
|
|
832
1360
|
case "version":
|
|
833
1361
|
console.log(VERSION);
|
|
834
1362
|
return 0;
|
|
@@ -871,6 +1399,10 @@ async function run(argv, env = process.env) {
|
|
|
871
1399
|
const { roomsCommand: roomsCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
|
|
872
1400
|
return roomsCommand2(flags, env);
|
|
873
1401
|
}
|
|
1402
|
+
case "who": {
|
|
1403
|
+
const { whoCommand: whoCommand2 } = await Promise.resolve().then(() => (init_who(), exports_who));
|
|
1404
|
+
return whoCommand2(flags, env);
|
|
1405
|
+
}
|
|
874
1406
|
case "invite": {
|
|
875
1407
|
const { inviteCommand: inviteCommand2 } = await Promise.resolve().then(() => (init_team(), exports_team));
|
|
876
1408
|
return inviteCommand2(rest, flags, env);
|
|
@@ -900,7 +1432,8 @@ Commands:
|
|
|
900
1432
|
room create <name> --team <slug> Add a room to a team
|
|
901
1433
|
invite <email> --team <s> Invite someone to a team
|
|
902
1434
|
rooms --team <slug> List a team's rooms
|
|
903
|
-
|
|
1435
|
+
who --team <slug> [--room] Live view of who's in a room (people + their sessions)
|
|
1436
|
+
claude --team <slug> [--room <name>] [--session <label>] [...]
|
|
904
1437
|
Launch Claude Code with the vibegroup channel
|
|
905
1438
|
help Show this help
|
|
906
1439
|
|
package/package.json
CHANGED
|
@@ -18,11 +18,11 @@ A Claude session joins **one** `team:room`, fixed at launch by `vibegroup claude
|
|
|
18
18
|
|
|
19
19
|
Peer agents in your team's room can ask each other what they're working on. Use the vibegroup MCP tools:
|
|
20
20
|
|
|
21
|
-
- `vibegroup_peers` —
|
|
21
|
+
- `vibegroup_peers` — the **people** in the room, each with their named **sessions** (one per repo/task)
|
|
22
22
|
- `vibegroup_ask` — ask a peer a question (returns a `qid`; the answer arrives later as a `<channel kind="answer">` event)
|
|
23
23
|
- `vibegroup_reply` — answer a peer's question (pass the `qid` from the incoming `<channel kind="question">` event)
|
|
24
24
|
|
|
25
|
-
To ask: call `vibegroup_peers` to find
|
|
25
|
+
To ask: call `vibegroup_peers` to find the person. **If that person has more than one session** (e.g. "tell jaime …" and jaime shows `PLA-345` and `billing-fix`), don't guess — tell the user which sessions exist and ask which one, then `vibegroup_ask` that session's `peerId`. If they have exactly one session, use it directly. Incoming questions arrive as channel events pushed into your session — answer read-only and call `vibegroup_reply` with the `qid`.
|
|
26
26
|
|
|
27
27
|
If `vibegroup status` shows you're not set up, run **/vibegroup:init** first.
|
|
28
28
|
|
package/plugin/dist/channel.js
CHANGED
|
@@ -15296,7 +15296,7 @@ class RelayClient {
|
|
|
15296
15296
|
const ws = new WebSocket(this.opts.url);
|
|
15297
15297
|
this.ws = ws;
|
|
15298
15298
|
this.joinWaiter = { resolve, reject };
|
|
15299
|
-
ws.addEventListener("open", () => this.send({ kind: "join", resumeToken: this.resumeToken, body: { accessToken: this.opts.accessToken, team: this.opts.team, room: this.opts.room, name: this.opts.name } }));
|
|
15299
|
+
ws.addEventListener("open", () => this.send({ kind: "join", resumeToken: this.resumeToken, body: { accessToken: this.opts.accessToken, team: this.opts.team, room: this.opts.room, name: this.opts.name, session: this.opts.session } }));
|
|
15300
15300
|
ws.addEventListener("message", (ev) => this.dispatch(parseEnvelope(String(ev.data))));
|
|
15301
15301
|
ws.addEventListener("error", () => this.failPending(new Error("websocket error")));
|
|
15302
15302
|
ws.addEventListener("close", () => this.failPending(new Error("websocket closed")));
|
|
@@ -16828,11 +16828,10 @@ function groupPeers(peers, selfPeerId) {
|
|
|
16828
16828
|
const key = p.memberId || p.peerId;
|
|
16829
16829
|
let g = byMember.get(key);
|
|
16830
16830
|
if (!g) {
|
|
16831
|
-
g = { name: p.name, memberId: p.memberId ?? "", sessions:
|
|
16831
|
+
g = { name: p.name, memberId: p.memberId ?? "", sessions: [], state: "offline", lastSeen: 0, isYou: false };
|
|
16832
16832
|
byMember.set(key, g);
|
|
16833
16833
|
}
|
|
16834
|
-
g.sessions
|
|
16835
|
-
g.peerIds.push(p.peerId);
|
|
16834
|
+
g.sessions.push({ peerId: p.peerId, session: p.session, state: p.state, lastSeen: p.lastSeen });
|
|
16836
16835
|
g.lastSeen = Math.max(g.lastSeen, p.lastSeen);
|
|
16837
16836
|
if (p.state === "available")
|
|
16838
16837
|
g.state = "available";
|
|
@@ -16863,7 +16862,7 @@ function createChannelTools(relay, pending, maxAnswerChars = 4000) {
|
|
|
16863
16862
|
return [
|
|
16864
16863
|
{
|
|
16865
16864
|
name: "vibegroup_peers",
|
|
16866
|
-
description: "List the people in your vibegroup room, grouped by user
|
|
16865
|
+
description: "List the people in your vibegroup room, grouped by user. Each person may run several named sessions (one per repo/task); if you mean to reach a person with more than one session, ask the user which `session` before vibegroup_ask, and use that session's peerId.",
|
|
16867
16866
|
inputSchema: { type: "object", properties: {} },
|
|
16868
16867
|
handler: async () => JSON.stringify({ people: groupPeers(await relay.peers(), relay.peerId) }, null, 2)
|
|
16869
16868
|
},
|
|
@@ -16912,7 +16911,7 @@ async function startChannel(opts) {
|
|
|
16912
16911
|
}
|
|
16913
16912
|
|
|
16914
16913
|
// src/config.ts
|
|
16915
|
-
import { join } from "path";
|
|
16914
|
+
import { join, basename } from "path";
|
|
16916
16915
|
import { existsSync, readFileSync } from "fs";
|
|
16917
16916
|
var DEFAULT_RELAY_WS = "wss://relay.vibegroup.sh/ws";
|
|
16918
16917
|
var DEFAULT_API_BASE = "https://api.vibegroup.sh";
|
|
@@ -16934,7 +16933,7 @@ function readAuth(env, home) {
|
|
|
16934
16933
|
return null;
|
|
16935
16934
|
}
|
|
16936
16935
|
}
|
|
16937
|
-
function resolveChannelConfig(env, home) {
|
|
16936
|
+
function resolveChannelConfig(env, home, cwd = process.cwd()) {
|
|
16938
16937
|
const auth = readAuth(env, home);
|
|
16939
16938
|
const team = env.VIBEGROUP_TEAM;
|
|
16940
16939
|
if (!auth || !team)
|
|
@@ -16945,7 +16944,8 @@ function resolveChannelConfig(env, home) {
|
|
|
16945
16944
|
accessToken: auth.accessToken,
|
|
16946
16945
|
team,
|
|
16947
16946
|
room: env.VIBEGROUP_ROOM ?? "general",
|
|
16948
|
-
name: env.VIBEGROUP_NAME ?? auth.email ?? ""
|
|
16947
|
+
name: env.VIBEGROUP_NAME ?? auth.email ?? "",
|
|
16948
|
+
session: env.VIBEGROUP_SESSION || basename(cwd) || "session"
|
|
16949
16949
|
};
|
|
16950
16950
|
}
|
|
16951
16951
|
async function fetchTeamKey(cfg, fetchImpl = fetch) {
|
|
@@ -16980,5 +16980,6 @@ await startChannel({
|
|
|
16980
16980
|
team: cfg.team,
|
|
16981
16981
|
room: cfg.room,
|
|
16982
16982
|
teamKey,
|
|
16983
|
-
name: cfg.name
|
|
16983
|
+
name: cfg.name,
|
|
16984
|
+
session: cfg.session
|
|
16984
16985
|
});
|