@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.
Files changed (107) hide show
  1. package/data/contextSlots.js +189 -0
  2. package/data/defaultActions.js +9 -0
  3. package/data/modelsAndRights.js +3245 -0
  4. package/data/modules.js +127 -0
  5. package/data/operations.js +5 -0
  6. package/data/services.js +139 -0
  7. package/index.js +11 -0
  8. package/lib/api/apiCallHandler.js +72 -0
  9. package/lib/api/apiClient.js +108 -0
  10. package/lib/api/auditCallHandler.js +21 -0
  11. package/lib/api/decodeToken.js +4 -0
  12. package/lib/api/handleAPIError.js +61 -0
  13. package/lib/api/login.js +45 -0
  14. package/lib/api/searchClient.js +119 -0
  15. package/lib/commands/createAccessCommand.js +126 -0
  16. package/lib/commands/createActionsCommand.js +140 -0
  17. package/lib/commands/createAiCommand.js +188 -0
  18. package/lib/commands/createAliasCommand.js +104 -0
  19. package/lib/commands/createAuditCommand.js +45 -0
  20. package/lib/commands/createBatchCommand.js +291 -0
  21. package/lib/commands/createCompletionsCommand.js +172 -0
  22. package/lib/commands/createConfigCommand.js +202 -0
  23. package/lib/commands/createContextCommand.js +163 -0
  24. package/lib/commands/createCopyCommand.js +36 -0
  25. package/lib/commands/createCreateCommand.js +47 -0
  26. package/lib/commands/createDeleteCommand.js +96 -0
  27. package/lib/commands/createDeployCommand.js +642 -0
  28. package/lib/commands/createDescribeCommand.js +44 -0
  29. package/lib/commands/createEditCommand.js +48 -0
  30. package/lib/commands/createEventsCommand.js +60 -0
  31. package/lib/commands/createExecCommand.js +212 -0
  32. package/lib/commands/createExportCommand.js +216 -0
  33. package/lib/commands/createGetCommand.js +46 -0
  34. package/lib/commands/createJobsCommand.js +163 -0
  35. package/lib/commands/createListCommand.js +48 -0
  36. package/lib/commands/createLoginCommand.js +30 -0
  37. package/lib/commands/createLogoutCommand.js +21 -0
  38. package/lib/commands/createModulesCommand.js +147 -0
  39. package/lib/commands/createMonitorCommand.js +276 -0
  40. package/lib/commands/createPermissionsCommand.js +233 -0
  41. package/lib/commands/createProgram.js +217 -0
  42. package/lib/commands/createSearchCommand.js +251 -0
  43. package/lib/commands/createSelfHostUpdateCommand.js +166 -0
  44. package/lib/commands/createServicesCommand.js +225 -0
  45. package/lib/commands/createSetupCommand.js +305 -0
  46. package/lib/commands/createStatusCommand.js +160 -0
  47. package/lib/commands/createStorageCommand.js +53 -0
  48. package/lib/commands/createStreamCommand.js +50 -0
  49. package/lib/commands/createSyncCommand.js +174 -0
  50. package/lib/commands/createTemplateCommand.js +231 -0
  51. package/lib/commands/createUpdateCommand.js +112 -0
  52. package/lib/commands/createUsersCommand.js +275 -0
  53. package/lib/commands/createWebhooksCommand.js +149 -0
  54. package/lib/commands/createWhoAmICommand.js +90 -0
  55. package/lib/exitCodes.js +6 -0
  56. package/lib/helpers/addSpacesToCamelCase.js +5 -0
  57. package/lib/helpers/cliError.js +24 -0
  58. package/lib/helpers/config.js +7 -0
  59. package/lib/helpers/configLoader.js +66 -0
  60. package/lib/helpers/contextInjector.js +65 -0
  61. package/lib/helpers/contextResolver.js +70 -0
  62. package/lib/helpers/contextStore.js +46 -0
  63. package/lib/helpers/formatDocument.js +176 -0
  64. package/lib/helpers/handleResponseFormatOptions.js +134 -0
  65. package/lib/helpers/initializeSettings.js +14 -0
  66. package/lib/helpers/localStorage.js +4 -0
  67. package/lib/helpers/logo.js +48 -0
  68. package/lib/helpers/makeRandomToken.js +27 -0
  69. package/lib/helpers/parseInt.js +7 -0
  70. package/lib/helpers/requireArg.js +9 -0
  71. package/lib/helpers/resolveCollectionArgs.js +36 -0
  72. package/lib/helpers/sanitizeFields.js +18 -0
  73. package/lib/helpers/saveFile.js +39 -0
  74. package/lib/helpers/secureStorage.js +72 -0
  75. package/lib/helpers/tokenPayload.js +21 -0
  76. package/lib/helpers/updateNotifier.js +50 -0
  77. package/lib/models/getCollections.js +15 -0
  78. package/lib/models/getModelsInModules.js +13 -0
  79. package/lib/models/getModuleFromCollection.js +10 -0
  80. package/lib/operations/handleAuditOperation.js +22 -0
  81. package/lib/operations/handleCollectionOperation.js +91 -0
  82. package/lib/operations/handleCopySingleOperation.js +30 -0
  83. package/lib/operations/handleCreateSingleOperation.js +38 -0
  84. package/lib/operations/handleDeleteSingleOperation.js +14 -0
  85. package/lib/operations/handleDescribeSingleOperation.js +45 -0
  86. package/lib/operations/handleEditOperation.js +51 -0
  87. package/lib/operations/handleEditRawOperation.js +35 -0
  88. package/lib/operations/handleGetOperation.js +35 -0
  89. package/lib/operations/handleGetSingleOperation.js +20 -0
  90. package/lib/operations/handleListOperation.js +67 -0
  91. package/lib/operations/handleSingleAuditOperation.js +27 -0
  92. package/lib/prompts/promptAudits.js +15 -0
  93. package/lib/prompts/promptCollection.js +13 -0
  94. package/lib/prompts/promptCollectionInModule.js +13 -0
  95. package/lib/prompts/promptCollectionWithModule.js +15 -0
  96. package/lib/prompts/promptConfirm.js +12 -0
  97. package/lib/prompts/promptDefaultAction.js +13 -0
  98. package/lib/prompts/promptEntry.js +19 -0
  99. package/lib/prompts/promptFileName.js +12 -0
  100. package/lib/prompts/promptFilePath.js +18 -0
  101. package/lib/prompts/promptModule.js +22 -0
  102. package/lib/prompts/promptName.js +11 -0
  103. package/lib/prompts/promptOperation.js +13 -0
  104. package/lib/socket/customerSocketClient.js +13 -0
  105. package/lib/socket/socketClient.js +12 -0
  106. package/lib/types.js +1 -0
  107. package/package.json +13 -10
