@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.
@@ -1,159 +0,0 @@
1
- /**
2
- * Client for the Rush account layer (`api.prix.dev`) — backs `agents auth` and
3
- * `agents auth space` (canonical) / `agents org` (deprecated alias). `agents auth space`
4
- * maps to `/api/v1/spaces`, not `/api/v1/orgs`: spaces
5
- * already carry the free-tier caps (1 owned space, 3 members) and can exist
6
- * standalone with no parent organization, matching a "just want a team" CLI flow
7
- * better than the heavier enterprise-tenancy `orgs` routes (domain, SSO). See
8
- * `.agents/artifacts/2026-08-20/agi-cli-client-integration-spec.md` §4.2 in the
9
- * agi-cli-web repo for the full reasoning and the live-confirmed route/shape audit.
10
- *
11
- * Token custody: `agents auth login` writes its own session file
12
- * (`getPrixSessionFile()`), separate from `~/.rush/user.yaml`, so `agents auth
13
- * logout` never signs the user out of `rush` (the two CLIs share a backend
14
- * account, not a credential store). Reads fall back to `~/.rush/user.yaml`
15
- * (the pattern `readRushToken` in `lib/secrets/drivers/rush.ts` already uses)
16
- * so a user who is only signed in via `rush login` still gets a working
17
- * `agents auth whoami` / `agents org` with zero extra login step.
18
- */
19
- export declare const PRIX_API_BASE = "https://api.prix.dev";
20
- /** The session `agents auth login` owns. */
21
- export interface PrixSession {
22
- access_token: string;
23
- refresh_token?: string;
24
- /** Unix ms. */
25
- expires_at?: number;
26
- email?: string;
27
- userId?: string;
28
- }
29
- export declare function getPrixSessionFile(): string;
30
- export declare function readPrixSession(): PrixSession | null;
31
- export declare function writePrixSession(session: PrixSession): void;
32
- export declare function clearPrixSession(): boolean;
33
- /** Where the caller's Bearer token came from — surfaced by `whoami` so the user knows which login is live. */
34
- export type PrixTokenSource = 'agents' | 'rush';
35
- export declare function resolvePrixToken(): {
36
- token: string;
37
- source: PrixTokenSource;
38
- } | null;
39
- export declare class PrixApiError extends Error {
40
- status: number;
41
- constructor(status: number, message: string);
42
- }
43
- export interface WhoAmI {
44
- userId: string;
45
- email: string;
46
- valid: true;
47
- }
48
- /** `GET /api/v1/auth/me` — live-confirmed shape: `{email, userId, valid}`. */
49
- export declare function fetchWhoAmI(token?: string): Promise<WhoAmI>;
50
- export interface DeviceAuthorization {
51
- device_code: string;
52
- user_code: string;
53
- verification_uri: string;
54
- verification_uri_complete: string;
55
- expires_in: number;
56
- interval: number;
57
- }
58
- /** `POST /api/v1/auth/device/authorization` — public, no token. */
59
- export declare function startDeviceAuthorization(): Promise<DeviceAuthorization>;
60
- export type DeviceTokenPoll = {
61
- status: 'authorized';
62
- access_token: string;
63
- refresh_token?: string;
64
- expires_in?: number;
65
- user: {
66
- email: string;
67
- id: string;
68
- };
69
- } | {
70
- status: 'pending';
71
- } | {
72
- status: 'slow_down';
73
- } | {
74
- status: 'expired';
75
- } | {
76
- status: 'denied';
77
- };
78
- /** `POST /api/v1/auth/device/token` — one poll attempt. Callers own the interval loop. */
79
- export declare function pollDeviceToken(deviceCode: string): Promise<DeviceTokenPoll>;
80
- export interface SpaceSummary {
81
- id: string;
82
- slug: string;
83
- name: string;
84
- organization_id: string | null;
85
- owner_user_id: string;
86
- invite_code?: string;
87
- user_role: 'owner' | 'admin' | 'member';
88
- created_at: string;
89
- }
90
- export interface SpaceMember {
91
- user_id: string;
92
- email: string;
93
- name?: string;
94
- avatar_url?: string;
95
- role: 'owner' | 'admin' | 'member';
96
- joined_at: string;
97
- }
98
- /** `GET /api/v1/spaces` — live-confirmed: array of `SpaceSummary`. */
99
- export declare function listSpaces(): Promise<SpaceSummary[]>;
100
- /** `POST /api/v1/spaces` — 403 if the caller already owns a space (free tier: 1). */
101
- export declare function createSpace(input: {
102
- name: string;
103
- slug: string;
104
- description?: string;
105
- }): Promise<SpaceSummary>;
106
- /** `GET /api/v1/spaces/:id` — requires membership. */
107
- export declare function getSpace(spaceId: string): Promise<SpaceSummary>;
108
- /** `GET /api/v1/spaces/:id/members`. */
109
- export declare function listSpaceMembers(spaceId: string): Promise<SpaceMember[]>;
110
- export type CreateSpaceInviteResult = {
111
- invited: true;
112
- email: string;
113
- role: string;
114
- member_added: true;
115
- } | {
116
- invited: true;
117
- email: string;
118
- role: string;
119
- invite_code: string;
120
- member_added?: false;
121
- };
122
- /** `POST /api/v1/spaces/:id/invites` — sends a real email for the pending-invite path. */
123
- export declare function createSpaceInvite(spaceId: string, email: string, role?: 'admin' | 'member'): Promise<CreateSpaceInviteResult>;
124
- export interface SpaceInvite {
125
- id: string;
126
- space_id: string;
127
- email: string;
128
- role: 'admin' | 'member';
129
- invite_code: string;
130
- created_at: string;
131
- }
132
- /** `GET /api/v1/spaces/:id/invites`. */
133
- export declare function listSpaceInvites(spaceId: string): Promise<SpaceInvite[]>;
134
- /** `DELETE /api/v1/spaces/:id/invites/:inviteId`. */
135
- export declare function revokeSpaceInvite(spaceId: string, inviteId: string): Promise<{
136
- revoked: true;
137
- }>;
138
- /** `PATCH /api/v1/spaces/:id/members/:userId` — owner-only for admin changes. Route takes userId, not email. */
139
- export declare function updateSpaceMemberRole(spaceId: string, userId: string, role: 'admin' | 'member'): Promise<{
140
- user_id: string;
141
- role: string;
142
- updated: true;
143
- }>;
144
- /** `DELETE /api/v1/spaces/:id/members/:userId` — owner, admin, or the member themself (leave). */
145
- export declare function removeSpaceMember(spaceId: string, userId: string): Promise<void>;
146
- /** `DELETE /api/v1/spaces/:id` — soft delete, 30-day restore window. */
147
- export declare function deleteSpace(spaceId: string): Promise<void>;
148
- /** `agents-cli-space-name` -> `agi-cli-space-name`; lowercase, hyphenated, matches the backend's `^[a-z0-9-]+$` slug rule. */
149
- export declare function slugify(name: string): string;
150
- /**
151
- * Resolve the `--space` a command should act on: an explicit id/slug match
152
- * against the caller's own space list, or — with nothing passed — the
153
- * caller's sole space (free tier caps ownership at one, so this is almost
154
- * always unambiguous). Pure over an already-fetched list so it's cheaply
155
- * unit-testable with a fixture.
156
- */
157
- export declare function resolveSpaceFromList(spaces: SpaceSummary[], explicit?: string): SpaceSummary;
158
- /** Resolve a member's email to their `user_id` from an already-fetched member list. */
159
- export declare function resolveMemberFromList(members: SpaceMember[], email: string): SpaceMember;
@@ -1,215 +0,0 @@
1
- /**
2
- * Client for the Rush account layer (`api.prix.dev`) — backs `agents auth` and
3
- * `agents auth space` (canonical) / `agents org` (deprecated alias). `agents auth space`
4
- * maps to `/api/v1/spaces`, not `/api/v1/orgs`: spaces
5
- * already carry the free-tier caps (1 owned space, 3 members) and can exist
6
- * standalone with no parent organization, matching a "just want a team" CLI flow
7
- * better than the heavier enterprise-tenancy `orgs` routes (domain, SSO). See
8
- * `.agents/artifacts/2026-08-20/agi-cli-client-integration-spec.md` §4.2 in the
9
- * agi-cli-web repo for the full reasoning and the live-confirmed route/shape audit.
10
- *
11
- * Token custody: `agents auth login` writes its own session file
12
- * (`getPrixSessionFile()`), separate from `~/.rush/user.yaml`, so `agents auth
13
- * logout` never signs the user out of `rush` (the two CLIs share a backend
14
- * account, not a credential store). Reads fall back to `~/.rush/user.yaml`
15
- * (the pattern `readRushToken` in `lib/secrets/drivers/rush.ts` already uses)
16
- * so a user who is only signed in via `rush login` still gets a working
17
- * `agents auth whoami` / `agents org` with zero extra login step.
18
- */
19
- import * as fs from 'fs';
20
- import * as os from 'os';
21
- import * as path from 'path';
22
- import * as yaml from 'yaml';
23
- import { getRuntimeStateDir } from './state.js';
24
- export const PRIX_API_BASE = 'https://api.prix.dev';
25
- const RUSH_USER_YAML = path.join(os.homedir(), '.rush', 'user.yaml');
26
- /** Computed per-call (not cached at module load) so `AGENTS_STATE_DIR` overrides in tests take effect. */
27
- function prixSessionFile() {
28
- return path.join(getRuntimeStateDir(), 'prix-account.json');
29
- }
30
- /** Read the token `rush login` wrote, with no expiry check — the same shape `readRushToken` reads. */
31
- function readRushSessionToken() {
32
- if (!fs.existsSync(RUSH_USER_YAML))
33
- return null;
34
- try {
35
- const data = yaml.parse(fs.readFileSync(RUSH_USER_YAML, 'utf-8'));
36
- return data?.session?.access_token ?? null;
37
- }
38
- catch {
39
- return null;
40
- }
41
- }
42
- export function getPrixSessionFile() {
43
- return prixSessionFile();
44
- }
45
- export function readPrixSession() {
46
- const file = prixSessionFile();
47
- if (!fs.existsSync(file))
48
- return null;
49
- try {
50
- const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
51
- if (!parsed || typeof parsed.access_token !== 'string')
52
- return null;
53
- return parsed;
54
- }
55
- catch {
56
- return null;
57
- }
58
- }
59
- export function writePrixSession(session) {
60
- const file = prixSessionFile();
61
- fs.mkdirSync(path.dirname(file), { recursive: true });
62
- const tmp = `${file}.${process.pid}.tmp`;
63
- fs.writeFileSync(tmp, JSON.stringify(session, null, 2), { mode: 0o600 });
64
- fs.renameSync(tmp, file);
65
- }
66
- export function clearPrixSession() {
67
- const file = prixSessionFile();
68
- if (!fs.existsSync(file))
69
- return false;
70
- fs.rmSync(file);
71
- return true;
72
- }
73
- export function resolvePrixToken() {
74
- const own = readPrixSession();
75
- if (own?.access_token)
76
- return { token: own.access_token, source: 'agents' };
77
- const rushToken = readRushSessionToken();
78
- if (rushToken)
79
- return { token: rushToken, source: 'rush' };
80
- return null;
81
- }
82
- export class PrixApiError extends Error {
83
- status;
84
- constructor(status, message) {
85
- super(message);
86
- this.status = status;
87
- this.name = 'PrixApiError';
88
- }
89
- }
90
- async function prixRequest(method, endpoint, opts = {}) {
91
- const auth = opts.auth !== false;
92
- let token = opts.token;
93
- if (auth && !token) {
94
- const resolved = resolvePrixToken();
95
- if (!resolved)
96
- throw new Error("Not signed in. Run 'agents auth login' first.");
97
- token = resolved.token;
98
- }
99
- const headers = { 'Content-Type': 'application/json' };
100
- if (token)
101
- headers.Authorization = `Bearer ${token}`;
102
- const res = await fetch(`${PRIX_API_BASE}${endpoint}`, {
103
- method,
104
- headers,
105
- body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
106
- });
107
- const text = await res.text();
108
- const data = text ? JSON.parse(text) : undefined;
109
- if (!res.ok) {
110
- const message = (data && typeof data === 'object' && 'error' in data) ? String(data.error) : `${res.status} ${res.statusText}`;
111
- throw new PrixApiError(res.status, message);
112
- }
113
- return data;
114
- }
115
- /** `GET /api/v1/auth/me` — live-confirmed shape: `{email, userId, valid}`. */
116
- export async function fetchWhoAmI(token) {
117
- return prixRequest('GET', '/api/v1/auth/me', { token });
118
- }
119
- /** `POST /api/v1/auth/device/authorization` — public, no token. */
120
- export async function startDeviceAuthorization() {
121
- return prixRequest('POST', '/api/v1/auth/device/authorization', { auth: false, body: {} });
122
- }
123
- /** `POST /api/v1/auth/device/token` — one poll attempt. Callers own the interval loop. */
124
- export async function pollDeviceToken(deviceCode) {
125
- try {
126
- const data = await prixRequest('POST', '/api/v1/auth/device/token', { auth: false, body: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: deviceCode } });
127
- return { status: 'authorized', ...data };
128
- }
129
- catch (err) {
130
- if (err instanceof PrixApiError) {
131
- if (err.message.includes('authorization_pending'))
132
- return { status: 'pending' };
133
- if (err.message.includes('slow_down'))
134
- return { status: 'slow_down' };
135
- if (err.message.includes('expired_token'))
136
- return { status: 'expired' };
137
- if (err.message.includes('access_denied'))
138
- return { status: 'denied' };
139
- }
140
- throw err;
141
- }
142
- }
143
- /** `GET /api/v1/spaces` — live-confirmed: array of `SpaceSummary`. */
144
- export async function listSpaces() {
145
- return prixRequest('GET', '/api/v1/spaces');
146
- }
147
- /** `POST /api/v1/spaces` — 403 if the caller already owns a space (free tier: 1). */
148
- export async function createSpace(input) {
149
- return prixRequest('POST', '/api/v1/spaces', { body: input });
150
- }
151
- /** `GET /api/v1/spaces/:id` — requires membership. */
152
- export async function getSpace(spaceId) {
153
- return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}`);
154
- }
155
- /** `GET /api/v1/spaces/:id/members`. */
156
- export async function listSpaceMembers(spaceId) {
157
- return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members`);
158
- }
159
- /** `POST /api/v1/spaces/:id/invites` — sends a real email for the pending-invite path. */
160
- export async function createSpaceInvite(spaceId, email, role = 'member') {
161
- return prixRequest('POST', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites`, { body: { email, role } });
162
- }
163
- /** `GET /api/v1/spaces/:id/invites`. */
164
- export async function listSpaceInvites(spaceId) {
165
- return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites`);
166
- }
167
- /** `DELETE /api/v1/spaces/:id/invites/:inviteId`. */
168
- export async function revokeSpaceInvite(spaceId, inviteId) {
169
- return prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites/${encodeURIComponent(inviteId)}`);
170
- }
171
- /** `PATCH /api/v1/spaces/:id/members/:userId` — owner-only for admin changes. Route takes userId, not email. */
172
- export async function updateSpaceMemberRole(spaceId, userId, role) {
173
- return prixRequest('PATCH', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(userId)}`, { body: { role } });
174
- }
175
- /** `DELETE /api/v1/spaces/:id/members/:userId` — owner, admin, or the member themself (leave). */
176
- export async function removeSpaceMember(spaceId, userId) {
177
- await prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(userId)}`);
178
- }
179
- /** `DELETE /api/v1/spaces/:id` — soft delete, 30-day restore window. */
180
- export async function deleteSpace(spaceId) {
181
- await prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}`);
182
- }
183
- /** `agents-cli-space-name` -> `agi-cli-space-name`; lowercase, hyphenated, matches the backend's `^[a-z0-9-]+$` slug rule. */
184
- export function slugify(name) {
185
- const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
186
- return slug || 'space';
187
- }
188
- /**
189
- * Resolve the `--space` a command should act on: an explicit id/slug match
190
- * against the caller's own space list, or — with nothing passed — the
191
- * caller's sole space (free tier caps ownership at one, so this is almost
192
- * always unambiguous). Pure over an already-fetched list so it's cheaply
193
- * unit-testable with a fixture.
194
- */
195
- export function resolveSpaceFromList(spaces, explicit) {
196
- if (explicit) {
197
- const match = spaces.find(s => s.id === explicit || s.slug === explicit);
198
- if (!match)
199
- throw new Error(`No space matching '${explicit}'. Run 'agents org list' to see your spaces.`);
200
- return match;
201
- }
202
- if (spaces.length === 0)
203
- throw new Error("You have no spaces. Create one with 'agents org create <name>'.");
204
- if (spaces.length > 1) {
205
- throw new Error(`You belong to ${spaces.length} spaces — pass --space <id-or-slug>: ${spaces.map(s => s.slug).join(', ')}`);
206
- }
207
- return spaces[0];
208
- }
209
- /** Resolve a member's email to their `user_id` from an already-fetched member list. */
210
- export function resolveMemberFromList(members, email) {
211
- const match = members.find(m => m.email.toLowerCase() === email.toLowerCase());
212
- if (!match)
213
- throw new Error(`'${email}' is not a member of this space. Run 'agents org members' to see who is.`);
214
- return match;
215
- }