@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,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
|
+
}
|