{"id":50889317,"url":"https://github.com/skwid138/shell-scripts","last_synced_at":"2026-06-15T20:02:20.662Z","repository":{"id":354756399,"uuid":"1224753980","full_name":"skwid138/shell-scripts","owner":"skwid138","description":"Personal macOS shell toolkit. Three-tier zsh init (env/login/rc), lazy Keychain-backed secrets, and bash CLI tools that integrate opencode with GitHub, Jira, SonarCloud, and Chrome DevTools MCP. Tested with bats; linted with shellcheck + shfmt.","archived":false,"fork":false,"pushed_at":"2026-06-13T03:04:11.000Z","size":511,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-13T05:06:19.349Z","etag":null,"topics":["automation","bash","bats","cli","macos","opencode","shell","shellcheck","zsh"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skwid138.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-29T15:38:36.000Z","updated_at":"2026-06-13T03:04:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/skwid138/shell-scripts","commit_stats":null,"previous_names":["skwid138/shell-scripts"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/skwid138/shell-scripts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skwid138%2Fshell-scripts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skwid138%2Fshell-scripts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skwid138%2Fshell-scripts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skwid138%2Fshell-scripts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skwid138","download_url":"https://codeload.github.com/skwid138/shell-scripts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skwid138%2Fshell-scripts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34377983,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","bash","bats","cli","macos","opencode","shell","shellcheck","zsh"],"created_at":"2026-06-15T20:02:19.850Z","updated_at":"2026-06-15T20:02:20.656Z","avatar_url":"https://github.com/skwid138.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# shell-scripts\n\nPersonal shell scripts and zsh configuration. Public mirror of `~/code/scripts/`.\n\nThis repo contains two distinct things:\n\n1. **Interactive-shell setup** (`shell/`) — sourced by zsh via a three-tier\n   init (env/login/rc barrels — see below) to compose the prompt, paths,\n   aliases, plugins, and lazy Keychain-backed secrets.\n2. **Standalone scripts** (`agent/`, `personal/`) — invoked directly. The\n   `agent/` ones are designed to be called by [opencode](https://opencode.ai)\n   skills/commands; the `personal/` ones are utilities I use myself.\n\n## Layout\n\n```\nshell/         # sourced by zsh — three-tier init (env/login/rc), see below\nagent/         # opencode-coupled tools; call directly\ndata/          # declarative manifests/templates consumed by agent scripts\nlib/           # shared helpers sourced by agent/ and shell/\npersonal/      # personal utilities (gist-linked tools, helpers)\ntests/         # bats test suites + bats submodules\ndocs/          # project docs (CONVENTIONS, EXIT-CODES, ADDING-A-SCRIPT)\n```\n\n`lib/common.sh` provides `die`, `warn`, `info`, `require_cmd`, `require_auth`.\n`lib/keychain.sh` wraps macOS `security` for secret retrieval.\n`lib/detect.sh` provides git-aware helpers (`detect_branch`, `detect_owner_repo`, `detect_pr_number`).\n\n## Shell init layers (`shell/`)\n\nThe `shell/` tree is split into three tiers, each driven by a small barrel\nfile that sources every `*.zsh` in its tier directory:\n\n```\nshell/\n├── init_env.zsh        # env tier — sourced from ~/.zshenv (every shell)\n├── init_profile.zsh    # login tier — sourced from ~/.zprofile\n├── init_rc.zsh         # interactive tier — sourced from ~/.zshrc\n├── init.zsh            # legacy single-source shim (kept for back-compat)\n├── env/                # PATH, exported vars; must be silent + side-effect-free\n│   ├── paths.zsh\n│   └── vars.zsh\n├── login/              # heavy login-once work (nvm, conda, gcloud)\n│   ├── conda.zsh\n│   ├── gcloud.zsh\n│   └── nvm.zsh\n├── rc/                 # interactive-only (aliases, plugins, completions)\n│   ├── aliases.zsh\n│   ├── functions.zsh\n│   ├── docker.zsh\n│   └── …\n└── lib/                # helpers sourced by tiers above\n    ├── auto_nvm.zsh\n    └── secrets.sh\n```\n\n**Why the split?** Non-interactive `zsh -c '…'` invocations (agent scripts,\nCI, cron) only run the env tier — they get PATH and exported vars and skip\nthe login/rc cost. Login shells run env + login. Interactive shells run all\nthree. See the dotfiles `README.md` for how the wiring lives in\n`~/.zshenv` / `~/.zprofile` / `~/.zshrc`.\n\n### `.zsh` vs `.sh` extension convention\n\n- **`.sh`** — POSIX/bash-portable. Linted by `shellcheck` in bash mode\n  (`make lint-sh`). Used for files that must source-run under `bash` too,\n  e.g. `lib/secrets.sh` is bash-portable so MCP launch wrappers can do\n  `bash -c '. lib/secrets.sh; secret_load …; exec …'`.\n- **`.zsh`** — zsh-only. Syntax-checked with `zsh -n` (`make lint-zsh`).\n  Excluded from shellcheck by default; opt-in per-file with a top-of-file\n  `# shellcheck shell=bash` directive when bash-overlap coverage is wanted.\n- Both extensions are formatted by `shfmt`, which auto-detects the dialect\n  from the extension (`-ln zsh` for `.zsh`, default for `.sh`).\n\n### Lazy secrets (`lib/secrets.sh`)\n\nSecrets are **not** loaded at shell startup. Nothing in the env tier reads\nKeychain. Instead, scripts that need a secret call `secret_load` (or\n`secret_get`) at the moment they need it. The result is memoized in-process,\nso repeated calls in the same shell are cheap.\n\n```bash\n# Recommended: secret_load exports the env var and short-circuits on\n# subsequent calls via an env-var fast path.\nsource ~/code/scripts/shell/lib/secrets.sh\nsecret_load WPRO_OPEN_AI_API_SECRET wpro-openai \\\n  || die_unauthed \"wpro-openai not in keychain\"\ncurl -H \"Authorization: Bearer $WPRO_OPEN_AI_API_SECRET\" …\n```\n\n`secret_get` exists for callers that don't want the secret in env, but be\naware of the **subshell quirk**: each `$(secret_get …)` runs in its own\nsubshell, so the cache write dies with that subshell and the next `$()`\nre-queries Keychain. If you find yourself calling `$(secret_get …)` more\nthan once, switch to `secret_load`. The full API contract — including the\ntwo-layer memoization (env-var fast path → in-process cache → Keychain) and\nthe `unset VAR` + `secret_clear ENTRY` re-load pattern after rotations — is\ndocumented in the header comment at the top of\n[`shell/lib/secrets.sh`](shell/lib/secrets.sh), which is the source of truth.\n\n## Requirements\n\n- macOS (primary). Linux works for most things; Keychain-backed secrets are\n  macOS-only.\n- `zsh` 5.8+ (only for `shell/`)\n- `bash` 3.2+ for the standalone scripts under `agent/` and `personal/`.\n  Most agent scripts work under macOS `/bin/bash` (3.2). Scripts that\n  genuinely require bash 4+ (e.g. for `declare -A` associative arrays)\n  guard with an explicit `BASH_VERSINFO` check and emit a `brew install bash`\n  hint on failure. See `docs/CONVENTIONS.md` for the portability rule.\n- Optional per-script: `gh`, `jq`, `acli` (Atlassian), `gcloud`, Chrome.\n\n## Setup\n\nClone, then either symlink the relevant dotfiles (recommended; see the\n[`dotfiles`](https://github.com/skwid138/dotfiles) repo) or wire the three\ninit barrels into your zsh startup files yourself:\n\n```bash\ngit clone --recurse-submodules git@github.com:skwid138/shell-scripts.git ~/code/scripts\n\n# In ~/.zshenv:\n[[ -f \"$HOME/code/scripts/shell/init_env.zsh\" ]] \u0026\u0026 source \"$HOME/code/scripts/shell/init_env.zsh\"\n# In ~/.zprofile:\n[[ -f \"$HOME/code/scripts/shell/init_profile.zsh\" ]] \u0026\u0026 source \"$HOME/code/scripts/shell/init_profile.zsh\"\n# In ~/.zshrc:\n[[ -f \"$HOME/code/scripts/shell/init_rc.zsh\" ]] \u0026\u0026 source \"$HOME/code/scripts/shell/init_rc.zsh\"\n```\n\nThe legacy `shell/init.zsh` shim still works (sources all three tiers in\norder) for any external consumer; new setups should source the three\nbarrels directly so non-interactive shells skip the login/rc cost.\n\nFor secrets, populate macOS Keychain entries (see `~/code/dotfiles/secrets-bootstrap/README.md`\nfor the inventory). Secrets are read lazily via `secret_load`/`secret_get`\nfrom `shell/lib/secrets.sh`; nothing is loaded into env at shell startup.\nFor local-only overrides or extra non-secret env vars, create\n`shell/env/vars.local.zsh` (gitignored; auto-sourced).\n\n## Usage examples\n\n```bash\n# Standalone tools\nagent/gh-pr-comments.sh wpromote/polaris-web#275\nagent/jira-fetch-ticket.sh BIXB-18835 --all\nagent/sonar-pr-issues.sh --severity MAJOR 275\nagent/branch-to-ticket.sh\nagent/chrome_mcp.sh --check\n```\n\nEvery script takes `-h` / `--help`.\n\n### Personal launchers (`personal/`)\n\nA few scripts here are interactive launchers wired up via aliases in\n`shell/rc/aliases.zsh`:\n\n- `openweb` → `personal/opencode-web.sh` — start opencode's web UI for remote\n  access via Tailscale (loads password lazily from Keychain, wraps in\n  `caffeinate -is`).\n- `openattach` → `personal/opencode-attach.sh` — attach a local TUI to the\n  running `openweb` backend so web + terminal clients share one session pool.\n  Loads the same Keychain password so basic-auth succeeds without env setup.\n  See `~/.config/opencode/README-remote-access.md` for the full setup.\n\n## Development\n\n```bash\nmake lint           # shellcheck (advisory)\nmake lint-strict    # shellcheck (CI gate; errors only)\nmake fmt            # apply shfmt formatting\nmake test           # bats (parallel by default; requires GNU parallel)\nmake test-serial    # bats sequentially (escape hatch)\nmake test-parallel  # alias for `make test`\nmake check          # full CI gate: lint-strict + fmt-check + test (parallel)\nmake check-serial   # sequential variant of `make check`\nmake help           # list all targets\n```\n\nCI runs `lint-strict`, `fmt-check`, and **both** `test-serial` and\n`test-parallel` as side-by-side blocking jobs (macOS + Ubuntu matrix),\nplus a separate gitleaks job for secret scanning. Two test jobs let us\ndistinguish serial-only regressions from parallel-only regressions.\n\nParallel test execution requires GNU `parallel` on PATH (preinstalled on\nGitHub-hosted runners; `brew install parallel` for local dev). The\n`JOBS` env var controls worker count and defaults to the CPU count\n(`sysctl -n hw.ncpu` on macOS, `nproc` on Linux, fallback 4); override\nwith `JOBS=N make test` for debugging.\n\n### Pre-commit hook (optional)\n\nInstall a fast pre-commit hook that lints, format-checks, and\nsecret-scans only the **staged** shell files (no full-tree work):\n\n```bash\nmake install-hook   # enables .githooks/pre-commit via core.hooksPath\nmake uninstall-hook # disables it\n```\n\nThe hook is a no-op when no shell files are staged. It runs `shellcheck`\n(errors only — matches CI), `shfmt -d` (formatting drift), and\n`gitleaks protect --staged` (if installed). Bypass with\n`git commit --no-verify` for emergency commits.\n\n## Conventions\n\n- All scripts start with `#!/usr/bin/env bash` and `set -uo pipefail` (not\n  `-e`; see `docs/CONVENTIONS.md`).\n- All scripts accept `-h`/`--help`.\n- All scripts exit non-zero on error with a clear message to **stderr**.\n- Data-retrieval scripts emit JSON to stdout; nothing else.\n- Scripts are read-only with respect to remote systems unless you pass an\n  explicit, off-by-default opt-in flag (e.g. `--apply`, `--post-jira`); see\n  `docs/CONVENTIONS.md`.\n- Scripts validate their own dependencies up front (`require_cmd`, `require_auth`).\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskwid138%2Fshell-scripts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskwid138%2Fshell-scripts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskwid138%2Fshell-scripts/lists"}