Skill: yzhong52/ascii-chord (version 2.0.4)

--- name: guitar-chord description: Show ASCII guitar chord diagrams using the ascii_chord CLI tool. Use when asked how to play a guitar chord, or to show chord charts/diagrams for any chord name (e.g. E, B7, Am, C, G, Dm, etc.). Requires cargo (Rust toolchain) to be installed. The source code is bundled with this skill — build it with cargo from the skill directory. metadata: openclaw: requires: bins: - cargo install: - type: shell label: "Post-install init (gitignore + pre-warm build)" script: "scripts/init.sh" note: "Creates .gitignore for /target and pre-builds the binary. Run once after install." sideEffects: note: > cargo build creates a target/ directory under the skill's source folder. These are normal Rust toolchain side effects and persist beyond a single invocation. If the Rust toolchain is not installed, installing rustup will modify the user's home directory (~/.cargo, ~/.rustup) and may update PATH. --- # guitar-chord Display ASCII guitar chord diagrams using [ascii_chord](https://github.com/ascii-music/ascii_chord) — an open-source Rust CLI (MIT license, authored by the same person as this skill). The source code is **bundled with this skill** — no cloning needed. ## Required Tools | Tool | Purpose | Check | |---|---|---| | **cargo / Rust** | Build and run the CLI | `cargo --version` | ### Installing Rust (if not already installed) ```bash # macOS (Homebrew — recommended) brew install rustup-init && rustup-init ``` Or download from [rustup.rs](https://rustup.rs). > **Note:** Installing Rust via rustup creates `~/.cargo` and `~/.rustup` in your home directory and may modify your shell `PATH`. ## Post-Install Setup After installing, run the init script once: ```bash bash ~/.openclaw/workspace/skills/ascii-chord/scripts/init.sh ``` This will: 1. Create a `.gitignore` in the skill directory to exclude cargo build artifacts (`/target`) from git 2. Pre-warm the cargo build cache so the first chord lookup is fast ## First Run Warning If you skip the init script, the **first `cargo run` will take 30–60 seconds** while cargo compiles the binary. This is normal — subsequent runs are fast (binary is cached in `target/`). Do not interrupt the first build. ## Diagram Legend ``` ✕ = mute this string (don't play) ◯ = open string (play unfretted) ● = fret this position (filled dot) ═ = nut (top of fretboard) ─ = fret wire │ = string ``` Numbers on the left (when shown) indicate the starting fret position for higher-up chords. ## Usage The skill directory contains the full Rust source. Run from there: **Single chord:** ```bash cd <skill_dir> && cargo run -- get <CHORD> 2>/dev/null ``` **Multiple chords side by side:** ```bash cd <skill_dir> && cargo run -- list <CHORD1> <CHORD2> ... 2>/dev/null ``` **List all supported chords:** ```bash cd <skill_dir> && cargo run -- all 2>/dev/null ``` > Replace `<skill_dir>` with the path where this skill is installed (e.g. `~/.openclaw/workspace/skills/ascii-chord`). ## Examples ```bash # Single chord cd ~/.openclaw/workspace/skills/ascii-chord && cargo run -- get Am 2>/dev/null # Multiple side by side (great for progressions) cd ~/.openclaw/workspace/skills/ascii-chord && cargo run -- list C G Am F 2>/dev/null # Full list of all supported chord names cd ~/.openclaw/workspace/skills/ascii-chord && cargo run -- all 2>/dev/null ``` ## Discovering Supported Chords Not all chord voicings are supported. If a user asks for a chord that fails, use `all` to list every supported chord name and find the closest match: ```bash cd ~/.openclaw/workspace/skills/ascii-chord && cargo run -- all 2>/dev/null ``` ## Notes - Suppress build warnings with `2>/dev/null` - Chord names are case-sensitive (`Am` not `am`, `B7` not `b7`) - After first build, subsequent runs are fast (binary cached by cargo in `target/`) - Source repo: https://github.com/ascii-music/ascii_chord (MIT licensed)

path: CLAUDE.md
content: # ascii_chord — Claude Notes

## What this project is

A Rust CLI (`aschord`) that renders ASCII guitar chord diagrams in the terminal.

## Commands

