@tolinax/ayoune-cli 2026.11.4 → 2026.12.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/lib/commands/createAccessCommand.js +202 -48
- package/package.json +1 -1
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
2
3
|
import { modelsAndRights } from "../../data/modelsAndRights.js";
|
|
3
4
|
import { aYOUneModules } from "../../data/modules.js";
|
|
4
5
|
import { isSuperUser, getTokenPayload } from "../helpers/tokenPayload.js";
|
|
5
6
|
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
6
7
|
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
7
8
|
import { cliError } from "../helpers/cliError.js";
|
|
9
|
+
// ── Visual helpers ──────────────────────────────────────────────────────────
|
|
10
|
+
const BAR_CHAR = "█";
|
|
11
|
+
const BAR_HALF = "▌";
|
|
12
|
+
const DIM_CHAR = "░";
|
|
13
|
+
function bar(value, max, width) {
|
|
14
|
+
if (max === 0)
|
|
15
|
+
return chalk.dim(DIM_CHAR.repeat(width));
|
|
16
|
+
const filled = (value / max) * width;
|
|
17
|
+
const full = Math.floor(filled);
|
|
18
|
+
const half = filled - full >= 0.5 ? 1 : 0;
|
|
19
|
+
const empty = width - full - half;
|
|
20
|
+
return chalk.cyan(BAR_CHAR.repeat(full)) + (half ? chalk.cyan(BAR_HALF) : "") + chalk.dim(DIM_CHAR.repeat(empty));
|
|
21
|
+
}
|
|
8
22
|
function getAccessibleModules(su) {
|
|
9
23
|
var _a;
|
|
10
24
|
const moduleSummaries = [];
|
|
@@ -21,52 +35,209 @@ function getAccessibleModules(su) {
|
|
|
21
35
|
}
|
|
22
36
|
return moduleSummaries;
|
|
23
37
|
}
|
|
24
|
-
function
|
|
38
|
+
function getModuleCollections(modName) {
|
|
39
|
+
return modelsAndRights
|
|
40
|
+
.filter((m) => m.module === modName)
|
|
41
|
+
.map((m) => ({ collection: m.plural, singular: m.singular, right: m.right }))
|
|
42
|
+
.sort((a, b) => a.collection.localeCompare(b.collection));
|
|
43
|
+
}
|
|
44
|
+
// ── Display: summary ────────────────────────────────────────────────────────
|
|
45
|
+
function displaySummary(modules, su) {
|
|
25
46
|
const payload = getTokenPayload();
|
|
26
47
|
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
48
|
const type = (payload === null || payload === void 0 ? void 0 : payload.type) || "user";
|
|
49
|
+
const totalCollections = modules.reduce((sum, m) => sum + m.collections, 0);
|
|
50
|
+
const maxCount = Math.max(...modules.map((m) => m.collections));
|
|
51
|
+
// Sort: largest modules first for visual impact
|
|
52
|
+
const sorted = [...modules].sort((a, b) => b.collections - a.collections);
|
|
28
53
|
console.log();
|
|
29
|
-
console.log(chalk.bold(` Access Summary
|
|
30
|
-
console.log(chalk.dim(` Type:
|
|
31
|
-
console.log(chalk.dim(` ${
|
|
54
|
+
console.log(chalk.bold(` Access Summary`));
|
|
55
|
+
console.log(chalk.dim(` User: `) + chalk.cyan(name) + chalk.dim(` Type: `) + (su ? chalk.yellow.bold("superuser") : chalk.white(type)));
|
|
56
|
+
console.log(chalk.dim(` ${modules.length} modules, ${totalCollections} collections accessible`));
|
|
32
57
|
console.log();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
console.log(` ${
|
|
38
|
-
|
|
39
|
-
for (const mod of modules) {
|
|
58
|
+
// Header
|
|
59
|
+
const maxMod = Math.max(...sorted.map((m) => m.module.length), 6);
|
|
60
|
+
const maxLabel = Math.max(Math.min(Math.max(...sorted.map((m) => m.label.length)), 30), 5);
|
|
61
|
+
console.log(chalk.dim(` ${"MODULE".padEnd(maxMod + 2)} ${"LABEL".padEnd(maxLabel + 2)} ${"#".padStart(4)} SHARE`));
|
|
62
|
+
console.log(chalk.dim(` ${"─".repeat(maxMod + maxLabel + 32)}`));
|
|
63
|
+
for (const mod of sorted) {
|
|
40
64
|
const modName = mod.superuserOnly
|
|
41
65
|
? chalk.yellow(mod.module.padEnd(maxMod + 2))
|
|
42
|
-
: mod.module.padEnd(maxMod + 2);
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
66
|
+
: chalk.white(mod.module.padEnd(maxMod + 2));
|
|
67
|
+
const labelStr = mod.label.length > 30 ? mod.label.slice(0, 28) + ".." : mod.label;
|
|
68
|
+
const label = chalk.dim(labelStr.padEnd(maxLabel + 2));
|
|
69
|
+
const count = mod.collections > 0 ? chalk.white(String(mod.collections).padStart(4)) : chalk.dim(" 0");
|
|
70
|
+
const viz = bar(mod.collections, maxCount, 16);
|
|
71
|
+
console.log(` ${modName} ${label} ${count} ${viz}`);
|
|
46
72
|
}
|
|
47
73
|
console.log();
|
|
48
|
-
console.log(chalk.dim(` ${modules.length} modules, ${totalCollections} collections accessible`));
|
|
49
74
|
if (su) {
|
|
50
|
-
console.log(chalk.yellow(` Superuser
|
|
75
|
+
console.log(chalk.yellow(` ★ Superuser — all modules including system collections`));
|
|
76
|
+
console.log();
|
|
51
77
|
}
|
|
78
|
+
}
|
|
79
|
+
// ── Display: module detail ──────────────────────────────────────────────────
|
|
80
|
+
function displayModuleDetail(modName, modLabel, collections) {
|
|
81
|
+
const maxCol = Math.max(...collections.map((c) => c.collection.length), 10);
|
|
82
|
+
console.log();
|
|
83
|
+
console.log(chalk.bold(` ${chalk.cyan(modLabel)} `) + chalk.dim(`(${modName})`));
|
|
84
|
+
console.log(chalk.dim(` ${collections.length} collections`));
|
|
52
85
|
console.log();
|
|
86
|
+
// Group by right prefix if there's variety, else flat list
|
|
87
|
+
const rights = [...new Set(collections.map((c) => c.right.split(".")[0]))];
|
|
88
|
+
const grouped = rights.length > 1;
|
|
89
|
+
if (grouped) {
|
|
90
|
+
for (const prefix of rights) {
|
|
91
|
+
const group = collections.filter((c) => c.right.startsWith(prefix + "."));
|
|
92
|
+
console.log(chalk.dim(` ─ ${prefix} (${group.length})`));
|
|
93
|
+
for (const col of group) {
|
|
94
|
+
const rightSuffix = col.right.split(".").slice(1).join(".");
|
|
95
|
+
console.log(` ${chalk.white(col.collection.padEnd(maxCol + 2))} ${chalk.dim(rightSuffix)}`);
|
|
96
|
+
}
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log(chalk.dim(` ${"COLLECTION".padEnd(maxCol + 2)} RIGHT`));
|
|
102
|
+
console.log(chalk.dim(` ${"─".repeat(maxCol + 30)}`));
|
|
103
|
+
for (const col of collections) {
|
|
104
|
+
console.log(` ${chalk.white(col.collection.padEnd(maxCol + 2))} ${chalk.dim(col.right)}`);
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
}
|
|
53
108
|
}
|
|
109
|
+
// ── Interactive drill-down ──────────────────────────────────────────────────
|
|
110
|
+
async function interactiveDrillDown(modules, su) {
|
|
111
|
+
const sorted = [...modules].sort((a, b) => b.collections - a.collections);
|
|
112
|
+
const choices = sorted.map((m) => ({
|
|
113
|
+
name: `${m.module.padEnd(14)} ${chalk.dim(m.label.padEnd(32))} ${chalk.cyan(String(m.collections).padStart(3))} collections`,
|
|
114
|
+
value: m.module,
|
|
115
|
+
short: m.module,
|
|
116
|
+
}));
|
|
117
|
+
choices.push(new inquirer.Separator());
|
|
118
|
+
choices.push({ name: chalk.dim("Exit"), value: "__exit__", short: "exit" });
|
|
119
|
+
while (true) {
|
|
120
|
+
const { selectedModule } = await inquirer.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: "list",
|
|
123
|
+
name: "selectedModule",
|
|
124
|
+
message: "Select a module to explore:",
|
|
125
|
+
choices,
|
|
126
|
+
pageSize: Math.min(modules.length + 2, 20),
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
if (selectedModule === "__exit__")
|
|
130
|
+
break;
|
|
131
|
+
const mod = aYOUneModules.find((m) => m.module === selectedModule);
|
|
132
|
+
if (!mod)
|
|
133
|
+
break;
|
|
134
|
+
const collections = getModuleCollections(selectedModule);
|
|
135
|
+
displayModuleDetail(selectedModule, mod.label, collections);
|
|
136
|
+
// Offer to drill further into a collection or go back
|
|
137
|
+
if (collections.length > 0) {
|
|
138
|
+
const collChoices = [
|
|
139
|
+
{ name: chalk.dim("← Back to modules"), value: "__back__" },
|
|
140
|
+
new inquirer.Separator(),
|
|
141
|
+
...collections.map((c) => ({
|
|
142
|
+
name: `${c.collection.padEnd(30)} ${chalk.dim(c.right)}`,
|
|
143
|
+
value: c.collection,
|
|
144
|
+
short: c.collection,
|
|
145
|
+
})),
|
|
146
|
+
];
|
|
147
|
+
const { selectedCollection } = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: "list",
|
|
150
|
+
name: "selectedCollection",
|
|
151
|
+
message: `Drill into a collection (use with ay list ${selectedModule} <collection>):`,
|
|
152
|
+
choices: collChoices,
|
|
153
|
+
pageSize: Math.min(collections.length + 3, 20),
|
|
154
|
+
},
|
|
155
|
+
]);
|
|
156
|
+
if (selectedCollection !== "__back__") {
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(chalk.bold(` Quick commands for ${chalk.cyan(selectedCollection)}:`));
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk.dim(` List: `) + `ay list ${selectedModule} ${selectedCollection.toLowerCase()}`);
|
|
161
|
+
console.log(chalk.dim(` Get: `) + `ay get ${selectedModule} ${selectedCollection.toLowerCase()}`);
|
|
162
|
+
console.log(chalk.dim(` Search: `) + `ay search ${selectedCollection.toLowerCase()} "<query>"`);
|
|
163
|
+
console.log(chalk.dim(` Describe: `) + `ay describe ${selectedCollection.toLowerCase()} <id>`);
|
|
164
|
+
console.log(chalk.dim(` Export: `) + `ay export run ${selectedModule} ${selectedCollection.toLowerCase()}`);
|
|
165
|
+
console.log();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ── Search ──────────────────────────────────────────────────────────────────
|
|
171
|
+
function displaySearch(query, su) {
|
|
172
|
+
const q = query.toLowerCase();
|
|
173
|
+
const matchedCollections = modelsAndRights.filter((m) => {
|
|
174
|
+
if (m.module === "su" && !su)
|
|
175
|
+
return false;
|
|
176
|
+
return m.plural.toLowerCase().includes(q) || m.singular.toLowerCase().includes(q) || m.right.toLowerCase().includes(q);
|
|
177
|
+
});
|
|
178
|
+
if (matchedCollections.length === 0) {
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(chalk.dim(` No collections matching "${query}"`));
|
|
181
|
+
console.log();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(chalk.bold(` Search results for "${chalk.cyan(query)}"`));
|
|
186
|
+
console.log(chalk.dim(` ${matchedCollections.length} matches`));
|
|
187
|
+
console.log();
|
|
188
|
+
// Group by module
|
|
189
|
+
const byModule = new Map();
|
|
190
|
+
for (const m of matchedCollections) {
|
|
191
|
+
if (!byModule.has(m.module))
|
|
192
|
+
byModule.set(m.module, []);
|
|
193
|
+
byModule.get(m.module).push(m);
|
|
194
|
+
}
|
|
195
|
+
const maxCol = Math.max(...matchedCollections.map((m) => m.plural.length), 10);
|
|
196
|
+
for (const [modName, cols] of byModule) {
|
|
197
|
+
const mod = aYOUneModules.find((m) => m.module === modName);
|
|
198
|
+
console.log(chalk.dim(` ─ ${modName}`) + chalk.dim(` (${(mod === null || mod === void 0 ? void 0 : mod.label) || modName})`));
|
|
199
|
+
for (const col of cols) {
|
|
200
|
+
// Highlight the match
|
|
201
|
+
const highlighted = col.plural.replace(new RegExp(`(${query})`, "ig"), (m) => chalk.cyan.underline(m));
|
|
202
|
+
console.log(` ${highlighted.padEnd(maxCol + 12)} ${chalk.dim(col.right)} ${chalk.dim("→")} ay list ${modName} ${col.plural.toLowerCase()}`);
|
|
203
|
+
}
|
|
204
|
+
console.log();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// ── Command ─────────────────────────────────────────────────────────────────
|
|
54
208
|
export function createAccessCommand(program) {
|
|
55
209
|
program
|
|
56
210
|
.command("access")
|
|
57
211
|
.description("Show accessible modules and collections for the current user")
|
|
58
212
|
.addHelpText("after", `
|
|
59
213
|
Examples:
|
|
60
|
-
ay access
|
|
61
|
-
ay access -r json
|
|
62
|
-
ay access --module crm
|
|
214
|
+
ay access Interactive module explorer with drill-down
|
|
215
|
+
ay access -r json Output as JSON (non-interactive)
|
|
216
|
+
ay access --module crm Show collections in a specific module
|
|
217
|
+
ay access --search contacts Find collections by name
|
|
218
|
+
ay access --no-interactive Summary table without drill-down prompt`)
|
|
63
219
|
.option("--module <name>", "Show collections for a specific module")
|
|
220
|
+
.option("--search <query>", "Search collections by name or right")
|
|
221
|
+
.option("--no-interactive", "Disable interactive drill-down")
|
|
64
222
|
.action(async (options) => {
|
|
65
223
|
try {
|
|
66
224
|
const opts = { ...program.opts(), ...options };
|
|
67
225
|
const su = isSuperUser();
|
|
68
226
|
const modules = getAccessibleModules(su);
|
|
69
|
-
|
|
227
|
+
const isTTY = process.stdout.isTTY;
|
|
228
|
+
// Machine mode: explicit `-r` flag passed (e.g., -r table, -r yaml, -r csv),
|
|
229
|
+
// or saving to file, or piped (non-TTY) output.
|
|
230
|
+
// We check Commander's option source to distinguish "user typed -r table" from
|
|
231
|
+
// "user config defaults to yaml". Only an explicit CLI flag triggers machine mode.
|
|
232
|
+
const formatSource = program.getOptionValueSource("responseFormat");
|
|
233
|
+
const explicitFormat = formatSource === "cli";
|
|
234
|
+
const machineMode = explicitFormat || !!opts.save || !isTTY;
|
|
235
|
+
// ── Search mode ─────────────────────────────────────────────
|
|
236
|
+
if (opts.search) {
|
|
237
|
+
displaySearch(opts.search, su);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// ── Detail mode: specific module ────────────────────────────
|
|
70
241
|
if (opts.module) {
|
|
71
242
|
const modName = opts.module.toLowerCase();
|
|
72
243
|
const mod = aYOUneModules.find((m) => m.module === modName);
|
|
@@ -76,37 +247,17 @@ Examples:
|
|
|
76
247
|
if (mod.superuserOnly && !su) {
|
|
77
248
|
cliError(`Module "${opts.module}" requires superuser access.`, EXIT_GENERAL_ERROR);
|
|
78
249
|
}
|
|
79
|
-
const collections =
|
|
80
|
-
|
|
81
|
-
.map((m) => ({
|
|
82
|
-
collection: m.plural,
|
|
83
|
-
singular: m.singular,
|
|
84
|
-
right: m.right,
|
|
85
|
-
}));
|
|
86
|
-
if (opts.responseFormat !== "json" || opts.save) {
|
|
250
|
+
const collections = getModuleCollections(modName);
|
|
251
|
+
if (machineMode) {
|
|
87
252
|
handleResponseFormatOptions(opts, collections);
|
|
88
253
|
}
|
|
89
254
|
else {
|
|
90
|
-
|
|
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();
|
|
255
|
+
displayModuleDetail(modName, mod.label, collections);
|
|
104
256
|
}
|
|
105
257
|
return;
|
|
106
258
|
}
|
|
107
|
-
// Summary mode
|
|
108
|
-
if (
|
|
109
|
-
// For non-default formats or save, use standard formatting
|
|
259
|
+
// ── Summary mode ────────────────────────────────────────────
|
|
260
|
+
if (machineMode) {
|
|
110
261
|
const data = modules.map((m) => ({
|
|
111
262
|
module: m.module,
|
|
112
263
|
label: m.label,
|
|
@@ -114,9 +265,12 @@ Examples:
|
|
|
114
265
|
...(m.superuserOnly ? { superuserOnly: true } : {}),
|
|
115
266
|
}));
|
|
116
267
|
handleResponseFormatOptions(opts, data);
|
|
268
|
+
return;
|
|
117
269
|
}
|
|
118
|
-
|
|
119
|
-
|
|
270
|
+
// Pretty display + optional interactive drill-down
|
|
271
|
+
displaySummary(modules, su);
|
|
272
|
+
if (opts.interactive !== false) {
|
|
273
|
+
await interactiveDrillDown(modules, su);
|
|
120
274
|
}
|
|
121
275
|
}
|
|
122
276
|
catch (e) {
|