valknarandClaude Sonnet 5 d3e3360d6e Remove deprecated url.parse() usage from the custom server
Node flags legacy url.parse() (DEP0169) as having security implications
and recommends the WHATWG URL API instead. The main request handler's
parsedUrl argument to Next's handle() is optional and unused by us, so
that call is dropped entirely (matching Next's own minimal custom-server
example); the WS upgrade path-check now uses `new URL()` instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 19:41:37 +02:00
2026-08-15 18:37:30 +02:00

TriggerShell

A CLI that launches a self-hosted web app for running your shell scripts: define scripts and their parameters in a config file, get a generated form UI, run them asynchronously with live streaming output, and keep a searchable history — all behind optional login, with a full REST/WS API for automation.

Features

  • One config file defines the server, auth, and every script (command, working dir, timeout, and typed/validated variables with defaults and choices).
  • Generated forms — each variable renders as the right control (text, number, slider, select, radio, checkbox, switch, multi-select) based on its type, with client- and server-side validation from the same schema.
  • Safe execution — scripts run via argv arrays, never a shell string. User input can never break out into shell metacharacters.
  • Live monitoring — stdout/stderr and status stream over WebSocket to an in-browser terminal view; cancel a run from the UI or the API.
  • Run history — every run's variables (secrets redacted), resolved command, status, exit code, and full log are persisted in SQLite.
  • Optional auth — built-in username/password sessions and API tokens, toggleable per config.
  • REST + WebSocket API for scripting your script runner.

Quickstart

pip install triggershell         # or: pip install -e . from a checkout
triggershell init                # scaffold triggershell.yml (+ .env for secrets) in the current directory
triggershell users add admin     # create a login (skip if you set auth.enabled: false)
triggershell dev                 # start in dev mode and open the browser

Edit triggershell.yml to add your own scripts (see Configuration below), then run triggershell start for a production build.

Requirements

  • Python >= 3.9
  • Node.js >= 20 (checked by the CLI; not auto-installed)
  • pnpm (auto-provisioned via Corepack if missing and Corepack is available)

Configuration

TriggerShell is driven entirely by one YAML file (default: ./triggershell.yml, override with -c/--config or TRIGGERSHELL_CONFIG_PATH). Secrets referenced via ${VAR} (like auth.sessionSecret) are meant to live in a .env file next to the config, not in the config itself — triggershell init generates both. Minimal example:

server:
  host: 127.0.0.1
  port: 4173

auth:
  enabled: true
  sessionSecret: "${TRIGGERSHELL_SESSION_SECRET}" # >= 32 chars
  users:
    - username: admin
      passwordHash: "${TRIGGERSHELL_USER_ADMIN_PASSWORD_HASH}" # from `triggershell users add`
  tokens: []

database:
  path: .triggershell/triggershell.db
logs:
  dir: .triggershell/logs
  retentionDays: 30

scripts:
  - id: deploy-service
    name: Deploy Service
    command: bash
    args: ["./scripts/deploy.sh"]
    timeoutSeconds: 600
    variables:
      - name: environment
        type: enum
        choices: [staging, production]
        default: staging
        required: true
        passAs: arg
        argName: --env
      - name: dryRun
        type: boolean
        default: false
        passAs: flag
        argName: --dry-run

A full, richly-commented example lives at examples/triggershell.yml (with a matching .env.example), and the complete field-by-field reference is in docs/CONFIG_REFERENCE.md.

Each variable's type (string / number / boolean / enum / multiselect) picks a sensible default UI control; set control explicitly to override it (e.g. slider for a number, radio for an enum). passAs (arg / flag / env / stdin) decides how the validated value reaches the script — always as a discrete argv element or env var, never interpolated into a shell string.

CLI Usage

