@phnx-labs/agents-cli 1.22.45 → 1.22.47
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/CHANGELOG.md +303 -0
- package/LICENSE +102 -182
- package/README.md +54 -17
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.js +56 -12
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +229 -0
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +266 -15
- package/dist/commands/config.js +26 -4
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +55 -38
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/lease.js +3 -3
- package/dist/commands/routines.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +4 -0
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +194 -16
- package/dist/commands/teams.js +34 -2
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +19 -32
- package/dist/commands/watchdog.js +9 -14
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +92 -6
- package/dist/lib/accounting/usage.js +329 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +20 -7
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +33 -7
- package/dist/lib/browser/profiles.d.ts +106 -1
- package/dist/lib/browser/profiles.js +300 -17
- package/dist/lib/browser/remote-control.d.ts +26 -2
- package/dist/lib/browser/remote-control.js +33 -3
- package/dist/lib/browser/runtime-state.d.ts +9 -0
- package/dist/lib/browser/runtime-state.js +24 -8
- package/dist/lib/browser/service.d.ts +37 -0
- package/dist/lib/browser/service.js +78 -20
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/cli-resources.js +17 -15
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/config-keys.d.ts +4 -3
- package/dist/lib/config-keys.js +17 -7
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon-ticks.js +2 -2
- package/dist/lib/device-config.d.ts +25 -8
- package/dist/lib/device-config.js +90 -17
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/harness-inventory.js +20 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +6 -0
- package/dist/lib/devices/stats-cache.js +10 -2
- package/dist/lib/exec.d.ts +25 -3
- package/dist/lib/exec.js +75 -22
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/hooks/cache.js +8 -0
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/passthrough.js +16 -0
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/identity/client.d.ts +53 -0
- package/dist/lib/identity/client.js +106 -0
- package/dist/lib/identity/index.d.ts +115 -0
- package/dist/lib/identity/index.js +82 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +13 -0
- package/dist/lib/menubar/install-menubar.js +26 -2
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/probe.d.ts +8 -0
- package/dist/lib/probe.js +105 -0
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +69 -1
- package/dist/lib/session/db.js +4 -2
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +12 -6
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +6 -1
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.js +5 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +15 -0
- package/dist/lib/session/trajectory-html.js +750 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +277 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +335 -0
- package/dist/lib/startup/command-registry.d.ts +5 -2
- package/dist/lib/startup/command-registry.js +8 -5
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +4 -2
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +173 -27
- package/dist/lib/types.d.ts +16 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +17 -3
- package/dist/lib/usage-refresh.js +57 -11
- package/dist/lib/view-types.d.ts +2 -2
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/package.json +2 -2
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
|
@@ -3,7 +3,7 @@ import { password } from '@inquirer/prompts';
|
|
|
3
3
|
import { setHelpSections } from '../lib/help.js';
|
|
4
4
|
import { readMeta, updateMeta } from '../lib/state.js';
|
|
5
5
|
import { ALL_AGENT_IDS, getAccountInfo, resolveAgentName } from '../lib/agents.js';
|
|
6
|
-
import { getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
|
|
6
|
+
import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
|
|
7
7
|
import { assertNativeAccountNameable, nativeAccountCapability, nativeIdentityKey } from '../lib/account-capabilities.js';
|
|
8
8
|
import { collectRunCandidates } from '../lib/accounting/rotate.js';
|
|
9
9
|
import { isInteractiveTerminal } from './utils.js';
|
|
@@ -16,7 +16,10 @@ import { runDevicesAccounts } from './ssh.js';
|
|
|
16
16
|
import { discoverNativeAccounts } from '../lib/account-catalog.js';
|
|
17
17
|
import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
|
|
18
18
|
import { getAccountProvider, listAccountProviders } from '../lib/account-provider-registry.js';
|
|
19
|
-
import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
|
|
19
|
+
import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, labelNativeAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
|
|
20
|
+
function cleanCommandError(command, err) {
|
|
21
|
+
command.error(err instanceof Error ? err.message : String(err), { exitCode: 1, code: 'accounts.error' });
|
|
22
|
+
}
|
|
20
23
|
function parseInstallation(raw) {
|
|
21
24
|
const at = raw.lastIndexOf('@');
|
|
22
25
|
if (at < 1 || at === raw.length - 1)
|
|
@@ -273,12 +276,43 @@ export function registerAccountsCommand(program) {
|
|
|
273
276
|
});
|
|
274
277
|
accounts.command('name <source> <name>')
|
|
275
278
|
.description('Name a signed-in native installation without copying its OAuth credentials')
|
|
276
|
-
.action(async (source, name) => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
.action(async (source, name, _o, command) => {
|
|
280
|
+
try {
|
|
281
|
+
const identity = await nativeIdentityFromSource(source);
|
|
282
|
+
const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
|
|
283
|
+
console.log(chalk.green(`Named ${source} as ${account.name}.`));
|
|
284
|
+
if (account.scope === 'device')
|
|
285
|
+
console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
cleanCommandError(command, err);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
accounts.command('label <harness> [label]')
|
|
292
|
+
.description('Label a native login independently of its installed harness version')
|
|
293
|
+
.option('--account <email-or-id>', 'Native identity to label when the harness has multiple logins')
|
|
294
|
+
.action(async (harness, label, o, command) => {
|
|
295
|
+
try {
|
|
296
|
+
const agent = parseHarness(harness);
|
|
297
|
+
assertNativeAccountNameable(agent);
|
|
298
|
+
const candidates = (await collectRunCandidates(agent)).filter(candidate => candidate.signedIn);
|
|
299
|
+
const needle = o.account?.toLowerCase();
|
|
300
|
+
const selected = needle
|
|
301
|
+
? candidates.find(candidate => candidate.email?.toLowerCase() === needle || candidate.accountKey?.toLowerCase() === needle)
|
|
302
|
+
: candidates.find(candidate => candidate.version === getGlobalDefault(agent)) ?? (candidates.length === 1 ? candidates[0] : undefined);
|
|
303
|
+
if (!selected)
|
|
304
|
+
throw new Error(needle
|
|
305
|
+
? `Unknown ${agent} account '${o.account}'.`
|
|
306
|
+
: `Multiple ${agent} accounts are connected; pass --account <email|id>.`);
|
|
307
|
+
const identityKey = selected.accountKey ?? selected.email?.toLowerCase();
|
|
308
|
+
if (!identityKey)
|
|
309
|
+
throw new Error(`${agent} account '${selected.version}' has no stable identity.`);
|
|
310
|
+
const account = labelNativeAccount(agent, identityKey, selected.email ?? undefined, label, nativeAccountCapability(agent).scope);
|
|
311
|
+
console.log(chalk.green(`Labeled ${agent} account ${selected.email ?? identityKey} as '${account.name}'.`));
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
cleanCommandError(command, err);
|
|
315
|
+
}
|
|
282
316
|
});
|
|
283
317
|
accounts.command('attach <account> <target>')
|
|
284
318
|
.description('Attach a named account to a native installation or custom harness')
|
|
@@ -329,15 +363,25 @@ export function registerAccountsCommand(program) {
|
|
|
329
363
|
accounts.command('remove <name>').description('Remove an account and its device-local credential').action((name) => removeAccount(name));
|
|
330
364
|
accounts.command('set-default <agent> <name>')
|
|
331
365
|
.description('Use this account for a harness when --account is omitted')
|
|
332
|
-
.action((agentRaw, name) => {
|
|
333
|
-
|
|
334
|
-
|
|
366
|
+
.action((agentRaw, name, _o, command) => {
|
|
367
|
+
try {
|
|
368
|
+
const { agent, account } = setDefaultAccount(agentRaw, name);
|
|
369
|
+
console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
cleanCommandError(command, err);
|
|
373
|
+
}
|
|
335
374
|
});
|
|
336
375
|
const switchCmd = accounts.command('switch <harness> [account]')
|
|
337
376
|
.description('Pick the default account for a harness')
|
|
338
377
|
.option('--json', 'Machine-readable account list or the resulting default')
|
|
339
378
|
.action(async (harness, account, o, command) => {
|
|
340
|
-
|
|
379
|
+
try {
|
|
380
|
+
await runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) });
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
cleanCommandError(command, err);
|
|
384
|
+
}
|
|
341
385
|
});
|
|
342
386
|
accounts.command('clear-default <agent>')
|
|
343
387
|
.description('Return a harness to native login or balanced account selection')
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { PhoenixApiError, clearSession, createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, pollDeviceToken, readSession, removeSpaceMember, resolveMemberFromList, resolveSpaceFromList, slugify, startDeviceAuthorization, updateSpaceMemberRole, writeSession, } from '../lib/identity/index.js';
|
|
3
|
+
import { setHelpSections } from '../lib/help.js';
|
|
4
|
+
import { runOrDie } from '../lib/format.js';
|
|
5
|
+
/**
|
|
6
|
+
* `agents auth` — sign in to Phoenix ID, the account layer behind team spaces.
|
|
7
|
+
* Signing in is optional: every local feature works with no account. Everything
|
|
8
|
+
* here goes through `lib/identity`; this file builds no URLs and reads no
|
|
9
|
+
* credential files of its own.
|
|
10
|
+
*/
|
|
11
|
+
function sleep(ms) {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
}
|
|
14
|
+
async function login() {
|
|
15
|
+
const grant = await startDeviceAuthorization();
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(` Your code: ${chalk.bold.cyan(grant.user_code)}`);
|
|
18
|
+
console.log(` Open: ${chalk.underline(grant.verification_uri_complete)}`);
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(chalk.gray(' Waiting for you to approve it in the browser…'));
|
|
21
|
+
// The server sets the pace; `slow_down` widens it (RFC 8628 §3.5).
|
|
22
|
+
let interval = Math.max(1, grant.interval) * 1000;
|
|
23
|
+
const deadline = Date.now() + grant.expires_in * 1000;
|
|
24
|
+
while (Date.now() < deadline) {
|
|
25
|
+
await sleep(interval);
|
|
26
|
+
const poll = await pollDeviceToken(grant.device_code);
|
|
27
|
+
if (poll.status === 'authorized') {
|
|
28
|
+
writeSession({ access_token: poll.access_token, email: poll.user.email, userId: poll.user.id });
|
|
29
|
+
console.log(chalk.green(`\n Signed in as ${poll.user.email}.`));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (poll.status === 'slow_down') {
|
|
33
|
+
interval += 5000;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (poll.status === 'denied')
|
|
37
|
+
throw new Error('Sign-in was denied in the browser.');
|
|
38
|
+
if (poll.status === 'expired')
|
|
39
|
+
throw new Error("That code expired. Run 'agents auth login' again.");
|
|
40
|
+
}
|
|
41
|
+
throw new Error("Timed out waiting for approval. Run 'agents auth login' again.");
|
|
42
|
+
}
|
|
43
|
+
async function whoami(json) {
|
|
44
|
+
const session = readSession();
|
|
45
|
+
if (!session) {
|
|
46
|
+
if (json) {
|
|
47
|
+
console.log(JSON.stringify({ signedIn: false }, null, 2));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.gray("Not signed in. Run 'agents auth login'."));
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const me = await fetchWhoAmI();
|
|
56
|
+
if (json) {
|
|
57
|
+
console.log(JSON.stringify({ signedIn: true, ...me }, null, 2));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`${chalk.bold(me.email)} ${chalk.gray(me.userId)}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err instanceof PhoenixApiError && err.status === 401) {
|
|
64
|
+
throw new Error("Your session is no longer valid. Run 'agents auth login' again.");
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function printSpaces(spaces) {
|
|
70
|
+
if (!spaces.length) {
|
|
71
|
+
console.log(chalk.gray(" No spaces yet. Create one with 'agents auth space create <name>'."));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const space of spaces) {
|
|
75
|
+
console.log(` ${chalk.cyan(space.slug)} ${space.name} ${chalk.gray(space.user_role)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Resolve a space reference (or the caller's only space) to a concrete space. */
|
|
79
|
+
async function requireSpace(ref) {
|
|
80
|
+
const spaces = await listSpaces();
|
|
81
|
+
const space = resolveSpaceFromList(spaces, ref);
|
|
82
|
+
if (space)
|
|
83
|
+
return space;
|
|
84
|
+
if (!ref) {
|
|
85
|
+
throw new Error(spaces.length
|
|
86
|
+
? `You are in ${spaces.length} spaces — name one: ${spaces.map((s) => s.slug).join(', ')}.`
|
|
87
|
+
: "You are not in a space yet. Create one with 'agents auth space create <name>'.");
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`No space named '${ref}'.`);
|
|
90
|
+
}
|
|
91
|
+
export function registerAuthCommand(program) {
|
|
92
|
+
const auth = program
|
|
93
|
+
.command('auth')
|
|
94
|
+
.description('Sign in to Phoenix ID — the account layer behind team spaces');
|
|
95
|
+
setHelpSections(auth, {
|
|
96
|
+
examples: `agents auth login # device-code sign-in via your browser
|
|
97
|
+
agents auth whoami # who this machine is signed in as
|
|
98
|
+
agents auth space create "Design Team" # start a space
|
|
99
|
+
agents auth space invite ada@example.com # add a teammate
|
|
100
|
+
agents auth logout # clear this machine only`,
|
|
101
|
+
notes: `Signing in is optional — every local feature works with no account; team spaces are what it unlocks.
|
|
102
|
+
Sign-in is Google-only and opens a Phoenix-branded page; the CLI never sees a password.
|
|
103
|
+
The session lives in this machine's agents state dir, so logging out here signs out nothing else.
|
|
104
|
+
Point at a different backend with PHOENIX_ID_BASE (defaults to the production service).`,
|
|
105
|
+
});
|
|
106
|
+
auth
|
|
107
|
+
.command('login')
|
|
108
|
+
.description('Sign in with the device-code flow')
|
|
109
|
+
.action(() => runOrDie(() => login()));
|
|
110
|
+
auth
|
|
111
|
+
.command('whoami')
|
|
112
|
+
.description('Show the signed-in account')
|
|
113
|
+
.option('--json', 'Machine-readable output')
|
|
114
|
+
.action((o, command) => {
|
|
115
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
116
|
+
return runOrDie(() => whoami(json), { json });
|
|
117
|
+
});
|
|
118
|
+
auth
|
|
119
|
+
.command('logout')
|
|
120
|
+
.description("Clear this machine's session (no other device is affected)")
|
|
121
|
+
.action(() => runOrDie(() => {
|
|
122
|
+
const session = readSession();
|
|
123
|
+
clearSession();
|
|
124
|
+
console.log(session ? chalk.green(`Signed out ${session.email ?? 'this machine'}.`) : chalk.gray('Already signed out.'));
|
|
125
|
+
}));
|
|
126
|
+
const space = auth.command('space').description('Spaces — share work with teammates');
|
|
127
|
+
space
|
|
128
|
+
.command('list', { isDefault: true })
|
|
129
|
+
.description('Spaces you belong to')
|
|
130
|
+
.option('--json', 'Machine-readable output')
|
|
131
|
+
.action((o, command) => {
|
|
132
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
133
|
+
return runOrDie(async () => {
|
|
134
|
+
const spaces = await listSpaces();
|
|
135
|
+
if (json)
|
|
136
|
+
return console.log(JSON.stringify(spaces, null, 2));
|
|
137
|
+
printSpaces(spaces);
|
|
138
|
+
}, { json });
|
|
139
|
+
});
|
|
140
|
+
space
|
|
141
|
+
.command('create <name>')
|
|
142
|
+
.description('Create a space')
|
|
143
|
+
.option('--slug <slug>', 'URL-safe name (defaults to a slug of <name>)')
|
|
144
|
+
.option('--json', 'Machine-readable output')
|
|
145
|
+
.action((name, o, command) => {
|
|
146
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
147
|
+
return runOrDie(async () => {
|
|
148
|
+
const created = await createSpace({ name, slug: o.slug ?? slugify(name) });
|
|
149
|
+
if (json)
|
|
150
|
+
return console.log(JSON.stringify(created, null, 2));
|
|
151
|
+
console.log(chalk.green(`Created ${created.name} (${created.slug}).`));
|
|
152
|
+
}, { json });
|
|
153
|
+
});
|
|
154
|
+
space
|
|
155
|
+
.command('members [space]')
|
|
156
|
+
.description('Who is in a space')
|
|
157
|
+
.option('--json', 'Machine-readable output')
|
|
158
|
+
.action((ref, o, command) => {
|
|
159
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
160
|
+
return runOrDie(async () => {
|
|
161
|
+
const target = await requireSpace(ref);
|
|
162
|
+
const members = await listSpaceMembers(target.id);
|
|
163
|
+
if (json)
|
|
164
|
+
return console.log(JSON.stringify(members, null, 2));
|
|
165
|
+
for (const m of members)
|
|
166
|
+
console.log(` ${m.email} ${chalk.gray(m.role)}`);
|
|
167
|
+
}, { json });
|
|
168
|
+
});
|
|
169
|
+
space
|
|
170
|
+
.command('invite <email>')
|
|
171
|
+
.description('Invite someone to a space')
|
|
172
|
+
.option('--space <space>', 'Which space (defaults to your only one)')
|
|
173
|
+
.option('--role <role>', 'admin or member', 'member')
|
|
174
|
+
.option('--json', 'Machine-readable output')
|
|
175
|
+
.action((email, o, command) => {
|
|
176
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
177
|
+
return runOrDie(async () => {
|
|
178
|
+
if (o.role !== 'admin' && o.role !== 'member') {
|
|
179
|
+
throw new Error(`--role must be admin or member (got '${o.role}').`);
|
|
180
|
+
}
|
|
181
|
+
const target = await requireSpace(o.space);
|
|
182
|
+
const result = await createSpaceInvite(target.id, { email, role: o.role });
|
|
183
|
+
if (json)
|
|
184
|
+
return console.log(JSON.stringify(result, null, 2));
|
|
185
|
+
console.log(result.member_added
|
|
186
|
+
? chalk.green(`Added ${email} to ${target.name} as ${o.role}.`)
|
|
187
|
+
: chalk.green(`Invited ${email} to ${target.name} as ${o.role}. Invite code: ${result.invite_code}`));
|
|
188
|
+
}, { json });
|
|
189
|
+
});
|
|
190
|
+
space
|
|
191
|
+
.command('role <email> <role>')
|
|
192
|
+
.description('Change a member\'s role (owner only for admin)')
|
|
193
|
+
.option('--space <space>', 'Which space (defaults to your only one)')
|
|
194
|
+
.option('--json', 'Machine-readable output')
|
|
195
|
+
.action((email, role, o, command) => {
|
|
196
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
197
|
+
return runOrDie(async () => {
|
|
198
|
+
if (role !== 'admin' && role !== 'member') {
|
|
199
|
+
throw new Error(`role must be admin or member (got '${role}').`);
|
|
200
|
+
}
|
|
201
|
+
const target = await requireSpace(o.space);
|
|
202
|
+
const member = resolveMemberFromList(await listSpaceMembers(target.id), email);
|
|
203
|
+
if (!member)
|
|
204
|
+
throw new Error(`${email} is not in ${target.name}.`);
|
|
205
|
+
const updated = await updateSpaceMemberRole(target.id, member.user_id, role);
|
|
206
|
+
if (json)
|
|
207
|
+
return console.log(JSON.stringify(updated, null, 2));
|
|
208
|
+
console.log(chalk.green(`${email} is now ${role} in ${target.name}.`));
|
|
209
|
+
}, { json });
|
|
210
|
+
});
|
|
211
|
+
space
|
|
212
|
+
.command('remove <email>')
|
|
213
|
+
.description('Remove a member (or yourself) from a space')
|
|
214
|
+
.option('--space <space>', 'Which space (defaults to your only one)')
|
|
215
|
+
.option('--json', 'Machine-readable output')
|
|
216
|
+
.action((email, o, command) => {
|
|
217
|
+
const json = !!o.json || !!command.optsWithGlobals().json;
|
|
218
|
+
return runOrDie(async () => {
|
|
219
|
+
const target = await requireSpace(o.space);
|
|
220
|
+
const member = resolveMemberFromList(await listSpaceMembers(target.id), email);
|
|
221
|
+
if (!member)
|
|
222
|
+
throw new Error(`${email} is not in ${target.name}.`);
|
|
223
|
+
await removeSpaceMember(target.id, member.user_id);
|
|
224
|
+
if (json)
|
|
225
|
+
return console.log(JSON.stringify({ removed: true, email, space: target.slug }, null, 2));
|
|
226
|
+
console.log(chalk.green(`Removed ${email} from ${target.name}.`));
|
|
227
|
+
}, { json });
|
|
228
|
+
});
|
|
229
|
+
}
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
* second renderer. Interactive routing and the browse loop live in
|
|
11
11
|
* `sessions-picker-factory.ts` (shared with the computer twin).
|
|
12
12
|
*/
|
|
13
|
+
import { showFile } from '../lib/open-url.js';
|
|
13
14
|
import chalk from 'chalk';
|
|
14
15
|
import { itemPicker } from '../lib/picker.js';
|
|
15
16
|
import { isPromptCancelled } from './utils.js';
|
|
16
17
|
import { buildPreview } from './sessions-picker.js';
|
|
17
18
|
import { createSessionsPickerCommand } from './sessions-picker-factory.js';
|
|
18
|
-
import { runBrowserSessions, buildBrowserSessionRows, matchesBrowserSessionRow,
|
|
19
|
+
import { runBrowserSessions, buildBrowserSessionRows, matchesBrowserSessionRow, formatBytes, } from '../lib/browser/sessions-list.js';
|
|
19
20
|
import { formatRelativeTime } from '../lib/session/relative-time.js';
|
|
20
21
|
const KIND_LABEL = {
|
|
21
22
|
screenshot: 'shots',
|
|
@@ -89,11 +90,12 @@ function formatArtifactLabel(a) {
|
|
|
89
90
|
const age = formatRelativeTime(new Date(a.mtimeMs).toISOString());
|
|
90
91
|
return `${age.padEnd(11)} ${a.name.padEnd(34)} ${formatBytes(a.bytes).padStart(8)}`;
|
|
91
92
|
}
|
|
92
|
-
/** Open one artifact
|
|
93
|
-
*
|
|
94
|
-
|
|
93
|
+
/** Open one artifact, printing its path (matches the non-interactive `--open`
|
|
94
|
+
* behavior) and any open failure. Routes through the same viewer seam as
|
|
95
|
+
* `--open`, so the interactive and non-interactive halves cannot diverge. */
|
|
96
|
+
async function openAndReport(a) {
|
|
95
97
|
console.log(a.path);
|
|
96
|
-
if (
|
|
98
|
+
if ((await showFile(a.path)).via === 'none')
|
|
97
99
|
console.error(`Could not open ${a.path}`);
|
|
98
100
|
}
|
|
99
101
|
/** Second-level picker over one row's captures, newest first. Enter opens the
|
|
@@ -136,12 +138,12 @@ const browserSessionsPicker = createSessionsPickerCommand({
|
|
|
136
138
|
return;
|
|
137
139
|
}
|
|
138
140
|
if (row.artifacts.length === 1) {
|
|
139
|
-
openAndReport(row.artifacts[0]);
|
|
141
|
+
await openAndReport(row.artifacts[0]);
|
|
140
142
|
return;
|
|
141
143
|
}
|
|
142
144
|
const artifact = await pickArtifact(row);
|
|
143
145
|
if (artifact)
|
|
144
|
-
openAndReport(artifact);
|
|
146
|
+
await openAndReport(artifact);
|
|
145
147
|
},
|
|
146
148
|
});
|
|
147
149
|
/** True when the interactive picker should open instead of the printed table:
|