Initial implementation of TriggerShell

A Python CLI (typer) that bootstraps Node/pnpm and launches a Next.js 16 web
app for running configured shell scripts: YAML config validated by a shared
Zod schema, dynamic per-script forms mapped to shadcn controls, argv-safe
execa execution with live WebSocket streaming, SQLite/Drizzle run history,
and optional argon2 session + API token auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 18:37:30 +02:00
co-authored by Claude Sonnet 5
commit ced99a8e75
117 changed files with 17367 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import type { ServerMessage } from "@/lib/ws/protocol";
export function useRunSocket(
runId: string,
onMessage: (message: ServerMessage) => void,
) {
const wsRef = useRef<WebSocket | null>(null);
const [connected, setConnected] = useState(false);
const onMessageRef = useRef(onMessage);
useEffect(() => {
onMessageRef.current = onMessage;
}, [onMessage]);
useEffect(() => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/runs`);
wsRef.current = ws;
ws.onopen = () => {
setConnected(true);
ws.send(JSON.stringify({ type: "subscribe", runId }));
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data) as ServerMessage;
if (message.runId === runId) onMessageRef.current(message);
} catch {
// ignore malformed frames
}
};
ws.onclose = () => setConnected(false);
return () => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "unsubscribe", runId }));
}
ws.close();
};
}, [runId]);
const cancel = useCallback(() => {
wsRef.current?.send(JSON.stringify({ type: "cancel", runId }));
}, [runId]);
return { connected, cancel };
}