@@ -0,0 +1,225 @@
1
+ import { apiCallHandler } from "../api/apiCallHandler.js";
2
+ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
3
+ import { saveFile } from "../helpers/saveFile.js";
4
+ import { spinner } from "../../index.js";
5
+ import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
6
+ import { cliError } from "../helpers/cliError.js";
7
+ import { aYOUneModules } from "../../data/modules.js";
8
+ import { aYOUneServices } from "../../data/services.js";
9
+ function buildServiceRegistry() {
10
+ const services = [];
11
+ // APIs from modules
12
+ for (const m of aYOUneModules) {
13
+ services.push({
14
+ name: m.label,
15
+ type: "api",
16
+ module: m.module,
17
+ host: m.host,
18
+ });
19
+ }
20
+ // Services
21
+ for (const s of aYOUneServices) {
22
+ services.push({
23
+ name: s.label,
24
+ type: "service",
25
+ module: s.module,
26
+ host: s.host,
27
+ });
28
+ }
29
+ return services;
30
+ }
31
+ export function createServicesCommand(program) {
32
+ const svc = program
33
+ .command("services")
34
+ .alias("svc")
35
+ .description("Discover and manage aYOUne platform services");
36
+ // ay services list
37
+ svc
38
+ .command("list")
39
+ .alias("ls")
40
+ .description("List all registered services, APIs, and gateways")
41
+ .option("--type <type>", "Filter by type: api, service, gateway, worker")
42
+ .option("--module <module>", "Filter by module name")
43
+ .action(async (options) => {
44
+ try {
45
+ const opts = { ...program.opts(), ...options };
46
+ spinner.start({ text: "Loading service registry...", color: "magenta" });
47
+ let services = buildServiceRegistry();
48
+ if (opts.type) {
49
+ services = services.filter((s) => s.type === opts.type);
50
+ }
51
+ if (opts.module) {
52
+ services = services.filter((s) => s.module === opts.module);
53
+ }
54
+ const res = {
55
+ payload: services,
56
+ meta: { responseTime: 0, pageInfo: { totalEntries: services.length, page: 1, totalPages: 1 } },
57
+ };
58
+ handleResponseFormatOptions(opts, res);
59
+ spinner.success({ text: `Found ${services.length} services` });
60
+ spinner.stop();
61
+ if (opts.save)
62
+ await saveFile("services-list", opts, res);
63
+ }
64
+ catch (e) {
65
+ cliError(e.message || "Failed to list services", EXIT_GENERAL_ERROR);
66
+ }
67
+ });
68
+ // ay services endpoints <host>
69
+ svc
70
+ .command("endpoints <host>")
71
+ .alias("ep")
72
+ .description("List API endpoints for a service host")
73
+ .addHelpText("after", `
74
+ Examples:
75
+ ay services endpoints ai.ayoune.app
76
+ ay services endpoints crm-api.ayoune.app -r table`)
77
+ .action(async (host, options) => {
78
+ try {
79
+ const opts = { ...program.opts(), ...options };
80
+ spinner.start({ text: `Fetching endpoints for ${host}...`, color: "magenta" });
81
+ const res = await apiCallHandler("config", "ayouneapiactions", "get", null, {
82
+ limit: 500,
83
+ responseFormat: "json",
84
+ });
85
+ if (!(res === null || res === void 0 ? void 0 : res.payload)) {
86
+ spinner.error({ text: "No API actions found" });
87
+ return;
88
+ }
89
+ const endpoints = (Array.isArray(res.payload) ? res.payload : [])
90
+ .filter((a) => {
91
+ const h = a.host || "";
92
+ return h.includes(host) && !a.deprecated;
93
+ })
94
+ .map((a) => ({
95
+ method: (a.method || "GET").toUpperCase(),
96
+ endpoint: a.endpoint || "",
97
+ operationId: a.operationId || "",
98
+ action: a.action || "",
99
+ description: a.shortDescription || a.description || "",
100
+ }))
101
+ .sort((a, b) => a.endpoint.localeCompare(b.endpoint));
102
+ const formattedRes = {
103
+ payload: endpoints,
104
+ meta: { responseTime: 0, pageInfo: { totalEntries: endpoints.length, page: 1, totalPages: 1 } },
105
+ };
106
+ handleResponseFormatOptions(opts, formattedRes);
107
+ spinner.success({ text: `Found ${endpoints.length} endpoints on ${host}` });
108
+ spinner.stop();
109
+ if (opts.save)
110
+ await saveFile("service-endpoints", opts, formattedRes);
111
+ }
112
+ catch (e) {
113
+ cliError(e.message || "Failed to fetch endpoints", EXIT_GENERAL_ERROR);
114
+ }
115
+ });
116
+ // ay services health [host]
117
+ svc
118
+ .command("health [host]")
119
+ .description("Check health of a service or all services")
120
+ .option("--timeout <ms>", "Request timeout in ms", parseInt, 5000)
121
+ .action(async (host, options) => {
122
+ try {
123
+ const opts = { ...program.opts(), ...options };
124
+ const targets = host
125
+ ? [{ name: host, host }]
126
+ : buildServiceRegistry().map((s) => ({ name: s.name, host: s.host }));
127
+ // Deduplicate by host
128
+ const uniqueTargets = [...new Map(targets.map((t) => [t.host, t])).values()];
129
+ spinner.start({ text: `Checking ${uniqueTargets.length} service(s)...`, color: "magenta" });
130
+ const { default: axios } = await import("axios");
131
+ const https = await import("https");
132
+ const agent = new https.Agent({ rejectUnauthorized: false });
133
+ const results = await Promise.allSettled(uniqueTargets.map(async (t) => {
134
+ const start = Date.now();
135
+ try {
136
+ const resp = await axios.get(`https://${t.host}/`, {
137
+ timeout: opts.timeout,
138
+ httpsAgent: agent,
139
+ validateStatus: () => true,
140
+ });
141
+ return {
142
+ host: t.host,
143
+ name: t.name,
144
+ status: resp.status < 500 ? "healthy" : "unhealthy",
145
+ statusCode: resp.status,
146
+ responseTime: Date.now() - start,
147
+ };
148
+ }
149
+ catch (e) {
150
+ return {
151
+ host: t.host,
152
+ name: t.name,
153
+ status: "unreachable",
154
+ statusCode: 0,
155
+ responseTime: Date.now() - start,
156
+ error: e.code || e.message,
157
+ };
158
+ }
159
+ }));
160
+ const payload = results.map((r) => (r.status === "fulfilled" ? r.value : { status: "error" }));
161
+ const healthy = payload.filter((p) => p.status === "healthy").length;
162
+ const res = {
163
+ payload,
164
+ meta: {
165
+ responseTime: 0,
166
+ pageInfo: { totalEntries: payload.length, page: 1, totalPages: 1 },
167
+ },
168
+ };
169
+ handleResponseFormatOptions(opts, res);
170
+ spinner.success({ text: `${healthy}/${uniqueTargets.length} services healthy` });
171
+ spinner.stop();
172
+ if (opts.save)
173
+ await saveFile("services-health", opts, res);
174
+ }
175
+ catch (e) {
176
+ cliError(e.message || "Health check failed", EXIT_GENERAL_ERROR);
177
+ }
178
+ });
179
+ // ay services describe <module>
180
+ svc
181
+ .command("describe <module>")
182
+ .alias("desc")
183
+ .description("Show detailed info about a module/service")
184
+ .action(async (moduleName, options) => {
185
+ try {
186
+ const opts = { ...program.opts(), ...options };
187
+ spinner.start({ text: `Describing ${moduleName}...`, color: "magenta" });
188
+ // Find matching entries
189
+ const apis = aYOUneModules.filter((m) => m.module === moduleName);
190
+ const svcs = aYOUneServices.filter((s) => s.module === moduleName);
191
+ // Fetch endpoint count from apiactions
192
+ const res = await apiCallHandler("config", "ayouneapiactions", "get", null, {
193
+ limit: 500,
194
+ responseFormat: "json",
195
+ });
196
+ const allActions = Array.isArray(res === null || res === void 0 ? void 0 : res.payload) ? res.payload : [];
197
+ const moduleActions = allActions.filter((a) => a.nameSpace === moduleName && !a.deprecated);
198
+ const methods = {};
199
+ for (const a of moduleActions) {
200
+ const m = (a.method || "GET").toUpperCase();
201
+ methods[m] = (methods[m] || 0) + 1;
202
+ }
203
+ const description = {
204
+ module: moduleName,
205
+ apis: apis.map((a) => ({ label: a.label, host: a.host })),
206
+ services: svcs.map((s) => ({ label: s.label, host: s.host })),
207
+ endpoints: {
208
+ total: moduleActions.length,
209
+ byMethod: methods,
210
+ },
211
+ capabilities: [...new Set(moduleActions.map((a) => a.capability).filter(Boolean))].sort(),
212
+ };
213
+ const formattedRes = {
214
+ payload: description,
215
+ meta: { responseTime: 0 },
216
+ };
217
+ handleResponseFormatOptions(opts, formattedRes);
218
+ spinner.success({ text: `Module: ${moduleName} — ${moduleActions.length} endpoints` });
219
+ spinner.stop();
220
+ }
221
+ catch (e) {
222
+ cliError(e.message || "Failed to describe service", EXIT_GENERAL_ERROR);
223
+ }
224
+ });
225
+ }
@@ -0,0 +1,305 @@
1
+ import chalk from "chalk";
2
+ import inquirer from "inquirer";
3
+ import { writeFile, mkdir } from "fs/promises";
4
+ import { existsSync } from "fs";
5
+ import path from "path";
6
+ import { spinner } from "../../index.js";
7
+ import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
8
+ import { cliError } from "../helpers/cliError.js";
9
+ const AVAILABLE_MODULES = [
10
+ { name: "CRM", value: "crm", description: "Customer Relationship Management" },
11
+ { name: "Marketing", value: "marketing", description: "Marketing automation & campaigns" },
12
+ { name: "HR", value: "hr", description: "Human Resources" },
13
+ { name: "E-Commerce", value: "ecommerce", description: "Online shop & payments" },
14
+ { name: "Project Management", value: "pm", description: "Projects & tasks" },
15
+ { name: "DevOps", value: "devops", description: "Deployment & infrastructure" },
16
+ { name: "Accounting", value: "accounting", description: "Financial accounting" },
17
+ { name: "Automation", value: "automation", description: "Workflow automation" },
18
+ { name: "Support", value: "support", description: "Customer support & ticketing" },
19
+ { name: "Content", value: "content", description: "Content management" },
20
+ { name: "Communication", value: "communication", description: "Messaging & notifications" },
21
+ { name: "Reporting", value: "reporting", description: "Reports & analytics" },
22
+ { name: "Monitoring", value: "monitoring", description: "Platform monitoring" },
23
+ ];
24
+ function generateEnvFile(answers) {
25
+ const modules = answers.modules;
26
+ const lines = [
27
+ "# =============================================================================",
28
+ "# aYOUne Platform Configuration",
29
+ `# Generated by ay setup on ${new Date().toISOString()}`,
30
+ "# =============================================================================",
31
+ "",
32
+ "# -- Core --",
33
+ `NODE_ENV=production`,
34
+ `PORT=3000`,
35
+ "",
36
+ "# -- License --",
37
+ `AYOUNE_LICENSE_KEY=${answers.licenseKey}`,
38
+ "",
39
+ "# -- Database --",
40
+ `MONGO_CONNECTSTRING=${answers.mongoUri}`,
41
+ `MONGO_CONNECTSTRING_AGGREGATION=${answers.mongoUri}`,
42
+ "",
43
+ "# -- Redis --",
44
+ `JOB_REDIS_HOST=${answers.redisHost}`,
45
+ `JOB_REDIS_PORT=${answers.redisPort}`,
46
+ `JOB_REDIS_PASS=${answers.redisPass}`,
47
+ `CACHE_REDIS_HOST=${answers.redisHost}`,
48
+ `CACHE_REDIS_PORT=${answers.redisPort}`,
49
+ `CACHE_REDIS_PASS=${answers.redisPass}`,
50
+ "",
51
+ "# -- Security --",
52
+ `JWT_SECRET=${answers.jwtSecret}`,
53
+ `JWT_SIGN=${answers.jwtSecret}`,
54
+ `JWT_sign=${answers.jwtSecret}`,
55
+ `JWT_AUDIENCE=ayoune.app`,
56
+ `JWT_ISSUER=ayoune.app`,
57
+ `JWT_EXPIRATION=360000`,
58
+ `COOKIE_KEY=ayouneSession`,
59
+ `COOKIE_SECRET=${generateRandomString(32)}`,
60
+ `SALT_WORK_FACTOR=10`,
61
+ `SECURITY_ALGORITHM=aes-256-ctr`,
62
+ `SECURITY_HASH=${generateRandomString(32)}`,
63
+ "",
64
+ "# -- Email / SMTP --",
65
+ `SMTP_HOST=${answers.smtpHost}`,
66
+ `SMTP_PORT=${answers.smtpPort}`,
67
+ `SMTP_USER=${answers.smtpUser}`,
68
+ `SMTP_PASS=${answers.smtpPass}`,
69
+ `MAIL_HOST=${answers.smtpHost}`,
70
+ `MAIL_USER=${answers.smtpUser}`,
71
+ `MAIL_PASS=${answers.smtpPass}`,
72
+ "",
73
+ "# -- Domain --",
74
+ `frontendURL=https://${answers.domain}`,
75
+ `hostURL=https://api-v1.${answers.domain}`,
76
+ `authHost=https://auth.${answers.domain}`,
77
+ `apiHost=https://api.${answers.domain}`,
78
+ `apiConfig=https://config-api.${answers.domain}`,
79
+ `NOTIFIER=https://notifier.${answers.domain}`,
80
+ "",
81
+ "# -- Enabled Modules --",
82
+ `# Profiles: core,${modules.join(",")}`,
83
+ ];
84
+ // Add module-specific host URLs
85
+ for (const mod of modules) {
86
+ lines.push(`api${capitalize(mod)}=https://${mod}-api.${answers.domain}`);
87
+ }
88
+ lines.push("");
89
+ lines.push("# -- Monitoring --");
90
+ lines.push(`MONGO_PASSWORD=changeme`);
91
+ lines.push("");
92
+ return lines.join("\n");
93
+ }
94
+ function generateHelmValues(answers) {
95
+ const enabledModules = {};
96
+ for (const mod of AVAILABLE_MODULES) {
97
+ enabledModules[mod.value] = answers.modules.includes(mod.value);
98
+ }
99
+ const values = {
100
+ global: {
101
+ domain: answers.domain,
102
+ mongodb: { uri: answers.mongoUri },
103
+ redis: {
104
+ host: answers.redisHost,
105
+ port: parseInt(answers.redisPort),
106
+ password: answers.redisPass,
107
+ },
108
+ jwt: { secret: answers.jwtSecret },
109
+ smtp: {
110
+ host: answers.smtpHost,
111
+ port: parseInt(answers.smtpPort),
112
+ user: answers.smtpUser,
113
+ password: answers.smtpPass,
114
+ },
115
+ licenseKey: answers.licenseKey,
116
+ },
117
+ modules: enabledModules,
118
+ };
119
+ // Simple YAML serialization (avoid adding js-yaml dependency just for this)
120
+ return serializeYaml(values, 0);
121
+ }
122
+ function serializeYaml(obj, indent) {
123
+ const prefix = " ".repeat(indent);
124
+ const lines = [];
125
+ for (const [key, value] of Object.entries(obj)) {
126
+ if (value === null || value === undefined)
127
+ continue;
128
+ if (typeof value === "object" && !Array.isArray(value)) {
129
+ lines.push(`${prefix}${key}:`);
130
+ lines.push(serializeYaml(value, indent + 1));
131
+ }
132
+ else if (typeof value === "boolean") {
133
+ lines.push(`${prefix}${key}: ${value}`);
134
+ }
135
+ else if (typeof value === "number") {
136
+ lines.push(`${prefix}${key}: ${value}`);
137
+ }
138
+ else {
139
+ lines.push(`${prefix}${key}: "${value}"`);
140
+ }
141
+ }
142
+ return lines.join("\n");
143
+ }
144
+ function generateRandomString(length) {
145
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
146
+ let result = "";
147
+ for (let i = 0; i < length; i++) {
148
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
149
+ }
150
+ return result;
151
+ }
152
+ function capitalize(s) {
153
+ return s.charAt(0).toUpperCase() + s.slice(1);
154
+ }
155
+ export function createSetupCommand(program) {
156
+ program
157
+ .command("setup")
158
+ .description("Interactive setup wizard for self-hosted aYOUne deployments")
159
+ .addHelpText("after", `
160
+ Examples:
161
+ ay setup Start interactive setup wizard
162
+ ay setup --output ./config Generate files in ./config directory`)
163
+ .option("-o, --output <dir>", "Output directory for generated files", ".")
164
+ .action(async (options) => {
165
+ try {
166
+ console.log(chalk.cyan.bold("\n aYOUne Self-Hosted Setup Wizard\n") +
167
+ chalk.dim(" This wizard will generate configuration files for your deployment.\n"));
168
+ const answers = await inquirer.prompt([
169
+ {
170
+ type: "list",
171
+ name: "deploymentMode",
172
+ message: "Deployment mode:",
173
+ choices: [
174
+ { name: "Docker Compose (single server)", value: "compose" },
175
+ { name: "Kubernetes (Helm chart)", value: "kubernetes" },
176
+ ],
177
+ },
178
+ {
179
+ type: "input",
180
+ name: "domain",
181
+ message: "Your domain (e.g. ayoune.example.com):",
182
+ validate: (v) => v.length > 0 || "Domain is required",
183
+ },
184
+ {
185
+ type: "input",
186
+ name: "mongoUri",
187
+ message: "MongoDB connection string:",
188
+ default: "mongodb://ayoune:changeme@mongodb:27017/ayoune?authSource=admin&retryWrites=true&w=majority",
189
+ },
190
+ {
191
+ type: "input",
192
+ name: "redisHost",
193
+ message: "Redis host:",
194
+ default: "redis",
195
+ },
196
+ {
197
+ type: "input",
198
+ name: "redisPort",
199
+ message: "Redis port:",
200
+ default: "6379",
201
+ },
202
+ {
203
+ type: "password",
204
+ name: "redisPass",
205
+ message: "Redis password:",
206
+ default: "",
207
+ },
208
+ {
209
+ type: "password",
210
+ name: "jwtSecret",
211
+ message: "JWT secret (leave blank to auto-generate):",
212
+ default: "",
213
+ },
214
+ {
215
+ type: "input",
216
+ name: "smtpHost",
217
+ message: "SMTP host:",
218
+ default: "smtp.example.com",
219
+ },
220
+ {
221
+ type: "input",
222
+ name: "smtpPort",
223
+ message: "SMTP port:",
224
+ default: "587",
225
+ },
226
+ {
227
+ type: "input",
228
+ name: "smtpUser",
229
+ message: "SMTP username:",
230
+ default: "",
231
+ },
232
+ {
233
+ type: "password",
234
+ name: "smtpPass",
235
+ message: "SMTP password:",
236
+ default: "",
237
+ },
238
+ {
239
+ type: "input",
240
+ name: "licenseKey",
241
+ message: "License key (leave blank for 14-day trial):",
242
+ default: "",
243
+ },
244
+ {
245
+ type: "checkbox",
246
+ name: "modules",
247
+ message: "Select modules to enable:",
248
+ choices: AVAILABLE_MODULES.map((m) => ({
249
+ name: `${m.name} — ${m.description}`,
250
+ value: m.value,
251
+ checked: ["crm"].includes(m.value),
252
+ })),
253
+ validate: (v) => v.length > 0 || "Select at least one module",
254
+ },
255
+ ]);
256
+ // Auto-generate JWT secret if not provided
257
+ if (!answers.jwtSecret) {
258
+ answers.jwtSecret = generateRandomString(64);
259
+ }
260
+ answers.outputDir = options.output;
261
+ spinner.start({ text: "Generating configuration files...", color: "cyan" });
262
+ const outputDir = path.resolve(answers.outputDir);
263
+ if (!existsSync(outputDir)) {
264
+ await mkdir(outputDir, { recursive: true });
265
+ }
266
+ // Generate .env
267
+ const envContent = generateEnvFile(answers);
268
+ const envPath = path.join(outputDir, ".env");
269
+ await writeFile(envPath, envContent, "utf-8");
270
+ if (answers.deploymentMode === "kubernetes") {
271
+ // Generate values.yaml for Helm
272
+ const valuesContent = generateHelmValues(answers);
273
+ const valuesPath = path.join(outputDir, "values.yaml");
274
+ await writeFile(valuesPath, valuesContent, "utf-8");
275
+ spinner.success({ text: "Configuration files generated!" });
276
+ spinner.stop();
277
+ console.log(chalk.green("\n Generated files:"));
278
+ console.log(chalk.dim(` ${envPath}`));
279
+ console.log(chalk.dim(` ${valuesPath}`));
280
+ console.log(chalk.cyan("\n Next steps:"));
281
+ console.log(chalk.dim(" 1. Review and adjust the generated files"));
282
+ console.log(chalk.dim(" 2. helm install ayoune tolinax/ayoune -f values.yaml"));
283
+ console.log(chalk.dim(" 3. ay status — verify all services are healthy\n"));
284
+ }
285
+ else {
286
+ spinner.success({ text: "Configuration files generated!" });
287
+ spinner.stop();
288
+ const profiles = ["core", ...answers.modules].join(",");
289
+ console.log(chalk.green("\n Generated files:"));
290
+ console.log(chalk.dim(` ${envPath}`));
291
+ console.log(chalk.cyan("\n Next steps:"));
292
+ console.log(chalk.dim(" 1. Review and adjust the .env file"));
293
+ console.log(chalk.dim(` 2. docker compose --profile ${profiles} up -d`));
294
+ console.log(chalk.dim(" 3. ay status — verify all services are healthy\n"));
295
+ }
296
+ if (!answers.licenseKey) {
297
+ console.log(chalk.yellow(" Note: Running in 14-day trial mode.") +
298
+ chalk.dim(" Set AYOUNE_LICENSE_KEY to activate your license.\n"));
299
+ }
300
+ }
301
+ catch (e) {
302
+ cliError(e.message || "Setup failed", EXIT_GENERAL_ERROR);
303
+ }
304
+ });
305
+ }
@@ -0,0 +1,160 @@
1
+ import chalk from "chalk";
2
+ import { spinner } from "../../index.js";
3
+ import { api, getModuleBaseUrl } from "../api/apiClient.js";
4
+ import { secureStorage } from "../helpers/secureStorage.js";
5
+ import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
6
+ import { cliError } from "../helpers/cliError.js";
7
+ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
8
+ const CORE_SERVICES = [
9
+ { name: "config-api", module: "config" },
10
+ { name: "auth", module: "auth" },
11
+ ];
12
+ const MODULE_SERVICES = {
13
+ crm: [{ name: "crm-api", module: "crm" }],
14
+ marketing: [{ name: "marketing-api", module: "marketing" }],
15
+ hr: [{ name: "hr-api", module: "hr" }],
16
+ ecommerce: [{ name: "ecommerce-api", module: "ecommerce" }],
17
+ pm: [{ name: "pm-api", module: "pm" }],
18
+ devops: [{ name: "devops-api", module: "devops" }],
19
+ accounting: [{ name: "accounting-api", module: "accounting" }],
20
+ automation: [{ name: "automation-api", module: "automation" }],
21
+ support: [{ name: "support-api", module: "support" }],
22
+ reporting: [{ name: "reporting-api", module: "reporting" }],
23
+ monitoring: [{ name: "monitoring-api", module: "monitoring" }],
24
+ };
25
+ function extractVersion(data) {
26
+ var _a, _b, _c, _d, _e;
27
+ return ((_b = (_a = data === null || data === void 0 ? void 0 : data.meta) === null || _a === void 0 ? void 0 : _a.version) === null || _b === void 0 ? void 0 : _b.core) || ((_d = (_c = data === null || data === void 0 ? void 0 : data.meta) === null || _c === void 0 ? void 0 : _c.version) === null || _d === void 0 ? void 0 : _d.host) || (data === null || data === void 0 ? void 0 : data.version) || ((_e = data === null || data === void 0 ? void 0 : data.payload) === null || _e === void 0 ? void 0 : _e.version) || undefined;
28
+ }
29
+ async function checkService(name, module) {
30
+ const baseUrl = getModuleBaseUrl(module);
31
+ const start = Date.now();
32
+ try {
33
+ const response = await api({
34
+ baseURL: baseUrl,
35
+ method: "get",
36
+ url: "/",
37
+ timeout: 10000,
38
+ headers: {
39
+ Authorization: `Bearer ${secureStorage.getItem("token") || ""}`,
40
+ },
41
+ });
42
+ const responseTime = Date.now() - start;
43
+ return {
44
+ service: name,
45
+ url: baseUrl,
46
+ status: "healthy",
47
+ responseTime,
48
+ version: extractVersion(response.data),
49
+ };
50
+ }
51
+ catch (e) {
52
+ const responseTime = Date.now() - start;
53
+ if (e.response) {
54
+ const data = e.response.data;
55
+ const version = extractVersion(data);
56
+ // A structured JSON response (even 404) with version info means the service is running
57
+ if (e.response.status < 500 && version) {
58
+ return {
59
+ service: name,
60
+ url: baseUrl,
61
+ status: "healthy",
62
+ responseTime,
63
+ version,
64
+ };
65
+ }
66
+ return {
67
+ service: name,
68
+ url: baseUrl,
69
+ status: "unhealthy",
70
+ responseTime,
71
+ error: `HTTP ${e.response.status}`,
72
+ };
73
+ }
74
+ return {
75
+ service: name,
76
+ url: baseUrl,
77
+ status: "unreachable",
78
+ responseTime,
79
+ error: e.message || "Connection failed",
80
+ };
81
+ }
82
+ }
83
+ export function createStatusCommand(program) {
84
+ program
85
+ .command("status")
86
+ .description("Check health status of aYOUne platform services")
87
+ .addHelpText("after", `
88
+ Examples:
89
+ ay status Check all reachable services
90
+ ay status --module crm Check only CRM services
91
+ ay status -r json Output as JSON (for scripting)
92
+ ay status -r table Output as table`)
93
+ .option("--module <name>", "Check only a specific module")
94
+ .action(async (options) => {
95
+ try {
96
+ const opts = { ...program.opts(), ...options };
97
+ spinner.start({ text: "Checking platform health...", color: "cyan" });
98
+ const servicesToCheck = [];
99
+ // Always check core
100
+ servicesToCheck.push(...CORE_SERVICES);
101
+ if (opts.module) {
102
+ const moduleServices = MODULE_SERVICES[opts.module];
103
+ if (moduleServices) {
104
+ servicesToCheck.push(...moduleServices);
105
+ }
106
+ else {
107
+ cliError(`Unknown module: ${opts.module}`, EXIT_GENERAL_ERROR);
108
+ }
109
+ }
110
+ else {
111
+ // Check all known modules
112
+ for (const services of Object.values(MODULE_SERVICES)) {
113
+ servicesToCheck.push(...services);
114
+ }
115
+ }
116
+ // Check services concurrently
117
+ const results = await Promise.all(servicesToCheck.map((svc) => checkService(svc.name, svc.module)));
118
+ spinner.stop();
119
+ const healthy = results.filter((r) => r.status === "healthy").length;
120
+ const unhealthy = results.filter((r) => r.status === "unhealthy").length;
121
+ const unreachable = results.filter((r) => r.status === "unreachable").length;
122
+ // Format output based on response format
123
+ if (opts.responseFormat === "json") {
124
+ const output = {
125
+ payload: results,
126
+ meta: {
127
+ total: results.length,
128
+ healthy,
129
+ unhealthy,
130
+ unreachable,
131
+ checkedAt: new Date().toISOString(),
132
+ },
133
+ };
134
+ handleResponseFormatOptions(opts, output);
135
+ }
136
+ else {
137
+ // Pretty terminal output
138
+ console.log(chalk.cyan.bold("\n aYOUne Platform Status\n"));
139
+ for (const result of results) {
140
+ const icon = result.status === "healthy" ? chalk.green("●") : result.status === "unhealthy" ? chalk.yellow("●") : chalk.red("●");
141
+ const time = result.responseTime ? chalk.dim(` (${result.responseTime}ms)`) : "";
142
+ const ver = result.version ? chalk.dim(` v${result.version}`) : "";
143
+ const err = result.error ? chalk.red(` — ${result.error}`) : "";
144
+ console.log(` ${icon} ${chalk.white(result.service)}${ver}${time}${err}`);
145
+ }
146
+ console.log("");
147
+ console.log(` ${chalk.green(healthy + " healthy")}` +
148
+ (unhealthy > 0 ? ` ${chalk.yellow(unhealthy + " unhealthy")}` : "") +
149
+ (unreachable > 0 ? ` ${chalk.red(unreachable + " unreachable")}` : ""));
150
+ console.log("");
151
+ }
152
+ if (unhealthy > 0 || unreachable > 0) {
153
+ process.exit(EXIT_GENERAL_ERROR);
154
+ }
155
+ }
156
+ catch (e) {
157
+ cliError(e.message || "Status check failed", EXIT_GENERAL_ERROR);
158
+ }
159
+ });
160
+ }