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
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env node
const args = process.argv.slice(2);
function getArg(name) {
const index = args.indexOf(name);
return index === -1 ? undefined : args[index + 1];
}
const targetDir = getArg("--dir");
const olderThanDays = getArg("--days");
const types = getArg("--types");
console.log(`Scanning ${targetDir} for files older than ${olderThanDays} days (types: ${types})`);
if (process.env.CLEANUP_API_KEY) {
console.log("Using configured external API key.");
}
console.log("No matching files found. Cleanup complete.");
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
env=""
replicas=""
dry_run="false"
while [[ $# -gt 0 ]]; do
case "$1" in
--env) env="$2"; shift 2 ;;
--replicas) replicas="$2"; shift 2 ;;
--dry-run) dry_run="true"; shift ;;
*) echo "unknown argument: $1" >&2; exit 1 ;;
esac
done
echo "Deploying to '${env}' with ${replicas} replicas (dry_run=${dry_run})"
if [[ -n "${SLACK_CHANNEL:-}" ]]; then
echo "Would notify Slack channel: ${SLACK_CHANNEL}"
fi
for i in $(seq 1 5); do
echo "[${i}/5] working..."
sleep 1
done
echo "Deploy complete."
+116
View File
@@ -0,0 +1,116 @@
# TriggerShell example configuration.
# See docs/CONFIG_REFERENCE.md for the full field reference.
server:
host: 127.0.0.1
port: 4173
auth:
# Set to false for trusted/local-only use - no login required.
enabled: true
# Must be >= 32 characters. Generate one with `triggershell init` or `openssl rand -hex 32`.
sessionSecret: "${TRIGGERSHELL_SESSION_SECRET:-dev-only-insecure-session-secret-change-me-before-deploying}"
sessionTtlHours: 12
users:
# Generate with `triggershell users add`. This demo hash is for the password "admin" -
# change it before using this example anywhere but your own machine.
- username: admin
passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$zQavMjKmTLUlSSy8e7doRQ$s6uQeh9rc4Jl+l0GHepH4S8zhcqGfIAbvrxk9F3rT4U"
tokens:
# Generate with `triggershell users add-token`. This demo token is "ci-bot-demo-token".
- name: ci-bot
tokenHash: "sha256:131709125e3c04e7c1dd59bc840a997a772ba54ff7448184c558ebc550d9d245"
database:
path: .triggershell/triggershell.db
logs:
dir: .triggershell/logs
retentionDays: 30
scripts:
- id: deploy-service
name: Deploy Service
description: Deploys the given service to the target environment.
command: bash
args: ["./scripts/deploy.sh"]
cwd: ./
timeoutSeconds: 600
variables:
- name: environment
label: Target Environment
type: enum
choices: [staging, production]
default: staging
required: true
control: select
passAs: arg
argName: --env
- name: replicas
label: Replica Count
type: number
default: 3
min: 1
max: 20
control: slider
passAs: arg
argName: --replicas
- name: dryRun
label: Dry Run
type: boolean
default: false
control: checkbox
passAs: flag
argName: --dry-run
- name: notifySlack
label: Notify Slack Channel
type: string
required: false
pattern: '^#[a-z0-9-]+$'
passAs: env
envName: SLACK_CHANNEL
- id: cleanup-logs
name: Cleanup Old Logs
description: Removes log files older than N days from a target directory.
command: node
args: ["./scripts/cleanup.js"]
cwd: ./
timeoutSeconds: 120
variables:
- name: targetDir
label: Target Directory
type: string
required: true
default: /var/log/app
passAs: arg
argName: --dir
- name: olderThanDays
label: Older Than (days)
type: number
default: 14
min: 1
control: number
passAs: arg
argName: --days
- name: fileTypes
label: File Types
type: multiselect
choices: [log, txt, gz, json]
default: [log, gz]
passAs: arg
argName: --types
joinWith: ","
- name: apiKey
label: External API Key
type: string
secret: true
required: true
passAs: env
envName: CLEANUP_API_KEY