@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,8 +1,10 @@
1
- import { apiCallHandler } from "../api/apiCallHandler.js";
1
+ import { api, getModuleBaseUrl } from "../api/apiClient.js";
2
2
  import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
3
3
  import { saveFile } from "../helpers/saveFile.js";
4
+ import { secureStorage } from "../helpers/secureStorage.js";
4
5
  import { spinner } from "../../index.js";
5
- import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
6
+ import { EXIT_GENERAL_ERROR, EXIT_PERMISSION_DENIED } from "../exitCodes.js";
7
+ import { cliError } from "../helpers/cliError.js";
6
8
  export function createActionsCommand(program) {
7
9
  program
8
10
  .command("actions [search]")
@@ -26,7 +28,7 @@ Examples:
26
28
  .option("-p, --page <number>", "Page", parseInt, 1)
27
29
  .option("-l, --limit <number>", "Limit", parseInt, 100)
28
30
  .action(async (search, options) => {
29
- var _a;
31
+ var _a, _b, _c;
30
32
  try {
31
33
  const opts = { ...program.opts(), ...options };
32
34
  spinner.start({ text: "Fetching API actions...", color: "magenta" });
@@ -40,10 +42,40 @@ Examples:
40
42
  };
41
43
  if (search)
42
44
  params.q = search;
43
- // Fetch actions from the SU config API (where apiactions are stored)
44
- const res = await apiCallHandler("config", "ayouneapiactions", "get", null, params);
45
+ // Try su module first (full access), fall back to config module (limited access)
46
+ const modules = ["su", "config"];
47
+ let res = null;
48
+ let permissionDenied = false;
49
+ for (const mod of modules) {
50
+ try {
51
+ const token = secureStorage.getItem("token") || "";
52
+ const response = await api({
53
+ baseURL: getModuleBaseUrl(mod),
54
+ method: "get",
55
+ url: "ayouneapiactions",
56
+ params,
57
+ headers: { Authorization: `Bearer ${token}` },
58
+ });
59
+ res = response.data;
60
+ if (res === null || res === void 0 ? void 0 : res.payload)
61
+ break;
62
+ }
63
+ catch (e) {
64
+ // On 401/403 permission denied, try next module
65
+ if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) === 403 || ((_b = e.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
66
+ permissionDenied = true;
67
+ continue;
68
+ }
69
+ // For other errors (network, 500, etc.), only throw on last module
70
+ if (mod === modules[modules.length - 1])
71
+ throw e;
72
+ }
73
+ }
45
74
  if (!(res === null || res === void 0 ? void 0 : res.payload)) {
46
- spinner.error({ text: "No API actions found or insufficient permissions" });
75
+ if (permissionDenied) {
76
+ cliError("Permission denied: you do not have access to query API actions. Contact your administrator for the required rights.", EXIT_PERMISSION_DENIED, "permission_denied");
77
+ }
78
+ spinner.error({ text: "No API actions found" });
47
79
  return;
48
80
  }
49
81
  let actions = Array.isArray(res.payload) ? res.payload : [res.payload];
@@ -91,7 +123,7 @@ Examples:
91
123
  })),
92
124
  meta: {
93
125
  ...res.meta,
94
- pageInfo: { ...(_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo, totalEntries: actions.length },
126
+ pageInfo: { ...(_c = res.meta) === null || _c === void 0 ? void 0 : _c.pageInfo, totalEntries: actions.length },
95
127
  },
96
128
  };
97
129
  const { plainResult, result, content } = handleResponseFormatOptions(opts, formattedRes);
@@ -102,8 +134,7 @@ Examples:
102
134
  }
103
135
  }
104
136
  catch (e) {
105
- spinner.error({ text: e.message || "Failed to fetch API actions" });
106
- process.exit(EXIT_GENERAL_ERROR);
137
+ cliError(e.message || "Failed to fetch API actions", EXIT_GENERAL_ERROR);
107
138
  }
108
139
  });
109
140
  }
@@ -2,9 +2,11 @@ import { apiCallHandler } from "../api/apiCallHandler.js";
2
2
  import { api } from "../api/apiClient.js";
