@tolinax/ayoune-cli 2026.2.3 → 2026.2.4
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/lib/commands/createCompletionsCommand.js +16 -1
- package/lib/commands/createExportCommand.js +219 -0
- package/lib/commands/createPermissionsCommand.js +241 -0
- package/lib/commands/createProgram.js +10 -0
- package/lib/commands/createSyncCommand.js +177 -0
- package/lib/commands/createTemplateCommand.js +238 -0
- package/lib/commands/createUsersCommand.js +285 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ _ay_completions() {
|
|
|
3
3
|
local cur commands
|
|
4
4
|
COMPREPLY=()
|
|
5
5
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
6
|
-
commands="modules list get edit copy create describe audit stream events storage whoami login logout completions alias config actions exec ai services deploy monitor delete update batch search webhooks jobs"
|
|
6
|
+
commands="modules list get edit copy create describe audit stream events storage whoami login logout completions alias config actions exec ai services deploy monitor delete update batch search webhooks jobs export users sync permissions templates"
|
|
7
7
|
|
|
8
8
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
9
9
|
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
@@ -44,6 +44,11 @@ _ay() {
|
|
|
44
44
|
'search:Search entries with advanced filtering'
|
|
45
45
|
'webhooks:Manage webhooks and event subscriptions'
|
|
46
46
|
'jobs:Manage background jobs and automations'
|
|
47
|
+
'export:Export data from collections'
|
|
48
|
+
'users:Manage users, teams, and roles'
|
|
49
|
+
'sync:Synchronize platform data across systems'
|
|
50
|
+
'permissions:Manage permissions and access requests'
|
|
51
|
+
'templates:Manage platform templates'
|
|
47
52
|
)
|
|
48
53
|
_describe 'command' commands
|
|
49
54
|
}
|
|
@@ -79,6 +84,11 @@ complete -c ay -n '__fish_use_subcommand' -a batch -d 'Run bulk operations on mu
|
|
|
79
84
|
complete -c ay -n '__fish_use_subcommand' -a search -d 'Search entries with advanced filtering'
|
|
80
85
|
complete -c ay -n '__fish_use_subcommand' -a webhooks -d 'Manage webhooks and event subscriptions'
|
|
81
86
|
complete -c ay -n '__fish_use_subcommand' -a jobs -d 'Manage background jobs and automations'
|
|
87
|
+
complete -c ay -n '__fish_use_subcommand' -a export -d 'Export data from collections'
|
|
88
|
+
complete -c ay -n '__fish_use_subcommand' -a users -d 'Manage users, teams, and roles'
|
|
89
|
+
complete -c ay -n '__fish_use_subcommand' -a sync -d 'Synchronize platform data across systems'
|
|
90
|
+
complete -c ay -n '__fish_use_subcommand' -a permissions -d 'Manage permissions and access requests'
|
|
91
|
+
complete -c ay -n '__fish_use_subcommand' -a templates -d 'Manage platform templates'
|
|
82
92
|
###-end-ay-completions-###`;
|
|
83
93
|
const POWERSHELL_COMPLETION = `###-begin-ay-completions-###
|
|
84
94
|
Register-ArgumentCompleter -CommandName ay -ScriptBlock {
|
|
@@ -113,6 +123,11 @@ Register-ArgumentCompleter -CommandName ay -ScriptBlock {
|
|
|
113
123
|
@{ Name = 'search'; Description = 'Search entries with advanced filtering' }
|
|
114
124
|
@{ Name = 'webhooks'; Description = 'Manage webhooks and event subscriptions' }
|
|
115
125
|
@{ Name = 'jobs'; Description = 'Manage background jobs and automations' }
|
|
126
|
+
@{ Name = 'export'; Description = 'Export data from collections' }
|
|
127
|
+
@{ Name = 'users'; Description = 'Manage users, teams, and roles' }
|
|
128
|
+
@{ Name = 'sync'; Description = 'Synchronize platform data across systems' }
|
|
129
|
+
@{ Name = 'permissions'; Description = 'Manage permissions and access requests' }
|
|
130
|
+
@{ Name = 'templates'; Description = 'Manage platform templates' }
|
|
116
131
|
)
|
|
117
132
|
$commands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {
|
|
118
133
|
[System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Description)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
|
|
2
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
3
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
4
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
5
|
+
import { spinner } from "../../index.js";
|
|
6
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
7
|
+
export function createExportCommand(program) {
|
|
8
|
+
const exp = program
|
|
9
|
+
.command("export")
|
|
10
|
+
.alias("exp")
|
|
11
|
+
.description("Export data from collections in various formats");
|
|
12
|
+
// ay export run <collection>
|
|
13
|
+
exp
|
|
14
|
+
.command("run <collection>")
|
|
15
|
+
.description("Export entries from a collection")
|
|
16
|
+
.addHelpText("after", `
|
|
17
|
+
Examples:
|
|
18
|
+
ay export run contacts --format csv --fields "firstName,lastName,email"
|
|
19
|
+
ay export run products --format json --filter "status=active"
|
|
20
|
+
ay export run invoices --format csv --filter "createdAt>2025-01-01" --save`)
|
|
21
|
+
.option("--format <format>", "Export format (json, csv, yaml)", "csv")
|
|
22
|
+
.option("--fields <fields>", "Comma-separated fields to include in export")
|
|
23
|
+
.option("--filter <filters>", "Comma-separated key=value filters")
|
|
24
|
+
.option("--sort <field>", "Sort by field (prefix with - for descending)", "-createdAt")
|
|
25
|
+
.option("-l, --limit <number>", "Limit results (0 = all)", parseInt, 0)
|
|
26
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
27
|
+
.action(async (collection, options) => {
|
|
28
|
+
var _a, _b, _c, _d, _e, _f;
|
|
29
|
+
try {
|
|
30
|
+
const opts = { ...program.opts(), ...options };
|
|
31
|
+
const module = getModuleFromCollection(collection);
|
|
32
|
+
spinner.start({ text: `Exporting ${collection}...`, color: "magenta" });
|
|
33
|
+
const params = {
|
|
34
|
+
page: opts.page,
|
|
35
|
+
sort: opts.sort,
|
|
36
|
+
responseFormat: opts.format,
|
|
37
|
+
verbosity: opts.verbosity,
|
|
38
|
+
};
|
|
39
|
+
if (opts.limit > 0)
|
|
40
|
+
params.limit = opts.limit;
|
|
41
|
+
if (opts.fields)
|
|
42
|
+
params.fields = opts.fields;
|
|
43
|
+
// Parse filters
|
|
44
|
+
if (opts.filter) {
|
|
45
|
+
const filters = opts.filter.split(",");
|
|
46
|
+
for (const f of filters) {
|
|
47
|
+
const match = f.match(/^(\w+)(!=|>=|<=|>|<|=)(.+)$/);
|
|
48
|
+
if (match) {
|
|
49
|
+
const [, key, op, value] = match;
|
|
50
|
+
if (op === "=")
|
|
51
|
+
params[key] = value;
|
|
52
|
+
else if (op === "!=")
|
|
53
|
+
params[`${key}[ne]`] = value;
|
|
54
|
+
else if (op === ">")
|
|
55
|
+
params[`${key}[gt]`] = value;
|
|
56
|
+
else if (op === "<")
|
|
57
|
+
params[`${key}[lt]`] = value;
|
|
58
|
+
else if (op === ">=")
|
|
59
|
+
params[`${key}[gte]`] = value;
|
|
60
|
+
else if (op === "<=")
|
|
61
|
+
params[`${key}[lte]`] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// If limit=0, fetch all pages
|
|
66
|
+
if (opts.limit === 0) {
|
|
67
|
+
params.limit = 500;
|
|
68
|
+
const allPayload = [];
|
|
69
|
+
let currentPage = 1;
|
|
70
|
+
let totalPages = 1;
|
|
71
|
+
do {
|
|
72
|
+
params.page = currentPage;
|
|
73
|
+
const res = await apiCallHandler(module.module, collection.toLowerCase(), "get", null, params);
|
|
74
|
+
if (res === null || res === void 0 ? void 0 : res.payload) {
|
|
75
|
+
if (Array.isArray(res.payload))
|
|
76
|
+
allPayload.push(...res.payload);
|
|
77
|
+
else
|
|
78
|
+
allPayload.push(res.payload);
|
|
79
|
+
}
|
|
80
|
+
totalPages = (_c = (_b = (_a = res === null || res === void 0 ? void 0 : res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalPages) !== null && _c !== void 0 ? _c : 1;
|
|
81
|
+
spinner.update({ text: `Exporting ${collection}... page ${currentPage}/${totalPages}` });
|
|
82
|
+
currentPage++;
|
|
83
|
+
} while (currentPage <= totalPages);
|
|
84
|
+
const fullRes = {
|
|
85
|
+
payload: allPayload,
|
|
86
|
+
meta: { pageInfo: { totalEntries: allPayload.length, page: 1, totalPages: 1 } },
|
|
87
|
+
};
|
|
88
|
+
// Force response format to match export format
|
|
89
|
+
opts.responseFormat = opts.format;
|
|
90
|
+
handleResponseFormatOptions(opts, fullRes);
|
|
91
|
+
spinner.success({ text: `Exported ${allPayload.length} entries from ${collection}` });
|
|
92
|
+
spinner.stop();
|
|
93
|
+
if (opts.save)
|
|
94
|
+
await saveFile(`export-${collection}`, opts, fullRes);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const res = await apiCallHandler(module.module, collection.toLowerCase(), "get", null, params);
|
|
98
|
+
opts.responseFormat = opts.format;
|
|
99
|
+
handleResponseFormatOptions(opts, res);
|
|
100
|
+
const total = (_f = (_e = (_d = res.meta) === null || _d === void 0 ? void 0 : _d.pageInfo) === null || _e === void 0 ? void 0 : _e.totalEntries) !== null && _f !== void 0 ? _f : 0;
|
|
101
|
+
spinner.success({ text: `Exported ${total} entries from ${collection}` });
|
|
102
|
+
spinner.stop();
|
|
103
|
+
if (opts.save)
|
|
104
|
+
await saveFile(`export-${collection}`, opts, res);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
spinner.error({ text: e.message || "Export failed" });
|
|
109
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// ay export list
|
|
113
|
+
exp
|
|
114
|
+
.command("list")
|
|
115
|
+
.alias("ls")
|
|
116
|
+
.description("List previous exports")
|
|
117
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
118
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
119
|
+
.action(async (options) => {
|
|
120
|
+
var _a, _b, _c;
|
|
121
|
+
try {
|
|
122
|
+
const opts = { ...program.opts(), ...options };
|
|
123
|
+
spinner.start({ text: "Fetching exports...", color: "magenta" });
|
|
124
|
+
const res = await apiCallHandler("export", "exports", "get", null, {
|
|
125
|
+
page: opts.page,
|
|
126
|
+
limit: opts.limit,
|
|
127
|
+
responseFormat: opts.responseFormat,
|
|
128
|
+
verbosity: opts.verbosity,
|
|
129
|
+
});
|
|
130
|
+
handleResponseFormatOptions(opts, res);
|
|
131
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
132
|
+
spinner.success({ text: `Found ${total} exports` });
|
|
133
|
+
spinner.stop();
|
|
134
|
+
if (opts.save)
|
|
135
|
+
await saveFile("exports-list", opts, res);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
spinner.error({ text: e.message || "Failed to list exports" });
|
|
139
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// ay export get <id>
|
|
143
|
+
exp
|
|
144
|
+
.command("get <id>")
|
|
145
|
+
.description("Get export details and download URL")
|
|
146
|
+
.action(async (id, options) => {
|
|
147
|
+
try {
|
|
148
|
+
const opts = { ...program.opts(), ...options };
|
|
149
|
+
spinner.start({ text: `Fetching export ${id}...`, color: "magenta" });
|
|
150
|
+
const res = await apiCallHandler("export", `exports/${id}`, "get", null, {
|
|
151
|
+
responseFormat: opts.responseFormat,
|
|
152
|
+
verbosity: opts.verbosity,
|
|
153
|
+
});
|
|
154
|
+
handleResponseFormatOptions(opts, res);
|
|
155
|
+
spinner.success({ text: `Export ${id} loaded` });
|
|
156
|
+
spinner.stop();
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
spinner.error({ text: e.message || "Failed to get export" });
|
|
160
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
// ay export configs
|
|
164
|
+
exp
|
|
165
|
+
.command("configs")
|
|
166
|
+
.description("List export configurations")
|
|
167
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
168
|
+
.action(async (options) => {
|
|
169
|
+
var _a, _b, _c;
|
|
170
|
+
try {
|
|
171
|
+
const opts = { ...program.opts(), ...options };
|
|
172
|
+
spinner.start({ text: "Fetching export configs...", color: "magenta" });
|
|
173
|
+
const res = await apiCallHandler("export", "exportconfigs", "get", null, {
|
|
174
|
+
limit: opts.limit,
|
|
175
|
+
responseFormat: opts.responseFormat,
|
|
176
|
+
verbosity: opts.verbosity,
|
|
177
|
+
});
|
|
178
|
+
handleResponseFormatOptions(opts, res);
|
|
179
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
180
|
+
spinner.success({ text: `Found ${total} export configurations` });
|
|
181
|
+
spinner.stop();
|
|
182
|
+
if (opts.save)
|
|
183
|
+
await saveFile("export-configs", opts, res);
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
spinner.error({ text: e.message || "Failed to list export configs" });
|
|
187
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// ay export logs
|
|
191
|
+
exp
|
|
192
|
+
.command("logs")
|
|
193
|
+
.description("List export logs")
|
|
194
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
195
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
196
|
+
.action(async (options) => {
|
|
197
|
+
var _a, _b, _c;
|
|
198
|
+
try {
|
|
199
|
+
const opts = { ...program.opts(), ...options };
|
|
200
|
+
spinner.start({ text: "Fetching export logs...", color: "magenta" });
|
|
201
|
+
const res = await apiCallHandler("logs", "export", "get", null, {
|
|
202
|
+
page: opts.page,
|
|
203
|
+
limit: opts.limit,
|
|
204
|
+
responseFormat: opts.responseFormat,
|
|
205
|
+
verbosity: opts.verbosity,
|
|
206
|
+
});
|
|
207
|
+
handleResponseFormatOptions(opts, res);
|
|
208
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
209
|
+
spinner.success({ text: `Found ${total} export log entries` });
|
|
210
|
+
spinner.stop();
|
|
211
|
+
if (opts.save)
|
|
212
|
+
await saveFile("export-logs", opts, res);
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
spinner.error({ text: e.message || "Failed to list export logs" });
|
|
216
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
2
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
3
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
4
|
+
import { spinner } from "../../index.js";
|
|
5
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
6
|
+
export function createPermissionsCommand(program) {
|
|
7
|
+
const perms = program
|
|
8
|
+
.command("permissions")
|
|
9
|
+
.alias("perms")
|
|
10
|
+
.description("Manage permissions, access requests, and user rights");
|
|
11
|
+
// ── PERMISSION REQUESTS ────────────────────────────────
|
|
12
|
+
const requests = perms
|
|
13
|
+
.command("requests")
|
|
14
|
+
.alias("req")
|
|
15
|
+
.description("Manage permission requests");
|
|
16
|
+
// ay permissions requests list
|
|
17
|
+
requests
|
|
18
|
+
.command("list")
|
|
19
|
+
.alias("ls")
|
|
20
|
+
.description("List permission requests")
|
|
21
|
+
.option("--status <status>", "Filter by status (pending, approved, rejected)")
|
|
22
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
23
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
var _a, _b, _c;
|
|
26
|
+
try {
|
|
27
|
+
const opts = { ...program.opts(), ...options };
|
|
28
|
+
spinner.start({ text: "Fetching permission requests...", color: "magenta" });
|
|
29
|
+
const params = {
|
|
30
|
+
page: opts.page,
|
|
31
|
+
limit: opts.limit,
|
|
32
|
+
responseFormat: opts.responseFormat,
|
|
33
|
+
verbosity: opts.verbosity,
|
|
34
|
+
};
|
|
35
|
+
if (opts.status)
|
|
36
|
+
params.status = opts.status;
|
|
37
|
+
const res = await apiCallHandler("config", "permissionrequests", "get", null, params);
|
|
38
|
+
handleResponseFormatOptions(opts, res);
|
|
39
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
40
|
+
spinner.success({ text: `Found ${total} permission requests` });
|
|
41
|
+
spinner.stop();
|
|
42
|
+
if (opts.save)
|
|
43
|
+
await saveFile("permission-requests", opts, res);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
spinner.error({ text: e.message || "Failed to list permission requests" });
|
|
47
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// ay permissions requests approve <id>
|
|
51
|
+
requests
|
|
52
|
+
.command("approve <id>")
|
|
53
|
+
.description("Approve a permission request")
|
|
54
|
+
.action(async (id, options) => {
|
|
55
|
+
try {
|
|
56
|
+
const opts = { ...program.opts(), ...options };
|
|
57
|
+
spinner.start({ text: `Approving request ${id}...`, color: "magenta" });
|
|
58
|
+
const res = await apiCallHandler("config", "permissionrequests", "put", { _id: id, status: "approved" }, { responseFormat: opts.responseFormat });
|
|
59
|
+
handleResponseFormatOptions(opts, res);
|
|
60
|
+
spinner.success({ text: `Permission request ${id} approved` });
|
|
61
|
+
spinner.stop();
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
spinner.error({ text: e.message || "Failed to approve request" });
|
|
65
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// ay permissions requests reject <id>
|
|
69
|
+
requests
|
|
70
|
+
.command("reject <id>")
|
|
71
|
+
.description("Reject a permission request")
|
|
72
|
+
.option("--reason <reason>", "Reason for rejection")
|
|
73
|
+
.action(async (id, options) => {
|
|
74
|
+
try {
|
|
75
|
+
const opts = { ...program.opts(), ...options };
|
|
76
|
+
spinner.start({ text: `Rejecting request ${id}...`, color: "magenta" });
|
|
77
|
+
const body = { _id: id, status: "rejected" };
|
|
78
|
+
if (opts.reason)
|
|
79
|
+
body.reason = opts.reason;
|
|
80
|
+
const res = await apiCallHandler("config", "permissionrequests", "put", body, { responseFormat: opts.responseFormat });
|
|
81
|
+
handleResponseFormatOptions(opts, res);
|
|
82
|
+
spinner.success({ text: `Permission request ${id} rejected` });
|
|
83
|
+
spinner.stop();
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
spinner.error({ text: e.message || "Failed to reject request" });
|
|
87
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// ay permissions requests create
|
|
91
|
+
requests
|
|
92
|
+
.command("create")
|
|
93
|
+
.description("Create a new permission request")
|
|
94
|
+
.option("--body <json>", "Request definition as JSON")
|
|
95
|
+
.option("--right <right>", "Right to request (e.g. crm.contacts.edit)")
|
|
96
|
+
.option("--reason <reason>", "Reason for the request")
|
|
97
|
+
.action(async (options) => {
|
|
98
|
+
try {
|
|
99
|
+
const opts = { ...program.opts(), ...options };
|
|
100
|
+
let body = null;
|
|
101
|
+
if (opts.body) {
|
|
102
|
+
try {
|
|
103
|
+
body = JSON.parse(opts.body);
|
|
104
|
+
}
|
|
105
|
+
catch (_a) {
|
|
106
|
+
spinner.error({ text: "Invalid JSON in --body" });
|
|
107
|
+
process.exit(EXIT_MISUSE);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (opts.right) {
|
|
111
|
+
body = { right: opts.right };
|
|
112
|
+
if (opts.reason)
|
|
113
|
+
body.reason = opts.reason;
|
|
114
|
+
}
|
|
115
|
+
if (!body) {
|
|
116
|
+
spinner.error({ text: "Provide request via --body or --right" });
|
|
117
|
+
process.exit(EXIT_MISUSE);
|
|
118
|
+
}
|
|
119
|
+
spinner.start({ text: "Creating permission request...", color: "magenta" });
|
|
120
|
+
const res = await apiCallHandler("config", "permissionrequests", "post", body, {
|
|
121
|
+
responseFormat: opts.responseFormat,
|
|
122
|
+
});
|
|
123
|
+
handleResponseFormatOptions(opts, res);
|
|
124
|
+
spinner.success({ text: "Permission request created" });
|
|
125
|
+
spinner.stop();
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
spinner.error({ text: e.message || "Failed to create permission request" });
|
|
129
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// ── USER RIGHTS ────────────────────────────────────────
|
|
133
|
+
const rights = perms
|
|
134
|
+
.command("rights")
|
|
135
|
+
.description("View and manage user rights");
|
|
136
|
+
// ay permissions rights list
|
|
137
|
+
rights
|
|
138
|
+
.command("list")
|
|
139
|
+
.alias("ls")
|
|
140
|
+
.description("List user rights / permission assignments")
|
|
141
|
+
.option("--userId <id>", "Filter by user ID")
|
|
142
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
143
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
144
|
+
.action(async (options) => {
|
|
145
|
+
var _a, _b, _c;
|
|
146
|
+
try {
|
|
147
|
+
const opts = { ...program.opts(), ...options };
|
|
148
|
+
spinner.start({ text: "Fetching user rights...", color: "magenta" });
|
|
149
|
+
const params = {
|
|
150
|
+
page: opts.page,
|
|
151
|
+
limit: opts.limit,
|
|
152
|
+
responseFormat: opts.responseFormat,
|
|
153
|
+
verbosity: opts.verbosity,
|
|
154
|
+
};
|
|
155
|
+
if (opts.userId)
|
|
156
|
+
params.userId = opts.userId;
|
|
157
|
+
const res = await apiCallHandler("config", "userrights", "get", null, params);
|
|
158
|
+
handleResponseFormatOptions(opts, res);
|
|
159
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
160
|
+
spinner.success({ text: `Found ${total} user right assignments` });
|
|
161
|
+
spinner.stop();
|
|
162
|
+
if (opts.save)
|
|
163
|
+
await saveFile("user-rights", opts, res);
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
spinner.error({ text: e.message || "Failed to list user rights" });
|
|
167
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// ay permissions rights get <id>
|
|
171
|
+
rights
|
|
172
|
+
.command("get <id>")
|
|
173
|
+
.description("Get user right details")
|
|
174
|
+
.action(async (id, options) => {
|
|
175
|
+
try {
|
|
176
|
+
const opts = { ...program.opts(), ...options };
|
|
177
|
+
spinner.start({ text: `Fetching right ${id}...`, color: "magenta" });
|
|
178
|
+
const res = await apiCallHandler("config", `userrights/${id}`, "get", null, {
|
|
179
|
+
responseFormat: opts.responseFormat,
|
|
180
|
+
verbosity: opts.verbosity,
|
|
181
|
+
});
|
|
182
|
+
handleResponseFormatOptions(opts, res);
|
|
183
|
+
spinner.success({ text: `Right ${id} loaded` });
|
|
184
|
+
spinner.stop();
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
spinner.error({ text: e.message || "Failed to get user right" });
|
|
188
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// ── AUDIT (ENHANCED) ──────────────────────────────────
|
|
192
|
+
perms
|
|
193
|
+
.command("audit")
|
|
194
|
+
.description("Audit permission changes and access logs")
|
|
195
|
+
.addHelpText("after", `
|
|
196
|
+
Examples:
|
|
197
|
+
ay permissions audit --action delete --days 30
|
|
198
|
+
ay permissions audit --userId abc123 --collection contacts
|
|
199
|
+
ay permissions audit --action create --days 7 --save`)
|
|
200
|
+
.option("--userId <id>", "Filter by user ID")
|
|
201
|
+
.option("--action <action>", "Filter by action (create, update, delete, login)")
|
|
202
|
+
.option("--collection <name>", "Filter by collection")
|
|
203
|
+
.option("--days <number>", "Show entries from last N days", parseInt, 7)
|
|
204
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
205
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
206
|
+
.action(async (options) => {
|
|
207
|
+
var _a, _b, _c;
|
|
208
|
+
try {
|
|
209
|
+
const opts = { ...program.opts(), ...options };
|
|
210
|
+
spinner.start({ text: "Fetching audit logs...", color: "magenta" });
|
|
211
|
+
const params = {
|
|
212
|
+
page: opts.page,
|
|
213
|
+
limit: opts.limit,
|
|
214
|
+
responseFormat: opts.responseFormat,
|
|
215
|
+
verbosity: opts.verbosity,
|
|
216
|
+
};
|
|
217
|
+
if (opts.userId)
|
|
218
|
+
params.userId = opts.userId;
|
|
219
|
+
if (opts.action)
|
|
220
|
+
params.action = opts.action;
|
|
221
|
+
if (opts.collection)
|
|
222
|
+
params.collection = opts.collection;
|
|
223
|
+
if (opts.days) {
|
|
224
|
+
const fromDate = new Date();
|
|
225
|
+
fromDate.setDate(fromDate.getDate() - opts.days);
|
|
226
|
+
params.from = fromDate.toISOString();
|
|
227
|
+
}
|
|
228
|
+
const res = await apiCallHandler("general", "auditlogs", "get", null, params);
|
|
229
|
+
handleResponseFormatOptions(opts, res);
|
|
230
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
231
|
+
spinner.success({ text: `Found ${total} audit entries (last ${opts.days} days)` });
|
|
232
|
+
spinner.stop();
|
|
233
|
+
if (opts.save)
|
|
234
|
+
await saveFile("permissions-audit", opts, res);
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
spinner.error({ text: e.message || "Failed to fetch audit logs" });
|
|
238
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
@@ -36,6 +36,11 @@ import { createBatchCommand } from "./createBatchCommand.js";
|
|
|
36
36
|
import { createSearchCommand } from "./createSearchCommand.js";
|
|
37
37
|
import { createWebhooksCommand } from "./createWebhooksCommand.js";
|
|
38
38
|
import { createJobsCommand } from "./createJobsCommand.js";
|
|
39
|
+
import { createExportCommand } from "./createExportCommand.js";
|
|
40
|
+
import { createUsersCommand } from "./createUsersCommand.js";
|
|
41
|
+
import { createSyncCommand } from "./createSyncCommand.js";
|
|
42
|
+
import { createPermissionsCommand } from "./createPermissionsCommand.js";
|
|
43
|
+
import { createTemplateCommand } from "./createTemplateCommand.js";
|
|
39
44
|
import { localStorage } from "../helpers/localStorage.js";
|
|
40
45
|
import { login } from "../api/login.js";
|
|
41
46
|
import { loadConfig } from "../helpers/configLoader.js";
|
|
@@ -91,6 +96,11 @@ export function createProgram(program) {
|
|
|
91
96
|
createSearchCommand(program);
|
|
92
97
|
createWebhooksCommand(program);
|
|
93
98
|
createJobsCommand(program);
|
|
99
|
+
createExportCommand(program);
|
|
100
|
+
createUsersCommand(program);
|
|
101
|
+
createSyncCommand(program);
|
|
102
|
+
createPermissionsCommand(program);
|
|
103
|
+
createTemplateCommand(program);
|
|
94
104
|
createCompletionsCommand(program);
|
|
95
105
|
createAliasCommand(program);
|
|
96
106
|
createConfigCommand(program);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
2
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
3
|
+
import { spinner } from "../../index.js";
|
|
4
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
5
|
+
export function createSyncCommand(program) {
|
|
6
|
+
const sync = program
|
|
7
|
+
.command("sync")
|
|
8
|
+
.description("Synchronize platform data across systems");
|
|
9
|
+
// ay sync repos
|
|
10
|
+
sync
|
|
11
|
+
.command("repos")
|
|
12
|
+
.description("Sync repositories from connected providers")
|
|
13
|
+
.addHelpText("after", `
|
|
14
|
+
Examples:
|
|
15
|
+
ay sync repos Sync all repositories
|
|
16
|
+
ay sync repos --provider bitbucket Sync only Bitbucket repos
|
|
17
|
+
ay sync repos --id <repoId> Sync a specific repository`)
|
|
18
|
+
.option("--provider <provider>", "Filter by provider (bitbucket, github)")
|
|
19
|
+
.option("--id <repoId>", "Sync a specific repository by ID")
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
try {
|
|
22
|
+
const opts = { ...program.opts(), ...options };
|
|
23
|
+
if (opts.id) {
|
|
24
|
+
spinner.start({ text: `Syncing repository ${opts.id}...`, color: "magenta" });
|
|
25
|
+
const res = await apiCallHandler("devops", `repositories/${opts.id}/sync`, "post", null, { responseFormat: opts.responseFormat });
|
|
26
|
+
handleResponseFormatOptions(opts, res);
|
|
27
|
+
spinner.success({ text: `Repository ${opts.id} sync initiated` });
|
|
28
|
+
spinner.stop();
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
spinner.start({ text: "Syncing repositories...", color: "magenta" });
|
|
32
|
+
const params = {
|
|
33
|
+
responseFormat: opts.responseFormat,
|
|
34
|
+
verbosity: opts.verbosity,
|
|
35
|
+
};
|
|
36
|
+
if (opts.provider)
|
|
37
|
+
params.provider = opts.provider;
|
|
38
|
+
// List repos first, then trigger sync
|
|
39
|
+
const listRes = await apiCallHandler("devops", "repositories", "get", null, {
|
|
40
|
+
...params,
|
|
41
|
+
limit: 200,
|
|
42
|
+
});
|
|
43
|
+
const repos = Array.isArray(listRes === null || listRes === void 0 ? void 0 : listRes.payload) ? listRes.payload : [];
|
|
44
|
+
let syncCount = 0;
|
|
45
|
+
let errorCount = 0;
|
|
46
|
+
for (const repo of repos) {
|
|
47
|
+
if (opts.provider && repo.provider !== opts.provider)
|
|
48
|
+
continue;
|
|
49
|
+
try {
|
|
50
|
+
await apiCallHandler("devops", `repositories/${repo._id}/sync`, "post", null, { responseFormat: "json" });
|
|
51
|
+
syncCount++;
|
|
52
|
+
spinner.update({ text: `Syncing repositories... ${syncCount}/${repos.length}` });
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
errorCount++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
spinner.success({ text: `Synced ${syncCount}/${repos.length} repositories` });
|
|
59
|
+
spinner.stop();
|
|
60
|
+
if (errorCount > 0) {
|
|
61
|
+
console.error(` ${errorCount} repositories failed to sync`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
spinner.error({ text: e.message || "Repository sync failed" });
|
|
67
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// ay sync clusters
|
|
71
|
+
sync
|
|
72
|
+
.command("clusters")
|
|
73
|
+
.description("Sync Kubernetes cluster state")
|
|
74
|
+
.option("--id <clusterId>", "Sync a specific cluster by ID")
|
|
75
|
+
.action(async (options) => {
|
|
76
|
+
try {
|
|
77
|
+
const opts = { ...program.opts(), ...options };
|
|
78
|
+
if (opts.id) {
|
|
79
|
+
spinner.start({ text: `Syncing cluster ${opts.id}...`, color: "magenta" });
|
|
80
|
+
const res = await apiCallHandler("devops", `clusters/${opts.id}/sync`, "post", null, { responseFormat: opts.responseFormat });
|
|
81
|
+
handleResponseFormatOptions(opts, res);
|
|
82
|
+
spinner.success({ text: `Cluster ${opts.id} sync initiated` });
|
|
83
|
+
spinner.stop();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
spinner.start({ text: "Syncing all clusters...", color: "magenta" });
|
|
87
|
+
const listRes = await apiCallHandler("devops", "clusters", "get", null, {
|
|
88
|
+
limit: 100,
|
|
89
|
+
responseFormat: "json",
|
|
90
|
+
});
|
|
91
|
+
const clusters = Array.isArray(listRes === null || listRes === void 0 ? void 0 : listRes.payload) ? listRes.payload : [];
|
|
92
|
+
let syncCount = 0;
|
|
93
|
+
for (const cluster of clusters) {
|
|
94
|
+
try {
|
|
95
|
+
await apiCallHandler("devops", `clusters/${cluster._id}/sync`, "post", null, { responseFormat: "json" });
|
|
96
|
+
syncCount++;
|
|
97
|
+
}
|
|
98
|
+
catch (_a) {
|
|
99
|
+
// ignore individual failures
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
spinner.success({ text: `Synced ${syncCount}/${clusters.length} clusters` });
|
|
103
|
+
spinner.stop();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
spinner.error({ text: e.message || "Cluster sync failed" });
|
|
108
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// ay sync pipelines
|
|
112
|
+
sync
|
|
113
|
+
.command("pipelines")
|
|
114
|
+
.description("Sync pipeline status from CI/CD providers")
|
|
115
|
+
.option("--provider <provider>", "Filter by provider (bitbucket, github)")
|
|
116
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
117
|
+
.action(async (options) => {
|
|
118
|
+
try {
|
|
119
|
+
const opts = { ...program.opts(), ...options };
|
|
120
|
+
spinner.start({ text: "Syncing pipeline status...", color: "magenta" });
|
|
121
|
+
const params = {
|
|
122
|
+
limit: opts.limit,
|
|
123
|
+
responseFormat: opts.responseFormat,
|
|
124
|
+
verbosity: opts.verbosity,
|
|
125
|
+
};
|
|
126
|
+
if (opts.provider)
|
|
127
|
+
params.provider = opts.provider;
|
|
128
|
+
const res = await apiCallHandler("devops", "pipelines/sync", "post", null, params);
|
|
129
|
+
handleResponseFormatOptions(opts, res);
|
|
130
|
+
spinner.success({ text: "Pipeline sync initiated" });
|
|
131
|
+
spinner.stop();
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
spinner.error({ text: e.message || "Pipeline sync failed" });
|
|
135
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// ay sync status
|
|
139
|
+
sync
|
|
140
|
+
.command("status")
|
|
141
|
+
.description("Show sync status across all systems")
|
|
142
|
+
.action(async (options) => {
|
|
143
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
144
|
+
try {
|
|
145
|
+
const opts = { ...program.opts(), ...options };
|
|
146
|
+
spinner.start({ text: "Fetching sync status...", color: "magenta" });
|
|
147
|
+
// Gather status from multiple endpoints
|
|
148
|
+
const [reposRes, clustersRes, pipelinesRes] = await Promise.allSettled([
|
|
149
|
+
apiCallHandler("devops", "repositories", "get", null, { limit: 1, responseFormat: "json" }),
|
|
150
|
+
apiCallHandler("devops", "clusters", "get", null, { limit: 1, responseFormat: "json" }),
|
|
151
|
+
apiCallHandler("devops", "pipelines", "get", null, { limit: 1, responseFormat: "json" }),
|
|
152
|
+
]);
|
|
153
|
+
const status = {
|
|
154
|
+
repositories: {
|
|
155
|
+
total: reposRes.status === "fulfilled" ? (_d = (_c = (_b = (_a = reposRes.value) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.pageInfo) === null || _c === void 0 ? void 0 : _c.totalEntries) !== null && _d !== void 0 ? _d : 0 : "error",
|
|
156
|
+
},
|
|
157
|
+
clusters: {
|
|
158
|
+
total: clustersRes.status === "fulfilled" ? (_h = (_g = (_f = (_e = clustersRes.value) === null || _e === void 0 ? void 0 : _e.meta) === null || _f === void 0 ? void 0 : _f.pageInfo) === null || _g === void 0 ? void 0 : _g.totalEntries) !== null && _h !== void 0 ? _h : 0 : "error",
|
|
159
|
+
},
|
|
160
|
+
pipelines: {
|
|
161
|
+
total: pipelinesRes.status === "fulfilled" ? (_m = (_l = (_k = (_j = pipelinesRes.value) === null || _j === void 0 ? void 0 : _j.meta) === null || _k === void 0 ? void 0 : _k.pageInfo) === null || _l === void 0 ? void 0 : _l.totalEntries) !== null && _m !== void 0 ? _m : 0 : "error",
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
const res = {
|
|
165
|
+
payload: status,
|
|
166
|
+
meta: { pageInfo: { totalEntries: 1, page: 1, totalPages: 1 } },
|
|
167
|
+
};
|
|
168
|
+
handleResponseFormatOptions(opts, res);
|
|
169
|
+
spinner.success({ text: "Sync status retrieved" });
|
|
170
|
+
spinner.stop();
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
spinner.error({ text: e.message || "Failed to get sync status" });
|
|
174
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
2
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
3
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
4
|
+
import { spinner } from "../../index.js";
|
|
5
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
6
|
+
export function createTemplateCommand(program) {
|
|
7
|
+
const tmpl = program
|
|
8
|
+
.command("templates")
|
|
9
|
+
.alias("tmpl")
|
|
10
|
+
.description("Manage platform templates (email, notification, report, store)");
|
|
11
|
+
// ── EMAIL TEMPLATES ────────────────────────────────────
|
|
12
|
+
const email = tmpl
|
|
13
|
+
.command("email")
|
|
14
|
+
.description("Manage email templates");
|
|
15
|
+
// ay templates email list
|
|
16
|
+
email
|
|
17
|
+
.command("list")
|
|
18
|
+
.alias("ls")
|
|
19
|
+
.description("List email templates")
|
|
20
|
+
.option("--search <query>", "Search by name")
|
|
21
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
22
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
var _a, _b, _c;
|
|
25
|
+
try {
|
|
26
|
+
const opts = { ...program.opts(), ...options };
|
|
27
|
+
spinner.start({ text: "Fetching email templates...", color: "magenta" });
|
|
28
|
+
const params = {
|
|
29
|
+
page: opts.page,
|
|
30
|
+
limit: opts.limit,
|
|
31
|
+
responseFormat: opts.responseFormat,
|
|
32
|
+
verbosity: opts.verbosity,
|
|
33
|
+
};
|
|
34
|
+
if (opts.search)
|
|
35
|
+
params.q = opts.search;
|
|
36
|
+
const res = await apiCallHandler("marketing", "emailtemplates", "get", null, params);
|
|
37
|
+
handleResponseFormatOptions(opts, res);
|
|
38
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
39
|
+
spinner.success({ text: `Found ${total} email templates` });
|
|
40
|
+
spinner.stop();
|
|
41
|
+
if (opts.save)
|
|
42
|
+
await saveFile("email-templates", opts, res);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
spinner.error({ text: e.message || "Failed to list email templates" });
|
|
46
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// ay templates email get <id>
|
|
50
|
+
email
|
|
51
|
+
.command("get <id>")
|
|
52
|
+
.description("Get email template details")
|
|
53
|
+
.action(async (id, options) => {
|
|
54
|
+
try {
|
|
55
|
+
const opts = { ...program.opts(), ...options };
|
|
56
|
+
spinner.start({ text: `Fetching email template ${id}...`, color: "magenta" });
|
|
57
|
+
const res = await apiCallHandler("marketing", `emailtemplates/${id}`, "get", null, {
|
|
58
|
+
responseFormat: opts.responseFormat,
|
|
59
|
+
verbosity: opts.verbosity,
|
|
60
|
+
});
|
|
61
|
+
handleResponseFormatOptions(opts, res);
|
|
62
|
+
spinner.success({ text: `Email template ${id} loaded` });
|
|
63
|
+
spinner.stop();
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
spinner.error({ text: e.message || "Failed to get email template" });
|
|
67
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// ── NOTIFICATION TEMPLATES ─────────────────────────────
|
|
71
|
+
const notify = tmpl
|
|
72
|
+
.command("notification")
|
|
73
|
+
.alias("notify")
|
|
74
|
+
.description("Manage notification templates");
|
|
75
|
+
// ay templates notification list
|
|
76
|
+
notify
|
|
77
|
+
.command("list")
|
|
78
|
+
.alias("ls")
|
|
79
|
+
.description("List notification templates")
|
|
80
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
81
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
82
|
+
.action(async (options) => {
|
|
83
|
+
var _a, _b, _c;
|
|
84
|
+
try {
|
|
85
|
+
const opts = { ...program.opts(), ...options };
|
|
86
|
+
spinner.start({ text: "Fetching notification templates...", color: "magenta" });
|
|
87
|
+
const res = await apiCallHandler("config", "notificationtemplates", "get", null, {
|
|
88
|
+
page: opts.page,
|
|
89
|
+
limit: opts.limit,
|
|
90
|
+
responseFormat: opts.responseFormat,
|
|
91
|
+
verbosity: opts.verbosity,
|
|
92
|
+
});
|
|
93
|
+
handleResponseFormatOptions(opts, res);
|
|
94
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
95
|
+
spinner.success({ text: `Found ${total} notification templates` });
|
|
96
|
+
spinner.stop();
|
|
97
|
+
if (opts.save)
|
|
98
|
+
await saveFile("notification-templates", opts, res);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
spinner.error({ text: e.message || "Failed to list notification templates" });
|
|
102
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// ay templates notification get <id>
|
|
106
|
+
notify
|
|
107
|
+
.command("get <id>")
|
|
108
|
+
.description("Get notification template details")
|
|
109
|
+
.action(async (id, options) => {
|
|
110
|
+
try {
|
|
111
|
+
const opts = { ...program.opts(), ...options };
|
|
112
|
+
spinner.start({ text: `Fetching notification template ${id}...`, color: "magenta" });
|
|
113
|
+
const res = await apiCallHandler("config", `notificationtemplates/${id}`, "get", null, {
|
|
114
|
+
responseFormat: opts.responseFormat,
|
|
115
|
+
verbosity: opts.verbosity,
|
|
116
|
+
});
|
|
117
|
+
handleResponseFormatOptions(opts, res);
|
|
118
|
+
spinner.success({ text: `Notification template ${id} loaded` });
|
|
119
|
+
spinner.stop();
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
spinner.error({ text: e.message || "Failed to get notification template" });
|
|
123
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// ── REPORT TEMPLATES ───────────────────────────────────
|
|
127
|
+
const report = tmpl
|
|
128
|
+
.command("report")
|
|
129
|
+
.description("Manage report templates");
|
|
130
|
+
// ay templates report list
|
|
131
|
+
report
|
|
132
|
+
.command("list")
|
|
133
|
+
.alias("ls")
|
|
134
|
+
.description("List report templates")
|
|
135
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
136
|
+
.action(async (options) => {
|
|
137
|
+
var _a, _b, _c;
|
|
138
|
+
try {
|
|
139
|
+
const opts = { ...program.opts(), ...options };
|
|
140
|
+
spinner.start({ text: "Fetching report templates...", color: "magenta" });
|
|
141
|
+
const res = await apiCallHandler("reporting", "reporttemplates", "get", null, {
|
|
142
|
+
limit: opts.limit,
|
|
143
|
+
responseFormat: opts.responseFormat,
|
|
144
|
+
verbosity: opts.verbosity,
|
|
145
|
+
});
|
|
146
|
+
handleResponseFormatOptions(opts, res);
|
|
147
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
148
|
+
spinner.success({ text: `Found ${total} report templates` });
|
|
149
|
+
spinner.stop();
|
|
150
|
+
if (opts.save)
|
|
151
|
+
await saveFile("report-templates", opts, res);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
spinner.error({ text: e.message || "Failed to list report templates" });
|
|
155
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
// ── STORE TEMPLATES ────────────────────────────────────
|
|
159
|
+
const store = tmpl
|
|
160
|
+
.command("store")
|
|
161
|
+
.description("Browse template store / marketplace");
|
|
162
|
+
// ay templates store list
|
|
163
|
+
store
|
|
164
|
+
.command("list")
|
|
165
|
+
.alias("ls")
|
|
166
|
+
.description("List available store templates")
|
|
167
|
+
.option("--category <category>", "Filter by category")
|
|
168
|
+
.option("--search <query>", "Search templates")
|
|
169
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
170
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
171
|
+
.action(async (options) => {
|
|
172
|
+
var _a, _b, _c;
|
|
173
|
+
try {
|
|
174
|
+
const opts = { ...program.opts(), ...options };
|
|
175
|
+
spinner.start({ text: "Fetching store templates...", color: "magenta" });
|
|
176
|
+
const params = {
|
|
177
|
+
page: opts.page,
|
|
178
|
+
limit: opts.limit,
|
|
179
|
+
responseFormat: opts.responseFormat,
|
|
180
|
+
verbosity: opts.verbosity,
|
|
181
|
+
};
|
|
182
|
+
if (opts.category)
|
|
183
|
+
params.category = opts.category;
|
|
184
|
+
if (opts.search)
|
|
185
|
+
params.q = opts.search;
|
|
186
|
+
const res = await apiCallHandler("config", "templategroups", "get", null, params);
|
|
187
|
+
handleResponseFormatOptions(opts, res);
|
|
188
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
189
|
+
spinner.success({ text: `Found ${total} store templates` });
|
|
190
|
+
spinner.stop();
|
|
191
|
+
if (opts.save)
|
|
192
|
+
await saveFile("store-templates", opts, res);
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
spinner.error({ text: e.message || "Failed to list store templates" });
|
|
196
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// ay templates store get <id>
|
|
200
|
+
store
|
|
201
|
+
.command("get <id>")
|
|
202
|
+
.description("Get store template details")
|
|
203
|
+
.action(async (id, options) => {
|
|
204
|
+
try {
|
|
205
|
+
const opts = { ...program.opts(), ...options };
|
|
206
|
+
spinner.start({ text: `Fetching store template ${id}...`, color: "magenta" });
|
|
207
|
+
const res = await apiCallHandler("config", `templategroups/${id}`, "get", null, {
|
|
208
|
+
responseFormat: opts.responseFormat,
|
|
209
|
+
verbosity: opts.verbosity,
|
|
210
|
+
});
|
|
211
|
+
handleResponseFormatOptions(opts, res);
|
|
212
|
+
spinner.success({ text: `Store template ${id} loaded` });
|
|
213
|
+
spinner.stop();
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
spinner.error({ text: e.message || "Failed to get store template" });
|
|
217
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// ay templates store install <id>
|
|
221
|
+
store
|
|
222
|
+
.command("install <id>")
|
|
223
|
+
.description("Install a template from the store")
|
|
224
|
+
.action(async (id, options) => {
|
|
225
|
+
try {
|
|
226
|
+
const opts = { ...program.opts(), ...options };
|
|
227
|
+
spinner.start({ text: `Installing template ${id}...`, color: "magenta" });
|
|
228
|
+
const res = await apiCallHandler("config", `templategroups/${id}/install`, "post", null, { responseFormat: opts.responseFormat });
|
|
229
|
+
handleResponseFormatOptions(opts, res);
|
|
230
|
+
spinner.success({ text: `Template ${id} installed` });
|
|
231
|
+
spinner.stop();
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
spinner.error({ text: e.message || "Failed to install template" });
|
|
235
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
3
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
4
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
5
|
+
import { spinner } from "../../index.js";
|
|
6
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
7
|
+
export function createUsersCommand(program) {
|
|
8
|
+
const users = program
|
|
9
|
+
.command("users")
|
|
10
|
+
.description("Manage users, teams, and roles");
|
|
11
|
+
// ── USERS ──────────────────────────────────────────────
|
|
12
|
+
// ay users list
|
|
13
|
+
users
|
|
14
|
+
.command("list")
|
|
15
|
+
.alias("ls")
|
|
16
|
+
.description("List users")
|
|
17
|
+
.option("--search <query>", "Search by name or email")
|
|
18
|
+
.option("--role <roleId>", "Filter by role ID")
|
|
19
|
+
.option("--active", "Show only active users")
|
|
20
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
21
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
var _a, _b, _c;
|
|
24
|
+
try {
|
|
25
|
+
const opts = { ...program.opts(), ...options };
|
|
26
|
+
spinner.start({ text: "Fetching users...", color: "magenta" });
|
|
27
|
+
const params = {
|
|
28
|
+
page: opts.page,
|
|
29
|
+
limit: opts.limit,
|
|
30
|
+
responseFormat: opts.responseFormat,
|
|
31
|
+
verbosity: opts.verbosity,
|
|
32
|
+
};
|
|
33
|
+
if (opts.search)
|
|
34
|
+
params.q = opts.search;
|
|
35
|
+
if (opts.role)
|
|
36
|
+
params.role = opts.role;
|
|
37
|
+
if (opts.active)
|
|
38
|
+
params.active = "true";
|
|
39
|
+
const res = await apiCallHandler("config", "users", "get", null, params);
|
|
40
|
+
handleResponseFormatOptions(opts, res);
|
|
41
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
42
|
+
spinner.success({ text: `Found ${total} users` });
|
|
43
|
+
spinner.stop();
|
|
44
|
+
if (opts.save)
|
|
45
|
+
await saveFile("users-list", opts, res);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
spinner.error({ text: e.message || "Failed to list users" });
|
|
49
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// ay users get <id>
|
|
53
|
+
users
|
|
54
|
+
.command("get <id>")
|
|
55
|
+
.description("Get user details")
|
|
56
|
+
.action(async (id, options) => {
|
|
57
|
+
try {
|
|
58
|
+
const opts = { ...program.opts(), ...options };
|
|
59
|
+
spinner.start({ text: `Fetching user ${id}...`, color: "magenta" });
|
|
60
|
+
const res = await apiCallHandler("config", `users/${id}`, "get", null, {
|
|
61
|
+
responseFormat: opts.responseFormat,
|
|
62
|
+
verbosity: opts.verbosity,
|
|
63
|
+
});
|
|
64
|
+
handleResponseFormatOptions(opts, res);
|
|
65
|
+
spinner.success({ text: `User ${id} loaded` });
|
|
66
|
+
spinner.stop();
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
spinner.error({ text: e.message || "Failed to get user" });
|
|
70
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// ay users invite --email <email> --role <roleId>
|
|
74
|
+
users
|
|
75
|
+
.command("invite")
|
|
76
|
+
.description("Invite a new user")
|
|
77
|
+
.requiredOption("--email <email>", "Email address of the user to invite")
|
|
78
|
+
.option("--role <roleId>", "Role ID to assign")
|
|
79
|
+
.option("--firstName <name>", "First name")
|
|
80
|
+
.option("--lastName <name>", "Last name")
|
|
81
|
+
.action(async (options) => {
|
|
82
|
+
try {
|
|
83
|
+
const opts = { ...program.opts(), ...options };
|
|
84
|
+
spinner.start({ text: `Inviting ${opts.email}...`, color: "magenta" });
|
|
85
|
+
const body = { email: opts.email };
|
|
86
|
+
if (opts.role)
|
|
87
|
+
body.role = opts.role;
|
|
88
|
+
if (opts.firstName)
|
|
89
|
+
body.firstName = opts.firstName;
|
|
90
|
+
if (opts.lastName)
|
|
91
|
+
body.lastName = opts.lastName;
|
|
92
|
+
const res = await apiCallHandler("config", "users", "post", body, {
|
|
93
|
+
responseFormat: opts.responseFormat,
|
|
94
|
+
});
|
|
95
|
+
handleResponseFormatOptions(opts, res);
|
|
96
|
+
spinner.success({ text: `Invitation sent to ${opts.email}` });
|
|
97
|
+
spinner.stop();
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
spinner.error({ text: e.message || "Failed to invite user" });
|
|
101
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// ay users deactivate <id>
|
|
105
|
+
users
|
|
106
|
+
.command("deactivate <id>")
|
|
107
|
+
.description("Deactivate a user account")
|
|
108
|
+
.action(async (id, options) => {
|
|
109
|
+
try {
|
|
110
|
+
const opts = { ...program.opts(), ...options };
|
|
111
|
+
if (!opts.force && process.stdin.isTTY) {
|
|
112
|
+
const inquirer = (await import("inquirer")).default;
|
|
113
|
+
const { confirmed } = await inquirer.prompt([
|
|
114
|
+
{ type: "confirm", name: "confirmed", message: `Deactivate user ${id}?`, default: false },
|
|
115
|
+
]);
|
|
116
|
+
if (!confirmed) {
|
|
117
|
+
console.error(chalk.yellow(" Aborted."));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
spinner.start({ text: `Deactivating user ${id}...`, color: "magenta" });
|
|
122
|
+
const res = await apiCallHandler("config", "users", "put", { _id: id, active: false }, {
|
|
123
|
+
responseFormat: opts.responseFormat,
|
|
124
|
+
});
|
|
125
|
+
handleResponseFormatOptions(opts, res);
|
|
126
|
+
spinner.success({ text: `User ${id} deactivated` });
|
|
127
|
+
spinner.stop();
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
spinner.error({ text: e.message || "Failed to deactivate user" });
|
|
131
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// ── TEAMS ──────────────────────────────────────────────
|
|
135
|
+
const teams = users
|
|
136
|
+
.command("teams")
|
|
137
|
+
.description("Manage teams");
|
|
138
|
+
// ay users teams list
|
|
139
|
+
teams
|
|
140
|
+
.command("list")
|
|
141
|
+
.alias("ls")
|
|
142
|
+
.description("List teams")
|
|
143
|
+
.option("--search <query>", "Search teams by name")
|
|
144
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
145
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
146
|
+
.action(async (options) => {
|
|
147
|
+
var _a, _b, _c;
|
|
148
|
+
try {
|
|
149
|
+
const opts = { ...program.opts(), ...options };
|
|
150
|
+
spinner.start({ text: "Fetching teams...", color: "magenta" });
|
|
151
|
+
const params = {
|
|
152
|
+
page: opts.page,
|
|
153
|
+
limit: opts.limit,
|
|
154
|
+
responseFormat: opts.responseFormat,
|
|
155
|
+
verbosity: opts.verbosity,
|
|
156
|
+
};
|
|
157
|
+
if (opts.search)
|
|
158
|
+
params.q = opts.search;
|
|
159
|
+
const res = await apiCallHandler("config", "teams", "get", null, params);
|
|
160
|
+
handleResponseFormatOptions(opts, res);
|
|
161
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
162
|
+
spinner.success({ text: `Found ${total} teams` });
|
|
163
|
+
spinner.stop();
|
|
164
|
+
if (opts.save)
|
|
165
|
+
await saveFile("teams-list", opts, res);
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
spinner.error({ text: e.message || "Failed to list teams" });
|
|
169
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
// ay users teams get <id>
|
|
173
|
+
teams
|
|
174
|
+
.command("get <id>")
|
|
175
|
+
.description("Get team details with members")
|
|
176
|
+
.action(async (id, options) => {
|
|
177
|
+
try {
|
|
178
|
+
const opts = { ...program.opts(), ...options };
|
|
179
|
+
spinner.start({ text: `Fetching team ${id}...`, color: "magenta" });
|
|
180
|
+
const res = await apiCallHandler("config", `teams/${id}`, "get", null, {
|
|
181
|
+
responseFormat: opts.responseFormat,
|
|
182
|
+
verbosity: opts.verbosity,
|
|
183
|
+
});
|
|
184
|
+
handleResponseFormatOptions(opts, res);
|
|
185
|
+
spinner.success({ text: `Team ${id} loaded` });
|
|
186
|
+
spinner.stop();
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
spinner.error({ text: e.message || "Failed to get team" });
|
|
190
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
// ay users teams create --body '{...}'
|
|
194
|
+
teams
|
|
195
|
+
.command("create")
|
|
196
|
+
.description("Create a new team")
|
|
197
|
+
.option("--name <name>", "Team name")
|
|
198
|
+
.option("--body <json>", "Full team definition as JSON")
|
|
199
|
+
.action(async (options) => {
|
|
200
|
+
try {
|
|
201
|
+
const opts = { ...program.opts(), ...options };
|
|
202
|
+
let body = null;
|
|
203
|
+
if (opts.body) {
|
|
204
|
+
try {
|
|
205
|
+
body = JSON.parse(opts.body);
|
|
206
|
+
}
|
|
207
|
+
catch (_a) {
|
|
208
|
+
spinner.error({ text: "Invalid JSON in --body" });
|
|
209
|
+
process.exit(EXIT_MISUSE);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (opts.name) {
|
|
213
|
+
body = { name: opts.name };
|
|
214
|
+
}
|
|
215
|
+
if (!body) {
|
|
216
|
+
spinner.error({ text: "Provide team definition via --name or --body" });
|
|
217
|
+
process.exit(EXIT_MISUSE);
|
|
218
|
+
}
|
|
219
|
+
spinner.start({ text: "Creating team...", color: "magenta" });
|
|
220
|
+
const res = await apiCallHandler("config", "teams", "post", body, {
|
|
221
|
+
responseFormat: opts.responseFormat,
|
|
222
|
+
});
|
|
223
|
+
handleResponseFormatOptions(opts, res);
|
|
224
|
+
spinner.success({ text: "Team created" });
|
|
225
|
+
spinner.stop();
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
spinner.error({ text: e.message || "Failed to create team" });
|
|
229
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
// ── ROLES ──────────────────────────────────────────────
|
|
233
|
+
const roles = users
|
|
234
|
+
.command("roles")
|
|
235
|
+
.description("Manage roles");
|
|
236
|
+
// ay users roles list
|
|
237
|
+
roles
|
|
238
|
+
.command("list")
|
|
239
|
+
.alias("ls")
|
|
240
|
+
.description("List available roles")
|
|
241
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
242
|
+
.action(async (options) => {
|
|
243
|
+
var _a, _b, _c;
|
|
244
|
+
try {
|
|
245
|
+
const opts = { ...program.opts(), ...options };
|
|
246
|
+
spinner.start({ text: "Fetching roles...", color: "magenta" });
|
|
247
|
+
const res = await apiCallHandler("config", "roles", "get", null, {
|
|
248
|
+
limit: opts.limit,
|
|
249
|
+
responseFormat: opts.responseFormat,
|
|
250
|
+
verbosity: opts.verbosity,
|
|
251
|
+
});
|
|
252
|
+
handleResponseFormatOptions(opts, res);
|
|
253
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
254
|
+
spinner.success({ text: `Found ${total} roles` });
|
|
255
|
+
spinner.stop();
|
|
256
|
+
if (opts.save)
|
|
257
|
+
await saveFile("roles-list", opts, res);
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
spinner.error({ text: e.message || "Failed to list roles" });
|
|
261
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
// ay users roles get <id>
|
|
265
|
+
roles
|
|
266
|
+
.command("get <id>")
|
|
267
|
+
.description("Get role details with permissions")
|
|
268
|
+
.action(async (id, options) => {
|
|
269
|
+
try {
|
|
270
|
+
const opts = { ...program.opts(), ...options };
|
|
271
|
+
spinner.start({ text: `Fetching role ${id}...`, color: "magenta" });
|
|
272
|
+
const res = await apiCallHandler("config", `roles/${id}`, "get", null, {
|
|
273
|
+
responseFormat: opts.responseFormat,
|
|
274
|
+
verbosity: opts.verbosity,
|
|
275
|
+
});
|
|
276
|
+
handleResponseFormatOptions(opts, res);
|
|
277
|
+
spinner.success({ text: `Role ${id} loaded` });
|
|
278
|
+
spinner.stop();
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
spinner.error({ text: e.message || "Failed to get role" });
|
|
282
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|