@tolinax/ayoune-cli 2026.11.1 → 2026.11.2

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.
@@ -214,6 +214,7 @@ function installPreActionHook(program) {
214
214
  */
215
215
  function installGlobalOptions(program) {
216
216
  program
217
+ .name("ay")
217
218
  .version(pkg.version || "0.0.0")
218
219
  .addOption(new Option("-r, --responseFormat <format>", "Set the output format")
219
220
  .choices(["json", "csv", "yaml", "table"])
@@ -292,6 +293,16 @@ export async function createProgram(program) {
292
293
  // Lightweight stubs for every command so the help listing is complete and
293
294
  // commander can suggest similar names for typos.
294
295
  registerStubsForHelp(program);
296
+ // Bare `ay` (no args) should print help and exit cleanly. Commander's
297
+ // default behaviour is to print help then exit with code 1, treating
298
+ // "no command" as a usage error. For an end-user CLI that's hostile —
299
+ // a user typing `ay` to discover the tool gets a non-zero exit, which
300
+ // breaks shell scripts that gate on `ay && ...`. Force exit 0 here for
301
+ // the bare invocation.
302
+ if (process.argv.length <= 2) {
303
+ program.outputHelp();
304
+ process.exit(0);
305
+ }
295
306
  }
296
307
  else if (kind === "cmdHelp" || kind === "command") {
297
308
  if (spec) {
@@ -1,5 +1,5 @@
1
1
  import { apiCallHandler } from "../api/apiCallHandler.js";
2
- import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
2
+ import { handleResponseFormatOptions, printFormattedResponse, } from "../helpers/handleResponseFormatOptions.js";
3
3
  import { saveFile } from "../helpers/saveFile.js";
4
4
  import { spinner } from "../../index.js";
5
5
  import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
@@ -56,9 +56,10 @@ export function createServicesCommand(program) {
56
56
  payload: services,
57
57
  meta: { responseTime: 0, pageInfo: { totalEntries: services.length, page: 1, totalPages: 1 } },
58
58
  };
59
- handleResponseFormatOptions(opts, res);
59
+ const formatted = handleResponseFormatOptions(opts, res, { noPrint: true });
60
60
  spinner.success({ text: `Found ${services.length} services` });
61
61
  spinner.stop();
62
+ printFormattedResponse(opts, formatted);
62
63
  if (opts.save)
63
64
  await saveFile("services-list", opts, res);
64
65
  }
@@ -104,9 +105,10 @@ Examples:
104
105
  payload: endpoints,
105
106
  meta: { responseTime: 0, pageInfo: { totalEntries: endpoints.length, page: 1, totalPages: 1 } },
106
107
  };
107
- handleResponseFormatOptions(opts, formattedRes);
108
+ const formatted = handleResponseFormatOptions(opts, formattedRes, { noPrint: true });
108
109
  spinner.success({ text: `Found ${endpoints.length} endpoints on ${host}` });
109
110
  spinner.stop();
111
+ printFormattedResponse(opts, formatted);
110
112
  if (opts.save)
111
113
  await saveFile("service-endpoints", opts, formattedRes);
112
114
  }
@@ -168,9 +170,10 @@ Examples:
168
170
  pageInfo: { totalEntries: payload.length, page: 1, totalPages: 1 },
169
171
  },
170
172
  };
171
- handleResponseFormatOptions(opts, res);
173
+ const formatted = handleResponseFormatOptions(opts, res, { noPrint: true });
172
174
  spinner.success({ text: `${healthy}/${uniqueTargets.length} services healthy` });
173
175
  spinner.stop();
176
+ printFormattedResponse(opts, formatted);
174
177
  if (opts.save)
175
178
  await saveFile("services-health", opts, res);
176
179
  }
@@ -216,9 +219,10 @@ Examples:
216
219
  payload: description,
217
220
  meta: { responseTime: 0 },
218
221
  };
219
- handleResponseFormatOptions(opts, formattedRes);
222
+ const formatted = handleResponseFormatOptions(opts, formattedRes, { noPrint: true });
220
223
  spinner.success({ text: `Module: ${moduleName} — ${moduleActions.length} endpoints` });
221
224
  spinner.stop();
225
+ printFormattedResponse(opts, formatted);
222
226
  }
223
227
  catch (e) {
224
228
  cliError(e.message || "Failed to describe service", EXIT_GENERAL_ERROR);
@@ -32,7 +32,23 @@ function filterColumns(data, columns) {
32
32
  }
33
33
  return data;
34
34
  }
