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
118 changes: 118 additions & 0 deletions .cli-flags.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# flags-2-env config — https://github.com/ORESoftware/flags-2-env
#
# This file is the CLI contract. The binary parses it directly on every
# platform, and `--help`, shell completions, env-var names, types, and defaults
# all render from it at runtime — there is no second copy in Rust to drift.

[help]
url = "https://github.com/led-dynamo/leddy-cli"
columns = ["options", "env", "type", "default", "description"]

[parse]
stop_at_first_positional = false
allow_unknown = false

[env]
# A device API token, when the deployment uses one, is a credential: a flag
# value shows up in `ps` output and shell history. It is read straight from the
# environment and never declared as a flag.
ignore = ["LEDDY_API_TOKEN"]

[commands.publish]
help = "Publish a scrolling message to the display."
aliases = ["send"]

[commands.clear]
help = "Clear the display."

[commands.health]
help = "Check that the Leddy API is reachable."

[commands.preview]
help = "Render the message locally as ASCII art — no device, no network."

[commands.preview.flags.at]
env = "LEDDY_PREVIEW_AT_MS"
aliases = ["at"]
type = "integer"
default = 0
help = "Milliseconds into the scroll to render (0 through 86400000)."

[commands.preview.flags.width]
env = "LEDDY_WIDTH"
aliases = ["width"]
type = "integer"
default = 128
help = "Display width in pixels (1 through 4096)."

[commands.preview.flags.height]
env = "LEDDY_HEIGHT"
aliases = ["height"]
type = "integer"
default = 8
help = "Display height in pixels (1 through 512)."

[commands.completion]
help = "Print a shell completion script for this CLI."

[commands.completion.flags.shell]
env = "LEDDY_COMPLETION_SHELL"
aliases = ["shell"]
short = "s"
type = "string"
default = "bash"
help = "Shell dialect to emit: bash or zsh."

[flags.url]
env = "LEDDY_API_URL"
aliases = ["url", "api-url"]
short = "u"
type = "string"
default = "http://localhost:8080"
help = "Base URL of the Leddy API."

[flags.text]
env = "LEDDY_TEXT"
aliases = ["text"]
short = "t"
type = "string"
help = "Message text to scroll. Required by publish and preview."

[flags.speed]
env = "LEDDY_SCROLL_SPEED"
aliases = ["speed"]
type = "double"
default = 24.0
help = "Scroll speed in pixels per second (must be positive and finite)."

[flags.direction]
env = "LEDDY_DIRECTION"
aliases = ["direction"]
short = "d"
type = "string"
default = "left"
help = "Scroll direction: left or right."

[flags.repeat]
env = "LEDDY_REPEAT"
aliases = ["repeat"]
short = "r"
type = "string"
default = "forever"
help = "Repeat mode: forever, once, or a positive count such as 3."

[flags.id]
env = "LEDDY_MESSAGE_ID"
aliases = ["id"]
type = "string"
help = "Message id (default: a cli-<timestamp> id)."

[flags.json]
env = "LEDDY_JSON"
aliases = ["json"]
short = "j"
type = "bool"
default = false
true_aliases = ["1", "yes", "on"]
false_aliases = ["0", "no", "off"]
help = "Emit machine-readable JSON instead of the human output."
72 changes: 64 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
# CI for the leddy CLI.
#
# Three things are checked that a plain `cargo test` would not catch:
# * `.cli-flags.toml` passes the flags-2-env audit (ambiguous aliases,
# duplicate shorts, colliding env targets);
# * `src/cli_config.rs` still matches what the generator emits from that
# contract, so the typed struct cannot drift from the flags;
# * the `.zpkg.toml` org dependency graph is exactly what it should be.
name: ci

on:
pull_request:
push:
branches: [main, dev]
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

