Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
build:
name: Build ${{ matrix.archive_suffix }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-14
target: aarch64-apple-darwin
archive_suffix: macos-arm64
- runner: macos-14
target: x86_64-apple-darwin
archive_suffix: macos-intel
- runner: ubuntu-22.04
target: x86_64-unknown-linux-gnu
archive_suffix: linux-x86_64

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- name: Build release binary
run: cargo build --release --locked --target "${{ matrix.target }}"

- name: Package archive
env:
VERSION: ${{ github.ref_name }}
TARGET: ${{ matrix.target }}
ARCHIVE_SUFFIX: ${{ matrix.archive_suffix }}
run: |
ARCHIVE="fire-${VERSION}-${ARCHIVE_SUFFIX}.tar.gz"
BIN_PATH="target/${TARGET}/release/fire"

test -f "${BIN_PATH}"
tar -czf "${ARCHIVE}" -C "target/${TARGET}/release" fire
shasum -a 256 "${ARCHIVE}" > "${ARCHIVE}.sha256"

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.archive_suffix }}
path: |
fire-${{ github.ref_name }}-${{ matrix.archive_suffix }}.tar.gz
fire-${{ github.ref_name }}-${{ matrix.archive_suffix }}.tar.gz.sha256

release:
name: Publish GitHub Release
runs-on: ubuntu-22.04
needs: build

steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
pattern: dist-*
path: dist
merge-multiple: true