3
3
  import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
4
4
  import { saveFile } from "../helpers/saveFile.js";
5
- import { localStorage } from "../helpers/localStorage.js";
5
+ import { secureStorage } from "../helpers/secureStorage.js";
6
6
  import { spinner } from "../../index.js";
7
7
  import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
8
+ import { cliError } from "../helpers/cliError.js";
9
+ import { getContextSummary, getContextForAI, hasActiveContext } from "../helpers/contextInjector.js";
8
10
  const AI_HOST = "ai.ayoune.app";
9
11
  export function createAiCommand(program) {
10
12
  const ai = program
@@ -17,7 +19,7 @@ export function createAiCommand(program) {
17
19
  try {
18
20
  const opts = { ...program.opts() };
19
21
  spinner.start({ text: "Fetching AI actions...", color: "magenta" });
20
- const res = await apiCallHandler("config", "ayouneapiactions", "get", null, {
22
+ const res = await apiCallHandler("su", "ayouneapiactions", "get", null, {
21
23
  q: "ai",
22
24
  limit: 200,
23
25
  responseFormat: "json",
@@ -43,8 +45,7 @@ export function createAiCommand(program) {
43
45
  spinner.stop();
44
46
  }
45
47
  catch (e) {
46
- spinner.error({ text: e.message || "Failed to fetch AI actions" });
47
- process.exit(EXIT_GENERAL_ERROR);
48
+ cliError(e.message || "Failed to fetch AI actions", EXIT_GENERAL_ERROR);
48
49
  }
49
50
  });
50
51
  // ay ai conversations — list AI conversations
@@ -57,7 +58,7 @@ export function createAiCommand(program) {
57
58
  try {
58
59
  const opts = { ...program.opts(), ...options };
59
60
  spinner.start({ text: "Fetching AI conversations...", color: "magenta" });
60
- const res = await apiCallHandler("ai", "conversations", "get", null, {
61
+ const res = await apiCallHandler("ai", "aiconversations", "get", null, {
61
62
  page: opts.page,
62
63
  limit: opts.limit,
63
64
  responseFormat: opts.responseFormat,
@@ -72,8 +73,7 @@ export function createAiCommand(program) {
72
73
  await saveFile("ai-conversations", opts, res);
73
74
  }
74
75
  catch (e) {
75
- spinner.error({ text: e.message || "Failed to list conversations" });
76
- process.exit(EXIT_GENERAL_ERROR);
76
+ cliError(e.message || "Failed to list conversations", EXIT_GENERAL_ERROR);
77
77
  }
78
78
  });
79
79
  // ay ai prompts — list AI prompt templates
@@ -86,7 +86,7 @@ export function createAiCommand(program) {
86
86
  try {
87
87
  const opts = { ...program.opts(), ...options };
88
88
  spinner.start({ text: "Fetching AI prompts...", color: "magenta" });
89
- const res = await apiCallHandler("ai", "prompts", "get", null, {
89
+ const res = await apiCallHandler("ai", "aiprompts", "get", null, {
90
90
  page: opts.page,
91
91
  limit: opts.limit,
92
92
  responseFormat: opts.responseFormat,
@@ -101,8 +101,7 @@ export function createAiCommand(program) {
101
101
  await saveFile("ai-prompts", opts, res);
102
102
  }
103
103
  catch (e) {
104
- spinner.error({ text: e.message || "Failed to list prompts" });
105
- process.exit(EXIT_GENERAL_ERROR);
104
+ cliError(e.message || "Failed to list prompts", EXIT_GENERAL_ERROR);
106
105
  }
107
106
  });
108
107
  // ay ai ask <prompt> — send a prompt to AI
@@ -120,6 +119,10 @@ export function createAiCommand(program) {
120
119
  body.conversationId = opts.conversation;
121
120
  if (opts.model)
122
121
  body.model = opts.model;
122
+ if (hasActiveContext()) {
123
+ body.context = getContextForAI();
124
+ body.prompt = `${getContextSummary()}\n\nUser question: ${prompt}`;
125
+ }
123
126
  const res = await apiCallHandler("ai", "ask", "post", body, {
124
127
  responseFormat: opts.responseFormat,
125
128
  verbosity: opts.verbosity,
@@ -132,8 +135,7 @@ export function createAiCommand(program) {
132
135
  await saveFile("ai-ask", opts, res);
133
136
  }
134
137
  catch (e) {
135
- spinner.error({ text: e.message || "AI request failed" });
136
- process.exit(EXIT_GENERAL_ERROR);
138
+ cliError(e.message || "AI request failed", EXIT_GENERAL_ERROR);
137
139
  }
138
140
  });
139
141
  // ay ai generate <type> <prompt> — generate content
@@ -156,8 +158,7 @@ Examples:
156
158
  };
157
159
  const host = typeHostMap[type];
158
160
  if (!host) {
159
- spinner.error({ text: `Unknown type: ${type}. Use: text, image, video` });
160
- process.exit(EXIT_GENERAL_ERROR);
161
+ cliError(`Unknown type: ${type}. Use: text, image, video`, EXIT_GENERAL_ERROR);
161
162
  }
162
163
  spinner.start({ text: `Generating ${type}...`, color: "magenta" });
163
164
  const response = await api({
@@ -170,7 +171,7 @@ Examples:
170
171
  verbosity: opts.verbosity,
171
172
  },
172
173
  headers: {
173
- Authorization: `Bearer ${localStorage.getItem("token")}`,
174
+ Authorization: `Bearer ${secureStorage.getItem("token")}`,
174
175
  },
175
176
  });
176
177
  const res = response.data;
@@ -181,8 +182,7 @@ Examples:
181
182
  await saveFile(`ai-generate-${type}`, opts, res);
182
183
  }
183
184
  catch (e) {
184
- spinner.error({ text: e.message || `${type} generation failed` });
185
- process.exit(EXIT_GENERAL_ERROR);
185
+ cliError(e.message || `${type} generation failed`, EXIT_GENERAL_ERROR);
186
186
  }
187
187
  });
188
188
  }
@@ -3,6 +3,7 @@ import path from "path";
3
3
  import os from "os";
4
4
  import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
5
5
  import { spinner } from "../../index.js";
6
+ import { cliError } from "../helpers/cliError.js";
6
7
  const ALIASES_PATH = path.join(os.homedir(), ".config", "ayoune", "aliases.json");
7
8
  export function loadAliases() {
8
9
  if (!existsSync(ALIASES_PATH)) {
@@ -42,8 +43,7 @@ Examples:
42
43
  spinner.success({ text: `Alias '${name}' set to '${command}'` });
43
44
  }
44
45
  catch (e) {
45
- spinner.error({ text: e.message || "Failed to save alias" });
46
- process.exit(EXIT_GENERAL_ERROR);
46
+ cliError(e.message || "Failed to save alias", EXIT_GENERAL_ERROR);
47
47
  }
48
48
  });
49
49
  alias
@@ -67,16 +67,14 @@ Examples:
67
67
  try {
68
68
  const aliases = loadAliases();
69
69
  if (!(name in aliases)) {
70
- spinner.error({ text: `Alias '${name}' not found` });
71
- process.exit(EXIT_MISUSE);
70
+ cliError(`Alias '${name}' not found`, EXIT_MISUSE);
72
71
  }
73
72
  delete aliases[name];
74
73
  saveAliases(aliases);
75
74
  spinner.success({ text: `Alias '${name}' removed` });
76
75
  }
77
76
  catch (e) {
78
- spinner.error({ text: e.message || "Failed to remove alias" });
79
- process.exit(EXIT_GENERAL_ERROR);
77
+ cliError(e.message || "Failed to remove alias", EXIT_GENERAL_ERROR);
80
78
  }
81
79
  });
82
80
  }
@@ -4,8 +4,8 @@ import { handleAuditOperation } from "../operations/handleAuditOperation.js";
4
4
  import { promptAudits } from "../prompts/promptAudits.js";
5
5
  import { handleSingleAuditOperation } from "../operations/handleSingleAuditOperation.js";
6
6
  import yaml from "js-yaml";
7
- import { spinner } from "../../index.js";
8
7
  import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
8
+ import { cliError } from "../helpers/cliError.js";
9
9
  export function createAuditCommand(program) {
10
10
  program
11
11
  .command("audit")
@@ -20,20 +20,17 @@ Examples:
20
20
  .action(async (collection, id, options) => {
21
21
  try {
22
22
  if (!collection) {
23
- spinner.error({ text: "Missing required argument: collection. Run a list or get command first, or provide it explicitly." });
24
- process.exit(EXIT_MISUSE);
23
+ cliError("Missing required argument: collection. Run a list or get command first, or provide it explicitly.", EXIT_MISUSE);
25
24
  }
26
25
  if (!id) {
27
- spinner.error({ text: "Missing required argument: id. Provide an entry ID explicitly." });
28
- process.exit(EXIT_MISUSE);
26
+ cliError("Missing required argument: id. Provide an entry ID explicitly.", EXIT_MISUSE);
29
27
  }
30
28
  const opts = { ...program.opts(), ...options };
31
29
  const { data, content, result, meta } = await handleAuditOperation(collection, id, {
32
30
  ...opts,
33
31
  });
34
32
  if (!process.stdin.isTTY) {
35
- spinner.error({ text: "The audit command requires an interactive terminal for audit selection" });
36
- process.exit(EXIT_MISUSE);
33
+ cliError("The audit command requires an interactive terminal for audit selection", EXIT_MISUSE);
37
34
  }
38
35
  const selectedAudit = await promptAudits(result);
39
36
  const { singleData, singleContent, singleResult, singleMeta } = await handleSingleAuditOperation(collection, id, selectedAudit, {
@@ -42,8 +39,7 @@ Examples:
42
39
  console.log(yaml.dump(singleContent));
43
40
  }
44
41
  catch (e) {
45
- spinner.error({ text: e.message || "An unexpected error occurred" });
46
- process.exit(EXIT_GENERAL_ERROR);
42
+ cliError(e.message || "An unexpected error occurred", EXIT_GENERAL_ERROR);
47
43
  }
48
44
  });
49
45
  }
@@ -6,6 +6,7 @@ import { handleDeleteSingleOperation } from "../operations/handleDeleteSingleOpe
6
6
  import { saveFile } from "../helpers/saveFile.js";
7
7
  import { spinner } from "../../index.js";
8
8
  import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
9
+ import { cliError } from "../helpers/cliError.js";
9
10
  async function readStdin() {
10
11
  const chunks = [];
11
12
  for await (const chunk of process.stdin) {
@@ -42,8 +43,7 @@ Examples:
42
43
  idList.push(...stdinIds);
43
44
  }
44
45
  if (idList.length === 0) {
45
- spinner.error({ text: "No IDs provided" });
46
- process.exit(EXIT_MISUSE);
46
+ cliError("No IDs provided", EXIT_MISUSE);
47
47
  }
48
48
  spinner.start({ text: `Fetching ${idList.length} entries from ${collection}...`, color: "magenta" });
49
49
  const results = [];
@@ -69,8 +69,7 @@ Examples:
69
69
  await saveFile("batch-get", opts, res);
70
70
  }
71
71
  catch (e) {
72
- spinner.error({ text: e.message || "Batch get failed" });
73
- process.exit(EXIT_GENERAL_ERROR);
72
+ cliError(e.message || "Batch get failed", EXIT_GENERAL_ERROR);
74
73
  }
75
74
  });
76
75
  // ay batch delete <collection> <ids>
@@ -93,8 +92,7 @@ Examples:
93
92
  idList.push(...stdinIds);
94
93
  }
95
94
  if (idList.length === 0) {
96
- spinner.error({ text: "No IDs provided" });
97
- process.exit(EXIT_MISUSE);
95
+ cliError("No IDs provided", EXIT_MISUSE);
98
96
  }
99
97
  // Confirmation
100
98
  if (!opts.force && process.stdin.isTTY) {
@@ -130,8 +128,7 @@ Examples:
130
128
  process.exit(EXIT_GENERAL_ERROR);
131
129
  }
132
130
  catch (e) {
133
- spinner.error({ text: e.message || "Batch delete failed" });
134
- process.exit(EXIT_GENERAL_ERROR);
131
+ cliError(e.message || "Batch delete failed", EXIT_GENERAL_ERROR);
135
132
  }
136
133
  });
137
134
  // ay batch update <collection> --body-file <file>
@@ -158,8 +155,7 @@ Examples:
158
155
  entries = JSON.parse(opts.body);
159
156
  }
