@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
@@ -0,0 +1,189 @@
1
+ export const contextSlots = [
2
+ // Tier 1 — Core
3
+ {
4
+ slot: "project",
5
+ collection: "projects",
6
+ module: "pm",
7
+ nameField: "subject",
8
+ injectAsCreateField: {
9
+ tasks: "project",
10
+ sprints: "_project",
11
+ milestones: "_project",
12
+ epics: "_project",
13
+ },
14
+ injectAsFilterField: {
15
+ tasks: "project",
16
+ sprints: "_project",
17
+ milestones: "_project",
18
+ epics: "_project",
19
+ },
20
+ tier: 1,
21
+ },
22
+ {
23
+ slot: "consumer",
24
+ collection: "consumers",
25
+ module: "crm",
26
+ nameField: "name",
27
+ injectAsCreateField: {
28
+ tasks: "_consumerID",
29
+ opportunities: "_consumerID",
30
+ offers: "_consumerID",
31
+ invoices: "_consumerID",
32
+ orders: "_consumerID",
33
+ },
34
+ injectAsFilterField: {
35
+ tasks: "_consumerID",
36
+ opportunities: "_consumerID",
37
+ offers: "_consumerID",
38
+ invoices: "_consumerID",
39
+ orders: "_consumerID",
40
+ },
41
+ tier: 1,
42
+ },
43
+ {
44
+ slot: "sprint",
45
+ collection: "sprints",
46
+ module: "pm",
47
+ nameField: "subject",
48
+ injectAsCreateField: {
49
+ tasks: "sprint",
50
+ },
51
+ injectAsFilterField: {
52
+ tasks: "sprint",
53
+ },
54
+ parent: "project",
55
+ tier: 1,
56
+ },
57
+ {
58
+ slot: "milestone",
59
+ collection: "milestones",
60
+ module: "pm",
61
+ nameField: "subject",
62
+ injectAsCreateField: {
63
+ tasks: "milestone",
64
+ sprints: "_milestone",
65
+ epics: "_milestone",
66
+ },
67
+ injectAsFilterField: {
68
+ tasks: "milestone",
69
+ sprints: "_milestone",
70
+ epics: "_milestone",
71
+ },
72
+ parent: "project",
73
+ tier: 1,
74
+ },
75
+ {
76
+ slot: "epic",
77
+ collection: "epics",
78
+ module: "pm",
79
+ nameField: "subject",
80
+ injectAsCreateField: {
81
+ tasks: "epic",
82
+ sprints: "_epic",
83
+ },
84
+ injectAsFilterField: {
85
+ tasks: "epic",
86
+ sprints: "_epic",
87
+ },
88
+ parent: "project",
89
+ tier: 1,
90
+ },
91
+ // Tier 2 — Domain
92
+ {
93
+ slot: "campaign",
94
+ collection: "campaigns",
95
+ module: "marketing",
96
+ nameField: "name",
97
+ injectAsCreateField: {
98
+ mailinglists: "_campaign",
99
+ },
100
+ injectAsFilterField: {
101
+ mailinglists: "_campaign",
102
+ },
103
+ tier: 2,
104
+ },
105
+ {
106
+ slot: "opportunity",
107
+ collection: "opportunities",
108
+ module: "crm",
109
+ nameField: "name",
110
+ injectAsCreateField: {},
111
+ injectAsFilterField: {},
112
+ tier: 2,
113
+ },
114
+ {
115
+ slot: "team",
116
+ collection: "teams",
117
+ module: "hr",
118
+ nameField: "name",
119
+ injectAsCreateField: {},
120
+ injectAsFilterField: {},
121
+ tier: 2,
122
+ },
123
+ {
124
+ slot: "department",
125
+ collection: "departments",
126
+ module: "hr",
127
+ nameField: "name",
128
+ injectAsCreateField: {
129
+ teams: "_department",
130
+ },
131
+ injectAsFilterField: {
132
+ teams: "_department",
133
+ },
134
+ tier: 2,
135
+ },
136
+ {
137
+ slot: "kanbanboard",
138
+ collection: "kanbanboards",
139
+ module: "pm",
140
+ nameField: "name",
141
+ injectAsCreateField: {},
142
+ injectAsFilterField: {},
143
+ tier: 2,
144
+ },
145
+ // Tier 3 — AI/Auto
146
+ {
147
+ slot: "agent",
148
+ collection: "aiagents",
149
+ module: "ai",
150
+ nameField: "name",
151
+ injectAsCreateField: {
152
+ aiconversations: "_agent",
153
+ },
154
+ injectAsFilterField: {
155
+ aiconversations: "_agent",
156
+ },
157
+ tier: 3,
158
+ },
159
+ {
160
+ slot: "workflow",
161
+ collection: "automationworkflows",
162
+ module: "automation",
163
+ nameField: "name",
164
+ injectAsCreateField: {},
165
+ injectAsFilterField: {},
166
+ tier: 3,
167
+ },
168
+ {
169
+ slot: "repository",
170
+ collection: "repositories",
171
+ module: "devops",
172
+ nameField: "name",
173
+ injectAsCreateField: {},
174
+ injectAsFilterField: {},
175
+ tier: 3,
176
+ },
177
+ ];
178
+ export function getSlot(slotName) {
179
+ return contextSlots.find((s) => s.slot === slotName);
180
+ }
181
+ export function getSlotByCollection(collection) {
182
+ return contextSlots.find((s) => s.collection === collection.toLowerCase());
183
+ }
184
+ export function getChildSlots(parentSlot) {
185
+ return contextSlots.filter((s) => s.parent === parentSlot);
186
+ }
187
+ export function getSlotsByTier(tier) {
188
+ return contextSlots.filter((s) => s.tier === tier);
189
+ }
@@ -3184,6 +3184,62 @@ const modelsAndRights = [
3184
3184
  module: "crm",
3185
3185
  right: "crm.xingcontacts",
3186
3186
  },
