kubeagent 0.1.13 → 0.1.15
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 +12 -2
- package/dist/cli.js +1 -1
- package/dist/onboard/index.js +2 -2
- package/dist/orchestrator.js +15 -3
- package/dist/proxy-client.d.ts +7 -0
- package/dist/proxy-client.js +26 -0
- package/dist/telemetry.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Built for solo DevOps engineers and small teams who want an intelligent on-call
|
|
|
10
10
|
# Install
|
|
11
11
|
npm install -g kubeagent
|
|
12
12
|
|
|
13
|
-
# Log in (
|
|
13
|
+
# Log in (opens browser; uses device-code flow on headless machines)
|
|
14
14
|
kubeagent login
|
|
15
15
|
|
|
16
16
|
# Onboard — scan cluster, detect your services, build knowledge base
|
|
@@ -64,7 +64,17 @@ Actions are classified into tiers:
|
|
|
64
64
|
|
|
65
65
|
## Notifications
|
|
66
66
|
|
|
67
|
-
Configure
|
|
67
|
+
Configure notification channels from the dashboard at [app.kubeagent.net](https://app.kubeagent.net):
|
|
68
|
+
|
|
69
|
+
| Channel | Setup |
|
|
70
|
+
|---------|-------|
|
|
71
|
+
| **Slack** | Connect via OAuth from the dashboard |
|
|
72
|
+
| **PagerDuty** | Add your integration key — verified automatically |
|
|
73
|
+
| **Discord** | Paste a Discord webhook URL |
|
|
74
|
+
| **Microsoft Teams** | Paste a Teams incoming webhook URL |
|
|
75
|
+
| **Custom Webhook** | Any HTTP endpoint that accepts JSON payloads |
|
|
76
|
+
|
|
77
|
+
You can also configure webhooks manually in `~/.kubeagent/config.yaml`:
|
|
68
78
|
|
|
69
79
|
```yaml
|
|
70
80
|
webhooks:
|
package/dist/cli.js
CHANGED
|
@@ -326,7 +326,7 @@ notifyCmd
|
|
|
326
326
|
});
|
|
327
327
|
notifyCmd
|
|
328
328
|
.command("add")
|
|
329
|
-
.description("Add a
|
|
329
|
+
.description("Add a notification channel")
|
|
330
330
|
.action(async () => {
|
|
331
331
|
const { interactiveAddChannel } = await import("./notify/setup.js");
|
|
332
332
|
const channel = await interactiveAddChannel();
|
package/dist/onboard/index.js
CHANGED
|
@@ -113,7 +113,7 @@ export async function onboard() {
|
|
|
113
113
|
if (existingChannels.length > 0) {
|
|
114
114
|
console.log(chalk.dim(` ${existingChannels.length} channel(s) already configured.`));
|
|
115
115
|
}
|
|
116
|
-
const addNotify = await ask("Set up a notification channel
|
|
116
|
+
const addNotify = await ask("Set up a notification channel? [y/N]");
|
|
117
117
|
if (addNotify.toLowerCase() === "y") {
|
|
118
118
|
let adding = true;
|
|
119
119
|
while (adding) {
|
|
@@ -290,7 +290,7 @@ export async function onboard() {
|
|
|
290
290
|
if (existingChannels.length > 0) {
|
|
291
291
|
console.log(chalk.dim(` ${existingChannels.length} channel(s) already configured.`));
|
|
292
292
|
}
|
|
293
|
-
const addNotify = await ask("Set up a notification channel
|
|
293
|
+
const addNotify = await ask("Set up a notification channel? [y/N]");
|
|
294
294
|
if (addNotify.toLowerCase() === "y") {
|
|
295
295
|
let adding = true;
|
|
296
296
|
while (adding) {
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { diagnose } from "./diagnoser/index.js";
|
|
3
3
|
import { verify } from "./verifier.js";
|
|
4
|
-
import {
|
|
4
|
+
import { notifyViaServer } from "./proxy-client.js";
|
|
5
5
|
import { writeIncident, writeUnresolved } from "./kb/writer.js";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { configDir } from "./config.js";
|
|
@@ -11,6 +11,7 @@ import { renderMarkdown, sectionHeader, commandBox } from "./render.js";
|
|
|
11
11
|
import { broadcastQuestion } from "./notify/index.js";
|
|
12
12
|
import readline from "node:readline";
|
|
13
13
|
const MAX_ATTEMPTS = 3;
|
|
14
|
+
let localChannelWarningShown = false;
|
|
14
15
|
const issueHistory = new Map();
|
|
15
16
|
function issueKey(issue) {
|
|
16
17
|
return `${issue.kind}:${issue.namespace ?? ""}:${issue.resource ?? ""}`;
|
|
@@ -115,7 +116,19 @@ export async function handleIssues(issues, config, clusterContext, noInteractive
|
|
|
115
116
|
}
|
|
116
117
|
if (actionableIssues.length === 0)
|
|
117
118
|
return;
|
|
118
|
-
|
|
119
|
+
// Warn once if user still has local notification channels configured
|
|
120
|
+
if (config.notifications.channels.length && !localChannelWarningShown) {
|
|
121
|
+
localChannelWarningShown = true;
|
|
122
|
+
console.log(chalk.yellow("⚠")
|
|
123
|
+
+ " Local notification channels detected in config. "
|
|
124
|
+
+ "These are no longer used — configure notifications on your dashboard instead: "
|
|
125
|
+
+ chalk.cyan("https://app.kubeagent.net/dashboard"));
|
|
126
|
+
}
|
|
127
|
+
// Dispatch via server when authenticated (SaaS-managed integrations)
|
|
128
|
+
const auth = loadAuth();
|
|
129
|
+
if (auth?.apiKey) {
|
|
130
|
+
await notifyViaServer(auth, actionableIssues, clusterContext);
|
|
131
|
+
}
|
|
119
132
|
// ── Diagnosing ────────────────────────────────────────────
|
|
120
133
|
sectionHeader("Diagnosing", chalk.blue.bold);
|
|
121
134
|
const result = await diagnose(actionableIssues, kbDir, clusterContext, {
|
|
@@ -200,7 +213,6 @@ export async function handleIssues(issues, config, clusterContext, noInteractive
|
|
|
200
213
|
].join("\n");
|
|
201
214
|
writeIncident(kbDir, dateStr, incidentName, incidentContent);
|
|
202
215
|
// Report to SaaS if authenticated
|
|
203
|
-
const auth = loadAuth();
|
|
204
216
|
if (auth?.apiKey) {
|
|
205
217
|
try {
|
|
206
218
|
await reportIncident(auth, {
|
package/dist/proxy-client.d.ts
CHANGED
|
@@ -16,3 +16,10 @@ export declare function reportIncident(auth: AuthState, incident: {
|
|
|
16
16
|
export declare function registerWebhook(auth: AuthState, url: string, label: string | undefined, severity: string): Promise<{
|
|
17
17
|
secret?: string;
|
|
18
18
|
}>;
|
|
19
|
+
export declare function notifyViaServer(auth: AuthState, issues: Array<{
|
|
20
|
+
kind: string;
|
|
21
|
+
severity: string;
|
|
22
|
+
message: string;
|
|
23
|
+
namespace?: string;
|
|
24
|
+
resource?: string;
|
|
25
|
+
}>, clusterContext?: string): Promise<void>;
|
package/dist/proxy-client.js
CHANGED
|
@@ -89,3 +89,29 @@ export async function registerWebhook(auth, url, label, severity) {
|
|
|
89
89
|
return {};
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
export async function notifyViaServer(auth, issues, clusterContext) {
|
|
93
|
+
const apiKey = auth.apiKey ?? auth.token;
|
|
94
|
+
try {
|
|
95
|
+
await fetch(`${auth.serverUrl}/notify`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
Authorization: `ApiKey ${apiKey}`,
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
issues: issues.map((i) => ({
|
|
103
|
+
kind: i.kind,
|
|
104
|
+
severity: i.severity,
|
|
105
|
+
message: i.message,
|
|
106
|
+
namespace: i.namespace,
|
|
107
|
+
resource: i.resource,
|
|
108
|
+
})),
|
|
109
|
+
clusterContext,
|
|
110
|
+
}),
|
|
111
|
+
signal: AbortSignal.timeout(15_000),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Silent — don't break the watch loop if server is unreachable
|
|
116
|
+
}
|
|
117
|
+
}
|
package/dist/telemetry.js
CHANGED
|
@@ -8,16 +8,16 @@ export function sendTelemetry(cliVersion) {
|
|
|
8
8
|
// Only fire once per machine
|
|
9
9
|
if (existsSync(SENT_FILE))
|
|
10
10
|
return;
|
|
11
|
-
//
|
|
11
|
+
// Ensure directory exists
|
|
12
12
|
try {
|
|
13
13
|
if (!existsSync(TELEMETRY_DIR))
|
|
14
14
|
mkdirSync(TELEMETRY_DIR, { recursive: true });
|
|
15
|
-
writeFileSync(SENT_FILE, new Date().toISOString());
|
|
16
15
|
}
|
|
17
16
|
catch {
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
20
19
|
// Fire-and-forget — never block the CLI
|
|
20
|
+
// Only mark as sent after successful delivery
|
|
21
21
|
const payload = JSON.stringify({
|
|
22
22
|
event: "cli_first_run",
|
|
23
23
|
cliVersion,
|
|
@@ -30,5 +30,9 @@ export function sendTelemetry(cliVersion) {
|
|
|
30
30
|
headers: { "Content-Type": "application/json" },
|
|
31
31
|
body: payload,
|
|
32
32
|
signal: AbortSignal.timeout(5000),
|
|
33
|
+
}).then((res) => {
|
|
34
|
+
if (res.ok) {
|
|
35
|
+
writeFileSync(SENT_FILE, new Date().toISOString());
|
|
36
|
+
}
|
|
33
37
|
}).catch(() => { });
|
|
34
38
|
}
|