160
157
  catch (_a) {
161
- spinner.error({ text: "Invalid JSON in --body" });
162
- process.exit(EXIT_MISUSE);
158
+ cliError("Invalid JSON in --body", EXIT_MISUSE);
163
159
  }
164
160
  }
165
161
  if (opts.bodyFile) {
@@ -169,8 +165,7 @@ Examples:
169
165
  entries = JSON.parse(content);
170
166
  }
171
167
  catch (_b) {
172
- spinner.error({ text: `Invalid JSON in file: ${opts.bodyFile}` });
173
- process.exit(EXIT_MISUSE);
168
+ cliError(`Invalid JSON in file: ${opts.bodyFile}`, EXIT_MISUSE);
174
169
  }
175
170
  }
176
171
  if (opts.bodyStdin && !process.stdin.isTTY) {
@@ -180,14 +175,12 @@ Examples:
180
175
  entries = JSON.parse(stdinContent);
181
176
  }
182
177
  catch (_c) {
183
- spinner.error({ text: "Invalid JSON from stdin" });
184
- process.exit(EXIT_MISUSE);
178
+ cliError("Invalid JSON from stdin", EXIT_MISUSE);
185
179
  }
186
180
  }
187
181
  }
188
182
  if (!Array.isArray(entries) || entries.length === 0) {
189
- spinner.error({ text: "Provide a JSON array of entries with _id fields" });
190
- process.exit(EXIT_MISUSE);
183
+ cliError("Provide a JSON array of entries with _id fields", EXIT_MISUSE);
191
184
  }