jobs:
rust:
runs-on: ubuntu-24.04
contract:
name: cli contract
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build the flags-2-env generator
run: |
set -euo pipefail
git clone --depth 1 https://github.com/ORESoftware/flags-2-env.git /tmp/flags-2-env
git -C /tmp/flags-2-env fetch --depth 1 origin "$FLAGS2ENV_REV"
git -C /tmp/flags-2-env checkout --detach FETCH_HEAD
make -C /tmp/flags-2-env cli
env:
# Keep in step with the `flags2env` rev pinned in Cargo.toml, so the
# audit and the linked parser are the same build.
FLAGS2ENV_REV: 8c8465561075a8d7ebb58074b3a8087138e97d8f
- name: Audit the CLI contract
run: /tmp/flags-2-env/build/flags2env audit .cli-flags.toml
- name: Check the generated typed config for drift
run: |
set -euo pipefail
diff -u src/cli_config.rs \
<(/tmp/flags-2-env/build/flags2env generate rust .cli-flags.toml --name CliConfig)
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Validate the zed dependency graph
run: python3 scripts/check-zed-dependencies.py

test:
name: test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- run: cargo fmt --all -- --check
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo test --all-targets --all-features
- run: cargo clippy --all-targets --locked -- -D warnings
- run: cargo test --all-targets --locked
2 changes: 1 addition & 1 deletion .github/workflows/zed-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python3 scripts/check-zed-package.py
- run: python3 scripts/check-zed-dependencies.py
30 changes: 7 additions & 23 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
# Build output
target/
dist/
build/
coverage/

# Zed package materialization
.vendor/.zed/
.zed/
.zed-pack/

# Local configuration
/target
**/*.rs.bk
.env
.env.*
!.env.example
secrets.h
include/secrets.h

# Editors and operating systems
.DS_Store
.idea/
.vscode/
*.swp

# Logs
.direnv/
.zed/
.zed-pack/
.vendor/.zed/
*.log
.DS_Store
35 changes: 16 additions & 19 deletions .zpkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
org = "led-dynamo"
name = "leddy-cli"
version = "0.1.0"
description = "Command-line client for publishing, clearing, inspecting, and automating Leddy displays"
description = "Command-line client for publishing, previewing, clearing, and inspecting Leddy displays"
license = "MIT"
keywords = ["led-matrix", "cli", "automation", "iot", "rust"]
language = "rust"
Expand All @@ -11,39 +11,36 @@ language = "rust"
vcs = "git"
url = "https://github.com/led-dynamo/leddy-cli"

# Org dependency graph: interfaces (shapes) -> lib (behaviour) -> clients
# (transport) -> this CLI. All three are real Cargo edges too;
# scripts/check-zed-dependencies.py fails CI if the two manifests disagree.
[dependencies]
"led-dynamo/leddy-interfaces" = "^0.1.0"
"led-dynamo/leddy-lib" = "^0.1.0"
"led-dynamo/leddy-clients" = "^0.1.0"

[build]
command = "cargo build --release"
outputs = ["target/release/leddy-cli"]
command = "cargo build --release --locked --bin leddy"
outputs = ["target/release/leddy"]

[bin]
leddy = "target/release/leddy-cli"
leddy = "target/release/leddy"

[install]
adapter = "none"
dir = ".vendor/.zed"

[publish]
include_readme = true
tag_format = "v{version}"
smoke_test = "test -x \"$ZED_PKG_TEST_TARGET/target/release/leddy-cli\" && \"$ZED_PKG_TEST_TARGET/target/release/leddy-cli\" --help"
exclude = [
".env",
".env.*",
".vendor/.zed/**",
".zed/**",
".zed-pack/**",
"target/**",
"**/*.log",
]
smoke_test = "test -x \"$ZED_PKG_TEST_TARGET/target/release/leddy\" && \"$ZED_PKG_TEST_TARGET/target/release/leddy\" --help >/dev/null"
exclude = [".env", ".env.*", ".direnv/**", ".zed/**", ".zed-pack/**", ".vendor/.zed/**", "target/**", "tmp/**", "**/*.log", ".DS_Store"]

[publish.native]
registry = "crates-io"
package = "leddy-cli"

[install]
adapter = "none"
dir = ".vendor/.zed"

[scripts]
test = "cargo test --all-targets"
test = "cargo test --all-targets --locked"
lint = "cargo clippy --all-targets --locked -- -D warnings"
check-deps = "python3 scripts/check-zed-dependencies.py"
Loading
Loading