35
- export function handleResponseFormatOptions(opts, res) {
35
+ /**
36
+ * Print a previously-computed formatted response to stdout. Mirrors the
37
+ * print branch of `handleResponseFormatOptions` exactly. Call this AFTER
38
+ * `spinner.success(...)` / `spinner.stop()` so the data block appears below
39
+ * the spinner status line, not before it.
40
+ */
41
+ export function printFormattedResponse(opts, formatted) {
42
+ if (opts.quiet)
43
+ return;
44
+ if (opts.responseFormat === "table") {
45
+ console.table(formatted.result);
46
+ }
47
+ else if (formatted.content !== undefined) {
48
+ console.log(formatted.content);
49
+ }
50
+ }
51
+ export function handleResponseFormatOptions(opts, res, formatOpts = {}) {
36
52
  let plainResult;
37
53
  let result;
38
54
  let meta = {};
@@ -122,13 +138,9 @@ export function handleResponseFormatOptions(opts, res) {
122
138
  else {
123
139
  content = JSON.stringify(result, null, 4);
124
140
  }
125
- if (!opts.quiet) {
126
- if (opts.responseFormat === "table") {
127
- console.table(result);
128
- }
129
- else if (content !== undefined) {
130
- console.log(content);
131
- }
141
+ const formatted = { plainResult, result, meta, content };
142
+ if (!formatOpts.noPrint) {
143
+ printFormattedResponse(opts, formatted);
132
144
  }
133
- return { plainResult, result, meta, content };
145
+ return formatted;
134
146
  }
@@ -1,6 +1,6 @@
1
1
  // Operation handling functions
2
2
  import { spinner } from "../../index.js";
3
- import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
3
+ import { handleResponseFormatOptions, printFormattedResponse, } from "../helpers/handleResponseFormatOptions.js";
4
4
  import { localStorage } from "../helpers/localStorage.js";
5
5
  import { auditCallHandler } from "../api/auditCallHandler.js";
6
6
  export async function handleAuditOperation(collection, id, opts) {
@@ -12,11 +12,12 @@ export async function handleAuditOperation(collection, id, opts) {
12
12
  responseFormat: opts.responseFormat,
13
13
  verbosity: opts.verbosity,
14
14
  });
15
- let { plainResult, result, meta, content } = handleResponseFormatOptions({ ...opts }, res);
15
+ const formatted = handleResponseFormatOptions({ ...opts }, res, { noPrint: true });
16
16
  spinner.success({
17
17
  text: `Got Audit ${id} in ${collection}`,
18
18
  });
19
19
  spinner.stop();
20
- localStorage.setItem("lastId", result._id);
21
- return { data: plainResult, content, result, meta };
20
+ printFormattedResponse({ ...opts }, formatted);
21
+ localStorage.setItem("lastId", formatted.result._id);
22
+ return { data: formatted.plainResult, content: formatted.content, result: formatted.result, meta: formatted.meta };
22
23
  }
@@ -1,9 +1,10 @@
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, printFormattedResponse, } from "../helpers/handleResponseFormatOptions.js";
5
5
  import { localStorage } from "../helpers/localStorage.js";
6
6
  export async function handleCopySingleOperation(module, collection, id, opts) {
7
+ var _a, _b;
7
8
  spinner.start({
8
9
  text: `Copying entry ${id} in [${collection}]`,
9
10
  color: "magenta",
@@ -18,13 +19,14 @@ export async function handleCopySingleOperation(module, collection, id, opts) {
18
19
  spinner.stop();
19
20
  return { data: null, content: null, result: null, meta: {} };
20
21
  }
21
- let { plainResult, result, meta, content } = handleResponseFormatOptions({ ...opts }, res);
22
- const newId = (result === null || result === void 0 ? void 0 : result._id) || "unknown";
22
+ const formatted = handleResponseFormatOptions({ ...opts }, res, { noPrint: true });
23
+ const newId = ((_a = formatted.result) === null || _a === void 0 ? void 0 : _a._id) || "unknown";
23
24
  spinner.success({
24
25
  text: `Copied entry ${id} in ${collection}: New ID [${newId}]`,
25
26
  });
26
27
  spinner.stop();
27
- if (result === null || result === void 0 ? void 0 : result._id)
28
- localStorage.setItem("lastId", result._id);
29
- return { data: plainResult, content, result, meta };
28
+ printFormattedResponse({ ...opts }, formatted);
29
+ if ((_b = formatted.result) === null || _b === void 0 ? void 0 : _b._id)
30
+ localStorage.setItem("lastId", formatted.result._id);
31
+ return { data: formatted.plainResult, content: formatted.content, result: formatted.result, meta: formatted.meta };
30
32
  }
@@ -1,11 +1,11 @@
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, printFormattedResponse, } from "../helpers/handleResponseFormatOptions.js";
5
5
  import { localStorage } from "../helpers/localStorage.js";
6
6
  import { getContextCreateFields } from "../helpers/contextInjector.js";
7
7
  export async function handleCreateSingleOperation(module, collection, name, opts) {
8
- var _a;
8
+ var _a, _b, _c;
9
9
  spinner.start({
10
10
  text: `Creating entry in [${collection}]`,
11
11
  color: "magenta",
@@ -26,13 +26,14 @@ export async function handleCreateSingleOperation(module, collection, name, opts
26
26
  spinner.stop();
27
27
  return { data: null, content: null, result: null, meta: (_a = res === null || res === void 0 ? void 0 : res.meta) !== null && _a !== void 0 ? _a : {} };
28
28
  }
29
- let { plainResult, result, meta, content } = handleResponseFormatOptions({ ...opts }, res);
29
+ const formatted = handleResponseFormatOptions({ ...opts }, res, { noPrint: true });
30
30
  spinner.success({
31
- text: `Created entry [${result === null || result === void 0 ? void 0 : result._id}] in ${collection}`,
31
+ text: `Created entry [${(_b = formatted.result) === null || _b === void 0 ? void 0 : _b._id}] in ${collection}`,
32
32
  });
33
33
  spinner.stop();
34
- if (result === null || result === void 0 ? void 0 : result._id) {
35
- localStorage.setItem("lastId", result._id);
34
+ printFormattedResponse({ ...opts }, formatted);
35
+ if ((_c = formatted.result) === null || _c === void 0 ? void 0 : _c._id) {
36
+ localStorage.setItem("lastId", formatted.result._id);
36
37
  }
37
- return { data: plainResult, content, result, meta };
38
+ return { data: formatted.plainResult, content: formatted.content, result: formatted.result, meta: formatted.meta };
38
39
  }
@@ -1,7 +1,7 @@
1
1
  // Operation handling functions
2
2
  import { apiCallHandler } from "../api/apiCallHandler.js";
3
3
  import { spinner } from "../../index.js";
4
- import { handleResponseFormatOptions, getApiResponseFormat } from "../helpers/handleResponseFormatOptions.js";
4
+ import { handleResponseFormatOptions, printFormattedResponse, getApiResponseFormat, } from "../helpers/handleResponseFormatOptions.js";
5
5
  import { sanitizeFields } from "../helpers/sanitizeFields.js";
6
6
  import { getContextFilterParams } from "../helpers/contextInjector.js";
7
7
  export async function handleGetOperation(module, collection, opts) {
@@ -23,7 +23,10 @@ export async function handleGetOperation(module, collection, opts) {
23
23
  verbosity: opts.verbosity,
24
24
  ...contextParams,
25
25
  });
26
- let { plainResult, result, meta, content } = handleResponseFormatOptions(opts, res);
26
+ // Compute formatted result without printing spinner success line should
27
+ // appear ABOVE the data block, not after it.
28
+ const formatted = handleResponseFormatOptions(opts, res, { noPrint: true });
29
+ const { plainResult, result, meta, content } = formatted;
27
30
  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 : '?';
28
31
  const page = (_d = (_c = meta === null || meta === void 0 ? void 0 : meta.pageInfo) === null || _c === void 0 ? void 0 : _c.page) !== null && _d !== void 0 ? _d : 1;
29
32
  const totalPages = (_f = (_e = meta === null || meta === void 0 ? void 0 : meta.pageInfo) === null || _e === void 0 ? void 0 : _e.totalPages) !== null && _f !== void 0 ? _f : '?';
@@ -31,5 +34,6 @@ export async function handleGetOperation(module, collection, opts) {
31
34
  text: `Got ${opts.limit} entries of ${totalEntries} : Page ${page} of ${totalPages}`,
32
35
  });
33
36
  spinner.stop();
37
+ printFormattedResponse(opts, formatted);
34
38
  return { data: plainResult, content, result, meta };
35
39
  }
@@ -1,7 +1,7 @@
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, printFormattedResponse, } from "../helpers/handleResponseFormatOptions.js";
5
5
  export async function handleGetSingleOperation(module, collection, id, opts) {
6
6
  spinner.start({
7
7
  text: `Getting entry in [${collection}]`,
@@ -11,10 +11,12 @@ export async function handleGetSingleOperation(module, collection, id, opts) {
11
11
  responseFormat: "table",
12
12
  verbosity: opts.verbosity,
13
13
  });
14
- let { plainResult, result, meta, content } = handleResponseFormatOptions({ ...opts, responseFormat: "table" }, res);
14
+ const formattedOpts = { ...opts, responseFormat: "table" };
15
+ const formatted = handleResponseFormatOptions(formattedOpts, res, { noPrint: true });
15
16
  spinner.success({
16
17
  text: `Got entry in ${collection}`,
17
18
  });
18
19
  spinner.stop();
19
- return { data: plainResult, content, result, meta };
20
+ printFormattedResponse(formattedOpts, formatted);
21
+ return { data: formatted.plainResult, content: formatted.content, result: formatted.result, meta: formatted.meta };
20
22
  }
@@ -1,7 +1,7 @@
1
1
  // Operation handling functions
2
2
  import { apiCallHandler } from "../api/apiCallHandler.js";
3
3
  import { spinner } from "../../index.js";
4
- import { handleResponseFormatOptions, getApiResponseFormat } from "../helpers/handleResponseFormatOptions.js";
4
+ import { handleResponseFormatOptions, printFormattedResponse, getApiResponseFormat, } from "../helpers/handleResponseFormatOptions.js";
5
5
  import { getContextFilterParams, hasActiveContext } from "../helpers/contextInjector.js";
6
6
  async function fetchPage(module, collection, opts) {
7
7
  const contextParams = getContextFilterParams(collection);
@@ -47,14 +47,19 @@ export async function handleListOperation(module, collection, opts) {
47
47
  payload: allPayload,
48
48
  meta: { ...meta, pageInfo: { ...(meta.pageInfo || {}), page: 1, totalPages: 1, totalEntries: allPayload.length } },
49
49
  };
50
- const { plainResult, result, content } = handleResponseFormatOptions(opts, syntheticRes);
50
+ // Compute formatted result without printing we want the spinner success
51
+ // line to appear ABOVE the data block, not below it. handleListOperation
52
+ // is the canonical CRUD-display path for `ay list`.
53
+ const formatted = handleResponseFormatOptions(opts, syntheticRes, { noPrint: true });
51
54
  spinner.success({ text: `Got all ${allPayload.length} entries from ${totalPages} pages` });
52
55
  spinner.stop();
53
- return { data: plainResult, content, result, meta: syntheticRes.meta };
56
+ printFormattedResponse(opts, formatted);
57
+ return { data: formatted.plainResult, content: formatted.content, result: formatted.result, meta: syntheticRes.meta };
54
58
  }
55
59
  // Single page fetch
56
60
  let res = await fetchPage(module, collection, opts);
57
- let { plainResult, result, meta, content } = handleResponseFormatOptions(opts, res);
61
+ const formatted = handleResponseFormatOptions(opts, res, { noPrint: true });
62
+ const { plainResult, result, meta, content } = formatted;
58
63
  const count = Array.isArray(result) ? result.length : 0;
59
64
  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;
60
65
  spinner.success({
@@ -63,5 +68,6 @@ export async function handleListOperation(module, collection, opts) {
63
68
  : `No entries found in [${collection}]`,
64
69
  });
65
70
  spinner.stop();
71
+ printFormattedResponse(opts, formatted);
66
72
  return { data: plainResult, content, result, meta };
67
73
  }
@@ -1,6 +1,6 @@
1
1
  // Operation handling functions
2
2
  import { spinner } from "../../index.js";
3
- import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
3
+ import { handleResponseFormatOptions, printFormattedResponse, } from "../helpers/handleResponseFormatOptions.js";
4
4
  import { localStorage } from "../helpers/localStorage.js";
5
5
  import { auditCallHandler } from "../api/auditCallHandler.js";
6
6
  export async function handleSingleAuditOperation(collection, id, auditId, opts) {
@@ -12,16 +12,17 @@ export async function handleSingleAuditOperation(collection, id, auditId, opts)
12
12
  responseFormat: opts.responseFormat,
13
13
  verbosity: opts.verbosity,
14
14
  });
15
- let { plainResult, result, meta, content } = handleResponseFormatOptions({ ...opts }, res);
15
+ const formatted = handleResponseFormatOptions({ ...opts }, res, { noPrint: true });
16
16
  spinner.success({
17
17
  text: `Got audit [${auditId}] for [${id}] in [${collection}]`,
18
18
  });
19
19
  spinner.stop();
20
- localStorage.setItem("lastId", result._id);
20
+ printFormattedResponse({ ...opts }, formatted);
21
+ localStorage.setItem("lastId", formatted.result._id);
21
22
  return {
22
- singleData: plainResult,
23
- singleContent: content,
24
- singleResult: result,
25
- singleMeta: meta,
23
+ singleData: formatted.plainResult,
24
+ singleContent: formatted.content,
25
+ singleResult: formatted.result,
26
+ singleMeta: formatted.meta,
26
27
  };
27
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tolinax/ayoune-cli",
3
- "version": "2026.11.1",
3
+ "version": "2026.11.2",
4
4
  "description": "CLI for the aYOUne Business-as-a-Service platform",
5
5
  "type": "module",
6
6
  "main": "./index.js",