triagent 0.1.0-alpha1

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 ADDED
@@ -0,0 +1,51 @@
1
+ # Triagent
2
+
3
+ AI-powered Kubernetes debugging agent with terminal UI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun install triagent
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Run interactive TUI
15
+ triagent
16
+
17
+ # Run webhook server only
18
+ triagent --webhook-only
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Set the following environment variables:
24
+
25
+ ```bash
26
+ ANTHROPIC_API_KEY=your-api-key
27
+ # or
28
+ OPENAI_API_KEY=your-api-key
29
+ # or
30
+ GOOGLE_GENERATIVE_AI_API_KEY=your-api-key
31
+ ```
32
+
33
+ ## Development
34
+
35
+ ```bash
36
+ # Install dependencies
37
+ bun install
38
+
39
+ # Run in development mode
40
+ bun run dev
41
+
42
+ # Build
43
+ bun run build
44
+
45
+ # Type check
46
+ bun run typecheck
47
+ ```
48
+
49
+ ## License
50
+
51
+ MIT
package/bunfig.toml ADDED
@@ -0,0 +1 @@
1
+ preload = ["@opentui/solid/preload"]
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "triagent",
3
+ "version": "0.1.0-alpha1",
4
+ "description": "AI-powered Kubernetes debugging agent with terminal UI",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "bin": {
8
+ "triagent": "./src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "bunfig.toml"
13
+ ],
14
+ "engines": {
15
+ "bun": ">=1.2.0"
16
+ },
17
+ "keywords": [
18
+ "kubernetes",
19
+ "k8s",
20
+ "debugging",
21
+ "ai",
22
+ "agent",
23
+ "cli",
24
+ "tui",
25
+ "devops",
26
+ "sre"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/OWNER/triagent.git"
31
+ },
32
+ "license": "MIT",
33
+ "scripts": {
34
+ "dev": "bun run src/index.ts",
35
+ "webhook": "bun run src/index.ts --webhook-only",
36
+ "build": "bun run scripts/build.ts",
37
+ "typecheck": "tsc --noEmit",
38
+ "prepublishOnly": "bun run typecheck"
39
+ },
40
+ "dependencies": {
41
+ "@ai-sdk/anthropic": "^1.2.0",
42
+ "@ai-sdk/google": "^1.2.0",
43
+ "@ai-sdk/openai": "^1.3.0",
44
+ "@bashlet/sdk": "^0.1.0-alpha1",
45
+ "@mastra/core": "^1.0.0-beta.21",
46
+ "@opentui/core": "^0.1.72",
47
+ "@opentui/solid": "^0.1.72",
48
+ "hono": "^4.6.0",
49
+ "solid-js": "^1.9.10",
50
+ "zod": "^3.24.0"
51
+ },
52
+ "devDependencies": {
53
+ "typescript": "^5.7.0",
54
+ "@types/node": "^22.0.0",
55
+ "bun-types": "^1.2.0"
56
+ }
57
+ }
@@ -0,0 +1,60 @@
1
+ import { mkdir, readFile, writeFile } from "fs/promises";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import type { AIProvider } from "../config.js";
5
+
6
+ export interface StoredConfig {
7
+ aiProvider?: AIProvider;
8
+ aiModel?: string;
9
+ apiKey?: string;
10
+ webhookPort?: number;
11
+ codebasePath?: string;
12
+ kubeConfigPath?: string;
13
+ }
14
+
15
+ const CONFIG_DIR = join(homedir(), ".config", "triagent");
16
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
17
+
18
+ export async function getConfigPath(): Promise<string> {
19
+ return CONFIG_FILE;
20
+ }
21
+
22
+ export async function loadStoredConfig(): Promise<StoredConfig> {
23
+ try {
24
+ const content = await readFile(CONFIG_FILE, "utf-8");
25
+ return JSON.parse(content);
26
+ } catch {
27
+ return {};
28
+ }
29
+ }
30
+
31
+ export async function saveStoredConfig(config: StoredConfig): Promise<void> {
32
+ await mkdir(CONFIG_DIR, { recursive: true });
33
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
34
+ }
35
+
36
+ export async function setConfigValue(key: keyof StoredConfig, value: string | number): Promise<void> {
37
+ const config = await loadStoredConfig();
38
+ (config as Record<string, string | number>)[key] = value;
39
+ await saveStoredConfig(config);
40
+ }
41
+
42
+ export async function getConfigValue(key: keyof StoredConfig): Promise<string | number | undefined> {
43
+ const config = await loadStoredConfig();
44
+ return config[key];
45
+ }
46
+
47
+ export async function deleteConfigValue(key: keyof StoredConfig): Promise<void> {
48
+ const config = await loadStoredConfig();
49
+ delete config[key];
50
+ await saveStoredConfig(config);
51
+ }
52
+
53
+ export async function listConfig(): Promise<StoredConfig> {
54
+ return loadStoredConfig();
55
+ }
56
+
57
+ export function maskApiKey(key: string): string {
58
+ if (key.length <= 8) return "****";
59
+ return key.slice(0, 4) + "****" + key.slice(-4);
60
+ }
package/src/config.ts ADDED
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import { resolve } from "path";
3
+ import { homedir } from "os";
4
+ import { loadStoredConfig, type StoredConfig } from "./cli/config.js";
5
+
6
+ const AIProviderSchema = z.enum(["openai", "anthropic", "google"]);
7
+ export type AIProvider = z.infer<typeof AIProviderSchema>;
8
+
9
+ const ConfigSchema = z.object({
10
+ aiProvider: AIProviderSchema,
11
+ aiModel: z.string().min(1),
12
+ apiKey: z.string().min(1),
13
+ webhookPort: z.number().int().positive().default(3000),
14
+ codebasePath: z.string().min(1).default("./"),
15
+ kubeConfigPath: z.string().min(1).default("~/.kube"),
16
+ });
17
+
18
+ export type Config = z.infer<typeof ConfigSchema>;
19
+
20
+ function expandPath(path: string): string {
21
+ if (path.startsWith("~")) {
22
+ return resolve(homedir(), path.slice(2));
23
+ }
24
+ return resolve(path);
25
+ }
26
+
27
+ function getApiKey(provider: AIProvider, stored: StoredConfig): string {
28
+ // Check stored config first, then env vars
29
+ if (stored.apiKey) return stored.apiKey;
30
+
31
+ switch (provider) {
32
+ case "openai":
33
+ return process.env.OPENAI_API_KEY || "";
34
+ case "anthropic":
35
+ return process.env.ANTHROPIC_API_KEY || "";
36
+ case "google":
37
+ return process.env.GOOGLE_GENERATIVE_AI_API_KEY || "";
38
+ }
39
+ }
40
+
41
+ export async function loadConfig(): Promise<Config> {
42
+ const stored = await loadStoredConfig();
43
+
44
+ const provider = (process.env.AI_PROVIDER || stored.aiProvider || "anthropic") as AIProvider;
45
+
46
+ const rawConfig = {
47
+ aiProvider: provider,
48
+ aiModel: process.env.AI_MODEL || stored.aiModel || getDefaultModel(provider),
49
+ apiKey: getApiKey(provider, stored),
50
+ webhookPort: parseInt(process.env.WEBHOOK_PORT || String(stored.webhookPort || 3000), 10),
51
+ codebasePath: expandPath(process.env.CODEBASE_PATH || stored.codebasePath || "./"),
52
+ kubeConfigPath: expandPath(process.env.KUBE_CONFIG_PATH || stored.kubeConfigPath || "~/.kube"),
53
+ };
54
+
55
+ const result = ConfigSchema.safeParse(rawConfig);
56
+ if (!result.success) {
57
+ const errors = result.error.errors
58
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
59
+ .join("\n");
60
+ throw new Error(`Invalid configuration:\n${errors}`);
61
+ }
62
+
63
+ return result.data;
64
+ }
65
+
66
+ function getDefaultModel(provider: AIProvider): string {
67
+ switch (provider) {
68
+ case "openai":
69
+ return "gpt-4o";
70
+ case "anthropic":
71
+ return "claude-3-5-sonnet-20241022";
72
+ case "google":
73
+ return "gemini-1.5-pro";
74
+ }
75
+ }
76
+
77
+ export function getModelConfig(config: Config) {
78
+ const { aiProvider, aiModel } = config;
79
+
80
+ switch (aiProvider) {
81
+ case "openai":
82
+ return {
83
+ provider: "openai" as const,
84
+ model: aiModel,
85
+ };
86
+ case "anthropic":
87
+ return {
88
+ provider: "anthropic" as const,
89
+ model: aiModel,
90
+ };
91
+ case "google":
92
+ return {
93
+ provider: "google" as const,
94
+ model: aiModel,
95
+ };
96
+ }
97
+ }
package/src/index.ts ADDED
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env bun
2
+ import { loadConfig } from "./config.js";
3
+ import { initSandboxFromConfig } from "./sandbox/bashlet.js";
4
+ import { createMastraInstance, buildIncidentPrompt, getDebuggerAgent } from "./mastra/index.js";
5
+ import { runTUI } from "./tui/app.jsx";
6
+ import { startWebhookServer } from "./server/webhook.js";
7
+ import {
8
+ loadStoredConfig,
9
+ saveStoredConfig,
10
+ getConfigPath,
11
+ maskApiKey,
12
+ type StoredConfig,
13
+ } from "./cli/config.js";
14
+ import type { AIProvider } from "./config.js";
15
+
16
+ interface CliArgs {
17
+ command: "run" | "config";
18
+ configAction?: "set" | "get" | "list" | "path";
19
+ configKey?: string;
20
+ configValue?: string;
21
+ webhookOnly: boolean;
22
+ incident: string | null;
23
+ help: boolean;
24
+ host: boolean;
25
+ }
26
+
27
+ function parseArgs(): CliArgs {
28
+ const args = process.argv.slice(2);
29
+ const result: CliArgs = {
30
+ command: "run",
31
+ webhookOnly: false,
32
+ incident: null,
33
+ help: false,
34
+ host: false,
35
+ };
36
+
37
+ // Check for config subcommand
38
+ if (args[0] === "config") {
39
+ result.command = "config";
40
+ result.configAction = args[1] as "set" | "get" | "list" | "path";
41
+ result.configKey = args[2];
42
+ result.configValue = args[3];
43
+ return result;
44
+ }
45
+
46
+ for (let i = 0; i < args.length; i++) {
47
+ const arg = args[i];
48
+
49
+ if (arg === "--webhook-only" || arg === "-w") {
50
+ result.webhookOnly = true;
51
+ } else if (arg === "--incident" || arg === "-i") {
52
+ result.incident = args[++i] || null;
53
+ } else if (arg === "--help" || arg === "-h") {
54
+ result.help = true;
55
+ } else if (arg === "--host") {
56
+ result.host = true;
57
+ }
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ function printHelp(): void {
64
+ console.log(`
65
+ 🚨 TRIAGENT - AI Kubernetes Debugging Agent
66
+
67
+ USAGE:
68
+ triagent [OPTIONS]
69
+ triagent config <action> [key] [value]
70
+
71
+ OPTIONS:
72
+ -h, --help Show this help message
73
+ -w, --webhook-only Run only the webhook server (no TUI)
74
+ -i, --incident Direct incident input (runs once and exits)
75
+ --host Run commands on host machine (no sandbox)
76
+
77
+ CONFIG COMMANDS:
78
+ triagent config set <key> <value> Set a configuration value
79
+ triagent config get <key> Get a configuration value
80
+ triagent config list List all configuration values
81
+ triagent config path Show config file path
82
+
83
+ CONFIG KEYS:
84
+ aiProvider - AI provider (openai, anthropic, google)
85
+ aiModel - Model ID (e.g., gpt-4o, claude-sonnet-4-20250514)
86
+ apiKey - API key for the provider
87
+ webhookPort - Webhook server port (default: 3000)
88
+ codebasePath - Path to codebase (default: ./)
89
+ kubeConfigPath - Kubernetes config path (default: ~/.kube)
90
+
91
+ MODES:
92
+ Interactive (default):
93
+ Run with no arguments to start the interactive TUI.
94
+ Enter incident descriptions and see real-time debugging output.
95
+
96
+ Webhook Server:
97
+ Use --webhook-only to start an HTTP server that accepts
98
+ incident webhooks from alerting systems.
99
+
100
+ Endpoints:
101
+ POST /webhook/incident - Submit an incident
102
+ GET /investigations/:id - Get investigation results
103
+ GET /health - Health check
104
+
105
+ Direct Input:
106
+ Use --incident "description" for one-shot debugging.
107
+ Example: triagent --incident "checkout pods crashing"
108
+
109
+ ENVIRONMENT VARIABLES:
110
+ AI_PROVIDER - AI provider (openai, anthropic, google)
111
+ AI_MODEL - Model ID (e.g., gpt-4o, claude-3-5-sonnet)
112
+ OPENAI_API_KEY - OpenAI API key
113
+ ANTHROPIC_API_KEY - Anthropic API key
114
+ GOOGLE_GENERATIVE_AI_API_KEY - Google AI API key
115
+ WEBHOOK_PORT - Webhook server port (default: 3000)
116
+ CODEBASE_PATH - Path to codebase (default: ./)
117
+ KUBE_CONFIG_PATH - Kubernetes config path (default: ~/.kube)
118
+
119
+ EXAMPLES:
120
+ # Interactive TUI mode
121
+ triagent
122
+
123
+ # Webhook server mode
124
+ triagent --webhook-only
125
+
126
+ # Direct incident investigation
127
+ triagent -i "API gateway returning 503 errors"
128
+
129
+ # Submit via curl (webhook mode)
130
+ curl -X POST http://localhost:3000/webhook/incident \\
131
+ -H "Content-Type: application/json" \\
132
+ -d '{"title": "API Error", "description": "checkout not working", "severity": "critical"}'
133
+ `);
134
+ }
135
+
136
+ async function runDirectIncident(description: string): Promise<void> {
137
+ console.log("🚨 TRIAGENT - Direct Investigation Mode\n");
138
+ console.log(`Incident: ${description}\n`);
139
+ console.log("Starting investigation...\n");
140
+ console.log("─".repeat(60) + "\n");
141
+
142
+ const agent = getDebuggerAgent();
143
+ const prompt = buildIncidentPrompt({
144
+ title: "Direct Investigation",
145
+ description,
146
+ });
147
+
148
+ try {
149
+ const stream = await agent.stream(prompt, {
150
+ maxSteps: 20,
151
+ onStepFinish: ({ toolCalls }) => {
152
+ if (toolCalls && toolCalls.length > 0) {
153
+ const toolCall = toolCalls[0];
154
+ const toolName = "toolName" in toolCall ? toolCall.toolName : "tool";
155
+ console.log(`\n[Tool: ${toolName}]\n`);
156
+ }
157
+ },
158
+ });
159
+
160
+ for await (const chunk of stream.textStream) {
161
+ process.stdout.write(chunk);
162
+ }
163
+
164
+ console.log("\n\n" + "─".repeat(60));
165
+ console.log("✅ Investigation complete");
166
+ } catch (error) {
167
+ console.error("\n❌ Investigation failed:", error);
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ async function handleConfigCommand(args: CliArgs): Promise<void> {
173
+ const validKeys: (keyof StoredConfig)[] = [
174
+ "aiProvider",
175
+ "aiModel",
176
+ "apiKey",
177
+ "webhookPort",
178
+ "codebasePath",
179
+ "kubeConfigPath",
180
+ ];
181
+
182
+ switch (args.configAction) {
183
+ case "set": {
184
+ if (!args.configKey || args.configValue === undefined) {
185
+ console.error("Usage: triagent config set <key> <value>");
186
+ process.exit(1);
187
+ }
188
+ if (!validKeys.includes(args.configKey as keyof StoredConfig)) {
189
+ console.error(`Invalid key: ${args.configKey}`);
190
+ console.error(`Valid keys: ${validKeys.join(", ")}`);
191
+ process.exit(1);
192
+ }
193
+ const config = await loadStoredConfig();
194
+ let value: string | number = args.configValue;
195
+ if (args.configKey === "webhookPort") {
196
+ value = parseInt(args.configValue, 10);
197
+ }
198
+ (config as Record<string, string | number>)[args.configKey] = value;
199
+ await saveStoredConfig(config);
200
+ console.log(`✅ Set ${args.configKey}`);
201
+ break;
202
+ }
203
+ case "get": {
204
+ if (!args.configKey) {
205
+ console.error("Usage: triagent config get <key>");
206
+ process.exit(1);
207
+ }
208
+ const config = await loadStoredConfig();
209
+ const value = config[args.configKey as keyof StoredConfig];
210
+ if (value === undefined) {
211
+ console.log(`${args.configKey}: (not set)`);
212
+ } else if (args.configKey === "apiKey") {
213
+ console.log(`${args.configKey}: ${maskApiKey(String(value))}`);
214
+ } else {
215
+ console.log(`${args.configKey}: ${value}`);
216
+ }
217
+ break;
218
+ }
219
+ case "list": {
220
+ const config = await loadStoredConfig();
221
+ console.log("Current configuration:\n");
222
+ for (const key of validKeys) {
223
+ const value = config[key];
224
+ if (value === undefined) {
225
+ console.log(` ${key}: (not set)`);
226
+ } else if (key === "apiKey") {
227
+ console.log(` ${key}: ${maskApiKey(String(value))}`);
228
+ } else {
229
+ console.log(` ${key}: ${value}`);
230
+ }
231
+ }
232
+ break;
233
+ }
234
+ case "path": {
235
+ const path = await getConfigPath();
236
+ console.log(path);
237
+ break;
238
+ }
239
+ default:
240
+ console.error("Usage: triagent config <set|get|list|path> [key] [value]");
241
+ process.exit(1);
242
+ }
243
+ }
244
+
245
+ async function main(): Promise<void> {
246
+ const args = parseArgs();
247
+
248
+ if (args.help) {
249
+ printHelp();
250
+ process.exit(0);
251
+ }
252
+
253
+ // Handle config command
254
+ if (args.command === "config") {
255
+ await handleConfigCommand(args);
256
+ process.exit(0);
257
+ }
258
+
259
+ // Load configuration
260
+ let config;
261
+ try {
262
+ config = await loadConfig();
263
+ } catch (error) {
264
+ console.error("❌ Configuration error:", error);
265
+ console.error("\nRun 'triagent config set apiKey <your-key>' to configure.");
266
+ console.error("Or set environment variables (see --help for details).");
267
+ process.exit(1);
268
+ }
269
+
270
+ // Initialize sandbox and Mastra
271
+ try {
272
+ initSandboxFromConfig(config, args.host);
273
+ createMastraInstance(config);
274
+ if (args.host) {
275
+ console.log("⚠️ Running in host mode (no sandbox)\n");
276
+ }
277
+ } catch (error) {
278
+ console.error("❌ Initialization error:", error);
279
+ process.exit(1);
280
+ }
281
+
282
+ // Run in appropriate mode
283
+ if (args.webhookOnly) {
284
+ // Webhook server mode
285
+ await startWebhookServer(config.webhookPort);
286
+ } else if (args.incident) {
287
+ // Direct incident mode
288
+ await runDirectIncident(args.incident);
289
+ } else {
290
+ // Interactive TUI mode
291
+ console.log("Starting Triagent TUI...\n");
292
+ const tui = await runTUI();
293
+
294
+ // Handle graceful shutdown
295
+ process.on("SIGINT", () => {
296
+ tui.shutdown();
297
+ process.exit(0);
298
+ });
299
+
300
+ process.on("SIGTERM", () => {
301
+ tui.shutdown();
302
+ process.exit(0);
303
+ });
304
+ }
305
+ }
306
+
307
+ main().catch((error) => {
308
+ console.error("Fatal error:", error);
309
+ process.exit(1);
310
+ });