@tolinax/ayoune-cli 2026.2.2 → 2026.2.3
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/createBatchCommand.js +304 -0
- package/lib/commands/createCompletionsCommand.js +19 -1
- package/lib/commands/createDeleteCommand.js +98 -0
- package/lib/commands/createJobsCommand.js +168 -0
- package/lib/commands/createProgram.js +12 -0
- package/lib/commands/createSearchCommand.js +101 -0
- package/lib/commands/createUpdateCommand.js +115 -0
- package/lib/commands/createWebhooksCommand.js +156 -0
- package/package.json +1 -1
|
@@ -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"
|
|
7
7
|
|
|
8
8
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
9
9
|
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
@@ -38,6 +38,12 @@ _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'
|
|
41
47
|
)
|
|
42
48
|
_describe 'command' commands
|
|
43
49
|
}
|
|
@@ -67,6 +73,12 @@ complete -c ay -n '__fish_use_subcommand' -a ai -d 'AI assistant commands'
|
|
|
67
73
|
complete -c ay -n '__fish_use_subcommand' -a services -d 'Discover and manage platform services'
|
|
68
74
|
complete -c ay -n '__fish_use_subcommand' -a deploy -d 'Manage deployments and pipelines'
|
|
69
75
|
complete -c ay -n '__fish_use_subcommand' -a monitor -d 'Monitor platform activity, logs, and alerts'
|
|
76
|
+
complete -c ay -n '__fish_use_subcommand' -a delete -d 'Delete entries from a collection'
|
|
77
|
+
complete -c ay -n '__fish_use_subcommand' -a update -d 'Update an entry non-interactively'
|
|
78
|
+
complete -c ay -n '__fish_use_subcommand' -a batch -d 'Run bulk operations on multiple entries'
|
|
79
|
+
complete -c ay -n '__fish_use_subcommand' -a search -d 'Search entries with advanced filtering'
|
|
80
|
+
complete -c ay -n '__fish_use_subcommand' -a webhooks -d 'Manage webhooks and event subscriptions'
|
|
81
|
+
complete -c ay -n '__fish_use_subcommand' -a jobs -d 'Manage background jobs and automations'
|
|
70
82
|
###-end-ay-completions-###`;
|
|
71
83
|
const POWERSHELL_COMPLETION = `###-begin-ay-completions-###
|
|
72
84
|
Register-ArgumentCompleter -CommandName ay -ScriptBlock {
|
|
@@ -95,6 +107,12 @@ Register-ArgumentCompleter -CommandName ay -ScriptBlock {
|
|
|
95
107
|
@{ Name = 'services'; Description = 'Discover and manage platform services' }
|
|
96
108
|
@{ Name = 'deploy'; Description = 'Manage deployments and pipelines' }
|
|
97
109
|
@{ Name = 'monitor'; Description = 'Monitor platform activity, logs, and alerts' }
|
|
110
|
+
@{ Name = 'delete'; Description = 'Delete entries from a collection' }
|
|
111
|
+
@{ Name = 'update'; Description = 'Update an entry non-interactively' }
|
|
112
|
+
@{ Name = 'batch'; Description = 'Run bulk operations on multiple entries' }
|
|
113
|
+
@{ Name = 'search'; Description = 'Search entries with advanced filtering' }
|
|
114
|
+
@{ Name = 'webhooks'; Description = 'Manage webhooks and event subscriptions' }
|
|
115
|
+
@{ Name = 'jobs'; Description = 'Manage background jobs and automations' }
|
|
98
116
|
)
|
|
99
117
|
$commands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {
|
|
100
118
|
[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
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
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 createJobsCommand(program) {
|
|
7
|
+
const jobs = program
|
|
8
|
+
.command("jobs")
|
|
9
|
+
.alias("j")
|
|
10
|
+
.description("Manage background jobs, automations, and triggers");
|
|
11
|
+
// ay jobs list
|
|
12
|
+
jobs
|
|
13
|
+
.command("list")
|
|
14
|
+
.alias("ls")
|
|
15
|
+
.description("List scheduled/queued jobs")
|
|
16
|
+
.option("--status <status>", "Filter: active, waiting, completed, failed, delayed")
|
|
17
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
18
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
var _a, _b, _c;
|
|
21
|
+
try {
|
|
22
|
+
const opts = { ...program.opts(), ...options };
|
|
23
|
+
spinner.start({ text: "Fetching jobs...", color: "magenta" });
|
|
24
|
+
const params = {
|
|
25
|
+
page: opts.page,
|
|
26
|
+
limit: opts.limit,
|
|
27
|
+
responseFormat: opts.responseFormat,
|
|
28
|
+
verbosity: opts.verbosity,
|
|
29
|
+
};
|
|
30
|
+
if (opts.status)
|
|
31
|
+
params.status = opts.status;
|
|
32
|
+
const res = await apiCallHandler("general", "agendajobs", "get", null, params);
|
|
33
|
+
handleResponseFormatOptions(opts, res);
|
|
34
|
+
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;
|
|
35
|
+
spinner.success({ text: `Found ${total} jobs` });
|
|
36
|
+
spinner.stop();
|
|
37
|
+
if (opts.save)
|
|
38
|
+
await saveFile("jobs-list", opts, res);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
spinner.error({ text: e.message || "Failed to list jobs" });
|
|
42
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// ay jobs triggers
|
|
46
|
+
jobs
|
|
47
|
+
.command("triggers")
|
|
48
|
+
.description("List automation triggers")
|
|
49
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
50
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
51
|
+
.action(async (options) => {
|
|
52
|
+
var _a, _b, _c;
|
|
53
|
+
try {
|
|
54
|
+
const opts = { ...program.opts(), ...options };
|
|
55
|
+
spinner.start({ text: "Fetching triggers...", color: "magenta" });
|
|
56
|
+
const res = await apiCallHandler("automation", "triggers", "get", null, {
|
|
57
|
+
page: opts.page,
|
|
58
|
+
limit: opts.limit,
|
|
59
|
+
responseFormat: opts.responseFormat,
|
|
60
|
+
verbosity: opts.verbosity,
|
|
61
|
+
});
|
|
62
|
+
handleResponseFormatOptions(opts, res);
|
|
63
|
+
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;
|
|
64
|
+
spinner.success({ text: `Found ${total} triggers` });
|
|
65
|
+
spinner.stop();
|
|
66
|
+
if (opts.save)
|
|
67
|
+
await saveFile("jobs-triggers", opts, res);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
spinner.error({ text: e.message || "Failed to list triggers" });
|
|
71
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// ay jobs automations
|
|
75
|
+
jobs
|
|
76
|
+
.command("automations")
|
|
77
|
+
.alias("auto")
|
|
78
|
+
.description("List automations")
|
|
79
|
+
.option("--active", "Show only active automations")
|
|
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 automations...", color: "magenta" });
|
|
87
|
+
const params = {
|
|
88
|
+
page: opts.page,
|
|
89
|
+
limit: opts.limit,
|
|
90
|
+
responseFormat: opts.responseFormat,
|
|
91
|
+
verbosity: opts.verbosity,
|
|
92
|
+
};
|
|
93
|
+
if (opts.active)
|
|
94
|
+
params.active = "true";
|
|
95
|
+
const res = await apiCallHandler("automation", "automations", "get", null, params);
|
|
96
|
+
handleResponseFormatOptions(opts, res);
|
|
97
|
+
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;
|
|
98
|
+
spinner.success({ text: `Found ${total} automations` });
|
|
99
|
+
spinner.stop();
|
|
100
|
+
if (opts.save)
|
|
101
|
+
await saveFile("jobs-automations", opts, res);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
spinner.error({ text: e.message || "Failed to list automations" });
|
|
105
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// ay jobs execute <automationId>
|
|
109
|
+
jobs
|
|
110
|
+
.command("execute <automationId>")
|
|
111
|
+
.alias("run")
|
|
112
|
+
.description("Execute an automation immediately")
|
|
113
|
+
.option("--body <json>", "Optional payload as JSON")
|
|
114
|
+
.action(async (automationId, options) => {
|
|
115
|
+
try {
|
|
116
|
+
const opts = { ...program.opts(), ...options };
|
|
117
|
+
spinner.start({ text: `Executing automation ${automationId}...`, color: "magenta" });
|
|
118
|
+
let body = null;
|
|
119
|
+
if (opts.body) {
|
|
120
|
+
try {
|
|
121
|
+
body = JSON.parse(opts.body);
|
|
122
|
+
}
|
|
123
|
+
catch (_a) {
|
|
124
|
+
spinner.error({ text: "Invalid JSON in --body" });
|
|
125
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const res = await apiCallHandler("automation", `automations/${automationId}/execute`, "post", body, { responseFormat: opts.responseFormat });
|
|
129
|
+
handleResponseFormatOptions(opts, res);
|
|
130
|
+
spinner.success({ text: `Automation ${automationId} executed` });
|
|
131
|
+
spinner.stop();
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
spinner.error({ text: e.message || "Failed to execute automation" });
|
|
135
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// ay jobs notifications
|
|
139
|
+
jobs
|
|
140
|
+
.command("notifications")
|
|
141
|
+
.alias("notify")
|
|
142
|
+
.description("List recent notifications")
|
|
143
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
144
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
145
|
+
.action(async (options) => {
|
|
146
|
+
var _a, _b, _c;
|
|
147
|
+
try {
|
|
148
|
+
const opts = { ...program.opts(), ...options };
|
|
149
|
+
spinner.start({ text: "Fetching notifications...", color: "magenta" });
|
|
150
|
+
const res = await apiCallHandler("general", "notifications", "get", null, {
|
|
151
|
+
page: opts.page,
|
|
152
|
+
limit: opts.limit,
|
|
153
|
+
responseFormat: opts.responseFormat,
|
|
154
|
+
verbosity: opts.verbosity,
|
|
155
|
+
});
|
|
156
|
+
handleResponseFormatOptions(opts, res);
|
|
157
|
+
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;
|
|
158
|
+
spinner.success({ text: `Found ${total} notifications` });
|
|
159
|
+
spinner.stop();
|
|
160
|
+
if (opts.save)
|
|
161
|
+
await saveFile("jobs-notifications", opts, res);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
spinner.error({ text: e.message || "Failed to list notifications" });
|
|
165
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -30,6 +30,12 @@ import { enableJsonErrors } from "../api/handleAPIError.js";
|
|
|
30
30
|
import { createServicesCommand } from "./createServicesCommand.js";
|
|
31
31
|
import { createDeployCommand } from "./createDeployCommand.js";
|
|
32
32
|
import { createMonitorCommand } from "./createMonitorCommand.js";
|
|
33
|
+
import { createDeleteCommand } from "./createDeleteCommand.js";
|
|
34
|
+
import { createUpdateCommand } from "./createUpdateCommand.js";
|
|
35
|
+
import { createBatchCommand } from "./createBatchCommand.js";
|
|
36
|
+
import { createSearchCommand } from "./createSearchCommand.js";
|
|
37
|
+
import { createWebhooksCommand } from "./createWebhooksCommand.js";
|
|
38
|
+
import { createJobsCommand } from "./createJobsCommand.js";
|
|
33
39
|
import { localStorage } from "../helpers/localStorage.js";
|
|
34
40
|
import { login } from "../api/login.js";
|
|
35
41
|
import { loadConfig } from "../helpers/configLoader.js";
|
|
@@ -79,6 +85,12 @@ export function createProgram(program) {
|
|
|
79
85
|
createServicesCommand(program);
|
|
80
86
|
createDeployCommand(program);
|
|
81
87
|
createMonitorCommand(program);
|
|
88
|
+
createDeleteCommand(program);
|
|
89
|
+
createUpdateCommand(program);
|
|
90
|
+
createBatchCommand(program);
|
|
91
|
+
createSearchCommand(program);
|
|
92
|
+
createWebhooksCommand(program);
|
|
93
|
+
createJobsCommand(program);
|
|
82
94
|
createCompletionsCommand(program);
|
|
83
95
|
createAliasCommand(program);
|
|
84
96
|
createConfigCommand(program);
|
|
@@ -0,0 +1,101 @@
|
|
|
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 { localStorage } from "../helpers/localStorage.js";
|
|
6
|
+
import { spinner } from "../../index.js";
|
|
7
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
8
|
+
export function createSearchCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command("search <collection> [query]")
|
|
11
|
+
.alias("find")
|
|
12
|
+
.description("Search entries in a collection with advanced filtering")
|
|
13
|
+
.addHelpText("after", `
|
|
14
|
+
Examples:
|
|
15
|
+
ay search contacts "John" Full-text search
|
|
16
|
+
ay search contacts --filter "status=active" Filter by field
|
|
17
|
+
ay search products --filter "price>100" Comparison filter
|
|
18
|
+
ay search orders --filter "status=paid" --sort "-createdAt"
|
|
19
|
+
ay search leads --filter "source=website,stage=qualified" --fields "name,email,stage"
|
|
20
|
+
ay find invoices --filter "total>500" --count Just count matches`)
|
|
21
|
+
.option("--filter <filters>", "Comma-separated key=value filters (supports =, !=, >, <, >=, <=)")
|
|
22
|
+
.option("--fields <fields>", "Comma-separated fields to return (projection)")
|
|
23
|
+
.option("--sort <field>", "Sort by field (prefix with - for descending)", "-createdAt")
|
|
24
|
+
.option("--count", "Only return count of matching entries", false)
|
|
25
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
26
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
27
|
+
.action(async (collection, query, options) => {
|
|
28
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
29
|
+
try {
|
|
30
|
+
const opts = { ...program.opts(), ...options };
|
|
31
|
+
const module = getModuleFromCollection(collection);
|
|
32
|
+
spinner.start({ text: `Searching ${collection}...`, color: "magenta" });
|
|
33
|
+
const params = {
|
|
34
|
+
page: opts.page,
|
|
35
|
+
limit: opts.limit,
|
|
36
|
+
sort: opts.sort,
|
|
37
|
+
responseFormat: opts.responseFormat,
|
|
38
|
+
verbosity: opts.verbosity,
|
|
39
|
+
hideMeta: opts.hideMeta,
|
|
40
|
+
};
|
|
41
|
+
if (query) {
|
|
42
|
+
params.q = query;
|
|
43
|
+
}
|
|
44
|
+
if (opts.fields) {
|
|
45
|
+
params.fields = opts.fields;
|
|
46
|
+
}
|
|
47
|
+
// Parse filters into query params
|
|
48
|
+
if (opts.filter) {
|
|
49
|
+
const filters = opts.filter.split(",");
|
|
50
|
+
for (const f of filters) {
|
|
51
|
+
const match = f.match(/^(\w+)(!=|>=|<=|>|<|=)(.+)$/);
|
|
52
|
+
if (match) {
|
|
53
|
+
const [, key, op, value] = match;
|
|
54
|
+
if (op === "=") {
|
|
55
|
+
params[key] = value;
|
|
56
|
+
}
|
|
57
|
+
else if (op === "!=") {
|
|
58
|
+
params[`${key}[ne]`] = value;
|
|
59
|
+
}
|
|
60
|
+
else if (op === ">") {
|
|
61
|
+
params[`${key}[gt]`] = value;
|
|
62
|
+
}
|
|
63
|
+
else if (op === "<") {
|
|
64
|
+
params[`${key}[lt]`] = value;
|
|
65
|
+
}
|
|
66
|
+
else if (op === ">=") {
|
|
67
|
+
params[`${key}[gte]`] = value;
|
|
68
|
+
}
|
|
69
|
+
else if (op === "<=") {
|
|
70
|
+
params[`${key}[lte]`] = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const res = await apiCallHandler(module.module, collection.toLowerCase(), "get", null, params);
|
|
76
|
+
if (opts.count) {
|
|
77
|
+
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;
|
|
78
|
+
spinner.success({ text: `${total} matching entries in ${collection}` });
|
|
79
|
+
spinner.stop();
|
|
80
|
+
console.log(total);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
handleResponseFormatOptions(opts, res);
|
|
84
|
+
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;
|
|
85
|
+
const page = (_j = (_h = (_g = res.meta) === null || _g === void 0 ? void 0 : _g.pageInfo) === null || _h === void 0 ? void 0 : _h.page) !== null && _j !== void 0 ? _j : 1;
|
|
86
|
+
const totalPages = (_m = (_l = (_k = res.meta) === null || _k === void 0 ? void 0 : _k.pageInfo) === null || _l === void 0 ? void 0 : _l.totalPages) !== null && _m !== void 0 ? _m : 1;
|
|
87
|
+
spinner.success({
|
|
88
|
+
text: `Found ${total} entries in ${collection} (page ${page}/${totalPages})`,
|
|
89
|
+
});
|
|
90
|
+
spinner.stop();
|
|
91
|
+
localStorage.setItem("lastModule", module.module);
|
|
92
|
+
localStorage.setItem("lastCollection", collection);
|
|
93
|
+
if (opts.save)
|
|
94
|
+
await saveFile("search", opts, res);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
spinner.error({ text: e.message || "Search failed" });
|
|
98
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Argument } from "commander";
|
|
2
|
+
import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
|
|
3
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
4
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
5
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
6
|
+
import { localStorage } from "../helpers/localStorage.js";
|
|
7
|
+
import { spinner } from "../../index.js";
|
|
8
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
9
|
+
export function createUpdateCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("update")
|
|
12
|
+
.alias("u")
|
|
13
|
+
.description("Update an entry non-interactively (for scripts and AI agents)")
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Unlike 'edit' (which opens an interactive editor), 'update' accepts changes
|
|
16
|
+
as JSON via --body, --body-file, or --body-stdin. Perfect for automation.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
ay update contacts 64a1b2c3 --body '{"firstName":"Jane"}'
|
|
20
|
+
ay update products 64a1b2c3 --body-file changes.json
|
|
21
|
+
echo '{"status":"active"}' | ay update contacts 64a1b2c3 --body-stdin
|
|
22
|
+
ay u tasks 64a1b2c3 --body '{"done":true}' --force`)
|
|
23
|
+
.addArgument(new Argument("[collection]", "The collection to use").default(localStorage.getItem("lastCollection"), `The last used collection (${localStorage.getItem("lastCollection")})`))
|
|
24
|
+
.addArgument(new Argument("[id]", "The ID of the entry to update").default(localStorage.getItem("lastId"), `The last used id (${localStorage.getItem("lastId")})`))
|
|
25
|
+
.option("--body <json>", "Update data as JSON string")
|
|
26
|
+
.option("--body-file <path>", "Read update data from JSON file")
|
|
27
|
+
.option("--body-stdin", "Read update data from stdin")
|
|
28
|
+
.option("--merge", "Merge with existing entry (GET + merge + PUT)", false)
|
|
29
|
+
.action(async (collection, id, options) => {
|
|
30
|
+
try {
|
|
31
|
+
if (!collection) {
|
|
32
|
+
spinner.error({ text: "Missing required argument: collection" });
|
|
33
|
+
process.exit(EXIT_MISUSE);
|
|
34
|
+
}
|
|
35
|
+
if (!id) {
|
|
36
|
+
spinner.error({ text: "Missing required argument: id" });
|
|
37
|
+
process.exit(EXIT_MISUSE);
|
|
38
|
+
}
|
|
39
|
+
const opts = { ...program.opts(), ...options };
|
|
40
|
+
const module = getModuleFromCollection(collection);
|
|
41
|
+
// Parse body from various sources
|
|
42
|
+
let body = null;
|
|
43
|
+
if (opts.body) {
|
|
44
|
+
try {
|
|
45
|
+
body = JSON.parse(opts.body);
|
|
46
|
+
}
|
|
47
|
+
catch (_a) {
|
|
48
|
+
spinner.error({ text: "Invalid JSON in --body" });
|
|
49
|
+
process.exit(EXIT_MISUSE);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (opts.bodyFile) {
|
|
53
|
+
const fs = await import("fs");
|
|
54
|
+
const content = fs.readFileSync(opts.bodyFile, "utf-8");
|
|
55
|
+
try {
|
|
56
|
+
body = JSON.parse(content);
|
|
57
|
+
}
|
|
58
|
+
catch (_b) {
|
|
59
|
+
spinner.error({ text: `Invalid JSON in file: ${opts.bodyFile}` });
|
|
60
|
+
process.exit(EXIT_MISUSE);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (opts.bodyStdin && !process.stdin.isTTY) {
|
|
64
|
+
const chunks = [];
|
|
65
|
+
for await (const chunk of process.stdin) {
|
|
66
|
+
chunks.push(chunk);
|
|
67
|
+
}
|
|
68
|
+
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
69
|
+
if (stdinContent) {
|
|
70
|
+
try {
|
|
71
|
+
body = JSON.parse(stdinContent);
|
|
72
|
+
}
|
|
73
|
+
catch (_c) {
|
|
74
|
+
spinner.error({ text: "Invalid JSON from stdin" });
|
|
75
|
+
process.exit(EXIT_MISUSE);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!body) {
|
|
80
|
+
spinner.error({ text: "No update data provided. Use --body, --body-file, or --body-stdin." });
|
|
81
|
+
process.exit(EXIT_MISUSE);
|
|
82
|
+
}
|
|
83
|
+
// Merge mode: GET existing → merge → PUT
|
|
84
|
+
if (opts.merge) {
|
|
85
|
+
spinner.start({ text: `Fetching ${id} from ${collection}...`, color: "magenta" });
|
|
86
|
+
const existing = await apiCallHandler(module.module, `${collection.toLowerCase()}/${id}`, "get", null, { responseFormat: "json" });
|
|
87
|
+
const existingData = (existing === null || existing === void 0 ? void 0 : existing.payload) || {};
|
|
88
|
+
body = { ...existingData, ...body };
|
|
89
|
+
spinner.update({ text: `Updating ${id} in ${collection} (merged)...` });
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
spinner.start({ text: `Updating ${id} in ${collection}...`, color: "magenta" });
|
|
93
|
+
}
|
|
94
|
+
// Ensure _id is set
|
|
95
|
+
body._id = id;
|
|
96
|
+
const res = await apiCallHandler(module.module, collection.toLowerCase(), "put", body, {
|
|
97
|
+
responseFormat: opts.responseFormat,
|
|
98
|
+
verbosity: opts.verbosity,
|
|
99
|
+
hideMeta: opts.hideMeta,
|
|
100
|
+
});
|
|
101
|
+
handleResponseFormatOptions(opts, res);
|
|
102
|
+
spinner.success({ text: `Updated entry ${id} in ${collection}` });
|
|
103
|
+
spinner.stop();
|
|
104
|
+
localStorage.setItem("lastModule", module.module);
|
|
105
|
+
localStorage.setItem("lastCollection", collection);
|
|
106
|
+
localStorage.setItem("lastId", id);
|
|
107
|
+
if (opts.save)
|
|
108
|
+
await saveFile("update", opts, res);
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
spinner.error({ text: e.message || "An unexpected error occurred" });
|
|
112
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
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 createWebhooksCommand(program) {
|
|
7
|
+
const hooks = program
|
|
8
|
+
.command("webhooks")
|
|
9
|
+
.alias("hooks")
|
|
10
|
+
.description("Manage webhooks and event subscriptions");
|
|
11
|
+
// ay webhooks list
|
|
12
|
+
hooks
|
|
13
|
+
.command("list")
|
|
14
|
+
.alias("ls")
|
|
15
|
+
.description("List registered webhooks")
|
|
16
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
17
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
var _a, _b, _c;
|
|
20
|
+
try {
|
|
21
|
+
const opts = { ...program.opts(), ...options };
|
|
22
|
+
spinner.start({ text: "Fetching webhooks...", color: "magenta" });
|
|
23
|
+
const res = await apiCallHandler("config", "hooks", "get", null, {
|
|
24
|
+
page: opts.page,
|
|
25
|
+
limit: opts.limit,
|
|
26
|
+
responseFormat: opts.responseFormat,
|
|
27
|
+
verbosity: opts.verbosity,
|
|
28
|
+
});
|
|
29
|
+
handleResponseFormatOptions(opts, res);
|
|
30
|
+
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;
|
|
31
|
+
spinner.success({ text: `Found ${total} webhooks` });
|
|
32
|
+
spinner.stop();
|
|
33
|
+
if (opts.save)
|
|
34
|
+
await saveFile("webhooks-list", opts, res);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
spinner.error({ text: e.message || "Failed to list webhooks" });
|
|
38
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// ay webhooks get <id>
|
|
42
|
+
hooks
|
|
43
|
+
.command("get <id>")
|
|
44
|
+
.description("Get webhook details")
|
|
45
|
+
.action(async (id, options) => {
|
|
46
|
+
try {
|
|
47
|
+
const opts = { ...program.opts(), ...options };
|
|
48
|
+
spinner.start({ text: `Fetching webhook ${id}...`, color: "magenta" });
|
|
49
|
+
const res = await apiCallHandler("config", `hooks/${id}`, "get", null, {
|
|
50
|
+
responseFormat: opts.responseFormat,
|
|
51
|
+
verbosity: opts.verbosity,
|
|
52
|
+
});
|
|
53
|
+
handleResponseFormatOptions(opts, res);
|
|
54
|
+
spinner.success({ text: `Webhook ${id} loaded` });
|
|
55
|
+
spinner.stop();
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
spinner.error({ text: e.message || "Failed to get webhook" });
|
|
59
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// ay webhooks create --body '{...}'
|
|
63
|
+
hooks
|
|
64
|
+
.command("create")
|
|
65
|
+
.description("Create a new webhook")
|
|
66
|
+
.option("--body <json>", "Webhook definition as JSON")
|
|
67
|
+
.option("--body-file <path>", "Read webhook definition from file")
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
try {
|
|
70
|
+
const opts = { ...program.opts(), ...options };
|
|
71
|
+
let body = null;
|
|
72
|
+
if (opts.body) {
|
|
73
|
+
try {
|
|
74
|
+
body = JSON.parse(opts.body);
|
|
75
|
+
}
|
|
76
|
+
catch (_a) {
|
|
77
|
+
spinner.error({ text: "Invalid JSON in --body" });
|
|
78
|
+
process.exit(EXIT_MISUSE);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (opts.bodyFile) {
|
|
82
|
+
const fs = await import("fs");
|
|
83
|
+
try {
|
|
84
|
+
body = JSON.parse(fs.readFileSync(opts.bodyFile, "utf-8"));
|
|
85
|
+
}
|
|
86
|
+
catch (_b) {
|
|
87
|
+
spinner.error({ text: `Invalid JSON in file: ${opts.bodyFile}` });
|
|
88
|
+
process.exit(EXIT_MISUSE);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!body) {
|
|
92
|
+
spinner.error({ text: "Provide webhook definition via --body or --body-file" });
|
|
93
|
+
process.exit(EXIT_MISUSE);
|
|
94
|
+
}
|
|
95
|
+
spinner.start({ text: "Creating webhook...", color: "magenta" });
|
|
96
|
+
const res = await apiCallHandler("config", "hooks", "post", body, {
|
|
97
|
+
responseFormat: opts.responseFormat,
|
|
98
|
+
});
|
|
99
|
+
handleResponseFormatOptions(opts, res);
|
|
100
|
+
spinner.success({ text: `Webhook created` });
|
|
101
|
+
spinner.stop();
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
spinner.error({ text: e.message || "Failed to create webhook" });
|
|
105
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// ay webhooks delete <id>
|
|
109
|
+
hooks
|
|
110
|
+
.command("delete <id>")
|
|
111
|
+
.alias("rm")
|
|
112
|
+
.description("Delete a webhook")
|
|
113
|
+
.action(async (id, options) => {
|
|
114
|
+
try {
|
|
115
|
+
const opts = { ...program.opts(), ...options };
|
|
116
|
+
spinner.start({ text: `Deleting webhook ${id}...`, color: "magenta" });
|
|
117
|
+
const res = await apiCallHandler("config", `hooks/${id}`, "delete", null, {
|
|
118
|
+
responseFormat: opts.responseFormat,
|
|
119
|
+
});
|
|
120
|
+
handleResponseFormatOptions(opts, res);
|
|
121
|
+
spinner.success({ text: `Webhook ${id} deleted` });
|
|
122
|
+
spinner.stop();
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
spinner.error({ text: e.message || "Failed to delete webhook" });
|
|
126
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
// ay webhooks templates
|
|
130
|
+
hooks
|
|
131
|
+
.command("templates")
|
|
132
|
+
.description("List available webhook templates")
|
|
133
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
134
|
+
.action(async (options) => {
|
|
135
|
+
var _a, _b, _c;
|
|
136
|
+
try {
|
|
137
|
+
const opts = { ...program.opts(), ...options };
|
|
138
|
+
spinner.start({ text: "Fetching webhook templates...", color: "magenta" });
|
|
139
|
+
const res = await apiCallHandler("config", "hooktemplates", "get", null, {
|
|
140
|
+
limit: opts.limit,
|
|
141
|
+
responseFormat: opts.responseFormat,
|
|
142
|
+
verbosity: opts.verbosity,
|
|
143
|
+
});
|
|
144
|
+
handleResponseFormatOptions(opts, res);
|
|
145
|
+
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;
|
|
146
|
+
spinner.success({ text: `Found ${total} webhook templates` });
|
|
147
|
+
spinner.stop();
|
|
148
|
+
if (opts.save)
|
|
149
|
+
await saveFile("webhook-templates", opts, res);
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
spinner.error({ text: e.message || "Failed to list templates" });
|
|
153
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|