# Build Cli

> >

- **Type:** Skill
- **Install:** `agentstack add skill-urmzd-dotfiles-build-cli`
- **Verified:** Pending review
- **Seller:** [urmzd](https://agentstack.voostack.com/s/urmzd)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [urmzd](https://github.com/urmzd)
- **Source:** https://github.com/urmzd/dotfiles/tree/main/dot_agents/skills/build-cli

## Install

```sh
agentstack add skill-urmzd-dotfiles-build-cli
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# CLI Patterns

## Design Philosophy

A junior developer should understand any CLI tool's behavior from `--help` alone. Prefer obvious defaults. Machine output (JSON) enables composability. All human-facing output goes to stderr.

## Output Modes

### Decision Tree

| Scenario | Output Mode | Flag |
|----------|------------|------|
| Data/device API (zigbee-rest) | Always JSON on stdout | None needed |
| Dual human + machine consumers (e.g. release tools, agent orchestrators) | `--format json\|human` | Default: human |
| CLI with optional machine output (llmem) | `--json` flag + TTY auto-detect | Default: human if TTY, JSON if piped |
| Human-only tool (e.g. demo recorders, interactive wizards) | Styled stderr only | No JSON mode |

### TTY Auto-Detection

Standard behavior when no explicit format flag is given:

```text
stdout.is_terminal() → human output (styled, colored)
!stdout.is_terminal() → JSON output (machine-readable)
```

Override flags:
- **`--json`** force JSON regardless of TTY
- **`--no-color`** / `NO_COLOR=1` env. Strip ANSI escape codes
- **`CI=true`** env. Treat as non-TTY (no spinners, no prompts, no color)

### JSON Conventions

- Rust: `serde_json::to_string_pretty()` to stdout
- Go: `json.MarshalIndent(v, "", "  ")` to stdout
- Python: `json.dumps(v, indent=2)` to stdout
- Errors in JSON mode: `{"error": "message"}` to stdout (not stderr), exit 1

## Stdout vs Stderr

| Stream | Content |
|--------|---------|
| **stdout** | Machine-readable data: JSON, generated content, completions |
| **stderr** | All human-facing UI: headers, spinners, checkmarks, errors, progress |

This enables piping: `tool command | jq '.field'` and `tool command 2>/dev/null`.

## Interactivity

### Prompts

```text
  Prompt text? [y/N] _
```

Bold prompt, `[y/N]` suffix. Rules:
- Return `false` / skip in non-TTY (CI-safe)
- `--yes` / `-y` flag: auto-confirm all prompts
- Never block on stdin without checking `stdin.is_terminal()`

### Dry-Run

Prefix all hypothetical actions with `[dry-run]` to stderr:

```text
[dry-run] Would create tag: v2.1.0
[dry-run] Would push tag: v2.1.0
```

### TUI Policy

Never use full-screen TUI frameworks (ratatui, bubbletea, textual, blessed). Reasons:
- Breaks piping and shell composition
- Breaks CI / non-interactive environments
- Breaks screen readers and accessibility tools
- Adds heavy dependencies for marginal UX gain

Use inline styled output (the ui module pattern below) for all interactive feedback. For selection, use a numbered list + prompt, not a TUI picker.

## Signal Handling

Trap SIGINT and SIGTERM. On signal: finish current atomic operation, clean up temp files, clear spinner line, exit with correct code.

| Language | Pattern |
|----------|---------|
| Rust | `ctrlc` crate or `tokio::signal`. Set `AtomicBool`, check in loops. |
| Go | `signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)`. Pass `ctx` to all long operations. |
| Python | `signal.signal(SIGINT, handler)` + `threading.Event` for cooperative cancellation. |

## Exit Codes

| Code | Meaning |
|------|---------|
| `0` | Success |
| `1` | Error |
| `2` | No-op / no changes (sr pattern) |
| `130` | Interrupted (SIGINT / Ctrl-C) |
| `143` | Terminated (SIGTERM) |

## Config Systems

| Format | Use | Discovery |
|--------|-----|-----------|
| TOML | User-editable configs | Walk up from cwd looking for `.toml` |
| YAML | Release/CI configs | Fixed name at repo root (`sr.yaml`) |

- Override: `--override key.path=value` (dot-notation)
- Env vars: `TOOL_CONFIG`, `TOOL_VERSION`, etc.

## Output Directories

- **`outputs///`** timestamped results (linear-gp)
- **`showcase/`** demo captures (default output dir for your demo recorder)
- **`bin/`** Go builds, **`target/`** Rust builds

