@tolinax/ayoune-cli 2026.3.1 → 2026.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/contextSlots.js +189 -0
- package/data/defaultActions.js +9 -0
- package/data/modelsAndRights.js +3245 -0
- package/data/modules.js +127 -0
- package/data/operations.js +5 -0
- package/data/services.js +139 -0
- package/index.js +11 -0
- package/lib/api/apiCallHandler.js +72 -0
- package/lib/api/apiClient.js +108 -0
- package/lib/api/auditCallHandler.js +21 -0
- package/lib/api/decodeToken.js +4 -0
- package/lib/api/handleAPIError.js +61 -0
- package/lib/api/login.js +45 -0
- package/lib/api/searchClient.js +119 -0
- package/lib/commands/createAccessCommand.js +126 -0
- package/lib/commands/createActionsCommand.js +140 -0
- package/lib/commands/createAiCommand.js +188 -0
- package/lib/commands/createAliasCommand.js +104 -0
- package/lib/commands/createAuditCommand.js +45 -0
- package/lib/commands/createBatchCommand.js +291 -0
- package/lib/commands/createCompletionsCommand.js +172 -0
- package/lib/commands/createConfigCommand.js +202 -0
- package/lib/commands/createContextCommand.js +163 -0
- package/lib/commands/createCopyCommand.js +36 -0
- package/lib/commands/createCreateCommand.js +47 -0
- package/lib/commands/createDeleteCommand.js +96 -0
- package/lib/commands/createDeployCommand.js +642 -0
- package/lib/commands/createDescribeCommand.js +44 -0
- package/lib/commands/createEditCommand.js +48 -0
- package/lib/commands/createEventsCommand.js +60 -0
- package/lib/commands/createExecCommand.js +212 -0
- package/lib/commands/createExportCommand.js +216 -0
- package/lib/commands/createGetCommand.js +46 -0
- package/lib/commands/createJobsCommand.js +163 -0
- package/lib/commands/createListCommand.js +48 -0
- package/lib/commands/createLoginCommand.js +30 -0
- package/lib/commands/createLogoutCommand.js +21 -0
- package/lib/commands/createModulesCommand.js +147 -0
- package/lib/commands/createMonitorCommand.js +276 -0
- package/lib/commands/createPermissionsCommand.js +233 -0
- package/lib/commands/createProgram.js +211 -0
- package/lib/commands/createSearchCommand.js +251 -0
- package/lib/commands/createSelfHostUpdateCommand.js +166 -0
- package/lib/commands/createServicesCommand.js +225 -0
- package/lib/commands/createSetupCommand.js +305 -0
- package/lib/commands/createStatusCommand.js +147 -0
- package/lib/commands/createStorageCommand.js +53 -0
- package/lib/commands/createStreamCommand.js +50 -0
- package/lib/commands/createSyncCommand.js +174 -0
- package/lib/commands/createTemplateCommand.js +231 -0
- package/lib/commands/createUpdateCommand.js +112 -0
- package/lib/commands/createUsersCommand.js +275 -0
- package/lib/commands/createWebhooksCommand.js +149 -0
- package/lib/commands/createWhoAmICommand.js +90 -0
- package/lib/exitCodes.js +6 -0
- package/lib/helpers/addSpacesToCamelCase.js +5 -0
- package/lib/helpers/cliError.js +24 -0
- package/lib/helpers/config.js +7 -0
- package/lib/helpers/configLoader.js +66 -0
- package/lib/helpers/contextInjector.js +65 -0
- package/lib/helpers/contextResolver.js +70 -0
- package/lib/helpers/contextStore.js +46 -0
- package/lib/helpers/formatDocument.js +176 -0
- package/lib/helpers/handleResponseFormatOptions.js +134 -0
- package/lib/helpers/initializeSettings.js +14 -0
- package/lib/helpers/localStorage.js +4 -0
- package/lib/helpers/logo.js +48 -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/resolveCollectionArgs.js +36 -0
- package/lib/helpers/sanitizeFields.js +18 -0
- package/lib/helpers/saveFile.js +39 -0
- package/lib/helpers/secureStorage.js +72 -0
- package/lib/helpers/tokenPayload.js +21 -0
- package/lib/helpers/updateNotifier.js +49 -0
- package/lib/models/getCollections.js +15 -0
- package/lib/models/getModelsInModules.js +13 -0
- package/lib/models/getModuleFromCollection.js +10 -0
- package/lib/operations/handleAuditOperation.js +22 -0
- package/lib/operations/handleCollectionOperation.js +91 -0
- package/lib/operations/handleCopySingleOperation.js +30 -0
- package/lib/operations/handleCreateSingleOperation.js +38 -0
- package/lib/operations/handleDeleteSingleOperation.js +14 -0
- package/lib/operations/handleDescribeSingleOperation.js +45 -0
- package/lib/operations/handleEditOperation.js +51 -0
- package/lib/operations/handleEditRawOperation.js +35 -0
- package/lib/operations/handleGetOperation.js +35 -0
- package/lib/operations/handleGetSingleOperation.js +20 -0
- package/lib/operations/handleListOperation.js +67 -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 +22 -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 +13 -10
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Argument } from "commander";
|
|
2
|
+
import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
|
|
3
|
+
import { handleDescribeSingleOperation } from "../operations/handleDescribeSingleOperation.js";
|
|
4
|
+
import { localStorage } from "../helpers/localStorage.js";
|
|
5
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
6
|
+
import { cliError } from "../helpers/cliError.js";
|
|
7
|
+
export function createDescribeCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("describe")
|
|
10
|
+
.alias("d")
|
|
11
|
+
.description("Show detailed YAML description of an entry")
|
|
12
|
+
.addHelpText("after", `
|
|
13
|
+
Examples:
|
|
14
|
+
ay describe contacts 64a1b2c3d4e5 Describe a contact entry
|
|
15
|
+
ay describe tasks 64a1b2c3d4e5 comments Extract sub-resource array
|
|
16
|
+
ay describe tasks 64a1b2c3d4e5 comments -r json Sub-resource as JSON
|
|
17
|
+
ay describe tasks 64a1b2c3d4e5 --jq "subject" JMESPath on describe
|
|
18
|
+
ay d Describe last used entry`)
|
|
19
|
+
.addArgument(new Argument("[collection]", "The collection to use").default(localStorage.getItem("lastCollection"), `The last used collection (${localStorage.getItem("lastCollection")})`))
|
|
20
|
+
.addArgument(new Argument("[id]", "The ID of the entry to describe").default(localStorage.getItem("lastId"), `The last used id (${localStorage.getItem("lastId")})`))
|
|
21
|
+
.addArgument(new Argument("[subResource]", "Extract a sub-resource array (e.g., comments, attachments, worklogs)"))
|
|
22
|
+
.action(async (collection, id, subResource, options) => {
|
|
23
|
+
try {
|
|
24
|
+
if (!collection) {
|
|
25
|
+
cliError("Missing required argument: collection. Run a list or get command first, or provide it explicitly.", EXIT_MISUSE);
|
|
26
|
+
}
|
|
27
|
+
if (!id) {
|
|
28
|
+
cliError("Missing required argument: id. Provide an entry ID explicitly.", EXIT_MISUSE);
|
|
29
|
+
}
|
|
30
|
+
const opts = { ...program.opts(), ...options };
|
|
31
|
+
const module = getModuleFromCollection(collection);
|
|
32
|
+
localStorage.setItem("lastModule", module.module);
|
|
33
|
+
localStorage.setItem("lastCollection", collection);
|
|
34
|
+
localStorage.setItem("lastId", id);
|
|
35
|
+
await handleDescribeSingleOperation(module.module, collection, id, {
|
|
36
|
+
...opts,
|
|
37
|
+
subResource,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
cliError(e.message || "An unexpected error occurred", EXIT_GENERAL_ERROR);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Argument } from "commander";
|
|
2
|
+
import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
|
|
3
|
+
import { handleGetSingleOperation } from "../operations/handleGetSingleOperation.js";
|
|
4
|
+
import { handleEditOperation } from "../operations/handleEditOperation.js";
|
|
5
|
+
import { handleEditRawOperation } from "../operations/handleEditRawOperation.js";
|
|
6
|
+
import { localStorage } from "../helpers/localStorage.js";
|
|
7
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
8
|
+
import { cliError } from "../helpers/cliError.js";
|
|
9
|
+
export function createEditCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("edit")
|
|
12
|
+
.alias("e")
|
|
13
|
+
.description("Edit an entry by ID in a collection")
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Examples:
|
|
16
|
+
ay edit contacts 64a1b2c3d4e5 Edit contact by ID
|
|
17
|
+
ay edit Edit last used entry`)
|
|
18
|
+
.addArgument(new Argument("[collection]", "The collection to use").default(localStorage.getItem("lastCollection"), `The last used collection (${localStorage.getItem("lastCollection")})`))
|
|
19
|
+
.addArgument(new Argument("[id]", "The ID of the entry to edit").default(localStorage.getItem("lastId"), `The last used id (${localStorage.getItem("lastId")})`))
|
|
20
|
+
.action(async (collection, id, options) => {
|
|
21
|
+
try {
|
|
22
|
+
if (!collection) {
|
|
23
|
+
cliError("Missing required argument: collection. Run a list or get command first, or provide it explicitly.", EXIT_MISUSE);
|
|
24
|
+
}
|
|
25
|
+
if (!id) {
|
|
26
|
+
cliError("Missing required argument: id. Provide an entry ID explicitly.", EXIT_MISUSE);
|
|
27
|
+
}
|
|
28
|
+
const opts = { ...program.opts(), ...options };
|
|
29
|
+
const module = getModuleFromCollection(collection);
|
|
30
|
+
localStorage.setItem("lastModule", module.module);
|
|
31
|
+
localStorage.setItem("lastCollection", collection);
|
|
32
|
+
localStorage.setItem("lastId", id);
|
|
33
|
+
let result = {};
|
|
34
|
+
result = await handleGetSingleOperation(module.module, collection, id, opts);
|
|
35
|
+
// handleEditOperation expects {columns, rows} table structure;
|
|
36
|
+
// fall back to raw JSON editor if content doesn't have columns
|
|
37
|
+
if (result.content && result.content.columns) {
|
|
38
|
+
await handleEditOperation(module.module, collection, result.content);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
await handleEditRawOperation(module.module, collection, result.result || result.data);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
cliError(e.message || "An unexpected error occurred", EXIT_GENERAL_ERROR);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
import { secureStorage } from "../helpers/secureStorage.js";
|
|
3
|
+
import { decodeToken } from "../api/decodeToken.js";
|
|
4
|
+
import { customerSocket } from "../socket/customerSocketClient.js";
|
|
5
|
+
import { spinner } from "../../index.js";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
8
|
+
import { cliError } from "../helpers/cliError.js";
|
|
9
|
+
export function createEventsCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("events")
|
|
12
|
+
.alias("sub")
|
|
13
|
+
.description("Subscribe to filtered events via WebSocket")
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Examples:
|
|
16
|
+
ay events Listen to all events
|
|
17
|
+
ay events -c sales -a create Filter by category and action
|
|
18
|
+
ay sub -f yaml -c crm Subscribe to CRM events as YAML`)
|
|
19
|
+
.addOption(new Option("-f, --format <format>", "Set the output format")
|
|
20
|
+
.choices(["json", "yaml", "table"])
|
|
21
|
+
.default("json"))
|
|
22
|
+
.addOption(new Option("-c, --category <category>", "Set the category to listen").default("*"))
|
|
23
|
+
.addOption(new Option("-a, --action <action>", "Set the action to listen").default("*"))
|
|
24
|
+
.addOption(new Option("-l, --label <label>", "Set the label to listen").default("*"))
|
|
25
|
+
.addOption(new Option("-V, --value <value>", "Set the value to listen").default("*"))
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
try {
|
|
28
|
+
const tokenPayload = decodeToken(secureStorage.getItem("token"));
|
|
29
|
+
const user = tokenPayload.payload;
|
|
30
|
+
spinner.start({ text: `Starting stream with [${user._customerID}]` });
|
|
31
|
+
const socket = customerSocket(user);
|
|
32
|
+
spinner.update({ text: "Stream active" });
|
|
33
|
+
socket.on("event", (data) => {
|
|
34
|
+
if (options.category !== "*" && options.category !== data.category)
|
|
35
|
+
return;
|
|
36
|
+
if (options.action !== "*" && options.action !== data.action)
|
|
37
|
+
return;
|
|
38
|
+
if (options.label !== "*" && options.label !== data.label)
|
|
39
|
+
return;
|
|
40
|
+
if (options.value !== "*" && options.value !== data.value)
|
|
41
|
+
return;
|
|
42
|
+
spinner.update({
|
|
43
|
+
text: `Received [${data.category}.${data.action}.${data.label}.${data.value}]`,
|
|
44
|
+
});
|
|
45
|
+
if (options.format === "table") {
|
|
46
|
+
console.table(data.evt_data);
|
|
47
|
+
}
|
|
48
|
+
if (options.format === "yaml") {
|
|
49
|
+
console.log(yaml.dump(data.evt_data));
|
|
50
|
+
}
|
|
51
|
+
if (options.format === "json") {
|
|
52
|
+
console.log(JSON.stringify(data.evt_data, null, 2));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
cliError(e.message || "An unexpected error occurred", EXIT_GENERAL_ERROR);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { api, getModuleBaseUrl } from "../api/apiClient.js";
|
|
3
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
4
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
5
|
+
import { secureStorage } from "../helpers/secureStorage.js";
|
|
6
|
+
import { spinner } from "../../index.js";
|
|
7
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE, EXIT_PERMISSION_DENIED } from "../exitCodes.js";
|
|
8
|
+
import { cliError } from "../helpers/cliError.js";
|
|
9
|
+
async function resolveAction(searchTerm) {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
// Try su module first (full access), fall back to config module (limited access)
|
|
12
|
+
const modules = ["su", "config"];
|
|
13
|
+
let permissionDenied = false;
|
|
14
|
+
for (const mod of modules) {
|
|
15
|
+
try {
|
|
16
|
+
const token = secureStorage.getItem("token") || "";
|
|
17
|
+
const response = await api({
|
|
18
|
+
baseURL: getModuleBaseUrl(mod),
|
|
19
|
+
method: "get",
|
|
20
|
+
url: "ayouneapiactions",
|
|
21
|
+
params: { q: searchTerm, limit: 50, responseFormat: "json" },
|
|
22
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
23
|
+
});
|
|
24
|
+
const res = response.data;
|
|
25
|
+
if (!(res === null || res === void 0 ? void 0 : res.payload) || !Array.isArray(res.payload) || res.payload.length === 0) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const actions = res.payload.filter((a) => !a.deprecated);
|
|
29
|
+
// Exact operationId match
|
|
30
|
+
const exact = actions.find((a) => a.operationId === searchTerm);
|
|
31
|
+
if (exact)
|
|
32
|
+
return mapAction(exact);
|
|
33
|
+
// Partial operationId match
|
|
34
|
+
const partial = actions.find((a) => { var _a; return (_a = a.operationId) === null || _a === void 0 ? void 0 : _a.includes(searchTerm); });
|
|
35
|
+
if (partial)
|
|
36
|
+
return mapAction(partial);
|
|
37
|
+
// If multiple results, return first match
|
|
38
|
+
if (actions.length > 0)
|
|
39
|
+
return mapAction(actions[0]);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
// On 401/403 permission denied, try next module
|
|
43
|
+
if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) === 403 || ((_b = e.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
|
|
44
|
+
permissionDenied = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// For other errors (network, 500, etc.), only throw on last module
|
|
48
|
+
if (mod === modules[modules.length - 1])
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Distinguish between "not found" and "permission denied on all modules"
|
|
53
|
+
if (permissionDenied) {
|
|
54
|
+
throw new PermissionError("Permission denied: you do not have access to query API actions. Contact your administrator for the required rights.");
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
class PermissionError extends Error {
|
|
59
|
+
constructor(message) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.isPermissionError = true;
|
|
62
|
+
this.name = "PermissionError";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function mapAction(a) {
|
|
66
|
+
return {
|
|
67
|
+
host: a.host || "",
|
|
68
|
+
endpoint: a.endpoint || "",
|
|
69
|
+
method: (a.method || "GET").toUpperCase(),
|
|
70
|
+
operationId: a.operationId || "",
|
|
71
|
+
nameSpace: a.nameSpace || "",
|
|
72
|
+
capability: a.capability || "",
|
|
73
|
+
params: a.params || [],
|
|
74
|
+
query: a.query || [],
|
|
75
|
+
description: a.shortDescription || a.description || "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function buildUrl(endpoint, paramValues) {
|
|
79
|
+
let url = endpoint;
|
|
80
|
+
for (const [key, value] of Object.entries(paramValues)) {
|
|
81
|
+
url = url.replace(`:${key}`, encodeURIComponent(value));
|
|
82
|
+
}
|
|
83
|
+
return url;
|
|
84
|
+
}
|
|
85
|
+
export function createExecCommand(program) {
|
|
86
|
+
program
|
|
87
|
+
.command("exec <operationId>")
|
|
88
|
+
.alias("x")
|
|
89
|
+
.description("Execute any registered API action by operationId")
|
|
90
|
+
.addHelpText("after", `
|
|
91
|
+
Examples:
|
|
92
|
+
ay exec ayoune.ai.conversations.create.one --body '{"prompt":"Hello"}'
|
|
93
|
+
ay exec ayoune.crm.contacts.list
|
|
94
|
+
ay exec ayoune.pm.projects.create.one --body '{"subject":"Migration"}'
|
|
95
|
+
ay exec ayoune.ai.prompts.list -r table
|
|
96
|
+
ay exec ayoune.sale.orders.get.one --param id=507f1f77bcf86cd799439011`)
|
|
97
|
+
.option("--body <json>", "Request body as JSON string")
|
|
98
|
+
.option("--body-file <path>", "Read request body from file")
|
|
99
|
+
.option("--body-stdin", "Read request body from stdin (for piping)")
|
|
100
|
+
.option("--param <kv...>", "Path parameters as key=value pairs")
|
|
101
|
+
.option("--query <kv...>", "Query parameters as key=value pairs")
|
|
102
|
+
.action(async (operationId, options) => {
|
|
103
|
+
try {
|
|
104
|
+
const opts = { ...program.opts(), ...options };
|
|
105
|
+
spinner.start({ text: `Resolving action: ${operationId}`, color: "magenta" });
|
|
106
|
+
const action = await resolveAction(operationId);
|
|
107
|
+
if (!action) {
|
|
108
|
+
cliError(`No API action found for: ${operationId}`, EXIT_MISUSE);
|
|
109
|
+
}
|
|
110
|
+
spinner.update({ text: `Executing: ${action.method} https://${action.host}${action.endpoint}` });
|
|
111
|
+
// Parse path params
|
|
112
|
+
const paramValues = {};
|
|
113
|
+
if (opts.param) {
|
|
114
|
+
for (const p of opts.param) {
|
|
115
|
+
const [key, ...rest] = p.split("=");
|
|
116
|
+
paramValues[key] = rest.join("=");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Parse query params
|
|
120
|
+
const queryParams = {
|
|
121
|
+
responseFormat: opts.responseFormat,
|
|
122
|
+
verbosity: opts.verbosity,
|
|
123
|
+
};
|
|
124
|
+
if (opts.hideMeta)
|
|
125
|
+
queryParams.hideMeta = "true";
|
|
126
|
+
if (opts.query) {
|
|
127
|
+
for (const q of opts.query) {
|
|
128
|
+
const [key, ...rest] = q.split("=");
|
|
129
|
+
queryParams[key] = rest.join("=");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Parse body
|
|
133
|
+
let body = null;
|
|
134
|
+
if (opts.body) {
|
|
135
|
+
try {
|
|
136
|
+
body = JSON.parse(opts.body);
|
|
137
|
+
}
|
|
138
|
+
catch (_a) {
|
|
139
|
+
cliError("Invalid JSON in --body", EXIT_MISUSE);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (opts.bodyFile) {
|
|
143
|
+
const fs = await import("fs");
|
|
144
|
+
const content = fs.readFileSync(opts.bodyFile, "utf-8");
|
|
145
|
+
try {
|
|
146
|
+
body = JSON.parse(content);
|
|
147
|
+
}
|
|
148
|
+
catch (_b) {
|
|
149
|
+
cliError(`Invalid JSON in file: ${opts.bodyFile}`, EXIT_MISUSE);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (opts.bodyStdin && !process.stdin.isTTY) {
|
|
153
|
+
const chunks = [];
|
|
154
|
+
for await (const chunk of process.stdin) {
|
|
155
|
+
chunks.push(chunk);
|
|
156
|
+
}
|
|
157
|
+
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
158
|
+
if (stdinContent) {
|
|
159
|
+
try {
|
|
160
|
+
body = JSON.parse(stdinContent);
|
|
161
|
+
}
|
|
162
|
+
catch (_c) {
|
|
163
|
+
cliError("Invalid JSON from stdin", EXIT_MISUSE);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Build final URL
|
|
168
|
+
const url = buildUrl(action.endpoint, paramValues);
|
|
169
|
+
const fullUrl = `https://${action.host}${url}`;
|
|
170
|
+
// Dry-run support
|
|
171
|
+
if (opts.dryRun && action.method !== "GET") {
|
|
172
|
+
spinner.stop();
|
|
173
|
+
console.error(chalk.yellow.bold("\n [DRY RUN] Request not sent:\n"));
|
|
174
|
+
console.error(` ${chalk.dim("Operation:")} ${chalk.cyan(action.operationId)}`);
|
|
175
|
+
console.error(` ${chalk.dim("Method:")} ${chalk.cyan(action.method)}`);
|
|
176
|
+
console.error(` ${chalk.dim("URL:")} ${chalk.cyan(fullUrl)}`);
|
|
177
|
+
if (body) {
|
|
178
|
+
console.error(` ${chalk.dim("Body:")} ${JSON.stringify(body, null, 2).split("\n").join("\n ")}`);
|
|
179
|
+
}
|
|
180
|
+
console.error();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Execute the request directly against the service host
|
|
184
|
+
const response = await api({
|
|
185
|
+
baseURL: `https://${action.host}`,
|
|
186
|
+
method: action.method.toLowerCase(),
|
|
187
|
+
url,
|
|
188
|
+
data: body,
|
|
189
|
+
params: queryParams,
|
|
190
|
+
headers: {
|
|
191
|
+
Authorization: `Bearer ${secureStorage.getItem("token")}`,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const res = response.data;
|
|
195
|
+
const { plainResult, result, content } = handleResponseFormatOptions(opts, res);
|
|
196
|
+
const payloadCount = Array.isArray(res === null || res === void 0 ? void 0 : res.payload) ? res.payload.length : 1;
|
|
197
|
+
spinner.success({
|
|
198
|
+
text: `${action.method} ${action.operationId} — ${payloadCount} result(s)`,
|
|
199
|
+
});
|
|
200
|
+
spinner.stop();
|
|
201
|
+
if (opts.save) {
|
|
202
|
+
await saveFile("exec", opts, res);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
if (e instanceof PermissionError) {
|
|
207
|
+
cliError(e.message, EXIT_PERMISSION_DENIED);
|
|
208
|
+
}
|
|
209
|
+
cliError(e.message || "Failed to execute API action", EXIT_GENERAL_ERROR);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
|
|
2
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
3
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
4
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
5
|
+
import { spinner } from "../../index.js";
|
|
6
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
7
|
+
import { cliError } from "../helpers/cliError.js";
|
|
8
|
+
import { sanitizeFields } from "../helpers/sanitizeFields.js";
|
|
9
|
+
export function createExportCommand(program) {
|
|
10
|
+
const exp = program
|
|
11
|
+
.command("export")
|
|
12
|
+
.alias("exp")
|
|
13
|
+
.description("Export data from collections in various formats");
|
|
14
|
+
// ay export run <collection>
|
|
15
|
+
exp
|
|
16
|
+
.command("run <collection>")
|
|
17
|
+
.description("Export entries from a collection")
|
|
18
|
+
.addHelpText("after", `
|
|
19
|
+
Examples:
|
|
20
|
+
ay export run contacts --format csv --fields "firstName,lastName,email"
|
|
21
|
+
ay export run products --format json --filter "status=active"
|
|
22
|
+
ay export run invoices --format csv --filter "createdAt>2025-01-01" --save`)
|
|
23
|
+
.option("--format <format>", "Export format (json, csv, yaml)", "csv")
|
|
24
|
+
.option("--fields <fields>", "Comma-separated fields to include in export")
|
|
25
|
+
.option("--filter <filters>", "Comma-separated key=value filters")
|
|
26
|
+
.option("--sort <field>", "Sort by field (prefix with - for descending)", "-createdAt")
|
|
27
|
+
.option("-l, --limit <number>", "Limit results (0 = all)", parseInt, 0)
|
|
28
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
29
|
+
.action(async (collection, options) => {
|
|
30
|
+
var _a, _b, _c, _d, _e;
|
|
31
|
+
try {
|
|
32
|
+
const opts = { ...program.opts(), ...options };
|
|
33
|
+
const module = getModuleFromCollection(collection);
|
|
34
|
+
spinner.start({ text: `Exporting ${collection}...`, color: "magenta" });
|
|
35
|
+
const params = {
|
|
36
|
+
page: opts.page,
|
|
37
|
+
sort: opts.sort,
|
|
38
|
+
responseFormat: opts.format,
|
|
39
|
+
verbosity: opts.verbosity,
|
|
40
|
+
};
|
|
41
|
+
if (opts.limit > 0)
|
|
42
|
+
params.limit = opts.limit;
|
|
43
|
+
if (opts.fields)
|
|
44
|
+
params.fields = sanitizeFields(opts.fields).join(",");
|
|
45
|
+
// Parse filters
|
|
46
|
+
if (opts.filter) {
|
|
47
|
+
const filters = opts.filter.split(",");
|
|
48
|
+
for (const f of filters) {
|
|
49
|
+
const match = f.match(/^(\w+)(!=|>=|<=|>|<|=)(.+)$/);
|
|
50
|
+
if (match) {
|
|
51
|
+
const [, key, op, value] = match;
|
|
52
|
+
if (op === "=")
|
|
53
|
+
params[key] = value;
|
|
54
|
+
else if (op === "!=")
|
|
55
|
+
params[`${key}[ne]`] = value;
|
|
56
|
+
else if (op === ">")
|
|
57
|
+
params[`${key}[gt]`] = value;
|
|
58
|
+
else if (op === "<")
|
|
59
|
+
params[`${key}[lt]`] = value;
|
|
60
|
+
else if (op === ">=")
|
|
61
|
+
params[`${key}[gte]`] = value;
|
|
62
|
+
else if (op === "<=")
|
|
63
|
+
params[`${key}[lte]`] = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// If limit=0, fetch all pages
|
|
68
|
+
if (opts.limit === 0) {
|
|
69
|
+
params.limit = 500;
|
|
70
|
+
const allPayload = [];
|
|
71
|
+
let currentPage = 1;
|
|
72
|
+
let totalPages = 1;
|
|
73
|
+
do {
|
|
74
|
+
params.page = currentPage;
|
|
75
|
+
const res = await apiCallHandler(module.module, collection.toLowerCase(), "get", null, params);
|
|
76
|
+
if (res === null || res === void 0 ? void 0 : res.payload) {
|
|
77
|
+
if (Array.isArray(res.payload))
|
|
78
|
+
allPayload.push(...res.payload);
|
|
79
|
+
else
|
|
80
|
+
allPayload.push(res.payload);
|
|
81
|
+
}
|
|
82
|
+
totalPages = (_c = (_b = (_a = res === null || res === void 0 ? void 0 : res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalPages) !== null && _c !== void 0 ? _c : 1;
|
|
83
|
+
spinner.update({ text: `Exporting ${collection}... page ${currentPage}/${totalPages}` });
|
|
84
|
+
currentPage++;
|
|
85
|
+
} while (currentPage <= totalPages);
|
|
86
|
+
const fullRes = {
|
|
87
|
+
payload: allPayload,
|
|
88
|
+
meta: { pageInfo: { totalEntries: allPayload.length, page: 1, totalPages: 1 } },
|
|
89
|
+
};
|
|
90
|
+
// Force response format to match export format
|
|
91
|
+
opts.responseFormat = opts.format;
|
|
92
|
+
handleResponseFormatOptions(opts, fullRes);
|
|
93
|
+
spinner.success({ text: `Exported ${allPayload.length} entries from ${collection}` });
|
|
94
|
+
spinner.stop();
|
|
95
|
+
if (opts.save)
|
|
96
|
+
await saveFile(`export-${collection}`, opts, fullRes);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const res = await apiCallHandler(module.module, collection.toLowerCase(), "get", null, params);
|
|
100
|
+
opts.responseFormat = opts.format;
|
|
101
|
+
const { result: expResult, meta: expMeta } = handleResponseFormatOptions(opts, res);
|
|
102
|
+
const total = (_e = (_d = expMeta === null || expMeta === void 0 ? void 0 : expMeta.pageInfo) === null || _d === void 0 ? void 0 : _d.totalEntries) !== null && _e !== void 0 ? _e : (Array.isArray(expResult) ? expResult.length : 0);
|
|
103
|
+
spinner.success({ text: `Exported ${total} entries from ${collection}` });
|
|
104
|
+
spinner.stop();
|
|
105
|
+
if (opts.save)
|
|
106
|
+
await saveFile(`export-${collection}`, opts, res);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
cliError(e.message || "Export failed", EXIT_GENERAL_ERROR);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
// ay export list
|
|
114
|
+
exp
|
|
115
|
+
.command("list")
|
|
116
|
+
.alias("ls")
|
|
117
|
+
.description("List previous exports")
|
|
118
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
119
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
var _a, _b;
|
|
122
|
+
try {
|
|
123
|
+
const opts = { ...program.opts(), ...options };
|
|
124
|
+
spinner.start({ text: "Fetching exports...", color: "magenta" });
|
|
125
|
+
const res = await apiCallHandler("export", "exports", "get", null, {
|
|
126
|
+
page: opts.page,
|
|
127
|
+
limit: opts.limit,
|
|
128
|
+
responseFormat: opts.responseFormat,
|
|
129
|
+
verbosity: opts.verbosity,
|
|
130
|
+
});
|
|
131
|
+
const { result: listResult, meta: listMeta } = handleResponseFormatOptions(opts, res);
|
|
132
|
+
const total = (_b = (_a = listMeta === null || listMeta === void 0 ? void 0 : listMeta.pageInfo) === null || _a === void 0 ? void 0 : _a.totalEntries) !== null && _b !== void 0 ? _b : (Array.isArray(listResult) ? listResult.length : 0);
|
|
133
|
+
spinner.success({ text: `Found ${total} exports` });
|
|
134
|
+
spinner.stop();
|
|
135
|
+
if (opts.save)
|
|
136
|
+
await saveFile("exports-list", opts, res);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
cliError(e.message || "Failed to list exports", EXIT_GENERAL_ERROR);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// ay export get <id>
|
|
143
|
+
exp
|
|
144
|
+
.command("get <id>")
|
|
145
|
+
.description("Get export details and download URL")
|
|
146
|
+
.action(async (id, options) => {
|
|
147
|
+
try {
|
|
148
|
+
const opts = { ...program.opts(), ...options };
|
|
149
|
+
spinner.start({ text: `Fetching export ${id}...`, color: "magenta" });
|
|
150
|
+
const res = await apiCallHandler("export", `exports/${id}`, "get", null, {
|
|
151
|
+
responseFormat: opts.responseFormat,
|
|
152
|
+
verbosity: opts.verbosity,
|
|
153
|
+
});
|
|
154
|
+
handleResponseFormatOptions(opts, res);
|
|
155
|
+
spinner.success({ text: `Export ${id} loaded` });
|
|
156
|
+
spinner.stop();
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
cliError(e.message || "Failed to get export", EXIT_GENERAL_ERROR);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// ay export configs
|
|
163
|
+
exp
|
|
164
|
+
.command("configs")
|
|
165
|
+
.description("List export configurations")
|
|
166
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 50)
|
|
167
|
+
.action(async (options) => {
|
|
168
|
+
var _a, _b;
|
|
169
|
+
try {
|
|
170
|
+
const opts = { ...program.opts(), ...options };
|
|
171
|
+
spinner.start({ text: "Fetching export configs...", color: "magenta" });
|
|
172
|
+
const res = await apiCallHandler("export", "exportconfigs", "get", null, {
|
|
173
|
+
limit: opts.limit,
|
|
174
|
+
responseFormat: opts.responseFormat,
|
|
175
|
+
verbosity: opts.verbosity,
|
|
176
|
+
});
|
|
177
|
+
const { result: cfgResult, meta: cfgMeta } = handleResponseFormatOptions(opts, res);
|
|
178
|
+
const total = (_b = (_a = cfgMeta === null || cfgMeta === void 0 ? void 0 : cfgMeta.pageInfo) === null || _a === void 0 ? void 0 : _a.totalEntries) !== null && _b !== void 0 ? _b : (Array.isArray(cfgResult) ? cfgResult.length : 0);
|
|
179
|
+
spinner.success({ text: `Found ${total} export configurations` });
|
|
180
|
+
spinner.stop();
|
|
181
|
+
if (opts.save)
|
|
182
|
+
await saveFile("export-configs", opts, res);
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
cliError(e.message || "Failed to list export configs", EXIT_GENERAL_ERROR);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
// ay export logs
|
|
189
|
+
exp
|
|
190
|
+
.command("logs")
|
|
191
|
+
.description("List export logs")
|
|
192
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
193
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
194
|
+
.action(async (options) => {
|
|
195
|
+
var _a, _b;
|
|
196
|
+
try {
|
|
197
|
+
const opts = { ...program.opts(), ...options };
|
|
198
|
+
spinner.start({ text: "Fetching export logs...", color: "magenta" });
|
|
199
|
+
const res = await apiCallHandler("export", "exportlogs", "get", null, {
|
|
200
|
+
page: opts.page,
|
|
201
|
+
limit: opts.limit,
|
|
202
|
+
responseFormat: opts.responseFormat,
|
|
203
|
+
verbosity: opts.verbosity,
|
|
204
|
+
});
|
|
205
|
+
const { result: logResult, meta: logMeta } = handleResponseFormatOptions(opts, res);
|
|
206
|
+
const total = (_b = (_a = logMeta === null || logMeta === void 0 ? void 0 : logMeta.pageInfo) === null || _a === void 0 ? void 0 : _a.totalEntries) !== null && _b !== void 0 ? _b : (Array.isArray(logResult) ? logResult.length : 0);
|
|
207
|
+
spinner.success({ text: `Found ${total} export log entries` });
|
|
208
|
+
spinner.stop();
|
|
209
|
+
if (opts.save)
|
|
210
|
+
await saveFile("export-logs", opts, res);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
cliError(e.message || "Failed to list export logs", EXIT_GENERAL_ERROR);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { parseInteger } from "../helpers/parseInt.js";
|
|
2
|
+
import { promptCollectionWithModule } from "../prompts/promptCollectionWithModule.js";
|
|
3
|
+
import { resolveCollectionArgs } from "../helpers/resolveCollectionArgs.js";
|
|
4
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
5
|
+
import { handleGetOperation } from "../operations/handleGetOperation.js";
|
|
6
|
+
import { localStorage } from "../helpers/localStorage.js";
|
|
7
|
+
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
8
|
+
import { cliError } from "../helpers/cliError.js";
|
|
9
|
+
export function createGetCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("get [collectionOrModule] [collection]")
|
|
12
|
+
.alias("g")
|
|
13
|
+
.description("Retrieve entries from a collection with field selection")
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Examples:
|
|
16
|
+
ay get contacts Get contacts with default fields
|
|
17
|
+
ay get crm consumers Get consumers (explicit module)
|
|
18
|
+
ay get products -i name price Get only name and price fields
|
|
19
|
+
ay get orders -p 3 -l 10 -r csv Get orders page 3, 10 per page, as CSV`)
|
|
20
|
+
.option("-p, --page <number>", "Page", parseInteger, 1)
|
|
21
|
+
.option("-l, --limit <number>", "Limit", parseInteger, 20)
|
|
22
|
+
.option("-f, --from <date>", "From date")
|
|
23
|
+
.option("-i, --fields <fields...>", "Fields to get")
|
|
24
|
+
.action(async (collectionOrModule, collection, options) => {
|
|
25
|
+
try {
|
|
26
|
+
const opts = { ...program.opts(), ...options };
|
|
27
|
+
if (!collectionOrModule) {
|
|
28
|
+
if (!process.stdin.isTTY) {
|
|
29
|
+
cliError("Missing required argument: collection", EXIT_MISUSE);
|
|
30
|
+
}
|
|
31
|
+
collectionOrModule = await promptCollectionWithModule();
|
|
32
|
+
}
|
|
33
|
+
const resolved = resolveCollectionArgs(collectionOrModule, collection);
|
|
34
|
+
localStorage.setItem("lastModule", resolved.module);
|
|
35
|
+
localStorage.setItem("lastCollection", resolved.collection);
|
|
36
|
+
let result = {};
|
|
37
|
+
result = await handleGetOperation(resolved.module, resolved.collection, opts);
|
|
38
|
+
if (opts.save) {
|
|
39
|
+
await saveFile("get", opts, result);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
cliError(e.message || "An unexpected error occurred", EXIT_GENERAL_ERROR);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|