diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml new file mode 100644 index 0000000..b84e73f --- /dev/null +++ b/.github/workflows/demo.yml @@ -0,0 +1,84 @@ +name: Generate Demo GIF + +on: + push: + branches: [ main ] + paths: + - 'bashful.ts' + - 'tools/demo-commands.sh' + - '.github/workflows/demo.yml' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + generate-demo: + name: Record demo and convert to GIF + runs-on: ubuntu-latest + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + + - name: Install dependencies + run: bun install + + - name: Install asciinema and jq + run: sudo apt-get install -y asciinema jq + + - name: Install agg + run: | + curl -fsSL "https://github.com/asciinema/agg/releases/latest/download/agg-x86_64-unknown-linux-gnu" \ + -o /usr/local/bin/agg + chmod +x /usr/local/bin/agg + + - name: Create docs directory + run: mkdir -p docs + + - name: Record demo session + env: + # asciinema needs a home dir for config + HOME: /root + run: | + chmod +x tools/demo-commands.sh + asciinema rec docs/bashful-demo.cast \ + --command "bash tools/demo-commands.sh" \ + --cols 82 --rows 26 \ + --overwrite + + - name: Convert cast to GIF + run: | + agg docs/bashful-demo.cast docs/demo.gif \ + --font-size 14 \ + --speed 1.2 \ + --idle-time-limit 3 + + - name: Commit GIF via PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add docs/demo.gif docs/bashful-demo.cast + if git diff --staged --quiet; then + echo "GIF unchanged — nothing to commit." + exit 0 + fi + BRANCH="chore/update-demo-gif-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH" + git commit -m "chore: regenerate demo GIF" + git push origin "$BRANCH" + gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "chore: regenerate demo GIF" \ + --body "Auto-generated by the [demo workflow](../actions/workflows/demo.yml). Merges the updated \`docs/demo.gif\` and \`docs/bashful-demo.cast\`." \ + --label "documentation" || true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5e2b701 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Peter Isberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4d954bc..b79a4ac 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# Bashful +# Bashful — CLI-to-REST API Auto-Wrapper + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Build and Test](https://github.com/PIsberg/bashful-cli/actions/workflows/test.yml/badge.svg)](https://github.com/PIsberg/bashful-cli/actions/workflows/test.yml) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PIsberg/bashful-cli/badge)](https://securityscorecards.dev/viewer/?uri=github.com/PIsberg/bashful-cli) +[![Bun >= 1.0](https://img.shields.io/badge/Bun-%3E%3D%201.0-black?logo=bun)](https://bun.sh/) +[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-blue)](https://bun.sh/) +[![Lines of Code](https://www.aschey.tech/tokei/github/PIsberg/bashful-cli?languages=TypeScript&category=code)](https://github.com/PIsberg/bashful-cli) **Bashful** gives your CLI tools a REST API — no code required. @@ -6,6 +13,9 @@ The name is a double meaning: it wraps **bash**ful tools (tools that only speak Think of it as a shy CLI tool finding its voice over HTTP. + +![Bashful demo — wrap a CLI tool in a REST API and UI in seconds](docs/demo.gif) + ![bashful_infographics-v1](https://github.com/user-attachments/assets/f2e9d659-0e9d-4d84-ba27-b50ca399f397) @@ -41,26 +51,38 @@ powershell -c "irm bun.sh/install.ps1 | iex" --- -## Quick start +## ⚡ Add REST to Your CLI in 30 Seconds +**1. Install dependencies:** ```bash bun install +``` + +**2. Start Bashful wrapping your CLI tool (e.g., `curl`):** +```bash bun run bashful.ts curl ``` -Then open `http://localhost:3000` in your browser, or: +**3. Explore the dynamic Swagger-like Web UI:** +Open [http://localhost:3000](http://localhost:3000) in your browser to view the auto-generated dashboard where you can interactively run commands. -```bash -# Get the parsed flag schema -curl http://localhost:3000/curl/schema +**4. Or interact with the auto-generated endpoints directly:** -# Execute curl via HTTP -curl -X POST http://localhost:3000/curl \ - -H "Content-Type: application/json" \ - -d '{"silent": true, "output": "example.html", "_args": ["http://example.com"]}' -``` +* **Get the parsed flag schema:** + ```bash + curl http://localhost:3000/curl/schema + ``` + +* **Execute `curl` over HTTP:** + ```bash + curl -X POST http://localhost:3000/curl \ + -H "Content-Type: application/json" \ + -d '{"silent": true, "output": "example.html", "_args": ["http://example.com"]}' + ``` -That POST translates to: `curl --silent --output example.html http://example.com` +> [!TIP] +> The POST above translates dynamically to the native CLI invocation: +> `curl --silent --output example.html http://example.com` --- @@ -141,3 +163,9 @@ Everything lives in a single file: `bashful.ts`. - **`buildCLIArgs(payload, schema)`** — translates a JSON payload back into a flat CLI argument array. - **Server** — `Bun.serve` on port 3000. Routes: `GET /` (UI), `GET //schema`, `POST /`. - **Execution** — `Bun.spawn` runs the real command and streams stdout directly as the HTTP response. + +--- + +## License + +[MIT](LICENSE) © Peter Isberg diff --git a/tools/demo-commands.sh b/tools/demo-commands.sh new file mode 100644 index 0000000..2581886 --- /dev/null +++ b/tools/demo-commands.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Replayed by asciinema to produce docs/demo.gif. +# Run from: tools/demo/ or repository root. + +GREEN='\033[0;32m' +CYAN='\033[0;36m' +BOLD='\033[1m' +DIM='\033[2m' +RESET='\033[0m' + +step() { echo -e "${DIM}# $*${RESET}"; sleep 0.5; } +cmd() { + echo -e "${GREEN}\$${RESET} ${BOLD}$*${RESET}" + sleep 0.4 + eval "$*" + echo "" + sleep 1.0 +} + +clear +echo -e "${BOLD}Bashful CLI-to-REST auto-wrapper${RESET} — instantly give CLI tools a REST API" +echo "" +sleep 0.8 + +step "1. Wrap curl using bashful.ts" +cmd "bun run bashful.ts curl &" +sleep 2 + +step "2. Look at the parsed flag schema that was automatically generated" +cmd "curl -s http://localhost:3000/curl/schema | jq 'with_entries(.value |= {shortFlag, longFlag, type}) | to_entries | .[0:4]'" +sleep 0.3 + +step "3. Execute curl via the REST API with a JSON payload!" +cmd "curl -X POST http://localhost:3000/curl -H 'Content-Type: application/json' -d '{\"silent\": true, \"_args\": [\"https://icanhazip.com\"]}'" +sleep 0.3 + +step "4. Stop the bashful server" +cmd "kill %1" + +echo -e "${GREEN}✓ Instantly served REST API and Swagger UI with zero config!${RESET}" +sleep 2