primitive-admin 1.0.21 → 1.0.22

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.
@@ -0,0 +1,768 @@
1
+ import { ApiClient } from "../lib/api-client.js";
2
+ import { getCurrentAppId } from "../lib/config.js";
3
+ import { success, error, info, keyValue, formatTable, formatId, formatDate, json, } from "../lib/output.js";
4
+ function resolveAppId(appId, options) {
5
+ const resolved = appId || options.app || getCurrentAppId();
6
+ if (!resolved) {
7
+ error("No app specified. Use <app-id>, --app, or 'primitive use <app-id>' to set context.");
8
+ process.exit(1);
9
+ }
10
+ return resolved;
11
+ }
12
+ export function registerDatabasesCommands(program) {
13
+ const databases = program
14
+ .command("databases")
15
+ .description("Create, manage, and configure online databases")
16
+ .addHelpText("after", `
17
+ Examples:
18
+ $ primitive databases list
19
+ $ primitive databases create "My Database"
20
+ $ primitive databases get <database-id>
21
+ $ primitive databases records models <database-id>
22
+ $ primitive databases records query <database-id> <model-name>
23
+ $ primitive databases records save <database-id> <model-name> --data '{"name":"Alice"}'
24
+ $ primitive databases indexes list <database-id>
25
+ $ primitive databases indexes create <database-id> --model contacts --field email
26
+ $ primitive databases permissions list <database-id>
27
+ $ primitive databases group-permissions list <database-id>
28
+ `);
29
+ // List databases
30
+ databases
31
+ .command("list")
32
+ .description("List databases in an app")
33
+ .argument("[app-id]", "App ID (uses current app if not specified)")
34
+ .option("--app <app-id>", "App ID")
35
+ .option("--json", "Output as JSON")
36
+ .action(async (appId, options) => {
37
+ const resolvedAppId = resolveAppId(appId, options);
38
+ const client = new ApiClient();
39
+ try {
40
+ const result = await client.listDatabases(resolvedAppId);
41
+ const list = Array.isArray(result) ? result : result?.databases ?? [];
42
+ if (options.json) {
43
+ json(list);
44
+ return;
45
+ }
46
+ if (list.length === 0) {
47
+ info("No databases found.");
48
+ return;
49
+ }
50
+ console.log(formatTable(list, [
51
+ { header: "ID", key: "databaseId", format: formatId },
52
+ { header: "TITLE", key: "title" },
53
+ { header: "DEFAULT PERM", key: "defaultPermission", format: (v) => v || "—" },
54
+ { header: "PERMISSION", key: "permission" },
55
+ { header: "CREATED", key: "createdAt", format: formatDate },
56
+ ]));
57
+ }
58
+ catch (err) {
59
+ error(err.message);
60
+ process.exit(1);
61
+ }
62
+ });
63
+ // Create database
64
+ databases
65
+ .command("create")
66
+ .description("Create a new database")
67
+ .argument("<title>", "Database title")
68
+ .option("--app <app-id>", "App ID")
69
+ .option("--default-permission <level>", "Default permission for all app users: reader or read-write")
70
+ .option("--json", "Output as JSON")
71
+ .action(async (title, options) => {
72
+ const resolvedAppId = resolveAppId(undefined, options);
73
+ const client = new ApiClient();
74
+ try {
75
+ const params = { title };
76
+ if (options.defaultPermission) {
77
+ params.defaultPermission = options.defaultPermission;
78
+ }
79
+ const result = await client.createDatabase(resolvedAppId, params);
80
+ if (options.json) {
81
+ json(result);
82
+ return;
83
+ }
84
+ success("Database created.");
85
+ keyValue("Database ID", result.databaseId);
86
+ keyValue("Title", result.title);
87
+ if (result.defaultPermission) {
88
+ keyValue("Default Permission", result.defaultPermission);
89
+ }
90
+ }
91
+ catch (err) {
92
+ error(err.message);
93
+ process.exit(1);
94
+ }
95
+ });
96
+ // Get database
97
+ databases
98
+ .command("get")
99
+ .description("Get database details")
100
+ .argument("<database-id>", "Database ID")
101
+ .option("--app <app-id>", "App ID")
102
+ .option("--json", "Output as JSON")
103
+ .action(async (databaseId, options) => {
104
+ const resolvedAppId = resolveAppId(undefined, options);
105
+ const client = new ApiClient();
106
+ try {
107
+ const result = await client.getDatabase(resolvedAppId, databaseId);
108
+ if (options.json) {
109
+ json(result);
110
+ return;
111
+ }
112
+ keyValue("Database ID", result.databaseId);
113
+ keyValue("Title", result.title);
114
+ keyValue("Default Permission", result.defaultPermission || "none");
115
+ keyValue("Rule Set", result.ruleSetId || "none");
116
+ keyValue("Permission", result.permission);
117
+ keyValue("Created", formatDate(result.createdAt));
118
+ }
119
+ catch (err) {
120
+ error(err.message);
121
+ process.exit(1);
122
+ }
123
+ });
124
+ // Update database
125
+ databases
126
+ .command("update")
127
+ .description("Update a database (title, default permission, rule set)")
128
+ .argument("<database-id>", "Database ID")
129
+ .option("--app <app-id>", "App ID")
130
+ .option("--title <title>", "New title")
131
+ .option("--default-permission <level>", "Default permission for all app users: reader, read-write, or none")
132
+ .option("--rule-set-id <id>", "Attach a rule set (use 'none' to detach)")
133
+ .option("--json", "Output as JSON")
134
+ .action(async (databaseId, options) => {
135
+ const resolvedAppId = resolveAppId(undefined, options);
136
+ const client = new ApiClient();
137
+ const params = {};
138
+ if (options.title) {
139
+ params.title = options.title;
140
+ }
141
+ if (options.defaultPermission !== undefined) {
142
+ params.defaultPermission =
143
+ options.defaultPermission === "none"
144
+ ? null
145
+ : options.defaultPermission;
146
+ }
147
+ if (options.ruleSetId !== undefined) {
148
+ params.ruleSetId =
149
+ options.ruleSetId === "none"
150
+ ? null
151
+ : options.ruleSetId;
152
+ }
153
+ if (Object.keys(params).length === 0) {
154
+ error("Provide at least one of --title, --default-permission, or --rule-set-id.");
155
+ process.exit(1);
156
+ }
157
+ try {
158
+ const result = await client.updateDatabase(resolvedAppId, databaseId, params);
159
+ if (options.json) {
160
+ json(result);
161
+ return;
162
+ }
163
+ success("Database updated.");
164
+ keyValue("Database ID", result.databaseId);
165
+ keyValue("Title", result.title);
166
+ keyValue("Default Permission", result.defaultPermission || "none");
167
+ keyValue("Rule Set", result.ruleSetId || "none");
168
+ }
169
+ catch (err) {
170
+ error(err.message);
171
+ process.exit(1);
172
+ }
173
+ });
174
+ // Delete database
175
+ databases
176
+ .command("delete")
177
+ .description("Delete a database")
178
+ .argument("<database-id>", "Database ID")
179
+ .option("--app <app-id>", "App ID")
180
+ .option("-y, --yes", "Skip confirmation prompt")
181
+ .action(async (databaseId, options) => {
182
+ const resolvedAppId = resolveAppId(undefined, options);
183
+ if (!options.yes) {
184
+ const inquirer = await import("inquirer");
185
+ const { confirm } = await inquirer.default.prompt([
186
+ {
187
+ type: "confirm",
188
+ name: "confirm",
189
+ message: `Delete database ${databaseId}? This cannot be undone.`,
190
+ default: false,
191
+ },
192
+ ]);
193
+ if (!confirm) {
194
+ info("Cancelled.");
195
+ return;
196
+ }
197
+ }
198
+ const client = new ApiClient();
199
+ try {
200
+ await client.deleteDatabase(resolvedAppId, databaseId);
201
+ success(`Database ${databaseId} deleted.`);
202
+ }
203
+ catch (err) {
204
+ error(err.message);
205
+ process.exit(1);
206
+ }
207
+ });
208
+ // ---- Permissions subcommand group ----
209
+ const permissions = databases
210
+ .command("permissions")
211
+ .description("Manage user permissions on a database");
212
+ // List permissions
213
+ permissions
214
+ .command("list")
215
+ .description("List user permissions for a database")
216
+ .argument("<database-id>", "Database ID")
217
+ .option("--app <app-id>", "App ID")
218
+ .option("--json", "Output as JSON")
219
+ .action(async (databaseId, options) => {
220
+ const resolvedAppId = resolveAppId(undefined, options);
221
+ const client = new ApiClient();
222
+ try {
223
+ const result = await client.listDatabasePermissions(resolvedAppId, databaseId);
224
+ const list = Array.isArray(result) ? result : result?.permissions ?? [];
225
+ if (options.json) {
226
+ json(list);
227
+ return;
228
+ }
229
+ if (list.length === 0) {
230
+ info("No permissions found.");
231
+ return;
232
+ }
233
+ console.log(formatTable(list, [
234
+ { header: "USER_ID", key: "userId", format: formatId },
235
+ { header: "EMAIL", key: "userEmail" },
236
+ { header: "PERMISSION", key: "permission" },
237
+ { header: "GRANTED", key: "grantedAt", format: formatDate },
238
+ ]));
239
+ }
240
+ catch (err) {
241
+ error(err.message);
242
+ process.exit(1);
243
+ }
244
+ });
245
+ // Grant permission
246
+ permissions
247
+ .command("grant")
248
+ .description("Grant a user permission on a database")
249
+ .argument("<database-id>", "Database ID")
250
+ .requiredOption("--user-id <user-id>", "User ID to grant permission to")
251
+ .requiredOption("--permission <permission>", "Permission level: owner, read-write, or reader")
252
+ .option("--app <app-id>", "App ID")
253
+ .option("--json", "Output as JSON")
254
+ .action(async (databaseId, options) => {
255
+ const resolvedAppId = resolveAppId(undefined, options);
256
+ const client = new ApiClient();
257
+ try {
258
+ const result = await client.grantDatabasePermission(resolvedAppId, databaseId, {
259
+ userId: options.userId,
260
+ permission: options.permission,
261
+ });
262
+ if (options.json) {
263
+ json(result);
264
+ return;
265
+ }
266
+ success(`Permission '${options.permission}' granted to user ${options.userId}.`);
267
+ }
268
+ catch (err) {
269
+ error(err.message);
270
+ process.exit(1);
271
+ }
272
+ });
273
+ // Revoke permission
274
+ permissions
275
+ .command("revoke")
276
+ .description("Revoke a user's permission on a database")
277
+ .argument("<database-id>", "Database ID")
278
+ .argument("<user-id>", "User ID to revoke")
279
+ .option("--app <app-id>", "App ID")
280
+ .option("-y, --yes", "Skip confirmation prompt")
281
+ .action(async (databaseId, userId, options) => {
282
+ const resolvedAppId = resolveAppId(undefined, options);
283
+ if (!options.yes) {
284
+ const inquirer = await import("inquirer");
285
+ const { confirm } = await inquirer.default.prompt([
286
+ {
287
+ type: "confirm",
288
+ name: "confirm",
289
+ message: `Revoke permission for user ${userId} on database ${databaseId}?`,
290
+ default: false,
291
+ },
292
+ ]);
293
+ if (!confirm) {
294
+ info("Cancelled.");
295
+ return;
296
+ }
297
+ }
298
+ const client = new ApiClient();
299
+ try {
300
+ await client.revokeDatabasePermission(resolvedAppId, databaseId, userId);
301
+ success(`Permission revoked for user ${userId}.`);
302
+ }
303
+ catch (err) {
304
+ error(err.message);
305
+ process.exit(1);
306
+ }
307
+ });
308
+ // ---- Records subcommand group ----
309
+ const records = databases
310
+ .command("records")
311
+ .description("Query, create, and manage records in a database");
312
+ // List models
313
+ records
314
+ .command("models")
315
+ .description("List model names in a database")
316
+ .argument("<database-id>", "Database ID")
317
+ .option("--app <app-id>", "App ID")
318
+ .option("--json", "Output as JSON")
319
+ .action(async (databaseId, options) => {
320
+ const resolvedAppId = resolveAppId(undefined, options);
321
+ const client = new ApiClient();
322
+ try {
323
+ const result = await client.listDatabaseModels(resolvedAppId, databaseId);
324
+ const models = result.models || [];
325
+ if (options.json) {
326
+ json(models);
327
+ return;
328
+ }
329
+ if (models.length === 0) {
330
+ info("No models found.");
331
+ return;
332
+ }
333
+ for (const m of models) {
334
+ console.log(` ${m}`);
335
+ }
336
+ }
337
+ catch (err) {
338
+ error(err.message);
339
+ process.exit(1);
340
+ }
341
+ });
342
+ // Describe model
343
+ records
344
+ .command("describe")
345
+ .description("Show inferred schema for a model")
346
+ .argument("<database-id>", "Database ID")
347
+ .argument("<model-name>", "Model name")
348
+ .option("--app <app-id>", "App ID")
349
+ .option("--json", "Output as JSON")
350
+ .action(async (databaseId, modelName, options) => {
351
+ const resolvedAppId = resolveAppId(undefined, options);
352
+ const client = new ApiClient();
353
+ try {
354
+ const result = await client.describeDatabaseModel(resolvedAppId, databaseId, modelName);
355
+ const fields = result.fields || [];
356
+ if (options.json) {
357
+ json(fields);
358
+ return;
359
+ }
360
+ if (fields.length === 0) {
361
+ info("No fields detected.");
362
+ return;
363
+ }
364
+ console.log(formatTable(fields, [
365
+ { header: "FIELD", key: "field_name" },
366
+ { header: "TYPE", key: "inferred_type" },
367
+ ]));
368
+ }
369
+ catch (err) {
370
+ error(err.message);
371
+ process.exit(1);
372
+ }
373
+ });
374
+ // Query records
375
+ records
376
+ .command("query")
377
+ .description("Query records in a database")
378
+ .argument("<database-id>", "Database ID")
379
+ .argument("<model-name>", "Model name")
380
+ .option("--filter <json>", "Filter as JSON", "{}")
381
+ .option("--limit <n>", "Max records to return", "25")
382
+ .option("--cursor <cursor>", "Pagination cursor")
383
+ .option("--app <app-id>", "App ID")
384
+ .option("--json", "Output as JSON")
385
+ .action(async (databaseId, modelName, options) => {
386
+ const resolvedAppId = resolveAppId(undefined, options);
387
+ const client = new ApiClient();
388
+ let filter = {};
389
+ try {
390
+ filter = JSON.parse(options.filter);
391
+ }
392
+ catch {
393
+ error("Invalid --filter JSON.");
394
+ process.exit(1);
395
+ }
396
+ try {
397
+ const body = {
398
+ modelName,
399
+ filter,
400
+ options: { limit: parseInt(options.limit, 10) },
401
+ };
402
+ if (options.cursor)
403
+ body.options.cursor = options.cursor;
404
+ const result = await client.queryDatabaseRecords(resolvedAppId, databaseId, body);
405
+ const data = result.data || [];
406
+ if (options.json) {
407
+ json(result);
408
+ return;
409
+ }
410
+ if (data.length === 0) {
411
+ info("No records found.");
412
+ return;
413
+ }
414
+ // Derive columns from first record
415
+ const cols = Object.keys(data[0]).filter((k) => k !== "type");
416
+ console.log(formatTable(data, cols.map((c) => ({
417
+ header: c.toUpperCase(),
418
+ key: c,
419
+ format: (v) => {
420
+ if (v === null || v === undefined)
421
+ return "—";
422
+ if (typeof v === "object") {
423
+ const s = JSON.stringify(v);
424
+ return s.length > 50 ? s.slice(0, 47) + "..." : s;
425
+ }
426
+ const s = String(v);
427
+ return s.length > 50 ? s.slice(0, 47) + "..." : s;
428
+ },
429
+ }))));
430
+ if (result.hasMore) {
431
+ info(`More records available. Use --cursor ${result.cursor} to continue.`);
432
+ }
433
+ }
434
+ catch (err) {
435
+ error(err.message);
436
+ process.exit(1);
437
+ }
438
+ });
439
+ // Count records
440
+ records
441
+ .command("count")
442
+ .description("Count records of a model")
443
+ .argument("<database-id>", "Database ID")
444
+ .argument("<model-name>", "Model name")
445
+ .option("--filter <json>", "Filter as JSON", "{}")
446
+ .option("--app <app-id>", "App ID")
447
+ .option("--json", "Output as JSON")
448
+ .action(async (databaseId, modelName, options) => {
449
+ const resolvedAppId = resolveAppId(undefined, options);
450
+ const client = new ApiClient();
451
+ let filter = {};
452
+ try {
453
+ filter = JSON.parse(options.filter);
454
+ }
455
+ catch {
456
+ error("Invalid --filter JSON.");
457
+ process.exit(1);
458
+ }
459
+ try {
460
+ const result = await client.countDatabaseRecords(resolvedAppId, databaseId, {
461
+ modelName,
462
+ filter,
463
+ });
464
+ if (options.json) {
465
+ json(result);
466
+ return;
467
+ }
468
+ console.log(`Count: ${result.count}`);
469
+ }
470
+ catch (err) {
471
+ error(err.message);
472
+ process.exit(1);
473
+ }
474
+ });
475
+ // Save record
476
+ records
477
+ .command("save")
478
+ .description("Create or update a record")
479
+ .argument("<database-id>", "Database ID")
480
+ .argument("<model-name>", "Model name")
481
+ .option("--id <id>", "Record ID (auto-generated if omitted)")
482
+ .requiredOption("--data <json>", "Record data as JSON")
483
+ .option("--app <app-id>", "App ID")
484
+ .option("--json", "Output as JSON")
485
+ .action(async (databaseId, modelName, options) => {
486
+ const resolvedAppId = resolveAppId(undefined, options);
487
+ const client = new ApiClient();
488
+ let data;
489
+ try {
490
+ data = JSON.parse(options.data);
491
+ }
492
+ catch {
493
+ error("Invalid --data JSON.");
494
+ process.exit(1);
495
+ return;
496
+ }
497
+ const id = options.id || crypto.randomUUID().replace(/-/g, "").slice(0, 20);
498
+ try {
499
+ const result = await client.saveDatabaseRecord(resolvedAppId, databaseId, {
500
+ modelName,
501
+ id,
502
+ data,
503
+ });
504
+ if (options.json) {
505
+ json(result);
506
+ return;
507
+ }
508
+ success(`Record saved.`);
509
+ keyValue("ID", result.id || id);
510
+ }
511
+ catch (err) {
512
+ error(err.message);
513
+ process.exit(1);
514
+ }
515
+ });
516
+ // Delete record
517
+ records
518
+ .command("delete")
519
+ .description("Delete a record")
520
+ .argument("<database-id>", "Database ID")
521
+ .argument("<model-name>", "Model name")
522
+ .argument("<record-id>", "Record ID")
523
+ .option("--app <app-id>", "App ID")
524
+ .option("-y, --yes", "Skip confirmation prompt")
525
+ .action(async (databaseId, modelName, recordId, options) => {
526
+ const resolvedAppId = resolveAppId(undefined, options);
527
+ if (!options.yes) {
528
+ const inquirer = await import("inquirer");
529
+ const { confirm } = await inquirer.default.prompt([
530
+ {
531
+ type: "confirm",
532
+ name: "confirm",
533
+ message: `Delete record ${recordId} from ${modelName}?`,
534
+ default: false,
535
+ },
536
+ ]);
537
+ if (!confirm) {
538
+ info("Cancelled.");
539
+ return;
540
+ }
541
+ }
542
+ const client = new ApiClient();
543
+ try {
544
+ await client.deleteDatabaseRecord(resolvedAppId, databaseId, {
545
+ modelName,
546
+ id: recordId,
547
+ });
548
+ success(`Record ${recordId} deleted.`);
549
+ }
550
+ catch (err) {
551
+ error(err.message);
552
+ process.exit(1);
553
+ }
554
+ });
555
+ // ---- Indexes subcommand group ----
556
+ const indexes = databases
557
+ .command("indexes")
558
+ .description("Manage indexes on a database");
559
+ // List indexes
560
+ indexes
561
+ .command("list")
562
+ .description("List indexes for a database or model")
563
+ .argument("<database-id>", "Database ID")
564
+ .option("--model <model-name>", "Filter by model name")
565
+ .option("--app <app-id>", "App ID")
566
+ .option("--json", "Output as JSON")
567
+ .action(async (databaseId, options) => {
568
+ const resolvedAppId = resolveAppId(undefined, options);
569
+ const client = new ApiClient();
570
+ try {
571
+ const result = await client.listDatabaseIndexes(resolvedAppId, databaseId, options.model);
572
+ const list = result.indexes || [];
573
+ if (options.json) {
574
+ json(list);
575
+ return;
576
+ }
577
+ if (list.length === 0) {
578
+ info("No indexes found.");
579
+ return;
580
+ }
581
+ console.log(formatTable(list, [
582
+ { header: "MODEL", key: "model_name" },
583
+ { header: "FIELD", key: "field_name" },
584
+ { header: "TYPE", key: "field_type" },
585
+ { header: "UNIQUE", key: "is_unique", format: (v) => (v ? "Yes" : "No") },
586
+ ]));
587
+ }
588
+ catch (err) {
589
+ error(err.message);
590
+ process.exit(1);
591
+ }
592
+ });
593
+ // Register index
594
+ indexes
595
+ .command("create")
596
+ .description("Create an index on a model field")
597
+ .argument("<database-id>", "Database ID")
598
+ .requiredOption("--model <model-name>", "Model name")
599
+ .requiredOption("--field <field-name>", "Field name to index")
600
+ .option("--type <field-type>", "Field type: string, number, or boolean", "string")
601
+ .option("--unique", "Make this a unique index")
602
+ .option("--app <app-id>", "App ID")
603
+ .option("--json", "Output as JSON")
604
+ .action(async (databaseId, options) => {
605
+ const resolvedAppId = resolveAppId(undefined, options);
606
+ const client = new ApiClient();
607
+ try {
608
+ const result = await client.registerDatabaseIndex(resolvedAppId, databaseId, {
609
+ modelName: options.model,
610
+ fieldName: options.field,
611
+ fieldType: options.type,
612
+ unique: !!options.unique,
613
+ });
614
+ if (options.json) {
615
+ json(result);
616
+ return;
617
+ }
618
+ success(`Index created on ${options.model}.${options.field} (${options.type}${options.unique ? ", unique" : ""}).`);
619
+ }
620
+ catch (err) {
621
+ error(err.message);
622
+ process.exit(1);
623
+ }
624
+ });
625
+ // Drop index
626
+ indexes
627
+ .command("drop")
628
+ .description("Drop an index from a model field")
629
+ .argument("<database-id>", "Database ID")
630
+ .requiredOption("--model <model-name>", "Model name")
631
+ .requiredOption("--field <field-name>", "Field name")
632
+ .option("--app <app-id>", "App ID")
633
+ .option("-y, --yes", "Skip confirmation prompt")
634
+ .action(async (databaseId, options) => {
635
+ const resolvedAppId = resolveAppId(undefined, options);
636
+ if (!options.yes) {
637
+ const inquirer = await import("inquirer");
638
+ const { confirm } = await inquirer.default.prompt([
639
+ {
640
+ type: "confirm",
641
+ name: "confirm",
642
+ message: `Drop index on ${options.model}.${options.field}?`,
643
+ default: false,
644
+ },
645
+ ]);
646
+ if (!confirm) {
647
+ info("Cancelled.");
648
+ return;
649
+ }
650
+ }
651
+ const client = new ApiClient();
652
+ try {
653
+ await client.dropDatabaseIndex(resolvedAppId, databaseId, {
654
+ modelName: options.model,
655
+ fieldName: options.field,
656
+ });
657
+ success(`Index dropped on ${options.model}.${options.field}.`);
658
+ }
659
+ catch (err) {
660
+ error(err.message);
661
+ process.exit(1);
662
+ }
663
+ });
664
+ // ---- Group Permissions subcommand group ----
665
+ const groupPermissions = databases
666
+ .command("group-permissions")
667
+ .description("Manage group permissions on a database");
668
+ // List group permissions
669
+ groupPermissions
670
+ .command("list")
671
+ .description("List group permissions for a database")
672
+ .argument("<database-id>", "Database ID")
673
+ .option("--app <app-id>", "App ID")
674
+ .option("--json", "Output as JSON")
675
+ .action(async (databaseId, options) => {
676
+ const resolvedAppId = resolveAppId(undefined, options);
677
+ const client = new ApiClient();
678
+ try {
679
+ const result = await client.listDatabaseGroupPermissions(resolvedAppId, databaseId);
680
+ const list = Array.isArray(result) ? result : result?.permissions ?? [];
681
+ if (options.json) {
682
+ json(list);
683
+ return;
684
+ }
685
+ if (list.length === 0) {
686
+ info("No group permissions found.");
687
+ return;
688
+ }
689
+ console.log(formatTable(list, [
690
+ { header: "GROUP_TYPE", key: "groupType" },
691
+ { header: "GROUP_ID", key: "groupId" },
692
+ { header: "PERMISSION", key: "permission" },
693
+ { header: "GRANTED", key: "grantedAt", format: formatDate },
694
+ ]));
695
+ }
696
+ catch (err) {
697
+ error(err.message);
698
+ process.exit(1);
699
+ }
700
+ });
701
+ // Grant group permission
702
+ groupPermissions
703
+ .command("grant")
704
+ .description("Grant a group permission on a database")
705
+ .argument("<database-id>", "Database ID")
706
+ .requiredOption("--group-type <type>", "Group type")
707
+ .requiredOption("--group-id <id>", "Group ID")
708
+ .requiredOption("--permission <permission>", "Permission level: read-write or reader")
709
+ .option("--app <app-id>", "App ID")
710
+ .option("--json", "Output as JSON")
711
+ .action(async (databaseId, options) => {
712
+ const resolvedAppId = resolveAppId(undefined, options);
713
+ const client = new ApiClient();
714
+ try {
715
+ const result = await client.grantDatabaseGroupPermission(resolvedAppId, databaseId, {
716
+ groupType: options.groupType,
717
+ groupId: options.groupId,
718
+ permission: options.permission,
719
+ });
720
+ if (options.json) {
721
+ json(result);
722
+ return;
723
+ }
724
+ success(`Permission '${options.permission}' granted to group ${options.groupType}/${options.groupId}.`);
725
+ }
726
+ catch (err) {
727
+ error(err.message);
728
+ process.exit(1);
729
+ }
730
+ });
731
+ // Revoke group permission
732
+ groupPermissions
733
+ .command("revoke")
734
+ .description("Revoke a group's permission on a database")
735
+ .argument("<database-id>", "Database ID")
736
+ .argument("<group-type>", "Group type")
737
+ .argument("<group-id>", "Group ID")
738
+ .option("--app <app-id>", "App ID")
739
+ .option("-y, --yes", "Skip confirmation prompt")
740
+ .action(async (databaseId, groupType, groupId, options) => {
741
+ const resolvedAppId = resolveAppId(undefined, options);
742
+ if (!options.yes) {
743
+ const inquirer = await import("inquirer");
744
+ const { confirm } = await inquirer.default.prompt([
745
+ {
746
+ type: "confirm",
747
+ name: "confirm",
748
+ message: `Revoke permission for group ${groupType}/${groupId} on database ${databaseId}?`,
749
+ default: false,
750
+ },
751
+ ]);
752
+ if (!confirm) {
753
+ info("Cancelled.");
754
+ return;
755
+ }
756
+ }
757
+ const client = new ApiClient();
758
+ try {
759
+ await client.revokeDatabaseGroupPermission(resolvedAppId, databaseId, groupType, groupId);
760
+ success(`Permission revoked for group ${groupType}/${groupId}.`);
761
+ }
762
+ catch (err) {
763
+ error(err.message);
764
+ process.exit(1);
765
+ }
766
+ });
767
+ }
768
+ //# sourceMappingURL=databases.js.map