Initial commit: Agentic OS troubleshooting platform

Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 22:11:07 +03:00
commit 6185b9b85a
126 changed files with 14565 additions and 0 deletions

80
frontend/lib/api.ts Normal file
View File

@@ -0,0 +1,80 @@
export const API_BASE =
process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
const TOKEN_KEY = "agentic_token";
export function getToken(): string | null {
if (typeof window === "undefined") return null;
return window.localStorage.getItem(TOKEN_KEY);
}
export function setToken(token: string | null) {
if (typeof window === "undefined") return;
if (token) window.localStorage.setItem(TOKEN_KEY, token);
else window.localStorage.removeItem(TOKEN_KEY);
}
export class ApiError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
export async function api<T = any>(
path: string,
options: RequestInit = {}
): Promise<T> {
const headers = new Headers(options.headers);
const token = getToken();
if (token) headers.set("Authorization", `Bearer ${token}`);
if (!headers.has("Content-Type") && options.body && !(options.body instanceof FormData)) {
headers.set("Content-Type", "application/json");
}
const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
if (res.status === 401 && typeof window !== "undefined") {
setToken(null);
if (!window.location.pathname.startsWith("/login")) {
window.location.href = "/login";
}
}
if (!res.ok) {
let detail = res.statusText;
try {
const body = await res.json();
detail = body.detail || detail;
} catch {}
throw new ApiError(res.status, typeof detail === "string" ? detail : JSON.stringify(detail));
}
if (res.status === 204) return undefined as T;
return res.json();
}
export async function login(email: string, password: string) {
const form = new URLSearchParams();
form.set("username", email);
form.set("password", password);
const res = await fetch(`${API_BASE}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: form.toString(),
});
if (!res.ok) {
let detail = "Login failed";
try {
detail = (await res.json()).detail || detail;
} catch {}
throw new ApiError(res.status, detail);
}
return res.json() as Promise<{ access_token: string; role: string; email: string }>;
}
export function wsUrl(path: string): string {
const base = API_BASE.replace(/^http/, "ws");
const token = getToken();
const sep = path.includes("?") ? "&" : "?";
return `${base}${path}${sep}token=${encodeURIComponent(token || "")}`;
}
export const fetcher = (path: string) => api(path);