3187
+ // Config module additions
3188
+ {
3189
+ plural: "Credentials",
3190
+ singular: "Credential",
3191
+ module: "config",
3192
+ right: "config.credentials",
3193
+ },
3194
+ {
3195
+ plural: "NotificationPolicies",
3196
+ singular: "NotificationPolicy",
3197
+ module: "config",
3198
+ right: "config.notificationpolicies",
3199
+ },
3200
+ {
3201
+ plural: "MessageTemplates",
3202
+ singular: "MessageTemplate",
3203
+ module: "config",
3204
+ right: "config.messagetemplates",
3205
+ },
3206
+ // DevOps module
3207
+ {
3208
+ plural: "K8Pods",
3209
+ singular: "K8Pod",
3210
+ module: "devops",
3211
+ right: "devops.k8pods",
3212
+ },
3213
+ {
3214
+ plural: "BitbucketPipelines",
3215
+ singular: "BitbucketPipeline",
3216
+ module: "devops",
3217
+ right: "devops.bitbucketpipelines",
3218
+ },
3219
+ {
3220
+ plural: "DevOpsAlerts",
3221
+ singular: "DevOpsAlert",
3222
+ module: "devops",
3223
+ right: "devops.devopsalerts",
3224
+ },
3225
+ {
3226
+ plural: "DeploymentActions",
3227
+ singular: "DeploymentAction",
3228
+ module: "devops",
3229
+ right: "devops.deploymentactions",
3230
+ },
3231
+ {
3232
+ plural: "Repositories",
3233
+ singular: "Repository",
3234
+ module: "devops",
3235
+ right: "devops.repositories",
3236
+ },
3237
+ {
3238
+ plural: "DeploymentPlans",
3239
+ singular: "DeploymentPlan",
3240
+ module: "devops",
3241
+ right: "devops.deploymentplans",
3242
+ },
3187
3243
  ];
3188
3244
  export default modelsAndRights;
3189
3245
  export { modelsAndRights };
package/data/modules.js CHANGED
@@ -106,6 +106,22 @@ const aYOUneModules = [
106
106
  module: "audit",
107
107
  host: "audit.ayoune.app",
108
108
  },
