fishbone
fishbone — Fish Script Writing Skill
For AI agents (Copilot, Claude Code, etc.)
Decision: source the library or inline?
Apply this rule every time you write a Fish script:
| Condition | Action |
|---|---|
| Script uses 3 or more library functions | source /path/to/skeleton.fish and call them directly |
Script uses lifecycle functions (script:initialize, script:safe_exit, io:die) |
Always source — these depend on internal state |
Script uses only 1–2 simple, stateless helpers (str:trim, str:lower, str:column, str:row, str:md5, utility:round) |
Inline a minimal adapted version without sourcing |
| Script is a one-liner or short pipeline | Inline or use native Fish builtins |
Inline-safe functions (stateless, no library deps)
These can be copied verbatim or lightly adapted when sourcing the whole library is overkill. Copy only the functions you actually need.
# str:trim — remove leading/trailing whitespace
function str:trim
if test (count $argv) -gt 0
printf '%s' $argv | string trim
else
string trim
end
end
# str:lower — lowercase
function str:lower
if test (count $argv) -gt 0
printf '%s' $argv | string lower
else
string lower
end
end
# str:upper — uppercase
function str:upper
if test (count $argv) -gt 0
printf '%s' $argv | string upper
else
string upper
end
end
# str:column <n> [delim] — extract column n from piped input
function str:column
set -l col $argv[1]
set -l sep (if test -n "$argv[2]"; echo $argv[2]; else; echo ' '; end)
awk -F"$sep" "{print \$$col}"
end
# str:row <n> — extract row n from piped input
function str:row
sed -n "$argv[1]p"
end
# utility:round <n> [decimals] — round a number
function utility:round
set -l num $argv[1]
set -l dec (if test -n "$argv[2]"; echo $argv[2]; else; echo 0; end)
printf '%.*f\n' $dec $num
end
Standard script template (full library)
Use this when sourcing the library is appropriate:
#!/usr/bin/env fish
source /path/to/skeleton.fish # adjust path as needed
set LOG_DIR /tmp/myscript/log
set TMP_DIR /tmp/myscript/tmp
script:initialize # sets up io, housekeeping, log/tmp dirs
system:load_env # load .env files if present
system:require jq # declare external dependencies early
# --- script logic ---
set tmpfile (system:tempfile json)
io:announce "Fetching data..."
curl -s "$API_BASE/items" > $tmpfile
io:success "Done"
script:safe_exit # clean up temp files and exit
Function quick-reference
io: — user-facing output
io:print "Normal line (respects QUIET)"
io:debug "Debug line to stderr (shown when VERBOSE=0)"
io:alert "Warning to stderr, script continues"
io:success "Success message with icon"
io:announce "Slow-step notice + 1s pause"
io:progress "In-place status line (loop-safe)"
io:countdown 5 "Deleting in" # countdown before destructive step
io:confirm # [y/N] prompt; auto-yes when FORCE=0
io:ask "Prompt text" default_value # read a value; capture with $()
io:log "Timestamped line" # appends to $log_file
io:die "Fatal message" # print + safe_exit
str: — string helpers
echo " hello " | str:trim # strip whitespace
str:trim " hello " # same, via arg
echo "Hello" | str:lower # lowercase
str:upper "hello" # uppercase
echo "crème" | str:ascii # strip diacritics
str:slugify "Jack & Jill" "-" # jack-jill
str:title "jack and jill" # JackAndJill
echo "hello" | str:md5 # MD5, 10 chars
echo "hello" | str:md5 6 # MD5, 6 chars
echo "a b c" | str:column 2 # b
echo "a,b,c" | str:column 2 "," # b
printf "x\ny\nz\n" | str:row 2 # y
system: — OS / filesystem
set f (system:tempfile json) # unique temp file; auto-cleaned on exit
system:folder /tmp/app/log 30 # create dir; purge files older than 30d
set p (system:follow_link ./link) # resolve symlink
system:load_env # source .env files near script
system:notify "Done" "MyScript" # desktop notification
sleep 5 &; system:busy $last_pid "…" # spinner while background pid runs
system:require jq # assert binary exists or exit
system:require convert "brew install imagemagick" # with install hint
utility: — numeric helpers
utility:round 3.14159 2 # → 3.14
set t0 (utility:time) # high-res timestamp
utility:throughput $t0 100 files # print ops/sec since t0
script: — lifecycle
script:initialize # preferred: io:init + housekeeping + log/tmp setup
script:safe_exit # clean temp files and exit
script:meta # print runtime summary (version, OS, git, hash)
script:check_version # warn if upstream git updates exist
script:show_required ./deploy.fish # list system:require deps in a file
option: — option parsing helper
# Define option:config in your script, then call option:filter
function option:config
printf '%s\n' \
'flag|--verbose|-v|Show verbose output' \
'value|--output|-o|Output file path'
end
option:filter flag # returns matching option rows
Key behavioral rules
-
Boolean globals use exit-code style:
0= enabled/true,1= disabled/false.FORCE=0→ auto-confirm prompts and auto-install missing packagesQUIET=0→ suppressio:printoutputVERBOSE=0→ showio:debugoutput
-
Capture return values with
$(...):set answer (io:ask "Name" default) set tmpfile (system:tempfile json) -
Pipeline helpers read from stdin when no args given:
echo " hello " | str:trim -
system:folderonly cleans paths containinglog,temp, ortmp— safe guard against accidental deletions. -
Always end scripts with
script:safe_exit(not bareexit) to ensure temp files are removed. -
Declare dependencies near the top with
system:requireso failures are fast and clear.
Common patterns
Non-interactive / CI script
#!/usr/bin/env fish
source /path/to/skeleton.fish
set FORCE 0 # auto-yes to all prompts
set QUIET 1 # show normal output
set VERBOSE 1 # hide debug output
set LOG_DIR /tmp/ci/log
set TMP_DIR /tmp/ci/tmp
script:initialize
system:require curl
system:require jq
set out (system:tempfile json)
curl -fsSL "$API_URL" > $out
jq '.items[]' $out
script:safe_exit
Timed batch job
set t0 (utility:time)
for i in (seq 1 $total)
io:progress "Processing $i / $total"
# ... work ...
end
io:print ""
utility:throughput $t0 $total items
script:safe_exit
Safe destructive operation
io:countdown 5 "Dropping database in"
if io:confirm
do_destructive_thing
io:success "Done"
else
io:alert "Aborted"
end
script:safe_exit
Inline-only (no source needed)
#!/usr/bin/env fish
# Minimal script: only needs str:trim — inline it, skip source
function str:trim
if test (count $argv) -gt 0
printf '%s' $argv | string trim
else
string trim
end
end
for line in (cat input.txt)
echo (str:trim $line)
end
More from cshen/skills
dida365
Manage TickTick/Dida365 tasks and projects from the terminal. List, create, complete, update, and delete tasks and projects via the Dida365 Open API.
14mail139
Read, send, delete, reply, and forward email via IMAP/SMTP. Check for new/unread messages, fetch content, search mailboxes, mark as read/unread, and send emails with attachments. Works with any IMAP/SMTP server including Gmail, Outlook, 163.com, vip.163.com, etc.
5xiaomi
Control Xiaomi Mijia smart home devices — list devices/scenes, get/set device properties, run scenes, and issue natural language commands via Xiao Ai speaker. Use when users ask to control smart home devices, turn on/off lights, adjust brightness or temperature, run scenes, or query device status.
3arxiv
Download arXiv papers as PDF, show title/authors/abstract, and generate BibTeX entries for arXiv preprints by arXiv ID.
3youtube-download
Download videos, audio, or subtitles from YouTube, Bilibili, and other sites using yt-dlp. Use when users ask to download online videos or extract audio from video URLs.
1personal-wiki
Build and maintain a personal knowledge wiki. Use when users need to collect and organize knowledge, manage research notes, build a persistent knowledge system, or document problem-solving experiences.
1