@tolinax/ayoune-cli 2026.2.2 → 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.
@@ -0,0 +1,304 @@
1
+ import chalk from "chalk";
2
+ import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
3
+ import { apiCallHandler } from "../api/apiCallHandler.js";
4
+ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
5
+ import { handleDeleteSingleOperation } from "../operations/handleDeleteSingleOperation.js";
6
+ import { saveFile } from "../helpers/saveFile.js";
7
+ import { spinner } from "../../index.js";
8
+ import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
9
+ async function readStdin() {
10
+ const chunks = [];
11
+ for await (const chunk of process.stdin) {
12
+ chunks.push(chunk);
13
+ }
14
+ return Buffer.concat(chunks).toString("utf-8").trim();
15
+ }
16
+ function parseIds(input) {
17
+ return input
18
+ .split(/[,\n]/)
19
+ .map((id) => id.trim())
20
+ .filter(Boolean);
21
+ }
22
+ export function createBatchCommand(program) {
23
+ const batch = program
24
+ .command("batch")
25
+ .description("Run bulk operations on multiple entries (for scripts and AI agents)");
26
+ // ay batch get <collection> <ids>
27
+ batch
28
+ .command("get <collection> <ids>")
29
+ .description("Get multiple entries by comma-separated IDs")
30
+ .addHelpText("after", `
31
+ Examples:
32
+ ay batch get contacts id1,id2,id3
33
+ echo "id1,id2,id3" | ay batch get contacts --stdin`)
34
+ .option("--stdin", "Read IDs from stdin")
35
+ .action(async (collection, ids, options) => {
36
+ try {
37
+ const opts = { ...program.opts(), ...options };
38
+ const module = getModuleFromCollection(collection);
39
+ let idList = parseIds(ids);
40
+ if (opts.stdin && !process.stdin.isTTY) {
41
+ const stdinIds = parseIds(await readStdin());
42
+ idList.push(...stdinIds);
43
+ }
44
+ if (idList.length === 0) {
45
+ spinner.error({ text: "No IDs provided" });
46
+ process.exit(EXIT_MISUSE);
47
+ }
48
+ spinner.start({ text: `Fetching ${idList.length} entries from ${collection}...`, color: "magenta" });
49
+ const results = [];
50
+ let errorCount = 0;
51
+ for (const id of idList) {
52
+ try {
53
+ const res = await apiCallHandler(module.module, `${collection.toLowerCase()}/${id}`, "get", null, { responseFormat: "json" });
54
+ if (res === null || res === void 0 ? void 0 : res.payload)
55
+ results.push(res.payload);
56
+ }
57
+ catch (_a) {
58
+ errorCount++;
59
+ }
60
+ }
61
+ const res = {
62
+ payload: results,
63
+ meta: { pageInfo: { totalEntries: results.length, page: 1, totalPages: 1 } },
64
+ };
65
+ handleResponseFormatOptions(opts, res);
66
+ spinner.success({ text: `Fetched ${results.length}/${idList.length} entries` });
67
+ spinner.stop();
68
+ if (opts.save)
69
+ await saveFile("batch-get", opts, res);
70
+ }
71
+ catch (e) {
72
+ spinner.error({ text: e.message || "Batch get failed" });
73
+ process.exit(EXIT_GENERAL_ERROR);
74
+ }
75
+ });
76
+ // ay batch delete <collection> <ids>
77
+ batch
78
+ .command("delete <collection> <ids>")
79
+ .alias("rm")
80
+ .description("Delete multiple entries by comma-separated IDs")
81
+ .addHelpText("after", `
82
+ Examples:
83
+ ay batch delete contacts id1,id2,id3 --force
84
+ echo "id1,id2" | ay batch delete contacts --stdin --force`)
85
+ .option("--stdin", "Read IDs from stdin")
86
+ .action(async (collection, ids, options) => {
87
+ try {
88
+ const opts = { ...program.opts(), ...options };
89
+ const module = getModuleFromCollection(collection);
90
+ let idList = parseIds(ids);
91
+ if (opts.stdin && !process.stdin.isTTY) {
92
+ const stdinIds = parseIds(await readStdin());
93
+ idList.push(...stdinIds);
94
+ }
95
+ if (idList.length === 0) {
96
+ spinner.error({ text: "No IDs provided" });
97
+ process.exit(EXIT_MISUSE);
98
+ }
99
+ // Confirmation
100
+ if (!opts.force && process.stdin.isTTY) {
101
+ const inquirer = (await import("inquirer")).default;
102
+ const { confirmed } = await inquirer.prompt([
103
+ {
104
+ type: "confirm",
105
+ name: "confirmed",
106
+ message: `Delete ${idList.length} entries from ${collection}?`,
107
+ default: false,
108
+ },
109
+ ]);
110
+ if (!confirmed) {
111
+ console.error(chalk.yellow(" Aborted."));
112
+ return;
113
+ }
114
+ }
115
+ spinner.start({ text: `Deleting ${idList.length} entries from ${collection}...`, color: "magenta" });
116
+ let successCount = 0;
117
+ let errorCount = 0;
118
+ for (const id of idList) {
119
+ try {
120
+ await handleDeleteSingleOperation(module.module, collection, id, opts);
121
+ successCount++;
122
+ }
123
+ catch (_a) {
124
+ errorCount++;
125
+ }
126
+ }
127
+ spinner.success({ text: `Deleted ${successCount}/${idList.length} entries from ${collection}` });
128
+ spinner.stop();
129
+ if (errorCount > 0)
130
+ process.exit(EXIT_GENERAL_ERROR);
131
+ }
132
+ catch (e) {
133
+ spinner.error({ text: e.message || "Batch delete failed" });
134
+ process.exit(EXIT_GENERAL_ERROR);
135
+ }
136
+ });
137
+ // ay batch update <collection> --body-file <file>
138
+ batch
139
+ .command("update <collection>")
140
+ .description("Update multiple entries from a JSON array")
141
+ .addHelpText("after", `
142
+ Expects a JSON array where each object has an _id field.
143
+
144
+ Examples:
145
+ ay batch update contacts --body '[{"_id":"id1","status":"active"},{"_id":"id2","status":"inactive"}]'
146
+ ay batch update contacts --body-file updates.json
147
+ cat updates.json | ay batch update contacts --body-stdin`)
148
+ .option("--body <json>", "JSON array of entries to update")
149
+ .option("--body-file <path>", "Read entries from file")
150
+ .option("--body-stdin", "Read entries from stdin")
151
+ .action(async (collection, options) => {
152
+ try {
153
+ const opts = { ...program.opts(), ...options };
154
+ const module = getModuleFromCollection(collection);
155
+ let entries = [];
156
+ if (opts.body) {
157
+ try {
158
+ entries = JSON.parse(opts.body);
159
+ }
160
+ catch (_a) {
161
+ spinner.error({ text: "Invalid JSON in --body" });
162
+ process.exit(EXIT_MISUSE);
163
+ }
164
+ }
165
+ if (opts.bodyFile) {
166
+ const fs = await import("fs");
167
+ const content = fs.readFileSync(opts.bodyFile, "utf-8");
168
+ try {
169
+ entries = JSON.parse(content);
170
+ }
171
+ catch (_b) {
172
+ spinner.error({ text: `Invalid JSON in file: ${opts.bodyFile}` });
173
+ process.exit(EXIT_MISUSE);
174
+ }
175
+ }
176
+ if (opts.bodyStdin && !process.stdin.isTTY) {
177
+ const stdinContent = await readStdin();
178
+ if (stdinContent) {
179
+ try {
180
+ entries = JSON.parse(stdinContent);
181
+ }
182
+ catch (_c) {
183
+ spinner.error({ text: "Invalid JSON from stdin" });
184
+ process.exit(EXIT_MISUSE);
185
+ }
186
+ }
187
+ }
188
+ if (!Array.isArray(entries) || entries.length === 0) {
189
+ spinner.error({ text: "Provide a JSON array of entries with _id fields" });
190
+ process.exit(EXIT_MISUSE);
191
+ }
192
+ spinner.start({ text: `Updating ${entries.length} entries in ${collection}...`, color: "magenta" });
193
+ let successCount = 0;
194
+ let errorCount = 0;
195
+ for (const entry of entries) {
196
+ if (!entry._id) {
197
+ errorCount++;
198
+ continue;
199
+ }
200
+ try {
201
+ await apiCallHandler(module.module, collection.toLowerCase(), "put", entry, { responseFormat: "json" });
202
+ successCount++;
203
+ }
204
+ catch (_d) {
205
+ errorCount++;
206
+ }
207
+ }
208
+ spinner.success({ text: `Updated ${successCount}/${entries.length} entries in ${collection}` });
209
+ spinner.stop();
210
+ if (errorCount > 0)
211
+ process.exit(EXIT_GENERAL_ERROR);
212
+ }
213
+ catch (e) {
214
+ spinner.error({ text: e.message || "Batch update failed" });
215
+ process.exit(EXIT_GENERAL_ERROR);
216
+ }
217
+ });
218
+ // ay batch create <collection> --body-file <file>
219
+ batch
220
+ .command("create <collection>")
221
+ .description("Create multiple entries from a JSON array")
222
+ .addHelpText("after", `
223
+ Expects a JSON array of objects to create.
224
+
225
+ Examples:
226
+ ay batch create contacts --body '[{"firstName":"A"},{"firstName":"B"}]'
227
+ ay batch create contacts --body-file entries.json`)
228
+ .option("--body <json>", "JSON array of entries to create")
229
+ .option("--body-file <path>", "Read entries from file")
230
+ .option("--body-stdin", "Read entries from stdin")
231
+ .action(async (collection, options) => {
232
+ try {
233
+ const opts = { ...program.opts(), ...options };
234
+ const module = getModuleFromCollection(collection);
235
+ let entries = [];
236
+ if (opts.body) {
237
+ try {
238
+ entries = JSON.parse(opts.body);
239
+ }
240
+ catch (_a) {
241
+ spinner.error({ text: "Invalid JSON in --body" });
242
+ process.exit(EXIT_MISUSE);
243
+ }
244
+ }
245
+ if (opts.bodyFile) {
246
+ const fs = await import("fs");
247
+ const content = fs.readFileSync(opts.bodyFile, "utf-8");
248
+ try {
249
+ entries = JSON.parse(content);
250
+ }
251
+ catch (_b) {
252
+ spinner.error({ text: `Invalid JSON in file: ${opts.bodyFile}` });
253
+ process.exit(EXIT_MISUSE);
254
+ }
255
+ }
256
+ if (opts.bodyStdin && !process.stdin.isTTY) {
257
+ const stdinContent = await readStdin();
258
+ if (stdinContent) {
259
+ try {
260
+ entries = JSON.parse(stdinContent);
261
+ }
262
+ catch (_c) {
263
+ spinner.error({ text: "Invalid JSON from stdin" });
264
+ process.exit(EXIT_MISUSE);
265
+ }
266
+ }
267
+ }
268
+ if (!Array.isArray(entries) || entries.length === 0) {
269
+ spinner.error({ text: "Provide a JSON array of entries to create" });
270
+ process.exit(EXIT_MISUSE);
271
+ }
272
+ spinner.start({ text: `Creating ${entries.length} entries in ${collection}...`, color: "magenta" });
273
+ let successCount = 0;
274
+ let errorCount = 0;
275
+ const created = [];
276
+ for (const entry of entries) {
277
+ try {
278
+ const res = await apiCallHandler(module.module, collection.toLowerCase(), "post", entry, { responseFormat: "json" });
279
+ if (res === null || res === void 0 ? void 0 : res.payload)
280
+ created.push(res.payload);
281
+ successCount++;
282
+ }
283
+ catch (_d) {
284
+ errorCount++;
285
+ }
286
+ }
287
+ const res = {
288
+ payload: created,
289
+ meta: { pageInfo: { totalEntries: created.length, page: 1, totalPages: 1 } },
290
+ };
291
+ handleResponseFormatOptions(opts, res);
292
+ spinner.success({ text: `Created ${successCount}/${entries.length} entries in ${collection}` });
293
+ spinner.stop();
294
+ if (opts.save)
295
+ await saveFile("batch-create", opts, res);
296
+ if (errorCount > 0)
297
+ process.exit(EXIT_GENERAL_ERROR);
298
+ }
299
+ catch (e) {
300
+ spinner.error({ text: e.message || "Batch create failed" });
301
+ process.exit(EXIT_GENERAL_ERROR);
302
+ }
303
+ });
304
+ }
@@ -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"
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}") )
@@ -38,6 +38,17 @@ _ay() {
38
38
  'services:Discover and manage platform services'
39
39
  'deploy:Manage deployments and pipelines'
40
40
  'monitor:Monitor platform activity, logs, and alerts'
41
+ 'delete:Delete entries from a collection'
42
+ 'update:Update an entry non-interactively'
43
+ 'batch:Run bulk operations on multiple entries'
44
+ 'search:Search entries with advanced filtering'
45
+ 'webhooks:Manage webhooks and event subscriptions'
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'
41
52
  )
