@tolinax/ayoune-cli 2026.3.1 → 2026.5.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 +217 -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 +160 -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 +50 -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,217 @@
|
|
|
1
|
+
//Defines the main program structure for command line interface
|
|
2
|
+
import { Command, Option } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import { spinner } from "../../index.js";
|
|
7
|
+
import { createModulesCommand } from "./createModulesCommand.js";
|
|
8
|
+
import { createListCommand } from "./createListCommand.js";
|
|
9
|
+
import { createGetCommand } from "./createGetCommand.js";
|
|
10
|
+
import { createEditCommand } from "./createEditCommand.js";
|
|
11
|
+
import { createLoginCommand } from "./createLoginCommand.js";
|
|
12
|
+
import { createDescribeCommand } from "./createDescribeCommand.js";
|
|
13
|
+
import { createCopyCommand } from "./createCopyCommand.js";
|
|
14
|
+
import { createCreateCommand } from "./createCreateCommand.js";
|
|
15
|
+
import { createStorageCommand } from "./createStorageCommand.js";
|
|
16
|
+
import { createAuditCommand } from "./createAuditCommand.js";
|
|
17
|
+
import { createStreamCommand } from "./createStreamCommand.js";
|
|
18
|
+
import { createEventsCommand } from "./createEventsCommand.js";
|
|
19
|
+
import { createWhoAmICommand } from "./createWhoAmICommand.js";
|
|
20
|
+
import { createLogoutCommand } from "./createLogoutCommand.js";
|
|
21
|
+
import { createCompletionsCommand } from "./createCompletionsCommand.js";
|
|
22
|
+
import { createAliasCommand, registerUserAliases } from "./createAliasCommand.js";
|
|
23
|
+
import { createConfigCommand } from "./createConfigCommand.js";
|
|
24
|
+
import { createActionsCommand } from "./createActionsCommand.js";
|
|
25
|
+
import { createExecCommand } from "./createExecCommand.js";
|
|
26
|
+
import { createAiCommand } from "./createAiCommand.js";
|
|
27
|
+
import { enableDebug } from "../api/apiClient.js";
|
|
28
|
+
import { enableDryRun } from "../api/apiCallHandler.js";
|
|
29
|
+
import { setJsonErrorsEnabled } from "../helpers/cliError.js";
|
|
30
|
+
import { disableContext } from "../helpers/contextInjector.js";
|
|
31
|
+
import { createServicesCommand } from "./createServicesCommand.js";
|
|
32
|
+
import { createDeployCommand } from "./createDeployCommand.js";
|
|
33
|
+
import { createMonitorCommand } from "./createMonitorCommand.js";
|
|
34
|
+
import { createDeleteCommand } from "./createDeleteCommand.js";
|
|
35
|
+
import { createUpdateCommand } from "./createUpdateCommand.js";
|
|
36
|
+
import { createBatchCommand } from "./createBatchCommand.js";
|
|
37
|
+
import { createSearchCommand } from "./createSearchCommand.js";
|
|
38
|
+
import { createWebhooksCommand } from "./createWebhooksCommand.js";
|
|
39
|
+
import { createJobsCommand } from "./createJobsCommand.js";
|
|
40
|
+
import { createExportCommand } from "./createExportCommand.js";
|
|
41
|
+
import { createUsersCommand } from "./createUsersCommand.js";
|
|
42
|
+
import { createSyncCommand } from "./createSyncCommand.js";
|
|
43
|
+
import { createPermissionsCommand } from "./createPermissionsCommand.js";
|
|
44
|
+
import { createTemplateCommand } from "./createTemplateCommand.js";
|
|
45
|
+
import { createSetupCommand } from "./createSetupCommand.js";
|
|
46
|
+
import { createStatusCommand } from "./createStatusCommand.js";
|
|
47
|
+
import { createSelfHostUpdateCommand } from "./createSelfHostUpdateCommand.js";
|
|
48
|
+
import { createContextCommand } from "./createContextCommand.js";
|
|
49
|
+
import { createAccessCommand } from "./createAccessCommand.js";
|
|
50
|
+
import { secureStorage } from "../helpers/secureStorage.js";
|
|
51
|
+
import { login } from "../api/login.js";
|
|
52
|
+
import { loadConfig } from "../helpers/configLoader.js";
|
|
53
|
+
import { getLogo, getDescription, BRAND_BLUE } from "../helpers/logo.js";
|
|
54
|
+
import { checkForUpdates } from "../helpers/updateNotifier.js";
|
|
55
|
+
import { createRequire } from "module";
|
|
56
|
+
const require = createRequire(import.meta.url);
|
|
57
|
+
let pkg;
|
|
58
|
+
try {
|
|
59
|
+
pkg = require("../../package.json"); // dist/ and npm install
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
pkg = require("../../../package.json"); // source tree (tests)
|
|
63
|
+
}
|
|
64
|
+
export function createProgram(program) {
|
|
65
|
+
program
|
|
66
|
+
.version(pkg.version || "0.0.0")
|
|
67
|
+
.addOption(new Option("-r, --responseFormat <format>", "Set the output format")
|
|
68
|
+
.choices(["json", "csv", "yaml", "table"])
|
|
69
|
+
.default("json"))
|
|
70
|
+
.addOption(new Option("-v, --verbosity <level>", "Set the verbosity level of the returned meta information")
|
|
71
|
+
.choices(["default", "extended", "minimal"])
|
|
72
|
+
.default("default")
|
|
73
|
+
.conflicts("hideMeta"))
|
|
74
|
+
.addOption(new Option("-m, --hideMeta", "Returns only the payload without meta information. ")
|
|
75
|
+
.default(false)
|
|
76
|
+
.conflicts("verbosity"))
|
|
77
|
+
.addOption(new Option("-s, --save", "Saves the response as file"))
|
|
78
|
+
.addOption(new Option("-d, --debug", "Show detailed request/response information"))
|
|
79
|
+
.addOption(new Option("-o, --outPath [filePath]", "Lets you set a path").default(path.join(os.homedir(), "aYOUne")))
|
|
80
|
+
.addOption(new Option("-n, --name [fileName]", "Lets you set a filename"))
|
|
81
|
+
.addOption(new Option("-q, --quiet", "Suppress all output except errors and results"))
|
|
82
|
+
.addOption(new Option("--force", "Skip confirmation prompts for destructive actions"))
|
|
83
|
+
.addOption(new Option("--dry-run", "Preview what a command would do without executing"))
|
|
84
|
+
.addOption(new Option("--jq <expression>", "Filter JSON output using JMESPath expression"))
|
|
85
|
+
.addOption(new Option("--columns <fields>", "Comma-separated list of columns to display"))
|
|
86
|
+
.addOption(new Option("--no-color", "Disable colored output"))
|
|
87
|
+
.addOption(new Option("--json-errors", "Output errors as JSON to stderr (for AI agents/scripts)"))
|
|
88
|
+
.addOption(new Option("--token <jwt>", "Authenticate with a JWT token for this session"))
|
|
89
|
+
.addOption(new Option("--no-context", "Disable context injection for this command"))
|
|
90
|
+
.description(getDescription());
|
|
91
|
+
program.showHelpAfterError();
|
|
92
|
+
program.showSuggestionAfterError(true);
|
|
93
|
+
createModulesCommand(program);
|
|
94
|
+
createListCommand(program);
|
|
95
|
+
createGetCommand(program);
|
|
96
|
+
createEditCommand(program);
|
|
97
|
+
createCopyCommand(program);
|
|
98
|
+
createDescribeCommand(program);
|
|
99
|
+
createCreateCommand(program);
|
|
100
|
+
createStorageCommand(program);
|
|
101
|
+
createAuditCommand(program);
|
|
102
|
+
createStreamCommand(program);
|
|
103
|
+
createEventsCommand(program);
|
|
104
|
+
createWhoAmICommand(program);
|
|
105
|
+
createLogoutCommand(program);
|
|
106
|
+
createActionsCommand(program);
|
|
107
|
+
createExecCommand(program);
|
|
108
|
+
createAiCommand(program);
|
|
109
|
+
createServicesCommand(program);
|
|
110
|
+
createDeployCommand(program);
|
|
111
|
+
createMonitorCommand(program);
|
|
112
|
+
createDeleteCommand(program);
|
|
113
|
+
createUpdateCommand(program);
|
|
114
|
+
createBatchCommand(program);
|
|
115
|
+
createSearchCommand(program);
|
|
116
|
+
createWebhooksCommand(program);
|
|
117
|
+
createJobsCommand(program);
|
|
118
|
+
createExportCommand(program);
|
|
119
|
+
createUsersCommand(program);
|
|
120
|
+
createSyncCommand(program);
|
|
121
|
+
createPermissionsCommand(program);
|
|
122
|
+
createTemplateCommand(program);
|
|
123
|
+
createAccessCommand(program);
|
|
124
|
+
createSetupCommand(program);
|
|
125
|
+
createStatusCommand(program);
|
|
126
|
+
createSelfHostUpdateCommand(program);
|
|
127
|
+
createContextCommand(program);
|
|
128
|
+
createCompletionsCommand(program);
|
|
129
|
+
createAliasCommand(program);
|
|
130
|
+
createConfigCommand(program);
|
|
131
|
+
registerUserAliases(program);
|
|
132
|
+
createLoginCommand(program);
|
|
133
|
+
program.addHelpText("beforeAll", getLogo());
|
|
134
|
+
program.addHelpText("afterAll", chalk.dim("\n Run ay <command> --help for more info\n"));
|
|
135
|
+
program.configureHelp({
|
|
136
|
+
sortOptions: true,
|
|
137
|
+
sortSubcommands: true,
|
|
138
|
+
showGlobalOptions: true,
|
|
139
|
+
formatHelp: (cmd, helper) => {
|
|
140
|
+
const defaultHelp = Command.prototype.createHelp().formatHelp(cmd, helper);
|
|
141
|
+
return defaultHelp.replace(/^(Usage:|Commands:|Options:|Global Options:)/gm, (match) => chalk.hex(BRAND_BLUE).bold(match));
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
// Respect NO_COLOR env var (no-color.org standard)
|
|
145
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
146
|
+
chalk.level = 0;
|
|
147
|
+
}
|
|
148
|
+
program.hook("preAction", async (thisCommand) => {
|
|
149
|
+
var _a;
|
|
150
|
+
// Apply saved defaults — only when the user didn't pass the flag explicitly
|
|
151
|
+
const config = loadConfig();
|
|
152
|
+
const defaults = (_a = config.defaults) !== null && _a !== void 0 ? _a : {};
|
|
153
|
+
const rawArgs = process.argv.slice(2);
|
|
154
|
+
if (defaults.responseFormat && !rawArgs.some(a => ["-r", "--responseFormat"].includes(a)))
|
|
155
|
+
program.setOptionValue("responseFormat", defaults.responseFormat);
|
|
156
|
+
if (defaults.verbosity && !rawArgs.some(a => ["-v", "--verbosity"].includes(a)))
|
|
157
|
+
program.setOptionValue("verbosity", defaults.verbosity);
|
|
158
|
+
if (defaults.outPath && !rawArgs.some(a => ["-o", "--outPath"].includes(a)))
|
|
159
|
+
program.setOptionValue("outPath", defaults.outPath);
|
|
160
|
+
if (defaults.hideMeta && !rawArgs.some(a => ["-m", "--hideMeta"].includes(a)))
|
|
161
|
+
program.setOptionValue("hideMeta", defaults.hideMeta);
|
|
162
|
+
if (defaults.quiet && !rawArgs.some(a => ["-q", "--quiet"].includes(a)))
|
|
163
|
+
program.setOptionValue("quiet", defaults.quiet);
|
|
164
|
+
if (defaults.force && !rawArgs.some(a => ["--force"].includes(a)))
|
|
165
|
+
program.setOptionValue("force", defaults.force);
|
|
166
|
+
if (defaults.dryRun && !rawArgs.some(a => ["--dry-run"].includes(a)))
|
|
167
|
+
program.setOptionValue("dryRun", defaults.dryRun);
|
|
168
|
+
const opts = program.opts();
|
|
169
|
+
if (opts.color === false) {
|
|
170
|
+
chalk.level = 0;
|
|
171
|
+
}
|
|
172
|
+
if (opts.debug) {
|
|
173
|
+
enableDebug();
|
|
174
|
+
}
|
|
175
|
+
if (opts.dryRun) {
|
|
176
|
+
enableDryRun();
|
|
177
|
+
}
|
|
178
|
+
if (opts.jsonErrors) {
|
|
179
|
+
setJsonErrorsEnabled(true);
|
|
180
|
+
}
|
|
181
|
+
if (opts.context === false) {
|
|
182
|
+
disableContext();
|
|
183
|
+
}
|
|
184
|
+
if (opts.quiet) {
|
|
185
|
+
spinner.start = () => spinner;
|
|
186
|
+
spinner.update = () => spinner;
|
|
187
|
+
spinner.success = () => spinner;
|
|
188
|
+
spinner.stop = () => spinner;
|
|
189
|
+
// spinner.error left intact
|
|
190
|
+
}
|
|
191
|
+
// Token from --token flag (highest priority), then env var, then stored token
|
|
192
|
+
if (opts.token) {
|
|
193
|
+
secureStorage.setItem("token", opts.token);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const envToken = process.env.AYOUNE_TOKEN;
|
|
197
|
+
if (envToken && !secureStorage.getItem("token")) {
|
|
198
|
+
secureStorage.setItem("token", envToken);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// First-run onboarding: auto-login if no token stored
|
|
202
|
+
const cmdName = thisCommand.name();
|
|
203
|
+
const skipAuth = ["login", "logout", "whoami", "completions", "alias", "config", "help", "setup", "status", "self-host-update", "context"];
|
|
204
|
+
if (!skipAuth.includes(cmdName) && process.stdin.isTTY) {
|
|
205
|
+
const token = secureStorage.getItem("token");
|
|
206
|
+
if (!token) {
|
|
207
|
+
console.error(chalk.hex('#2B8DC6').bold("\n Welcome to aYOUne CLI!\n"));
|
|
208
|
+
console.error(chalk.dim(" You need to authenticate before using this command.\n"));
|
|
209
|
+
await login();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Non-blocking update check (throttled, silent on errors)
|
|
213
|
+
checkForUpdates(pkg.version || "0.0.0");
|
|
214
|
+
});
|
|
215
|
+
//Parse command line arguments
|
|
216
|
+
program.parse(process.argv);
|
|
217
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { resolveCollectionArgs } from "../helpers/resolveCollectionArgs.js";
|
|
2
|
+
import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
|
|
3
|
+
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
4
|
+
import { searchModel, searchOne, searchGlobal } from "../api/searchClient.js";
|
|
5
|
+
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
6
|
+
import { saveFile } from "../helpers/saveFile.js";
|
|
7
|
+
import { localStorage } from "../helpers/localStorage.js";
|
|
8
|
+
import { spinner } from "../../index.js";
|
|
9
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
10
|
+
import { cliError } from "../helpers/cliError.js";
|
|
11
|
+
import { sanitizeFields } from "../helpers/sanitizeFields.js";
|
|
12
|
+
import { getContextFilterParams } from "../helpers/contextInjector.js";
|
|
13
|
+
export function createSearchCommand(program) {
|
|
14
|
+
program
|
|
15
|
+
.command("search [collectionOrModule] [collectionOrQuery] [query]")
|
|
16
|
+
.alias("find")
|
|
17
|
+
.description("Search entries via the aYOUne Search Service")
|
|
18
|
+
.addHelpText("after", `
|
|
19
|
+
Examples:
|
|
20
|
+
ay search consumers "John" Search via search service
|
|
21
|
+
ay search crm consumers "John" Search with explicit module
|
|
22
|
+
ay search -g "John" Global search across all collections (SSE)
|
|
23
|
+
ay search consumers "John" --one Find first match
|
|
24
|
+
ay search consumers "John" --field name Search specific field
|
|
25
|
+
ay search contacts --filter "status=active" Filter by field
|
|
26
|
+
ay search orders --filter "status=paid" --sort "-createdAt"
|
|
27
|
+
ay search consumers "John" --legacy Use legacy module API
|
|
28
|
+
ay find invoices --filter "total>500" --count Just count matches`)
|
|
29
|
+
.option("-g, --global <query>", "Global search across all collections (SSE streaming)")
|
|
30
|
+
.option("--field <name>", "Search in a specific field")
|
|
31
|
+
.option("--one", "Return only the first matching entry", false)
|
|
32
|
+
.option("--legacy", "Use legacy module API instead of search service", false)
|
|
33
|
+
.option("--filter <filters>", "Comma-separated key=value filters (supports =, !=, >, <, >=, <=)")
|
|
34
|
+
.option("--fields <fields>", "Comma-separated fields to return (projection)")
|
|
35
|
+
.option("--sort <field>", "Sort by field (prefix with - for descending)", "-createdAt")
|
|
36
|
+
.option("--count", "Only return count of matching entries", false)
|
|
37
|
+
.option("-l, --limit <number>", "Limit results", parseInt, 25)
|
|
38
|
+
.option("-p, --page <number>", "Page number", parseInt, 1)
|
|
39
|
+
.action(async (collectionOrModule, collectionOrQuery, query, options) => {
|
|
40
|
+
try {
|
|
41
|
+
const opts = { ...program.opts(), ...options };
|
|
42
|
+
// Mode 1: Global SSE search
|
|
43
|
+
if (opts.global) {
|
|
44
|
+
return await handleGlobalSearch(opts);
|
|
45
|
+
}
|
|
46
|
+
// For all other modes, we need a collection
|
|
47
|
+
if (!collectionOrModule) {
|
|
48
|
+
cliError("Collection name is required (or use --global for global search)", EXIT_GENERAL_ERROR);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Detect if first arg is a module: ay search crm consumers "John"
|
|
52
|
+
let resolved;
|
|
53
|
+
let searchQuery = query;
|
|
54
|
+
const m = getModuleFromCollection(collectionOrModule.toLowerCase());
|
|
55
|
+
if (!m && collectionOrQuery) {
|
|
56
|
+
resolved = resolveCollectionArgs(collectionOrModule, collectionOrQuery);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
resolved = resolveCollectionArgs(collectionOrModule);
|
|
60
|
+
searchQuery = collectionOrQuery;
|
|
61
|
+
}
|
|
62
|
+
const collection = resolved.collection;
|
|
63
|
+
// Mode 4: Legacy module API search
|
|
64
|
+
if (opts.legacy) {
|
|
65
|
+
return await handleLegacySearch(resolved, searchQuery, opts);
|
|
66
|
+
}
|
|
67
|
+
// Mode 3: FindOne via search service
|
|
68
|
+
if (opts.one) {
|
|
69
|
+
return await handleFindOneSearch(resolved, searchQuery, opts);
|
|
70
|
+
}
|
|
71
|
+
// Mode 2: Single-model search via search service
|
|
72
|
+
return await handleModelSearch(resolved, searchQuery, opts);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
cliError(e.message || "Search failed", EXIT_GENERAL_ERROR);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function handleGlobalSearch(opts) {
|
|
80
|
+
const query = opts.global;
|
|
81
|
+
spinner.start({ text: `Global search for "${query}"...`, color: "magenta" });
|
|
82
|
+
const results = await searchGlobal(query, opts.limit, {
|
|
83
|
+
onResult: (result) => {
|
|
84
|
+
spinner.update({
|
|
85
|
+
text: `Searching... found ${result.total} in ${result.collection}`,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
onError: (error) => {
|
|
89
|
+
spinner.update({ text: `Search warning: ${error}` });
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// Flatten results into a grouped response for handleResponseFormatOptions
|
|
93
|
+
const totalEntries = results.reduce((sum, r) => sum + r.total, 0);
|
|
94
|
+
const allEntries = results.flatMap((r) => r.entries.map((e) => ({ ...e, _collection: r.collection })));
|
|
95
|
+
const res = {
|
|
96
|
+
payload: allEntries,
|
|
97
|
+
meta: {
|
|
98
|
+
responseTime: 0,
|
|
99
|
+
pageInfo: { totalEntries: totalEntries, page: 1, totalPages: 1 },
|
|
100
|
+
collections: results.map((r) => ({ name: r.collection, count: r.total })),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
if (opts.count) {
|
|
104
|
+
spinner.success({ text: `${totalEntries} matching entries across ${results.length} collections` });
|
|
105
|
+
spinner.stop();
|
|
106
|
+
console.log(totalEntries);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
handleResponseFormatOptions(opts, res);
|
|
110
|
+
spinner.success({
|
|
111
|
+
text: `Found ${totalEntries} entries across ${results.length} collections`,
|
|
112
|
+
});
|
|
113
|
+
spinner.stop();
|
|
114
|
+
if (opts.save)
|
|
115
|
+
await saveFile("search-global", opts, res);
|
|
116
|
+
}
|
|
117
|
+
async function handleModelSearch(resolved, searchQuery, opts) {
|
|
118
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
119
|
+
const collection = resolved.collection;
|
|
120
|
+
spinner.start({ text: `Searching ${collection}...`, color: "magenta" });
|
|
121
|
+
const params = {
|
|
122
|
+
limit: opts.limit,
|
|
123
|
+
skip: (opts.page - 1) * opts.limit,
|
|
124
|
+
};
|
|
125
|
+
if (searchQuery)
|
|
126
|
+
params.q = searchQuery;
|
|
127
|
+
if (opts.field)
|
|
128
|
+
params.field = opts.field;
|
|
129
|
+
if (opts.fields)
|
|
130
|
+
params.fields = sanitizeFields(opts.fields).join(",");
|
|
131
|
+
if (opts.sort)
|
|
132
|
+
params.sort = opts.sort;
|
|
133
|
+
// Inject context params
|
|
134
|
+
const contextParams = getContextFilterParams(collection);
|
|
135
|
+
Object.assign(params, contextParams);
|
|
136
|
+
// Parse filters
|
|
137
|
+
applyFilters(params, opts.filter);
|
|
138
|
+
const res = await searchModel(collection, params);
|
|
139
|
+
if (opts.count) {
|
|
140
|
+
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;
|
|
141
|
+
spinner.success({ text: `${total} matching entries in ${collection}` });
|
|
142
|
+
spinner.stop();
|
|
143
|
+
console.log(total);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
handleResponseFormatOptions(opts, res);
|
|
147
|
+
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;
|
|
148
|
+
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;
|
|
149
|
+
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;
|
|
150
|
+
spinner.success({
|
|
151
|
+
text: `Found ${total} entries in ${collection} (page ${page}/${totalPages})`,
|
|
152
|
+
});
|
|
153
|
+
spinner.stop();
|
|
154
|
+
localStorage.setItem("lastModule", resolved.module);
|
|
155
|
+
localStorage.setItem("lastCollection", resolved.collection);
|
|
156
|
+
if (opts.save)
|
|
157
|
+
await saveFile("search", opts, res);
|
|
158
|
+
}
|
|
159
|
+
async function handleFindOneSearch(resolved, searchQuery, opts) {
|
|
160
|
+
const collection = resolved.collection;
|
|
161
|
+
spinner.start({ text: `Finding one in ${collection}...`, color: "magenta" });
|
|
162
|
+
const params = {};
|
|
163
|
+
if (searchQuery)
|
|
164
|
+
params.q = searchQuery;
|
|
165
|
+
if (opts.field)
|
|
166
|
+
params.field = opts.field;
|
|
167
|
+
if (opts.fields)
|
|
168
|
+
params.fields = sanitizeFields(opts.fields).join(",");
|
|
169
|
+
const contextParams = getContextFilterParams(collection);
|
|
170
|
+
Object.assign(params, contextParams);
|
|
171
|
+
applyFilters(params, opts.filter);
|
|
172
|
+
const res = await searchOne(collection, params);
|
|
173
|
+
handleResponseFormatOptions(opts, res);
|
|
174
|
+
spinner.success({
|
|
175
|
+
text: `Found match in ${collection}`,
|
|
176
|
+
});
|
|
177
|
+
spinner.stop();
|
|
178
|
+
localStorage.setItem("lastModule", resolved.module);
|
|
179
|
+
localStorage.setItem("lastCollection", resolved.collection);
|
|
180
|
+
if (opts.save)
|
|
181
|
+
await saveFile("search-one", opts, res);
|
|
182
|
+
}
|
|
183
|
+
async function handleLegacySearch(resolved, searchQuery, opts) {
|
|
184
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
185
|
+
const collection = resolved.collection;
|
|
186
|
+
spinner.start({ text: `Searching ${collection} (legacy)...`, color: "magenta" });
|
|
187
|
+
const params = {
|
|
188
|
+
page: opts.page,
|
|
189
|
+
limit: opts.limit,
|
|
190
|
+
sort: opts.sort,
|
|
191
|
+
responseFormat: opts.responseFormat,
|
|
192
|
+
verbosity: opts.verbosity,
|
|
193
|
+
hideMeta: opts.hideMeta,
|
|
194
|
+
};
|
|
195
|
+
const contextParams = getContextFilterParams(collection);
|
|
196
|
+
Object.assign(params, contextParams);
|
|
197
|
+
if (searchQuery)
|
|
198
|
+
params.q = searchQuery;
|
|
199
|
+
if (opts.fields)
|
|
200
|
+
params.fields = sanitizeFields(opts.fields).join(",");
|
|
201
|
+
applyFilters(params, opts.filter);
|
|
202
|
+
const res = await apiCallHandler(resolved.module, collection, "get", null, params);
|
|
203
|
+
if (opts.count) {
|
|
204
|
+
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;
|
|
205
|
+
spinner.success({ text: `${total} matching entries in ${collection}` });
|
|
206
|
+
spinner.stop();
|
|
207
|
+
console.log(total);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
handleResponseFormatOptions(opts, res);
|
|
211
|
+
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;
|
|
212
|
+
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;
|
|
213
|
+
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;
|
|
214
|
+
spinner.success({
|
|
215
|
+
text: `Found ${total} entries in ${collection} (page ${page}/${totalPages})`,
|
|
216
|
+
});
|
|
217
|
+
spinner.stop();
|
|
218
|
+
localStorage.setItem("lastModule", resolved.module);
|
|
219
|
+
localStorage.setItem("lastCollection", resolved.collection);
|
|
220
|
+
if (opts.save)
|
|
221
|
+
await saveFile("search", opts, res);
|
|
222
|
+
}
|
|
223
|
+
function applyFilters(params, filter) {
|
|
224
|
+
if (!filter)
|
|
225
|
+
return;
|
|
226
|
+
const filters = filter.split(",");
|
|
227
|
+
for (const f of filters) {
|
|
228
|
+
const match = f.match(/^(\w+)(!=|>=|<=|>|<|=)(.+)$/);
|
|
229
|
+
if (match) {
|
|
230
|
+
const [, key, op, value] = match;
|
|
231
|
+
if (op === "=") {
|
|
232
|
+
params[key] = value;
|
|
233
|
+
}
|
|
234
|
+
else if (op === "!=") {
|
|
235
|
+
params[`${key}[ne]`] = value;
|
|
236
|
+
}
|
|
237
|
+
else if (op === ">") {
|
|
238
|
+
params[`${key}[gt]`] = value;
|
|
239
|
+
}
|
|
240
|
+
else if (op === "<") {
|
|
241
|
+
params[`${key}[lt]`] = value;
|
|
242
|
+
}
|
|
243
|
+
else if (op === ">=") {
|
|
244
|
+
params[`${key}[gte]`] = value;
|
|
245
|
+
}
|
|
246
|
+
else if (op === "<=") {
|
|
247
|
+
params[`${key}[lte]`] = value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { spinner } from "../../index.js";
|
|
3
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
4
|
+
import { cliError } from "../helpers/cliError.js";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
function runCommand(cmd) {
|
|
7
|
+
try {
|
|
8
|
+
return execSync(cmd, { encoding: "utf-8", timeout: 30000 }).trim();
|
|
9
|
+
}
|
|
10
|
+
catch (_a) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function detectRuntime() {
|
|
15
|
+
// Check for docker compose
|
|
16
|
+
const composeResult = runCommand("docker compose version 2>&1");
|
|
17
|
+
if (composeResult.includes("Docker Compose"))
|
|
18
|
+
return "compose";
|
|
19
|
+
// Check for kubectl
|
|
20
|
+
const kubectlResult = runCommand("kubectl version --client 2>&1");
|
|
21
|
+
if (kubectlResult.includes("Client Version"))
|
|
22
|
+
return "kubernetes";
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
function getRunningComposeServices() {
|
|
26
|
+
const output = runCommand('docker compose ps --format "{{.Name}}" 2>&1');
|
|
27
|
+
if (!output)
|
|
28
|
+
return [];
|
|
29
|
+
return output.split("\n").filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
function getComposeImageVersions() {
|
|
32
|
+
const versions = new Map();
|
|
33
|
+
const output = runCommand('docker compose ps --format "{{.Name}}|{{.Image}}" 2>&1');
|
|
34
|
+
if (!output)
|
|
35
|
+
return versions;
|
|
36
|
+
for (const line of output.split("\n")) {
|
|
37
|
+
const [name, image] = line.split("|");
|
|
38
|
+
if (name && image) {
|
|
39
|
+
const tag = image.split(":").pop() || "unknown";
|
|
40
|
+
versions.set(name, tag);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return versions;
|
|
44
|
+
}
|
|
45
|
+
export function createSelfHostUpdateCommand(program) {
|
|
46
|
+
program
|
|
47
|
+
.command("self-host-update")
|
|
48
|
+
.alias("shu")
|
|
49
|
+
.description("Check for and apply updates to a self-hosted aYOUne deployment")
|
|
50
|
+
.addHelpText("after", `
|
|
51
|
+
Examples:
|
|
52
|
+
ay self-host-update Check for available updates
|
|
53
|
+
ay self-host-update --apply Pull new images and restart services
|
|
54
|
+
ay self-host-update --service crm Check updates for a specific service`)
|
|
55
|
+
.option("--apply", "Apply available updates (pull new images, restart services)")
|
|
56
|
+
.option("--service <name>", "Update only a specific service")
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
try {
|
|
59
|
+
const opts = { ...program.opts(), ...options };
|
|
60
|
+
const runtime = detectRuntime();
|
|
61
|
+
if (runtime === "unknown") {
|
|
62
|
+
cliError("Could not detect Docker Compose or Kubernetes. Ensure docker or kubectl is installed and accessible.", EXIT_GENERAL_ERROR);
|
|
63
|
+
}
|
|
64
|
+
console.log(chalk.cyan.bold("\n aYOUne Self-Hosted Update\n"));
|
|
65
|
+
console.log(chalk.dim(` Runtime: ${runtime === "compose" ? "Docker Compose" : "Kubernetes"}\n`));
|
|
66
|
+
if (runtime === "compose") {
|
|
67
|
+
await handleComposeUpdate(opts);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
await handleKubernetesUpdate(opts);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
cliError(e.message || "Update check failed", EXIT_GENERAL_ERROR);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async function handleComposeUpdate(opts) {
|
|
79
|
+
const services = getRunningComposeServices();
|
|
80
|
+
if (services.length === 0) {
|
|
81
|
+
console.log(chalk.yellow(" No running aYOUne services found."));
|
|
82
|
+
console.log(chalk.dim(" Start services first: docker compose --profile core up -d\n"));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const versions = getComposeImageVersions();
|
|
86
|
+
console.log(chalk.white(` Running services: ${services.length}\n`));
|
|
87
|
+
for (const [name, version] of versions) {
|
|
88
|
+
if (opts.service && !name.includes(opts.service))
|
|
89
|
+
continue;
|
|
90
|
+
console.log(` ${chalk.dim("●")} ${name} ${chalk.dim(`(${version})`)}`);
|
|
91
|
+
}
|
|
92
|
+
console.log("");
|
|
93
|
+
if (opts.apply) {
|
|
94
|
+
spinner.start({ text: "Pulling latest images...", color: "cyan" });
|
|
95
|
+
const pullTarget = opts.service ? opts.service : "";
|
|
96
|
+
const pullResult = runCommand(`docker compose pull ${pullTarget} 2>&1`);
|
|
97
|
+
if (!pullResult) {
|
|
98
|
+
spinner.error({ text: "Failed to pull images" });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
spinner.success({ text: "Images pulled successfully" });
|
|
102
|
+
spinner.start({ text: "Restarting services...", color: "cyan" });
|
|
103
|
+
const upTarget = opts.service ? opts.service : "";
|
|
104
|
+
runCommand(`docker compose up -d ${upTarget} 2>&1`);
|
|
105
|
+
spinner.success({ text: "Services restarted" });
|
|
106
|
+
spinner.stop();
|
|
107
|
+
console.log(chalk.green("\n Update complete!"));
|
|
108
|
+
console.log(chalk.dim(" Run 'ay status' to verify all services are healthy.\n"));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
spinner.start({ text: "Checking for new images...", color: "cyan" });
|
|
112
|
+
const pullTarget = opts.service ? opts.service : "";
|
|
113
|
+
const dryRunResult = runCommand(`docker compose pull --dry-run ${pullTarget} 2>&1`);
|
|
114
|
+
spinner.stop();
|
|
115
|
+
if (dryRunResult) {
|
|
116
|
+
console.log(chalk.dim(" Image check output:"));
|
|
117
|
+
for (const line of dryRunResult.split("\n")) {
|
|
118
|
+
console.log(chalk.dim(` ${line}`));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log(chalk.cyan("\n To apply updates, run:"));
|
|
122
|
+
console.log(chalk.dim(" ay self-host-update --apply\n"));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function handleKubernetesUpdate(opts) {
|
|
126
|
+
// Check for Helm releases
|
|
127
|
+
const helmList = runCommand("helm list -n ayoune --output json 2>&1");
|
|
128
|
+
if (!helmList || helmList.startsWith("[")) {
|
|
129
|
+
try {
|
|
130
|
+
const releases = JSON.parse(helmList || "[]");
|
|
131
|
+
if (releases.length === 0) {
|
|
132
|
+
console.log(chalk.yellow(" No Helm releases found in 'ayoune' namespace."));
|
|
133
|
+
console.log(chalk.dim(" Install: helm install ayoune tolinax/ayoune -f values.yaml\n"));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const release of releases) {
|
|
137
|
+
console.log(` ${chalk.white(release.name)} ${chalk.dim(`v${release.app_version} (chart: ${release.chart})`)}`);
|
|
138
|
+
console.log(` ${chalk.dim(`Status: ${release.status} | Updated: ${release.updated}`)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (_a) {
|
|
142
|
+
console.log(chalk.yellow(" Could not parse Helm releases."));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
console.log("");
|
|
146
|
+
if (opts.apply) {
|
|
147
|
+
spinner.start({ text: "Updating Helm repo...", color: "cyan" });
|
|
148
|
+
runCommand("helm repo update 2>&1");
|
|
149
|
+
spinner.success({ text: "Helm repo updated" });
|
|
150
|
+
spinner.start({ text: "Upgrading release...", color: "cyan" });
|
|
151
|
+
const upgradeResult = runCommand("helm upgrade ayoune tolinax/ayoune -n ayoune --reuse-values 2>&1");
|
|
152
|
+
if (upgradeResult.includes("Error")) {
|
|
153
|
+
spinner.error({ text: `Helm upgrade failed: ${upgradeResult}` });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
spinner.success({ text: "Helm release upgraded" });
|
|
157
|
+
spinner.stop();
|
|
158
|
+
console.log(chalk.green("\n Update complete!"));
|
|
159
|
+
console.log(chalk.dim(" Run 'ay status' to verify all services are healthy.\n"));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.log(chalk.cyan(" To apply updates, run:"));
|
|
163
|
+
console.log(chalk.dim(" ay self-host-update --apply"));
|
|
164
|
+
console.log(chalk.dim(" # or manually: helm upgrade ayoune tolinax/ayoune -n ayoune --reuse-values\n"));
|
|
165
|
+
}
|
|
166
|
+
}
|