@tolinax/ayoune-cli 2026.3.0 → 2026.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/data/contextSlots.js +189 -0
  2. package/data/modelsAndRights.js +56 -0
  3. package/data/modules.js +16 -0
  4. package/lib/api/apiCallHandler.js +6 -2
  5. package/lib/api/apiClient.js +9 -1
  6. package/lib/api/auditCallHandler.js +2 -2
  7. package/lib/api/handleAPIError.js +20 -18
  8. package/lib/api/login.js +3 -3
  9. package/lib/api/searchClient.js +119 -0
  10. package/lib/commands/createAccessCommand.js +126 -0
  11. package/lib/commands/createActionsCommand.js +40 -9
  12. package/lib/commands/createAiCommand.js +17 -17
  13. package/lib/commands/createAliasCommand.js +4 -6
  14. package/lib/commands/createAuditCommand.js +5 -9
  15. package/lib/commands/createBatchCommand.js +15 -28
  16. package/lib/commands/createCompletionsCommand.js +6 -3
  17. package/lib/commands/createConfigCommand.js +8 -14
  18. package/lib/commands/createContextCommand.js +163 -0
  19. package/lib/commands/createCopyCommand.js +4 -7
  20. package/lib/commands/createCreateCommand.js +4 -7
  21. package/lib/commands/createDeleteCommand.js +4 -6
  22. package/lib/commands/createDeployCommand.js +31 -55
  23. package/lib/commands/createDescribeCommand.js +12 -10
  24. package/lib/commands/createEditCommand.js +13 -8
  25. package/lib/commands/createEventsCommand.js +4 -4
  26. package/lib/commands/createExecCommand.js +65 -35
  27. package/lib/commands/createExportCommand.js +21 -24
  28. package/lib/commands/createGetCommand.js +13 -14
  29. package/lib/commands/createJobsCommand.js +8 -13
  30. package/lib/commands/createListCommand.js +13 -14
  31. package/lib/commands/createLoginCommand.js +16 -4
  32. package/lib/commands/createLogoutCommand.js +2 -2
  33. package/lib/commands/createModulesCommand.js +16 -19
  34. package/lib/commands/createMonitorCommand.js +9 -16
  35. package/lib/commands/createPermissionsCommand.js +10 -18
  36. package/lib/commands/createProgram.js +47 -21
  37. package/lib/commands/createSearchCommand.js +219 -69
  38. package/lib/commands/createSelfHostUpdateCommand.js +166 -0
  39. package/lib/commands/createServicesCommand.js +5 -8
  40. package/lib/commands/createSetupCommand.js +305 -0
  41. package/lib/commands/createStatusCommand.js +147 -0
  42. package/lib/commands/createStorageCommand.js +2 -3
  43. package/lib/commands/createStreamCommand.js +4 -4
  44. package/lib/commands/createSyncCommand.js +5 -8
  45. package/lib/commands/createTemplateCommand.js +9 -16
  46. package/lib/commands/createUpdateCommand.js +12 -15
  47. package/lib/commands/createUsersCommand.js +21 -31
  48. package/lib/commands/createWebhooksCommand.js +15 -22
  49. package/lib/commands/createWhoAmICommand.js +8 -6
  50. package/lib/helpers/cliError.js +24 -0
  51. package/lib/helpers/config.js +1 -0
  52. package/lib/helpers/configLoader.js +6 -0
  53. package/lib/helpers/contextInjector.js +65 -0
  54. package/lib/helpers/contextResolver.js +70 -0
  55. package/lib/helpers/contextStore.js +46 -0
  56. package/lib/helpers/handleResponseFormatOptions.js +59 -10
  57. package/lib/helpers/logo.js +48 -0
  58. package/lib/helpers/resolveCollectionArgs.js +36 -0
  59. package/lib/helpers/sanitizeFields.js +18 -0
  60. package/lib/helpers/secureStorage.js +72 -0
  61. package/lib/helpers/tokenPayload.js +21 -0
  62. package/lib/helpers/updateNotifier.js +49 -0
  63. package/lib/models/getModuleFromCollection.js +4 -1
  64. package/lib/operations/handleCopySingleOperation.js +10 -2
  65. package/lib/operations/handleCreateSingleOperation.js +3 -0
  66. package/lib/operations/handleDescribeSingleOperation.js +23 -0
  67. package/lib/operations/handleGetOperation.js +9 -3
  68. package/lib/operations/handleListOperation.js +14 -10
  69. package/lib/prompts/promptModule.js +9 -6
  70. package/package.json +163 -158