42
53
  _describe 'command' commands
43
54
  }
@@ -67,6 +78,17 @@ complete -c ay -n '__fish_use_subcommand' -a ai -d 'AI assistant commands'
67
78
  complete -c ay -n '__fish_use_subcommand' -a services -d 'Discover and manage platform services'
68
79
  complete -c ay -n '__fish_use_subcommand' -a deploy -d 'Manage deployments and pipelines'
69
80
  complete -c ay -n '__fish_use_subcommand' -a monitor -d 'Monitor platform activity, logs, and alerts'
81
+ complete -c ay -n '__fish_use_subcommand' -a delete -d 'Delete entries from a collection'
82
+ complete -c ay -n '__fish_use_subcommand' -a update -d 'Update an entry non-interactively'
83
+ complete -c ay -n '__fish_use_subcommand' -a batch -d 'Run bulk operations on multiple entries'
84
+ complete -c ay -n '__fish_use_subcommand' -a search -d 'Search entries with advanced filtering'
85
+ complete -c ay -n '__fish_use_subcommand' -a webhooks -d 'Manage webhooks and event subscriptions'
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'
70
92
  ###-end-ay-completions-###`;
71
93
  const POWERSHELL_COMPLETION = `###-begin-ay-completions-###
72
94
  Register-ArgumentCompleter -CommandName ay -ScriptBlock {
@@ -95,6 +117,17 @@ Register-ArgumentCompleter -CommandName ay -ScriptBlock {
95
117
  @{ Name = 'services'; Description = 'Discover and manage platform services' }
96
118
  @{ Name = 'deploy'; Description = 'Manage deployments and pipelines' }
97
119
  @{ Name = 'monitor'; Description = 'Monitor platform activity, logs, and alerts' }
120
+ @{ Name = 'delete'; Description = 'Delete entries from a collection' }
121
+ @{ Name = 'update'; Description = 'Update an entry non-interactively' }
122
+ @{ Name = 'batch'; Description = 'Run bulk operations on multiple entries' }
123
+ @{ Name = 'search'; Description = 'Search entries with advanced filtering' }
124
+ @{ Name = 'webhooks'; Description = 'Manage webhooks and event subscriptions' }
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' }
98
131
  )
99
132
  $commands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {
100
133
  [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Description)
@@ -0,0 +1,98 @@
1
+ import { Argument } from "commander";
2
+ import chalk from "chalk";
3
+ import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
4
+ import { handleDeleteSingleOperation } from "../operations/handleDeleteSingleOperation.js";
5
+ import { localStorage } from "../helpers/localStorage.js";
6
+ import { spinner } from "../../index.js";
7
+ import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
8
+ export function createDeleteCommand(program) {
9
+ program
10
+ .command("delete")
11
+ .alias("rm")
12
+ .description("Delete one or more entries from a collection")
13
+ .addHelpText("after", `
14
+ Examples:
15
+ ay delete contacts 64a1b2c3d4e5 Delete a single contact
16
+ ay rm contacts 64a1b2c3d4e5 --force Skip confirmation
17
+ ay delete contacts id1,id2,id3 Delete multiple entries
18
+ echo "id1,id2" | ay delete contacts --ids-stdin Read IDs from stdin`)
19
+ .addArgument(new Argument("[collection]", "The collection to use").default(localStorage.getItem("lastCollection"), `The last used collection (${localStorage.getItem("lastCollection")})`))
20
+ .addArgument(new Argument("[ids]", "Comma-separated ID(s) to delete").default(localStorage.getItem("lastId"), `The last used id (${localStorage.getItem("lastId")})`))
21
+ .option("--ids-stdin", "Read IDs from stdin (one per line or comma-separated)")
22
+ .action(async (collection, ids, options) => {
23
+ try {
24
+ if (!collection) {
25
+ spinner.error({ text: "Missing required argument: collection. Run a list or get command first, or provide it explicitly." });
26
+ process.exit(EXIT_MISUSE);
27
+ }
28
+ const opts = { ...program.opts(), ...options };
29
+ const module = getModuleFromCollection(collection);
30
+ // Collect IDs from argument and/or stdin
31
+ let idList = [];
32
+ if (ids) {
33
+ idList = ids.split(",").map((id) => id.trim()).filter(Boolean);
34
+ }
35
+ if (opts.idsStdin && !process.stdin.isTTY) {
36
+ const chunks = [];
37
+ for await (const chunk of process.stdin) {
38
+ chunks.push(chunk);
39
+ }
40
+ const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
41
+ if (stdinContent) {
42
+ const stdinIds = stdinContent
43
+ .split(/[,\n]/)
44
+ .map((id) => id.trim())
45
+ .filter(Boolean);
46
+ idList.push(...stdinIds);
47
+ }
48
+ }
49
+ if (idList.length === 0) {
50
+ spinner.error({ text: "No IDs provided. Pass IDs as argument or via --ids-stdin." });
51
+ process.exit(EXIT_MISUSE);
52
+ }
53
+ // Confirmation prompt (unless --force)
54
+ if (!opts.force && process.stdin.isTTY) {
55
+ const inquirer = (await import("inquirer")).default;
56
+ const { confirmed } = await inquirer.prompt([
57
+ {
58
+ type: "confirm",
59
+ name: "confirmed",
60
+ message: `Delete ${idList.length} entry/entries from ${collection}?`,
61
+ default: false,
62
+ },
63
+ ]);
64
+ if (!confirmed) {
65
+ spinner.stop();
66
+ console.error(chalk.yellow(" Aborted."));
67
+ return;
68
+ }
69
+ }
70
+ // Execute deletions
71
+ let successCount = 0;
72
+ let errorCount = 0;
73
+ for (const id of idList) {
74
+ try {
75
+ await handleDeleteSingleOperation(module.module, collection, id, opts);
76
+ successCount++;
77
+ }
78
+ catch (e) {
79
+ errorCount++;
80
+ spinner.error({ text: `Failed to delete ${id}: ${e.message}` });
81
+ }
82
+ }
83
+ if (idList.length > 1) {
84
+ spinner.success({ text: `Deleted ${successCount}/${idList.length} entries from ${collection}` });
85
+ spinner.stop();
86
+ }
87
+ localStorage.setItem("lastModule", module.module);
88
+ localStorage.setItem("lastCollection", collection);
89
+ if (errorCount > 0) {
90
+ process.exit(EXIT_GENERAL_ERROR);
91
+ }
92
+ }
93
+ catch (e) {
94
+ spinner.error({ text: e.message || "An unexpected error occurred" });
95
+ process.exit(EXIT_GENERAL_ERROR);
96
+ }
97
+ });
98
+ }