feat: full Komodo API coverage — 11 tools, ~336 endpoints

Expand from 7 tools to 11 tools covering 100% of the Komodo v2.3.2 API:

New tools:
- komodo_summary: resource summaries/stats
- komodo_inspect: Docker object inspection (16 types)
- komodo_search_logs: log search with query
- komodo_list_detail: extended listing (27 types)

Expanded existing tools:
- 12 → 21 resource types (action, repo, builder, swarm, variable, user_group, alert, terminal, git_provider_account)
- 37 → 100+ execute operations
- komodo_logs: 4 resource types with proper endpoints

Other changes:
- API key auth support (X-Api-Key / X-Api-Secret headers)
- 30s fetch timeout + structured error handling
- Updated docs: references/api.md (483 lines), README.md (238 lines)
This commit is contained in:
2026-09-07 21:10:19 +00:00
parent e5929a940e
commit 2402b3b7fa
11 changed files with 1139 additions and 257 deletions
+53 -10
View File
@@ -1,16 +1,44 @@
import { KomodoApiError } from "./errors.js";
export class KomodoClient {
private baseUrl: string;
private username: string;
private password: string;
private apiKey: string | null = null;
private apiSecret: string | null = null;
private token: string | null = null;
private useApiKey: boolean;
constructor() {
this.baseUrl = process.env.KOMODO_BASE_URL || process.env.KOMODO_URL || "http://10.10.2.114:9120";
this.baseUrl =
process.env.KOMODO_BASE_URL || process.env.KOMODO_URL || "http://10.10.2.114:9120";
this.username = process.env.KOMODO_USERNAME || "";
this.password = process.env.KOMODO_PASSWORD || "";
this.apiKey = process.env.KOMODO_API_KEY || null;
this.apiSecret = process.env.KOMODO_API_SECRET || null;
this.useApiKey = !!(this.apiKey && this.apiSecret);
}
/**
* Returns the appropriate auth headers based on the configured auth method.
* API key auth (X-Api-Key / X-Api-Secret) is preferred when credentials are set;
* otherwise falls back to JWT Bearer token.
*/
private async getAuthHeaders(): Promise<Record<string, string>> {
if (this.useApiKey) {
return {
"X-Api-Key": this.apiKey!,
"X-Api-Secret": this.apiSecret!,
};
}
await this.ensureAuth();
return {
Authorization: `Bearer ${this.token}`,
};
}
private async ensureAuth(): Promise<void> {
if (this.useApiKey) return; // No login needed for API key auth
if (this.token) return;
await this.login();
}
@@ -23,6 +51,7 @@ export class KomodoClient {
username: this.username,
password: this.password,
}),
signal: AbortSignal.timeout(30000),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
@@ -40,31 +69,45 @@ export class KomodoClient {
const url = `${this.baseUrl}/${route}/${requestName}`;
const attempt = async (token: string): Promise<Response> => {
const attempt = async (
authHeaders: Record<string, string>,
): Promise<Response> => {
return fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...authHeaders,
},
body: JSON.stringify(params),
signal: AbortSignal.timeout(30000),
});
};
let res = await attempt(this.token!);
const authHeaders = await this.getAuthHeaders();
let res = await attempt(authHeaders);
// Auto-refresh on 401
if (res.status === 401) {
// Auto-refresh on 401 (JWT only)
if (res.status === 401 && !this.useApiKey) {
this.token = null;
await this.login();
res = await attempt(this.token!);
res = await attempt({ Authorization: `Bearer ${this.token}` });
}
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(
`Komodo RPC ${route}/${requestName} failed (${res.status}): ${text}`,
);
let message = `Komodo RPC ${route}/${requestName} failed (${res.status}): ${text}`;
try {
const body = JSON.parse(text) as {
error?: string;
trace?: unknown[];
};
if (body.error) {
message = `Komodo RPC ${route}/${requestName} failed (${res.status}): ${body.error}`;
}
} catch {
// Not JSON, use raw text
}
throw new KomodoApiError(message, res.status, route, requestName);
}
const text = await res.text();