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.
- package/README.md +6 -9
- package/dist/cli.js +375 -108
- 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
|
-
|
|
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
|
-
|
|
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
|
|
48
|
-
vibegroup
|
|
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.
|
|
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(
|
|
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] =
|
|
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__ */
|
|
568
|
+
return /* @__PURE__ */ jsxs2(Box2, {
|
|
384
569
|
flexDirection: "column",
|
|
385
570
|
gap: 1,
|
|
386
571
|
paddingY: 1,
|
|
387
572
|
children: [
|
|
388
|
-
/* @__PURE__ */
|
|
573
|
+
/* @__PURE__ */ jsxs2(Text2, {
|
|
389
574
|
children: [
|
|
390
|
-
/* @__PURE__ */
|
|
575
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
391
576
|
color: color.brand,
|
|
392
577
|
bold: true,
|
|
393
578
|
children: "vibegroup"
|
|
394
579
|
}),
|
|
395
|
-
/* @__PURE__ */
|
|
580
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
396
581
|
dimColor: true,
|
|
397
582
|
children: " · sign in"
|
|
398
583
|
})
|
|
399
584
|
]
|
|
400
585
|
}),
|
|
401
|
-
phase.t === "email" && /* @__PURE__ */
|
|
586
|
+
phase.t === "email" && /* @__PURE__ */ jsxs2(Box2, {
|
|
402
587
|
flexDirection: "column",
|
|
403
588
|
children: [
|
|
404
|
-
/* @__PURE__ */
|
|
589
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
405
590
|
children: "Email to sign in with:"
|
|
406
591
|
}),
|
|
407
|
-
/* @__PURE__ */
|
|
592
|
+
/* @__PURE__ */ jsx2(Box2, {
|
|
408
593
|
marginTop: 1,
|
|
409
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
607
|
+
phase.t === "sending" && /* @__PURE__ */ jsx2(Spinner, {
|
|
423
608
|
label: `Sending a code to ${phase.email}…`
|
|
424
609
|
}),
|
|
425
|
-
phase.t === "code" && /* @__PURE__ */
|
|
610
|
+
phase.t === "code" && /* @__PURE__ */ jsxs2(Box2, {
|
|
426
611
|
flexDirection: "column",
|
|
427
612
|
children: [
|
|
428
|
-
/* @__PURE__ */
|
|
613
|
+
/* @__PURE__ */ jsxs2(Text2, {
|
|
429
614
|
children: [
|
|
430
615
|
"Enter the 6-digit code sent to ",
|
|
431
|
-
/* @__PURE__ */
|
|
616
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
432
617
|
color: color.accent,
|
|
433
618
|
children: phase.email
|
|
434
619
|
}),
|
|
435
620
|
":"
|
|
436
621
|
]
|
|
437
622
|
}),
|
|
438
|
-
/* @__PURE__ */
|
|
623
|
+
/* @__PURE__ */ jsx2(Box2, {
|
|
439
624
|
marginTop: 1,
|
|
440
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
634
|
+
phase.error && /* @__PURE__ */ jsx2(Box2, {
|
|
450
635
|
marginTop: 1,
|
|
451
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
646
|
+
phase.t === "verifying" && /* @__PURE__ */ jsx2(Spinner, {
|
|
462
647
|
label: "Verifying…"
|
|
463
648
|
}),
|
|
464
|
-
phase.t === "done" && /* @__PURE__ */
|
|
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__ */
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
733
|
-
import { Box as
|
|
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
|
|
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
|
-
|
|
996
|
+
getToken,
|
|
753
997
|
selfMemberId,
|
|
754
998
|
onExit,
|
|
755
999
|
intervalMs = 3000
|
|
756
1000
|
}) {
|
|
757
|
-
const [people, setPeople] =
|
|
758
|
-
const [selected, setSelected] =
|
|
759
|
-
const [error, setError] =
|
|
760
|
-
const [updated, setUpdated] =
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1055
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
807
1056
|
children: [
|
|
808
|
-
/* @__PURE__ */
|
|
1057
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
809
1058
|
color: color.brand,
|
|
810
1059
|
bold: true,
|
|
811
1060
|
children: "vibegroup"
|
|
812
1061
|
}),
|
|
813
|
-
/* @__PURE__ */
|
|
1062
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
814
1063
|
dimColor: true,
|
|
815
1064
|
children: " · "
|
|
816
1065
|
}),
|
|
817
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1082
|
+
!people && !error && /* @__PURE__ */ jsx3(Spinner2, {
|
|
834
1083
|
label: "Loading who's here…"
|
|
835
1084
|
}),
|
|
836
|
-
error && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1101
|
+
return /* @__PURE__ */ jsxs3(Box3, {
|
|
853
1102
|
children: [
|
|
854
|
-
/* @__PURE__ */
|
|
1103
|
+
/* @__PURE__ */ jsx3(Text3, {
|
|
855
1104
|
color: active ? color.accent : undefined,
|
|
856
1105
|
children: active ? "❯ " : " "
|
|
857
1106
|
}),
|
|
858
1107
|
dot(p.state),
|
|
859
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1115
|
+
p.isYou && /* @__PURE__ */ jsx3(Text3, {
|
|
867
1116
|
color: color.dim,
|
|
868
1117
|
children: " (you)"
|
|
869
1118
|
}),
|
|
870
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1153
|
+
/* @__PURE__ */ jsxs3(Text3, {
|
|
905
1154
|
children: [
|
|
906
1155
|
" ",
|
|
907
1156
|
s.session || shortId(s.peerId)
|
|
908
1157
|
]
|
|
909
1158
|
}),
|
|
910
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1184
|
+
var dot = (state) => state === "available" ? /* @__PURE__ */ jsx3(Text3, {
|
|
936
1185
|
color: color.ok,
|
|
937
1186
|
children: "●"
|
|
938
|
-
}) : /* @__PURE__ */
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1109
|
-
return
|
|
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
|
|
1177
|
-
|
|
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
|