@phnx-labs/agents-cli 1.22.44 → 1.22.46
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 +32 -0
- package/README.md +24 -0
- package/dist/bootstrap.js +1 -6
- package/dist/cli/command-registry.d.ts +0 -1
- package/dist/cli/command-registry.js +1 -3
- package/dist/commands/accounts.d.ts +1 -9
- package/dist/commands/accounts.js +12 -90
- package/dist/commands/auth.d.ts +0 -7
- package/dist/commands/auth.js +198 -83
- package/dist/commands/insights.js +82 -154
- package/dist/commands/view.js +1 -1
- package/dist/lib/accounting/usage.d.ts +22 -3
- package/dist/lib/accounting/usage.js +94 -12
- package/dist/lib/agent-spec/agents.js +6 -1
- package/dist/lib/cli-resources.js +17 -15
- package/dist/lib/devices/harness-inventory.js +20 -3
- package/dist/lib/exec.d.ts +20 -0
- package/dist/lib/exec.js +43 -6
- 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/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/probe.d.ts +8 -0
- package/dist/lib/probe.js +105 -0
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +5 -2
- package/dist/lib/view-types.d.ts +2 -2
- package/package.json +1 -1
- 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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phoenix ID — the typed surface commands use. Every route the account backend
|
|
3
|
+
* exposes is a function here; no command builds a URL or reads a token itself.
|
|
4
|
+
*/
|
|
5
|
+
import { type PhoenixSession } from './client.js';
|
|
6
|
+
export { PHOENIX_ID_BASE, PhoenixApiError, clearSession, readSession, sessionFilePath, writeSession, type PhoenixSession, } from './client.js';
|
|
7
|
+
export interface DeviceAuthorization {
|
|
8
|
+
device_code: string;
|
|
9
|
+
user_code: string;
|
|
10
|
+
verification_uri: string;
|
|
11
|
+
verification_uri_complete: string;
|
|
12
|
+
expires_in: number;
|
|
13
|
+
interval: number;
|
|
14
|
+
}
|
|
15
|
+
export interface WhoAmI {
|
|
16
|
+
userId: string;
|
|
17
|
+
email: string;
|
|
18
|
+
valid: true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* RFC 8628 poll outcomes. `pending` and `slow_down` are normal states of a
|
|
22
|
+
* login in progress, not failures — the server signals them through the error
|
|
23
|
+
* body, and this is where that wire detail stops.
|
|
24
|
+
*/
|
|
25
|
+
export type DevicePoll = {
|
|
26
|
+
status: 'authorized';
|
|
27
|
+
access_token: string;
|
|
28
|
+
user: {
|
|
29
|
+
email: string;
|
|
30
|
+
id: string;
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
status: 'pending';
|
|
34
|
+
} | {
|
|
35
|
+
status: 'slow_down';
|
|
36
|
+
} | {
|
|
37
|
+
status: 'expired';
|
|
38
|
+
} | {
|
|
39
|
+
status: 'denied';
|
|
40
|
+
};
|
|
41
|
+
export declare function startDeviceAuthorization(): Promise<DeviceAuthorization>;
|
|
42
|
+
export declare function pollDeviceToken(deviceCode: string): Promise<DevicePoll>;
|
|
43
|
+
export declare function fetchWhoAmI(token?: string): Promise<WhoAmI>;
|
|
44
|
+
export interface SpaceSummary {
|
|
45
|
+
id: string;
|
|
46
|
+
slug: string;
|
|
47
|
+
name: string;
|
|
48
|
+
organization_id: string | null;
|
|
49
|
+
owner_user_id: string;
|
|
50
|
+
invite_code?: string;
|
|
51
|
+
user_role: 'owner' | 'admin' | 'member';
|
|
52
|
+
created_at: string;
|
|
53
|
+
}
|
|
54
|
+
export interface SpaceMember {
|
|
55
|
+
user_id: string;
|
|
56
|
+
email: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
avatar_url?: string;
|
|
59
|
+
role: 'owner' | 'admin' | 'member';
|
|
60
|
+
joined_at: string;
|
|
61
|
+
}
|
|
62
|
+
export interface SpaceInvite {
|
|
63
|
+
id: string;
|
|
64
|
+
space_id: string;
|
|
65
|
+
email: string;
|
|
66
|
+
role: 'admin' | 'member';
|
|
67
|
+
invite_code: string;
|
|
68
|
+
created_at: string;
|
|
69
|
+
}
|
|
70
|
+
export type CreateInviteResult = {
|
|
71
|
+
invited: true;
|
|
72
|
+
email: string;
|
|
73
|
+
role: string;
|
|
74
|
+
member_added: true;
|
|
75
|
+
} | {
|
|
76
|
+
invited: true;
|
|
77
|
+
email: string;
|
|
78
|
+
role: string;
|
|
79
|
+
invite_code: string;
|
|
80
|
+
member_added: false;
|
|
81
|
+
};
|
|
82
|
+
export declare const listSpaces: () => Promise<SpaceSummary[]>;
|
|
83
|
+
export declare const createSpace: (input: {
|
|
84
|
+
name: string;
|
|
85
|
+
slug: string;
|
|
86
|
+
}) => Promise<SpaceSummary>;
|
|
87
|
+
export declare const getSpace: (id: string) => Promise<SpaceSummary>;
|
|
88
|
+
export declare const listSpaceMembers: (id: string) => Promise<SpaceMember[]>;
|
|
89
|
+
export declare const createSpaceInvite: (id: string, input: {
|
|
90
|
+
email: string;
|
|
91
|
+
role: "admin" | "member";
|
|
92
|
+
}) => Promise<CreateInviteResult>;
|
|
93
|
+
export declare const listSpaceInvites: (id: string) => Promise<SpaceInvite[]>;
|
|
94
|
+
export declare const revokeSpaceInvite: (id: string, inviteId: string) => Promise<{
|
|
95
|
+
revoked: true;
|
|
96
|
+
}>;
|
|
97
|
+
export declare const updateSpaceMemberRole: (id: string, userId: string, role: "admin" | "member") => Promise<{
|
|
98
|
+
user_id: string;
|
|
99
|
+
role: string;
|
|
100
|
+
updated: true;
|
|
101
|
+
}>;
|
|
102
|
+
export declare const removeSpaceMember: (id: string, userId: string) => Promise<void>;
|
|
103
|
+
export declare const deleteSpace: (id: string) => Promise<void>;
|
|
104
|
+
export interface Subscription {
|
|
105
|
+
tierName?: string;
|
|
106
|
+
[key: string]: unknown;
|
|
107
|
+
}
|
|
108
|
+
export declare const fetchSubscription: (agent?: string) => Promise<Subscription>;
|
|
109
|
+
/** `Design Team` → `design-team`; the slug a space gets when the user gives only a name. */
|
|
110
|
+
export declare function slugify(name: string): string;
|
|
111
|
+
/** Resolve a space by id, slug, or name from a list the caller already fetched. */
|
|
112
|
+
export declare function resolveSpaceFromList(spaces: SpaceSummary[], ref?: string): SpaceSummary | null;
|
|
113
|
+
/** Resolve a member by email or user id from a list the caller already fetched. */
|
|
114
|
+
export declare function resolveMemberFromList(members: SpaceMember[], ref: string): SpaceMember | null;
|
|
115
|
+
export type { PhoenixSession as Session };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phoenix ID — the typed surface commands use. Every route the account backend
|
|
3
|
+
* exposes is a function here; no command builds a URL or reads a token itself.
|
|
4
|
+
*/
|
|
5
|
+
import { phoenixRequest, PhoenixApiError } from './client.js';
|
|
6
|
+
export { PHOENIX_ID_BASE, PhoenixApiError, clearSession, readSession, sessionFilePath, writeSession, } from './client.js';
|
|
7
|
+
export function startDeviceAuthorization() {
|
|
8
|
+
return phoenixRequest('POST', '/api/v1/auth/device/authorization', {
|
|
9
|
+
auth: false,
|
|
10
|
+
body: {},
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export async function pollDeviceToken(deviceCode) {
|
|
14
|
+
try {
|
|
15
|
+
return await phoenixRequest('POST', '/api/v1/auth/device/token', {
|
|
16
|
+
auth: false,
|
|
17
|
+
body: {
|
|
18
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
19
|
+
device_code: deviceCode,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (!(err instanceof PhoenixApiError))
|
|
25
|
+
throw err;
|
|
26
|
+
// The server encodes poll state in the error body (RFC 8628 §3.5).
|
|
27
|
+
if (err.message.includes('authorization_pending'))
|
|
28
|
+
return { status: 'pending' };
|
|
29
|
+
if (err.message.includes('slow_down'))
|
|
30
|
+
return { status: 'slow_down' };
|
|
31
|
+
if (err.message.includes('expired_token'))
|
|
32
|
+
return { status: 'expired' };
|
|
33
|
+
if (err.message.includes('access_denied'))
|
|
34
|
+
return { status: 'denied' };
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function fetchWhoAmI(token) {
|
|
39
|
+
return phoenixRequest('GET', '/api/v1/auth/me', { token });
|
|
40
|
+
}
|
|
41
|
+
export const listSpaces = () => phoenixRequest('GET', '/api/v1/spaces');
|
|
42
|
+
export const createSpace = (input) => phoenixRequest('POST', '/api/v1/spaces', { body: input });
|
|
43
|
+
export const getSpace = (id) => phoenixRequest('GET', `/api/v1/spaces/${encodeURIComponent(id)}`);
|
|
44
|
+
export const listSpaceMembers = (id) => phoenixRequest('GET', `/api/v1/spaces/${encodeURIComponent(id)}/members`);
|
|
45
|
+
export const createSpaceInvite = (id, input) => phoenixRequest('POST', `/api/v1/spaces/${encodeURIComponent(id)}/invites`, {
|
|
46
|
+
body: input,
|
|
47
|
+
});
|
|
48
|
+
export const listSpaceInvites = (id) => phoenixRequest('GET', `/api/v1/spaces/${encodeURIComponent(id)}/invites`);
|
|
49
|
+
export const revokeSpaceInvite = (id, inviteId) => phoenixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(id)}/invites/${encodeURIComponent(inviteId)}`);
|
|
50
|
+
export const updateSpaceMemberRole = (id, userId, role) => phoenixRequest('PATCH', `/api/v1/spaces/${encodeURIComponent(id)}/members/${encodeURIComponent(userId)}`, {
|
|
51
|
+
body: { role },
|
|
52
|
+
});
|
|
53
|
+
export const removeSpaceMember = (id, userId) => phoenixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(id)}/members/${encodeURIComponent(userId)}`);
|
|
54
|
+
export const deleteSpace = (id) => phoenixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(id)}`);
|
|
55
|
+
export const fetchSubscription = (agent = 'agents-cli') => phoenixRequest('GET', `/api/v1/billing/subscription?agent=${encodeURIComponent(agent)}`);
|
|
56
|
+
// ─── Helpers shared by the commands ──────────────────────────────────────────
|
|
57
|
+
/** `Design Team` → `design-team`; the slug a space gets when the user gives only a name. */
|
|
58
|
+
export function slugify(name) {
|
|
59
|
+
return name
|
|
60
|
+
.trim()
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
63
|
+
.replace(/^-+|-+$/g, '')
|
|
64
|
+
.slice(0, 63);
|
|
65
|
+
}
|
|
66
|
+
/** Resolve a space by id, slug, or name from a list the caller already fetched. */
|
|
67
|
+
export function resolveSpaceFromList(spaces, ref) {
|
|
68
|
+
if (!ref)
|
|
69
|
+
return spaces.length === 1 ? spaces[0] : null;
|
|
70
|
+
const needle = ref.trim().toLowerCase();
|
|
71
|
+
return (spaces.find((s) => s.id === ref) ??
|
|
72
|
+
spaces.find((s) => s.slug.toLowerCase() === needle) ??
|
|
73
|
+
spaces.find((s) => s.name.toLowerCase() === needle) ??
|
|
74
|
+
null);
|
|
75
|
+
}
|
|
76
|
+
/** Resolve a member by email or user id from a list the caller already fetched. */
|
|
77
|
+
export function resolveMemberFromList(members, ref) {
|
|
78
|
+
const needle = ref.trim().toLowerCase();
|
|
79
|
+
return (members.find((m) => m.user_id === ref) ??
|
|
80
|
+
members.find((m) => m.email.toLowerCase() === needle) ??
|
|
81
|
+
null);
|
|
82
|
+
}
|
|
Binary file
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
<key>CFBundleIdentifier</key>
|
|
8
8
|
<string>com.phnx-labs.agents-menubar</string>
|
|
9
9
|
<key>CFBundleName</key>
|
|
10
|
-
<string>
|
|
10
|
+
<string>AGI Menu</string>
|
|
11
|
+
<key>CFBundleDisplayName</key>
|
|
12
|
+
<string>AGI Menu</string>
|
|
13
|
+
<key>CFBundleIconFile</key>
|
|
14
|
+
<string>AppIcon</string>
|
|
11
15
|
<key>CFBundlePackageType</key>
|
|
12
16
|
<string>APPL</string>
|
|
13
17
|
<key>CFBundleShortVersionString</key>
|
|
Binary file
|
|
@@ -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>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async probe capturing stdout. Rejects on spawn error, non-zero exit, or
|
|
3
|
+
* timeout — matching the `execFileAsync` contract the version probe had — and
|
|
4
|
+
* reaps the probe's whole process group on every settle path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function probeCapture(cmd: string, args: string[], timeoutMs: number): Promise<{
|
|
7
|
+
stdout: string;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn helpers for CAPABILITY PROBES — short-lived invocations of third-party
|
|
3
|
+
* binaries (`copilot --version`, a manifest's `check:` command) whose only job
|
|
4
|
+
* is an exit status or a line of stdout.
|
|
5
|
+
*
|
|
6
|
+
* A probed binary may fork children of its own: the copilot npm wrapper forks
|
|
7
|
+
* a platform-binary downloader into `~/Library/Caches/copilot` on first run
|
|
8
|
+
* under a fresh HOME. Node's `timeout:` option kills only the DIRECT child, so
|
|
9
|
+
* such grandchildren outlive the probe and keep writing — under a test's temp
|
|
10
|
+
* HOME that race is the ENOTEMPTY teardown class (RUSH-3028; residual after
|
|
11
|
+
* RUSH-3021 gated the daemon autostart). Every probe here therefore runs in
|
|
12
|
+
* its OWN process group (`detached`), and the whole group is reaped once the
|
|
13
|
+
* probe settles, so nothing a probe spawned can outlive it.
|
|
14
|
+
*
|
|
15
|
+
* Group semantics are POSIX only: on win32 `detached` means a new console and
|
|
16
|
+
* negative-pid group kills are unsupported, so there the DIRECT child is
|
|
17
|
+
* killed on settle instead — the same guarantee `execFileAsync`'s `timeout:`
|
|
18
|
+
* gave (the grandchild leak class is a darwin/linux temp-HOME teardown race).
|
|
19
|
+
*
|
|
20
|
+
* The parent dying mid-probe is covered too: a detached probe leaves the
|
|
21
|
+
* terminal's foreground group, so a Ctrl-C that hard-exits the CLI
|
|
22
|
+
* (`process.exit(130)` in index.ts) would strand it. Live probe groups are
|
|
23
|
+
* tracked in LIVE_GROUPS and a `process.on('exit')` hook — which runs on
|
|
24
|
+
* every `process.exit` path, including that SIGINT handler — reaps them
|
|
25
|
+
* synchronously.
|
|
26
|
+
*/
|
|
27
|
+
import { spawn } from 'child_process';
|
|
28
|
+
const GROUP_REAP = process.platform !== 'win32';
|
|
29
|
+
const LIVE_GROUPS = new Set();
|
|
30
|
+
let exitHookInstalled = false;
|
|
31
|
+
function ensureExitHook() {
|
|
32
|
+
if (exitHookInstalled)
|
|
33
|
+
return;
|
|
34
|
+
exitHookInstalled = true;
|
|
35
|
+
process.on('exit', () => {
|
|
36
|
+
for (const pid of LIVE_GROUPS) {
|
|
37
|
+
try {
|
|
38
|
+
process.kill(-pid, 'SIGKILL');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* group already fully exited */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function reapGroup(pid) {
|
|
47
|
+
if (!GROUP_REAP || !pid)
|
|
48
|
+
return;
|
|
49
|
+
LIVE_GROUPS.delete(pid);
|
|
50
|
+
try {
|
|
51
|
+
process.kill(-pid, 'SIGKILL');
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* group already fully exited */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Async probe capturing stdout. Rejects on spawn error, non-zero exit, or
|
|
59
|
+
* timeout — matching the `execFileAsync` contract the version probe had — and
|
|
60
|
+
* reaps the probe's whole process group on every settle path.
|
|
61
|
+
*/
|
|
62
|
+
export function probeCapture(cmd, args, timeoutMs) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const child = spawn(cmd, args, {
|
|
65
|
+
detached: GROUP_REAP,
|
|
66
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
67
|
+
windowsHide: true,
|
|
68
|
+
});
|
|
69
|
+
if (GROUP_REAP && child.pid) {
|
|
70
|
+
LIVE_GROUPS.add(child.pid);
|
|
71
|
+
ensureExitHook();
|
|
72
|
+
}
|
|
73
|
+
let out = '';
|
|
74
|
+
let settled = false;
|
|
75
|
+
const settle = (err) => {
|
|
76
|
+
if (settled)
|
|
77
|
+
return;
|
|
78
|
+
settled = true;
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
reapGroup(child.pid);
|
|
81
|
+
// win32 has no group to reap: kill the direct child so a timed-out
|
|
82
|
+
// probe still dies, matching execFile's `timeout:` behavior. No-op
|
|
83
|
+
// after a clean exit.
|
|
84
|
+
if (!GROUP_REAP)
|
|
85
|
+
child.kill('SIGKILL');
|
|
86
|
+
if (err)
|
|
87
|
+
reject(err);
|
|
88
|
+
else
|
|
89
|
+
resolve({ stdout: out });
|
|
90
|
+
};
|
|
91
|
+
const timer = setTimeout(() => settle(new Error(`probe timed out after ${timeoutMs}ms: ${cmd} ${args.join(' ')}`)), timeoutMs);
|
|
92
|
+
child.stdout?.setEncoding('utf8');
|
|
93
|
+
child.stdout?.on('data', (d) => {
|
|
94
|
+
out += d;
|
|
95
|
+
});
|
|
96
|
+
child.on('error', (e) => settle(e));
|
|
97
|
+
// 'exit', not 'close': a forked grandchild inherits the stdout pipe, and
|
|
98
|
+
// 'close' waits for EVERY holder of that pipe to exit — exactly the
|
|
99
|
+
// process this helper exists to reap. Settle when the probed binary
|
|
100
|
+
// itself exits; one tick's grace lets its final stdout chunks land.
|
|
101
|
+
child.on('exit', (code) => {
|
|
102
|
+
setImmediate(() => settle(code === 0 ? null : new Error(`probe exited ${code}: ${cmd} ${args.join(' ')}`)));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -30,7 +30,9 @@ 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). `org` (the Prix-coupled account layer) was
|
|
34
|
+
* removed; `agents auth` returned against Phoenix ID with `auth space` as the
|
|
35
|
+
* team surface (RUSH-2581).
|
|
34
36
|
*/
|
|
35
37
|
export declare const RETIRED_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
36
38
|
export declare function isKnownTopLevelCommand(name: string): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const LOADED_COMMAND_NAMES = [
|
|
2
|
-
'accounts', 'auth', '
|
|
2
|
+
'accounts', 'auth', '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). `org` (the Prix-coupled account layer) was
|
|
54
|
+
* removed; `agents auth` returned against Phoenix ID with `auth space` as the
|
|
55
|
+
* team surface (RUSH-2581).
|
|
54
56
|
*/
|
|
55
57
|
export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
56
58
|
'webhook',
|
|
59
|
+
'org',
|
|
57
60
|
'serve',
|
|
58
61
|
'login',
|
|
59
62
|
'logout',
|
package/dist/lib/view-types.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ export interface ViewJsonVersion {
|
|
|
28
28
|
resetsAt: string | null;
|
|
29
29
|
}>;
|
|
30
30
|
unavailable?: {
|
|
31
|
-
reason: 'session_limit';
|
|
32
|
-
resetsAt
|
|
31
|
+
reason: 'session_limit' | 'out_of_credits';
|
|
32
|
+
resetsAt?: string;
|
|
33
33
|
};
|
|
34
34
|
lastActive: string | null;
|
|
35
35
|
path: string;
|
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.46",
|
|
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/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
|
-
}
|