MVP: Go daemon, password auth, persistent PTY terminals, React/xterm UI, PWA

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-17 10:25:35 -07:00
co-authored by Claude Fable 5.1
parent 190604247f
commit 436732dac0
35 changed files with 3576 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
export class Unauthenticated extends Error {}
async function req<T>(method: string, path: string, body?: unknown): Promise<T> {
const r = await fetch(path, {
method,
credentials: 'same-origin',
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
body: body !== undefined ? JSON.stringify(body) : undefined,
})
if (r.status === 401) throw new Unauthenticated()
if (!r.ok) {
let msg = r.statusText
try { msg = (await r.json()).error ?? msg } catch { /* ignore */ }
throw new Error(msg)
}
if (r.status === 204) return undefined as T
return r.json()
}
export interface Me { host: string; user: string; version: string; load: string; uptime: number }
export interface Term { id: string; name: string; running: boolean; exit_code: number; created: string; clients: number }
export const api = {
login: (password: string) => req<void>('POST', '/api/login', { password }),
logout: () => req<void>('POST', '/api/logout'),
me: () => req<Me>('GET', '/api/me'),
terminals: () => req<Term[]>('GET', '/api/terminals'),
createTerminal: (name?: string) => req<{ id: string; name: string }>('POST', '/api/terminals', { name }),
renameTerminal: (id: string, name: string) => req<void>('PATCH', `/api/terminals/${id}`, { name }),
deleteTerminal: (id: string) => req<void>('DELETE', `/api/terminals/${id}`),
}
export function wsURL(id: string): string {
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
return `${proto}//${location.host}/api/terminals/${id}/attach`
}