@inceptionstack/roundhouse 0.3.15 → 0.3.17
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/package.json +1 -1
- package/src/cli/doctor/checks/credentials.ts +27 -41
- package/src/cli/doctor/checks/telegram.ts +20 -31
- package/src/cli/doctor.ts +1 -1
- package/src/cli/setup.ts +16 -0
package/package.json
CHANGED
|
@@ -1,62 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Credential checks
|
|
2
|
+
* Credential checks — validates token presence and format.
|
|
3
|
+
* API connectivity is tested by the telegram checks (checks/telegram.ts).
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
|
-
import
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { parseEnvFile } from "../../env-file";
|
|
8
|
+
import type { DoctorCheck, DoctorContext } from "../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the Telegram bot token from process env or the .env file.
|
|
12
|
+
* Shared by credential and telegram checks.
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveToken(ctx: DoctorContext): Promise<string | null> {
|
|
15
|
+
let token = ctx.env.TELEGRAM_BOT_TOKEN;
|
|
16
|
+
if (!token) {
|
|
17
|
+
try {
|
|
18
|
+
const entries = parseEnvFile(await readFile(ctx.envFilePath, "utf8"));
|
|
19
|
+
const raw = entries.get("TELEGRAM_BOT_TOKEN");
|
|
20
|
+
if (raw) token = raw.replace(/^["']|["']$/g, "");
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
return token || null;
|
|
24
|
+
}
|
|
6
25
|
|
|
7
26
|
export const credentialChecks: DoctorCheck[] = [
|
|
8
27
|
{
|
|
9
28
|
id: "telegram-token", category: "credentials", name: "Telegram bot token",
|
|
10
29
|
async run(ctx) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const { readFile } = await import("node:fs/promises");
|
|
16
|
-
const envContent = await readFile(ctx.envFilePath, "utf8");
|
|
17
|
-
const match = envContent.match(/^TELEGRAM_BOT_TOKEN=(.+)$/m);
|
|
18
|
-
if (match) token = match[1].trim().replace(/^["']|["']$/g, "");
|
|
19
|
-
} catch {}
|
|
20
|
-
}
|
|
30
|
+
const base = { id: "telegram-token", category: "credentials" as const, name: "Telegram bot token" };
|
|
31
|
+
const token = await resolveToken(ctx);
|
|
32
|
+
|
|
21
33
|
if (!token) {
|
|
22
34
|
return {
|
|
23
|
-
|
|
24
|
-
status: "fail", summary: "TELEGRAM_BOT_TOKEN not set",
|
|
35
|
+
...base, status: "fail", summary: "TELEGRAM_BOT_TOKEN not set",
|
|
25
36
|
details: ["Set TELEGRAM_BOT_TOKEN in your environment or ~/.roundhouse/.env"],
|
|
26
37
|
};
|
|
27
38
|
}
|
|
28
39
|
if (!/^\d+:[A-Za-z0-9_-]+$/.test(token)) {
|
|
29
40
|
return {
|
|
30
|
-
|
|
31
|
-
status: "fail", summary: "invalid format",
|
|
41
|
+
...base, status: "fail", summary: "invalid format",
|
|
32
42
|
details: ["Token should match pattern: digits:alphanumeric"],
|
|
33
43
|
};
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const res = await fetch(`https://api.telegram.org/bot${token}/getMe`, {
|
|
38
|
-
signal: AbortSignal.timeout(10000),
|
|
39
|
-
});
|
|
40
|
-
if (res.ok) {
|
|
41
|
-
const data = await res.json() as any;
|
|
42
|
-
const username = data.result?.username ?? "unknown";
|
|
43
|
-
return {
|
|
44
|
-
id: "telegram-token", category: "credentials", name: "Telegram bot token",
|
|
45
|
-
status: "pass", summary: `@${username}`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
id: "telegram-token", category: "credentials", name: "Telegram bot token",
|
|
50
|
-
status: "fail", summary: `API returned ${res.status}`,
|
|
51
|
-
details: ["Token may be invalid or revoked"],
|
|
52
|
-
};
|
|
53
|
-
} catch (err) {
|
|
54
|
-
return {
|
|
55
|
-
id: "telegram-token", category: "credentials", name: "Telegram bot token",
|
|
56
|
-
status: "warn", summary: "cannot reach Telegram API",
|
|
57
|
-
details: [(err as Error).message],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
45
|
+
return { ...base, status: "pass", summary: "present, valid format" };
|
|
60
46
|
},
|
|
61
47
|
},
|
|
62
48
|
];
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram connectivity checks
|
|
2
|
+
* Telegram connectivity checks — tests API access, config, and webhook status.
|
|
3
|
+
* Token resolution is shared with credentials.ts via resolveToken().
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import type { DoctorCheck } from "../types";
|
|
7
|
-
import {
|
|
7
|
+
import type { DoctorCheck, DoctorContext } from "../types";
|
|
8
|
+
import { resolveToken } from "./credentials";
|
|
8
9
|
|
|
9
|
-
/**
|
|
10
|
-
async function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const raw = entries.get("TELEGRAM_BOT_TOKEN");
|
|
16
|
-
if (raw) token = raw.replace(/^["']|["']$/g, "");
|
|
17
|
-
} catch {}
|
|
10
|
+
/** Load and parse the gateway config, returning null on any error. */
|
|
11
|
+
async function loadGatewayConfig(ctx: DoctorContext): Promise<any | null> {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(await readFile(ctx.configPath, "utf8"));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
18
16
|
}
|
|
19
|
-
return token || null;
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
export const telegramChecks: DoctorCheck[] = [
|
|
@@ -24,17 +21,15 @@ export const telegramChecks: DoctorCheck[] = [
|
|
|
24
21
|
id: "telegram-configured", category: "network", name: "Telegram adapter configured",
|
|
25
22
|
async run(ctx) {
|
|
26
23
|
const base = { id: "telegram-configured", category: "network" as const, name: "Telegram adapter configured" };
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const cfg = JSON.parse(raw);
|
|
30
|
-
if (cfg.chat?.adapters?.telegram) {
|
|
31
|
-
const mode = cfg.chat.adapters.telegram.mode ?? "polling";
|
|
32
|
-
return { ...base, status: "pass", summary: `mode: ${mode}` };
|
|
33
|
-
}
|
|
34
|
-
return { ...base, status: "info", summary: "not configured (no telegram adapter in config)" };
|
|
35
|
-
} catch {
|
|
24
|
+
const cfg = await loadGatewayConfig(ctx);
|
|
25
|
+
if (!cfg) {
|
|
36
26
|
return { ...base, status: "info", summary: "skipped (no config file)" };
|
|
37
27
|
}
|
|
28
|
+
if (cfg.chat?.adapters?.telegram) {
|
|
29
|
+
const mode = cfg.chat.adapters.telegram.mode ?? "polling";
|
|
30
|
+
return { ...base, status: "pass", summary: `mode: ${mode}` };
|
|
31
|
+
}
|
|
32
|
+
return { ...base, status: "info", summary: "not configured (no telegram adapter in config)" };
|
|
38
33
|
},
|
|
39
34
|
},
|
|
40
35
|
|
|
@@ -77,13 +72,8 @@ export const telegramChecks: DoctorCheck[] = [
|
|
|
77
72
|
return { ...base, status: "info", summary: "skipped (no token)" };
|
|
78
73
|
}
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const raw = await readFile(ctx.configPath, "utf8");
|
|
84
|
-
const cfg = JSON.parse(raw);
|
|
85
|
-
configuredMode = cfg.chat?.adapters?.telegram?.mode ?? "polling";
|
|
86
|
-
} catch {}
|
|
75
|
+
const cfg = await loadGatewayConfig(ctx);
|
|
76
|
+
const configuredMode = cfg?.chat?.adapters?.telegram?.mode ?? "polling";
|
|
87
77
|
|
|
88
78
|
try {
|
|
89
79
|
const res = await fetch(`https://api.telegram.org/bot${token}/getWebhookInfo`, {
|
|
@@ -99,7 +89,7 @@ export const telegramChecks: DoctorCheck[] = [
|
|
|
99
89
|
if (configuredMode === "polling") {
|
|
100
90
|
if (webhookUrl) {
|
|
101
91
|
return {
|
|
102
|
-
...base, status: "warn", summary:
|
|
92
|
+
...base, status: "warn", summary: "webhook set but mode is polling",
|
|
103
93
|
details: [
|
|
104
94
|
`Webhook URL: ${webhookUrl}`,
|
|
105
95
|
"Polling won't receive updates while a webhook is active.",
|
|
@@ -109,7 +99,6 @@ export const telegramChecks: DoctorCheck[] = [
|
|
|
109
99
|
}
|
|
110
100
|
return { ...base, status: "pass", summary: `no webhook (polling mode), ${pendingUpdates} pending updates` };
|
|
111
101
|
} else {
|
|
112
|
-
// webhook mode
|
|
113
102
|
if (!webhookUrl) {
|
|
114
103
|
return { ...base, status: "warn", summary: "webhook mode configured but no webhook set" };
|
|
115
104
|
}
|
package/src/cli/doctor.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { formatResult, formatSummary, formatCategoryHeader } from "./doctor/outp
|
|
|
7
7
|
import { runDoctor, createDoctorContext } from "./doctor/runner";
|
|
8
8
|
|
|
9
9
|
const CATEGORY_ORDER: DoctorCategory[] = [
|
|
10
|
-
"system", "config", "credentials", "agent", "stt", "disk", "systemd",
|
|
10
|
+
"system", "config", "credentials", "network", "agent", "stt", "disk", "systemd",
|
|
11
11
|
];
|
|
12
12
|
|
|
13
13
|
export async function cmdDoctor(args: string[]): Promise<void> {
|
package/src/cli/setup.ts
CHANGED
|
@@ -232,6 +232,22 @@ async function stepPreflight(opts: SetupOptions): Promise<void> {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// Seed .env with commented-out example if it doesn't exist yet
|
|
236
|
+
if (!(await fileExists(ENV_PATH))) {
|
|
237
|
+
const seed = [
|
|
238
|
+
"# Roundhouse environment file",
|
|
239
|
+
"# Uncomment and set values, or use: roundhouse setup",
|
|
240
|
+
"#",
|
|
241
|
+
"# TELEGRAM_BOT_TOKEN=\"your-bot-token\"",
|
|
242
|
+
"# BOT_USERNAME=\"your_bot_username\"",
|
|
243
|
+
"# ALLOWED_USERS=\"your_telegram_username\"",
|
|
244
|
+
"# AWS_PROFILE=\"default\"",
|
|
245
|
+
"# AWS_REGION=\"us-east-1\"",
|
|
246
|
+
"",
|
|
247
|
+
].join("\n");
|
|
248
|
+
await writeFile(ENV_PATH, seed, { mode: 0o600 });
|
|
249
|
+
}
|
|
250
|
+
|
|
235
251
|
// Disk space (rough check)
|
|
236
252
|
try {
|
|
237
253
|
const dfOut = execSafe("df", ["-BG", "--output=avail", homedir()], { silent: true });
|