## Visual Style

### Stack by Language

| Language | Styling | Spinners | CLI Parsing |
|----------|---------|----------|-------------|
| Rust | crossterm 0.28 | indicatif 0.17 | clap 4 (derive) |
| Go | lipgloss / termenv | spinner (braille) | cobra |
| Python | rich | rich.progress | typer |

### Color Semantics

Every color has exactly one meaning. Do not deviate.

| Color | Meaning | Used for |
|-------|---------|----------|
| Cyan + bold | Action / heading | Headers, section titles, spinner, indices `[1]` |
| Green + bold | Success | Checkmarks `✓`, file additions `A`, arrows `→` |
| Yellow + bold | Warning / caution | Warnings `⚠`, modifications `M`, skipped `−` |
| Red | Error / destructive | Deletions `D`, error messages |
| Blue | Informational alternate | Renames `R` |
| Dim | Secondary / supporting | Dividers, detail text, tree connectors, timestamps |
| Bold (no color) | Primary content | Commit messages, prompts, emphasis |

### Symbol Set

| Symbol | Color | Purpose |
|--------|-------|---------|
| `✓` | green bold | Phase/step completion |
| `⚠` | yellow bold | Warning |
| `ℹ` | cyan | Informational note |
| `→` | green bold | Item created/produced |
| `−` | yellow | Skipped action |
| `▸` | cyan | Sub-action / tool call |
| `⊘` | dim | Usage/stats |

### Layout

All output is indented **2 spaces** from the terminal edge. Nested content adds **2 more spaces**.

```text
  header text
  ────────────────────────────────────────

  ✓ Phase completed · detail
  ⚠ Warning message
  ℹ Informational note

  SECTION TITLE · count
  ──────────────────────────────────────────────────

  [1] Primary content
   │  Secondary detail
   │
   ├─ A file_added.rs
   └─ M file_modified.rs

  ──────────────────────────────────────────────────
```

### Header

```rust
println!();  // blank line
eprintln!("  {}", cmd.cyan().bold());
eprintln!("  {}", "─".repeat(40).dim());
println!();
```

Cyan bold title, 40-char dim rule, blank lines above and below. All to stderr.

### Spinner

Braille animation, cyan, 80ms tick, 2-space indent:

```rust
ProgressStyle::default_spinner()
    .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
    .template("  {spinner:.cyan} {msg}")
```

When done, replace with `✓` checkmark line via `phase_ok()`.

### Phase Completion

```text
  ✓ Phase name · optional detail
```

Green+bold checkmark. Detail after `·` is dim. Always 2-space indent.

### Tree Visualization

```text
   │   dim vertical connector
   ├─  dim branch connector (non-last item)
   └─  dim last-item connector
```

Tree connectors are always dim. Content after connectors uses semantic colors.

### Progress Tracking

```text
  [1/3] Step description
    ✓ sub-item
    → result
```

Index is cyan+bold. Description is bold. Sub-items indented 4 spaces.

### Warnings, Info, Errors

```text
  ⚠ Warning text          (yellow symbol, yellow text)
  ℹ Info text              (cyan symbol, dim text)
  error: message           (red, to stderr, with anyhow context chain)
```

### Token/Cost Usage

```text
  ⊘ 1.2k in / 3.4k out · $0.0042
```

Dim symbol, dim counts, dim cost. Format: `>=1M` → `1.2M`, `>=1k` → `1.2k`, else raw.

## The ui Module Pattern

Every CLI project has a `ui` module exporting a consistent API. Do not extract a shared crate/library; the modules are 30-60 lines each, and copying is cheaper than cross-repo dependency management.

### Canonical API

| Function | Purpose |
|----------|---------|
| `header(title)` | Cyan bold title + dim rule to stderr |
| `phase_ok(msg, detail?)` | Green checkmark + message to stderr |
| `warn(msg)` | Yellow warning to stderr |
| `info(msg)` | Cyan info note to stderr |
| `error(msg)` | Red error to stderr |
| `spinner(msg) → handle` | Start braille spinner to stderr |
| `confirm(prompt) → bool` | TTY-safe y/N prompt (false in non-TTY) |

