Files
triggershell/README.md
T
valknarandClaude Sonnet 5 3f379ca2ac Flatten the repo: move everything out of app/ to the root
Now that the CLI and the Next.js app are one package, nesting it inside
app/ served no purpose - the repo root itself becomes the published
npm package. Merges app/.gitignore and app/README.md into the root
versions, drops the now-duplicate app/LICENSE, and updates path
references (README, docs/ARCHITECTURE.md, docs/CONFIG_REFERENCE.md,
package.json's repository.directory) that assumed the app/ nesting.

Also fixes a real bug this surfaced: the in-app docs viewer resolved
docs/ relative to process.cwd(), which only worked by accident when the
CLI happened to be invoked from app/'s parent directory. A first attempt
at fixing it with import.meta.dirname broke instead, for the same
cross-module-graph reason config-path resolution already documented -
Next compiles Route Handlers through a separate module graph that
doesn't preserve source-relative import.meta paths. Fixed by exposing
the app root via TRIGGERSHELL_APP_ROOT (set once in server.ts, where
import.meta *does* resolve correctly), the same pattern already used
for TRIGGERSHELL_CONFIG_PATH.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:14:01 +02:00

9.1 KiB

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

npm install -g triggershell      # or: npx triggershell <command> for one-off use
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 start               # start the app and open the browser

Edit triggershell.yml to add your own scripts (see Configuration below), then re-run triggershell start.

The triggershell package isn't published to npm yet. Until it is, build and link a local copy instead: pnpm install && pnpm build && pnpm link --global.

Requirements

  • Node.js >= 20 — the only thing you need installed. Everything else triggershell needs ships inside the package itself and is resolved automatically when you install it.

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 against the full schema
triggershell start [-c CONFIG] [--port] [--host] [--no-browser] Run the web app
triggershell doctor [-c CONFIG] 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)
triggershell service install [--system] Install a systemd unit that runs triggershell start (per-user by default, Linux only)
triggershell service uninstall [--system] Stop, disable, and remove the systemd unit
triggershell service status [--system] Show the systemd unit's status

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

This is the workflow for working on TriggerShell itself, not for installing/running it — it bypasses the CLI entirely and talks to the app's own scripts directly, with hot reload. The app is still not meant to be run standalone with next dev/next start, since it needs the custom server (server.ts) for the WebSocket endpoint — pnpm dev/pnpm start below cover that:

pnpm install
pnpm dev            # tsx watch server.ts - reads TRIGGERSHELL_CONFIG_PATH from the environment
pnpm lint
pnpm typecheck
pnpm test            # CLI unit tests (src/cli/**/*.test.ts)
pnpm db:studio       # browse the SQLite DB

Repo layout — the repo root itself is the published npm package (Next.js app + the triggershell CLI in bin//src/cli/), alongside:

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.