From 7519de4e56323230d06cfc64b00df52339eb2434 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Fri, 27 Mar 2026 23:23:43 +0000 Subject: Initial project setup Add modal-shell project with .agent/ config, mode stubs, ms dispatcher, bare repo at /site/git.terst.org/repos/modal-shell.git, and ADR-001. --- .agent/coding_standards.md | 65 +++++++++++++++++++++++++++++++ .agent/config.md | 88 ++++++++++++++++++++++++++++++++++++++++++ .agent/design.md | 71 ++++++++++++++++++++++++++++++++++ .agent/mission.md | 21 ++++++++++ .agent/narrative.md | 15 +++++++ .agent/preferences.md | 22 +++++++++++ .agent/worklog.md | 26 +++++++++++++ .envrc | 8 ++++ .gitignore | 11 ++++++ CLAUDE.md | 9 +++++ docs/adr/001-mode-as-intent.md | 71 ++++++++++++++++++++++++++++++++++ modes/base.sh | 53 +++++++++++++++++++++++++ modes/build.sh | 29 ++++++++++++++ modes/db.sh | 24 ++++++++++++ modes/logs.sh | 26 +++++++++++++ modes/reset.sh | 30 ++++++++++++++ ms | 28 ++++++++++++++ 17 files changed, 597 insertions(+) create mode 100644 .agent/coding_standards.md create mode 100644 .agent/config.md create mode 100644 .agent/design.md create mode 100644 .agent/mission.md create mode 100644 .agent/narrative.md create mode 100644 .agent/preferences.md create mode 100644 .agent/worklog.md create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 docs/adr/001-mode-as-intent.md create mode 100755 modes/base.sh create mode 100755 modes/build.sh create mode 100755 modes/db.sh create mode 100755 modes/logs.sh create mode 100755 modes/reset.sh create mode 100755 ms diff --git a/.agent/coding_standards.md b/.agent/coding_standards.md new file mode 100644 index 0000000..955ec35 --- /dev/null +++ b/.agent/coding_standards.md @@ -0,0 +1,65 @@ +# Coding Standards — modal-shell + +## Shell + +- **Default shell:** bash. Shebang is always `#!/usr/bin/env bash`. +- **Strict mode:** Every file starts with `set -euo pipefail`. No exceptions. +- **No bashisms in base.sh:** `base.sh` must also be compatible with zsh (users may source it from their zsh config). +- **Functions over repetition:** If the same logic appears in two modes, move it to `base.sh`. +- **No global side effects in sourced files:** `base.sh` sets env vars; it does not print output, start processes, or modify files. + +## Naming + +- Mode files: lowercase, hyphen-separated, `.sh` suffix. e.g. `db.sh`, `build.sh`, `reset.sh`. +- Variables in `base.sh`: `UPPER_SNAKE_CASE`. Exported. +- Local variables in mode files: `lower_snake_case`. Use `local` inside functions. +- The dispatcher: `ms` (no extension, executable). + +## Mode File Structure + +```sh +#!/usr/bin/env bash +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/base.sh" + +# mode logic here +``` + +## Confirmation Pattern for Destructive Modes + +```sh +echo "This will destroy and rebuild the environment." +read -rp "Continue? [y/N] " confirm +[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; } +``` + +## Error Handling + +- Let `set -e` do the work. Do not swallow errors with `|| true` unless intentional and commented. +- On expected failure paths, print a helpful message to stderr before exiting: `echo "error: " >&2` +- Do not use `exit 0` at the end of scripts — implicit success is fine. + +## Testing + +- Syntax check: `bash -n modes/.sh` before committing any mode file. +- Dry-run: modes that exec into an environment can be tested with `bash -c 'source modes/base.sh && echo $PROJECT_ROOT'`. +- No automated test framework required for this project's scope. Manual verification is acceptable. + +## The Dispatcher (`ms`) + +- Must stay under 40 lines. +- No logic beyond: find the modes directory, match argument to filename, exec. +- If no argument: list available modes (strip `.sh`, sort). +- If unknown mode: print error to stderr, exit 1. +- Never source the mode file — always exec it (so the mode can itself exec into a shell without forking). + +## What Not to Do + +- No `eval`. +- No `set +e` blocks. +- No hardcoded paths outside of `base.sh`. +- No secrets in any file tracked by git. +- No modes that call other modes. diff --git a/.agent/config.md b/.agent/config.md new file mode 100644 index 0000000..af6c1fd --- /dev/null +++ b/.agent/config.md @@ -0,0 +1,88 @@ +# Agent Config — modal-shell +*Main entry point. Read this first, every session.* + +## Quick Links + +| File | Purpose | +|---|---| +| `worklog.md` | Session state: current focus, recently completed, next steps | +| `design.md` | Architecture overview and component map | +| `coding_standards.md` | Shell scripting conventions and testing | +| `mission.md` | Project goals and agent role | +| `narrative.md` | Background and history | +| `preferences.md` | User-specific preferences | +| `docs/adr/` | Architectural decision records | + +--- + +## Core Mandates + +### Safety Protocol + +1. **Inquiry-Only Default** — Treat every message as research or analysis unless it is an explicit, imperative instruction. +2. **Zero Unsolicited Changes** — Never modify files or scripts based on assumptions about intent. +3. **Strategy Checkpoint** — Research first. Present approach. Wait for explicit approval before touching anything outside `.agent/`. +4. **Verify State First** — Check current state (`ls`, `git status`, `cat` the relevant file) before proposing changes. +5. **Destructive Operations Require Confirmation** — Any change that deletes, overwrites, or resets state must be confirmed explicitly. + +### Living Documentation + +1. **Update `.agent/` continuously** — When decisions are made, patterns emerge, or preferences are revealed, update the relevant file immediately. +2. **Worklog at start and end** — `worklog.md` reflects what was done this session and what comes next. +3. **ADRs for architecture decisions** — Any significant structural or tooling decision gets an entry in `docs/adr/`. + +--- + +## Workflow + +``` +Research → Strategy → Execution +``` + +1. **Research** — Read the relevant mode files and base.sh. Understand the current pattern before proposing changes. +2. **Strategy** — Describe the change and why. For anything beyond a trivial edit, wait for a "go ahead." +3. **Execution** + - Make the change. + - Verify it works: run `ms ` in a subshell or with `bash -n` for syntax. + - Update `worklog.md`. + +--- + +## Project Layout + +``` +modal-shell/ + ms # dispatcher (entry point for users) + .envrc # direnv: auto-sources base.sh on cd + modes/ + base.sh # sourced by all modes; env, secrets, PROJECT_ROOT + db.sh # database shell mode + build.sh # build pipeline mode + reset.sh # destructive rebuild mode (prompts for confirmation) + logs.sh # log streaming mode + docs/ + adr/ # architectural decision records + .agent/ # agent configuration (this directory) + CLAUDE.md # repo root entry point +``` + +--- + +## Essential Commands + +| Command | Action | +|---|---| +| `ms ` | Enter a named mode | +| `ms` (no args) | List available modes | +| `bash -n modes/.sh` | Syntax-check a mode file | +| `direnv allow` | Trust `.envrc` after changes | +| `source modes/base.sh` | Manually load base env | + +--- + +## What NOT to Do + +- Do not add logic to the `ms` dispatcher beyond finding and executing mode files. +- Do not store secrets in any file in this repo. +- Do not create modes that silently do destructive things. +- Do not introduce dependencies that aren't already in the base environment. diff --git a/.agent/design.md b/.agent/design.md new file mode 100644 index 0000000..f17a80b --- /dev/null +++ b/.agent/design.md @@ -0,0 +1,71 @@ +# Architecture — modal-shell + +## Overview + +modal-shell is a three-layer system: + +``` +[ ms dispatcher ] → [ mode file ] → [ base.sh + runtime ] +``` + +1. **`ms`** — dumb dispatcher. Reads `modes/` directory, matches argument to filename, executes. +2. **Mode files** — encode intent. Each sources `base.sh` and then does exactly one thing. +3. **`base.sh`** — shared environment foundation. Sets `PROJECT_ROOT`, loads secrets, configures toolchain. + +## Component Map + +| Component | Role | +|---|---| +| `ms` | Dispatcher. Lists modes if no arg. Exits non-zero on unknown mode. | +| `modes/base.sh` | Foundation. Sourced by all modes. Never executed directly. | +| `modes/db.sh` | Opens an interactive database shell. | +| `modes/build.sh` | Runs the build pipeline to completion. | +| `modes/reset.sh` | Destroys and rebuilds the environment. Requires confirmation. | +| `modes/logs.sh` | Streams runtime logs. | +| `.envrc` | direnv hook. Sources `base.sh` automatically on `cd`. | + +## Composition Model + +Every mode follows this pattern: + +```sh +#!/usr/bin/env bash +set -euo pipefail +source "$(dirname "$0")/base.sh" + +# mode-specific logic here +``` + +Modes do not call other modes. Modes do not share state with each other. If two modes need the same sub-behavior, that behavior belongs in `base.sh`. + +## Data Flow + +``` +user: ms db + ms → finds modes/db.sh + modes/db.sh → source base.sh (sets PROJECT_ROOT, loads env) + modes/db.sh → exec psql / mysql / redis-cli (as configured in base.sh) + user lands in DB shell with correct credentials +``` + +## Secrets + +Secrets are never stored in files. `base.sh` loads them at runtime via: +- `op run --` (1Password CLI) — preferred +- `doppler run --` — alternative +- `.env` file sourced locally — acceptable for non-sensitive local dev only, never committed + +## Adding a New Mode + +1. Create `modes/.sh` +2. Start with the standard header (shebang + `set -euo pipefail` + `source base.sh`) +3. Implement the mode +4. Add a brief comment at the top describing intent +5. If destructive: add a confirmation prompt before proceeding +6. Run `bash -n modes/.sh` to verify syntax +7. Update `worklog.md` + +## ADRs + +See `docs/adr/` for decisions on: +- [ADR-001](../docs/adr/001-mode-as-intent.md) — Mode-as-intent architecture diff --git a/.agent/mission.md b/.agent/mission.md new file mode 100644 index 0000000..96cf618 --- /dev/null +++ b/.agent/mission.md @@ -0,0 +1,21 @@ +# Mission + +## Core Purpose + +modal-shell is a lightweight, portable shell environment dispatcher. It encodes *intent* at the entry point — instead of remembering commands, you invoke a named mode and land directly in the right context for the task at hand. + +## Problem Being Solved + +Shell environments accumulate ambient state. Developers remember incantations. Onboarding is tribal knowledge. modal-shell replaces all of that with a `modes/` directory and a dumb dispatcher: if you can name what you're doing, you can enter that mode. + +## Strategic Values + +- **Composability over configuration** — modes layer on top of a shared base; no duplication. +- **Explicitness** — every mode declares its own environment. Nothing is implicit. +- **Portability** — works locally, in CI, in containers. Same modes everywhere. +- **Minimal footprint** — no framework, no daemon, no state. Just shell. +- **Safety for destructive operations** — modes that destroy things ask before proceeding. + +## Agent Role + +When working on this project, the agent is a careful shell scripter and tool designer. Changes should be surgical and composable. New modes follow the established pattern. The dispatcher stays dumb. Complexity lives in modes, never in the framework. diff --git a/.agent/narrative.md b/.agent/narrative.md new file mode 100644 index 0000000..f14998e --- /dev/null +++ b/.agent/narrative.md @@ -0,0 +1,15 @@ +# Narrative + +## Origin + +modal-shell began as four zsh scripts in a Vagrant-based PHP project circa ~2014. The original `up.zsh` booted a VM and SSH'd in; sibling scripts (`mysql.zsh`, `phing.zsh`) set `VAGRANT_OPTS` before delegating to it. The pattern was right — intent-at-entry-point — but the implementation was buried in a single project and tied entirely to Vagrant and Phing. + +## The Insight Worth Keeping + +The composition model — "set context, call the base" — is sound. What was wrong was everything specific to the stack: Vagrant, Phing, hardcoded paths, unprotected env vars for secrets, no confirmation on destructive operations. + +The modal concept maps cleanly to modern tooling. `vagrant up && vagrant ssh` becomes `docker compose exec` or `nix develop`. `VAGRANT_OPTS` becomes proper mode files with sourced base environments. Phing becomes `just` or `make`. + +## Current Phase + +Greenfield rewrite. The original repo (`thepeterstone/modal-shell`) is reference only. The new implementation lives at `/workspace/modal-shell` and follows the project conventions established here. diff --git a/.agent/preferences.md b/.agent/preferences.md new file mode 100644 index 0000000..a882e39 --- /dev/null +++ b/.agent/preferences.md @@ -0,0 +1,22 @@ +# Preferences + +*Living record. Update when new preferences are revealed.* + +## Interaction Style + +- Concise responses. No trailing summaries restating what was just done. +- Present strategy before execution for anything non-trivial. +- Ask when ambiguous rather than assuming. + +## Technical Preferences + +- bash over zsh for portability, but `base.sh` must be zsh-compatible for direct sourcing. +- `direnv` for automatic environment loading — preferred over manual `source` steps. +- Secrets via 1Password CLI (`op run --`) where available. +- `just` preferred over `make` for new task runners, but don't add it as a dep if the project doesn't already use it. + +## What to Avoid + +- Adding frameworks or dependencies that aren't already present. +- Clever shell tricks that obscure intent. Readable over terse. +- Modes that do more than one thing. diff --git a/.agent/worklog.md b/.agent/worklog.md new file mode 100644 index 0000000..7ef71c7 --- /dev/null +++ b/.agent/worklog.md @@ -0,0 +1,26 @@ +# Worklog + +## Current Focus + +Initial project setup. Directory structure, agent config files, mode stubs, and dispatcher are being created. + +--- + +## Session: 2026-03-27 — Project Initialization + +### Completed +- Created project at `/workspace/modal-shell` +- Established `.agent/` directory with full config suite +- Written ADR-001 (mode-as-intent architecture) +- Created `modes/` directory with `base.sh`, `db.sh`, `build.sh`, `reset.sh`, `logs.sh` stubs +- Created `ms` dispatcher script +- Created `.envrc` for direnv integration +- Created `.gitignore` + +### Next Steps +- Fill in `base.sh` for the specific target project/environment +- Customize mode stubs to match actual toolchain (`psql`/`mysql`, `docker compose`/`nix`, etc.) +- Run `direnv allow` after reviewing `.envrc` +- Add project-specific modes as needed + +--- diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3c594fc --- /dev/null +++ b/.envrc @@ -0,0 +1,8 @@ +# direnv — auto-sources base.sh when you cd into this directory +# Run: direnv allow +# See: https://direnv.net + +source_up_if_exists # inherit parent .envrc if present + +# Source the shared base environment +source "$(pwd)/modes/base.sh" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07ed8a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Secrets — never commit +.env +.env.* +!.env.example + +# direnv +.direnv/ + +# OS +.DS_Store +Thumbs.db diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..59922b4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,9 @@ +# modal-shell + +Agent entry point. Read `.agent/config.md` before doing anything else. + +- **Config / Rules:** `.agent/config.md` +- **Session State:** `.agent/worklog.md` +- **Architecture:** `.agent/design.md` +- **Coding Standards:** `.agent/coding_standards.md` +- **ADRs:** `docs/adr/` diff --git a/docs/adr/001-mode-as-intent.md b/docs/adr/001-mode-as-intent.md new file mode 100644 index 0000000..c50d28b --- /dev/null +++ b/docs/adr/001-mode-as-intent.md @@ -0,0 +1,71 @@ +# ADR-001: Mode-as-Intent Architecture + +**Status:** Accepted +**Date:** 2026-03-27 + +--- + +## Context + +Shell environments accumulate ambient state. Developers memorize incantations. Onboarding is tribal knowledge. The original modal-shell (circa 2014) had the right instinct — encode intent at the entry point — but buried it inside a Vagrant/PHP project with one-level composition via `VAGRANT_OPTS`. + +The question: what is the minimal, portable, modern expression of the same idea? + +## Decision + +modal-shell is organized around **named modes**. Each mode is a shell script that: + +1. Sources a shared `base.sh` (environment foundation) +2. Does exactly one thing (opens a DB shell, runs a build, streams logs, etc.) + +A dumb dispatcher (`ms`) maps a name to a file in `modes/` and executes it. + +``` +[ ms ] → [ modes/.sh ] → [ source base.sh + do the thing ] +``` + +### What This Replaces + +| Old pattern | New pattern | +|---|---| +| Remember the right `vagrant ssh` incantation | `ms db` | +| Set `VAGRANT_OPTS` before calling `up.zsh` | Mode file sets its own context, sources base | +| Hardcoded paths and VM-specific env vars | `PROJECT_ROOT` in `base.sh`, configurable | +| Secrets in env var assignments in scripts | Secrets loaded at runtime via `op run --` | +| Destructive script with no confirmation | `reset.sh` prompts before proceeding | +| Vagrant + Phing | Any runtime: Docker, nix, k8s, bare metal | + +### Why Not Use an Existing Tool? + +- **direnv alone:** handles automatic env loading, not intent-encoded entry points. +- **`just` / `make` alone:** task runners, not environment launchers. Good for build steps, not for "drop me into a database shell." +- **devcontainer:** heavy, IDE-coupled. Good for standardizing dev environments, not for mode dispatch within one. +- **A framework:** violates the minimal-footprint principle. The dispatcher is 30 lines of shell. There is nothing to install. + +The right answer is composing these tools: `direnv` handles automatic base env, `ms` handles explicit mode entry, `just`/`make` handles build tasks invoked from within modes. + +## Consequences + +- **Positive:** Any project member who knows `ms ` knows all available modes. +- **Positive:** Modes are self-documenting — the filename is the intent, the file is the implementation. +- **Positive:** Adding a mode requires creating one file. Removing one requires deleting it. +- **Positive:** Works identically in CI — `ms build` in a CI job is the same as locally. +- **Negative:** `base.sh` becomes a coordination point. It must be kept minimal; project-specific customization should be documented clearly. +- **Negative:** No state between modes. If a mode needs to know what another mode set up, that coordination must happen externally (e.g., a running container, a written file). + +## Alternatives Considered + +**1. Single script with subcommands (`ms db`, `ms build` all in one file)** +Rejected. Concentrates all mode logic in one file; adding a mode requires editing the dispatcher; violates the "dispatcher stays dumb" principle. + +**2. Environment variables to select mode (`MODE=db ms`)** +Rejected. Opaque, harder to tab-complete, non-idiomatic. + +**3. Aliases per mode in shell config** +Rejected. Not portable across machines, not version-controlled with the project, requires per-machine setup. + +## Related + +- `modes/base.sh` — the shared foundation +- `ms` — the dispatcher +- `.envrc` — direnv integration diff --git a/modes/base.sh b/modes/base.sh new file mode 100755 index 0000000..e914d96 --- /dev/null +++ b/modes/base.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# base.sh — sourced by all modes. Sets shared environment. Never executed directly. +# +# Customize this file for your specific project and toolchain. +# Do not add mode-specific logic here. + +# ── Project root ────────────────────────────────────────────────────────────── +# Resolves to the repo root regardless of where ms is invoked from. +export PROJECT_ROOT +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# ── Secrets ─────────────────────────────────────────────────────────────────── +# Load secrets from 1Password CLI if available, otherwise fall back to .env. +# Never commit .env. Never hardcode credentials here. +# +# Option A: 1Password CLI (preferred) +# export OP_ACCOUNT="my.1password.com" +# eval "$(op signin)" +# +# Option B: doppler +# eval "$(doppler secrets download --no-file --format env)" +# +# Option C: local .env (dev only, never commit) +if [[ -f "$PROJECT_ROOT/.env" ]]; then + # shellcheck source=/dev/null + set -a + source "$PROJECT_ROOT/.env" + set +a +fi + +# ── Database ────────────────────────────────────────────────────────────────── +# Set these via secrets above; stubs provided for clarity. +export DB_HOST="${DB_HOST:-localhost}" +export DB_PORT="${DB_PORT:-5432}" +export DB_NAME="${DB_NAME:-}" +export DB_USER="${DB_USER:-}" +export DB_PASS="${DB_PASS:-}" + +# ── Runtime ─────────────────────────────────────────────────────────────────── +# Configure the runtime environment your project uses. +# Examples — uncomment and adjust as needed: +# +# Docker Compose: +# export COMPOSE_FILE="$PROJECT_ROOT/docker-compose.yml" +# +# nix: +# # (handled by nix develop — base.sh runs inside the devshell) +# +# Tool versions (if using mise/asdf): +# export MISE_TOML="$PROJECT_ROOT/.mise.toml" + +# ── Path additions ──────────────────────────────────────────────────────────── +export PATH="$PROJECT_ROOT/bin:$PATH" diff --git a/modes/build.sh b/modes/build.sh new file mode 100755 index 0000000..1b75a05 --- /dev/null +++ b/modes/build.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# build — run the project build pipeline +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=base.sh +source "$SCRIPT_DIR/base.sh" + +cd "$PROJECT_ROOT" + +# ── TODO: uncomment and configure for your build tool ──────────────────────── + +# just +# exec just build + +# make +# exec make build + +# go +# exec go build ./... + +# npm / yarn / pnpm +# exec npm run build + +# cargo +# exec cargo build --release + +echo "error: no build command configured in modes/build.sh" >&2 +exit 1 diff --git a/modes/db.sh b/modes/db.sh new file mode 100755 index 0000000..75c4833 --- /dev/null +++ b/modes/db.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# db — open an interactive database shell with project credentials +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=base.sh +source "$SCRIPT_DIR/base.sh" + +# ── TODO: uncomment and configure for your database ────────────────────────── + +# PostgreSQL +# exec psql "postgresql://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME" + +# MySQL / MariaDB +# exec mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" + +# SQLite +# exec sqlite3 "$PROJECT_ROOT/db.sqlite3" + +# Redis +# exec redis-cli -h "$DB_HOST" + +echo "error: no database configured in modes/db.sh" >&2 +exit 1 diff --git a/modes/logs.sh b/modes/logs.sh new file mode 100755 index 0000000..ae396bc --- /dev/null +++ b/modes/logs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# logs — stream runtime logs for the project +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=base.sh +source "$SCRIPT_DIR/base.sh" + +cd "$PROJECT_ROOT" + +# ── TODO: uncomment and configure for your runtime ─────────────────────────── + +# Docker Compose (all services) +# exec docker compose logs -f + +# Docker Compose (specific service) +# exec docker compose logs -f app + +# journald +# exec journalctl -fu your-service-name + +# Log file +# exec tail -f "$PROJECT_ROOT/logs/app.log" + +echo "error: no log source configured in modes/logs.sh" >&2 +exit 1 diff --git a/modes/reset.sh b/modes/reset.sh new file mode 100755 index 0000000..4a8a3fc --- /dev/null +++ b/modes/reset.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# reset — destroy and rebuild the environment from scratch (DESTRUCTIVE) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=base.sh +source "$SCRIPT_DIR/base.sh" + +cd "$PROJECT_ROOT" + +echo "WARNING: This will destroy and rebuild the environment." +echo "All local state will be lost." +echo "" +read -rp "Continue? [y/N] " confirm +[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; } + +# ── TODO: implement your teardown and rebuild steps ─────────────────────────── + +# Example: Docker Compose teardown + rebuild +# docker compose down --volumes --remove-orphans +# docker compose build --no-cache +# docker compose up -d +# docker compose exec app + +# Example: nix / devenv +# nix flake update +# direnv reload + +echo "error: no reset steps configured in modes/reset.sh" >&2 +exit 1 diff --git a/ms b/ms new file mode 100755 index 0000000..109b155 --- /dev/null +++ b/ms @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ms — modal shell dispatcher +# Usage: ms [mode] +# ms list available modes +# ms enter named mode +set -euo pipefail + +MODES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/modes" && pwd)" + +if [[ $# -eq 0 ]]; then + echo "Available modes:" + for f in "$MODES_DIR"/*.sh; do + [[ "$(basename "$f")" == "base.sh" ]] && continue + printf " %s\n" "$(basename "$f" .sh)" + done + exit 0 +fi + +mode="$1" +mode_file="$MODES_DIR/${mode}.sh" + +if [[ ! -f "$mode_file" ]]; then + echo "error: unknown mode '$mode'" >&2 + echo "Run 'ms' to list available modes." >&2 + exit 1 +fi + +exec bash "$mode_file" -- cgit v1.2.3