feat: create claude skills for sofmani/wand

This commit is contained in:
2026-03-31 11:52:49 +03:00
parent e77a293ade
commit ed5cbee160
3 changed files with 611 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
---
name: sofmani
description: Add, edit, or manage sofmani installer entries in sofmani.yml. This skill should be used when the user wants to add software to their provisioning manifest, modify existing installer steps, create cross-platform install groups, or troubleshoot sofmani configuration. Triggers on requests involving sofmani.yml, adding packages/tools to the manifest, or provisioning setup.
---
# Sofmani
Work with [sofmani](https://github.com/chenasraf/sofmani) (Software Manifest) — a declarative provisioning tool that automates software installations via YAML config. The config file lives at `~/.dotfiles/.config/sofmani.yml`.
## Quick Reference
### CLI Flags
| Flag | Purpose |
|------|---------|
| `-u` / `-U` | Enable/disable update checking |
| `-d` / `-D` | Enable/disable debug mode |
| `-s` / `-S` | Enable/disable summary |
| `-f <filter>` | Filter installers (see below) |
| `-m` | Show machine ID |
Filter syntax: `-f <name>`, `-f tag:<tag>`, `-f type:<type>`. Negate with `!`: `-f "!tag:system"`.
### Template Variables
Available in shell commands and `opts` string fields:
| Variable | Value |
|----------|-------|
| `{{ .Arch }}` | CPU architecture |
| `{{ .ArchAlias }}` | Architecture alias (e.g. `amd64`) |
| `{{ .OS }}` | Operating system |
| `{{ .DeviceID }}` | Machine unique ID |
| `{{ .DeviceIDAlias }}` | Friendly machine name from `machine_aliases` |
| `{{ .Tag }}` | Release tag (github-release only) |
| `{{ .Version }}` | Version without `v` prefix (github-release only) |
Environment variables `$DEVICE_ID` and `$DEVICE_ID_ALIAS` are also injected into shell commands.
## Installer Entry Reference
Every entry in the `install` array supports these fields:
| Field | Type | Purpose |
|-------|------|---------|
| `name` | string (required) | Step identifier |
| `type` | string (required) | Installer type |
| `tags` | string | Space-separated tags for filtering |
| `bin_name` | string | Binary name if different from `name` |
| `enabled` | bool/string | Conditional execution (string = shell command) |
| `platforms` | object | `only` / `except` arrays: `macos`, `linux`, `windows` |
| `machines` | object | `only` / `except` arrays of machine alias names |
| `check_installed` | string | Shell command to verify installation |
| `check_has_update` | string | Shell command to check for updates |
| `pre_install` | string | Shell hook before install |
| `post_install` | string | Shell hook after install |
| `pre_update` | string | Shell hook before update |
| `post_update` | string | Shell hook after update |
| `skip_summary` | bool/object | Exclude from summary (`true`, or `{ install: true, update: true }`) |
| `env_shell` | object | Platform-specific shell override (e.g. `{ linux: /bin/bash }`) |
| `opts` | object | Type-specific options (see below) |
## Installer Types
### brew
Homebrew package. The project defaults restrict brew to macOS only — no need to add `platforms` for brew entries.
```yaml
- name: ripgrep
type: brew
```
With tap:
```yaml
- name: sofmani
type: brew
opts:
tap: chenasraf/tap
```
opts: `tap`, `cask` (bool).
### group
Sequence multiple steps. The primary pattern for cross-platform installs: brew on macOS + github-release or apt on Linux.
```yaml
- name: lazygit
type: group
steps:
- name: lazygit
type: brew
- name: lazygit
type: github-release
platforms:
only: ['linux']
opts:
repository: jesseduffield/lazygit
strategy: tar
destination: ~/.local/bin
download_filename: lazygit_{{ .Version }}_Linux_{{ .ArchAlias }}.tar.gz
```
### shell
Execute arbitrary shell commands. The most flexible type.
```yaml
- name: git-config
type: shell
check_installed: git config --global user.name > /dev/null 2>&1
opts:
command: |
git config --global user.name "Name"
git config --global user.email "email@example.com"
update_command: <same or different command for updates>
```
opts: `command` (required), `update_command`.
### git
Clone a git repository.
```yaml
- name: my-plugin
type: git
opts:
repository: https://github.com/user/repo.git
destination: ~/.local/share/plugins/repo
ref: main
```
opts: `repository` (required), `destination` (required), `ref`.
GitHub shorthand: `repository: user/repo` expands to `https://github.com/user/repo.git`.
### github-release
Download a binary from GitHub releases.
```yaml
- name: tool
type: github-release
opts:
repository: user/tool
destination: ~/.local/bin
strategy: tar # or: binary, zip
download_filename: tool_{{ .Version }}_Linux_{{ .ArchAlias }}.tar.gz
```
opts: `repository` (required), `destination` (required), `strategy` (`tar`/`binary`/`zip`), `download_filename` (supports template vars including `{{ .Version }}`, `{{ .Tag }}`, `{{ .Arch }}`, `{{ .ArchAlias }}`).
### manifest
Load an external sofmani config file (local or from a git repo).
```yaml
- name: lazygit
type: manifest
opts:
source: git@github.com/chenasraf/sofmani.git
path: docs/recipes/lazygit.yml
```
opts: `source`, `path`.
### apt
Debian/Ubuntu package manager.
```yaml
- name: stow
type: apt
platforms:
only: ['linux']
```
### npm / pnpm / yarn
Node.js package managers. Install global packages.
```yaml
- name: typescript
type: pnpm
opts:
global: true
```
### pipx
Python tool installer.
```yaml
- name: black
type: pipx
```
### cargo
Rust package installer.
```yaml
- name: ripgrep
type: cargo
```
### rsync
File synchronization.
```yaml
- name: sync-config
type: rsync
opts:
source: ./config/
destination: ~/.config/app/
```
### docker
Pull and optionally run containers.
```yaml
- name: my-service
type: docker
opts:
image: nginx:latest
```
## Common Patterns
### Cross-platform group (brew + github-release)
The standard pattern for CLI tools. Brew handles macOS, github-release handles Linux:
```yaml
- name: tool-name
type: group
steps:
- name: tool-name
type: brew
- name: tool-name
type: github-release
platforms:
only: ['linux']
opts:
repository: owner/tool-name
destination: ~/.local/bin
strategy: tar
download_filename: tool-name-linux-{{ .Arch }}.tar.gz
```
### Cross-platform group (brew + apt)
For packages available in both package managers:
```yaml
- name: stow
type: group
steps:
- name: stow
type: brew
- name: stow
type: apt
platforms:
only: ['linux']
```
### Config installer with idempotency
Use `check_installed` and `check_has_update` for shell installers that manage config:
```yaml
- name: tmux-config
type: shell
tags: config tmux
enabled: test -f "$HOME/.config/tmux_{{ .DeviceIDAlias }}.yml"
check_installed: test -f ~/.config/tmux_local.yml
check_has_update: '! diff -q "$HOME/.config/tmux_{{ .DeviceIDAlias }}.yml" ~/.config/tmux_local.yml > /dev/null 2>&1'
opts:
command: cp "$HOME/.config/tmux_{{ .DeviceIDAlias }}.yml" ~/.config/tmux_local.yml
update_command: cp "$HOME/.config/tmux_{{ .DeviceIDAlias }}.yml" ~/.config/tmux_local.yml
```
### Machine-specific installer
Restrict to specific machines using aliases defined in `machine_aliases`:
```yaml
- name: glab
type: brew
machines:
only: ['planck']
```
## Working with the Config File
- The config file is at `~/.dotfiles/.config/sofmani.yml` and symlinked to `~/.config/sofmani.yml` via stow.
- Read the full file before making changes to understand existing structure and conventions.
- New entries should be placed in the appropriate category section (marked by comment headers).
- Follow the existing indentation and style conventions in the file.
- When adding a new tool, check if a similar entry already exists that can be extended.
- For detailed installer type documentation, see `references/installer-types.md`.

View File

@@ -0,0 +1,148 @@
# Sofmani Installer Types — Detailed Reference
Full documentation for each installer type's `opts` fields, behavior, and edge cases.
## shell
Runs arbitrary shell commands. Most flexible type.
**opts:**
- `command` (string, required): Shell command to run for installation
- `update_command` (string): Shell command for updates (defaults to `command` if omitted)
**Behavior:**
- Uses the system shell (overridable with `env_shell`)
- Template variables are expanded in commands
- `$DEVICE_ID` and `$DEVICE_ID_ALIAS` env vars are injected
## group
Orchestrates a sequence of sub-installers. The group itself has no `opts`; configuration lives on each child step.
**Fields:**
- `steps` (array, required): Array of installer entries (same schema as top-level `install`)
- `post_install` / `pre_install`: Hooks run before/after the entire group
**Behavior:**
- Steps execute sequentially
- If any step fails, subsequent steps are skipped
- The group is considered "installed" if all steps report installed
## git
Clones a git repository.
**opts:**
- `repository` (string, required): Git URL or GitHub shorthand (`user/repo`)
- `destination` (string, required): Local clone path
- `ref` (string): Branch, tag, or commit to check out
**Behavior:**
- GitHub shorthand `user/repo` expands to `https://github.com/user/repo.git`
- If destination exists, performs `git pull` on update
- Supports template variables in all string opts
## github-release
Downloads assets from GitHub releases.
**opts:**
- `repository` (string, required): GitHub `owner/repo`
- `destination` (string, required): Directory to extract/copy to
- `strategy` (string): `tar` (extract tar.gz), `binary` (direct binary), `zip` (extract zip)
- `download_filename` (string): Asset filename pattern with template variables
- `github_token` (string): GitHub API token for private repos / rate limits
**Template variables available:**
- `{{ .Tag }}`: Full release tag (e.g. `v1.2.3`)
- `{{ .Version }}`: Tag without `v` prefix (e.g. `1.2.3`)
- `{{ .Arch }}`: Raw architecture string
- `{{ .ArchAlias }}`: Normalized architecture (e.g. `amd64`, `arm64`)
- `{{ .OS }}`: Operating system
**Behavior:**
- Automatically finds the latest release
- Compares installed version to latest for update detection
## manifest
Loads and executes an external sofmani config file.
**opts:**
- `source` (string): Git repository URL for remote manifests
- `path` (string): Path to the manifest file (relative to repo root for remote, or absolute/relative for local)
**Behavior:**
- Remote manifests are cloned/cached locally
- The loaded manifest's `install` array is executed inline
- Useful for sharing common recipes across machines
## rsync
Synchronizes files/directories.
**opts:**
- `source` (string, required): Source path
- `destination` (string, required): Destination path
- Additional rsync flags can be set
**Behavior:**
- Uses rsync under the hood
- `verbose: true` in defaults enables `-v` flag
## brew
Homebrew package manager.
**opts:**
- `tap` (string): Custom tap to install from (e.g. `chenasraf/tap`)
- `cask` (bool): Install as a cask instead of formula
**Behavior:**
- The project's global defaults restrict brew to `platforms: { only: ['macos'] }`
- No need to add platform restriction on individual brew entries
- Taps are added automatically before install
## npm / pnpm / yarn
Node.js package managers.
**opts:**
- `global` (bool): Install globally
**Behavior:**
- Defaults to global installation in most configurations
## apt
Debian/Ubuntu package manager.
No special opts. Always requires `platforms: { only: ['linux'] }` unless in a group that already restricts.
## apk
Alpine Linux package manager. Same behavior as apt.
## pacman / yay
Arch Linux package managers. Yay is an AUR helper that wraps pacman.
## pipx
Python application installer. Installs Python CLI tools in isolated environments.
No special opts — uses the package `name` directly.
## cargo
Rust package installer.
No special opts — uses the package `name` directly.
## docker
Docker container management.
**opts:**
- `image` (string, required): Docker image to pull
- Additional run configuration as needed

View File

@@ -0,0 +1,157 @@
---
name: wand
description:
Refactor shell functions and aliases into wand YAML configs. This skill should be used when the
user wants to extract shell functions, aliases, or scripts into a wand.yml command runner config,
create new wand configs, or add commands to existing wand configs. Triggers on requests like "move
these functions to wand", "create a wand config for X", "refactor this script into wand commands".
---
# Wand Refactor
Extract shell functions and aliases into [wand](https://github.com/chenasraf/wand) YAML configs,
replacing inline logic with declarative command definitions and thin alias wrappers.
## Wand Config Reference
Wand is a YAML-driven command runner. Config files are auto-discovered from CWD upward, `~/`, and
`~/.config/`. A custom path can be specified via `--wand-file <path>` or `WAND_FILE=<path>`.
### Command Fields
| Field | Type | Purpose |
| ----------------- | -------------------- | ------------------------------------ |
| `description` | `string` | Help text shown in `--help` |
| `cmd` | `string` | Shell command to execute |
| `children` | `map[string]Command` | Nested subcommands |
| `flags` | `map[string]Flag` | Custom typed flags |
| `env` | `map[string]string` | Environment variables |
| `working_dir` | `string` | Execution directory |
| `aliases` | `string[]` | Alternate command names |
| `confirm` | `bool` or `string` | Confirmation prompt before execution |
| `confirm_default` | `string` | Default answer for confirm |
### Flag Fields
| Field | Type | Purpose |
| ------------- | -------- | ------------------------------------------- |
| `alias` | `string` | Single-letter shorthand (e.g. `o` for `-o`) |
| `description` | `string` | Description shown in `--help` |
| `default` | `any` | Default value |
| `type` | `string` | `"bool"` for boolean flags, omit for string |
Flag values are accessible as `$WAND_FLAG_<NAME>` env vars (uppercased, hyphens become underscores).
### Positional Arguments
Extra CLI arguments are available as `$1`, `$2`, `$@` in the command's `cmd`.
## Refactoring Process
### Step 1: Analyze the Source
Read the source file(s) containing the shell functions/aliases to refactor. Identify:
- **Command groups**: Functions that share a common prefix or domain (e.g. `nc-dev-*`, `nc-aio-*`)
- **Shared state**: Variables, config paths, or logic used across multiple functions
- **Modal behavior**: Functions that differ only by a mode/target (e.g. dev vs aio) — these become a
single command with a flag
- **Subcommand hierarchies**: Related commands that naturally nest (e.g.
`db-proxy start`/`db-proxy stop`)
### Step 2: Design the Wand Config
Map the analyzed functions to wand commands following these principles:
1. **Merge modal variants into flags**: If two functions differ only by target (e.g. `nc-dev-occ` vs
`nc-aio-occ`), create one command with a `--<mode>` flag (default to the more common mode).
2. **Use `children` for related pairs**: Commands that are natural opposites (start/stop,
enable/disable, backup/restore) belong as children of a parent command.
3. **Use `env` for shared config**: Constants like paths, container names, etc. go in the `env`
field rather than hardcoded in `cmd`.
4. **Use `working_dir`** instead of `pushd`/`popd` or `cd`.
5. **Use `confirm`** for destructive or long-running operations.
6. **Keep commands self-contained**: Each command's `cmd` must be independently runnable — do not
call other wand commands or rely on shell aliases being available.
7. **Add `set -euo pipefail`** at the top of multi-line commands that should fail fast.
8. **Use `aliases`** for common shorthand names within wand itself.
### Step 3: Choose Config File Location
- If adding to the existing global wand config: edit `~/.dotfiles/.config/wand.yml`
- If creating a domain-specific config (preferred for large command sets): create
`~/.dotfiles/.config/wand/<domain>.yml` and define an alias:
`alias <shortname>="wand --wand-file \$HOME/.config/wand/<domain>.yml"`
After creating or modifying a config file in `~/.dotfiles/.config/`, run
`stow -v -d $DOTFILES -t ~ .` to symlink it.
### Step 4: Create Aliases
Replace the original shell file with thin aliases that point to wand commands. This preserves
backward compatibility with existing muscle memory.
Alias conventions:
- Define a **base alias** for the wand config (e.g.
`alias nxc="wand --wand-file $HOME/.config/wand/nextcloud.yml"`)
- Map each old function/alias to `<base> <command> [--flags] [--]`
- Append `--` before positional args when the command has flags, to prevent flag/arg ambiguity
- Keep old alias names working so existing scripts and habits are preserved
### Step 5: Clean Up
- Remove the original shell functions from the source file, keeping only the alias definitions
- Remove any global variables that were only used by the extracted functions (they now live in `env`
fields)
- If the source file becomes aliases-only, consider whether it should stay as-is or merge into
`aliases.zsh`
## Example: Before and After
### Before (shell functions)
```zsh
APP_DIR="$HOME/myapp"
my-build() { pushd $APP_DIR; make build; popd; }
my-test() { pushd $APP_DIR; make test; popd; }
my-deploy() {
echo "Deploying..."
pushd $APP_DIR; make deploy ENV=$1; popd
}
alias my-deploy-prod="my-deploy prod"
alias my-deploy-staging="my-deploy staging"
```
### After (wand.yml + aliases)
```yaml
# ~/.dotfiles/.config/wand/myapp.yml
build:
description: Build the project
working_dir: ~/myapp
cmd: make build
test:
description: Run tests
working_dir: ~/myapp
cmd: make test
deploy:
description: Deploy the project
working_dir: ~/myapp
confirm: Deploy to $1?
cmd: |
echo "Deploying..."
make deploy ENV=$1
```
```zsh
# aliases
alias myapp="wand --wand-file \$HOME/.config/wand/myapp.yml"
alias my-build="myapp build"
alias my-test="myapp test"
alias my-deploy="myapp deploy --"
alias my-deploy-prod="myapp deploy prod"
alias my-deploy-staging="myapp deploy staging"
```