109
+ {
110
+ label: "DevOps",
111
+ module: "devops",
112
+ host: "devops.ayoune.app",
113
+ },
114
+ {
115
+ label: "Sync",
116
+ module: "sync",
117
+ host: "sync.ayoune.app",
118
+ },
119
+ {
120
+ label: "System (Superuser)",
121
+ module: "su",
122
+ host: "api-v1.ayoune.app",
123
+ superuserOnly: true,
124
+ },
109
125
  ];
110
126
  export default aYOUneModules;
111
127
  export { aYOUneModules };
@@ -1,7 +1,7 @@
1
1
  import { api, getModuleBaseUrl } from "./apiClient.js";
2
2
  import chalk from "chalk";
3
3
  import * as process from "process";
4
- import { localStorage } from "../helpers/localStorage.js";
4
+ import { secureStorage } from "../helpers/secureStorage.js";
5
5
  import { handleAPIError } from "./handleAPIError.js";
6
6
  import { login } from "./login.js";
7
7
  import { spinner } from "../../index.js";
@@ -16,6 +16,10 @@ function cleanParams(params) {
16
16
  continue;
17
17
  cleaned[key] = value;
18
18
  }
19
+ // The API only supports json, yaml, csv — table is formatted client-side from JSON
20
+ if (cleaned.responseFormat === "table") {
21
+ cleaned.responseFormat = "json";
22
+ }
19
23
  const version = process.env.AYOUNE_VERSION;