192
185
  spinner.start({ text: `Updating ${entries.length} entries in ${collection}...`, color: "magenta" });
193
186
  let successCount = 0;
@@ -211,8 +204,7 @@ Examples:
211
204
  process.exit(EXIT_GENERAL_ERROR);
212
205
  }
213
206
  catch (e) {
214
- spinner.error({ text: e.message || "Batch update failed" });
215
- process.exit(EXIT_GENERAL_ERROR);
207
+ cliError(e.message || "Batch update failed", EXIT_GENERAL_ERROR);
216
208
  }
217
209
  });
218
210
  // ay batch create <collection> --body-file <file>
@@ -238,8 +230,7 @@ Examples:
238
230
  entries = JSON.parse(opts.body);
239
231
  }
240
232
  catch (_a) {
241
- spinner.error({ text: "Invalid JSON in --body" });
242
- process.exit(EXIT_MISUSE);
233
+ cliError("Invalid JSON in --body", EXIT_MISUSE);
243
234
  }
244
235
  }
245
236
  if (opts.bodyFile) {
@@ -249,8 +240,7 @@ Examples:
249
240
  entries = JSON.parse(content);
250
241
  }
251
242
  catch (_b) {
252
- spinner.error({ text: `Invalid JSON in file: ${opts.bodyFile}` });
253
- process.exit(EXIT_MISUSE);
243
+ cliError(`Invalid JSON in file: ${opts.bodyFile}`, EXIT_MISUSE);
254
244
  }
