apihealthz 1.0.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/README.md +189 -0
- package/dist/commands/add.js +122 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/addApiCheck.js +14 -0
- package/dist/commands/addApiCheck.js.map +1 -0
- package/dist/commands/apiCheck.js +12 -0
- package/dist/commands/apiCheck.js.map +1 -0
- package/dist/commands/auth.js +26 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/delete.js +45 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/list.js +35 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/services/api.js +20 -0
- package/dist/services/api.js.map +1 -0
- package/dist/services/apiHealth.js +21 -0
- package/dist/services/apiHealth.js.map +1 -0
- package/dist/services/apiHealthz.js +48 -0
- package/dist/services/apiHealthz.js.map +1 -0
- package/dist/services/auth.js +102 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/services/timezone.js +29 -0
- package/dist/services/timezone.js.map +1 -0
- package/dist/utils/config.js +27 -0
- package/dist/utils/config.js.map +1 -0
- package/package.json +28 -0
- package/src/commands/add.ts +136 -0
- package/src/commands/auth.ts +28 -0
- package/src/commands/delete.ts +47 -0
- package/src/commands/list.ts +37 -0
- package/src/index.ts +21 -0
- package/src/services/api.ts +15 -0
- package/src/services/apiHealth.ts +25 -0
- package/src/services/auth.ts +121 -0
- package/src/services/timezone.ts +24 -0
- package/src/utils/config.ts +20 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loginWithProvider = loginWithProvider;
|
|
7
|
+
exports.logout = logout;
|
|
8
|
+
exports.authStatus = authStatus;
|
|
9
|
+
const open_1 = __importDefault(require("open"));
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const api_1 = require("./api");
|
|
12
|
+
const config_1 = require("../utils/config");
|
|
13
|
+
async function loginWithProvider(provider) {
|
|
14
|
+
if (!provider) {
|
|
15
|
+
console.log("Choose provider: github | google");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const { data } = await api_1.api.post("/auth/device/start", {
|
|
19
|
+
provider,
|
|
20
|
+
});
|
|
21
|
+
console.log("\nOpen this URL in your browser:");
|
|
22
|
+
console.log(data.verification_url);
|
|
23
|
+
console.log("\nEnter code:", data.user_code);
|
|
24
|
+
await (0, open_1.default)(data.verification_url);
|
|
25
|
+
await pollForToken(data.device_code);
|
|
26
|
+
}
|
|
27
|
+
async function pollForToken(deviceCode) {
|
|
28
|
+
process.stdout.write("\nWaiting for authorization");
|
|
29
|
+
let pollInterval = 3000; // Default 3 seconds
|
|
30
|
+
while (true) {
|
|
31
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
32
|
+
process.stdout.write(".");
|
|
33
|
+
let data;
|
|
34
|
+
try {
|
|
35
|
+
const response = await api_1.api.post("/auth/device/poll", {
|
|
36
|
+
device_code: deviceCode,
|
|
37
|
+
});
|
|
38
|
+
data = response.data;
|
|
39
|
+
// Update poll interval if provided by server
|
|
40
|
+
if (data.poll_interval) {
|
|
41
|
+
pollInterval = data.poll_interval * 1000; // Convert seconds to milliseconds
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.log("\n✖ Error polling for authorization");
|
|
46
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
47
|
+
if (error.response) {
|
|
48
|
+
// Server responded with error status
|
|
49
|
+
console.log(` Status: ${error.response.status}`);
|
|
50
|
+
console.log(` Message: ${error.response.statusText}`);
|
|
51
|
+
if (error.response.data) {
|
|
52
|
+
console.log(` Details:`, error.response.data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (error.request) {
|
|
56
|
+
// Request was made but no response received
|
|
57
|
+
console.log(` No response received from server`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Error setting up the request
|
|
61
|
+
console.log(` Error: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Unknown error type
|
|
66
|
+
console.log(` Error:`, error);
|
|
67
|
+
}
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (!data) {
|
|
71
|
+
console.log("\n✖ Error: No data received");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
if (data.status === "approved") {
|
|
75
|
+
if (!data.token) {
|
|
76
|
+
console.log("\n✖ Error: No token received");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
(0, config_1.saveToken)(data.token);
|
|
80
|
+
console.log("\n✔ Logged in successfully");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (data.status === "expired") {
|
|
84
|
+
console.log("\n✖ Login expired");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function logout() {
|
|
90
|
+
(0, config_1.clearToken)();
|
|
91
|
+
console.log("✔ Logged out");
|
|
92
|
+
}
|
|
93
|
+
async function authStatus() {
|
|
94
|
+
const token = (0, config_1.getToken)();
|
|
95
|
+
if (!token) {
|
|
96
|
+
console.log("Not logged in");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const { data } = await api_1.api.get("/auth/me");
|
|
100
|
+
console.log(`✔ Logged in as ${data?.email}`);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/services/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatDate = exports.timezone = void 0;
|
|
4
|
+
// Get user's timezone
|
|
5
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
6
|
+
exports.timezone = timezone;
|
|
7
|
+
// Format date in user's timezone
|
|
8
|
+
const formatDate = (dateString) => {
|
|
9
|
+
if (!dateString)
|
|
10
|
+
return "N/A";
|
|
11
|
+
try {
|
|
12
|
+
const date = new Date(dateString);
|
|
13
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
14
|
+
timeZone: timezone,
|
|
15
|
+
year: "numeric",
|
|
16
|
+
month: "short",
|
|
17
|
+
day: "numeric",
|
|
18
|
+
hour: "2-digit",
|
|
19
|
+
minute: "2-digit",
|
|
20
|
+
second: "2-digit",
|
|
21
|
+
hour12: true,
|
|
22
|
+
}).format(date);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return dateString;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.formatDate = formatDate;
|
|
29
|
+
//# sourceMappingURL=timezone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezone.js","sourceRoot":"","sources":["../../src/services/timezone.ts"],"names":[],"mappings":";;;AAAA,sBAAsB;AACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,CAAC;AAsBzD,4BAAQ;AApBjB,iCAAiC;AACjC,MAAM,UAAU,GAAG,CAAC,UAAyB,EAAE,EAAE;IAC/C,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YACtC,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,OAAO;YACd,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,CAAC;IACpB,CAAC;AACH,CAAC,CAAC;AAEiB,gCAAU"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveToken = saveToken;
|
|
7
|
+
exports.getToken = getToken;
|
|
8
|
+
exports.clearToken = clearToken;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".apihealthz");
|
|
13
|
+
const TOKEN_PATH = path_1.default.join(CONFIG_DIR, "token");
|
|
14
|
+
function saveToken(token) {
|
|
15
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
+
fs_1.default.writeFileSync(TOKEN_PATH, token, { mode: 0o600 });
|
|
17
|
+
}
|
|
18
|
+
function getToken() {
|
|
19
|
+
if (!fs_1.default.existsSync(TOKEN_PATH))
|
|
20
|
+
return null;
|
|
21
|
+
return fs_1.default.readFileSync(TOKEN_PATH, "utf-8");
|
|
22
|
+
}
|
|
23
|
+
function clearToken() {
|
|
24
|
+
if (fs_1.default.existsSync(TOKEN_PATH))
|
|
25
|
+
fs_1.default.unlinkSync(TOKEN_PATH);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":";;;;;AAOA,8BAGC;AAED,4BAGC;AAED,gCAEC;AAnBD,4CAAoB;AACpB,gDAAwB;AACxB,4CAAoB;AAEpB,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AAC1D,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAElD,SAAgB,SAAS,CAAC,KAAa;IACrC,YAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,SAAgB,QAAQ;IACtB,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,YAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,UAAU;IACxB,IAAI,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC3D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apihealthz",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"apihealthz": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"setup": "npm run build && npm link"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"type": "commonjs",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@inquirer/prompts": "^8.1.0",
|
|
19
|
+
"axios": "^1.13.2",
|
|
20
|
+
"commander": "^14.0.2",
|
|
21
|
+
"open": "^11.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.0.3",
|
|
25
|
+
"ts-node": "^10.9.2",
|
|
26
|
+
"typescript": "^5.9.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
2
|
+
import { getToken } from "../utils/config";
|
|
3
|
+
import { createHealthCheck } from "../services/apiHealth";
|
|
4
|
+
|
|
5
|
+
export async function add() {
|
|
6
|
+
const token = getToken();
|
|
7
|
+
|
|
8
|
+
if (!token) {
|
|
9
|
+
console.error("✖ Authentication required. Run `apihealthz login`");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const url = await input({
|
|
15
|
+
message: "API/Site URL to monitor:",
|
|
16
|
+
validate: (v) =>
|
|
17
|
+
v.startsWith("http://") || v.startsWith("https://")
|
|
18
|
+
? true
|
|
19
|
+
: "Invalid URL",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const interval = await select({
|
|
23
|
+
message: "Select check interval:",
|
|
24
|
+
choices: [
|
|
25
|
+
{ name: "Every 5 minutes", value: 300 },
|
|
26
|
+
{ name: "Every 15 minutes", value: 900 },
|
|
27
|
+
{ name: "Every 30 minutes", value: 1800 },
|
|
28
|
+
{ name: "Every 1 hour", value: 3600 },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const intervalName =
|
|
33
|
+
interval === 300
|
|
34
|
+
? "Every 5 minutes"
|
|
35
|
+
: interval === 900
|
|
36
|
+
? "Every 15 minutes"
|
|
37
|
+
: interval === 1800
|
|
38
|
+
? "Every 30 minutes"
|
|
39
|
+
: interval === 3600
|
|
40
|
+
? "Every 1 hour"
|
|
41
|
+
: `${interval} seconds`;
|
|
42
|
+
|
|
43
|
+
const alertType = await select({
|
|
44
|
+
message: "How do you want to receive alerts?",
|
|
45
|
+
choices: [
|
|
46
|
+
{ name: "Slack", value: "slack" },
|
|
47
|
+
{ name: "Email", value: "email" },
|
|
48
|
+
{ name: "WhatsApp", value: "whatsapp" },
|
|
49
|
+
{ name: "None", value: null },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let slack_webhook;
|
|
54
|
+
let email;
|
|
55
|
+
|
|
56
|
+
if (alertType === "slack") {
|
|
57
|
+
slack_webhook = await input({
|
|
58
|
+
message: "Slack webhook URL:",
|
|
59
|
+
validate: (v) =>
|
|
60
|
+
v.startsWith("https://hooks.slack.com/")
|
|
61
|
+
? true
|
|
62
|
+
: "Invalid Slack webhook URL",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (alertType === "email") {
|
|
67
|
+
email = await input({
|
|
68
|
+
message: "Email address:",
|
|
69
|
+
validate: (v) => (v.includes("@") ? true : "Invalid email address"),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Show a preview summary before confirmation
|
|
74
|
+
console.log("\n=== Health Check Preview ===");
|
|
75
|
+
console.log(`URL: ${url}`);
|
|
76
|
+
console.log(`Interval: ${intervalName}`);
|
|
77
|
+
console.log(
|
|
78
|
+
`Alert: ${
|
|
79
|
+
alertType === null
|
|
80
|
+
? "None"
|
|
81
|
+
: alertType === "slack"
|
|
82
|
+
? "Slack"
|
|
83
|
+
: alertType === "email"
|
|
84
|
+
? "Email"
|
|
85
|
+
: "N/A"
|
|
86
|
+
}`
|
|
87
|
+
);
|
|
88
|
+
if (slack_webhook) console.log(`Slack Webhook: ${slack_webhook}`);
|
|
89
|
+
if (email) console.log(`Email: ${email}`);
|
|
90
|
+
console.log("===========================\n");
|
|
91
|
+
|
|
92
|
+
const proceed = await confirm({
|
|
93
|
+
message: "Create this health check?",
|
|
94
|
+
default: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!proceed) {
|
|
98
|
+
console.log("Cancelled");
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const payload = {
|
|
103
|
+
url,
|
|
104
|
+
interval_sec: interval,
|
|
105
|
+
slack_webhook,
|
|
106
|
+
email,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (slack_webhook) payload.slack_webhook = slack_webhook;
|
|
110
|
+
if (email) payload.email = email;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
process.stdout.write("⠋ Creating health check...\r");
|
|
114
|
+
const response = await createHealthCheck({
|
|
115
|
+
url,
|
|
116
|
+
interval_sec: interval,
|
|
117
|
+
slack_webhook,
|
|
118
|
+
email,
|
|
119
|
+
});
|
|
120
|
+
console.log(`✔ Health check created successfully: ${response.id}`);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(
|
|
123
|
+
`✖ ${err instanceof Error ? err.message : "Unknown error"}`
|
|
124
|
+
);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
} catch (err: any) {
|
|
128
|
+
// Handle user cancellation (Ctrl+C)
|
|
129
|
+
if (err?.name === "ExitPromptError" || err?.message?.includes("SIGINT")) {
|
|
130
|
+
console.log("\nCancelled");
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
// Re-throw other errors
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { authStatus, loginWithProvider, logout } from "../services/auth";
|
|
3
|
+
|
|
4
|
+
export const authCommand = new Command("auth").description(
|
|
5
|
+
"Authentication commands"
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
authCommand
|
|
9
|
+
.command("login")
|
|
10
|
+
.description("Login to ApiHealthz")
|
|
11
|
+
.option("-p, --provider <provider>", "google")
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
await loginWithProvider(options.provider);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
authCommand
|
|
17
|
+
.command("logout")
|
|
18
|
+
.description("Logout from ApiHealthz")
|
|
19
|
+
.action(async () => {
|
|
20
|
+
await logout();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
authCommand
|
|
24
|
+
.command("status")
|
|
25
|
+
.description("Show auth status")
|
|
26
|
+
.action(async () => {
|
|
27
|
+
await authStatus();
|
|
28
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { confirm, input } from "@inquirer/prompts";
|
|
2
|
+
import { getToken } from "../utils/config";
|
|
3
|
+
import { api } from "../services/api";
|
|
4
|
+
|
|
5
|
+
export async function del() {
|
|
6
|
+
const token = getToken();
|
|
7
|
+
|
|
8
|
+
if (!token) {
|
|
9
|
+
console.error("✖ Authentication required. Run `apihealthz login`");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const id = await input({
|
|
15
|
+
message: "Enter the ID of the health check to delete:",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const proceed = await confirm({
|
|
19
|
+
message: `Delete check ${id}?`,
|
|
20
|
+
default: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!proceed) {
|
|
24
|
+
console.log("Cancelled");
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await api.delete(`/checks/${id}`);
|
|
30
|
+
console.log("✔ Health check deleted successfully");
|
|
31
|
+
process.exit(0);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(
|
|
34
|
+
`✖ ${err instanceof Error ? err.message : "Unknown error"}`
|
|
35
|
+
);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
// Handle user cancellation (Ctrl+C)
|
|
40
|
+
if (err?.name === "ExitPromptError" || err?.message?.includes("SIGINT")) {
|
|
41
|
+
console.log("\nCancelled");
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
// Re-throw other errors
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { api } from "../services/api";
|
|
2
|
+
import { formatDate } from "../services/timezone";
|
|
3
|
+
import { getToken } from "../utils/config";
|
|
4
|
+
|
|
5
|
+
export async function list() {
|
|
6
|
+
const token = getToken();
|
|
7
|
+
|
|
8
|
+
if (!token) {
|
|
9
|
+
console.error("✖ Authentication required. Run `apihealthz login`");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const response = await api.get("/checks");
|
|
15
|
+
const data = response.data;
|
|
16
|
+
|
|
17
|
+
if (!data || data.length === 0) {
|
|
18
|
+
console.log("✖ No health checks found, add one with: `apihealthz add`");
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.table(
|
|
23
|
+
data.map((c: any) => ({
|
|
24
|
+
ID: c.id,
|
|
25
|
+
URL: c.url,
|
|
26
|
+
Interval: `${c.interval_sec} seconds`,
|
|
27
|
+
"Last Status": c.last_status ? "✅ UP" : "❌ DOWN",
|
|
28
|
+
"Last Checked": c.last_checked_at
|
|
29
|
+
? formatDate(c.last_checked_at)
|
|
30
|
+
: "N/A",
|
|
31
|
+
}))
|
|
32
|
+
);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error(`✖ ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { authCommand } from "./commands/auth";
|
|
5
|
+
import { add } from "./commands/add";
|
|
6
|
+
import { list } from "./commands/list";
|
|
7
|
+
import { del } from "./commands/delete";
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name("apihealthz")
|
|
13
|
+
.description("API uptime & health monitoring CLI")
|
|
14
|
+
.version("1.0.0");
|
|
15
|
+
|
|
16
|
+
program.addCommand(authCommand);
|
|
17
|
+
program.command("add").description("Add a new API health check").action(add);
|
|
18
|
+
program.command("list").description("List all API health checks").action(list);
|
|
19
|
+
program.command("delete").description("Delete a health check").action(del);
|
|
20
|
+
|
|
21
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { getToken } from "../utils/config";
|
|
3
|
+
|
|
4
|
+
export const api = axios.create({
|
|
5
|
+
baseURL: "https://be.apihealthz.com",
|
|
6
|
+
timeout: 10000,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
api.interceptors.request.use((config) => {
|
|
10
|
+
const token = getToken();
|
|
11
|
+
if (token) {
|
|
12
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
13
|
+
}
|
|
14
|
+
return config;
|
|
15
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { clearToken, getToken } from "../utils/config";
|
|
2
|
+
import { api } from "./api";
|
|
3
|
+
|
|
4
|
+
export async function createHealthCheck(payload: {
|
|
5
|
+
url: string;
|
|
6
|
+
interval_sec: number;
|
|
7
|
+
slack_webhook?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
}) {
|
|
10
|
+
const token = getToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.error("✖ Authentication required. Run `apihealthz login`");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const body = {
|
|
17
|
+
url: payload.url,
|
|
18
|
+
interval_sec: payload.interval_sec,
|
|
19
|
+
slack_webhook: payload.slack_webhook,
|
|
20
|
+
email: payload.email,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const response = await api.post("/checks", body);
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import open from "open";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { api } from "./api";
|
|
4
|
+
import { saveToken, clearToken, getToken } from "../utils/config";
|
|
5
|
+
|
|
6
|
+
interface DeviceStartResponse {
|
|
7
|
+
device_code: string;
|
|
8
|
+
user_code: string;
|
|
9
|
+
verification_url: string;
|
|
10
|
+
expires_in: number;
|
|
11
|
+
poll_interval: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface DevicePollResponse {
|
|
15
|
+
status: "approved" | "expired" | "pending";
|
|
16
|
+
token?: string;
|
|
17
|
+
poll_interval?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function loginWithProvider(provider: string) {
|
|
21
|
+
if (provider !== "google") {
|
|
22
|
+
console.log("Choose provider: google");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { data } = await api.post<DeviceStartResponse>("/auth/device/start", {
|
|
27
|
+
provider,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log("\nOpen this URL in your browser:");
|
|
31
|
+
console.log(data.verification_url);
|
|
32
|
+
console.log("\nEnter code:", data.user_code);
|
|
33
|
+
|
|
34
|
+
await open(data.verification_url);
|
|
35
|
+
|
|
36
|
+
await pollForToken(data.device_code);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function pollForToken(deviceCode: string) {
|
|
40
|
+
process.stdout.write("\nWaiting for authorization");
|
|
41
|
+
|
|
42
|
+
let pollInterval = 3000; // Default 3 seconds
|
|
43
|
+
|
|
44
|
+
while (true) {
|
|
45
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
46
|
+
process.stdout.write(".");
|
|
47
|
+
|
|
48
|
+
let data;
|
|
49
|
+
try {
|
|
50
|
+
const response = await api.post<DevicePollResponse>("/auth/device/poll", {
|
|
51
|
+
device_code: deviceCode,
|
|
52
|
+
});
|
|
53
|
+
data = response.data;
|
|
54
|
+
|
|
55
|
+
// Update poll interval if provided by server
|
|
56
|
+
if (data.poll_interval) {
|
|
57
|
+
pollInterval = data.poll_interval * 1000; // Convert seconds to milliseconds
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.log("\n✖ Error polling for authorization");
|
|
61
|
+
|
|
62
|
+
if (axios.isAxiosError(error)) {
|
|
63
|
+
if (error.response) {
|
|
64
|
+
// Server responded with error status
|
|
65
|
+
console.log(` Status: ${error.response.status}`);
|
|
66
|
+
console.log(` Message: ${error.response.statusText}`);
|
|
67
|
+
if (error.response.data) {
|
|
68
|
+
console.log(` Details:`, error.response.data);
|
|
69
|
+
}
|
|
70
|
+
} else if (error.request) {
|
|
71
|
+
// Request was made but no response received
|
|
72
|
+
console.log(` No response received from server`);
|
|
73
|
+
} else {
|
|
74
|
+
// Error setting up the request
|
|
75
|
+
console.log(` Error: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Unknown error type
|
|
79
|
+
console.log(` Error:`, error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!data) {
|
|
86
|
+
console.log("\n✖ Error: No data received");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (data.status === "approved") {
|
|
91
|
+
if (!data.token) {
|
|
92
|
+
console.log("\n✖ Error: No token received");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
saveToken(data.token);
|
|
96
|
+
console.log("\n✔ Logged in successfully");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (data.status === "expired") {
|
|
101
|
+
console.log("\n✖ Login expired");
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function logout() {
|
|
108
|
+
clearToken();
|
|
109
|
+
console.log("✔ Logged out");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function authStatus() {
|
|
113
|
+
const token = getToken();
|
|
114
|
+
if (!token) {
|
|
115
|
+
console.log("Not logged in");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { data } = await api.get("/auth/me");
|
|
120
|
+
console.log(`✔ Logged in as ${data?.email}`);
|
|
121
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Get user's timezone
|
|
2
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
3
|
+
|
|
4
|
+
// Format date in user's timezone
|
|
5
|
+
const formatDate = (dateString: string | null) => {
|
|
6
|
+
if (!dateString) return "N/A";
|
|
7
|
+
try {
|
|
8
|
+
const date = new Date(dateString);
|
|
9
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
10
|
+
timeZone: timezone,
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "short",
|
|
13
|
+
day: "numeric",
|
|
14
|
+
hour: "2-digit",
|
|
15
|
+
minute: "2-digit",
|
|
16
|
+
second: "2-digit",
|
|
17
|
+
hour12: true,
|
|
18
|
+
}).format(date);
|
|
19
|
+
} catch {
|
|
20
|
+
return dateString;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { timezone, formatDate };
|