@phnx-labs/agents-cli 1.22.44 → 1.22.45
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 +8 -0
- package/dist/bootstrap.js +1 -6
- package/dist/cli/command-registry.d.ts +0 -2
- package/dist/cli/command-registry.js +0 -4
- package/dist/commands/accounts.d.ts +1 -9
- package/dist/commands/accounts.js +12 -90
- package/dist/commands/insights.js +82 -154
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
- package/dist/lib/startup/command-registry.d.ts +2 -1
- package/dist/lib/startup/command-registry.js +5 -2
- package/package.json +1 -1
- package/dist/commands/auth.d.ts +0 -9
- package/dist/commands/auth.js +0 -112
- package/dist/commands/org.d.ts +0 -11
- package/dist/commands/org.js +0 -228
- package/dist/lib/entitlement.d.ts +0 -31
- package/dist/lib/entitlement.js +0 -137
- package/dist/lib/prix-account.d.ts +0 -159
- package/dist/lib/prix-account.js +0 -215
|
@@ -3,9 +3,22 @@
|
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
5
|
<key>files</key>
|
|
6
|
-
<dict
|
|
6
|
+
<dict>
|
|
7
|
+
<key>Resources/AppIcon.icns</key>
|
|
8
|
+
<data>
|
|
9
|
+
jOjZVimcFRHoP2VPzgn8uM8mjkA=
|
|
10
|
+
</data>
|
|
11
|
+
</dict>
|
|
7
12
|
<key>files2</key>
|
|
8
|
-
<dict
|
|
13
|
+
<dict>
|
|
14
|
+
<key>Resources/AppIcon.icns</key>
|
|
15
|
+
<dict>
|
|
16
|
+
<key>hash2</key>
|
|
17
|
+
<data>
|
|
18
|
+
GFvSLeNYJ3ASxW3OzcuiB4aID0gZUBkpozmWZkbTFNw=
|
|
19
|
+
</data>
|
|
20
|
+
</dict>
|
|
21
|
+
</dict>
|
|
9
22
|
<key>rules</key>
|
|
10
23
|
<dict>
|
|
11
24
|
<key>^Resources/</key>
|
|
@@ -30,7 +30,8 @@ export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
|
30
30
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
31
31
|
* unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
|
|
32
32
|
* `agents fleet apply` / `agents devices apply`. `beta` nested under
|
|
33
|
-
* `agents setup beta` (RUSH-2981).
|
|
33
|
+
* `agents setup beta` (RUSH-2981). `auth` and `org` (the Prix-coupled account
|
|
34
|
+
* layer) were removed pending the Phoenix-backed replacement (RUSH-2581).
|
|
34
35
|
*/
|
|
35
36
|
export declare const RETIRED_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
36
37
|
export declare function isKnownTopLevelCommand(name: string): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const LOADED_COMMAND_NAMES = [
|
|
2
|
-
'accounts', '
|
|
2
|
+
'accounts', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
3
3
|
'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use', 'list',
|
|
4
4
|
'remove', 'rm', 'purge', 'update', 'prune', 'import', 'registry', 'search', 'install',
|
|
5
5
|
'routines', 'monitors', 'projects', 'run', 'open', 'reconnect', 'fork', 'config',
|
|
@@ -50,10 +50,13 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
|
|
|
50
50
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
51
51
|
* unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
|
|
52
52
|
* `agents fleet apply` / `agents devices apply`. `beta` nested under
|
|
53
|
-
* `agents setup beta` (RUSH-2981).
|
|
53
|
+
* `agents setup beta` (RUSH-2981). `auth` and `org` (the Prix-coupled account
|
|
54
|
+
* layer) were removed pending the Phoenix-backed replacement (RUSH-2581).
|
|
54
55
|
*/
|
|
55
56
|
export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
56
57
|
'webhook',
|
|
58
|
+
'auth',
|
|
59
|
+
'org',
|
|
57
60
|
'serve',
|
|
58
61
|
'login',
|
|
59
62
|
'logout',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.45",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/dist/commands/auth.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
/**
|
|
3
|
-
* `agents auth login` — RFC 8628 device-code flow against the already-shipped
|
|
4
|
-
* `/api/v1/auth/device/*` routes. If the live API rejects the request shape
|
|
5
|
-
* (a route this spec missed, or the endpoint moved), this fails loud with the
|
|
6
|
-
* `rush login` fallback rather than silently stubbing a fake session.
|
|
7
|
-
*/
|
|
8
|
-
export declare function runAuthLogin(): Promise<void>;
|
|
9
|
-
export declare function registerAuthCommand(program: Command): void;
|
package/dist/commands/auth.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { setHelpSections } from '../lib/help.js';
|
|
3
|
-
import { runOrDie } from '../lib/format.js';
|
|
4
|
-
import { clearPrixSession, fetchWhoAmI, pollDeviceToken, PrixApiError, readPrixSession, resolvePrixToken, startDeviceAuthorization, writePrixSession, } from '../lib/prix-account.js';
|
|
5
|
-
import { registerAuthSpaceCommand } from './org.js';
|
|
6
|
-
function sleep(ms) {
|
|
7
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* `agents auth login` — RFC 8628 device-code flow against the already-shipped
|
|
11
|
-
* `/api/v1/auth/device/*` routes. If the live API rejects the request shape
|
|
12
|
-
* (a route this spec missed, or the endpoint moved), this fails loud with the
|
|
13
|
-
* `rush login` fallback rather than silently stubbing a fake session.
|
|
14
|
-
*/
|
|
15
|
-
export async function runAuthLogin() {
|
|
16
|
-
let authorization;
|
|
17
|
-
try {
|
|
18
|
-
authorization = await startDeviceAuthorization();
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
22
|
-
const existing = resolvePrixToken();
|
|
23
|
-
if (existing?.source === 'rush') {
|
|
24
|
-
console.log(chalk.yellow(`Device login is unavailable right now (${message}), but you're already signed in via 'rush login'.`));
|
|
25
|
-
console.log("Run 'agents auth whoami' to confirm.");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
throw new Error(`Device login is unavailable right now (${message}). Run 'rush login' instead, then 'agents auth whoami' to confirm.`);
|
|
29
|
-
}
|
|
30
|
-
console.log(`To sign in, open:\n\n ${chalk.cyan(authorization.verification_uri_complete)}\n`);
|
|
31
|
-
console.log(chalk.gray(`(code: ${authorization.user_code})`));
|
|
32
|
-
const deadline = Date.now() + authorization.expires_in * 1000;
|
|
33
|
-
let interval = Math.max(authorization.interval, 1) * 1000;
|
|
34
|
-
while (Date.now() < deadline) {
|
|
35
|
-
await sleep(interval);
|
|
36
|
-
const poll = await pollDeviceToken(authorization.device_code);
|
|
37
|
-
if (poll.status === 'pending')
|
|
38
|
-
continue;
|
|
39
|
-
if (poll.status === 'slow_down') {
|
|
40
|
-
interval += 1000;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (poll.status === 'expired')
|
|
44
|
-
throw new Error('Login code expired. Run `agents auth login` again.');
|
|
45
|
-
if (poll.status === 'denied')
|
|
46
|
-
throw new Error('Login was denied.');
|
|
47
|
-
writePrixSession({
|
|
48
|
-
access_token: poll.access_token,
|
|
49
|
-
refresh_token: poll.refresh_token,
|
|
50
|
-
expires_at: poll.expires_in ? Date.now() + poll.expires_in * 1000 : undefined,
|
|
51
|
-
email: poll.user.email,
|
|
52
|
-
userId: poll.user.id,
|
|
53
|
-
});
|
|
54
|
-
console.log(chalk.green(`Signed in as ${poll.user.email}.`));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
throw new Error('Login code expired. Run `agents auth login` again.');
|
|
58
|
-
}
|
|
59
|
-
async function runAuthWhoami(json) {
|
|
60
|
-
const resolved = resolvePrixToken();
|
|
61
|
-
if (!resolved)
|
|
62
|
-
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
63
|
-
let who;
|
|
64
|
-
try {
|
|
65
|
-
who = await fetchWhoAmI(resolved.token);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
if (err instanceof PrixApiError && err.status === 401) {
|
|
69
|
-
throw new Error(`Signed-in token is no longer valid (${resolved.source === 'agents' ? "run 'agents auth login'" : "run 'rush login'"} again).`);
|
|
70
|
-
}
|
|
71
|
-
throw err;
|
|
72
|
-
}
|
|
73
|
-
if (json) {
|
|
74
|
-
console.log(JSON.stringify({ ...who, source: resolved.source }, null, 2));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
console.log(`${chalk.bold(who.email)} ${chalk.gray(who.userId)}`);
|
|
78
|
-
console.log(chalk.gray(`signed in via ${resolved.source === 'agents' ? "'agents auth login'" : "'rush login' (shared session)"}`));
|
|
79
|
-
}
|
|
80
|
-
function runAuthLogout() {
|
|
81
|
-
const cleared = clearPrixSession();
|
|
82
|
-
const rushRemains = resolvePrixToken()?.source === 'rush';
|
|
83
|
-
if (!cleared && !readPrixSession()) {
|
|
84
|
-
console.log(chalk.gray('Not signed in via `agents auth login` — nothing to clear.'));
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
console.log(chalk.green('Signed out.'));
|
|
88
|
-
}
|
|
89
|
-
if (rushRemains) {
|
|
90
|
-
console.log(chalk.gray("You're still signed in via `rush login` — `agents auth whoami` will use that session. Run `rush logout` to clear it too."));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
export function registerAuthCommand(program) {
|
|
94
|
-
const auth = program.command('auth').description('Sign in to your Prix account (spaces, paid tiers)');
|
|
95
|
-
auth.command('login').description('Sign in via the device-code flow').action(() => runOrDie(() => runAuthLogin()));
|
|
96
|
-
auth.command('whoami').description('Show the signed-in account').option('--json', 'Machine-readable output')
|
|
97
|
-
.action((o, command) => {
|
|
98
|
-
const json = !!(o.json || command.optsWithGlobals().json);
|
|
99
|
-
return runOrDie(() => runAuthWhoami(json), { json });
|
|
100
|
-
});
|
|
101
|
-
auth.command('logout').description('Clear the local `agents auth login` session').action(() => runOrDie(() => runAuthLogout()));
|
|
102
|
-
registerAuthSpaceCommand(auth);
|
|
103
|
-
setHelpSections(auth, {
|
|
104
|
-
examples: `agents auth login
|
|
105
|
-
agents auth whoami
|
|
106
|
-
agents auth whoami --json
|
|
107
|
-
agents auth logout
|
|
108
|
-
agents auth space create acme-team
|
|
109
|
-
agents auth space invite dev@example.com --role admin`,
|
|
110
|
-
notes: "`agents auth login` writes its own session, separate from \`rush login\`'s \`~/.rush/user.yaml\` — \`agents auth logout\` never signs you out of \`rush\`. If you're already signed in via \`rush login\`, \`agents auth whoami\`/\`agents auth space\` use that session automatically; no separate login is required. \`agents org\` is a deprecated alias of \`agents auth space\`.",
|
|
111
|
-
});
|
|
112
|
-
}
|
package/dist/commands/org.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
/** `--role` defaults to member when omitted; anything present must be a real role — never silently coerced. */
|
|
3
|
-
export declare function resolveInviteRole(raw: string | undefined): 'admin' | 'member';
|
|
4
|
-
/** Validate a role argument before any network call — same rule the backend enforces server-side. */
|
|
5
|
-
export declare function parseRole(raw: string): 'admin' | 'member';
|
|
6
|
-
/** One-line deprecation for the leftover top-level `agents org` spelling. */
|
|
7
|
-
export declare function printOrgDeprecation(): void;
|
|
8
|
-
/** Canonical home: `agents auth space …`. */
|
|
9
|
-
export declare function registerAuthSpaceCommand(auth: Command): void;
|
|
10
|
-
/** Leftover top-level spelling — same tree, deprecation on every path. */
|
|
11
|
-
export declare function registerOrgCommand(program: Command): void;
|
package/dist/commands/org.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { setHelpSections } from '../lib/help.js';
|
|
3
|
-
import { runOrDie } from '../lib/format.js';
|
|
4
|
-
import { createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, removeSpaceMember, resolveMemberFromList, resolvePrixToken, resolveSpaceFromList, slugify, updateSpaceMemberRole, } from '../lib/prix-account.js';
|
|
5
|
-
/** Every `agents org` subcommand needs a token up front — one place to say so. */
|
|
6
|
-
function requireToken() {
|
|
7
|
-
if (!resolvePrixToken())
|
|
8
|
-
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
9
|
-
}
|
|
10
|
-
async function resolveSpace(explicit) {
|
|
11
|
-
const spaces = await listSpaces();
|
|
12
|
-
return resolveSpaceFromList(spaces, explicit);
|
|
13
|
-
}
|
|
14
|
-
function printSpace(space) {
|
|
15
|
-
console.log(`${chalk.bold(space.name)} ${chalk.gray(space.slug)} ${chalk.gray(space.id)}`);
|
|
16
|
-
console.log(` role: ${space.user_role}${space.organization_id ? ` org: ${space.organization_id}` : ''}`);
|
|
17
|
-
}
|
|
18
|
-
function printMembers(members) {
|
|
19
|
-
if (!members.length) {
|
|
20
|
-
console.log(chalk.gray(' No members.'));
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
for (const m of members)
|
|
24
|
-
console.log(` ${chalk.cyan(m.email)} ${m.role} ${chalk.gray(m.user_id)}`);
|
|
25
|
-
}
|
|
26
|
-
async function runCreate(name, o) {
|
|
27
|
-
requireToken();
|
|
28
|
-
const space = await createSpace({ name, slug: o.slug ?? slugify(name), description: o.description });
|
|
29
|
-
if (o.json) {
|
|
30
|
-
console.log(JSON.stringify(space, null, 2));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
console.log(chalk.green(`Created space '${space.name}' (${space.slug}).`));
|
|
34
|
-
printSpace(space);
|
|
35
|
-
}
|
|
36
|
-
async function runList(o) {
|
|
37
|
-
requireToken();
|
|
38
|
-
const spaces = await listSpaces();
|
|
39
|
-
if (o.json) {
|
|
40
|
-
console.log(JSON.stringify(spaces, null, 2));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (!spaces.length) {
|
|
44
|
-
console.log(chalk.gray("No spaces. Create one with 'agents auth space create <name>'."));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
for (const space of spaces)
|
|
48
|
-
printSpace(space);
|
|
49
|
-
}
|
|
50
|
-
async function runView(spaceArg, o) {
|
|
51
|
-
requireToken();
|
|
52
|
-
const space = await resolveSpace(spaceArg);
|
|
53
|
-
if (o.json) {
|
|
54
|
-
console.log(JSON.stringify(space, null, 2));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
printSpace(space);
|
|
58
|
-
}
|
|
59
|
-
/** `--role` defaults to member when omitted; anything present must be a real role — never silently coerced. */
|
|
60
|
-
export function resolveInviteRole(raw) {
|
|
61
|
-
return raw === undefined ? 'member' : parseRole(raw);
|
|
62
|
-
}
|
|
63
|
-
async function runInvite(email, o) {
|
|
64
|
-
requireToken();
|
|
65
|
-
const role = resolveInviteRole(o.role);
|
|
66
|
-
const space = await resolveSpace(o.space);
|
|
67
|
-
const result = await createSpaceInvite(space.id, email, role);
|
|
68
|
-
if (o.json) {
|
|
69
|
-
console.log(JSON.stringify(result, null, 2));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (result.member_added)
|
|
73
|
-
console.log(chalk.green(`Added ${email} to '${space.name}' as ${role}.`));
|
|
74
|
-
else
|
|
75
|
-
console.log(chalk.green(`Invited ${email} to '${space.name}' as ${role}. They'll get an email.`));
|
|
76
|
-
}
|
|
77
|
-
async function runMembers(spaceArg, o) {
|
|
78
|
-
requireToken();
|
|
79
|
-
const space = await resolveSpace(spaceArg);
|
|
80
|
-
const members = await listSpaceMembers(space.id);
|
|
81
|
-
if (o.json) {
|
|
82
|
-
console.log(JSON.stringify(members, null, 2));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
console.log(chalk.bold(`${space.name} (${members.length} member${members.length === 1 ? '' : 's'})`));
|
|
86
|
-
printMembers(members);
|
|
87
|
-
}
|
|
88
|
-
/** Validate a role argument before any network call — same rule the backend enforces server-side. */
|
|
89
|
-
export function parseRole(raw) {
|
|
90
|
-
if (raw === 'admin' || raw === 'member')
|
|
91
|
-
return raw;
|
|
92
|
-
throw new Error(`role must be 'admin' or 'member', got '${raw}'.`);
|
|
93
|
-
}
|
|
94
|
-
async function runRole(email, roleRaw, o) {
|
|
95
|
-
requireToken();
|
|
96
|
-
const role = parseRole(roleRaw);
|
|
97
|
-
const space = await resolveSpace(o.space);
|
|
98
|
-
const members = await listSpaceMembers(space.id);
|
|
99
|
-
const member = resolveMemberFromList(members, email);
|
|
100
|
-
const result = await updateSpaceMemberRole(space.id, member.user_id, role);
|
|
101
|
-
if (o.json) {
|
|
102
|
-
console.log(JSON.stringify(result, null, 2));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
console.log(chalk.green(`${email} is now ${role} in '${space.name}'.`));
|
|
106
|
-
}
|
|
107
|
-
async function runRemove(email, o) {
|
|
108
|
-
requireToken();
|
|
109
|
-
const space = await resolveSpace(o.space);
|
|
110
|
-
const members = await listSpaceMembers(space.id);
|
|
111
|
-
const member = resolveMemberFromList(members, email);
|
|
112
|
-
await removeSpaceMember(space.id, member.user_id);
|
|
113
|
-
if (o.json) {
|
|
114
|
-
console.log(JSON.stringify({ removed: email, space: space.slug }, null, 2));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
console.log(chalk.green(`Removed ${email} from '${space.name}'.`));
|
|
118
|
-
}
|
|
119
|
-
async function runLeave(spaceArg, o) {
|
|
120
|
-
requireToken();
|
|
121
|
-
const resolved = resolvePrixToken();
|
|
122
|
-
if (!resolved)
|
|
123
|
-
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
124
|
-
const who = await fetchWhoAmI(resolved.token);
|
|
125
|
-
const space = await resolveSpace(spaceArg);
|
|
126
|
-
if (space.owner_user_id === who.userId) {
|
|
127
|
-
throw new Error(`You own '${space.name}'. Transfer ownership or delete the space instead of leaving it.`);
|
|
128
|
-
}
|
|
129
|
-
await removeSpaceMember(space.id, who.userId);
|
|
130
|
-
if (o.json) {
|
|
131
|
-
console.log(JSON.stringify({ left: space.slug }, null, 2));
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
console.log(chalk.green(`Left '${space.name}'.`));
|
|
135
|
-
}
|
|
136
|
-
/** One-line deprecation for the leftover top-level `agents org` spelling. */
|
|
137
|
-
export function printOrgDeprecation() {
|
|
138
|
-
console.error(chalk.yellow('agents org is deprecated — use `agents auth space`.'));
|
|
139
|
-
}
|
|
140
|
-
/** Shared space CRUD tree — canonical under `auth space`, leftover under `org`. */
|
|
141
|
-
function attachSpaceCommands(parent, opts) {
|
|
142
|
-
const before = () => {
|
|
143
|
-
if (opts.deprecated)
|
|
144
|
-
printOrgDeprecation();
|
|
145
|
-
};
|
|
146
|
-
const jsonFlag = (o, command) => !!(o.json || command.optsWithGlobals().json);
|
|
147
|
-
parent.command('create <name>').description('Create a space (free tier: 1 owned space)')
|
|
148
|
-
.option('--slug <slug>', 'Override the derived slug')
|
|
149
|
-
.option('--description <text>', 'Optional description')
|
|
150
|
-
.option('--json', 'Machine-readable output')
|
|
151
|
-
.action((name, o, command) => {
|
|
152
|
-
before();
|
|
153
|
-
const json = jsonFlag(o, command);
|
|
154
|
-
return runOrDie(() => runCreate(name, { ...o, json }), { json });
|
|
155
|
-
});
|
|
156
|
-
parent.command('list').description('List spaces you own or belong to').option('--json', 'Machine-readable output')
|
|
157
|
-
.action((o, command) => {
|
|
158
|
-
before();
|
|
159
|
-
const json = jsonFlag(o, command);
|
|
160
|
-
return runOrDie(() => runList({ json }), { json });
|
|
161
|
-
});
|
|
162
|
-
parent.command('view [space]').description('Show one space (defaults to your only space)').option('--json', 'Machine-readable output')
|
|
163
|
-
.action((space, o, command) => {
|
|
164
|
-
before();
|
|
165
|
-
const json = jsonFlag(o, command);
|
|
166
|
-
return runOrDie(() => runView(space, { json }), { json });
|
|
167
|
-
});
|
|
168
|
-
parent.command('invite <email>').description('Invite (or directly add) a member')
|
|
169
|
-
.option('--role <role>', 'admin | member', 'member')
|
|
170
|
-
.option('--space <id-or-slug>', 'Space to invite into (defaults to your only space)')
|
|
171
|
-
.option('--json', 'Machine-readable output')
|
|
172
|
-
.action((email, o, command) => {
|
|
173
|
-
before();
|
|
174
|
-
const json = jsonFlag(o, command);
|
|
175
|
-
return runOrDie(() => runInvite(email, { ...o, json }), { json });
|
|
176
|
-
});
|
|
177
|
-
parent.command('members [space]').description("List a space's members").option('--json', 'Machine-readable output')
|
|
178
|
-
.action((space, o, command) => {
|
|
179
|
-
before();
|
|
180
|
-
const json = jsonFlag(o, command);
|
|
181
|
-
return runOrDie(() => runMembers(space, { json }), { json });
|
|
182
|
-
});
|
|
183
|
-
parent.command('role <email> <role>').description("Change a member's role (owner-only for admin)")
|
|
184
|
-
.option('--space <id-or-slug>', 'Space to change (defaults to your only space)')
|
|
185
|
-
.option('--json', 'Machine-readable output')
|
|
186
|
-
.action((email, role, o, command) => {
|
|
187
|
-
before();
|
|
188
|
-
const json = jsonFlag(o, command);
|
|
189
|
-
return runOrDie(() => runRole(email, role, { ...o, json }), { json });
|
|
190
|
-
});
|
|
191
|
-
parent.command('remove <email>').description('Remove a member from a space')
|
|
192
|
-
.option('--space <id-or-slug>', 'Space to remove from (defaults to your only space)')
|
|
193
|
-
.option('--json', 'Machine-readable output')
|
|
194
|
-
.action((email, o, command) => {
|
|
195
|
-
before();
|
|
196
|
-
const json = jsonFlag(o, command);
|
|
197
|
-
return runOrDie(() => runRemove(email, { ...o, json }), { json });
|
|
198
|
-
});
|
|
199
|
-
parent.command('leave [space]').description('Leave a space you do not own').option('--json', 'Machine-readable output')
|
|
200
|
-
.action((space, o, command) => {
|
|
201
|
-
before();
|
|
202
|
-
const json = jsonFlag(o, command);
|
|
203
|
-
return runOrDie(() => runLeave(space, { json }), { json });
|
|
204
|
-
});
|
|
205
|
-
setHelpSections(parent, {
|
|
206
|
-
examples: `${opts.prefix} create acme-team
|
|
207
|
-
${opts.prefix} list
|
|
208
|
-
${opts.prefix} view acme-team
|
|
209
|
-
${opts.prefix} invite dev@example.com --role admin
|
|
210
|
-
${opts.prefix} members
|
|
211
|
-
${opts.prefix} role dev@example.com admin
|
|
212
|
-
${opts.prefix} remove dev@example.com
|
|
213
|
-
${opts.prefix} leave acme-team`,
|
|
214
|
-
notes: opts.deprecated
|
|
215
|
-
? 'Deprecated alias of `agents auth space`. Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space.'
|
|
216
|
-
: 'Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space. The leftover `agents org` spelling still works and prints a deprecation line.',
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
/** Canonical home: `agents auth space …`. */
|
|
220
|
-
export function registerAuthSpaceCommand(auth) {
|
|
221
|
-
const space = auth.command('space').description('Create and manage a Prix space (invite collaborators)');
|
|
222
|
-
attachSpaceCommands(space, { prefix: 'agents auth space' });
|
|
223
|
-
}
|
|
224
|
-
/** Leftover top-level spelling — same tree, deprecation on every path. */
|
|
225
|
-
export function registerOrgCommand(program) {
|
|
226
|
-
const org = program.command('org').description('Deprecated alias of `agents auth space` — create and manage a Prix space');
|
|
227
|
-
attachSpaceCommands(org, { prefix: 'agents org', deprecated: true });
|
|
228
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/** How long a cached tier is trusted before the next call re-fetches. */
|
|
2
|
-
export declare const ENTITLEMENT_CACHE_TTL_MS: number;
|
|
3
|
-
export type EntitlementSource = 'live' | 'cache' | 'offline' | 'no-session';
|
|
4
|
-
export interface EntitlementTier {
|
|
5
|
-
/** The raw tier name from the backend ('free', 'admin', or a paid tier name). */
|
|
6
|
-
tierName: string;
|
|
7
|
-
/** true for 'admin' or any tier name other than 'free'. */
|
|
8
|
-
isPaid: boolean;
|
|
9
|
-
/** Where this value came from — mainly for diagnostics/tests. */
|
|
10
|
-
source: EntitlementSource;
|
|
11
|
-
}
|
|
12
|
-
export declare function setEntitlementUserYamlPathForTest(value: string | null): void;
|
|
13
|
-
export declare function setEntitlementCachePathForTest(value: string | null): void;
|
|
14
|
-
export declare function setEntitlementFetchForTest(fn: typeof globalThis.fetch): void;
|
|
15
|
-
export declare function entitlementCachePath(): string;
|
|
16
|
-
/**
|
|
17
|
-
* The live plan tier, cache-first with a TTL.
|
|
18
|
-
*
|
|
19
|
-
* - No `~/.rush/user.yaml` at all → free, no network call.
|
|
20
|
-
* - A fresh cache entry (within {@link ENTITLEMENT_CACHE_TTL_MS}) is returned
|
|
21
|
-
* with no network call.
|
|
22
|
-
* - A stale or missing cache triggers a live fetch; on success the result is
|
|
23
|
-
* cached and returned.
|
|
24
|
-
* - A failed fetch (offline, timeout, non-2xx) falls back to a stale cache if
|
|
25
|
-
* one exists — never silently drops a known-paid account to free just
|
|
26
|
-
* because the network hiccuped — and only falls back to free when there is
|
|
27
|
-
* no cache at all to fall back to.
|
|
28
|
-
*/
|
|
29
|
-
export declare function getTier(): Promise<EntitlementTier>;
|
|
30
|
-
/** Per-harness account cap for a tier: 3 on free, 10 on paid/admin. */
|
|
31
|
-
export declare function accountCapForTier(tier: Pick<EntitlementTier, 'isPaid'>): number;
|
package/dist/lib/entitlement.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan-tier entitlement — the one place agents-cli reads a user's live billing
|
|
3
|
-
* tier for plan gates (the `agents accounts` per-harness cap, the `agents
|
|
4
|
-
* insights` paid split). Fetches `GET /api/v1/billing/subscription?agent=agi-cli`
|
|
5
|
-
* using the Rush session token from `~/.rush/user.yaml` (same read pattern as
|
|
6
|
-
* `lib/cloud/rush.ts` / `lib/secrets/drivers/rush.ts` — this module does not
|
|
7
|
-
* import either, since neither exports its token reader), caches the result on
|
|
8
|
-
* disk with a TTL, and stays offline-tolerant: a stale cache is honored over a
|
|
9
|
-
* failed network call rather than silently dropping a paid account to free, and
|
|
10
|
-
* no session file at all resolves straight to the free tier with no request.
|
|
11
|
-
*/
|
|
12
|
-
import * as fs from 'node:fs';
|
|
13
|
-
import * as os from 'node:os';
|
|
14
|
-
import * as path from 'node:path';
|
|
15
|
-
import * as yaml from 'yaml';
|
|
16
|
-
import { getCacheDir } from './state.js';
|
|
17
|
-
import { atomicWriteFileSync, ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
18
|
-
const PROXY_BASE = process.env.RUSH_PROXY_BASE ?? 'https://api.prix.dev';
|
|
19
|
-
const SUBSCRIPTION_PATH = '/api/v1/billing/subscription?agent=agi-cli';
|
|
20
|
-
/** How long a cached tier is trusted before the next call re-fetches. */
|
|
21
|
-
export const ENTITLEMENT_CACHE_TTL_MS = 15 * 60_000;
|
|
22
|
-
/** Same shape as bootstrap.ts's fetchNpmPackageMetadata — a hung connection
|
|
23
|
-
* (dead peer, a firewall dropping packets silently) must not block every
|
|
24
|
-
* `accounts add`/`insights` call indefinitely once the cache goes stale. */
|
|
25
|
-
const FETCH_TIMEOUT_MS = 5000;
|
|
26
|
-
const FREE_TIER = { tierName: 'free', isPaid: false };
|
|
27
|
-
// ─── Injectable seams (tests only — the lib's own real read/write paths) ────
|
|
28
|
-
let userYamlPathOverride = null;
|
|
29
|
-
let cachePathOverride = null;
|
|
30
|
-
let fetchImpl = globalThis.fetch;
|
|
31
|
-
export function setEntitlementUserYamlPathForTest(value) {
|
|
32
|
-
userYamlPathOverride = value;
|
|
33
|
-
}
|
|
34
|
-
export function setEntitlementCachePathForTest(value) {
|
|
35
|
-
cachePathOverride = value;
|
|
36
|
-
}
|
|
37
|
-
export function setEntitlementFetchForTest(fn) {
|
|
38
|
-
fetchImpl = fn;
|
|
39
|
-
}
|
|
40
|
-
function userYamlPath() {
|
|
41
|
-
return userYamlPathOverride ?? path.join(os.homedir(), '.rush', 'user.yaml');
|
|
42
|
-
}
|
|
43
|
-
export function entitlementCachePath() {
|
|
44
|
-
return cachePathOverride ?? path.join(getCacheDir(), '.entitlement-cache.json');
|
|
45
|
-
}
|
|
46
|
-
function readRushToken() {
|
|
47
|
-
const file = userYamlPath();
|
|
48
|
-
if (!fs.existsSync(file))
|
|
49
|
-
return null;
|
|
50
|
-
try {
|
|
51
|
-
const data = yaml.parse(fs.readFileSync(file, 'utf-8'));
|
|
52
|
-
return data?.session?.access_token || null;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function readCache() {
|
|
59
|
-
try {
|
|
60
|
-
const parsed = JSON.parse(fs.readFileSync(entitlementCachePath(), 'utf-8'));
|
|
61
|
-
if (parsed?.version === 1 && typeof parsed.tierName === 'string' && typeof parsed.fetchedAt === 'number')
|
|
62
|
-
return parsed;
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
// missing or corrupt — treat as no cache
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
/** Best-effort write; a failed cache write just means the next call re-fetches. */
|
|
70
|
-
function writeCache(tierName, isPaid) {
|
|
71
|
-
try {
|
|
72
|
-
const target = entitlementCachePath();
|
|
73
|
-
ensureLockTarget(target);
|
|
74
|
-
withFileLock(target, () => {
|
|
75
|
-
const entry = { version: 1, tierName, isPaid, fetchedAt: Date.now() };
|
|
76
|
-
atomicWriteFileSync(target, JSON.stringify(entry, null, 2));
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
// best-effort
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Only `tierName: "free"` (or an absent tier name) reads as free — every other
|
|
85
|
-
* name (`admin`, or a real paid-tier name once agi-cli has a pricing manifest,
|
|
86
|
-
* see the integration spec §4.4 G3) is treated as paid. Backend tier config for
|
|
87
|
-
* agi-cli doesn't exist yet, so this deliberately does not lean on
|
|
88
|
-
* `hasSubscription`/`needsUpgrade` nuance the backend isn't populating meaningfully
|
|
89
|
-
* for this agent today.
|
|
90
|
-
*/
|
|
91
|
-
function classifySubscription(sub) {
|
|
92
|
-
const tierName = typeof sub.tierName === 'string' && sub.tierName ? sub.tierName : 'free';
|
|
93
|
-
return { tierName, isPaid: tierName !== 'free' };
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The live plan tier, cache-first with a TTL.
|
|
97
|
-
*
|
|
98
|
-
* - No `~/.rush/user.yaml` at all → free, no network call.
|
|
99
|
-
* - A fresh cache entry (within {@link ENTITLEMENT_CACHE_TTL_MS}) is returned
|
|
100
|
-
* with no network call.
|
|
101
|
-
* - A stale or missing cache triggers a live fetch; on success the result is
|
|
102
|
-
* cached and returned.
|
|
103
|
-
* - A failed fetch (offline, timeout, non-2xx) falls back to a stale cache if
|
|
104
|
-
* one exists — never silently drops a known-paid account to free just
|
|
105
|
-
* because the network hiccuped — and only falls back to free when there is
|
|
106
|
-
* no cache at all to fall back to.
|
|
107
|
-
*/
|
|
108
|
-
export async function getTier() {
|
|
109
|
-
const token = readRushToken();
|
|
110
|
-
if (!token)
|
|
111
|
-
return { ...FREE_TIER, source: 'no-session' };
|
|
112
|
-
const cached = readCache();
|
|
113
|
-
if (cached && Date.now() - cached.fetchedAt < ENTITLEMENT_CACHE_TTL_MS) {
|
|
114
|
-
return { tierName: cached.tierName, isPaid: cached.isPaid, source: 'cache' };
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const res = await fetchImpl(`${PROXY_BASE}${SUBSCRIPTION_PATH}`, {
|
|
118
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
119
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
120
|
-
});
|
|
121
|
-
if (!res.ok)
|
|
122
|
-
throw new Error(`billing/subscription responded ${res.status}`);
|
|
123
|
-
const sub = (await res.json());
|
|
124
|
-
const { tierName, isPaid } = classifySubscription(sub);
|
|
125
|
-
writeCache(tierName, isPaid);
|
|
126
|
-
return { tierName, isPaid, source: 'live' };
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
if (cached)
|
|
130
|
-
return { tierName: cached.tierName, isPaid: cached.isPaid, source: 'offline' };
|
|
131
|
-
return { ...FREE_TIER, source: 'offline' };
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/** Per-harness account cap for a tier: 3 on free, 10 on paid/admin. */
|
|
135
|
-
export function accountCapForTier(tier) {
|
|
136
|
-
return tier.isPaid ? 10 : 3;
|
|
137
|
-
}
|