20
24
  if (version) {
21
25
  cleaned.ref = `ayoune-cli@${version}`;
@@ -23,7 +27,7 @@ function cleanParams(params) {
23
27
  return cleaned;
24
28
  }
25
29
  function getToken() {
26
- return localStorage.getItem("token") || process.env.AYOUNE_TOKEN || "";
30
+ return secureStorage.getItem("token") || process.env.AYOUNE_TOKEN || "";
27
31
  }
28
32
  const makeRequest = (module, url, method, data, params) => api({
29
33
  baseURL: getModuleBaseUrl(module),
@@ -21,8 +21,16 @@ const audit = axios.create({
21
21
  baseURL: config.auditUrl,
22
22
  httpsAgent,
23
23
  });
24
+ // Modules that don't follow the standard {module}-api.ayoune.app pattern
25
+ const MODULE_HOST_OVERRIDES = {
26
+ su: "https://api-v1.ayoune.app/api/su",
27
+ logs: "https://monitoring-api.ayoune.app",
28
+ general: "https://api-v1.ayoune.app/api/general",
29
+ usersettings: "https://api-v1.ayoune.app/api/usersettings",
30
+ auth: "https://auth.ayoune.app",
31
+ };
24
32
  export function getModuleBaseUrl(module) {
25
- return `https://${module}-api.ayoune.app`;
33
+ return MODULE_HOST_OVERRIDES[module] || `https://${module}-api.ayoune.app`;
26
34
  }
27
35
  function addDebugInterceptors(instance) {
28
36
  instance.interceptors.request.use((req) => {
@@ -1,6 +1,6 @@
1
1
  import { audit } from "./apiClient.js";
2
2
  import * as process from "process";
3
- import { localStorage } from "../helpers/localStorage.js";
3
+ import { secureStorage } from "../helpers/secureStorage.js";
4
4
  import { handleAPIError } from "./handleAPIError.js";
5
5
  export const auditCallHandler = async (url, method = "get", data = null, params = {}) => {
6
6
  try {
@@ -10,7 +10,7 @@ export const auditCallHandler = async (url, method = "get", data = null, params
10
10
  data,
11
11
  params: { ...params, ref: `ayoune-cli@${process.env.AYOUNE_VERSION}` },
12
12
  headers: {
13
- Authorization: `Bearer ${localStorage.getItem("token")}`,
13
+ Authorization: `Bearer ${secureStorage.getItem("token")}`,
14
14
  },
15
15
  });
16
16
  return response.data;
@@ -1,11 +1,8 @@
1
1
  import { spinner } from "../../index.js";
2
2
  import { EXIT_GENERAL_ERROR, EXIT_AUTH_REQUIRED, EXIT_PERMISSION_DENIED, } from "../exitCodes.js";
3
- let jsonErrorsEnabled = false;
4
- export function enableJsonErrors() {
5
- jsonErrorsEnabled = true;
6
- }
3
+ import { isJsonErrorsEnabled } from "../helpers/cliError.js";
7
4
  function emitJsonError(code, type, message, details) {
8
- if (jsonErrorsEnabled) {
5
+ if (isJsonErrorsEnabled()) {
9
6
  const err = { error: type, code, message, ...(details ? { details } : {}) };
10
7
  console.error(JSON.stringify(err));
11
8
  }
@@ -13,6 +10,7 @@ function emitJsonError(code, type, message, details) {
13
10
  }
14
11
  export function handleAPIError(error) {
15
12
  var _a, _b, _c, _d;
13
+ const jsonErrors = isJsonErrorsEnabled();
16
14
  const apiError = error.response;
17
15
  const requestConfig = error.config;
18
16
  const method = ((requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.method) || "GET").toUpperCase();
@@ -20,35 +18,39 @@ export function handleAPIError(error) {
20
18
  ? `${requestConfig.baseURL || ""}/${requestConfig.url || ""}`.replace(/\/+/g, "/").replace(":/", "://")
21
19
  : "unknown";
22
20
  if (!apiError) {
23
- spinner.error({ text: `Network error: ${error.message}` });
24
- if (!jsonErrorsEnabled)
21
+ if (!jsonErrors) {
22
+ spinner.error({ text: `Network error: ${error.message}` });
25
23
  console.error(` Request: ${method} ${fullUrl}`);
24
+ }
26
25
  emitJsonError(EXIT_GENERAL_ERROR, "network_error", error.message, { method, url: fullUrl });
27
26
  }
28
27
  const serverMessage = ((_b = (_a = apiError.data) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.message) || apiError.statusText;
29
28
  if (apiError.status === 400) {
30
- spinner.error({ text: `Bad Request: ${serverMessage}` });
31
- if (!jsonErrorsEnabled)
29
+ if (!jsonErrors) {
30
+ spinner.error({ text: `Bad Request: ${serverMessage}` });
32
31
  console.error(` Request: ${method} ${fullUrl}`);
32
+ }
33
33
  emitJsonError(EXIT_GENERAL_ERROR, "bad_request", serverMessage, { method, url: fullUrl, status: 400 });
34
34
  }
35
35
  if (apiError.status === 401) {
36
- spinner.error({ text: "Session expired. Please run: ay login" });
36
+ if (!jsonErrors)
37
+ spinner.error({ text: "Session expired. Please run: ay login" });
37
38
  emitJsonError(EXIT_AUTH_REQUIRED, "auth_required", "Session expired", { status: 401 });
38
39
  }
39
40
  if (apiError.status === 403) {
40
41
  const rights = ((_d = (_c = apiError.data) === null || _c === void 0 ? void 0 : _c.meta) === null || _d === void 0 ? void 0 : _d.requiredRights) || [];
41
- spinner.error({
42
- text: `Forbidden. Required rights: [${rights.join(", ")}]`,
43
- });
44
- if (!jsonErrorsEnabled)
42
+ if (!jsonErrors) {
43
+ spinner.error({
44
+ text: `Forbidden. Required rights: [${rights.join(", ")}]`,
45
+ });
45
46
  console.error(` Request: ${method} ${fullUrl}`);
47
+ }
46
48
  emitJsonError(EXIT_PERMISSION_DENIED, "permission_denied", "Forbidden", { method, url: fullUrl, status: 403, requiredRights: rights });
47
49
  }
48
- spinner.error({
49
- text: `Error ${apiError.status}: ${serverMessage}`,
50
- });
51
- if (!jsonErrorsEnabled) {
50
+ if (!jsonErrors) {
51
+ spinner.error({
52
+ text: `Error ${apiError.status}: ${serverMessage}`,
53
+ });
52
54
  console.error(` Request: ${method} ${fullUrl}`);
53
55
  if (apiError.data && typeof apiError.data === "object") {
54
56
  const debugInfo = apiError.data.meta || apiError.data;
package/lib/api/login.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { socket } from "../socket/socketClient.js";
2
2
  import { randomAsciiString } from "../helpers/makeRandomToken.js";
3
3
  import { spinner } from "../../index.js";
4
- import { localStorage } from "../helpers/localStorage.js";
4
+ import { secureStorage } from "../helpers/secureStorage.js";
5
5
  import { config } from "../helpers/config.js";
6
6
  import chalk from "chalk";
7
7
  function terminalLink(text, url) {
@@ -28,8 +28,8 @@ const waitForCredentials = async (token) => {
28
28
  });
29
29
  _socket.on("cli-token", async (data) => {
30
30
  spinner.success({ text: `Received auth credentials` });
31
- localStorage.setItem("token", data.token);
32
- localStorage.setItem("refreshToken", data.refreshToken);
31
+ secureStorage.setItem("token", data.token);
32
+ secureStorage.setItem("refreshToken", data.refreshToken);
33
33
  _socket.disconnect();
34
34
  resolve(data);
35
35
  });
@@ -0,0 +1,119 @@
1
+ import { api } from "./apiClient.js";
2
+ import { config } from "../helpers/config.js";
3
+ import { secureStorage } from "../helpers/secureStorage.js";
4
+ function getToken() {
5
+ return secureStorage.getItem("token") || process.env.AYOUNE_TOKEN || "";
6
+ }
7
+ function getSearchBaseURL() {
8
+ return config.searchUrl;
9
+ }
10
+ export async function searchModel(collection, params) {
11
+ const response = await api({
12
+ baseURL: getSearchBaseURL(),
13
+ method: "get",
14
+ url: `/${collection}`,
15
+ params,
16
+ headers: {
17
+ Authorization: `Bearer ${getToken()}`,
18
+ },
19
+ });
20
+ return response.data;
21
+ }
22
+ export async function searchOne(collection, params) {
23
+ const response = await api({
24
+ baseURL: getSearchBaseURL(),
25
+ method: "get",
26
+ url: `/${collection}/one`,
27
+ params,
28
+ headers: {
29
+ Authorization: `Bearer ${getToken()}`,
30
+ },
31
+ });
32
+ return response.data;
33
+ }
34
+ export async function searchGlobal(query, limit = 25, callbacks = {}) {
35
+ const results = [];
36
+ const response = await api({
37
+ baseURL: getSearchBaseURL(),
38
+ method: "get",
39
+ url: "/",
40
+ params: { q: query, limit },
41
+ headers: {
42
+ Authorization: `Bearer ${getToken()}`,
43
+ Accept: "text/event-stream",
44
+ },
45
+ responseType: "stream",
46
+ });
47
+ return new Promise((resolve, reject) => {
48
+ let buffer = "";
49
+ let currentCollection = "";
50
+ let currentEntries = [];
51
+ const stream = response.data;
52
+ stream.on("data", (chunk) => {
53
+ var _a, _b, _c;
54
+ buffer += chunk.toString();
55
+ const lines = buffer.split("\n");
56
+ buffer = lines.pop() || "";
57
+ for (const line of lines) {
58
+ if (line.startsWith("event: ")) {
59
+ const event = line.slice(7).trim();
60
+ if (event === "model_done" && currentCollection) {
61
+ const result = {
62
+ collection: currentCollection,
63
+ entries: currentEntries,
64
+ total: currentEntries.length,
65
+ };
66
+ results.push(result);
67
+ (_a = callbacks.onResult) === null || _a === void 0 ? void 0 : _a.call(callbacks, result);
68
+ currentCollection = "";
69
+ currentEntries = [];
70
+ }
71
+ else if (event === "error") {
72
+ // Error data comes in the next data: line
73
+ }
74
+ else if (event === "done") {
75
+ (_b = callbacks.onDone) === null || _b === void 0 ? void 0 : _b.call(callbacks);
76
+ }
77
+ }
78
+ else if (line.startsWith("data: ")) {
79
+ const dataStr = line.slice(6).trim();
80
+ try {
81
+ const data = JSON.parse(dataStr);
82
+ if (data.model) {
83
+ currentCollection = data.model;
84
+ }
85
+ else if (data.entry) {
86
+ currentEntries.push(data.entry);
87
+ }
88
+ else if (data.error) {
89
+ (_c = callbacks.onError) === null || _c === void 0 ? void 0 : _c.call(callbacks, data.error);
90
+ }
91
+ }
92
+ catch (_d) {
93
+ // Non-JSON data line, skip
94
+ }
95
+ }
96
+ }
97
+ });
98
+ stream.on("end", () => {
99
+ var _a, _b;
100
+ // Flush remaining buffer
101
+ if (currentCollection && currentEntries.length > 0) {
102
+ const result = {
103
+ collection: currentCollection,
104
+ entries: currentEntries,
105
+ total: currentEntries.length,
106
+ };
107
+ results.push(result);
108
+ (_a = callbacks.onResult) === null || _a === void 0 ? void 0 : _a.call(callbacks, result);
109
+ }
110
+ (_b = callbacks.onDone) === null || _b === void 0 ? void 0 : _b.call(callbacks);
111
+ resolve(results);
112
+ });
113
+ stream.on("error", (err) => {
114
+ var _a;
115
+ (_a = callbacks.onError) === null || _a === void 0 ? void 0 : _a.call(callbacks, err.message);
116
+ reject(err);
117
+ });
118
+ });
119
+ }
@@ -0,0 +1,126 @@
1
+ import chalk from "chalk";
2
+ import { modelsAndRights } from "../../data/modelsAndRights.js";
3
+ import { aYOUneModules } from "../../data/modules.js";
4
+ import { isSuperUser, getTokenPayload } from "../helpers/tokenPayload.js";
5
+ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
6
+ import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
7
+ import { cliError } from "../helpers/cliError.js";
8
+ function getAccessibleModules(su) {
9
+ var _a;
10
+ const moduleSummaries = [];
11
+ for (const mod of aYOUneModules) {
12
+ if (mod.superuserOnly && !su)
13
+ continue;
14
+ const collections = modelsAndRights.filter((m) => m.module === mod.module);
15
+ moduleSummaries.push({
16
+ module: mod.module,
17
+ label: mod.label,
18
+ collections: collections.length,
19
+ superuserOnly: (_a = mod.superuserOnly) !== null && _a !== void 0 ? _a : false,
20
+ });
21
+ }
22
+ return moduleSummaries;
23
+ }
24
+ function displayAccess(modules, su) {
25
+ const payload = getTokenPayload();
26
+ const name = (payload === null || payload === void 0 ? void 0 : payload.name) || (payload === null || payload === void 0 ? void 0 : payload.username) || (payload === null || payload === void 0 ? void 0 : payload.email) || "Unknown";
27
+ const type = (payload === null || payload === void 0 ? void 0 : payload.type) || "user";
28
+ console.log();
29
+ console.log(chalk.bold(` Access Summary for ${chalk.cyan(name)}`));
30
+ console.log(chalk.dim(` Type: ${su ? chalk.yellow("superuser") : type}`));
31
+ console.log(chalk.dim(` ${"─".repeat(50)}`));
32
+ console.log();
33
+ const totalCollections = modules.reduce((sum, m) => sum + m.collections, 0);
34
+ // Column widths
35
+ const maxMod = Math.max(...modules.map((m) => m.module.length), 6);
36
+ const maxLabel = Math.max(...modules.map((m) => m.label.length), 5);
37
+ console.log(` ${chalk.dim("MODULE".padEnd(maxMod + 2))} ${chalk.dim("LABEL".padEnd(maxLabel + 2))} ${chalk.dim("COLLECTIONS")}`);
38
+ console.log(chalk.dim(` ${"─".repeat(maxMod + maxLabel + 18)}`));
39
+ for (const mod of modules) {
40
+ const modName = mod.superuserOnly
41
+ ? chalk.yellow(mod.module.padEnd(maxMod + 2))
42
+ : mod.module.padEnd(maxMod + 2);
43
+ const label = mod.label.padEnd(maxLabel + 2);
44
+ const count = String(mod.collections).padStart(4);
45
+ console.log(` ${modName} ${label} ${count}`);
46
+ }
47
+ console.log();
48
+ console.log(chalk.dim(` ${modules.length} modules, ${totalCollections} collections accessible`));
49
+ if (su) {
50
+ console.log(chalk.yellow(` Superuser access: all modules including system collections`));
51
+ }
52
+ console.log();
53
+ }
54
+ export function createAccessCommand(program) {
55
+ program
56
+ .command("access")
57
+ .description("Show accessible modules and collections for the current user")
58
+ .addHelpText("after", `
59
+ Examples:
60
+ ay access Show accessible modules and collection counts
61
+ ay access -r json Output as JSON
62
+ ay access --module crm Show collections in a specific module`)
63
+ .option("--module <name>", "Show collections for a specific module")
64
+ .action(async (options) => {
65
+ try {
66
+ const opts = { ...program.opts(), ...options };
67
+ const su = isSuperUser();
68
+ const modules = getAccessibleModules(su);
69
+ // Detail mode: list collections in a specific module
70
+ if (opts.module) {
71
+ const modName = opts.module.toLowerCase();
72
+ const mod = aYOUneModules.find((m) => m.module === modName);
73
+ if (!mod) {
74
+ cliError(`Unknown module: "${opts.module}". Use "ay access" to see available modules.`, EXIT_GENERAL_ERROR);
75
+ }
76
+ if (mod.superuserOnly && !su) {
77
+ cliError(`Module "${opts.module}" requires superuser access.`, EXIT_GENERAL_ERROR);
78
+ }
79
+ const collections = modelsAndRights
80
+ .filter((m) => m.module === modName)
81
+ .map((m) => ({
82
+ collection: m.plural,
83
+ singular: m.singular,
84
+ right: m.right,
85
+ }));
86
+ if (opts.responseFormat !== "json" || opts.save) {
87
+ handleResponseFormatOptions(opts, collections);
88
+ }
89
+ else {
90
+ console.log();
91
+ console.log(chalk.bold(` Collections in ${chalk.cyan(mod.label)} (${modName})`));
92
+ console.log(chalk.dim(` ${"─".repeat(50)}`));
93
+ console.log();
94
+ const maxCol = Math.max(...collections.map((c) => c.collection.length), 10);
95
+ const maxRight = Math.max(...collections.map((c) => c.right.length), 5);
96
+ console.log(` ${chalk.dim("COLLECTION".padEnd(maxCol + 2))} ${chalk.dim("RIGHT")}`);
97
+ console.log(chalk.dim(` ${"─".repeat(maxCol + maxRight + 6)}`));
98
+ for (const col of collections) {
99
+ console.log(` ${col.collection.padEnd(maxCol + 2)} ${chalk.dim(col.right)}`);
100
+ }
101
+ console.log();
102
+ console.log(chalk.dim(` ${collections.length} collections`));
103
+ console.log();
104
+ }
105
+ return;
106
+ }
107
+ // Summary mode: show all accessible modules
108
+ if (opts.responseFormat !== "json" || opts.save) {
109
+ // For non-default formats or save, use standard formatting
110
+ const data = modules.map((m) => ({
111
+ module: m.module,
112
+ label: m.label,
113
+ collections: m.collections,
114
+ ...(m.superuserOnly ? { superuserOnly: true } : {}),
115
+ }));
116
+ handleResponseFormatOptions(opts, data);
117
+ }
118
+ else {
119
+ displayAccess(modules, su);
120
+ }
121
+ }
122
+ catch (e) {
123
+ cliError(e.message || "An unexpected error occurred", EXIT_GENERAL_ERROR);
124
+ }
125
+ });
126
+ }