- name: Build checksums file
run: |
cat dist/*.sha256 | sort -k2 > dist/checksums.txt

- name: Publish release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Fire ${{ github.ref_name }}
generate_release_notes: true
fail_on_unmatched_files: true
files: |
dist/*.tar.gz
dist/checksums.txt
105 changes: 98 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

A CLI with dynamic completion powered by external configuration.

## Installation

Install with Homebrew using the `gbenm/labs` tap:

```bash
brew tap gbenm/labs
brew install fire
```

## Command Configuration
Fire loads YAML files from the current directory with these patterns:
- `fire.yaml`
Expand All @@ -15,9 +24,20 @@ Files are merged in this order:

If the same command name appears more than once, the last loaded definition wins.

Optional local include directories:
- Define `include` in a root fire file to extend local loading into specific subdirectories.
- Paths must be relative to the current directory (`samples/`, `tools/`), and Fire only scans each included directory root (non-recursive).
- Included files follow the same scope rules (`namespace`, `group`, commands). If a root file defines `namespace.prefix`, included files inherit that namespace.
- In globally installed directories, `include` is also honored using the installed directory as base path.

Example:

```yaml
group: backend
namespace:
prefix: ex
description: Example

commands:
run:
description: Run npm scripts
Expand All @@ -33,6 +53,60 @@ commands:

`exec` and `run` are both supported and treated as executable command actions.

Default namespace behavior:
- If `namespace.prefix` is omitted in a file, Fire inherits it from another file in the same directory that defines it.
- If no file in that directory defines `namespace.prefix`, the file remains outside namespace scope.

## Command Resolution Rules

Fire resolves commands by file scope:

1. No `namespace.prefix`, no `group`:
- `fire <command>` (local direct command)
- `fire <implicit-namespace> <command>` (namespace path)

2. With `namespace.prefix`, no global `group`:
- `fire <namespace> <command>`

3. With global `group`, no `namespace`:
- `fire <group> <command>`

4. With `namespace` and global `group`:
- `fire <namespace> <group> <command>`

Root completion priority (`fire <TAB>`) is:
1. Root-local commands
2. Global namespaces
3. Global groups without namespace
4. Global direct commands

## Global Installation

`fire cli` is a reserved internal command.

Install the current directory globally:

```bash
fire cli install
```

Create a minimal config with an interactive wizard:

```bash
fire cli init
```

Install shell completions (standard user locations for zsh and bash):

```bash
fire cli completion install
```

Behavior:
- Stores only the absolute directory path (no command cache, no file copy).
- Avoids duplicates if the path is already installed.
- On each run, Fire dynamically reads installed directories and loads fire files from each directory root (non-recursive).

## `config.fire.yaml` Validation in VS Code
The schema is available at [`schemas/fire.schema.json`](./schemas/fire.schema.json).

Expand Down Expand Up @@ -68,27 +142,32 @@ Expected validation error (example in VS Code): `Property cli is not allowed.`

Why: `cli` is reserved for internal CLI behavior and cannot be overridden as a user command at the root `commands` level.

## Autocomplete Without External Scripts
## Autocomplete
Fire supports two completion modes:

- Rich zsh completion (value + description)
- Bash-compatible command completion (`complete -C`)

### zsh
```zsh
source ./zsh_completations
Install both shell scripts:

```bash
fire cli completion install
```

### bash
Install only one shell:

```bash
complete -o nospace -C fire fire
fire cli completion install zsh
fire cli completion install bash
```

The installer writes completion scripts to user-level standard locations and updates `~/.zshrc` / `~/.bashrc` with managed blocks.

For `complete -C`, the shell invokes `fire` in completion mode using `COMP_LINE` / `COMP_POINT`.

Completion output includes command name and `description` when present (`name<TAB>description`) in the `__complete` protocol, which is consumed by the zsh completion script.

Note: `complete -C` only uses completion values; descriptions are a zsh-native feature through `zsh_completations`.
Note: `complete -C` only uses completion values; descriptions are shown through the installed zsh completion function.

## Execution Rules
- `fire <command>` executes `exec` (or `run`) of the resolved command.
Expand All @@ -101,6 +180,18 @@ Example:

`cli` is reserved for internal CLI management and cannot be overridden by user commands.

## Help Suffix

Append `:h` to any resolved command path to show help without execution:

- `fire run :h`
- `fire ex backend api :h`

It prints:
- Command path
- Command `description` (if any)
- Subcommands and their descriptions (if any)

## Run
```bash
cargo build
Expand Down
6 changes: 6 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
linux:
image: alpine:3.19
container_name: fire-alpine-svc
command: ["tail", "-f", "/dev/null"]
restart: unless-stopped
49 changes: 43 additions & 6 deletions config.sample.fire.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# yaml-language-server: $schema=./schemas/fire.schema.json
dir: . # default working directory
dir: . # default working directory "."

env_file: .env # default env file

namespace:
id: 51170376-ea38-4317-ba49-4e63dfc8a571 # optional
description: Pumpkat
alias: pk
description: Example
prefix: ex

group: backend

runtimes:
deno:
Expand All @@ -24,8 +25,7 @@ runtimes:
- scripts/helpers/*.py

include:
- common.fire.yml
- database.fire.yml
- samples/

x-arg-config: &arg-config
# Use {n} to represent the argument index: {{n}} -> {1}, {2}, ... ; #{n} -> #1, #2, ...
Expand Down Expand Up @@ -57,6 +57,9 @@ commands:
fallback_runner: docker run --rm -it node:lts-alpine /bin/bash # fallback
exec: npm

docker-node:
exec: docker run --rm -it node:lts-alpine sh

run:
description: |
y si agrego descripción a este?
Expand Down Expand Up @@ -114,5 +117,39 @@ commands:
exec: npm run
start: fire build && npm run start

fallback:
check: exit -1
before: echo "ignore me please"
fallback_runner: docker run --rm -it -v .:/mount alpine sh
exec:
- cd mount
- pwd
- ls
- cat README.md

alpine:
runner: docker compose exec linux sh
before: docker compose ps -q linux | grep -q . || docker compose up -d linux
exec:
- echo "This is running inside the alpine container!"
- pwd

container:
runner: docker run --rm -it -v .:/mount alpine sh
exec:
- cd /mount
- pwd
- ls

template:
macros:
"CMD": docker compose exec linux
<<: *arg-config
exec:
- "CMD echo Hello {1}, are you from {2}?"
- "CMD echo {2} is a beautiful place!"
- "CMD echo and who else is with you? ...{{n}}"
- "CMD echo and who else is with you? [{{n}}]"

web:
delegate: npm-run # built-in delegate that uses package.json scripts for autocompletion
4 changes: 4 additions & 0 deletions config.simple.config.fire.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# yaml-language-server: $schema=./schemas/fire.schema.json

commands:
say-hello: echo hello!!
4 changes: 4 additions & 0 deletions samples/test.fire.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# yaml-language-server: $schema=../schemas/fire.schema.json

commands:
test: echo from test command
19 changes: 9 additions & 10 deletions schemas/fire.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"namespace": {
"$ref": "#/$defs/namespace"
},
"group": {
"type": "string",
"description": "Optional file-level group used as a command prefix for all commands in this file."
},
"runtimes": {
"type": "object",
"description": "Runtime definitions keyed by alias (for example: deno, py).",
Expand All @@ -25,7 +29,7 @@
},
"include": {
"type": "array",
"description": "List of additional Fire config files to merge/include.",
"description": "List of additional local directories (relative to current directory) where Fire should load fire config files. Search is direct-level only (non-recursive).",
"items": {
"type": "string"
}
Expand Down Expand Up @@ -57,23 +61,18 @@
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Optional namespace UUID."
},
"description": {
"type": "string",
"description": "Human-readable namespace label."
},
"alias": {
"prefix": {
"type": "string",
"description": "Short namespace alias."
"description": "Namespace prefix used in CLI paths."
}
},
"required": [
"description",
"alias"
"prefix",
"description"
]
},
"runtime": {
Expand Down
Loading