### Reference Implementations

- Rust: `sr/crates/sr-ai/src/ui/mod.rs`
- Go: a small `internal/ui/` package wrapping `lipgloss` with `IsTTY()`, `Info/Warn/Error` helpers, and an `output.Render(format, v)` switch on `--format`. ~50 lines.
- Python: `rich.Console(stderr=True)` with matching color semantics

### Rule

All ui output goes to stderr. Use `eprintln!` / `fmt.Fprintf(os.Stderr, ...)` / `Console(stderr=True)`. Never `println!` / `fmt.Println` for UI.

## Install Script Convention

Every CLI tool MUST have `install.sh` at repo root:

- Portable `#!/bin/sh`
- Platform detection: `uname -s` (OS) + `uname -m` (arch)
- Targets: GNU + musl for Linux, Darwin for macOS, MSVC for Windows
- Version override: `${BINARY_VERSION:-}`, fallback to latest release
- Install dir: `$HOME/.local/bin` (override: `${BINARY_INSTALL_DIR:-}`)
- PATH management: detect shell, update rc file
- One-liner: `curl -fsSL https://raw.githubusercontent.com///main/install.sh | bash`

## GitHub Action Pattern

For tools that benefit from CI integration:

- Composite actions (`using: composite`), not JavaScript
- Binary download: detect OS/arch, download from releases
- JSON stdout → `jq -r '.field'` → export as action outputs
- Inputs/outputs in `action.yml`
- Branding: `icon` + `color` for GitHub Marketplace

## Anti-Patterns

| Don't | Do Instead |
|-------|------------|
| ASCII box borders (`═══`, `***`) | Dim `─` rules |
| `println!` without indentation | Always 2-space minimum |
| `info!()` / `warn!()` tracing macros for user output | Styled crossterm/lipgloss/rich output |
| Mixing colors arbitrarily | Follow color semantics table |
| Verbose banners or ASCII art | Clean, minimal headers |
| Raw error dumps | `{e:#}` with anyhow context chains |
| Full-screen TUI frameworks | Inline styled output (ui module) |
| Blocking on stdin without TTY check | Check `is_terminal()`, respect `--yes` |
| Ignoring SIGINT/SIGTERM | Trap signals, clean up, exit 130/143 |
| Swallowing errors in JSON mode | Output `{"error": "..."}` to stdout |

---

# Portfolio Requirements

The conventions above are portable design defaults. The rules below are **mandatory** for every CLI tool in this portfolio so every tool is independently installable, self-updating, and composable with scripts and CI pipelines.

## Argument Parser Selection

| Language | Framework | Notes |
|----------|-----------|-------|
| Rust | clap v4 (derive macros) | `#[derive(Parser)]`, `#[derive(Subcommand)]` |
| Go | Cobra | `rootCmd.AddCommand()`, persistent flags on root |
| Node | Commander v14 | `.command()`, `.option()`, `.addOption()` |
| Python | typer | Decorator-based, consistent with Rust mental model |

Never use stdlib flag parsing (`flag` package in Go, `argparse` in Python) for new tools.

## Self-Update (Mandatory)

**Every CLI tool must have a `update` subcommand** (or `self-update` if `update` is already taken for content management) that replaces the running binary with the latest GitHub release.

### Rust . `agentspec_update` crate

```toml
# workspace Cargo.toml
agentspec-update = "0.6.0"

# CLI crate Cargo.toml
agentspec-update = { workspace = true }
```

```rust
// In Commands enum
/// Self-update to the latest release
Update,

// Handler
Commands::Update => {
    eprintln!("current version: {}", env!("CARGO_PKG_VERSION"));
    match agentspec_update::self_update("/", env!("CARGO_PKG_VERSION"), "")? {
        agentspec_update::UpdateResult::AlreadyUpToDate => {
            eprintln!("already up to date");
        }
        agentspec_update::UpdateResult::Updated { from, to } => {
            eprintln!("updated: {from} → {to}");
        }
    }
    Ok(())
}
```

### Go . GitHub Releases HTTP fetch

