@tolinax/ayoune-cli 2026.10.1 → 2026.11.1
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/login.js +8 -4
- package/lib/commands/_registry.js +5 -0
- package/lib/commands/aggregate/list.js +3 -2
- package/lib/commands/createActionsCommand.js +11 -10
- package/lib/commands/createAiCommand.js +10 -9
- package/lib/commands/createDbCommand.js +2 -1
- package/lib/commands/createDoctorCommand.js +305 -0
- package/lib/commands/createExportCommand.js +12 -11
- package/lib/commands/createJobsCommand.js +19 -9
- package/lib/commands/createLoginCommand.js +5 -0
- package/lib/commands/createLogoutCommand.js +4 -0
- package/lib/commands/createMonitorCommand.js +20 -19
- package/lib/commands/createPermissionsCommand.js +12 -11
- package/lib/commands/createSearchCommand.js +13 -12
- package/lib/commands/createServicesCommand.js +5 -4
- package/lib/commands/createSetupCommand.js +11 -13
- package/lib/commands/createStorageCommand.js +4 -0
- package/lib/commands/createSyncCommand.js +6 -5
- package/lib/commands/createTemplateCommand.js +16 -8
- package/lib/commands/createUsersCommand.js +16 -6
- package/lib/commands/createWebhooksCommand.js +13 -4
- package/lib/commands/createWhoAmICommand.js +4 -0
- package/lib/commands/deploy/alerts.js +2 -1
- package/lib/commands/deploy/clusters.js +2 -1
- package/lib/commands/deploy/deployments.js +4 -3
- package/lib/commands/deploy/index.js +13 -1
- package/lib/commands/deploy/pipelines.js +2 -1
- package/lib/commands/deploy/plans.js +3 -2
- package/lib/commands/deploy/pods.js +2 -1
- package/lib/commands/deploy/repos.js +3 -2
- package/lib/commands/functions/list.js +3 -2
- package/lib/commands/functions/logs.js +2 -1
- package/lib/commands/functions/versions.js +2 -1
- package/lib/commands/provision/hetzner.js +2 -1
- package/lib/helpers/parseInt.js +1 -1
- package/lib/helpers/printNextSteps.js +34 -0
- package/package.json +1 -1
package/lib/api/login.js
CHANGED
|
@@ -13,10 +13,14 @@ export const login = async () => {
|
|
|
13
13
|
const token = randomAsciiString();
|
|
14
14
|
const url = `${config.loginUrl}/ayoune?cli=${token}`;
|
|
15
15
|
const clickable = terminalLink(chalk.cyan.underline(url), url);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
// Print the auth URL to STDERR — the same stream the spinner uses — so we
|
|
17
|
+
// don't pollute stdout when a piped command triggers a re-login (e.g. 401
|
|
18
|
+
// refresh inside apiCallHandler). stdout is reserved for the data payload
|
|
19
|
+
// so `ay list contacts | jq` keeps working.
|
|
20
|
+
process.stderr.write("\n");
|
|
21
|
+
process.stderr.write(` ${chalk.dim("Open this URL to authenticate:")}\n`);
|
|
22
|
+
process.stderr.write(` ${clickable}\n`);
|
|
23
|
+
process.stderr.write("\n");
|
|
20
24
|
const credentials = await waitForCredentials(token);
|
|
21
25
|
return credentials;
|
|
22
26
|
};
|
|
@@ -240,6 +240,11 @@ export const COMMAND_REGISTRY = [
|
|
|
240
240
|
description: "Provision aYOUne to a cloud provider (AWS / GCP / Azure / DigitalOcean / Hetzner)",
|
|
241
241
|
loader: async () => (await import("./provision/index.js")).createProvisionCommand,
|
|
242
242
|
},
|
|
243
|
+
{
|
|
244
|
+
name: "doctor",
|
|
245
|
+
description: "Run a health check on your local aYOUne environment",
|
|
246
|
+
loader: async () => (await import("./createDoctorCommand.js")).createDoctorCommand,
|
|
247
|
+
},
|
|
243
248
|
{
|
|
244
249
|
name: "context",
|
|
245
250
|
aliases: ["ctx"],
|
|
@@ -9,13 +9,14 @@ import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatO
|
|
|
9
9
|
import { spinner } from "../../../index.js";
|
|
10
10
|
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
11
11
|
import { cliError } from "../../helpers/cliError.js";
|
|
12
|
+
import { parseInteger } from "../../helpers/parseInt.js";
|
|
12
13
|
export function addListSubcommand(agg, rootProgram) {
|
|
13
14
|
agg
|
|
14
15
|
.command("list")
|
|
15
16
|
.alias("ls")
|
|
16
17
|
.description("List saved aggregation queries")
|
|
17
|
-
.option("-l, --limit <number>", "Limit results",
|
|
18
|
-
.option("-p, --page <number>", "Page number",
|
|
18
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 50)
|
|
19
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
19
20
|
.option("--search <term>", "Search by name")
|
|
20
21
|
.action(async (options) => {
|
|
21
22
|
var _a, _b, _c, _d, _e;
|
|
@@ -5,19 +5,20 @@ import { secureStorage } from "../helpers/secureStorage.js";
|
|
|
5
5
|
import { spinner } from "../../index.js";
|
|
6
6
|
import { EXIT_GENERAL_ERROR, EXIT_PERMISSION_DENIED } from "../exitCodes.js";
|
|
7
7
|
import { cliError } from "../helpers/cliError.js";
|
|
8
|
+
import { parseInteger } from "../helpers/parseInt.js";
|
|
8
9
|
export function createActionsCommand(program) {
|
|
9
10
|
program
|
|
10
11
|
.command("actions [search]")
|
|
11
12
|
.alias("act")
|
|
12
13
|
.description("Discover registered API actions from all services")
|
|
13
|
-
.addHelpText("after", `
|
|
14
|
-
Examples:
|
|
15
|
-
ay actions List all API actions (interactive)
|
|
16
|
-
ay actions generate Search actions matching "generate"
|
|
17
|
-
ay actions --namespace ai List all AI actions
|
|
18
|
-
ay actions --host ai.ayoune.app List actions for a specific host
|
|
19
|
-
ay actions --method POST List only POST actions
|
|
20
|
-
ay actions --list-namespaces List all available namespaces
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Examples:
|
|
16
|
+
ay actions List all API actions (interactive)
|
|
17
|
+
ay actions generate Search actions matching "generate"
|
|
18
|
+
ay actions --namespace ai List all AI actions
|
|
19
|
+
ay actions --host ai.ayoune.app List actions for a specific host
|
|
20
|
+
ay actions --method POST List only POST actions
|
|
21
|
+
ay actions --list-namespaces List all available namespaces
|
|
21
22
|
ay actions -r table --columns "operationId,method,endpoint,host"`)
|
|
22
23
|
.option("--namespace <ns>", "Filter by namespace (e.g. ai, crm, pm)")
|
|
23
24
|
.option("--host <host>", "Filter by host")
|
|
@@ -25,8 +26,8 @@ Examples:
|
|
|
25
26
|
.option("--capability <cap>", "Filter by capability")
|
|
26
27
|
.option("--list-namespaces", "List all unique namespaces")
|
|
27
28
|
.option("--list-hosts", "List all unique hosts")
|
|
28
|
-
.option("-p, --page <number>", "Page",
|
|
29
|
-
.option("-l, --limit <number>", "Limit",
|
|
29
|
+
.option("-p, --page <number>", "Page", parseInteger, 1)
|
|
30
|
+
.option("-l, --limit <number>", "Limit", parseInteger, 100)
|
|
30
31
|
.action(async (search, options) => {
|
|
31
32
|
var _a, _b, _c;
|
|
32
33
|
try {
|
|
@@ -7,6 +7,7 @@ import { spinner } from "../../index.js";
|
|
|
7
7
|
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
8
8
|
import { cliError } from "../helpers/cliError.js";
|
|
9
9
|
import { getContextSummary, getContextForAI, hasActiveContext } from "../helpers/contextInjector.js";
|
|
10
|
+
import { parseInteger } from "../helpers/parseInt.js";
|
|
10
11
|
const AI_HOST = "ai.ayoune.app";
|
|
11
12
|
export function createAiCommand(program) {
|
|
12
13
|
const ai = program
|
|
@@ -51,8 +52,8 @@ export function createAiCommand(program) {
|
|
|
51
52
|
// ay ai conversations — list AI conversations
|
|
52
53
|
ai.command("conversations")
|
|
53
54
|
.description("List AI conversations")
|
|
54
|
-
.option("-l, --limit <number>", "Limit",
|
|
55
|
-
.option("-p, --page <number>", "Page",
|
|
55
|
+
.option("-l, --limit <number>", "Limit", parseInteger, 20)
|
|
56
|
+
.option("-p, --page <number>", "Page", parseInteger, 1)
|
|
56
57
|
.action(async (options) => {
|
|
57
58
|
var _a, _b, _c;
|
|
58
59
|
try {
|
|
@@ -79,8 +80,8 @@ export function createAiCommand(program) {
|
|
|
79
80
|
// ay ai prompts — list AI prompt templates
|
|
80
81
|
ai.command("prompts")
|
|
81
82
|
.description("List AI prompt templates")
|
|
82
|
-
.option("-l, --limit <number>", "Limit",
|
|
83
|
-
.option("-p, --page <number>", "Page",
|
|
83
|
+
.option("-l, --limit <number>", "Limit", parseInteger, 20)
|
|
84
|
+
.option("-p, --page <number>", "Page", parseInteger, 1)
|
|
84
85
|
.action(async (options) => {
|
|
85
86
|
var _a, _b, _c;
|
|
86
87
|
try {
|
|
@@ -141,11 +142,11 @@ export function createAiCommand(program) {
|
|
|
141
142
|
// ay ai generate <type> <prompt> — generate content
|
|
142
143
|
ai.command("generate <type> <prompt...>")
|
|
143
144
|
.description("Generate content (text, image, video)")
|
|
144
|
-
.addHelpText("after", `
|
|
145
|
-
Types: text, image, video
|
|
146
|
-
|
|
147
|
-
Examples:
|
|
148
|
-
ay ai generate text "Write a product description for..."
|
|
145
|
+
.addHelpText("after", `
|
|
146
|
+
Types: text, image, video
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
ay ai generate text "Write a product description for..."
|
|
149
150
|
ay ai generate image "A modern office building"`)
|
|
150
151
|
.action(async (type, promptWords, options) => {
|
|
151
152
|
try {
|
|
@@ -6,6 +6,7 @@ import { cliError } from "../helpers/cliError.js";
|
|
|
6
6
|
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
7
7
|
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
8
8
|
import { initializeSettings } from "../helpers/initializeSettings.js";
|
|
9
|
+
import { parseInteger } from "../helpers/parseInt.js";
|
|
9
10
|
function maskUri(uri) {
|
|
10
11
|
return uri.replace(/:\/\/([^:]+):([^@]+)@/, "://***:***@");
|
|
11
12
|
}
|
|
@@ -97,7 +98,7 @@ Examples:
|
|
|
97
98
|
.command("list")
|
|
98
99
|
.alias("ls")
|
|
99
100
|
.description("List configured database targets")
|
|
100
|
-
.option("-l, --limit <number>", "Limit results",
|
|
101
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 50)
|
|
101
102
|
.action(async (options) => {
|
|
102
103
|
var _a, _b, _c, _d, _e;
|
|
103
104
|
try {
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// `ay doctor` — single-screen health check for the customer's local
|
|
2
|
+
// environment. Diagnoses everything that could prevent the rest of the CLI
|
|
3
|
+
// from working: Docker / Compose installed, the user is logged in, the
|
|
4
|
+
// stored JWT is still valid, the platform's auth endpoint is reachable, the
|
|
5
|
+
// CLI's config dir is writable, and (optionally) which cloud-provider tools
|
|
6
|
+
// are installed for `ay provision`.
|
|
7
|
+
//
|
|
8
|
+
// Output is a single screen with green / yellow / red indicators per check.
|
|
9
|
+
// Designed as the first thing a confused user runs ("nothing works") so each
|
|
10
|
+
// check has a one-line "fix it with X" hint when it fails.
|
|
11
|
+
//
|
|
12
|
+
// Heavy modules (jsonwebtoken, secureStorage, fetch) are loaded only inside
|
|
13
|
+
// the action handler so the help screen stays as fast as the lazy
|
|
14
|
+
// command-loading registry expects.
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync, unlinkSync } from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import os from "os";
|
|
19
|
+
import { detectRuntime, runCommand } from "../helpers/dockerCompose.js";
|
|
20
|
+
import { hasTool } from "./provision/_detectTools.js";
|
|
21
|
+
import { cliError } from "../helpers/cliError.js";
|
|
22
|
+
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
23
|
+
const SEVERITY_GLYPHS = {
|
|
24
|
+
ok: "✓",
|
|
25
|
+
warn: "!",
|
|
26
|
+
error: "✗",
|
|
27
|
+
info: "·",
|
|
28
|
+
};
|
|
29
|
+
const SEVERITY_COLORS = {
|
|
30
|
+
ok: chalk.green,
|
|
31
|
+
warn: chalk.yellow,
|
|
32
|
+
error: chalk.red,
|
|
33
|
+
info: chalk.dim,
|
|
34
|
+
};
|
|
35
|
+
function printCheck(r) {
|
|
36
|
+
const colour = SEVERITY_COLORS[r.severity];
|
|
37
|
+
const glyph = colour(SEVERITY_GLYPHS[r.severity]);
|
|
38
|
+
const label = r.label.padEnd(34);
|
|
39
|
+
const detail = r.detail ? chalk.dim(r.detail) : "";
|
|
40
|
+
console.log(` ${glyph} ${label} ${detail}`);
|
|
41
|
+
if (r.hint && r.severity !== "ok") {
|
|
42
|
+
console.log(` ${chalk.dim(r.hint)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function printSection(title) {
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log(chalk.cyan.bold(` ${title}`));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Verify the user's CLI config dir at ~/.config/ayoune/ is writable.
|
|
51
|
+
* Creates the dir if missing and round-trips a tiny temp file.
|
|
52
|
+
*/
|
|
53
|
+
function checkConfigWritable() {
|
|
54
|
+
const dir = path.join(os.homedir(), ".config", "ayoune");
|
|
55
|
+
try {
|
|
56
|
+
if (!existsSync(dir))
|
|
57
|
+
mkdirSync(dir, { recursive: true });
|
|
58
|
+
const testFile = path.join(dir, `.doctor-${Date.now()}.tmp`);
|
|
59
|
+
writeFileSync(testFile, "ok", "utf-8");
|
|
60
|
+
unlinkSync(testFile);
|
|
61
|
+
return { label: "Config dir writable", severity: "ok", detail: dir };
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return {
|
|
65
|
+
label: "Config dir writable",
|
|
66
|
+
severity: "error",
|
|
67
|
+
detail: dir,
|
|
68
|
+
hint: `Could not write to config dir: ${e.message}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Verify Docker is installed (probe `docker --version`). */
|
|
73
|
+
function checkDocker() {
|
|
74
|
+
const out = runCommand("docker --version 2>&1");
|
|
75
|
+
if (out.toLowerCase().includes("docker version")) {
|
|
76
|
+
return { label: "Docker", severity: "ok", detail: out };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
label: "Docker",
|
|
80
|
+
severity: "warn",
|
|
81
|
+
hint: "Install Docker Desktop: https://www.docker.com/products/docker-desktop/",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Verify Docker Compose is installed (uses the same probe as ay local). */
|
|
85
|
+
function checkDockerCompose() {
|
|
86
|
+
const runtime = detectRuntime();
|
|
87
|
+
if (runtime === "compose") {
|
|
88
|
+
return { label: "Docker Compose", severity: "ok", detail: "compose plugin available" };
|
|
89
|
+
}
|
|
90
|
+
if (runtime === "kubernetes") {
|
|
91
|
+
return {
|
|
92
|
+
label: "Docker Compose",
|
|
93
|
+
severity: "info",
|
|
94
|
+
detail: "not installed (kubectl detected instead)",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
label: "Docker Compose",
|
|
99
|
+
severity: "warn",
|
|
100
|
+
hint: "Required by `ay local` and `ay setup`. Install Docker Desktop or the compose plugin.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check whether the user is logged in (token present in secureStorage) and
|
|
105
|
+
* whether the JWT has not yet expired. Lazy-loads jsonwebtoken via the
|
|
106
|
+
* existing decodeToken helper to keep the doctor command's own import graph
|
|
107
|
+
* thin.
|
|
108
|
+
*/
|
|
109
|
+
async function checkAuth() {
|
|
110
|
+
var _a;
|
|
111
|
+
const { secureStorage } = await import("../helpers/secureStorage.js");
|
|
112
|
+
const { decodeToken } = await import("../api/decodeToken.js");
|
|
113
|
+
const token = secureStorage.getItem("token");
|
|
114
|
+
if (!token) {
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
label: "Logged in",
|
|
118
|
+
severity: "warn",
|
|
119
|
+
detail: "no token stored",
|
|
120
|
+
hint: "Run `ay login` to authenticate.",
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
let decoded;
|
|
125
|
+
try {
|
|
126
|
+
decoded = decodeToken(token);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
label: "Logged in",
|
|
132
|
+
severity: "error",
|
|
133
|
+
detail: "stored token is malformed",
|
|
134
|
+
hint: `Run \`ay logout && ay login\`. (${e.message})`,
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
const payload = (_a = decoded === null || decoded === void 0 ? void 0 : decoded.payload) !== null && _a !== void 0 ? _a : {};
|
|
139
|
+
const username = payload.username || payload.email || payload._id || "(unknown)";
|
|
140
|
+
const exp = typeof payload.exp === "number" ? payload.exp : null;
|
|
141
|
+
const now = Math.floor(Date.now() / 1000);
|
|
142
|
+
const results = [
|
|
143
|
+
{ label: "Logged in", severity: "ok", detail: String(username) },
|
|
144
|
+
];
|
|
145
|
+
if (exp === null) {
|
|
146
|
+
results.push({
|
|
147
|
+
label: "Token expiry",
|
|
148
|
+
severity: "info",
|
|
149
|
+
detail: "no exp claim",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (exp < now) {
|
|
153
|
+
results.push({
|
|
154
|
+
label: "Token expiry",
|
|
155
|
+
severity: "error",
|
|
156
|
+
detail: `expired ${formatDelta(now - exp)} ago`,
|
|
157
|
+
hint: "Run `ay login` to refresh your session.",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const remaining = exp - now;
|
|
162
|
+
const sev = remaining < 3600 ? "warn" : "ok";
|
|
163
|
+
results.push({
|
|
164
|
+
label: "Token expiry",
|
|
165
|
+
severity: sev,
|
|
166
|
+
detail: `expires in ${formatDelta(remaining)}`,
|
|
167
|
+
hint: sev === "warn" ? "Consider running `ay login` to refresh." : undefined,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (payload.type === "superuser") {
|
|
171
|
+
results.push({ label: "Account type", severity: "info", detail: "superuser" });
|
|
172
|
+
}
|
|
173
|
+
else if (payload.type) {
|
|
174
|
+
results.push({ label: "Account type", severity: "info", detail: String(payload.type) });
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
/** Format a duration in seconds as a human-readable "Xh Ym" / "Xm" / "Xs" string. */
|
|
179
|
+
function formatDelta(seconds) {
|
|
180
|
+
if (seconds < 60)
|
|
181
|
+
return `${seconds}s`;
|
|
182
|
+
const minutes = Math.floor(seconds / 60);
|
|
183
|
+
if (minutes < 60)
|
|
184
|
+
return `${minutes}m`;
|
|
185
|
+
const hours = Math.floor(minutes / 60);
|
|
186
|
+
const mins = minutes % 60;
|
|
187
|
+
if (hours < 24)
|
|
188
|
+
return mins ? `${hours}h ${mins}m` : `${hours}h`;
|
|
189
|
+
const days = Math.floor(hours / 24);
|
|
190
|
+
const hrs = hours % 24;
|
|
191
|
+
return hrs ? `${days}d ${hrs}h` : `${days}d`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Probe the platform's auth endpoint with native fetch + a short timeout.
|
|
195
|
+
* Anything 2xx/4xx counts as "reachable" — we only care that the network
|
|
196
|
+
* path works, not that the user is authorised.
|
|
197
|
+
*/
|
|
198
|
+
async function checkClusterReachable() {
|
|
199
|
+
const { config } = await import("../helpers/config.js");
|
|
200
|
+
const url = config.auditUrl.replace(/\/+$/, "") + "/healthz";
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
203
|
+
const start = Date.now();
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
const elapsed = Date.now() - start;
|
|
208
|
+
if (res.status < 500) {
|
|
209
|
+
return {
|
|
210
|
+
label: "Platform reachable",
|
|
211
|
+
severity: "ok",
|
|
212
|
+
detail: `${url} → ${res.status} in ${elapsed}ms`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
label: "Platform reachable",
|
|
217
|
+
severity: "warn",
|
|
218
|
+
detail: `${url} → ${res.status} in ${elapsed}ms`,
|
|
219
|
+
hint: "Endpoint responded with a server error.",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
return {
|
|
225
|
+
label: "Platform reachable",
|
|
226
|
+
severity: "error",
|
|
227
|
+
detail: e.name === "AbortError" ? "timeout after 5s" : e.message,
|
|
228
|
+
hint: `Could not reach ${url}. Check your network / firewall.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** List which provision provider tools are present (informational, never errors). */
|
|
233
|
+
function checkProviderTools() {
|
|
234
|
+
const tools = ["terraform", "aws", "gcloud", "az", "doctl"];
|
|
235
|
+
return tools.map((t) => ({
|
|
236
|
+
label: t,
|
|
237
|
+
severity: hasTool(t) ? "ok" : "info",
|
|
238
|
+
detail: hasTool(t) ? "installed" : "not installed",
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
export function createDoctorCommand(program) {
|
|
242
|
+
program
|
|
243
|
+
.command("doctor")
|
|
244
|
+
.description("Run a health check on your local aYOUne environment")
|
|
245
|
+
.addHelpText("after", `
|
|
246
|
+
Examples:
|
|
247
|
+
ay doctor Run all checks
|
|
248
|
+
ay doctor --providers Also list which cloud-provider tools are installed
|
|
249
|
+
ay doctor --quiet Hide info-level checks (only show non-OK)`)
|
|
250
|
+
.option("--providers", "Also list cloud-provider CLI tools (terraform, aws, gcloud, az, doctl)", false)
|
|
251
|
+
.option("--quiet", "Hide info-level checks; only show warnings + errors", false)
|
|
252
|
+
.action(async (options) => {
|
|
253
|
+
try {
|
|
254
|
+
console.log(chalk.cyan.bold("\n aYOUne Doctor"));
|
|
255
|
+
console.log(chalk.dim(" Diagnosing your local CLI environment..."));
|
|
256
|
+
const filter = (rs) => options.quiet ? rs.filter((r) => r.severity !== "ok" && r.severity !== "info") : rs;
|
|
257
|
+
// Run each check ONCE and reuse the results for both printing and the
|
|
258
|
+
// final tally. The tool detection probes shell out, so duplicating
|
|
259
|
+
// them would slow doctor down for no reason.
|
|
260
|
+
const env = [
|
|
261
|
+
checkConfigWritable(),
|
|
262
|
+
checkDocker(),
|
|
263
|
+
checkDockerCompose(),
|
|
264
|
+
];
|
|
265
|
+
const authResults = await checkAuth();
|
|
266
|
+
const cluster = await checkClusterReachable();
|
|
267
|
+
printSection("Local environment");
|
|
268
|
+
for (const r of filter(env))
|
|
269
|
+
printCheck(r);
|
|
270
|
+
printSection("Authentication");
|
|
271
|
+
for (const r of filter(authResults))
|
|
272
|
+
printCheck(r);
|
|
273
|
+
printSection("Connectivity");
|
|
274
|
+
for (const r of filter([cluster]))
|
|
275
|
+
printCheck(r);
|
|
276
|
+
if (options.providers) {
|
|
277
|
+
printSection("Cloud provider tools (for ay provision)");
|
|
278
|
+
for (const r of filter(checkProviderTools()))
|
|
279
|
+
printCheck(r);
|
|
280
|
+
}
|
|
281
|
+
// Exit code: 0 unless any error severity surfaced.
|
|
282
|
+
const allChecks = [...env, ...authResults, cluster];
|
|
283
|
+
const errors = allChecks.filter((r) => r.severity === "error").length;
|
|
284
|
+
const warns = allChecks.filter((r) => r.severity === "warn").length;
|
|
285
|
+
console.log("");
|
|
286
|
+
if (errors > 0) {
|
|
287
|
+
console.log(chalk.red(` ✗ ${errors} error${errors === 1 ? "" : "s"}`) +
|
|
288
|
+
(warns > 0 ? chalk.yellow(`, ${warns} warning${warns === 1 ? "" : "s"}`) : ""));
|
|
289
|
+
console.log("");
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
if (warns > 0) {
|
|
293
|
+
console.log(chalk.yellow(` ! ${warns} warning${warns === 1 ? "" : "s"} — see hints above.`));
|
|
294
|
+
console.log("");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
console.log(chalk.green(" ✓ All checks passed."));
|
|
298
|
+
console.log("");
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
cliError(e.message || "Doctor failed", EXIT_GENERAL_ERROR);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
export default createDoctorCommand;
|
|
@@ -6,6 +6,7 @@ import { spinner } from "../../index.js";
|
|
|
6
6
|
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
7
7
|
import { cliError } from "../helpers/cliError.js";
|
|
8
8
|
import { sanitizeFields } from "../helpers/sanitizeFields.js";
|
|
9
|
+
import { parseInteger } from "../helpers/parseInt.js";
|
|
9
10
|
export function createExportCommand(program) {
|
|
10
11
|
const exp = program
|
|
11
12
|
.command("export")
|
|
@@ -15,17 +16,17 @@ export function createExportCommand(program) {
|
|
|
15
16
|
exp
|
|
16
17
|
.command("run <collection>")
|
|
17
18
|
.description("Export entries from a collection")
|
|
18
|
-
.addHelpText("after", `
|
|
19
|
-
Examples:
|
|
20
|
-
ay export run contacts --format csv --fields "firstName,lastName,email"
|
|
21
|
-
ay export run products --format json --filter "status=active"
|
|
19
|
+
.addHelpText("after", `
|
|
20
|
+
Examples:
|
|
21
|
+
ay export run contacts --format csv --fields "firstName,lastName,email"
|
|
22
|
+
ay export run products --format json --filter "status=active"
|
|
22
23
|
ay export run invoices --format csv --filter "createdAt>2025-01-01" --save`)
|
|
23
24
|
.option("--format <format>", "Export format (json, csv, yaml)", "csv")
|
|
24
25
|
.option("--fields <fields>", "Comma-separated fields to include in export")
|
|
25
26
|
.option("--filter <filters>", "Comma-separated key=value filters")
|
|
26
27
|
.option("--sort <field>", "Sort by field (prefix with - for descending)", "-createdAt")
|
|
27
|
-
.option("-l, --limit <number>", "Limit results (0 = all)",
|
|
28
|
-
.option("-p, --page <number>", "Page number",
|
|
28
|
+
.option("-l, --limit <number>", "Limit results (0 = all)", parseInteger, 0)
|
|
29
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
29
30
|
.action(async (collection, options) => {
|
|
30
31
|
var _a, _b, _c, _d, _e;
|
|
31
32
|
try {
|
|
@@ -115,8 +116,8 @@ Examples:
|
|
|
115
116
|
.command("list")
|
|
116
117
|
.alias("ls")
|
|
117
118
|
.description("List previous exports")
|
|
118
|
-
.option("-l, --limit <number>", "Limit results",
|
|
119
|
-
.option("-p, --page <number>", "Page number",
|
|
119
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 25)
|
|
120
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
120
121
|
.action(async (options) => {
|
|
121
122
|
var _a, _b;
|
|
122
123
|
try {
|
|
@@ -163,7 +164,7 @@ Examples:
|
|
|
163
164
|
exp
|
|
164
165
|
.command("configs")
|
|
165
166
|
.description("List export configurations")
|
|
166
|
-
.option("-l, --limit <number>", "Limit results",
|
|
167
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 50)
|
|
167
168
|
.action(async (options) => {
|
|
168
169
|
var _a, _b;
|
|
169
170
|
try {
|
|
@@ -189,8 +190,8 @@ Examples:
|
|
|
189
190
|
exp
|
|
190
191
|
.command("logs")
|
|
191
192
|
.description("List export logs")
|
|
192
|
-
.option("-l, --limit <number>", "Limit results",
|
|
193
|
-
.option("-p, --page <number>", "Page number",
|
|
193
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 25)
|
|
194
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
194
195
|
.action(async (options) => {
|
|
195
196
|
var _a, _b;
|
|
196
197
|
try {
|
|
@@ -4,19 +4,29 @@ import { saveFile } from "../helpers/saveFile.js";
|
|
|
4
4
|
import { spinner } from "../../index.js";
|
|
5
5
|
import { EXIT_GENERAL_ERROR } from "../exitCodes.js";
|
|
6
6
|
import { cliError } from "../helpers/cliError.js";
|
|
7
|
+
import { parseInteger } from "../helpers/parseInt.js";
|
|
7
8
|
export function createJobsCommand(program) {
|
|
8
9
|
const jobs = program
|
|
9
10
|
.command("jobs")
|
|
10
11
|
.alias("j")
|
|
11
|
-
.description("Manage background jobs, automations, and triggers")
|
|
12
|
+
.description("Manage background jobs, automations, and triggers")
|
|
13
|
+
.addHelpText("after", `
|
|
14
|
+
Examples:
|
|
15
|
+
ay jobs list List queued + scheduled jobs
|
|
16
|
+
ay jobs list --status failed Show only failed jobs
|
|
17
|
+
ay jobs get <id> Inspect a single job
|
|
18
|
+
ay jobs retry <id> Retry a failed job
|
|
19
|
+
ay jobs cancel <id> Cancel a queued job
|
|
20
|
+
ay jobs triggers list List automation triggers
|
|
21
|
+
ay jobs notifications list List recent notifications`);
|
|
12
22
|
// ay jobs list
|
|
13
23
|
jobs
|
|
14
24
|
.command("list")
|
|
15
25
|
.alias("ls")
|
|
16
26
|
.description("List scheduled/queued jobs")
|
|
17
27
|
.option("--status <status>", "Filter: active, waiting, completed, failed, delayed")
|
|
18
|
-
.option("-l, --limit <number>", "Limit results",
|
|
19
|
-
.option("-p, --page <number>", "Page number",
|
|
28
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 50)
|
|
29
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
20
30
|
.action(async (options) => {
|
|
21
31
|
var _a, _b, _c;
|
|
22
32
|
try {
|
|
@@ -46,8 +56,8 @@ export function createJobsCommand(program) {
|
|
|
46
56
|
jobs
|
|
47
57
|
.command("triggers")
|
|
48
58
|
.description("List automation triggers")
|
|
49
|
-
.option("-l, --limit <number>", "Limit results",
|
|
50
|
-
.option("-p, --page <number>", "Page number",
|
|
59
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 50)
|
|
60
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
51
61
|
.action(async (options) => {
|
|
52
62
|
var _a, _b, _c;
|
|
53
63
|
try {
|
|
@@ -76,8 +86,8 @@ export function createJobsCommand(program) {
|
|
|
76
86
|
.alias("auto")
|
|
77
87
|
.description("List automations")
|
|
78
88
|
.option("--active", "Show only active automations")
|
|
79
|
-
.option("-l, --limit <number>", "Limit results",
|
|
80
|
-
.option("-p, --page <number>", "Page number",
|
|
89
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 50)
|
|
90
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
81
91
|
.action(async (options) => {
|
|
82
92
|
var _a, _b, _c;
|
|
83
93
|
try {
|
|
@@ -136,8 +146,8 @@ export function createJobsCommand(program) {
|
|
|
136
146
|
.command("notifications")
|
|
137
147
|
.alias("notify")
|
|
138
148
|
.description("List recent notifications")
|
|
139
|
-
.option("-l, --limit <number>", "Limit results",
|
|
140
|
-
.option("-p, --page <number>", "Page number",
|
|
149
|
+
.option("-l, --limit <number>", "Limit results", parseInteger, 25)
|
|
150
|
+
.option("-p, --page <number>", "Page number", parseInteger, 1)
|
|
141
151
|
.action(async (options) => {
|
|
142
152
|
var _a, _b, _c;
|
|
143
153
|
try {
|
|
@@ -9,6 +9,11 @@ export function createLoginCommand(program) {
|
|
|
9
9
|
.command("login")
|
|
10
10
|
.alias("auth")
|
|
11
11
|
.description("Authenticate with your aYOUne account via browser or JWT token")
|
|
12
|
+
.addHelpText("after", `
|
|
13
|
+
Examples:
|
|
14
|
+
ay login Open browser to authenticate
|
|
15
|
+
ay login --token <jwt> Authenticate with an existing JWT
|
|
16
|
+
ay auth Alias of \`ay login\``)
|
|
12
17
|
.action(async (_options, cmd) => {
|
|
13
18
|
var _a, _b, _c, _d;
|
|
14
19
|
try {
|
|
@@ -7,6 +7,10 @@ export function createLogoutCommand(program) {
|
|
|
7
7
|
.command("logout")
|
|
8
8
|
.alias("signout")
|
|
9
9
|
.description("Clear stored credentials and log out")
|
|
10
|
+
.addHelpText("after", `
|
|
11
|
+
Examples:
|
|
12
|
+
ay logout Clear stored credentials
|
|
13
|
+
ay signout Alias of \`ay logout\``)
|
|
10
14
|
.action(async (options) => {
|
|
11
15
|
try {
|
|
12
16
|
spinner.start({ text: "Clearing credentials" });
|