@tolinax/ayoune-cli 2026.8.2 → 2026.9.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/api/apiCallHandler.js +1 -1
- package/lib/api/apiClient.js +74 -62
- package/lib/commands/_registry.js +279 -0
- package/lib/commands/aggregate/_shared.js +21 -0
- package/lib/commands/aggregate/_stageBuilders.js +295 -0
- package/lib/commands/aggregate/exec.js +51 -0
- package/lib/commands/aggregate/index.js +38 -0
- package/lib/commands/aggregate/list.js +43 -0
- package/lib/commands/aggregate/models.js +43 -0
- package/lib/commands/aggregate/run.js +53 -0
- package/lib/commands/aggregate/save.js +53 -0
- package/lib/commands/aggregate/validate.js +47 -0
- package/lib/commands/aggregate/wizard.js +174 -0
- package/lib/commands/createAggregateCommand.js +5 -658
- package/lib/commands/createDeployCommand.js +5 -642
- package/lib/commands/createProgram.js +251 -161
- package/lib/commands/createServicesCommand.js +4 -5
- package/lib/commands/createWhoAmICommand.js +5 -5
- package/lib/commands/deploy/_token.js +8 -0
- package/lib/commands/deploy/alerts.js +43 -0
- package/lib/commands/deploy/clusters.js +62 -0
- package/lib/commands/deploy/dashboard.js +31 -0
- package/lib/commands/deploy/deployments.js +216 -0
- package/lib/commands/deploy/index.js +31 -0
- package/lib/commands/deploy/pipelines.js +82 -0
- package/lib/commands/deploy/plans.js +147 -0
- package/lib/commands/deploy/pods.js +70 -0
- package/lib/commands/deploy/repos.js +63 -0
- package/lib/helpers/dateFormat.js +119 -0
- package/lib/helpers/formatDocument.js +4 -5
- package/lib/helpers/logo.js +86 -13
- package/lib/helpers/saveFile.js +4 -9
- package/lib/models/getModelsInModules.js +6 -8
- package/lib/models/getModuleFromCollection.js +2 -2
- package/lib/operations/handleCollectionOperation.js +2 -3
- package/package.json +2 -12
|
@@ -1,60 +1,46 @@
|
|
|
1
|
-
//Defines the main program structure for command line interface
|
|
1
|
+
// Defines the main program structure for the `ay` command line interface.
|
|
2
|
+
//
|
|
3
|
+
// This module is on the cold-start path of EVERY `ay` invocation, so it must
|
|
4
|
+
// stay free of heavy imports. Two patterns enforce that:
|
|
5
|
+
//
|
|
6
|
+
// 1. **Lazy command loading.** Instead of statically importing all 41
|
|
7
|
+
// command modules, we look up the user's argv in COMMAND_REGISTRY
|
|
8
|
+
// (`./_registry.ts`) and dynamic-import only the one they're running.
|
|
9
|
+
// Help / version / unknown-command paths fall back to registering
|
|
10
|
+
// lightweight stubs for every command so the help listing still works.
|
|
11
|
+
//
|
|
12
|
+
// 2. **Lazy logo.** `logo.ts` no longer top-level-imports `figlet`. The
|
|
13
|
+
// help-text thunks below only resolve when commander actually shows
|
|
14
|
+
// help, so the cost is paid once per `--help` invocation, not on every
|
|
15
|
+
// `ay <real-command>`.
|
|
16
|
+
//
|
|
17
|
+
// See `_registry.ts` for the registry, and `bench-startup.mjs` for the
|
|
18
|
+
// benchmark used to verify cold-start improvements.
|
|
2
19
|
import { Command, Option } from "commander";
|
|
3
|
-
import chalk from "chalk";
|
|
4
20
|
import path from "path";
|
|
5
21
|
import os from "os";
|
|
6
22
|
import { spinner } from "../../index.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { createEditCommand } from "./createEditCommand.js";
|
|
11
|
-
import { createLoginCommand } from "./createLoginCommand.js";
|
|
12
|
-
import { createDescribeCommand } from "./createDescribeCommand.js";
|
|
13
|
-
import { createCopyCommand } from "./createCopyCommand.js";
|
|
14
|
-
import { createCreateCommand } from "./createCreateCommand.js";
|
|
15
|
-
import { createStorageCommand } from "./createStorageCommand.js";
|
|
16
|
-
import { createAuditCommand } from "./createAuditCommand.js";
|
|
17
|
-
import { createStreamCommand } from "./createStreamCommand.js";
|
|
18
|
-
import { createEventsCommand } from "./createEventsCommand.js";
|
|
19
|
-
import { createWhoAmICommand } from "./createWhoAmICommand.js";
|
|
20
|
-
import { createLogoutCommand } from "./createLogoutCommand.js";
|
|
21
|
-
import { createCompletionsCommand } from "./createCompletionsCommand.js";
|
|
22
|
-
import { createAliasCommand, registerUserAliases } from "./createAliasCommand.js";
|
|
23
|
-
import { createConfigCommand } from "./createConfigCommand.js";
|
|
24
|
-
import { createActionsCommand } from "./createActionsCommand.js";
|
|
25
|
-
import { createExecCommand } from "./createExecCommand.js";
|
|
26
|
-
import { createAiCommand } from "./createAiCommand.js";
|
|
27
|
-
import { enableDebug } from "../api/apiClient.js";
|
|
28
|
-
import { enableDryRun } from "../api/apiCallHandler.js";
|
|
23
|
+
import { findCommandSpec, registerStubsForHelp } from "./_registry.js";
|
|
24
|
+
// Cheap helpers — these are pure-TS modules with no heavy transitive imports,
|
|
25
|
+
// so they can stay top-level without affecting cold-start.
|
|
29
26
|
import { setJsonErrorsEnabled } from "../helpers/cliError.js";
|
|
30
27
|
import { disableContext } from "../helpers/contextInjector.js";
|
|
31
|
-
import { createServicesCommand } from "./createServicesCommand.js";
|
|
32
|
-
import { createDeployCommand } from "./createDeployCommand.js";
|
|
33
|
-
import { createMonitorCommand } from "./createMonitorCommand.js";
|
|
34
|
-
import { createDeleteCommand } from "./createDeleteCommand.js";
|
|
35
|
-
import { createUpdateCommand } from "./createUpdateCommand.js";
|
|
36
|
-
import { createBatchCommand } from "./createBatchCommand.js";
|
|
37
|
-
import { createSearchCommand } from "./createSearchCommand.js";
|
|
38
|
-
import { createWebhooksCommand } from "./createWebhooksCommand.js";
|
|
39
|
-
import { createJobsCommand } from "./createJobsCommand.js";
|
|
40
|
-
import { createExportCommand } from "./createExportCommand.js";
|
|
41
|
-
import { createUsersCommand } from "./createUsersCommand.js";
|
|
42
|
-
import { createSyncCommand } from "./createSyncCommand.js";
|
|
43
|
-
import { createPermissionsCommand } from "./createPermissionsCommand.js";
|
|
44
|
-
import { createTemplateCommand } from "./createTemplateCommand.js";
|
|
45
|
-
import { createSetupCommand } from "./createSetupCommand.js";
|
|
46
|
-
import { createStatusCommand } from "./createStatusCommand.js";
|
|
47
|
-
import { createSelfHostUpdateCommand } from "./createSelfHostUpdateCommand.js";
|
|
48
|
-
import { createContextCommand } from "./createContextCommand.js";
|
|
49
|
-
import { createAccessCommand } from "./createAccessCommand.js";
|
|
50
|
-
import { createAggregateCommand } from "./createAggregateCommand.js";
|
|
51
|
-
import { createDbCommand } from "./createDbCommand.js";
|
|
52
|
-
import { secureStorage } from "../helpers/secureStorage.js";
|
|
53
|
-
import { login } from "../api/login.js";
|
|
54
28
|
import { loadConfig } from "../helpers/configLoader.js";
|
|
55
|
-
import {
|
|
56
|
-
|
|
29
|
+
import { loadAliases } from "./createAliasCommand.js";
|
|
30
|
+
// logo.ts is intentionally lightweight: it does NOT top-level-import chalk
|
|
31
|
+
// or figlet, so static-importing it here is free. The expensive bits live
|
|
32
|
+
// inside getLogoSync()/brandHighlight()/dim(), each of which lazily requires
|
|
33
|
+
// chalk/figlet only on first call.
|
|
34
|
+
import { getLogoSync, brandHighlight, dim } from "../helpers/logo.js";
|
|
57
35
|
import { createRequire } from "module";
|
|
36
|
+
// HEAVY modules deliberately NOT imported at the top of this file:
|
|
37
|
+
// - ../api/apiClient (pulls in @tolinax/ayoune-core HTTP client + axios)
|
|
38
|
+
// - ../api/apiCallHandler (pulls in apiClient + secureStorage + login)
|
|
39
|
+
// - ../helpers/secureStorage (pulls in node-localstorage + crypto)
|
|
40
|
+
// - ../api/login (pulls in socket.io-client)
|
|
41
|
+
// - ../helpers/updateNotifier (pulls in node-localstorage)
|
|
42
|
+
// They are lazy-imported inside the preAction hook (which only fires for real
|
|
43
|
+
// command runs, never for --version/--help/unknown-command paths).
|
|
58
44
|
const require = createRequire(import.meta.url);
|
|
59
45
|
let pkg;
|
|
60
46
|
try {
|
|
@@ -63,120 +49,103 @@ try {
|
|
|
63
49
|
catch (_a) {
|
|
64
50
|
pkg = require("../../../package.json"); // source tree (tests)
|
|
65
51
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
.default(false)
|
|
78
|
-
.conflicts("verbosity"))
|
|
79
|
-
.addOption(new Option("-s, --save", "Saves the response as file"))
|
|
80
|
-
.addOption(new Option("-d, --debug", "Show detailed request/response information"))
|
|
81
|
-
.addOption(new Option("-o, --outPath [filePath]", "Lets you set a path").default(path.join(os.homedir(), "aYOUne")))
|
|
82
|
-
.addOption(new Option("-n, --name [fileName]", "Lets you set a filename"))
|
|
83
|
-
.addOption(new Option("-q, --quiet", "Suppress all output except errors and results"))
|
|
84
|
-
.addOption(new Option("--force", "Skip confirmation prompts for destructive actions"))
|
|
85
|
-
.addOption(new Option("--dry-run", "Preview what a command would do without executing"))
|
|
86
|
-
.addOption(new Option("--jq <expression>", "Filter JSON output using JMESPath expression"))
|
|
87
|
-
.addOption(new Option("--columns <fields>", "Comma-separated list of columns to display"))
|
|
88
|
-
.addOption(new Option("--no-color", "Disable colored output"))
|
|
89
|
-
.addOption(new Option("--json-errors", "Output errors as JSON to stderr (for AI agents/scripts)"))
|
|
90
|
-
.addOption(new Option("--token <jwt>", "Authenticate with a JWT token for this session"))
|
|
91
|
-
.addOption(new Option("--no-context", "Disable context injection for this command"))
|
|
92
|
-
.description(getDescription());
|
|
93
|
-
program.showHelpAfterError();
|
|
94
|
-
program.showSuggestionAfterError(true);
|
|
95
|
-
createModulesCommand(program);
|
|
96
|
-
createListCommand(program);
|
|
97
|
-
createGetCommand(program);
|
|
98
|
-
createEditCommand(program);
|
|
99
|
-
createCopyCommand(program);
|
|
100
|
-
createDescribeCommand(program);
|
|
101
|
-
createCreateCommand(program);
|
|
102
|
-
createStorageCommand(program);
|
|
103
|
-
createAuditCommand(program);
|
|
104
|
-
createStreamCommand(program);
|
|
105
|
-
createEventsCommand(program);
|
|
106
|
-
createWhoAmICommand(program);
|
|
107
|
-
createLogoutCommand(program);
|
|
108
|
-
createActionsCommand(program);
|
|
109
|
-
createExecCommand(program);
|
|
110
|
-
createAiCommand(program);
|
|
111
|
-
createServicesCommand(program);
|
|
112
|
-
createDeployCommand(program);
|
|
113
|
-
createMonitorCommand(program);
|
|
114
|
-
createDeleteCommand(program);
|
|
115
|
-
createUpdateCommand(program);
|
|
116
|
-
createBatchCommand(program);
|
|
117
|
-
createSearchCommand(program);
|
|
118
|
-
createWebhooksCommand(program);
|
|
119
|
-
createJobsCommand(program);
|
|
120
|
-
createExportCommand(program);
|
|
121
|
-
createUsersCommand(program);
|
|
122
|
-
createSyncCommand(program);
|
|
123
|
-
createPermissionsCommand(program);
|
|
124
|
-
createTemplateCommand(program);
|
|
125
|
-
createAccessCommand(program);
|
|
126
|
-
createAggregateCommand(program);
|
|
127
|
-
createDbCommand(program);
|
|
128
|
-
createSetupCommand(program);
|
|
129
|
-
createStatusCommand(program);
|
|
130
|
-
createSelfHostUpdateCommand(program);
|
|
131
|
-
createContextCommand(program);
|
|
132
|
-
createCompletionsCommand(program);
|
|
133
|
-
createAliasCommand(program);
|
|
134
|
-
createConfigCommand(program);
|
|
135
|
-
registerUserAliases(program);
|
|
136
|
-
createLoginCommand(program);
|
|
137
|
-
program.addHelpText("beforeAll", getLogo());
|
|
138
|
-
program.addHelpText("afterAll", chalk.dim("\n Run ay <command> --help for more info\n"));
|
|
139
|
-
program.configureHelp({
|
|
140
|
-
sortOptions: true,
|
|
141
|
-
sortSubcommands: true,
|
|
142
|
-
showGlobalOptions: true,
|
|
143
|
-
formatHelp: (cmd, helper) => {
|
|
144
|
-
const defaultHelp = Command.prototype.createHelp().formatHelp(cmd, helper);
|
|
145
|
-
return defaultHelp.replace(/^(Usage:|Commands:|Options:|Global Options:)/gm, (match) => chalk.hex(BRAND_BLUE).bold(match));
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
// Respect NO_COLOR env var (no-color.org standard)
|
|
149
|
-
if (process.env.NO_COLOR !== undefined) {
|
|
150
|
-
chalk.level = 0;
|
|
52
|
+
/**
|
|
53
|
+
* Find the first non-flag argument in argv (skipping `node` + script path).
|
|
54
|
+
* This is the command name candidate that we'll feed to the registry.
|
|
55
|
+
*
|
|
56
|
+
* Returns `undefined` if argv has no command (e.g. plain `ay`).
|
|
57
|
+
*/
|
|
58
|
+
function findFirstCommandArg(argv) {
|
|
59
|
+
for (let i = 2; i < argv.length; i++) {
|
|
60
|
+
const arg = argv[i];
|
|
61
|
+
if (!arg.startsWith("-"))
|
|
62
|
+
return arg;
|
|
151
63
|
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Expand a user alias in argv. If `argv[2]` is a key in `~/.config/ayoune/aliases.json`,
|
|
68
|
+
* replace it with the alias value (split on whitespace) and return the new argv.
|
|
69
|
+
* Otherwise return argv unchanged.
|
|
70
|
+
*
|
|
71
|
+
* Example: alias `ll` → `list leads`, invocation `ay ll --limit 5` becomes
|
|
72
|
+
* `ay list leads --limit 5`.
|
|
73
|
+
*/
|
|
74
|
+
function expandUserAlias(argv) {
|
|
75
|
+
const cmdArg = findFirstCommandArg(argv);
|
|
76
|
+
if (!cmdArg)
|
|
77
|
+
return argv;
|
|
78
|
+
// Cheap if no alias file exists; loadAliases() returns {} on miss.
|
|
79
|
+
const aliases = loadAliases();
|
|
80
|
+
const expansion = aliases[cmdArg];
|
|
81
|
+
if (!expansion)
|
|
82
|
+
return argv;
|
|
83
|
+
// Find where the alias sits in argv so we can splice in its expansion
|
|
84
|
+
// without disturbing leading global flags.
|
|
85
|
+
const idx = argv.indexOf(cmdArg, 2);
|
|
86
|
+
if (idx < 0)
|
|
87
|
+
return argv;
|
|
88
|
+
const expanded = expansion.split(/\s+/).filter(Boolean);
|
|
89
|
+
return [...argv.slice(0, idx), ...expanded, ...argv.slice(idx + 1)];
|
|
90
|
+
}
|
|
91
|
+
function classifyInvocation(argv) {
|
|
92
|
+
const args = argv.slice(2);
|
|
93
|
+
// --version trumps everything; commander handles it before any subcommand.
|
|
94
|
+
if (args.includes("--version") || args.includes("-V"))
|
|
95
|
+
return "version";
|
|
96
|
+
// Bare `ay` → root help
|
|
97
|
+
if (args.length === 0)
|
|
98
|
+
return "rootHelp";
|
|
99
|
+
// `ay help [subcmd?]`
|
|
100
|
+
if (args[0] === "help") {
|
|
101
|
+
return args.length >= 2 && !args[1].startsWith("-") ? "cmdHelp" : "rootHelp";
|
|
102
|
+
}
|
|
103
|
+
// `ay --help` / `ay -h` with no command before the flag → root help
|
|
104
|
+
const helpIdx = args.findIndex((a) => a === "--help" || a === "-h");
|
|
105
|
+
if (helpIdx === 0)
|
|
106
|
+
return "rootHelp";
|
|
107
|
+
// `ay <cmd> --help` / `ay <cmd> -h` → cmd-specific help
|
|
108
|
+
if (helpIdx > 0)
|
|
109
|
+
return "cmdHelp";
|
|
110
|
+
return "command";
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Apply global option side-effects (debug, dry-run, quiet, etc.) and
|
|
114
|
+
* config-file defaults. Runs in commander's preAction hook so it sees the
|
|
115
|
+
* fully-parsed global flags before any subcommand action fires.
|
|
116
|
+
*/
|
|
117
|
+
function installPreActionHook(program) {
|
|
152
118
|
program.hook("preAction", async (thisCommand) => {
|
|
153
119
|
var _a;
|
|
154
120
|
// Apply saved defaults — only when the user didn't pass the flag explicitly
|
|
155
121
|
const config = loadConfig();
|
|
156
122
|
const defaults = (_a = config.defaults) !== null && _a !== void 0 ? _a : {};
|
|
157
123
|
const rawArgs = process.argv.slice(2);
|
|
158
|
-
if (defaults.responseFormat && !rawArgs.some(a => ["-r", "--responseFormat"].includes(a)))
|
|
124
|
+
if (defaults.responseFormat && !rawArgs.some((a) => ["-r", "--responseFormat"].includes(a)))
|
|
159
125
|
program.setOptionValue("responseFormat", defaults.responseFormat);
|
|
160
|
-
if (defaults.verbosity && !rawArgs.some(a => ["-v", "--verbosity"].includes(a)))
|
|
126
|
+
if (defaults.verbosity && !rawArgs.some((a) => ["-v", "--verbosity"].includes(a)))
|
|
161
127
|
program.setOptionValue("verbosity", defaults.verbosity);
|
|
162
|
-
if (defaults.outPath && !rawArgs.some(a => ["-o", "--outPath"].includes(a)))
|
|
128
|
+
if (defaults.outPath && !rawArgs.some((a) => ["-o", "--outPath"].includes(a)))
|
|
163
129
|
program.setOptionValue("outPath", defaults.outPath);
|
|
164
|
-
if (defaults.hideMeta && !rawArgs.some(a => ["-m", "--hideMeta"].includes(a)))
|
|
130
|
+
if (defaults.hideMeta && !rawArgs.some((a) => ["-m", "--hideMeta"].includes(a)))
|
|
165
131
|
program.setOptionValue("hideMeta", defaults.hideMeta);
|
|
166
|
-
if (defaults.quiet && !rawArgs.some(a => ["-q", "--quiet"].includes(a)))
|
|
132
|
+
if (defaults.quiet && !rawArgs.some((a) => ["-q", "--quiet"].includes(a)))
|
|
167
133
|
program.setOptionValue("quiet", defaults.quiet);
|
|
168
|
-
if (defaults.force && !rawArgs.some(a => ["--force"].includes(a)))
|
|
134
|
+
if (defaults.force && !rawArgs.some((a) => ["--force"].includes(a)))
|
|
169
135
|
program.setOptionValue("force", defaults.force);
|
|
170
|
-
if (defaults.dryRun && !rawArgs.some(a => ["--dry-run"].includes(a)))
|
|
136
|
+
if (defaults.dryRun && !rawArgs.some((a) => ["--dry-run"].includes(a)))
|
|
171
137
|
program.setOptionValue("dryRun", defaults.dryRun);
|
|
172
138
|
const opts = program.opts();
|
|
173
139
|
if (opts.color === false) {
|
|
140
|
+
const { default: chalk } = await import("chalk");
|
|
174
141
|
chalk.level = 0;
|
|
175
142
|
}
|
|
176
143
|
if (opts.debug) {
|
|
144
|
+
const { enableDebug } = await import("../api/apiClient.js");
|
|
177
145
|
enableDebug();
|
|
178
146
|
}
|
|
179
147
|
if (opts.dryRun) {
|
|
148
|
+
const { enableDryRun } = await import("../api/apiCallHandler.js");
|
|
180
149
|
enableDryRun();
|
|
181
150
|
}
|
|
182
151
|
if (opts.jsonErrors) {
|
|
@@ -192,30 +161,151 @@ export function createProgram(program) {
|
|
|
192
161
|
spinner.stop = () => spinner;
|
|
193
162
|
// spinner.error left intact
|
|
194
163
|
}
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
else {
|
|
200
|
-
const envToken = process.env.AYOUNE_TOKEN;
|
|
201
|
-
if (envToken && !secureStorage.getItem("token")) {
|
|
202
|
-
secureStorage.setItem("token", envToken);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// First-run onboarding: auto-login if no token stored
|
|
164
|
+
// secureStorage is heavy (node-localstorage + crypto), so only load it
|
|
165
|
+
// when the user actually has a token to set or we need to check for one.
|
|
166
|
+
const hasTokenFlag = !!opts.token;
|
|
167
|
+
const hasTokenEnv = !!process.env.AYOUNE_TOKEN;
|
|
206
168
|
const cmdName = thisCommand.name();
|
|
207
|
-
const skipAuth = [
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
169
|
+
const skipAuth = [
|
|
170
|
+
"login",
|
|
171
|
+
"logout",
|
|
172
|
+
"whoami",
|
|
173
|
+
"completions",
|
|
174
|
+
"alias",
|
|
175
|
+
"config",
|
|
176
|
+
"help",
|
|
177
|
+
"setup",
|
|
178
|
+
"status",
|
|
179
|
+
"self-host-update",
|
|
180
|
+
"context",
|
|
181
|
+
"db",
|
|
182
|
+
];
|
|
183
|
+
const needsAuthCheck = !skipAuth.includes(cmdName) && process.stdin.isTTY;
|
|
184
|
+
if (hasTokenFlag || hasTokenEnv || needsAuthCheck) {
|
|
185
|
+
const { secureStorage } = await import("../helpers/secureStorage.js");
|
|
186
|
+
// Token from --token flag (highest priority), then env var, then stored token
|
|
187
|
+
if (hasTokenFlag) {
|
|
188
|
+
secureStorage.setItem("token", opts.token);
|
|
189
|
+
}
|
|
190
|
+
else if (hasTokenEnv && !secureStorage.getItem("token")) {
|
|
191
|
+
secureStorage.setItem("token", process.env.AYOUNE_TOKEN);
|
|
192
|
+
}
|
|
193
|
+
// First-run onboarding: auto-login if no token stored
|
|
194
|
+
if (needsAuthCheck) {
|
|
195
|
+
const token = secureStorage.getItem("token");
|
|
196
|
+
if (!token) {
|
|
197
|
+
const { default: chalk } = await import("chalk");
|
|
198
|
+
const { login } = await import("../api/login.js");
|
|
199
|
+
console.error(chalk.hex("#2B8DC6").bold("\n Welcome to aYOUne CLI!\n"));
|
|
200
|
+
console.error(chalk.dim(" You need to authenticate before using this command.\n"));
|
|
201
|
+
await login();
|
|
202
|
+
}
|
|
214
203
|
}
|
|
215
204
|
}
|
|
216
205
|
// Non-blocking update check (throttled, silent on errors)
|
|
206
|
+
const { checkForUpdates } = await import("../helpers/updateNotifier.js");
|
|
217
207
|
checkForUpdates(pkg.version || "0.0.0");
|
|
218
208
|
});
|
|
219
|
-
|
|
220
|
-
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Wire global options that exist on every command (responseFormat, verbosity,
|
|
212
|
+
* etc.). These must be added to the root program BEFORE subcommand
|
|
213
|
+
* registration so they're inherited.
|
|
214
|
+
*/
|
|
215
|
+
function installGlobalOptions(program) {
|
|
216
|
+
program
|
|
217
|
+
.version(pkg.version || "0.0.0")
|
|
218
|
+
.addOption(new Option("-r, --responseFormat <format>", "Set the output format")
|
|
219
|
+
.choices(["json", "csv", "yaml", "table"])
|
|
220
|
+
.default("json"))
|
|
221
|
+
.addOption(new Option("-v, --verbosity <level>", "Set the verbosity level of the returned meta information")
|
|
222
|
+
.choices(["default", "extended", "minimal"])
|
|
223
|
+
.default("default")
|
|
224
|
+
.conflicts("hideMeta"))
|
|
225
|
+
.addOption(new Option("-m, --hideMeta", "Returns only the payload without meta information. ")
|
|
226
|
+
.default(false)
|
|
227
|
+
.conflicts("verbosity"))
|
|
228
|
+
.addOption(new Option("-s, --save", "Saves the response as file"))
|
|
229
|
+
.addOption(new Option("-d, --debug", "Show detailed request/response information"))
|
|
230
|
+
.addOption(new Option("-o, --outPath [filePath]", "Lets you set a path").default(path.join(os.homedir(), "aYOUne")))
|
|
231
|
+
.addOption(new Option("-n, --name [fileName]", "Lets you set a filename"))
|
|
232
|
+
.addOption(new Option("-q, --quiet", "Suppress all output except errors and results"))
|
|
233
|
+
.addOption(new Option("--force", "Skip confirmation prompts for destructive actions"))
|
|
234
|
+
.addOption(new Option("--dry-run", "Preview what a command would do without executing"))
|
|
235
|
+
.addOption(new Option("--jq <expression>", "Filter JSON output using JMESPath expression"))
|
|
236
|
+
.addOption(new Option("--columns <fields>", "Comma-separated list of columns to display"))
|
|
237
|
+
.addOption(new Option("--no-color", "Disable colored output"))
|
|
238
|
+
.addOption(new Option("--json-errors", "Output errors as JSON to stderr (for AI agents/scripts)"))
|
|
239
|
+
.addOption(new Option("--token <jwt>", "Authenticate with a JWT token for this session"))
|
|
240
|
+
.addOption(new Option("--no-context", "Disable context injection for this command"));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Configure help output: the brand logo, custom formatter that highlights
|
|
244
|
+
* section headings, and respect for NO_COLOR. The thunks (`() => getLogo()`)
|
|
245
|
+
* are critical — they defer the figlet/chalk import until commander actually
|
|
246
|
+
* needs to render help, keeping the cold-start path tiny.
|
|
247
|
+
*/
|
|
248
|
+
function installHelp(program) {
|
|
249
|
+
program.showHelpAfterError();
|
|
250
|
+
program.showSuggestionAfterError(true);
|
|
251
|
+
// Help-text thunks: commander only invokes these when --help is rendered,
|
|
252
|
+
// so the figlet/chalk loads inside getLogoSync()/dim() are paid once per
|
|
253
|
+
// help invocation, never on the hot path.
|
|
254
|
+
program.addHelpText("beforeAll", () => getLogoSync());
|
|
255
|
+
program.addHelpText("afterAll", () => dim("\n Run ay <command> --help for more info\n"));
|
|
256
|
+
program.configureHelp({
|
|
257
|
+
sortOptions: true,
|
|
258
|
+
sortSubcommands: true,
|
|
259
|
+
showGlobalOptions: true,
|
|
260
|
+
formatHelp: (cmd, helper) => {
|
|
261
|
+
const defaultHelp = Command.prototype.createHelp().formatHelp(cmd, helper);
|
|
262
|
+
return defaultHelp.replace(/^(Usage:|Commands:|Options:|Global Options:)/gm, (match) => brandHighlight(match));
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
// Respect NO_COLOR env var (no-color.org standard) — applied here so it
|
|
266
|
+
// affects help output even before preAction fires.
|
|
267
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
268
|
+
// chalk is needed for the color level toggle; cheap to import.
|
|
269
|
+
// We do it lazily so it doesn't pollute non-color paths.
|
|
270
|
+
import("chalk").then(({ default: chalk }) => {
|
|
271
|
+
chalk.level = 0;
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
program.description("aYOUne — Business as a Service CLI");
|
|
275
|
+
}
|
|
276
|
+
export async function createProgram(program) {
|
|
277
|
+
installGlobalOptions(program);
|
|
278
|
+
installHelp(program);
|
|
279
|
+
installPreActionHook(program);
|
|
280
|
+
// Step 1: expand any user-defined alias in argv (e.g. `ay ll` → `ay list leads`).
|
|
281
|
+
// The expansion happens BEFORE registry lookup so the resolved name is what
|
|
282
|
+
// gets matched.
|
|
283
|
+
process.argv = expandUserAlias(process.argv);
|
|
284
|
+
// Step 2: classify the invocation and load only what's needed.
|
|
285
|
+
const kind = classifyInvocation(process.argv);
|
|
286
|
+
const firstCmd = findFirstCommandArg(process.argv);
|
|
287
|
+
const spec = firstCmd ? findCommandSpec(firstCmd) : undefined;
|
|
288
|
+
if (kind === "version") {
|
|
289
|
+
// Nothing to register — commander handles -V before any subcommand action.
|
|
290
|
+
}
|
|
291
|
+
else if (kind === "rootHelp") {
|
|
292
|
+
// Lightweight stubs for every command so the help listing is complete and
|
|
293
|
+
// commander can suggest similar names for typos.
|
|
294
|
+
registerStubsForHelp(program);
|
|
295
|
+
}
|
|
296
|
+
else if (kind === "cmdHelp" || kind === "command") {
|
|
297
|
+
if (spec) {
|
|
298
|
+
// Hot path: load and register the ONE full command the user is running
|
|
299
|
+
// (or asking help for). Its options/examples will be available to
|
|
300
|
+
// commander's help formatter.
|
|
301
|
+
const register = await spec.loader();
|
|
302
|
+
register(program);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// Unknown command — register all stubs so commander's "did you mean"
|
|
306
|
+
// can suggest similar names. parseAsync will exit non-zero.
|
|
307
|
+
registerStubsForHelp(program);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
await program.parseAsync(process.argv);
|
|
221
311
|
}
|
|
@@ -127,16 +127,15 @@ Examples:
|
|
|
127
127
|
// Deduplicate by host
|
|
128
128
|
const uniqueTargets = [...new Map(targets.map((t) => [t.host, t])).values()];
|
|
129
129
|
spinner.start({ text: `Checking ${uniqueTargets.length} service(s)...`, color: "magenta" });
|
|
130
|
-
const {
|
|
131
|
-
const https = await import("https");
|
|
132
|
-
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
130
|
+
const { http } = await import("@tolinax/ayoune-core/lib/http");
|
|
133
131
|
const results = await Promise.allSettled(uniqueTargets.map(async (t) => {
|
|
134
132
|
const start = Date.now();
|
|
135
133
|
try {
|
|
136
|
-
const resp = await
|
|
134
|
+
const resp = await http.get(`https://${t.host}/`, {
|
|
137
135
|
timeout: opts.timeout,
|
|
138
|
-
httpsAgent: agent,
|
|
139
136
|
validateStatus: () => true,
|
|
137
|
+
logging: false,
|
|
138
|
+
metrics: false,
|
|
140
139
|
});
|
|
141
140
|
return {
|
|
142
141
|
host: t.host,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
2
|
+
import { formatIsoLocal, fromNow, fromUnix, isBefore } from "../helpers/dateFormat.js";
|
|
3
3
|
import { secureStorage } from "../helpers/secureStorage.js";
|
|
4
4
|
import { decodeToken } from "../api/decodeToken.js";
|
|
5
5
|
import { login } from "../api/login.js";
|
|
@@ -22,10 +22,10 @@ const LABEL_MAP = {
|
|
|
22
22
|
const SKIP_FIELDS = new Set(["password", "hash", "salt", "__v"]);
|
|
23
23
|
function formatValue(key, value) {
|
|
24
24
|
if ((key === "iat" || key === "exp") && typeof value === "number") {
|
|
25
|
-
const
|
|
26
|
-
const relative =
|
|
27
|
-
const formatted =
|
|
28
|
-
if (key === "exp" &&
|
|
25
|
+
const date = fromUnix(value);
|
|
26
|
+
const relative = fromNow(date);
|
|
27
|
+
const formatted = formatIsoLocal(date);
|
|
28
|
+
if (key === "exp" && isBefore(date, new Date())) {
|
|
29
29
|
return chalk.red(`${formatted} (expired ${relative})`);
|
|
30
30
|
}
|
|
31
31
|
return `${formatted} ${chalk.dim(`(${relative})`)}`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Shared helper used by deploy subcommands that bypass apiCallHandler and
|
|
2
|
+
// hit the DevOps API directly (e.g. the SSE log stream in
|
|
3
|
+
// `deployments/logs --follow`). secureStorage is the canonical token source;
|
|
4
|
+
// AYOUNE_TOKEN env var is the script-friendly fallback.
|
|
5
|
+
import { secureStorage } from "../../helpers/secureStorage.js";
|
|
6
|
+
export function getDeployToken() {
|
|
7
|
+
return secureStorage.getItem("token") || process.env.AYOUNE_TOKEN || "";
|
|
8
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// `ay deploy alerts` — surface active deployment alerts (pod crashes,
|
|
2
|
+
// OOM kills, image pull errors, etc.). Read-only filter view; ack/resolve
|
|
3
|
+
// lives under `ay monitor` so we don't duplicate state mutation here.
|
|
4
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
5
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
6
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
7
|
+
import { spinner } from "../../../index.js";
|
|
8
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
9
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
10
|
+
export function addAlertsSubcommands(deploy, rootProgram) {
|
|
11
|
+
deploy
|
|
12
|
+
.command("alerts")
|
|
13
|
+
.description("List active deployment alerts")
|
|
14
|
+
.option("--severity <level>", "Filter: critical, warning, info")
|
|
15
|
+
.option("--type <type>", "Filter: pod_crash, oom_killed, image_pull_error, deployment_failed, pipeline_failed, cluster_unreachable, high_restart_count, custom")
|
|
16
|
+
.option("-l, --limit <number>", "Limit", parseInt, 50)
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
var _a, _b, _c;
|
|
19
|
+
try {
|
|
20
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
21
|
+
spinner.start({ text: "Fetching alerts...", color: "magenta" });
|
|
22
|
+
const params = {
|
|
23
|
+
limit: opts.limit,
|
|
24
|
+
responseFormat: opts.responseFormat,
|
|
25
|
+
verbosity: opts.verbosity,
|
|
26
|
+
};
|
|
27
|
+
if (opts.severity)
|
|
28
|
+
params.severity = opts.severity;
|
|
29
|
+
if (opts.type)
|
|
30
|
+
params.type = opts.type;
|
|
31
|
+
const res = await apiCallHandler("devops", "alerts", "get", null, params);
|
|
32
|
+
handleResponseFormatOptions(opts, res);
|
|
33
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
34
|
+
spinner.success({ text: `Found ${total} alerts` });
|
|
35
|
+
spinner.stop();
|
|
36
|
+
if (opts.save)
|
|
37
|
+
await saveFile("deploy-alerts", opts, res);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
cliError(e.message || "Failed to fetch alerts", EXIT_GENERAL_ERROR);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// `ay deploy {clusters, cluster-sync}` — Kubernetes cluster discovery and
|
|
2
|
+
// manual reconcile trigger. The DevOps API maintains a registry of clusters
|
|
3
|
+
// the customer has connected; `clusters` lists them and `cluster-sync` kicks
|
|
4
|
+
// off a re-scan of resources for one cluster.
|
|
5
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
6
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
7
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
8
|
+
import { spinner } from "../../../index.js";
|
|
9
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
10
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
11
|
+
export function addClustersSubcommands(deploy, rootProgram) {
|
|
12
|
+
// ay deploy clusters
|
|
13
|
+
deploy
|
|
14
|
+
.command("clusters")
|
|
15
|
+
.alias("cl")
|
|
16
|
+
.description("List Kubernetes clusters and their health status")
|
|
17
|
+
.addHelpText("after", `
|
|
18
|
+
Examples:
|
|
19
|
+
ay deploy clusters List all clusters
|
|
20
|
+
ay deploy clusters -r table Show clusters in table format`)
|
|
21
|
+
.option("-l, --limit <number>", "Limit", parseInt, 50)
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
var _a, _b, _c;
|
|
24
|
+
try {
|
|
25
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
26
|
+
spinner.start({ text: "Fetching clusters...", color: "magenta" });
|
|
27
|
+
const res = await apiCallHandler("devops", "clusters", "get", null, {
|
|
28
|
+
limit: opts.limit,
|
|
29
|
+
responseFormat: opts.responseFormat,
|
|
30
|
+
verbosity: opts.verbosity,
|
|
31
|
+
});
|
|
32
|
+
handleResponseFormatOptions(opts, res);
|
|
33
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
34
|
+
spinner.success({ text: `Found ${total} clusters` });
|
|
35
|
+
spinner.stop();
|
|
36
|
+
if (opts.save)
|
|
37
|
+
await saveFile("deploy-clusters", opts, res);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
cliError(e.message || "Failed to list clusters", EXIT_GENERAL_ERROR);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// ay deploy cluster-sync <id>
|
|
44
|
+
deploy
|
|
45
|
+
.command("cluster-sync <id>")
|
|
46
|
+
.description("Trigger sync for a Kubernetes cluster")
|
|
47
|
+
.action(async (id, options) => {
|
|
48
|
+
try {
|
|
49
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
50
|
+
spinner.start({ text: `Syncing cluster ${id}...`, color: "magenta" });
|
|
51
|
+
const res = await apiCallHandler("devops", `clusters/${id}/sync`, "post", null, {
|
|
52
|
+
responseFormat: opts.responseFormat,
|
|
53
|
+
});
|
|
54
|
+
handleResponseFormatOptions(opts, res);
|
|
55
|
+
spinner.success({ text: `Cluster ${id} sync initiated` });
|
|
56
|
+
spinner.stop();
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
cliError(e.message || "Failed to sync cluster", EXIT_GENERAL_ERROR);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// `ay deploy dashboard` — single-call overview that returns the same
|
|
2
|
+
// summary the web devops dashboard renders (cluster health, deployment
|
|
3
|
+
// counts, active alerts, recent pipelines). Useful as a one-shot health
|
|
4
|
+
// check during incident response.
|
|
5
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
6
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
7
|
+
import { spinner } from "../../../index.js";
|
|
8
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
9
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
10
|
+
export function addDashboardSubcommands(deploy, rootProgram) {
|
|
11
|
+
deploy
|
|
12
|
+
.command("dashboard")
|
|
13
|
+
.alias("dash")
|
|
14
|
+
.description("Show deployment overview dashboard (clusters, deployments, alerts, pipelines)")
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
try {
|
|
17
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
18
|
+
spinner.start({ text: "Fetching dashboard...", color: "magenta" });
|
|
19
|
+
const res = await apiCallHandler("devops", "dashboard", "get", null, {
|
|
20
|
+
responseFormat: opts.responseFormat,
|
|
21
|
+
verbosity: opts.verbosity,
|
|
22
|
+
});
|
|
23
|
+
handleResponseFormatOptions(opts, res);
|
|
24
|
+
spinner.success({ text: "Dashboard loaded" });
|
|
25
|
+
spinner.stop();
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
cliError(e.message || "Failed to load dashboard", EXIT_GENERAL_ERROR);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|