pal-explorer-cli 0.4.12 → 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -149
- package/bin/pal.js +63 -2
- package/extensions/@palexplorer/analytics/extension.json +20 -1
- package/extensions/@palexplorer/analytics/index.js +19 -9
- package/extensions/@palexplorer/audit/extension.json +14 -0
- package/extensions/@palexplorer/auth-email/extension.json +15 -0
- package/extensions/@palexplorer/auth-oauth/extension.json +15 -0
- package/extensions/@palexplorer/chat/extension.json +14 -0
- package/extensions/@palexplorer/discovery/extension.json +17 -0
- package/extensions/@palexplorer/discovery/index.js +1 -1
- package/extensions/@palexplorer/email-notifications/extension.json +23 -0
- package/extensions/@palexplorer/groups/extension.json +15 -0
- package/extensions/@palexplorer/share-links/extension.json +15 -0
- package/extensions/@palexplorer/sync/extension.json +16 -0
- package/extensions/@palexplorer/user-mgmt/extension.json +15 -0
- package/lib/capabilities.js +24 -24
- package/lib/commands/analytics.js +175 -175
- package/lib/commands/api-keys.js +131 -131
- package/lib/commands/audit.js +235 -235
- package/lib/commands/auth.js +137 -137
- package/lib/commands/backup.js +76 -76
- package/lib/commands/billing.js +148 -148
- package/lib/commands/chat.js +217 -217
- package/lib/commands/cloud-backup.js +231 -231
- package/lib/commands/comment.js +99 -99
- package/lib/commands/completion.js +203 -203
- package/lib/commands/compliance.js +218 -218
- package/lib/commands/config.js +136 -136
- package/lib/commands/connect.js +44 -44
- package/lib/commands/dept.js +294 -294
- package/lib/commands/device.js +146 -146
- package/lib/commands/download.js +240 -226
- package/lib/commands/explorer.js +178 -178
- package/lib/commands/extension.js +1060 -970
- package/lib/commands/favorite.js +90 -90
- package/lib/commands/federation.js +270 -270
- package/lib/commands/file.js +533 -533
- package/lib/commands/group.js +271 -271
- package/lib/commands/gui-share.js +29 -29
- package/lib/commands/init.js +61 -61
- package/lib/commands/invite.js +59 -59
- package/lib/commands/list.js +58 -58
- package/lib/commands/log.js +116 -116
- package/lib/commands/nearby.js +108 -108
- package/lib/commands/network.js +251 -251
- package/lib/commands/notify.js +198 -198
- package/lib/commands/org.js +273 -273
- package/lib/commands/pal.js +403 -180
- package/lib/commands/permissions.js +216 -216
- package/lib/commands/pin.js +97 -97
- package/lib/commands/protocol.js +357 -357
- package/lib/commands/rbac.js +147 -147
- package/lib/commands/recover.js +36 -36
- package/lib/commands/register.js +171 -171
- package/lib/commands/relay.js +131 -131
- package/lib/commands/remote.js +368 -368
- package/lib/commands/revoke.js +50 -50
- package/lib/commands/scanner.js +280 -280
- package/lib/commands/schedule.js +344 -344
- package/lib/commands/scim.js +203 -203
- package/lib/commands/search.js +181 -181
- package/lib/commands/serve.js +438 -438
- package/lib/commands/server.js +350 -350
- package/lib/commands/share-link.js +199 -199
- package/lib/commands/share.js +336 -323
- package/lib/commands/sso.js +200 -200
- package/lib/commands/status.js +145 -145
- package/lib/commands/stream.js +562 -562
- package/lib/commands/su.js +187 -187
- package/lib/commands/sync.js +979 -979
- package/lib/commands/transfers.js +152 -152
- package/lib/commands/uninstall.js +188 -188
- package/lib/commands/update.js +204 -204
- package/lib/commands/user.js +276 -276
- package/lib/commands/vfs.js +84 -84
- package/lib/commands/web-login.js +79 -79
- package/lib/commands/web.js +52 -52
- package/lib/commands/webhook.js +180 -180
- package/lib/commands/whoami.js +59 -59
- package/lib/commands/workspace.js +121 -121
- package/lib/core/billing.js +16 -5
- package/lib/core/dhtDiscovery.js +9 -2
- package/lib/core/discoveryClient.js +13 -7
- package/lib/core/extensions.js +142 -1
- package/lib/core/identity.js +33 -2
- package/lib/core/imageProcessor.js +109 -0
- package/lib/core/imageTorrent.js +167 -0
- package/lib/core/permissions.js +1 -1
- package/lib/core/pro.js +11 -4
- package/lib/core/serverList.js +4 -1
- package/lib/core/shares.js +12 -1
- package/lib/core/signalingServer.js +14 -2
- package/lib/core/su.js +1 -1
- package/lib/core/users.js +1 -1
- package/lib/protocol/messages.js +12 -3
- package/lib/utils/explorer.js +1 -1
- package/lib/utils/help.js +357 -357
- package/lib/utils/torrent.js +1 -0
- package/package.json +4 -3
package/lib/commands/org.js
CHANGED
|
@@ -1,273 +1,273 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
async function getAuthHeaders() {
|
|
4
|
-
const { fetchFirst } = await import('../core/discoveryClient.js');
|
|
5
|
-
const { getIdentity } = await import('../core/identity.js');
|
|
6
|
-
|
|
7
|
-
const identity = await getIdentity();
|
|
8
|
-
if (!identity?.publicKey) {
|
|
9
|
-
console.error(chalk.red('No identity. Run
|
|
10
|
-
process.exitCode = 1;
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const keysRes = await fetchFirst(`/api/v1/keys/${encodeURIComponent(identity.publicKey)}`);
|
|
15
|
-
const keys = keysRes ? await keysRes.json() : [];
|
|
16
|
-
if (!keys?.length) {
|
|
17
|
-
console.error(chalk.red('No API key. Create one:
|
|
18
|
-
process.exitCode = 1;
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return { 'Content-Type': 'application/json', 'x-api-key': keys[0].key };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function apiRequest(method, path, body) {
|
|
26
|
-
const { getPrimaryServer } = await import('../core/discoveryClient.js');
|
|
27
|
-
const headers = await getAuthHeaders();
|
|
28
|
-
if (!headers) return null;
|
|
29
|
-
|
|
30
|
-
const baseUrl = getPrimaryServer();
|
|
31
|
-
const opts = { method, headers, signal: AbortSignal.timeout(10000) };
|
|
32
|
-
if (body) opts.body = JSON.stringify(body);
|
|
33
|
-
|
|
34
|
-
const res = await fetch(`${baseUrl}${path}`, opts);
|
|
35
|
-
return res;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default function orgCommand(program) {
|
|
39
|
-
const cmd = program
|
|
40
|
-
.command('org')
|
|
41
|
-
.description('manage organizations for enterprise extension management')
|
|
42
|
-
.addHelpText('after', `
|
|
43
|
-
Examples:
|
|
44
|
-
$
|
|
45
|
-
$
|
|
46
|
-
$
|
|
47
|
-
$
|
|
48
|
-
$
|
|
49
|
-
$
|
|
50
|
-
$
|
|
51
|
-
$
|
|
52
|
-
`);
|
|
53
|
-
|
|
54
|
-
cmd
|
|
55
|
-
.command('create <name>')
|
|
56
|
-
.description('create an organization')
|
|
57
|
-
.action(async (name) => {
|
|
58
|
-
try {
|
|
59
|
-
const res = await apiRequest('POST', '/api/v1/orgs', { name });
|
|
60
|
-
if (!res) return;
|
|
61
|
-
const data = await res.json();
|
|
62
|
-
if (res.ok && data?.id) {
|
|
63
|
-
console.log(chalk.green(`\u2714 Created organization "${name}"`));
|
|
64
|
-
console.log(` ID: ${chalk.white(data.id)}`);
|
|
65
|
-
} else {
|
|
66
|
-
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
67
|
-
process.exitCode = 1;
|
|
68
|
-
}
|
|
69
|
-
} catch (err) {
|
|
70
|
-
console.error(chalk.red(`Create failed: ${err.message}`));
|
|
71
|
-
process.exitCode = 1;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
cmd
|
|
76
|
-
.command('list')
|
|
77
|
-
.description('list your organizations')
|
|
78
|
-
.action(async () => {
|
|
79
|
-
try {
|
|
80
|
-
const res = await apiRequest('GET', '/api/v1/orgs');
|
|
81
|
-
if (!res) return;
|
|
82
|
-
const data = await res.json();
|
|
83
|
-
if (!data?.orgs?.length) {
|
|
84
|
-
console.log(chalk.gray('No organizations.'));
|
|
85
|
-
console.log(chalk.gray('
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
console.log('');
|
|
89
|
-
console.log(chalk.cyan('Organizations:'));
|
|
90
|
-
console.log(chalk.gray('\u2500'.repeat(50)));
|
|
91
|
-
for (const org of data.orgs) {
|
|
92
|
-
const role = org.role === 'owner' ? chalk.yellow('owner') : chalk.gray(org.role);
|
|
93
|
-
console.log(` ${chalk.white(org.name)} ${chalk.gray(`(${org.id})`)}`);
|
|
94
|
-
console.log(` Role: ${role} Members: ${chalk.cyan(org.memberCount || 0)} Subscriptions: ${chalk.cyan(org.subscriptionCount || 0)}`);
|
|
95
|
-
}
|
|
96
|
-
console.log('');
|
|
97
|
-
} catch (err) {
|
|
98
|
-
console.error(chalk.red(`List failed: ${err.message}`));
|
|
99
|
-
process.exitCode = 1;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
cmd
|
|
104
|
-
.command('info <id>')
|
|
105
|
-
.description('show org details, members, and subscriptions')
|
|
106
|
-
.action(async (id) => {
|
|
107
|
-
try {
|
|
108
|
-
const res = await apiRequest('GET', `/api/v1/orgs/${encodeURIComponent(id)}`);
|
|
109
|
-
if (!res) return;
|
|
110
|
-
if (!res.ok) {
|
|
111
|
-
const err = await res.json().catch(() => ({}));
|
|
112
|
-
console.error(chalk.red(`Not found: ${err?.error || 'Organization not found'}`));
|
|
113
|
-
process.exitCode = 1;
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const data = await res.json();
|
|
117
|
-
console.log('');
|
|
118
|
-
console.log(chalk.cyan.bold(data.name));
|
|
119
|
-
console.log(chalk.gray('\u2500'.repeat(50)));
|
|
120
|
-
console.log(` ID: ${chalk.white(data.id)}`);
|
|
121
|
-
console.log(` Created: ${chalk.gray(new Date(data.createdAt).toLocaleDateString())}`);
|
|
122
|
-
console.log(` Plan: ${chalk.yellow(data.plan || 'free')}`);
|
|
123
|
-
|
|
124
|
-
if (data.members?.length) {
|
|
125
|
-
console.log('');
|
|
126
|
-
console.log(chalk.cyan(' Members:'));
|
|
127
|
-
for (const m of data.members) {
|
|
128
|
-
const roleColor = m.role === 'owner' ? chalk.yellow : m.role === 'admin' ? chalk.cyan : chalk.gray;
|
|
129
|
-
console.log(` ${chalk.white('@' + m.handle)} ${roleColor(m.role)}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (data.subscriptions?.length) {
|
|
134
|
-
console.log('');
|
|
135
|
-
console.log(chalk.cyan(' Subscriptions:'));
|
|
136
|
-
for (const sub of data.subscriptions) {
|
|
137
|
-
const status = sub.active ? chalk.green('active') : chalk.red('cancelled');
|
|
138
|
-
console.log(` ${chalk.white(sub.extName)} ${status} ${chalk.gray('$' + (sub.price || 0).toFixed(2) + '/mo')}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
console.log('');
|
|
142
|
-
} catch (err) {
|
|
143
|
-
console.error(chalk.red(`Info failed: ${err.message}`));
|
|
144
|
-
process.exitCode = 1;
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
cmd
|
|
149
|
-
.command('invite <id> <handle>')
|
|
150
|
-
.description('add a member to an organization')
|
|
151
|
-
.action(async (id, handle) => {
|
|
152
|
-
try {
|
|
153
|
-
const res = await apiRequest('POST', `/api/v1/orgs/${encodeURIComponent(id)}/members`, { handle });
|
|
154
|
-
if (!res) return;
|
|
155
|
-
const data = await res.json();
|
|
156
|
-
if (res.ok && data?.success) {
|
|
157
|
-
console.log(chalk.green(`\u2714 Invited @${handle} to organization`));
|
|
158
|
-
} else {
|
|
159
|
-
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
160
|
-
process.exitCode = 1;
|
|
161
|
-
}
|
|
162
|
-
} catch (err) {
|
|
163
|
-
console.error(chalk.red(`Invite failed: ${err.message}`));
|
|
164
|
-
process.exitCode = 1;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
cmd
|
|
169
|
-
.command('remove <id> <handle>')
|
|
170
|
-
.description('remove a member from an organization')
|
|
171
|
-
.action(async (id, handle) => {
|
|
172
|
-
try {
|
|
173
|
-
const res = await apiRequest('DELETE', `/api/v1/orgs/${encodeURIComponent(id)}/members/${encodeURIComponent(handle)}`);
|
|
174
|
-
if (!res) return;
|
|
175
|
-
const data = await res.json();
|
|
176
|
-
if (res.ok && data?.success) {
|
|
177
|
-
console.log(chalk.green(`\u2714 Removed @${handle} from organization`));
|
|
178
|
-
} else {
|
|
179
|
-
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
180
|
-
process.exitCode = 1;
|
|
181
|
-
}
|
|
182
|
-
} catch (err) {
|
|
183
|
-
console.error(chalk.red(`Remove failed: ${err.message}`));
|
|
184
|
-
process.exitCode = 1;
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
cmd
|
|
189
|
-
.command('subscribe <id> <ext-name>')
|
|
190
|
-
.description('subscribe organization to an enterprise extension')
|
|
191
|
-
.action(async (id, extName) => {
|
|
192
|
-
try {
|
|
193
|
-
const res = await apiRequest('POST', `/api/v1/orgs/${encodeURIComponent(id)}/subscriptions`, { extName });
|
|
194
|
-
if (!res) return;
|
|
195
|
-
const data = await res.json();
|
|
196
|
-
if (res.ok && data?.success) {
|
|
197
|
-
console.log(chalk.green(`\u2714 Subscribed to "${extName}"`));
|
|
198
|
-
if (data.price) console.log(` Price: ${chalk.yellow('$' + data.price.toFixed(2) + '/mo')}`);
|
|
199
|
-
} else {
|
|
200
|
-
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
201
|
-
process.exitCode = 1;
|
|
202
|
-
}
|
|
203
|
-
} catch (err) {
|
|
204
|
-
console.error(chalk.red(`Subscribe failed: ${err.message}`));
|
|
205
|
-
process.exitCode = 1;
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
cmd
|
|
210
|
-
.command('unsubscribe <id> <ext-name>')
|
|
211
|
-
.description('cancel an extension subscription')
|
|
212
|
-
.action(async (id, extName) => {
|
|
213
|
-
try {
|
|
214
|
-
const res = await apiRequest('DELETE', `/api/v1/orgs/${encodeURIComponent(id)}/subscriptions/${encodeURIComponent(extName)}`);
|
|
215
|
-
if (!res) return;
|
|
216
|
-
const data = await res.json();
|
|
217
|
-
if (res.ok && data?.success) {
|
|
218
|
-
console.log(chalk.green(`\u2714 Unsubscribed from "${extName}"`));
|
|
219
|
-
} else {
|
|
220
|
-
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
221
|
-
process.exitCode = 1;
|
|
222
|
-
}
|
|
223
|
-
} catch (err) {
|
|
224
|
-
console.error(chalk.red(`Unsubscribe failed: ${err.message}`));
|
|
225
|
-
process.exitCode = 1;
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
cmd
|
|
230
|
-
.command('billing <id>')
|
|
231
|
-
.description('show billing summary for an organization')
|
|
232
|
-
.action(async (id) => {
|
|
233
|
-
try {
|
|
234
|
-
const res = await apiRequest('GET', `/api/v1/orgs/${encodeURIComponent(id)}/billing`);
|
|
235
|
-
if (!res) return;
|
|
236
|
-
if (!res.ok) {
|
|
237
|
-
const err = await res.json().catch(() => ({}));
|
|
238
|
-
console.error(chalk.red(`Failed: ${err?.error || 'Unknown error'}`));
|
|
239
|
-
process.exitCode = 1;
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const data = await res.json();
|
|
243
|
-
console.log('');
|
|
244
|
-
console.log(chalk.cyan.bold('Organization Billing'));
|
|
245
|
-
console.log(chalk.gray('\u2500'.repeat(40)));
|
|
246
|
-
console.log(` Plan: ${chalk.yellow(data.plan || 'free')}`);
|
|
247
|
-
console.log(` Monthly Total: ${chalk.white('$' + (data.monthlyTotal || 0).toFixed(2))}`);
|
|
248
|
-
console.log(` Active Seats: ${chalk.cyan(data.activeSeats || 0)}`);
|
|
249
|
-
console.log(` Subscriptions: ${chalk.cyan(data.subscriptionCount || 0)}`);
|
|
250
|
-
|
|
251
|
-
if (data.subscriptions?.length) {
|
|
252
|
-
console.log('');
|
|
253
|
-
console.log(chalk.cyan(' Active Subscriptions:'));
|
|
254
|
-
for (const sub of data.subscriptions) {
|
|
255
|
-
console.log(` ${chalk.white(sub.extName)} ${chalk.gray('$' + (sub.price || 0).toFixed(2) + '/mo')} ${chalk.gray('since ' + new Date(sub.startDate).toLocaleDateString())}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (data.invoices?.length) {
|
|
260
|
-
console.log('');
|
|
261
|
-
console.log(chalk.cyan(' Recent Invoices:'));
|
|
262
|
-
for (const inv of data.invoices) {
|
|
263
|
-
const statusColor = inv.status === 'paid' ? chalk.green : inv.status === 'pending' ? chalk.yellow : chalk.red;
|
|
264
|
-
console.log(` ${chalk.gray(new Date(inv.date).toLocaleDateString())} $${inv.amount.toFixed(2)} ${statusColor(inv.status)}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
console.log('');
|
|
268
|
-
} catch (err) {
|
|
269
|
-
console.error(chalk.red(`Billing failed: ${err.message}`));
|
|
270
|
-
process.exitCode = 1;
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
async function getAuthHeaders() {
|
|
4
|
+
const { fetchFirst } = await import('../core/discoveryClient.js');
|
|
5
|
+
const { getIdentity } = await import('../core/identity.js');
|
|
6
|
+
|
|
7
|
+
const identity = await getIdentity();
|
|
8
|
+
if (!identity?.publicKey) {
|
|
9
|
+
console.error(chalk.red('No identity. Run pal init first.'));
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const keysRes = await fetchFirst(`/api/v1/keys/${encodeURIComponent(identity.publicKey)}`);
|
|
15
|
+
const keys = keysRes ? await keysRes.json() : [];
|
|
16
|
+
if (!keys?.length) {
|
|
17
|
+
console.error(chalk.red('No API key. Create one: pal api-keys create'));
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { 'Content-Type': 'application/json', 'x-api-key': keys[0].key };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function apiRequest(method, path, body) {
|
|
26
|
+
const { getPrimaryServer } = await import('../core/discoveryClient.js');
|
|
27
|
+
const headers = await getAuthHeaders();
|
|
28
|
+
if (!headers) return null;
|
|
29
|
+
|
|
30
|
+
const baseUrl = getPrimaryServer();
|
|
31
|
+
const opts = { method, headers, signal: AbortSignal.timeout(10000) };
|
|
32
|
+
if (body) opts.body = JSON.stringify(body);
|
|
33
|
+
|
|
34
|
+
const res = await fetch(`${baseUrl}${path}`, opts);
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function orgCommand(program) {
|
|
39
|
+
const cmd = program
|
|
40
|
+
.command('org')
|
|
41
|
+
.description('manage organizations for enterprise extension management')
|
|
42
|
+
.addHelpText('after', `
|
|
43
|
+
Examples:
|
|
44
|
+
$ pal org create "Acme Corp" Create an organization
|
|
45
|
+
$ pal org list List your organizations
|
|
46
|
+
$ pal org info abc123 Show org details and members
|
|
47
|
+
$ pal org invite abc123 alice Add a member by handle
|
|
48
|
+
$ pal org remove abc123 alice Remove a member
|
|
49
|
+
$ pal org subscribe abc123 analytics Subscribe org to an extension
|
|
50
|
+
$ pal org unsubscribe abc123 analytics Cancel extension subscription
|
|
51
|
+
$ pal org billing abc123 Show billing summary
|
|
52
|
+
`);
|
|
53
|
+
|
|
54
|
+
cmd
|
|
55
|
+
.command('create <name>')
|
|
56
|
+
.description('create an organization')
|
|
57
|
+
.action(async (name) => {
|
|
58
|
+
try {
|
|
59
|
+
const res = await apiRequest('POST', '/api/v1/orgs', { name });
|
|
60
|
+
if (!res) return;
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
if (res.ok && data?.id) {
|
|
63
|
+
console.log(chalk.green(`\u2714 Created organization "${name}"`));
|
|
64
|
+
console.log(` ID: ${chalk.white(data.id)}`);
|
|
65
|
+
} else {
|
|
66
|
+
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(chalk.red(`Create failed: ${err.message}`));
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
cmd
|
|
76
|
+
.command('list')
|
|
77
|
+
.description('list your organizations')
|
|
78
|
+
.action(async () => {
|
|
79
|
+
try {
|
|
80
|
+
const res = await apiRequest('GET', '/api/v1/orgs');
|
|
81
|
+
if (!res) return;
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
if (!data?.orgs?.length) {
|
|
84
|
+
console.log(chalk.gray('No organizations.'));
|
|
85
|
+
console.log(chalk.gray(' pal org create <name>'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(chalk.cyan('Organizations:'));
|
|
90
|
+
console.log(chalk.gray('\u2500'.repeat(50)));
|
|
91
|
+
for (const org of data.orgs) {
|
|
92
|
+
const role = org.role === 'owner' ? chalk.yellow('owner') : chalk.gray(org.role);
|
|
93
|
+
console.log(` ${chalk.white(org.name)} ${chalk.gray(`(${org.id})`)}`);
|
|
94
|
+
console.log(` Role: ${role} Members: ${chalk.cyan(org.memberCount || 0)} Subscriptions: ${chalk.cyan(org.subscriptionCount || 0)}`);
|
|
95
|
+
}
|
|
96
|
+
console.log('');
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(chalk.red(`List failed: ${err.message}`));
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
cmd
|
|
104
|
+
.command('info <id>')
|
|
105
|
+
.description('show org details, members, and subscriptions')
|
|
106
|
+
.action(async (id) => {
|
|
107
|
+
try {
|
|
108
|
+
const res = await apiRequest('GET', `/api/v1/orgs/${encodeURIComponent(id)}`);
|
|
109
|
+
if (!res) return;
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
const err = await res.json().catch(() => ({}));
|
|
112
|
+
console.error(chalk.red(`Not found: ${err?.error || 'Organization not found'}`));
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(chalk.cyan.bold(data.name));
|
|
119
|
+
console.log(chalk.gray('\u2500'.repeat(50)));
|
|
120
|
+
console.log(` ID: ${chalk.white(data.id)}`);
|
|
121
|
+
console.log(` Created: ${chalk.gray(new Date(data.createdAt).toLocaleDateString())}`);
|
|
122
|
+
console.log(` Plan: ${chalk.yellow(data.plan || 'free')}`);
|
|
123
|
+
|
|
124
|
+
if (data.members?.length) {
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log(chalk.cyan(' Members:'));
|
|
127
|
+
for (const m of data.members) {
|
|
128
|
+
const roleColor = m.role === 'owner' ? chalk.yellow : m.role === 'admin' ? chalk.cyan : chalk.gray;
|
|
129
|
+
console.log(` ${chalk.white('@' + m.handle)} ${roleColor(m.role)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (data.subscriptions?.length) {
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(chalk.cyan(' Subscriptions:'));
|
|
136
|
+
for (const sub of data.subscriptions) {
|
|
137
|
+
const status = sub.active ? chalk.green('active') : chalk.red('cancelled');
|
|
138
|
+
console.log(` ${chalk.white(sub.extName)} ${status} ${chalk.gray('$' + (sub.price || 0).toFixed(2) + '/mo')}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
console.log('');
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(chalk.red(`Info failed: ${err.message}`));
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
cmd
|
|
149
|
+
.command('invite <id> <handle>')
|
|
150
|
+
.description('add a member to an organization')
|
|
151
|
+
.action(async (id, handle) => {
|
|
152
|
+
try {
|
|
153
|
+
const res = await apiRequest('POST', `/api/v1/orgs/${encodeURIComponent(id)}/members`, { handle });
|
|
154
|
+
if (!res) return;
|
|
155
|
+
const data = await res.json();
|
|
156
|
+
if (res.ok && data?.success) {
|
|
157
|
+
console.log(chalk.green(`\u2714 Invited @${handle} to organization`));
|
|
158
|
+
} else {
|
|
159
|
+
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(chalk.red(`Invite failed: ${err.message}`));
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
cmd
|
|
169
|
+
.command('remove <id> <handle>')
|
|
170
|
+
.description('remove a member from an organization')
|
|
171
|
+
.action(async (id, handle) => {
|
|
172
|
+
try {
|
|
173
|
+
const res = await apiRequest('DELETE', `/api/v1/orgs/${encodeURIComponent(id)}/members/${encodeURIComponent(handle)}`);
|
|
174
|
+
if (!res) return;
|
|
175
|
+
const data = await res.json();
|
|
176
|
+
if (res.ok && data?.success) {
|
|
177
|
+
console.log(chalk.green(`\u2714 Removed @${handle} from organization`));
|
|
178
|
+
} else {
|
|
179
|
+
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(chalk.red(`Remove failed: ${err.message}`));
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
cmd
|
|
189
|
+
.command('subscribe <id> <ext-name>')
|
|
190
|
+
.description('subscribe organization to an enterprise extension')
|
|
191
|
+
.action(async (id, extName) => {
|
|
192
|
+
try {
|
|
193
|
+
const res = await apiRequest('POST', `/api/v1/orgs/${encodeURIComponent(id)}/subscriptions`, { extName });
|
|
194
|
+
if (!res) return;
|
|
195
|
+
const data = await res.json();
|
|
196
|
+
if (res.ok && data?.success) {
|
|
197
|
+
console.log(chalk.green(`\u2714 Subscribed to "${extName}"`));
|
|
198
|
+
if (data.price) console.log(` Price: ${chalk.yellow('$' + data.price.toFixed(2) + '/mo')}`);
|
|
199
|
+
} else {
|
|
200
|
+
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
201
|
+
process.exitCode = 1;
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(chalk.red(`Subscribe failed: ${err.message}`));
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
cmd
|
|
210
|
+
.command('unsubscribe <id> <ext-name>')
|
|
211
|
+
.description('cancel an extension subscription')
|
|
212
|
+
.action(async (id, extName) => {
|
|
213
|
+
try {
|
|
214
|
+
const res = await apiRequest('DELETE', `/api/v1/orgs/${encodeURIComponent(id)}/subscriptions/${encodeURIComponent(extName)}`);
|
|
215
|
+
if (!res) return;
|
|
216
|
+
const data = await res.json();
|
|
217
|
+
if (res.ok && data?.success) {
|
|
218
|
+
console.log(chalk.green(`\u2714 Unsubscribed from "${extName}"`));
|
|
219
|
+
} else {
|
|
220
|
+
console.error(chalk.red(`Failed: ${data?.error || 'Unknown error'}`));
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
}
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error(chalk.red(`Unsubscribe failed: ${err.message}`));
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
cmd
|
|
230
|
+
.command('billing <id>')
|
|
231
|
+
.description('show billing summary for an organization')
|
|
232
|
+
.action(async (id) => {
|
|
233
|
+
try {
|
|
234
|
+
const res = await apiRequest('GET', `/api/v1/orgs/${encodeURIComponent(id)}/billing`);
|
|
235
|
+
if (!res) return;
|
|
236
|
+
if (!res.ok) {
|
|
237
|
+
const err = await res.json().catch(() => ({}));
|
|
238
|
+
console.error(chalk.red(`Failed: ${err?.error || 'Unknown error'}`));
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const data = await res.json();
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log(chalk.cyan.bold('Organization Billing'));
|
|
245
|
+
console.log(chalk.gray('\u2500'.repeat(40)));
|
|
246
|
+
console.log(` Plan: ${chalk.yellow(data.plan || 'free')}`);
|
|
247
|
+
console.log(` Monthly Total: ${chalk.white('$' + (data.monthlyTotal || 0).toFixed(2))}`);
|
|
248
|
+
console.log(` Active Seats: ${chalk.cyan(data.activeSeats || 0)}`);
|
|
249
|
+
console.log(` Subscriptions: ${chalk.cyan(data.subscriptionCount || 0)}`);
|
|
250
|
+
|
|
251
|
+
if (data.subscriptions?.length) {
|
|
252
|
+
console.log('');
|
|
253
|
+
console.log(chalk.cyan(' Active Subscriptions:'));
|
|
254
|
+
for (const sub of data.subscriptions) {
|
|
255
|
+
console.log(` ${chalk.white(sub.extName)} ${chalk.gray('$' + (sub.price || 0).toFixed(2) + '/mo')} ${chalk.gray('since ' + new Date(sub.startDate).toLocaleDateString())}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (data.invoices?.length) {
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(chalk.cyan(' Recent Invoices:'));
|
|
262
|
+
for (const inv of data.invoices) {
|
|
263
|
+
const statusColor = inv.status === 'paid' ? chalk.green : inv.status === 'pending' ? chalk.yellow : chalk.red;
|
|
264
|
+
console.log(` ${chalk.gray(new Date(inv.date).toLocaleDateString())} $${inv.amount.toFixed(2)} ${statusColor(inv.status)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log('');
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error(chalk.red(`Billing failed: ${err.message}`));
|
|
270
|
+
process.exitCode = 1;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|