Implement `internal/updater/updater.go` with:
1. Fetch latest tag via `gh api repos///releases/latest` (works with github.com and GHES)
2. Compare against current version (injected via `-ldflags`)
3. Construct asset URL from the response's `assets[].browser_download_url` matching `BINARY-OS-ARCH`
4. Download to a temp file, `chmod +x`, then `os.Rename()` over `os.Executable()`
5. Wire as `cobra.Command` on root: `rootCmd.AddCommand(newUpdateCmd(version))`

### Node . npm self-install

```ts
// update command
.command('update')
.description('Update to latest release')
.action(async () => {
  const { execSync } = await import('child_process');
  execSync('npm install -g @/@latest', { stdio: 'inherit' });
});
```

## Output Format Flag

**Rule: `--format json|human` (default: human) for any command that emits structured data.**

- Always-JSON CLIs (device/REST APIs like zigbee-skill): no flag needed . all output is JSON on stdout
- Never use `--json` (boolean) . use the enum form instead
- Never use `--export-json` . that's not composable

### Rust

```rust
#[derive(Clone, clap::ValueEnum)]
enum OutputFormat {
    Human,
    Json,
}

// In Cli struct (global):
#[arg(long, global = true, default_value = "human", value_enum)]
format: OutputFormat,

// In command handler:
match cli.format {
    OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&data)?),
    OutputFormat::Human => { /* styled output to stderr */ }
}
```

### Go

```go
var format string
rootCmd.PersistentFlags().StringVar(&format, "format", "human", "Output format: json|human")

// In command:
if format == "json" {
    enc := json.NewEncoder(os.Stdout)
    enc.SetIndent("", "  ")
    enc.Encode(data)
} else {
    // human output to stderr
}
```

### Node

```ts
.addOption(new Option('--format ', 'Output format').choices(['json', 'human']).default('human'))
```

## Standard Global Flags

| Flag | Type | When to include |
|------|------|-----------------|
| `--format json\|human` | enum | Any tool with structured data output |
| `--verbose` / `-v` | bool | Tools with meaningful progress output |
| `--dry-run` | bool | Mutating tools (release, file modification) |

Do NOT add `--verbose` to always-JSON tools (no human output to make verbose).

## install.sh (Mandatory)

**Every CLI tool must have `install.sh` at the repo root.**

Template (copy from `sr/install.sh`, substitute binary name and prefix):

```sh
#!/bin/sh
# install.sh . Installs BINARY from GitHub releases.
#
# Usage:
#   curl -fsSL https://raw.githubusercontent.com///main/install.sh | sh
#
# Environment variables:
#   PREFIX_VERSION     . version to install (default: latest)
#   PREFIX_INSTALL_DIR . installation directory (default: $HOME/.local/bin)
#   PREFIX_SHA256      . optional SHA256 checksum

set -eu
REPO="/"
# ... (full template in sr/install.sh)
```

Prefix naming: `SR_` → `TEASR_`, `AGENTSPEC_`, `OAG_`, `MNEMONIST_`, `LGP_`, `EMBED_SRC_`, etc.

Platform targets (Rust musl): `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`, `x86_64-apple-darwin`, `aarch64-apple-darwin`.

## Subcommand Conventions

- Max 2 levels of nesting: `tool command subcommand`
- Binary name matches the `[[bin]]` name in `Cargo.toml` or `"bin"` in `package.json`
- Every CLI **must** have both:
  - `--version` flag (auto-provided by clap/cobra/commander)
  - `version` subcommand that prints ` v` to stdout
  - `update` subcommand for self-update (see above; use `self-update` only if `update` is taken at top level for content management)
- **fsrc exception**: uses `run ` subcommand to preserve positional-arg UX while still having `update` and `version` as peers

## Reference Implementations

| Pattern | Reference |
|---------|-----------|
| Self-update (Rust) | `sr/crates/sr-cli/src/main.rs` lines 343-356 |
| `--format json\|human` | `sr/crates/sr-cli/src/main.rs`, `oag/crates/oag-cli/src/main.rs` |
| install.sh | `sr/install.sh` |
| Cobra root setup | inline 20-line example below |
| Go self-update | `saige/internal/updater/updater.go` (after migration) |

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [urmzd](https://github.com/urmzd)
- **Source:** [urmzd/dotfiles](https://github.com/urmzd/dotfiles)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-urmzd-dotfiles-build-cli
- Seller: https://agentstack.voostack.com/s/urmzd
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
