@tolinax/ayoune-cli 2026.3.0 → 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.
Files changed (70) hide show
  1. package/data/contextSlots.js +189 -0
  2. package/data/modelsAndRights.js +56 -0
  3. package/data/modules.js +16 -0
  4. package/lib/api/apiCallHandler.js +6 -2
  5. package/lib/api/apiClient.js +9 -1
  6. package/lib/api/auditCallHandler.js +2 -2
  7. package/lib/api/handleAPIError.js +20 -18
  8. package/lib/api/login.js +3 -3
  9. package/lib/api/searchClient.js +119 -0
  10. package/lib/commands/createAccessCommand.js +126 -0
  11. package/lib/commands/createActionsCommand.js +40 -9
  12. package/lib/commands/createAiCommand.js +17 -17
  13. package/lib/commands/createAliasCommand.js +4 -6
  14. package/lib/commands/createAuditCommand.js +5 -9
  15. package/lib/commands/createBatchCommand.js +15 -28
  16. package/lib/commands/createCompletionsCommand.js +6 -3
  17. package/lib/commands/createConfigCommand.js +8 -14
  18. package/lib/commands/createContextCommand.js +163 -0
  19. package/lib/commands/createCopyCommand.js +4 -7
  20. package/lib/commands/createCreateCommand.js +4 -7
  21. package/lib/commands/createDeleteCommand.js +4 -6
  22. package/lib/commands/createDeployCommand.js +31 -55
  23. package/lib/commands/createDescribeCommand.js +12 -10
  24. package/lib/commands/createEditCommand.js +13 -8
  25. package/lib/commands/createEventsCommand.js +4 -4
  26. package/lib/commands/createExecCommand.js +65 -35
  27. package/lib/commands/createExportCommand.js +21 -24
  28. package/lib/commands/createGetCommand.js +13 -14
  29. package/lib/commands/createJobsCommand.js +8 -13
  30. package/lib/commands/createListCommand.js +13 -14
  31. package/lib/commands/createLoginCommand.js +16 -4
  32. package/lib/commands/createLogoutCommand.js +2 -2
  33. package/lib/commands/createModulesCommand.js +16 -19
  34. package/lib/commands/createMonitorCommand.js +9 -16
  35. package/lib/commands/createPermissionsCommand.js +10 -18
  36. package/lib/commands/createProgram.js +47 -21
  37. package/lib/commands/createSearchCommand.js +219 -69
  38. package/lib/commands/createSelfHostUpdateCommand.js +166 -0
  39. package/lib/commands/createServicesCommand.js +5 -8
  40. package/lib/commands/createSetupCommand.js +305 -0
  41. package/lib/commands/createStatusCommand.js +147 -0
  42. package/lib/commands/createStorageCommand.js +2 -3
  43. package/lib/commands/createStreamCommand.js +4 -4
  44. package/lib/commands/createSyncCommand.js +5 -8
  45. package/lib/commands/createTemplateCommand.js +9 -16
  46. package/lib/commands/createUpdateCommand.js +12 -15
  47. package/lib/commands/createUsersCommand.js +21 -31
  48. package/lib/commands/createWebhooksCommand.js +15 -22
  49. package/lib/commands/createWhoAmICommand.js +8 -6
  50. package/lib/helpers/cliError.js +24 -0
  51. package/lib/helpers/config.js +1 -0
  52. package/lib/helpers/configLoader.js +6 -0
  53. package/lib/helpers/contextInjector.js +65 -0
  54. package/lib/helpers/contextResolver.js +70 -0
  55. package/lib/helpers/contextStore.js +46 -0
  56. package/lib/helpers/handleResponseFormatOptions.js +59 -10
  57. package/lib/helpers/logo.js +48 -0
  58. package/lib/helpers/resolveCollectionArgs.js +36 -0
  59. package/lib/helpers/sanitizeFields.js +18 -0
  60. package/lib/helpers/secureStorage.js +72 -0
  61. package/lib/helpers/tokenPayload.js +21 -0
  62. package/lib/helpers/updateNotifier.js +49 -0
  63. package/lib/models/getModuleFromCollection.js +4 -1
  64. package/lib/operations/handleCopySingleOperation.js +10 -2
  65. package/lib/operations/handleCreateSingleOperation.js +3 -0
  66. package/lib/operations/handleDescribeSingleOperation.js +23 -0
  67. package/lib/operations/handleGetOperation.js +9 -3
  68. package/lib/operations/handleListOperation.js +14 -10
  69. package/lib/prompts/promptModule.js +9 -6
  70. package/package.json +163 -158
