diff --git a/.runseal/lib/python-module b/.runseal/lib/python-module new file mode 100755 index 0000000..118b317 --- /dev/null +++ b/.runseal/lib/python-module @@ -0,0 +1,15 @@ +#!/usr/bin/env sh +set -eu + +name=${RUNSEAL_WRAPPER_NAME:?RUNSEAL_WRAPPER_NAME is not set} +module=$(printf '%s' "$name" | tr '-' '_') +root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) + +if ! command -v uv >/dev/null 2>&1; then + echo "missing dependency: uv" >&2 + exit 1 +fi + +PYTHONPATH="$root/scripts${PYTHONPATH+:$PYTHONPATH}" +export PYTHONPATH +exec uv run --project "$root/scripts" python -m "cli.$module" "$@" diff --git a/.runseal/wrappers/cloudflare b/.runseal/wrappers/cloudflare new file mode 100755 index 0000000..34aa12b --- /dev/null +++ b/.runseal/wrappers/cloudflare @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -eu +exec "$(dirname -- "$0")/../lib/python-module" "$@" diff --git a/.runseal/wrappers/pr b/.runseal/wrappers/pr new file mode 100755 index 0000000..34aa12b --- /dev/null +++ b/.runseal/wrappers/pr @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -eu +exec "$(dirname -- "$0")/../lib/python-module" "$@" diff --git a/.runseal/wrappers/release b/.runseal/wrappers/release new file mode 100755 index 0000000..34aa12b --- /dev/null +++ b/.runseal/wrappers/release @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -eu +exec "$(dirname -- "$0")/../lib/python-module" "$@" diff --git a/AGENTS.md b/AGENTS.md index cc29cf1..52c8a37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,15 +16,15 @@ management. - `.github/workflows/` contains CI and release workflows. - `.github/scripts/` contains workflow-only helper scripts. Keep workflow-only scripts there. -- `cli.sh` is the repo-local operator entrypoint for support tasks that do not - belong in the installable `flavor` product binary. Keep it as a thin command - dispatcher, not a second product CLI. Current support commands are - `:cloudflare`, `:pr`, and `:release`. +- `runseal.toml` and `.runseal/wrappers/` are the repo-local operator entrypoints + for support tasks that do not belong in the installable `flavor` product + binary. Current support commands are `runseal :cloudflare`, `runseal :pr`, + and `runseal :release`. - `grammars/` contains the repo-visible `.g4` grammar source of truth plus `metadata.json` contract metadata. Parser backends, facts, diagnostics, and harnesses should align to these files. - `scripts/` contains both developer helpers (`scripts/dev/`) and the - repo-local uv-managed Python support command tree used by `cli.sh`. + repo-local uv-managed Python support command tree used by runseal wrappers. - `.local/` is repo-local private operator state (for example Cloudflare secrets). It must stay gitignored and must not become a source of truth for product behavior. @@ -87,11 +87,13 @@ cargo clippy --locked --workspace --all-targets -- -D warnings cargo test --locked --workspace cargo run --locked -p flavor-cli -- check --root . --config flavor.json python3 scripts/dev/antlr.py check +runseal :pr --help ``` -`python3 scripts/init.py` is the default post-clone command. Use `--force` only -when intentionally replacing existing non-init hooks; the script backs them up -first. +`python3 scripts/init.py` is the default post-clone command. It requires +`runseal` so repo-local support commands have one entrypoint shape. Use +`--force` only when intentionally replacing existing non-init hooks; the script +backs them up first. `python3 scripts/dev/antlr.py check` is an optional Dockerized ANTLR validation helper. It lazily builds its Docker image when needed, checks `.g4` files under `grammars/` in ANTLR dependency mode, and does not generate Java artifacts. diff --git a/README.md b/README.md index 0418e5a..46af900 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,19 @@ cargo run --locked -p flavor-cli -- check --root . --config flavor.json python3 scripts/dev/antlr.py check # optional Dockerized G4 validation ``` +Repo-local operator commands use runseal wrappers rather than the installable +`flavor` binary: + +```bash +runseal :cloudflare [args] +runseal :pr [options] +runseal :release --channel=stable|beta [options] +runseal @wrappers +``` + +`python3 scripts/init.py` checks that `runseal` is installed before installing +hooks. + ## Scope `flavor` does not format, rewrite, run services, manage repositories, or inspect product semantics. diff --git a/cli.sh b/cli.sh deleted file mode 100755 index b3cf858..0000000 --- a/cli.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env sh -set -eu - -commands="cloudflare pr release" - -if [ "$#" -lt 1 ]; then - echo "usage: ./cli.sh : [args...]" >&2 - echo "commands: $commands" >&2 - exit 1 -fi - -case "$1" in - :*) name="${1#:}" ;; - *) - echo "usage: ./cli.sh : [args...]" >&2 - echo "commands: $commands" >&2 - exit 1 - ;; -esac - -found=0 -for command in $commands; do - if [ "$command" = "$name" ]; then - found=1 - break - fi -done - -if [ "$found" -ne 1 ]; then - echo "unknown command: :$name" >&2 - echo "commands: $commands" >&2 - exit 1 -fi - -if ! command -v uv >/dev/null 2>&1; then - echo "missing dependency: uv" >&2 - exit 1 -fi - -module=$(printf '%s' "$name" | tr '-' '_') -root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -shift -cd "$root" -PYTHONPATH="$root/scripts${PYTHONPATH+:$PYTHONPATH}" -export PYTHONPATH -exec uv run --project "$root/scripts" python -m "cli.$module" "$@" diff --git a/runseal.toml b/runseal.toml new file mode 100644 index 0000000..d88f0db --- /dev/null +++ b/runseal.toml @@ -0,0 +1,10 @@ +[resources] +root = ".local" + +[[injections]] +type = "env" + +[injections.vars] +RUNSEAL_REPO_LOCAL_DIR = "resource://" +RUNSEAL_REPO_SECRETS_DIR = "resource://secrets" +RUNSEAL_REPO_TMP_DIR = "resource://tmp" diff --git a/scripts/cli/cloudflare.py b/scripts/cli/cloudflare.py index c7682c3..6ce7d60 100644 --- a/scripts/cli/cloudflare.py +++ b/scripts/cli/cloudflare.py @@ -24,7 +24,7 @@ def usage() -> None: print( - """Usage: ./cli.sh :cloudflare [args] + """Usage: runseal :cloudflare [args] Commands: init create repo-local .local/secrets/cloudflare.env template @@ -138,7 +138,7 @@ def cmd_manage_inspect(args: list[str]) -> int: def cmd_manage_ensure_redirect(args: list[str]) -> int: - parser = argparse.ArgumentParser(prog="./cli.sh :cloudflare manage-ensure-redirect", add_help=False) + parser = argparse.ArgumentParser(prog="runseal :cloudflare manage-ensure-redirect", add_help=False) parser.add_argument("--dry-run", action="store_true") parsed = parser.parse_args(args) @@ -187,7 +187,7 @@ def cmd_manage_ensure_redirect(args: list[str]) -> int: def cmd_api(args: list[str]) -> int: - parser = argparse.ArgumentParser(prog="./cli.sh :cloudflare api", add_help=False) + parser = argparse.ArgumentParser(prog="runseal :cloudflare api", add_help=False) parser.add_argument("method") parser.add_argument("path") parser.add_argument("--query", action="append", default=[]) diff --git a/scripts/cli/pr.py b/scripts/cli/pr.py index 601b0a8..f4401b9 100644 --- a/scripts/cli/pr.py +++ b/scripts/cli/pr.py @@ -10,7 +10,7 @@ def usage() -> None: print( - """Usage: ./cli.sh :pr [options] + """Usage: runseal :pr [options] Create or update the GitHub PR for the current branch. @@ -97,7 +97,7 @@ def create_pr(branch: str, base: str, title: str | None, body_file: str | None) def cmd_default(args: list[str]) -> int: - parser = argparse.ArgumentParser(prog="./cli.sh :pr", add_help=False) + parser = argparse.ArgumentParser(prog="runseal :pr", add_help=False) parser.add_argument("--base", default="main") parser.add_argument("--title") parser.add_argument("--body-file") diff --git a/scripts/cli/release.py b/scripts/cli/release.py index 460c141..47e0671 100644 --- a/scripts/cli/release.py +++ b/scripts/cli/release.py @@ -18,7 +18,7 @@ def usage() -> None: print( - """Usage: ./cli.sh :release --channel=stable|beta [options] + """Usage: runseal :release --channel=stable|beta [options] Trigger a release workflow. @@ -69,7 +69,7 @@ def latest_run_id(workflow: str, ref: str) -> str: def cmd_default(args: list[str]) -> int: - parser = argparse.ArgumentParser(prog="./cli.sh :release", add_help=False) + parser = argparse.ArgumentParser(prog="runseal :release", add_help=False) parser.add_argument("--channel", choices=sorted(CHANNEL_WORKFLOWS)) parser.add_argument("--ref", default="main") parser.add_argument("--version", default="") diff --git a/scripts/init.py b/scripts/init.py index 89de969..6039d9a 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -21,6 +21,7 @@ "git", "python3", "cargo", + "runseal", "uv", "sh", "bash", @@ -31,10 +32,14 @@ REQUIRED_PATHS = ( "Cargo.toml", "Cargo.lock", - "cli.sh", "flavor.json", "manage.sh", "manage.ps1", + "runseal.toml", + ".runseal/lib/python-module", + ".runseal/wrappers/cloudflare", + ".runseal/wrappers/pr", + ".runseal/wrappers/release", ".github/workflows/guard.yml", ".github/scripts/release/r2/publish.sh", ".github/scripts/release/smoke/smoke.sh", @@ -64,7 +69,10 @@ cargo run --locked -p flavor-cli -- check --root . --config flavor.json echo "==> shell syntax" -sh -n cli.sh +sh -n .runseal/lib/python-module +sh -n .runseal/wrappers/cloudflare +sh -n .runseal/wrappers/pr +sh -n .runseal/wrappers/release sh -n manage.sh bash -n .github/scripts/release/r2/publish.sh sh -n .github/scripts/release/smoke/smoke.sh