Command Description
triggershell init [PATH] Scaffold a new config file + .env (--port, --auth/--no-auth, --force)
triggershell validate [-c CONFIG] Validate a config file (fast Python pre-flight + full Node/Zod schema)
triggershell dev [-c CONFIG] [--port] [--host] [--no-browser] Run in development mode (hot reload)
triggershell start [-c CONFIG] [--port] [--host] [--no-browser] [--skip-build] Build (if stale) and run in production mode
triggershell doctor Print environment/config diagnostics
triggershell users add <username> [-c CONFIG] [--inline] Hash a password, store it in .env, and print a ${VAR} snippet for auth.users (--inline prints the raw hash instead)
triggershell users add-token <name> [-c CONFIG] [--inline] Generate an API token, store its hash in .env, and print a ${VAR} snippet for auth.tokens (--inline prints the raw hash instead)

Web App Guide

  • Scripts (/) — every configured script as a card; click through to its run form.
  • Run form (/scripts/:id) — a generated form for the script's variables, validated before submit.
  • Run detail (/runs/:id) — live streaming terminal output, status, exit code, and a Cancel button while the run is active.
  • History (/runs) — every past run, filterable by script/status via the API (?scriptId=, ?status=).

Authentication

Set auth.enabled: false for trusted/local-only use — the app then requires no login at all. When enabled, sessions are signed/encrypted cookies (no server-side session store), and API clients can instead send Authorization: Bearer <token> using a token from users add-token. Passwords are hashed with argon2id; only the hash ever lives in the config file.

API Reference

Full reference with request/response shapes and curl examples: docs/API.md.

Method Path Notes
GET /api/healthz Unauthenticated readiness probe
POST /api/auth/login {username, password} → sets session cookie
POST /api/auth/logout Clears the session
GET /api/auth/session Current auth state
GET /api/scripts List configured scripts
GET /api/scripts/:scriptId Full script schema (variables, UI hints)
POST /api/scripts/:scriptId/runs Start a run: {variables: {...}}
GET /api/runs History: ?scriptId=&status=&limit=&cursor=
GET /api/runs/:runId Single run's status/metadata
POST /api/runs/:runId/cancel Cancel an active run
GET /api/runs/:runId/logs Full or tailed log output (?tail=&download=)
WS /ws/runs Subscribe to a run's live output/status; send cancel

Development

pnpm --dir app install
pnpm --dir app dev            # or: triggershell dev, which wraps this
pnpm --dir app lint
pnpm --dir app typecheck
pnpm --dir app db:studio      # browse the SQLite DB

Repo layout:

triggershell/    Python CLI (launcher/orchestrator only)
app/             Next.js app - all server logic (API, auth, script execution) lives here
examples/        A runnable example config + scripts
docs/            Config/architecture/API reference docs

See docs/ARCHITECTURE.md for how the pieces fit together.

Security Notes

  • Scripts are always spawned with an argv array (execa), never a shell string — variable values can never inject additional shell commands. A script's own command/args may still use shell: true as an explicit, documented opt-in when the script genuinely needs pipes/globs; that reintroduces shell interpretation of passAs: arg values, so prefer passAs: env for anything user-controlled in that case.
  • secret: true variables are masked in the UI and redacted from persisted run records; only the live child process ever sees the real value.
  • The server binds to 127.0.0.1 by default — set server.host explicitly to expose it further.
  • Route Handlers check auth themselves (requireAuth()); proxy.ts is only a fast, optimistic redirect layer, not the security boundary.
  • auth.sessionSecret should live in .env, not the config file — triggershell init sets this up for you. passwordHash/tokenHash are one-way hashes (not the secrets themselves), so storing them directly in the config is reasonably safe — the same trust model as /etc/shadow or .htpasswd — but triggershell users add/add-token store them in .env via ${VAR} by default too, for cases where you don't want them readable by anyone with config access at all. Pass --inline to get the old behavior of printing the raw hash to paste into the config.

License

MIT — see LICENSE.

S
Description
A CLI that launches a self-hosted web app for running your shell scripts.
Readme MIT
659 KiB
Languages
TypeScript 97.8%
CSS 1.9%
JavaScript 0.3%