255
245
  }
256
246
  if (opts.bodyStdin && !process.stdin.isTTY) {
@@ -260,14 +250,12 @@ Examples:
260
250
  entries = JSON.parse(stdinContent);
261
251
  }
262
252
  catch (_c) {
263
- spinner.error({ text: "Invalid JSON from stdin" });
264
- process.exit(EXIT_MISUSE);
253
+ cliError("Invalid JSON from stdin", EXIT_MISUSE);
265
254
  }
266
255
  }
267
256
  }
268
257
  if (!Array.isArray(entries) || entries.length === 0) {
269
- spinner.error({ text: "Provide a JSON array of entries to create" });
270
- process.exit(EXIT_MISUSE);
258
+ cliError("Provide a JSON array of entries to create", EXIT_MISUSE);
271
259
  }
272
260
  spinner.start({ text: `Creating ${entries.length} entries in ${collection}...`, color: "magenta" });
273
261
  let successCount = 0;
@@ -297,8 +285,7 @@ Examples:
297
285
  process.exit(EXIT_GENERAL_ERROR);
298
286
  }
299
287
  catch (e) {
300
- spinner.error({ text: e.message || "Batch create failed" });
301
- process.exit(EXIT_GENERAL_ERROR);
288
+ cliError(e.message || "Batch create failed", EXIT_GENERAL_ERROR);
302
289
  }