@@ -1,5 +1,15 @@
1
1
  import yaml from "js-yaml";
2
2
  import jmespath from "jmespath";
3
+ /**
4
+ * Maps CLI response format to the API-compatible format.
5
+ * The API only supports json, yaml, csv — not table.
6
+ * Table formatting is done client-side from JSON data.
7
+ */
8
+ export function getApiResponseFormat(cliFormat) {
9
+ if (cliFormat === "table")
10
+ return "json";
11
+ return cliFormat;
12
+ }
3
13
  function filterColumns(data, columns) {
4
14
  const fields = columns.split(",").map((c) => c.trim());
5
15
  if (Array.isArray(data)) {
@@ -28,24 +38,63 @@ export function handleResponseFormatOptions(opts, res) {
28
38
  let meta = {};
29
39
  let content;
30
40
  if (opts.responseFormat && opts.responseFormat === "yaml") {
31
- plainResult = yaml.load(res);
32
- result = plainResult.payload;
33
- meta = plainResult.meta;
41
+ // API returns YAML string when responseFormat=yaml, but locally-constructed
42
+ // responses (sync status, dry-run) are already objects — handle both
43
+ if (typeof res === "string") {
44
+ plainResult = yaml.load(res);
45
+ }
46
+ else {
47
+ plainResult = res;
48
+ }
49
+ // With hideMeta, API returns raw data (no {payload, meta} wrapper)
50
+ // Without hideMeta, API returns {payload, meta} wrapper
51
+ if (plainResult && plainResult.payload !== undefined) {
52
+ result = plainResult.payload;
53
+ meta = plainResult.meta || {};
54
+ }
55
+ else {
56
+ result = plainResult;
57
+ meta = {};
58
+ }
34
59
  }
35
60
  if (opts.responseFormat && opts.responseFormat === "csv") {
36
- plainResult = res;
37
- result = res.payload;
38
- meta = res.meta;
61
+ // API returns raw CSV string when responseFormat=csv — no {payload,meta} wrapper
62
+ if (typeof res === "string") {
63
+ plainResult = res;
64
+ result = res;
65
+ meta = {};
66
+ }
67
+ else {
68
+ plainResult = res;
69
+ result = res.payload;
70
+ meta = res.meta || {};
71
+ }
39
72
  }
40
73
  if (opts.responseFormat && opts.responseFormat === "table") {
41
74
  plainResult = res;
42
- result = res.payload;
43
- meta = res.meta;
75
+ if (res && res.payload !== undefined) {
76
+ result = res.payload;
77
+ meta = res.meta || {};
78
+ }
79
+ else if (Array.isArray(res)) {
80
+ result = res;
81
+ }
82
+ else {
83
+ result = res;
84
+ }
44
85
  }
45
86
  if (opts.responseFormat && opts.responseFormat === "json") {
46
87
  plainResult = res;
47
- result = res.payload;
48
- meta = res.meta;
88
+ if (res && res.payload !== undefined) {
89
+ result = res.payload;
90
+ meta = res.meta || {};
91
+ }
92
+ else if (Array.isArray(res)) {
93
+ result = res;
94
+ }
95
+ else {
96
+ result = res;
97
+ }
49
98
  }
50
99
  // Apply --columns filter
51
100
  if (opts.columns && result) {
@@ -0,0 +1,48 @@
1
+ import chalk from "chalk";
2
+ import figlet from "figlet";
3
+ export const BRAND_PURPLE = "#6B3FA0";
4
+ export const BRAND_BLUE = "#2B8DC6";
5
+ export const BRAND_PINK = "#E91E8C";
6
+ export function getLogo() {
7
+ try {
8
+ const raw = figlet.textSync("aYOUne", { font: "Slant" });
9
+ return colorizeByColumns(raw);
10
+ }
11
+ catch (_a) {
12
+ // Fallback: simple colored text
13
+ return ("\n " +
14
+ chalk.hex(BRAND_PURPLE).bold("a") +
15
+ chalk.hex(BRAND_BLUE).bold("YOU") +
16
+ chalk.hex(BRAND_PINK).bold("ne") +
17
+ "\n");
18
+ }
19
+ }
20
+ function colorizeByColumns(text) {
21
+ const lines = text.split("\n");
22
+ const colored = lines.map((line) => {
23
+ let result = "";
24
+ for (let i = 0; i < line.length; i++) {
25
+ const ch = line[i];
26
+ if (ch === " ") {
27
+ result += ch;
28
+ }
29
+ else if (i < 7) {
30
+ result += chalk.hex(BRAND_PURPLE)(ch);
31
+ }
32
+ else if (i < 26) {
33
+ result += chalk.hex(BRAND_BLUE)(ch);
34
+ }
35
+ else {
36
+ result += chalk.hex(BRAND_PINK)(ch);
37
+ }
38
+ }
39
+ return result;
40
+ });
41
+ return "\n" + colored.join("\n") + "\n";
42
+ }
43
+ export function getDescription() {
44
+ return (chalk.hex(BRAND_PURPLE)("a") +
45
+ chalk.hex(BRAND_BLUE)("YOU") +
46
+ chalk.hex(BRAND_PINK)("ne") +
47
+ chalk.dim(" — Business as a Service CLI"));
48
+ }
@@ -0,0 +1,36 @@
1
+ import { getModuleFromCollection } from "../models/getModuleFromCollection.js";
2
+ import { aYOUneModules } from "../../data/modules.js";
3
+ import { isSuperUser } from "./tokenPayload.js";
4
+ /**
5
+ * Resolves module and collection from CLI arguments.
6
+ * Supports both `ay list consumers` and `ay list crm consumers`.
7
+ * Blocks access to superuser-only modules for non-superusers.
8
+ *
9
+ * @param arg1 - Collection name, or module name if arg2 is provided
10
+ * @param arg2 - Collection name when arg1 is a module
11
+ */
12
+ export function resolveCollectionArgs(arg1, arg2) {
13
+ if (arg2) {
14
+ // ay list crm consumers → arg1=module, arg2=collection
15
+ enforceSuperUserAccess(arg1);
16
+ return { module: arg1, collection: arg2.toLowerCase() };
17
+ }
18
+ // Check if arg1 is a module name (user forgot the collection)
19
+ const isModule = aYOUneModules.some((mod) => mod.module === arg1.toLowerCase());
20
+ if (isModule) {
21
+ throw new Error(`"${arg1}" is a module name, not a collection. Use: ay <command> ${arg1} <collection>`);
22
+ }
23
+ // ay list consumers → lookup module from collection (throws if unknown)
24
+ const m = getModuleFromCollection(arg1.toLowerCase());
25
+ enforceSuperUserAccess(m.module);
26
+ return { module: m.module, collection: arg1.toLowerCase() };
27
+ }
28
+ /**
29
+ * Throws if the resolved module is superuser-only and the current user is not a superuser.
30
+ */
31
+ function enforceSuperUserAccess(module) {
32
+ const mod = aYOUneModules.find((m) => m.module === module.toLowerCase());
33
+ if ((mod === null || mod === void 0 ? void 0 : mod.superuserOnly) && !isSuperUser()) {
34
+ throw new Error(`Module "${module}" requires superuser access. Log in as a superuser to use this module.`);
35
+ }
36
+ }
@@ -0,0 +1,18 @@
1
+ import { EXIT_MISUSE } from "../exitCodes.js";
2
+ import { cliError } from "./cliError.js";
3
+ const VALID_FIELD_PATTERN = /^-?[a-zA-Z_][a-zA-Z0-9_.$]*$/;
4
+ /**
5
+ * Validates and sanitizes field names for MongoDB projections.
6
+ * Rejects MongoDB operators ($where, $function, etc.) and injection attempts.
7
+ * Accepts dot-notation paths (e.g., "address.city") and sort prefixes ("-createdAt").
8
+ */
9
+ export function sanitizeFields(fields) {
10
+ const fieldArray = Array.isArray(fields)
11
+ ? fields.flatMap((f) => f.split(",").map((s) => s.trim()))
12
+ : fields.split(",").map((s) => s.trim());
13
+ const invalid = fieldArray.filter((f) => !VALID_FIELD_PATTERN.test(f));
14
+ if (invalid.length > 0) {
15
+ cliError(`Invalid field name(s): ${invalid.join(", ")}. Field names must be alphanumeric (with dots for nested paths).`, EXIT_MISUSE, "invalid_fields");
16
+ }
17
+ return fieldArray;
18
+ }
@@ -0,0 +1,72 @@
1
+ import crypto from "crypto";
2
+ import os from "os";
3
+ import { localStorage } from "./localStorage.js";
4
+ const ALGORITHM = "aes-256-cbc";
5
+ const SENSITIVE_KEYS = new Set(["token", "refreshToken"]);
6
+ /**
7
+ * Derives a deterministic 32-byte encryption key from machine identity.
8
+ * Uses hostname + username as seed — not bulletproof, but prevents
9
+ * casual file theft from being useful without the same machine context.
10
+ */
11
+ function deriveKey() {
12
+ const seed = `ayoune-cli:${os.hostname()}:${os.userInfo().username}`;
13
+ return crypto.createHash("sha256").update(seed).digest();
14
+ }
15
+ function encrypt(text) {
16
+ const key = deriveKey();
17
+ const iv = crypto.randomBytes(16);
18
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
19
+ let encrypted = cipher.update(text, "utf8", "hex");
20
+ encrypted += cipher.final("hex");
21
+ return iv.toString("hex") + ":" + encrypted;
22
+ }
23
+ function decrypt(data) {
24
+ const key = deriveKey();
25
+ const [ivHex, encrypted] = data.split(":");
26
+ if (!ivHex || !encrypted)
27
+ return data; // Not encrypted, return as-is (migration)
28
+ try {
29
+ const iv = Buffer.from(ivHex, "hex");
30
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
31
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
32
+ decrypted += decipher.final("utf8");
33
+ return decrypted;
34
+ }
35
+ catch (_a) {
36
+ // Decryption failed — likely a plaintext value from before encryption was added.
37
+ // Return as-is for backward compatibility (will be re-encrypted on next write).
38
+ return data;
39
+ }
40
+ }
41
+ /**
42
+ * Secure wrapper around localStorage that encrypts sensitive values (tokens).
43
+ * Non-sensitive values pass through unchanged for easy debugging.
44
+ * Transparently migrates plaintext tokens on first read.
45
+ */
46
+ export const secureStorage = {
47
+ getItem(key) {
48
+ const raw = localStorage.getItem(key);
49
+ if (raw === null)
50
+ return null;
51
+ if (SENSITIVE_KEYS.has(key)) {
52
+ const decrypted = decrypt(raw);
53
+ // If the raw value was plaintext (migration), re-encrypt it
54
+ if (raw === decrypted && !raw.includes(":")) {
55
+ secureStorage.setItem(key, decrypted);
56
+ }
57
+ return decrypted;
58
+ }
59
+ return raw;
60
+ },
61
+ setItem(key, value) {
62
+ if (SENSITIVE_KEYS.has(key)) {
63
+ localStorage.setItem(key, encrypt(value));
64
+ }
65
+ else {
66
+ localStorage.setItem(key, value);
67
+ }
68
+ },
69
+ removeItem(key) {
70
+ localStorage.removeItem(key);
71
+ },
72
+ };
@@ -0,0 +1,21 @@
1
+ import { secureStorage } from "./secureStorage.js";
2
+ import { decodeToken } from "../api/decodeToken.js";
3
+ /**
4
+ * Returns the decoded JWT payload from the stored token, or null if unavailable.
5
+ */
6
+ export function getTokenPayload() {
7
+ var _a;
8
+ const token = secureStorage.getItem("token");
9
+ if (!token)
10
+ return null;
11
+ const decoded = decodeToken(token);
12
+ return (_a = decoded === null || decoded === void 0 ? void 0 : decoded.payload) !== null && _a !== void 0 ? _a : null;
13
+ }
14
+ /**
15
+ * Returns true if the currently stored token belongs to a superuser.
16
+ * Checks `payload.type === "superuser"`.
17
+ */
18
+ export function isSuperUser() {
19
+ const payload = getTokenPayload();
20
+ return (payload === null || payload === void 0 ? void 0 : payload.type) === "superuser";
21
+ }
@@ -0,0 +1,49 @@
1
+ import { execSync } from "child_process";
2
+ import chalk from "chalk";
3
+ import { localStorage } from "./localStorage.js";
4
+ const PKG_NAME = "@tolinax/ayoune-cli";
5
+ const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
6
+ /**
7
+ * Non-blocking update check. Compares installed version against npm registry.
8
+ * Caches the result to avoid hammering npm on every invocation.
9
+ * Prints a notice to stderr if a newer version is available.
10
+ */
11
+ export function checkForUpdates(currentVersion) {
12
+ try {
13
+ // Skip in CI environments
14
+ if (process.env.CI || process.env.AYOUNE_NO_UPDATE_CHECK)
15
+ return;
16
+ // Throttle: only check every CHECK_INTERVAL_MS
17
+ const lastCheck = localStorage.getItem("updateCheckTimestamp");
18
+ if (lastCheck && Date.now() - parseInt(lastCheck, 10) < CHECK_INTERVAL_MS) {
19
+ // Still show cached notice if there was one
20
+ const cached = localStorage.getItem("updateCheckLatest");
21
+ if (cached && cached !== currentVersion) {
22
+ printUpdateNotice(currentVersion, cached);
23
+ }
24
+ return;
25
+ }
26
+ // Run npm view in background-ish (sync but with short timeout)
27
+ const latest = execSync(`npm view ${PKG_NAME} version 2>/dev/null`, {
28
+ encoding: "utf-8",
29
+ timeout: 5000,
30
+ }).trim();
31
+ localStorage.setItem("updateCheckTimestamp", String(Date.now()));
32
+ localStorage.setItem("updateCheckLatest", latest);
33
+ if (latest && latest !== currentVersion) {
34
+ printUpdateNotice(currentVersion, latest);
35
+ }
36
+ }
37
+ catch (_a) {
38
+ // Silently ignore — network issues, npm not available, etc.
39
+ }
40
+ }
41
+ function printUpdateNotice(current, latest) {
42
+ const msg = [
43
+ "",
44
+ chalk.yellow(` Update available: ${chalk.dim(current)} → ${chalk.green.bold(latest)}`),
45
+ chalk.dim(` Run ${chalk.white("npm install -g @tolinax/ayoune-cli")} to update`),
46
+ "",
47
+ ].join("\n");
48
+ process.stderr.write(msg);
49
+ }
@@ -1,7 +1,10 @@
1
1
  import _ from "lodash";
2
2
  import { modelsAndRights } from "../../data/modelsAndRights.js";
3
3
  const getModuleFromCollection = (collection) => {
4
- const m = _.find(modelsAndRights, (el) => el.plural.toLowerCase() === collection);
4
+ const m = _.find(modelsAndRights, (el) => el.plural.toLowerCase() === collection.toLowerCase());
5
+ if (!m) {
6
+ throw new Error(`Unknown collection: "${collection}". Use "ay modules" to browse available collections.`);
7
+ }
5
8
  return m;
6
9
  };
7
10
  export { getModuleFromCollection };
@@ -12,11 +12,19 @@ export async function handleCopySingleOperation(module, collection, id, opts) {
12
12
  responseFormat: opts.responseFormat,
13
13
  verbosity: opts.verbosity,
14
14
  });
15
+ // In dry-run mode, apiCallHandler returns null — skip post-copy logic
16
+ if (res == null) {
17
+ spinner.success({ text: `Copied entry ${id} in ${collection} (dry-run)` });
18
+ spinner.stop();
19
+ return { data: null, content: null, result: null, meta: {} };
20
+ }
15
21
  let { plainResult, result, meta, content } = handleResponseFormatOptions({ ...opts }, res);
22
+ const newId = (result === null || result === void 0 ? void 0 : result._id) || "unknown";
16
23
  spinner.success({
17
- text: `Copied entry ${id} in ${collection}: New ID [${result._id}]`,
24
+ text: `Copied entry ${id} in ${collection}: New ID [${newId}]`,
18
25
  });
19
26
  spinner.stop();
20
- localStorage.setItem("lastId", result._id);
27
+ if (result === null || result === void 0 ? void 0 : result._id)
28
+ localStorage.setItem("lastId", result._id);
21
29
  return { data: plainResult, content, result, meta };
22
30
  }
@@ -3,17 +3,20 @@ import { apiCallHandler } from "../api/apiCallHandler.js";
3
3
  import { spinner } from "../../index.js";
4
4
  import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
5
5
  import { localStorage } from "../helpers/localStorage.js";
6
+ import { getContextCreateFields } from "../helpers/contextInjector.js";
6
7
  export async function handleCreateSingleOperation(module, collection, name, opts) {
7
8
  var _a;
8
9
  spinner.start({
9
10
  text: `Creating entry in [${collection}]`,
10
11
  color: "magenta",
11
12
  });
13
+ const contextFields = getContextCreateFields(collection);
12
14
  let res = await apiCallHandler(module, collection.toLowerCase(), "post", {
13
15
  name,
14
16
  title: name,
15
17
  subject: name,
16
18
  summary: name,
19
+ ...contextFields,
17
20
  }, {
18
21
  responseFormat: opts.responseFormat,
19
22
  verbosity: opts.verbosity,
@@ -2,6 +2,9 @@
2
2
  import { apiCallHandler } from "../api/apiCallHandler.js";
3
3
  import { spinner } from "../../index.js";
4
4
  import { formatDocument } from "../helpers/formatDocument.js";
5
+ import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
6
+ import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
7
+ import { cliError } from "../helpers/cliError.js";
5
8
  export async function handleDescribeSingleOperation(module, collection, id, opts) {
6
9
  spinner.start({
7
10
  text: `Getting entry in [${collection}]`,
@@ -15,6 +18,26 @@ export async function handleDescribeSingleOperation(module, collection, id, opts
15
18
  });
16
19
  spinner.stop();
17
20
  const payload = res === null || res === void 0 ? void 0 : res.payload;
21
+ // Sub-resource extraction: ay describe tasks <id> comments
22
+ if (opts === null || opts === void 0 ? void 0 : opts.subResource) {
23
+ const subData = payload === null || payload === void 0 ? void 0 : payload[opts.subResource];
24
+ if (subData === undefined) {
25
+ const arrayFields = Object.keys(payload || {}).filter((k) => Array.isArray(payload[k]));
26
+ const hint = arrayFields.length > 0
27
+ ? `Available array fields: ${arrayFields.join(", ")}`
28
+ : "No array fields found in this document.";
29
+ cliError(`Sub-resource "${opts.subResource}" not found in ${collection}. ${hint}`, EXIT_GENERAL_ERROR);
30
+ }
31
+ const subRes = { payload: subData, meta: { total: Array.isArray(subData) ? subData.length : 1 } };
32
+ const formatted = handleResponseFormatOptions(opts, subRes);
33
+ return { data: res, content: formatted.content, result: formatted.result, meta: formatted.meta };
34
+ }
35
+ // Format options passthrough: --jq, -r json/table/csv, --columns
36
+ if ((opts === null || opts === void 0 ? void 0 : opts.jq) || (opts === null || opts === void 0 ? void 0 : opts.columns) || ((opts === null || opts === void 0 ? void 0 : opts.responseFormat) && opts.responseFormat !== "yaml")) {
37
+ const formatted = handleResponseFormatOptions(opts, { payload, meta: res === null || res === void 0 ? void 0 : res.meta });
38
+ return { data: res, content: formatted.content, result: formatted.result, meta: formatted.meta };
39
+ }
40
+ // Default: formatted YAML describe box
18
41
  if (!opts.quiet) {
19
42
  console.log(formatDocument(payload, { module, collection, id }));
20
43
  }
@@ -1,21 +1,27 @@
1
1
  // Operation handling functions
2
2
  import { apiCallHandler } from "../api/apiCallHandler.js";
3
3
  import { spinner } from "../../index.js";
4
- import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
4
+ import { handleResponseFormatOptions, getApiResponseFormat } from "../helpers/handleResponseFormatOptions.js";
5
+ import { sanitizeFields } from "../helpers/sanitizeFields.js";
6
+ import { getContextFilterParams } from "../helpers/contextInjector.js";
5
7
  export async function handleGetOperation(module, collection, opts) {
6
8
  var _a, _b, _c, _d, _e, _f;
7
9
  spinner.start({
8
10
  text: `Getting entries in [${collection}]`,
9
11
  color: "magenta",
10
12
  });
13
+ // Validate field names to prevent MongoDB injection
14
+ const fields = opts.fields ? sanitizeFields(opts.fields) : undefined;
15
+ const contextParams = getContextFilterParams(collection);
11
16
  let res = await apiCallHandler(module, collection.toLowerCase(), "get", null, {
12
17
  page: opts.page,
13
- responseFormat: opts.responseFormat,
18
+ responseFormat: getApiResponseFormat(opts.responseFormat),
14
19
  limit: opts.limit,
15
20
  from: opts.from,
16
- fields: opts.fields,
21
+ fields,
17
22
  hideMeta: opts.hideMeta,
18
23
  verbosity: opts.verbosity,
24
+ ...contextParams,
19
25
  });
20
26
  let { plainResult, result, meta, content } = handleResponseFormatOptions(opts, res);
21
27
  const totalEntries = (_b = (_a = meta === null || meta === void 0 ? void 0 : meta.pageInfo) === null || _a === void 0 ? void 0 : _a.totalEntries) !== null && _b !== void 0 ? _b : '?';
@@ -1,21 +1,25 @@
1
1
  // Operation handling functions
2
2
  import { apiCallHandler } from "../api/apiCallHandler.js";
3
3
  import { spinner } from "../../index.js";
4
- import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
4
+ import { handleResponseFormatOptions, getApiResponseFormat } from "../helpers/handleResponseFormatOptions.js";
5
+ import { getContextFilterParams, hasActiveContext } from "../helpers/contextInjector.js";
5
6
  async function fetchPage(module, collection, opts) {
7
+ const contextParams = getContextFilterParams(collection);
6
8
  return apiCallHandler(module, collection.toLowerCase(), "get", null, {
7
9
  page: opts.page,
8
- responseFormat: opts.responseFormat,
10
+ responseFormat: getApiResponseFormat(opts.responseFormat),
9
11
  limit: opts.limit,
10
12
  from: opts.from,
11
13
  hideMeta: opts.hideMeta,
12
14
  verbosity: opts.verbosity,
15
+ ...contextParams,
13
16
  });
14
17
  }
15
18
  export async function handleListOperation(module, collection, opts) {
16
- var _a, _b, _c, _d;
19
+ var _a, _b, _c, _d, _e, _f, _g, _h;
20
+ const scopeHint = hasActiveContext() ? " (scoped by context)" : "";
17
21
  spinner.start({
18
- text: `Listing entries in [${collection}]`,
22
+ text: `Listing entries in [${collection}]${scopeHint}`,
19
23
  color: "magenta",
20
24
  });
21
25
  if (opts.all) {
@@ -28,7 +32,7 @@ export async function handleListOperation(module, collection, opts) {
28
32
  const pageOpts = { ...opts, page };
29
33
  const res = await fetchPage(module, collection, pageOpts);
30
34
  const payload = res.payload;
31
- meta = res.meta;
35
+ meta = res.meta || {};
32
36
  totalPages = (_b = (_a = meta.pageInfo) === null || _a === void 0 ? void 0 : _a.totalPages) !== null && _b !== void 0 ? _b : 1;
33
37
  if (Array.isArray(payload)) {
34
38
  allPayload = allPayload.concat(payload);
@@ -41,7 +45,7 @@ export async function handleListOperation(module, collection, opts) {
41
45
  // Build a synthetic response with all data
42
46
  const syntheticRes = {
43
47
  payload: allPayload,
44
- meta: { ...meta, pageInfo: { ...meta.pageInfo, page: 1, totalPages: 1, totalEntries: allPayload.length } },
48
+ meta: { ...meta, pageInfo: { ...(meta.pageInfo || {}), page: 1, totalPages: 1, totalEntries: allPayload.length } },
45
49
  };
46
50
  const { plainResult, result, content } = handleResponseFormatOptions(opts, syntheticRes);
47
51
  spinner.success({ text: `Got all ${allPayload.length} entries from ${totalPages} pages` });
@@ -51,11 +55,11 @@ export async function handleListOperation(module, collection, opts) {
51
55
  // Single page fetch
52
56
  let res = await fetchPage(module, collection, opts);
53
57
  let { plainResult, result, meta, content } = handleResponseFormatOptions(opts, res);
54
- const total = (_d = (_c = meta.pageInfo) === null || _c === void 0 ? void 0 : _c.totalEntries) !== null && _d !== void 0 ? _d : 0;
55
- const count = Array.isArray(result) ? result.length : total;
58
+ const count = Array.isArray(result) ? result.length : 0;
59
+ const total = (_d = (_c = meta === null || meta === void 0 ? void 0 : meta.pageInfo) === null || _c === void 0 ? void 0 : _c.totalEntries) !== null && _d !== void 0 ? _d : count;
56
60
  spinner.success({
57
- text: total > 0
58
- ? `Got ${count} of ${total} entries : Page ${meta.pageInfo.page} of ${meta.pageInfo.totalPages}`
61
+ text: total > 0 || count > 0
62
+ ? `Got ${count} of ${total} entries : Page ${(_f = (_e = meta === null || meta === void 0 ? void 0 : meta.pageInfo) === null || _e === void 0 ? void 0 : _e.page) !== null && _f !== void 0 ? _f : 1} of ${(_h = (_g = meta === null || meta === void 0 ? void 0 : meta.pageInfo) === null || _g === void 0 ? void 0 : _g.totalPages) !== null && _h !== void 0 ? _h : '?'}`
59
63
  : `No entries found in [${collection}]`,
60
64
  });
61
65
  spinner.stop();
@@ -1,18 +1,21 @@
1
1
  // Prompt functions
2
2
  import inquirer from "inquirer";
3
3
  import { aYOUneModules } from "../../data/modules.js";
4
+ import { isSuperUser } from "../helpers/tokenPayload.js";
4
5
  export async function promptModule() {
6
+ const su = isSuperUser();
7
+ const choices = aYOUneModules
8
+ .filter((el) => !el.superuserOnly || su)
9
+ .map((el) => ({
10
+ name: el.label,
11
+ value: el.module,
12
+ }));
5
13
  const { module } = await inquirer.prompt([
6
14
  {
7
15
  type: "list",
8
16
  name: "module",
9
17
  message: "Select a business module:",
10
- choices: aYOUneModules.map((el) => {
11
- return {
12
- name: el.label,
13
- value: el.module,
14
- };
15
- }),
18
+ choices,
16
19
  },
17
20
  ]);
18
21
  return module;