```
aschord get G                          # single chord
aschord list C G7 Am E                 # row of chords
aschord list Bm --style full-names     # alternate name styles: short-names, full-names, both-names
aschord list -p 1 C E                  # padding between chords (default 4)
aschord all                            # print all chords
aschord all --save                     # write all_supported_chords.md (regenerate after chord changes)
```

Dev equivalents: `cargo run -- list C G Am E`, etc.

## Architecture

```
main.rs       — entry point, clap parsing
commands.rs   — GetArgs / ListArgs / AllArgs subcommands
chords.rs     — ALL_CHORDS static array + ALL_CHORDS_BY_SHORT_NAMES HashMap
chord.rs      — Chord struct, make_fretboard(), rendering logic
stitcher.rs   — row() — lays out multiple chords side by side into a 2D buffer
```

## How the fretboard renders (chord.rs)

`make_fretboard(num_frets)` generates the ASCII template dynamically. Each line (including `\n`) is exactly **12 chars wide**, giving a stride of **24 chars per fret** in the flat char array:

- Fret 1 → char offset 24 (`24 * 1`)
- Fret 2 → char offset 48 (`24 * 2`)
- Fret N → char offset `24 * N`

String positions within a fret row: string `i` → column `i * 2`.

`Chord.pattern` is a 6-char string: `x` = muted, `0` = open, `1`–`5` = fret number.

`Chord.fretboard()` auto-sizes to `self.max_fret()`. `fretboard_n(n)` renders at an explicit height (used by `stitcher::row()` so all chords in a row share the same height).

## Adding a new chord

Add a line to `ALL_CHORDS` in `chords.rs`:

```rust
Chord::new(&["ShortName"], "pattern", &["Full Name"], capo_option, barre_option),
```

Then regenerate `all_supported_chords.md`:

```
cargo run -- all --save
```

## After any rendering change

Update the examples in `readme.md` by running each command shown there and pasting the output.