303
290
  });
304
291
  }
@@ -1,9 +1,10 @@
1
+ import { cliError } from "../helpers/cliError.js";
1
2
  const BASH_COMPLETION = `###-begin-ay-completions-###
2
3
  _ay_completions() {
3
4
  local cur commands
4
5
  COMPREPLY=()
5
6
  cur="\${COMP_WORDS[COMP_CWORD]}"
6
- commands="modules list get edit copy create describe audit stream events storage whoami login logout completions alias config actions exec ai services deploy monitor delete update batch search webhooks jobs export users sync permissions templates"
7
+ commands="modules list get edit copy create describe audit stream events storage whoami login logout completions alias config actions exec ai services deploy monitor delete update batch search webhooks jobs export users sync permissions templates context"
7
8
 
8
9
  if [[ \${COMP_CWORD} -eq 1 ]]; then
9
10
  COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
@@ -49,6 +50,7 @@ _ay() {
49
50
  'sync:Synchronize platform data across systems'
50
51
  'permissions:Manage permissions and access requests'
51
52
  'templates:Manage platform templates'
53
+ 'context:Manage active entity context'
52
54
  )
53
55
  _describe 'command' commands
54
56
  }
@@ -89,6 +91,7 @@ complete -c ay -n '__fish_use_subcommand' -a users -d 'Manage users, teams, and
89
91
  complete -c ay -n '__fish_use_subcommand' -a sync -d 'Synchronize platform data across systems'
90
92
  complete -c ay -n '__fish_use_subcommand' -a permissions -d 'Manage permissions and access requests'
91
93
  complete -c ay -n '__fish_use_subcommand' -a templates -d 'Manage platform templates'
94
+ complete -c ay -n '__fish_use_subcommand' -a context -d 'Manage active entity context'
92
95
  ###-end-ay-completions-###`;
93
96
  const POWERSHELL_COMPLETION = `###-begin-ay-completions-###
94
97
  Register-ArgumentCompleter -CommandName ay -ScriptBlock {
@@ -128,6 +131,7 @@ Register-ArgumentCompleter -CommandName ay -ScriptBlock {
128
131
  @{ Name = 'sync'; Description = 'Synchronize platform data across systems' }
129
132
  @{ Name = 'permissions'; Description = 'Manage permissions and access requests' }
130
133
  @{ Name = 'templates'; Description = 'Manage platform templates' }
134
+ @{ Name = 'context'; Description = 'Manage active entity context' }
131
135
  )
132
136
  $commands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {
133
137
  [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Description)
@@ -162,8 +166,7 @@ Examples:
162
166
  console.log(POWERSHELL_COMPLETION);
163
167
  break;
164
168
  default:
165
- console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish, powershell`);
166
- process.exit(2);
169
+ cliError(`Unknown shell: ${shell}. Supported: bash, zsh, fish, powershell`, 2);
167
170
  }
168
171
  });
169
172
  }
@@ -3,6 +3,7 @@ import inquirer from "inquirer";
3
3
  import { loadConfig, saveConfig } from "../helpers/configLoader.js";
