@tolinax/ayoune-cli 2026.2.3 → 2026.3.0

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.
@@ -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
+ }
@@ -7,10 +7,15 @@ import { handleCollectionOperation } from "../operations/handleCollectionOperati
7
7
  import { localStorage } from "../helpers/localStorage.js";
8
8
  import { parseInteger } from "../helpers/parseInt.js";
9
9
  import { handleGetOperation } from "../operations/handleGetOperation.js";
10
+ import { handleCreateSingleOperation } from "../operations/handleCreateSingleOperation.js";
11
+ import { handleDeleteSingleOperation } from "../operations/handleDeleteSingleOperation.js";
10
12
  import { aYOUneModules } from "../../data/modules.js";
11
13
  import { getModelsInModules } from "../models/getModelsInModules.js";
14
+ import { apiCallHandler } from "../api/apiCallHandler.js";
15
+ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
12
16
  import { spinner } from "../../index.js";
13
17
  import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
18
+ const DIRECT_OPERATIONS = ["list", "get", "create", "delete"];
14
19
  function resolveModule(input) {
15
20
  const lower = input.toLowerCase();
16
21
  const match = aYOUneModules.find((m) => m.module === lower || m.label.toLowerCase() === lower);
@@ -24,25 +29,28 @@ function resolveCollection(module, input) {
24
29
  }
25
30
  export function createModulesCommand(program) {
26
31
  program
27
- .command("modules [module] [collection]")
32
+ .command("modules [module] [collection] [operation] [subject]")
28
33
  .alias("m")
29
34
  .description("Browse modules, collections, and entries interactively")
30
35
  .addHelpText("after", `
31
36
  Examples:
32
- ay modules Start interactive module browser
33
- ay m Same using alias
34
- ay m crm Jump straight to the CRM module
35
- ay m crm consumers Jump to CRM consumers collection`)
37
+ ay modules Start interactive module browser
38
+ ay m Same using alias
39
+ ay m crm Jump straight to the CRM module
40
+ ay m crm consumers Jump to CRM consumers collection
41
+ ay m pm projects list List all projects
42
+ ay m pm projects create "Migration" Create a new project
43
+ ay m pm projects get <id> Get a project by ID
44
+ ay m pm projects delete <id> Delete a project by ID`)
36
45
  .option("-p, --page <number>", "Page", parseInteger, 1)
37
46
  .option("-l, --limit <number>", "Limit", parseInteger, 20)
38
47
  .option("-f, --from <date>", "From date")
39
- .action(async (moduleArg, collectionArg, options) => {
48
+ .option("-a, --all", "Fetch all pages (list only)")
49
+ .option("-q, --search <term>", "Search term")
50
+ .action(async (moduleArg, collectionArg, operationArg, subjectArg, options) => {
40
51
  try {
41
52
  const opts = { ...program.opts(), ...options };
42
- if (!process.stdin.isTTY) {
43
- spinner.error({ text: "The modules command requires an interactive terminal" });
44
- process.exit(EXIT_MISUSE);
45
- }
53
+ // ─── Resolve module ──────────────────────────────────
46
54
  let module;
47
55
  if (moduleArg) {
48
56
  const resolved = resolveModule(moduleArg);
@@ -53,8 +61,13 @@ Examples:
53
61
  module = resolved;
54
62
  }
55
63
  else {
64
+ if (!process.stdin.isTTY) {
65
+ spinner.error({ text: "The modules command requires an interactive terminal" });
66
+ process.exit(EXIT_MISUSE);
67
+ }
56
68
  module = await promptModule();
57
69
  }
70
+ // ─── Resolve collection ──────────────────────────────
58
71
  let collection;
59
72
  if (collectionArg) {
60
73
  const resolved = resolveCollection(module, collectionArg);
@@ -65,11 +78,59 @@ Examples:
65
78
  collection = resolved;
66
79
  }
67
80
  else {
81
+ if (!process.stdin.isTTY) {
82
+ spinner.error({ text: "Missing required argument: collection" });
83
+ process.exit(EXIT_MISUSE);
84
+ }
68
85
  collection = await promptCollectionInModule(module);
69
86
  }
70
- const operation = await promptOperation();
71
87
  localStorage.setItem("lastModule", module);
72
88
  localStorage.setItem("lastCollection", collection);
89
+ // ─── Direct operation mode ───────────────────────────
90
+ if (operationArg && DIRECT_OPERATIONS.includes(operationArg.toLowerCase())) {
91
+ const op = operationArg.toLowerCase();
92
+ if (op === "list") {
93
+ const { result } = await handleListOperation(module, collection, opts);
94
+ return;
95
+ }
96
+ if (op === "get") {
97
+ if (!subjectArg) {
98
+ spinner.error({ text: "Missing required argument: id" });
99
+ process.exit(EXIT_MISUSE);
100
+ }
101
+ spinner.start({ text: `Getting ${collection} ${subjectArg}...`, color: "magenta" });
102
+ const res = await apiCallHandler(module, `${collection.toLowerCase()}/${subjectArg}`, "get", null, {
103
+ responseFormat: opts.responseFormat,
104
+ verbosity: opts.verbosity,
105
+ });
106
+ handleResponseFormatOptions(opts, res);
107
+ spinner.success({ text: `Got ${collection} [${subjectArg}]` });
108
+ spinner.stop();
109
+ return;
110
+ }
111
+ if (op === "create") {
112
+ if (!subjectArg) {
113
+ spinner.error({ text: "Missing required argument: name/subject" });
114
+ process.exit(EXIT_MISUSE);
115
+ }
116
+ await handleCreateSingleOperation(module, collection, subjectArg, opts);
117
+ return;
118
+ }
119
+ if (op === "delete") {
120
+ if (!subjectArg) {
121
+ spinner.error({ text: "Missing required argument: id" });
122
+ process.exit(EXIT_MISUSE);
123
+ }
124
+ await handleDeleteSingleOperation(module, collection, subjectArg, opts);
125
+ return;
126
+ }
127
+ }
128
+ // ─── Interactive mode (no operation provided) ────────
129
+ if (!process.stdin.isTTY) {
130
+ spinner.error({ text: "Interactive mode requires a terminal. Use: ay m <module> <collection> <operation> [subject]" });
131
+ process.exit(EXIT_MISUSE);
132
+ }
133
+ const operation = await promptOperation();
73
134
  let entry = "";
74
135
  if (operation === "list") {
75
136
  const { result } = await handleListOperation(module, collection, opts);
@@ -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);