@@ -1,101 +1,251 @@
1
+ import { resolveCollectionArgs } from "../helpers/resolveCollectionArgs.js";
1
2
  import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
2
3
  import { apiCallHandler } from "../api/apiCallHandler.js";
4
+ import { searchModel, searchOne, searchGlobal } from "../api/searchClient.js";
3
5
  import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
4
6
  import { saveFile } from "../helpers/saveFile.js";
5
7
  import { localStorage } from "../helpers/localStorage.js";
6
8
  import { spinner } from "../../index.js";
7
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";
8
13
  export function createSearchCommand(program) {
9
14
  program
10
- .command("search <collection> [query]")
15
+ .command("search [collectionOrModule] [collectionOrQuery] [query]")
11
16
  .alias("find")
12
- .description("Search entries in a collection with advanced filtering")
17
+ .description("Search entries via the aYOUne Search Service")
13
18
  .addHelpText("after", `
14
19
  Examples:
15
- ay search contacts "John" Full-text search
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
16
25
  ay search contacts --filter "status=active" Filter by field
17
- ay search products --filter "price>100" Comparison filter
18
26
  ay search orders --filter "status=paid" --sort "-createdAt"
19
- ay search leads --filter "source=website,stage=qualified" --fields "name,email,stage"
27
+ ay search consumers "John" --legacy Use legacy module API
20
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)
21
33
  .option("--filter <filters>", "Comma-separated key=value filters (supports =, !=, >, <, >=, <=)")
22
34
  .option("--fields <fields>", "Comma-separated fields to return (projection)")
23
35
  .option("--sort <field>", "Sort by field (prefix with - for descending)", "-createdAt")
24
36
  .option("--count", "Only return count of matching entries", false)
25
37
  .option("-l, --limit <number>", "Limit results", parseInt, 25)
26
38
  .option("-p, --page <number>", "Page number", parseInt, 1)
27
- .action(async (collection, query, options) => {
28
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
39
+ .action(async (collectionOrModule, collectionOrQuery, query, options) => {
29
40
  try {
30
41
  const opts = { ...program.opts(), ...options };
31
- const module = getModuleFromCollection(collection);
32
- spinner.start({ text: `Searching ${collection}...`, color: "magenta" });
33
- const params = {
34
- page: opts.page,
35
- limit: opts.limit,
36
- sort: opts.sort,
37
- responseFormat: opts.responseFormat,
38
- verbosity: opts.verbosity,
39
- hideMeta: opts.hideMeta,
40
- };
41
- if (query) {
42
- params.q = query;
42
+ // Mode 1: Global SSE search
43
+ if (opts.global) {
44
+ return await handleGlobalSearch(opts);
43
45
  }
44
- if (opts.fields) {
45
- params.fields = opts.fields;
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;
46
50
  }
47
- // Parse filters into query params
48
- if (opts.filter) {
49
- const filters = opts.filter.split(",");
50
- for (const f of filters) {
51
- const match = f.match(/^(\w+)(!=|>=|<=|>|<|=)(.+)$/);
52
- if (match) {
53
- const [, key, op, value] = match;
54
- if (op === "=") {
55
- params[key] = value;
56
- }
57
- else if (op === "!=") {
58
- params[`${key}[ne]`] = value;
59
- }
60
- else if (op === ">") {
61
- params[`${key}[gt]`] = value;
62
- }
63
- else if (op === "<") {
64
- params[`${key}[lt]`] = value;
65
- }
66
- else if (op === ">=") {
67
- params[`${key}[gte]`] = value;
68
- }
69
- else if (op === "<=") {
70
- params[`${key}[lte]`] = value;
71
- }
72
- }
73
- }
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);
74
57
  }
75
- const res = await apiCallHandler(module.module, collection.toLowerCase(), "get", null, params);
76
- if (opts.count) {
77
- const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
78
- spinner.success({ text: `${total} matching entries in ${collection}` });
79
- spinner.stop();
80
- console.log(total);
81
- return;
58
+ else {
59
+ resolved = resolveCollectionArgs(collectionOrModule);
60
+ searchQuery = collectionOrQuery;
82
61
  }
83
- handleResponseFormatOptions(opts, res);
84
- const total = (_f = (_e = (_d = res.meta) === null || _d === void 0 ? void 0 : _d.pageInfo) === null || _e === void 0 ? void 0 : _e.totalEntries) !== null && _f !== void 0 ? _f : 0;
85
- const page = (_j = (_h = (_g = res.meta) === null || _g === void 0 ? void 0 : _g.pageInfo) === null || _h === void 0 ? void 0 : _h.page) !== null && _j !== void 0 ? _j : 1;
86
- const totalPages = (_m = (_l = (_k = res.meta) === null || _k === void 0 ? void 0 : _k.pageInfo) === null || _l === void 0 ? void 0 : _l.totalPages) !== null && _m !== void 0 ? _m : 1;
87
- spinner.success({
88
- text: `Found ${total} entries in ${collection} (page ${page}/${totalPages})`,
89
- });
90
- spinner.stop();
91
- localStorage.setItem("lastModule", module.module);
92
- localStorage.setItem("lastCollection", collection);
93
- if (opts.save)
94
- await saveFile("search", opts, res);
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);
95
73
  }
96
74
  catch (e) {
97
- spinner.error({ text: e.message || "Search failed" });
98
- process.exit(EXIT_GENERAL_ERROR);
75
+ cliError(e.message || "Search failed", EXIT_GENERAL_ERROR);
99
76
  }
100
77
  });
101
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
+ }
@@ -3,6 +3,7 @@ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOpti
3
3
  import { saveFile } from "../helpers/saveFile.js";
4
4
  import { spinner } from "../../index.js";
5
5
  import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
6
+ import { cliError } from "../helpers/cliError.js";
6
7
  import { aYOUneModules } from "../../data/modules.js";
7
8
  import { aYOUneServices } from "../../data/services.js";
8
9
  function buildServiceRegistry() {
@@ -61,8 +62,7 @@ export function createServicesCommand(program) {
61
62
  await saveFile("services-list", opts, res);
62
63
  }
63
64
  catch (e) {
64
- spinner.error({ text: e.message || "Failed to list services" });
65
- process.exit(EXIT_GENERAL_ERROR);
65
+ cliError(e.message || "Failed to list services", EXIT_GENERAL_ERROR);
66
66
  }
67
67
  });
68
68
  // ay services endpoints <host>
@@ -110,8 +110,7 @@ Examples:
110
110
  await saveFile("service-endpoints", opts, formattedRes);
111
111
  }
112
112
  catch (e) {
113
- spinner.error({ text: e.message || "Failed to fetch endpoints" });
114
- process.exit(EXIT_GENERAL_ERROR);
113
+ cliError(e.message || "Failed to fetch endpoints", EXIT_GENERAL_ERROR);
115
114
  }
116
115
  });
117
116
  // ay services health [host]
@@ -174,8 +173,7 @@ Examples:
174
173
  await saveFile("services-health", opts, res);
175
174
  }
176
175
  catch (e) {
177
- spinner.error({ text: e.message || "Health check failed" });
178
- process.exit(EXIT_GENERAL_ERROR);
176
+ cliError(e.message || "Health check failed", EXIT_GENERAL_ERROR);
179
177
  }
180
178
  });
181
179
  // ay services describe <module>
@@ -221,8 +219,7 @@ Examples:
221
219
  spinner.stop();
222
220
  }
223
221
  catch (e) {
224
- spinner.error({ text: e.message || "Failed to describe service" });
225
- process.exit(EXIT_GENERAL_ERROR);
222
+ cliError(e.message || "Failed to describe service", EXIT_GENERAL_ERROR);
226
223
  }
227
224
  });
228
225
  }