4
4
  import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
5
5
  import { spinner } from "../../index.js";
6
+ import { cliError } from "../helpers/cliError.js";
6
7
  const SETTABLE_KEYS = {
7
8
  responseFormat: { type: "string", choices: ["json", "csv", "yaml", "table"] },
8
9
  verbosity: { type: "string", choices: ["default", "extended", "minimal"] },
@@ -113,8 +114,7 @@ Examples:
113
114
  spinner.success({ text: "Defaults saved" });
114
115
  }
115
116
  catch (e) {
116
- spinner.error({ text: e.message || "Failed to save config" });
117
- process.exit(EXIT_GENERAL_ERROR);
117
+ cliError(e.message || "Failed to save config", EXIT_GENERAL_ERROR);
118
118
  }
119
119
  });
120
120
  config
@@ -125,20 +125,17 @@ Examples:
125
125
  try {
126
126
  const meta = SETTABLE_KEYS[key];
127
127
  if (!meta) {
128
- spinner.error({ text: `Unknown config key '${key}'. Valid keys: ${Object.keys(SETTABLE_KEYS).join(", ")}` });
129
- process.exit(EXIT_MISUSE);
128
+ cliError(`Unknown config key '${key}'. Valid keys: ${Object.keys(SETTABLE_KEYS).join(", ")}`, EXIT_MISUSE);
130
129
  }
131
130
  let parsed = value;
132
131
  if (meta.type === "boolean") {
133
132
  parsed = parseBoolean(value);
134
133
  if (parsed === null) {
135
- spinner.error({ text: `Invalid boolean value '${value}'. Use true/false, yes/no, 1/0, on/off` });
136
- process.exit(EXIT_MISUSE);
134
+ cliError(`Invalid boolean value '${value}'. Use true/false, yes/no, 1/0, on/off`, EXIT_MISUSE);
137
135
  }
138
136
  }
139
137
  else if (meta.choices && !meta.choices.includes(value)) {
140
- spinner.error({ text: `Invalid value '${value}' for ${key}. Choices: ${meta.choices.join(", ")}` });
141
- process.exit(EXIT_MISUSE);
138
+ cliError(`Invalid value '${value}' for ${key}. Choices: ${meta.choices.join(", ")}`, EXIT_MISUSE);
142
139
  }
143
140
  const cfg = loadConfig();
144
141
  cfg.defaults = (_a = cfg.defaults) !== null && _a !== void 0 ? _a : {};
@@ -147,8 +144,7 @@ Examples:
147
144
  spinner.success({ text: `${key} set to '${parsed}'` });
148
145
  }
149
146
  catch (e) {
150
- spinner.error({ text: e.message || "Failed to save config" });
151
- process.exit(EXIT_GENERAL_ERROR);
147
+ cliError(e.message || "Failed to save config", EXIT_GENERAL_ERROR);
152
148
  }
153
149
  });
154
150
  config
@@ -157,8 +153,7 @@ Examples:
157
153
  .action((key) => {
158
154
  var _a;
159
155
  if (!SETTABLE_KEYS[key]) {
160
- spinner.error({ text: `Unknown config key '${key}'. Valid keys: ${Object.keys(SETTABLE_KEYS).join(", ")}` });
161
- process.exit(EXIT_MISUSE);
156
+ cliError(`Unknown config key '${key}'. Valid keys: ${Object.keys(SETTABLE_KEYS).join(", ")}`, EXIT_MISUSE);
162
157
  }
163
158
  const cfg = loadConfig();
164
159
  const value = (_a = cfg.defaults) === null || _a === void 0 ? void 0 : _a[key];
@@ -201,8 +196,7 @@ Examples:
201
196
  spinner.success({ text: "All defaults have been reset" });
202
197
  }
203
198
  catch (e) {
204
- spinner.error({ text: e.message || "Failed to reset config" });
205
- process.exit(EXIT_GENERAL_ERROR);
199
+ cliError(e.message || "Failed to reset config", EXIT_GENERAL_ERROR);
206
200
  }
207
201
  });
208
202
  }