@tolinax/ayoune-cli 2026.2.1 → 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/data/defaultActions.js +9 -0
- package/data/modelsAndRights.js +3189 -0
- package/data/modules.js +111 -0
- package/data/operations.js +5 -0
- package/data/services.js +139 -0
- package/lib/api/apiCallHandler.js +68 -0
- package/lib/api/apiClient.js +100 -0
- package/lib/api/auditCallHandler.js +21 -0
- package/lib/api/decodeToken.js +4 -0
- package/lib/api/handleAPIError.js +59 -0
- package/lib/api/login.js +45 -0
- package/lib/commands/createActionsCommand.js +109 -0
- package/lib/commands/createAiCommand.js +188 -0
- package/lib/commands/createAliasCommand.js +106 -0
- package/lib/commands/createAuditCommand.js +49 -0
- package/lib/commands/createBatchCommand.js +304 -0
- package/lib/commands/createCompletionsCommand.js +154 -0
- package/lib/commands/createConfigCommand.js +208 -0
- package/lib/commands/createCopyCommand.js +39 -0
- package/lib/commands/createCreateCommand.js +50 -0
- package/lib/commands/createDeleteCommand.js +98 -0
- package/lib/commands/createDeployCommand.js +666 -0
- package/lib/commands/createDescribeCommand.js +42 -0
- package/lib/commands/createEditCommand.js +43 -0
- package/lib/commands/createEventsCommand.js +60 -0
- package/lib/commands/createExecCommand.js +182 -0
- package/lib/commands/createGetCommand.js +47 -0
- package/lib/commands/createJobsCommand.js +168 -0
- package/lib/commands/createListCommand.js +49 -0
- package/lib/commands/createLoginCommand.js +18 -0
- package/lib/commands/createLogoutCommand.js +21 -0
- package/lib/commands/createModulesCommand.js +89 -0
- package/lib/commands/createMonitorCommand.js +283 -0
- package/lib/commands/createProgram.js +175 -0
- package/lib/commands/createSearchCommand.js +101 -0
- package/lib/commands/createServicesCommand.js +228 -0
- package/lib/commands/createStorageCommand.js +54 -0
- package/lib/commands/createStreamCommand.js +50 -0
- package/lib/commands/createUpdateCommand.js +115 -0
- package/lib/commands/createWebhooksCommand.js +156 -0
- package/lib/commands/createWhoAmICommand.js +88 -0
- package/lib/exitCodes.js +6 -0
- package/lib/helpers/addSpacesToCamelCase.js +5 -0
- package/lib/helpers/config.js +6 -0
- package/lib/helpers/configLoader.js +60 -0
- package/lib/helpers/formatDocument.js +176 -0
- package/lib/helpers/handleResponseFormatOptions.js +85 -0
- package/lib/helpers/initializeSettings.js +14 -0
- package/lib/helpers/localStorage.js +4 -0
- package/lib/helpers/makeRandomToken.js +27 -0
- package/lib/helpers/parseInt.js +7 -0
- package/lib/helpers/requireArg.js +9 -0
- package/lib/helpers/saveFile.js +39 -0
- package/lib/models/getCollections.js +15 -0
- package/lib/models/getModelsInModules.js +13 -0
- package/lib/models/getModuleFromCollection.js +7 -0
- package/lib/operations/handleAuditOperation.js +22 -0
- package/lib/operations/handleCollectionOperation.js +91 -0
- package/lib/operations/handleCopySingleOperation.js +22 -0
- package/lib/operations/handleCreateSingleOperation.js +35 -0
- package/lib/operations/handleDeleteSingleOperation.js +14 -0
- package/lib/operations/handleDescribeSingleOperation.js +22 -0
- package/lib/operations/handleEditOperation.js +51 -0
- package/lib/operations/handleEditRawOperation.js +35 -0
- package/lib/operations/handleGetOperation.js +29 -0
- package/lib/operations/handleGetSingleOperation.js +20 -0
- package/lib/operations/handleListOperation.js +63 -0
- package/lib/operations/handleSingleAuditOperation.js +27 -0
- package/lib/prompts/promptAudits.js +15 -0
- package/lib/prompts/promptCollection.js +13 -0
- package/lib/prompts/promptCollectionInModule.js +13 -0
- package/lib/prompts/promptCollectionWithModule.js +15 -0
- package/lib/prompts/promptConfirm.js +12 -0
- package/lib/prompts/promptDefaultAction.js +13 -0
- package/lib/prompts/promptEntry.js +19 -0
- package/lib/prompts/promptFileName.js +12 -0
- package/lib/prompts/promptFilePath.js +18 -0
- package/lib/prompts/promptModule.js +19 -0
- package/lib/prompts/promptName.js +11 -0
- package/lib/prompts/promptOperation.js +13 -0
- package/lib/socket/customerSocketClient.js +13 -0
- package/lib/socket/socketClient.js +12 -0
- package/lib/types.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
2
|
+
import { api } from "../api/apiClient.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
|
+
const AI_HOST = "ai.ayoune.app";
|
|
9
|
+
export function createAiCommand(program) {
|
|
10
|
+
const ai = program
|
|
11
|
+
.command("ai")
|
|
12
|
+
.description("Interact with aYOUne AI services");
|
|
13
|
+
// ay ai actions — list all AI endpoints
|
|
14
|
+
ai.command("actions")
|
|
15
|
+
.description("List all registered AI API actions")
|
|
16
|
+
.action(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const opts = { ...program.opts() };
|
|
19
|
+
spinner.start({ text: "Fetching AI actions...", color: "magenta" });
|
|
20
|
+
const res = await apiCallHandler("config", "ayouneapiactions", "get", null, {
|
|
21
|
+
q: "ai",
|
|
22
|
+
limit: 200,
|
|
23
|
+
responseFormat: "json",
|
|
24
|
+
});
|
|
25
|
+
if (!(res === null || res === void 0 ? void 0 : res.payload)) {
|
|
26
|
+
spinner.error({ text: "No AI actions found" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const actions = (Array.isArray(res.payload) ? res.payload : [])
|
|
30
|
+
.filter((a) => a.nameSpace === "ai" && !a.deprecated)
|
|
31
|
+
.map((a) => ({
|
|
32
|
+
operationId: a.operationId || "",
|
|
33
|
+
method: (a.method || "GET").toUpperCase(),
|
|
34
|
+
endpoint: a.endpoint || "",
|
|
35
|
+
description: a.shortDescription || a.description || "",
|
|
36
|
+
}));
|
|
37
|
+
const formattedRes = {
|
|
38
|
+
payload: actions,
|
|
39
|
+
meta: { responseTime: 0, pageInfo: { totalEntries: actions.length, page: 1, totalPages: 1 } },
|
|
40
|
+
};
|
|
41
|
+
handleResponseFormatOptions(opts, formattedRes);
|
|
42
|
+
spinner.success({ text: `Found ${actions.length} AI actions` });
|
|
43
|
+
spinner.stop();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
spinner.error({ text: e.message || "Failed to fetch AI actions" });
|
|
47
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// ay ai conversations — list AI conversations
|
|
51
|
+
ai.command("conversations")
|
|
52
|
+
.description("List AI conversations")
|
|
53
|
+
.option("-l, --limit <number>", "Limit", parseInt, 20)
|
|
54
|
+
.option("-p, --page <number>", "Page", parseInt, 1)
|
|
55
|
+
.action(async (options) => {
|
|
56
|
+
var _a, _b, _c;
|
|
57
|
+
try {
|
|
58
|
+
const opts = { ...program.opts(), ...options };
|
|
59
|
+
spinner.start({ text: "Fetching AI conversations...", color: "magenta" });
|
|
60
|
+
const res = await apiCallHandler("ai", "conversations", "get", null, {
|
|
61
|
+
page: opts.page,
|
|
62
|
+
limit: opts.limit,
|
|
63
|
+
responseFormat: opts.responseFormat,
|
|
64
|
+
verbosity: opts.verbosity,
|
|
65
|
+
hideMeta: opts.hideMeta,
|
|
66
|
+
});
|
|
67
|
+
const { plainResult, result, content } = handleResponseFormatOptions(opts, res);
|
|
68
|
+
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;
|
|
69
|
+
spinner.success({ text: `Got ${total} AI conversations` });
|
|
70
|
+
spinner.stop();
|
|
71
|
+
if (opts.save)
|
|
72
|
+
await saveFile("ai-conversations", opts, res);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
spinner.error({ text: e.message || "Failed to list conversations" });
|
|
76
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// ay ai prompts — list AI prompt templates
|
|
80
|
+
ai.command("prompts")
|
|
81
|
+
.description("List AI prompt templates")
|
|
82
|
+
.option("-l, --limit <number>", "Limit", parseInt, 20)
|
|
83
|
+
.option("-p, --page <number>", "Page", parseInt, 1)
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
var _a, _b, _c;
|
|
86
|
+
try {
|
|
87
|
+
const opts = { ...program.opts(), ...options };
|
|
88
|
+
spinner.start({ text: "Fetching AI prompts...", color: "magenta" });
|
|
89
|
+
const res = await apiCallHandler("ai", "prompts", "get", null, {
|
|
90
|
+
page: opts.page,
|
|
91
|
+
limit: opts.limit,
|
|
92
|
+
responseFormat: opts.responseFormat,
|
|
93
|
+
verbosity: opts.verbosity,
|
|
94
|
+
hideMeta: opts.hideMeta,
|
|
95
|
+
});
|
|
96
|
+
const { plainResult, result, content } = 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: `Got ${total} AI prompts` });
|
|
99
|
+
spinner.stop();
|
|
100
|
+
if (opts.save)
|
|
101
|
+
await saveFile("ai-prompts", opts, res);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
spinner.error({ text: e.message || "Failed to list prompts" });
|
|
105
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// ay ai ask <prompt> — send a prompt to AI
|
|
109
|
+
ai.command("ask <prompt...>")
|
|
110
|
+
.description("Send a prompt to aYOUne AI")
|
|
111
|
+
.option("--conversation <id>", "Continue an existing conversation")
|
|
112
|
+
.option("--model <model>", "AI model to use")
|
|
113
|
+
.action(async (promptWords, options) => {
|
|
114
|
+
try {
|
|
115
|
+
const opts = { ...program.opts(), ...options };
|
|
116
|
+
const prompt = promptWords.join(" ");
|
|
117
|
+
spinner.start({ text: "Sending prompt to AI...", color: "magenta" });
|
|
118
|
+
const body = { prompt };
|
|
119
|
+
if (opts.conversation)
|
|
120
|
+
body.conversationId = opts.conversation;
|
|
121
|
+
if (opts.model)
|
|
122
|
+
body.model = opts.model;
|
|
123
|
+
const res = await apiCallHandler("ai", "ask", "post", body, {
|
|
124
|
+
responseFormat: opts.responseFormat,
|
|
125
|
+
verbosity: opts.verbosity,
|
|
126
|
+
hideMeta: opts.hideMeta,
|
|
127
|
+
});
|
|
128
|
+
const { plainResult, result, content } = handleResponseFormatOptions(opts, res);
|
|
129
|
+
spinner.success({ text: "AI response received" });
|
|
130
|
+
spinner.stop();
|
|
131
|
+
if (opts.save)
|
|
132
|
+
await saveFile("ai-ask", opts, res);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
spinner.error({ text: e.message || "AI request failed" });
|
|
136
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// ay ai generate <type> <prompt> — generate content
|
|
140
|
+
ai.command("generate <type> <prompt...>")
|
|
141
|
+
.description("Generate content (text, image, video)")
|
|
142
|
+
.addHelpText("after", `
|
|
143
|
+
Types: text, image, video
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
ay ai generate text "Write a product description for..."
|
|
147
|
+
ay ai generate image "A modern office building"`)
|
|
148
|
+
.action(async (type, promptWords, options) => {
|
|
149
|
+
try {
|
|
150
|
+
const opts = { ...program.opts(), ...options };
|
|
151
|
+
const prompt = promptWords.join(" ");
|
|
152
|
+
const typeHostMap = {
|
|
153
|
+
text: "text-generation",
|
|
154
|
+
image: "image-generation",
|
|
155
|
+
video: "video-generation",
|
|
156
|
+
};
|
|
157
|
+
const host = typeHostMap[type];
|
|
158
|
+
if (!host) {
|
|
159
|
+
spinner.error({ text: `Unknown type: ${type}. Use: text, image, video` });
|
|
160
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
161
|
+
}
|
|
162
|
+
spinner.start({ text: `Generating ${type}...`, color: "magenta" });
|
|
163
|
+
const response = await api({
|
|
164
|
+
baseURL: `https://${host}.ayoune.app`,
|
|
165
|
+
method: "post",
|
|
166
|
+
url: "/generate",
|
|
167
|
+
data: { prompt },
|
|
168
|
+
params: {
|
|
169
|
+
responseFormat: opts.responseFormat,
|
|
170
|
+
verbosity: opts.verbosity,
|
|
171
|
+
},
|
|
172
|
+
headers: {
|
|
173
|
+
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const res = response.data;
|
|
177
|
+
handleResponseFormatOptions(opts, res);
|
|
178
|
+
spinner.success({ text: `${type} generation complete` });
|
|
179
|
+
spinner.stop();
|
|
180
|
+
if (opts.save)
|
|
181
|
+
await saveFile(`ai-generate-${type}`, opts, res);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
spinner.error({ text: e.message || `${type} generation failed` });
|
|
185
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
5
|
+
import { spinner } from "../../index.js";
|
|
6
|
+
const ALIASES_PATH = path.join(os.homedir(), ".config", "ayoune", "aliases.json");
|
|
7
|
+
export function loadAliases() {
|
|
8
|
+
if (!existsSync(ALIASES_PATH)) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(ALIASES_PATH, "utf-8"));
|
|
13
|
+
}
|
|
14
|
+
catch (_a) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function saveAliases(aliases) {
|
|
19
|
+
const dir = path.dirname(ALIASES_PATH);
|
|
20
|
+
if (!existsSync(dir)) {
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
writeFileSync(ALIASES_PATH, JSON.stringify(aliases, null, 2), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
export function createAliasCommand(program) {
|
|
26
|
+
const alias = program
|
|
27
|
+
.command("alias")
|
|
28
|
+
.description("Manage custom command aliases")
|
|
29
|
+
.addHelpText("after", `
|
|
30
|
+
Examples:
|
|
31
|
+
ay alias set ll "list leads" Create alias 'll' for 'list leads'
|
|
32
|
+
ay alias list Show all custom aliases
|
|
33
|
+
ay alias remove ll Remove the 'll' alias`);
|
|
34
|
+
alias
|
|
35
|
+
.command("set <name> <command>")
|
|
36
|
+
.description("Create or update a custom alias")
|
|
37
|
+
.action((name, command) => {
|
|
38
|
+
try {
|
|
39
|
+
const aliases = loadAliases();
|
|
40
|
+
aliases[name] = command;
|
|
41
|
+
saveAliases(aliases);
|
|
42
|
+
spinner.success({ text: `Alias '${name}' set to '${command}'` });
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
spinner.error({ text: e.message || "Failed to save alias" });
|
|
46
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
alias
|
|
50
|
+
.command("list")
|
|
51
|
+
.description("List all custom aliases")
|
|
52
|
+
.action(() => {
|
|
53
|
+
const aliases = loadAliases();
|
|
54
|
+
const entries = Object.entries(aliases);
|
|
55
|
+
if (entries.length === 0) {
|
|
56
|
+
console.log("No custom aliases defined.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const [name, command] of entries) {
|
|
60
|
+
console.log(` ${name} -> ${command}`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
alias
|
|
64
|
+
.command("remove <name>")
|
|
65
|
+
.description("Remove a custom alias")
|
|
66
|
+
.action((name) => {
|
|
67
|
+
try {
|
|
68
|
+
const aliases = loadAliases();
|
|
69
|
+
if (!(name in aliases)) {
|
|
70
|
+
spinner.error({ text: `Alias '${name}' not found` });
|
|
71
|
+
process.exit(EXIT_MISUSE);
|
|
72
|
+
}
|
|
73
|
+
delete aliases[name];
|
|
74
|
+
saveAliases(aliases);
|
|
75
|
+
spinner.success({ text: `Alias '${name}' removed` });
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
spinner.error({ text: e.message || "Failed to remove alias" });
|
|
79
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function registerUserAliases(program) {
|
|
84
|
+
const aliases = loadAliases();
|
|
85
|
+
for (const [name, command] of Object.entries(aliases)) {
|
|
86
|
+
// Check if we'd conflict with a built-in command
|
|
87
|
+
const existing = program.commands.find((cmd) => cmd.name() === name || cmd.aliases().includes(name));
|
|
88
|
+
if (existing)
|
|
89
|
+
continue;
|
|
90
|
+
program
|
|
91
|
+
.command(name)
|
|
92
|
+
.description(`Alias for '${command}'`)
|
|
93
|
+
.allowUnknownOption()
|
|
94
|
+
.allowExcessArguments()
|
|
95
|
+
.action(async (...args) => {
|
|
96
|
+
// Re-parse with the aliased command substituted in
|
|
97
|
+
const argv = process.argv.slice(0, 2).concat(command.split(/\s+/));
|
|
98
|
+
// Append any extra args passed after the alias
|
|
99
|
+
const extraArgs = args.slice(0, -1); // last arg is the Command object
|
|
100
|
+
if (Array.isArray(extraArgs[0])) {
|
|
101
|
+
argv.push(...extraArgs[0]);
|
|
102
|
+
}
|
|
103
|
+
program.parse(argv);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Argument } from "commander";
|
|
2
|
+
import { localStorage } from "../helpers/localStorage.js";
|
|
3
|
+
import { handleAuditOperation } from "../operations/handleAuditOperation.js";
|
|
4
|
+
import { promptAudits } from "../prompts/promptAudits.js";
|
|
5
|
+
import { handleSingleAuditOperation } from "../operations/handleSingleAuditOperation.js";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import { spinner } from "../../index.js";
|
|
8
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
9
|
+
export function createAuditCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("audit")
|
|
12
|
+
.addArgument(new Argument("[collection]", "The collection to use").default(localStorage.getItem("lastCollection"), `The last used collection (${localStorage.getItem("lastCollection")})`))
|
|
13
|
+
.addArgument(new Argument("[id]", "The id to get the audit from").default(localStorage.getItem("lastId"), `The last used id (${localStorage.getItem("lastId")})`))
|
|
14
|
+
.alias("history")
|
|
15
|
+
.description("View the change history of an entry")
|
|
16
|
+
.addHelpText("after", `
|
|
17
|
+
Examples:
|
|
18
|
+
ay audit contacts 64a1b2c3d4e5 View audit trail for a contact
|
|
19
|
+
ay history View audit for last used entry`)
|
|
20
|
+
.action(async (collection, id, options) => {
|
|
21
|
+
try {
|
|
22
|
+
if (!collection) {
|
|
23
|
+
spinner.error({ text: "Missing required argument: collection. Run a list or get command first, or provide it explicitly." });
|
|
24
|
+
process.exit(EXIT_MISUSE);
|
|
25
|
+
}
|
|
26
|
+
if (!id) {
|
|
27
|
+
spinner.error({ text: "Missing required argument: id. Provide an entry ID explicitly." });
|
|
28
|
+
process.exit(EXIT_MISUSE);
|
|
29
|
+
}
|
|
30
|
+
const opts = { ...program.opts(), ...options };
|
|
31
|
+
const { data, content, result, meta } = await handleAuditOperation(collection, id, {
|
|
32
|
+
...opts,
|
|
33
|
+
});
|
|
34
|
+
if (!process.stdin.isTTY) {
|
|
35
|
+
spinner.error({ text: "The audit command requires an interactive terminal for audit selection" });
|
|
36
|
+
process.exit(EXIT_MISUSE);
|
|
37
|
+
}
|
|
38
|
+
const selectedAudit = await promptAudits(result);
|
|
39
|
+
const { singleData, singleContent, singleResult, singleMeta } = await handleSingleAuditOperation(collection, id, selectedAudit, {
|
|
40
|
+
...opts,
|
|
41
|
+
});
|
|
42
|
+
console.log(yaml.dump(singleContent));
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
spinner.error({ text: e.message || "An unexpected error occurred" });
|
|
46
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -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
|
+
}
|