path: Cargo.toml
content: [package]
name = "aschord"
version = "0.3.5"
authors = ["Yuchen Zhong <[REDACTED_SECRET]>"]
license = "MIT OR Apache-2.0"
description = "A command line tool for showing how to play guitar chords"
readme = "README.md"
homepage = "https://github.com/yzhong52/ascii_chords"
repository = "https://github.com/yzhong52/ascii_chords"
keywords = ["cli", "ascii", "guitar", "chords"]
categories = ["command-line-utilities"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.5.60", features = ["derive"] }
itertools = "0.14.0"
once_cell = "1.21.3"


path: readme.md
content: [![CI](https://github.com/yzhong52/ascii_chord/actions/workflows/rust.yml/badge.svg)](
  https://github.com/yzhong52/ascii_chord/actions?query=workflow%3Arust
)


# Ascii Chord

A command line tool for showing how to play guitar chords

## Usage

For a single chord:

```
$ aschord get G

This is how you play 'G' chord: 
    ◯ ◯ ◯  
╒═╤═╤═╤═╤═╕
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
├─┼─┼─┼─┼─┤
◯ │ │ │ │ ◯
└─┴─┴─┴─┴─┘
```

For multiple chords:

```
$ aschord list Em Esus4 Asus2 F
Em             Esus|Esus4     Asus2          F          
◯     ◯ ◯ ◯    ◯       ◯ ◯    ✕ ◯     ◯ ◯               
╒═╤═╤═╤═╤═╕    ╒═╤═╤═╤═╤═╕    ╒═╤═╤═╤═╤═╕    ╒═╤═╤═╤═╤═╕
│ │ │ │ │ │    │ │ │ │ │ │    │ │ │ │ │ │    ◯―――――――◯―◯
├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤
│ ◯ ◯ │ │ │    │ ◯ ◯ ◯ │ │    │ │ ◯ ◯ │ │    │ │ │ ◯ │ │
├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤
│ │ │ │ │ │    │ │ │ │ │ │    │ │ │ │ │ │    │ ◯ ◯ │ │ │
└─┴─┴─┴─┴─┘    └─┴─┴─┴─┴─┘    └─┴─┴─┴─┴─┘    └─┴─┴─┴─┴─┘
```

For different ways to play the same chord:

```
$ aschord list Bm --style full-names
B minor          B minor (no bar)
✕                ✕ ✕             
╒═╤═╤═╤═╤═╕      ╒═╤═╤═╤═╤═╕     
│ │ │ │ │ │      │ │ │ │ │ │     
├─┼─┼─┼─┼─┤      ├─┼─┼─┼─┼─┤     
――◯―――――――◯      │ │ │ │ │ ◯     
├─┼─┼─┼─┼─┤      ├─┼─┼─┼─┼─┤     
│ │ │ │ ◯ │      │ │ │ │ ◯ │     
├─┼─┼─┼─┼─┤      ├─┼─┼─┼─┼─┤     
│ │ ◯ ◯ │ │      │ │ ◯ ◯ │ │     
└─┴─┴─┴─┴─┘      └─┴─┴─┴─┴─┘     
```

For multiple chords with full names:

```
$ aschord list Em Esus4 Asus2 F --style full-names
E minor                        E suspended|E suspended 4ᵗʰ    A suspended 2ⁿᵈ                F          
◯     ◯ ◯ ◯                    ◯       ◯ ◯                    ✕ ◯     ◯ ◯                               
╒═╤═╤═╤═╤═╕                    ╒═╤═╤═╤═╤═╕                    ╒═╤═╤═╤═╤═╕                    ╒═╤═╤═╤═╤═╕
│ │ │ │ │ │                    │ │ │ │ │ │                    │ │ │ │ │ │                    ◯―――――――◯―◯
├─┼─┼─┼─┼─┤                    ├─┼─┼─┼─┼─┤                    ├─┼─┼─┼─┼─┤                    ├─┼─┼─┼─┼─┤
│ ◯ ◯ │ │ │                    │ ◯ ◯ ◯ │ │                    │ │ ◯ ◯ │ │                    │ │ │ ◯ │ │
├─┼─┼─┼─┼─┤                    ├─┼─┼─┼─┼─┤                    ├─┼─┼─┼─┼─┤                    ├─┼─┼─┼─┼─┤
│ │ │ │ │ │                    │ │ │ │ │ │                    │ │ │ │ │ │                    │ ◯ ◯ │ │ │
└─┴─┴─┴─┴─┘                    └─┴─┴─┴─┴─┘                    └─┴─┴─┴─┴─┘                    └─┴─┴─┴─┴─┘
```

Here is an example of using a [bar chord](https://en.wikipedia.org/wiki/Bar_chord) on the first (1st) fret for F and a [capo](https://en.wikipedia.org/wiki/Capo_(musical_device)) placed on the fourth (4th) fret for G#:

```
$ aschord list F G#
F              G#         
               ◯       ◯ ◯
╒═╤═╤═╤═╤═╕    ╒𝟺╤𝟺╤𝟺╤𝟺╤𝟺╕
◯―――――――◯―◯    │ │ │ ◯ │ │
├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤
│ │ │ ◯ │ │    │ ◯ ◯ │ │ │
├─┼─┼─┼─┼─┤    ├─┼─┼─┼─┼─┤
│ ◯ ◯ │ │ │    │ │ │ │ │ │
└─┴─┴─┴─┴─┘    └─┴─┴─┴─┴─┘
```

For display purposes, one can adjust the amount of space padding between displayed chords using the `-p` short option or the longer `--padding` option:
```
$ aschord list -p 1 C E
C           E          
✕     ◯   ◯ ◯       ◯ ◯
╒═╤═╤═╤═╤═╕ ╒═╤═╤═╤═╤═╕
│ │ │ │ ◯ │ │ │ │ ◯ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
│ │ ◯ │ │ │ │ ◯ ◯ │ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │ │ │ │ │ │ │
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘

```

For all support chords:

```
$ aschord all
```

Also see all supported chords [here](all_supported_chords.md) (auto-generated, do not edit).

## Installation

Install local version:

```
git clone [REDACTED_SECRET]:yzhong52/ascii_chord.git
cd ascii_chord
cargo install --path .
```

Install from <https://crates.io/crates/aschord>:

```
cargo install aschord
```

## Development

### Build & Run

Show a single chord:

```
cargo run -- G
```

Show multiple chords:

```
cargo run -- list Em Esus4 Asus2 Fm D Am
```

Show all chords:

```
cargo run -- all
```

### Unit Tests

```
cargo test
```

### Release

```
cargo publish
```

## Clawhub

Available on clawhub here: https://clawhub.ai/yzhong52/ascii-chord



path: scripts/init.sh
content: #!/usr/bin/env bash
# init.sh — post-install setup for ascii-chord skill
# Run once after installing the skill into your workspace.

set -e

SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
echo "Initializing ascii-chord skill at: $SKILL_DIR"

# 1. Create .gitignore to exclude cargo build artifacts
GITIGNORE="$SKILL_DIR/.gitignore"
if [ ! -f "$GITIGNORE" ]; then
  echo "/target" > "$GITIGNORE"
  echo "✔ Created .gitignore (excludes /target)"
else
  if ! grep -q "^/target" "$GITIGNORE"; then
    echo "/target" >> "$GITIGNORE"
    echo "✔ Added /target to existing .gitignore"
  else
    echo "✔ .gitignore already configured"
  fi
fi

# 2. Pre-warm cargo build cache
echo "Building ascii-chord (first build may take 30-60 seconds)..."
cd "$SKILL_DIR"
cargo build --release 2>/dev/null && echo "✔ Build complete — subsequent runs will be fast"

echo ""
echo "ascii-chord is ready! Try: cargo run -- get Am"


path: src/main.rs
content: extern crate clap;
extern crate itertools;
extern crate once_cell;

mod chord;
mod chords;
mod commands;
mod stitcher;

use clap::Parser;
use commands::Args;

fn main() {
    Args::parse().run();
}


path: src/stitcher.rs
content: use chord::{Chord, make_fretboard};
use clap::ValueEnum;
use itertools::join;
use std::cmp::max;

#[derive(Debug, ValueEnum, Clone)]
pub enum NameStyle {
    ShortNames,
    FullNames,
    BothNames,
}

pub fn row<'a>(chords: Vec<Chord<'a>>, name_style: NameStyle, padding: u8) -> String {
    let display_names: Vec<String> = chords
        .iter()
        .map(|chord| match name_style {
            NameStyle::ShortNames => join(chord.short_names.iter(), "|"),
            NameStyle::FullNames => join(chord.names.iter(), "|"),
            NameStyle::BothNames => chord.both_names(),
        })
        .collect();

    let max_display_name_width = display_names.iter().map(|name| name.len()).max().unwrap();

    let num_frets = std::cmp::max(1, chords.iter().map(|c| c.max_fret()).max().unwrap_or(1));
    let fretboard = make_fretboard(num_frets);

    let num_chords = chords.len();
    let board: Vec<&str> = fretboard.split('\n').collect();
    let board_width = board[0].chars().count();

    // The 'padding' between chords horizontally
    // Minimum `padding` spaces between chords
    // Minimum 1 space between titles
    let pad: usize = max(
        padding as i32,
        max_display_name_width as i32 - board_width as i32 + 1,
    ) as usize;

    // We need to make sure the last one has enough additional padding for the title
    let last_padding = max(
        0,
        display_names.last().unwrap().len() as i32 - board_width as i32,
    ) as usize;

    let width = (board_width + pad) * num_chords - pad + last_padding;

    // +1 for the label - name of chord
    let height = board.len() + 1;

    let mut buffer = vec![vec![' '; width]; height];

    // Print the names of the chords
    for (i, display_name) in display_names.iter().enumerate() {
        for (char_id, char) in display_name.chars().enumerate() {
            buffer[0][char_id + i * (board_width + pad)] = char;
        }
    }

    // Print the chord diagram
    for (i, chord) in chords.iter().enumerate() {
        let diagram: Vec<String> = chord
            .fretboard_n(num_frets)
            .split('\n')
            .map(|line| line.to_owned())
            .collect();

        for (line_id, line) in diagram.iter().enumerate() {
            for (char_id, char) in line.chars().enumerate() {
                buffer[line_id + 1][char_id + i * (board_width + pad)] = char;
            }
        }
    }

    let lines: Vec<String> = buffer
        .into_iter()
        .map(|line| line.into_iter().collect())
        .collect();
    let result: String = lines.join("\n");
    result
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)