cmd refactor
Dependencies
- [2]
DHIPFBFPadded tests - [3]
KZKLAINJrun out of space on nest, cleaned out - [4]
YE6BZJUKtidy up lat lng timezone api for calls clip cmd - [5]
YUIQQPXYremoved --wav-only from calls clip cmd - [6]
SMWSHUOWcyclo over 15 - [7]
ZOSYO3IBck 3 - [8]
GE3VNRXLck 2 - [9]
2P27XV3Dfixed cyclo over 30 - [10]
3DVPQOKBbig tidy up of tools/ - [11]
QFPEKXL5ck 6 - [12]
GVOVKH5Rmore cyclo refactoring - [13]
R3IFCUGJcluster flags
Change contents
- replacement in cmd/xxhash.go at line 24
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak xxhash --file <path>\n\n")fmt.Fprintf(os.Stderr, "Compute XXH64 hash of a file (same format stored in database).\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak xxhash --file recording.wav\n")fmt.Fprintf(os.Stderr, " skraak xxhash --file /path/to/audio.wav | jq '.hash'\n")}fs.Usage = usagePrinter(fs,"skraak xxhash --file <path>","Compute XXH64 hash of a file (same format stored in database).","skraak xxhash --file recording.wav","skraak xxhash --file /path/to/audio.wav | jq '.hash'",) - replacement in cmd/xxhash.go at line 35[3.1038464]→[3.1038464:1038486](∅→∅),[3.1038544]→[3.1038544:1038557](∅→∅),[3.1038557]→[3.2008:2050](∅→∅)
if *filePath == "" {fs.Usage()return fmt.Errorf("--file is required")if err := requireFlags(fs, map[string]any{"--file": *filePath}); err != nil {return err - replacement in cmd/time.go at line 25
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak time\n\n")fmt.Fprintf(os.Stderr, "Get the current system time with timezone information.\n\n")fmt.Fprintf(os.Stderr, "Examples:\n")fmt.Fprintf(os.Stderr, " skraak time\n")fmt.Fprintf(os.Stderr, " skraak time | jq '.iso'\n")}fs.Usage = usagePrinter(fs,"skraak time","Get the current system time with timezone information.","skraak time","skraak time | jq '.iso'",) - replacement in cmd/sql.go at line 32
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak sql --db <path> [options] <query>\n\n")fmt.Fprintf(os.Stderr, "Execute a SQL SELECT query against the database.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak sql --db ./db/skraak.duckdb \"SELECT COUNT(*) FROM file WHERE active = true\"\n")fmt.Fprintf(os.Stderr, " skraak sql --db ./db/skraak.duckdb --limit 10 \"SELECT * FROM dataset\"\n")}fs.Usage = usagePrinter(fs,"skraak sql --db <path> [options] <query>","Execute a SQL SELECT query against the database.","skraak sql --db ./db/skraak.duckdb \"SELECT COUNT(*) FROM file WHERE active = true\"","skraak sql --db ./db/skraak.duckdb --limit 10 \"SELECT * FROM dataset\"",) - replacement in cmd/sql.go at line 43[3.1043261]→[3.1043261:1043281](∅→∅),[3.1043337]→[3.1043337:1043350](∅→∅),[3.1043350]→[3.2742:2782](∅→∅)
if *dbPath == "" {fs.Usage()return fmt.Errorf("--db is required")if err := requireFlags(fs, map[string]any{"--db": *dbPath}); err != nil {return err - replacement in cmd/replay.go at line 52
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak replay events [options]\n\n")fmt.Fprintf(os.Stderr, "Replay event log into database.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak replay events --db ./backup.duckdb --log ./events.jsonl\n")fmt.Fprintf(os.Stderr, " skraak replay events --db ./backup.duckdb --log ./events.jsonl --dry-run\n")fmt.Fprintf(os.Stderr, " skraak replay events --db ./backup.duckdb --log ./events.jsonl --last 10\n")}fs.Usage = usagePrinter(fs,"skraak replay events [options]","Replay event log into database.","skraak replay events --db ./backup.duckdb --log ./events.jsonl","skraak replay events --db ./backup.duckdb --log ./events.jsonl --dry-run","skraak replay events --db ./backup.duckdb --log ./events.jsonl --last 10",) - replacement in cmd/replay.go at line 64
if err := checkFlags(fs, "--db", *dbPath, "--log", *logPath); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--log": *logPath}); err != nil { - replacement in cmd/prepend.go at line 38
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak prepend --folder <path> --prefix <string> [--recursive] [--dry-run]\n\n")fmt.Fprintf(os.Stderr, "Rename files by prepending a prefix.\n\n")fmt.Fprintf(os.Stderr, "Target files:\n")fmt.Fprintf(os.Stderr, " - *.wav, *.WAV (must start with datestring YYYYMMDD_HHMMSS)\n")fmt.Fprintf(os.Stderr, " - *.wav.data, *.WAV.data (must start with datestring YYYYMMDD_HHMMSS)\n")fmt.Fprintf(os.Stderr, " - log.txt (exact name, always renamed)\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak prepend --folder ./recordings --prefix LOC001\n")fmt.Fprintf(os.Stderr, " skraak prepend --folder ./data --prefix SITE_A --recursive\n")fmt.Fprintf(os.Stderr, " skraak prepend --folder ./test --prefix TEST --dry-run\n")}fs.Usage = usagePrinter(fs,"skraak prepend --folder <path> --prefix <string> [--recursive] [--dry-run]","Rename files by prepending a prefix.\n\n"+"Target files:\n"+" - *.wav, *.WAV (must start with datestring YYYYMMDD_HHMMSS)\n"+" - *.wav.data, *.WAV.data (must start with datestring YYYYMMDD_HHMMSS)\n"+" - log.txt (exact name, always renamed)","skraak prepend --folder ./recordings --prefix LOC001","skraak prepend --folder ./data --prefix SITE_A --recursive","skraak prepend --folder ./test --prefix TEST --dry-run",) - replacement in cmd/prepend.go at line 54[3.1053218]→[3.1053218:1053238](∅→∅),[3.1053298]→[3.1053298:1053311](∅→∅),[3.1053311]→[3.3671:3715](∅→∅),[3.3715]→[3.1053324:1053327](∅→∅),[3.1053324]→[3.1053324:1053327](∅→∅),[3.1053328]→[3.1053328:1053348](∅→∅),[3.1053408]→[3.1053408:1053421](∅→∅),[3.1053421]→[3.3716:3760](∅→∅)
if *folder == "" {fs.Usage()return fmt.Errorf("--folder is required")}if *prefix == "" {fs.Usage()return fmt.Errorf("--prefix is required")if err := requireFlags(fs, map[string]any{"--folder": *folder, "--prefix": *prefix}); err != nil {return err - edit in cmd/pattern.go at line 7
"os" - replacement in cmd/pattern.go at line 33
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak pattern create [options]\n\n")fmt.Fprintf(os.Stderr, "Create a new cyclic recording pattern.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak pattern create --db ./db/skraak.duckdb --record 60 --sleep 1740\n")fmt.Fprintf(os.Stderr, " # Creates 60s record / 1740s sleep = 30 min cycle\n")}fs.Usage = usagePrinter(fs,"skraak pattern create [options]","Create a new cyclic recording pattern.","skraak pattern create --db ./db/skraak.duckdb --record 60 --sleep 1740","# Creates 60s record / 1740s sleep = 30 min cycle",) - replacement in cmd/pattern.go at line 44[3.1055433]→[3.1055433:1055461](∅→∅),[3.1055461]→[3.3968:4074](∅→∅),[3.4074]→[3.3011:3141](∅→∅),[3.3011]→[3.3011:3141](∅→∅),[3.3141]→[3.4075:4092](∅→∅)
// Validate required flagsif err := checkFlags(fs, "--db", *dbPath); err != nil {return err}if err := checkNonZeroFlags(fs,struct {Name stringValue int}{"--record", *record},struct {Name stringValue int}{"--sleep", *sleep},); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--record": *record,"--sleep": *sleep,}); err != nil { - replacement in cmd/pattern.go at line 78
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak pattern update [options]\n\n")fmt.Fprintf(os.Stderr, "Update an existing recording pattern. Only provided fields are updated.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak pattern update --db ./db/skraak.duckdb --id pattern123 --record 30\n")}fs.Usage = usagePrinter(fs,"skraak pattern update [options]","Update an existing recording pattern. Only provided fields are updated.","skraak pattern update --db ./db/skraak.duckdb --id pattern123 --record 30",) - replacement in cmd/pattern.go at line 88
if err := checkFlags(fs, "--db", *dbPath, "--id", *id); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *id}); err != nil { - replacement in cmd/metadata.go at line 30
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak metadata --file <path>\n\n")fmt.Fprintf(os.Stderr, "Extract metadata from a WAV file header.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak metadata --file recording.wav\n")fmt.Fprintf(os.Stderr, " skraak metadata --file /path/to/audio.wav | jq '.duration_seconds'\n")}fs.Usage = usagePrinter(fs,"skraak metadata --file <path>","Extract metadata from a WAV file header.","skraak metadata --file recording.wav","skraak metadata --file /path/to/audio.wav | jq '.duration_seconds'",) - replacement in cmd/metadata.go at line 41[3.1059560]→[3.1059560:1059582](∅→∅),[3.1059640]→[3.1059640:1059653](∅→∅),[3.1059653]→[3.4613:4655](∅→∅)
if *filePath == "" {fs.Usage()return fmt.Errorf("--file is required")if err := requireFlags(fs, map[string]any{"--file": *filePath}); err != nil {return err - edit in cmd/location.go at line 7
"os" - replacement in cmd/location.go at line 41
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak location create [options]\n\n")fmt.Fprintf(os.Stderr, "Create a new location with GPS coordinates.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak location create --db ./db/skraak.duckdb --dataset abc123 --name \"Site A\" --lat -36.85 --lon 174.76 --timezone Pacific/Auckland\n")}fs.Usage = usagePrinter(fs,"skraak location create [options]","Create a new location with GPS coordinates.","skraak location create --db ./db/skraak.duckdb --dataset abc123 --name \"Site A\" --lat -36.85 --lon 174.76 --timezone Pacific/Auckland",) - replacement in cmd/location.go at line 51
if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--name", *name, "--lat", *lat, "--lon", *lon, "--timezone", *tz); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--name": *name,"--lat": *lat,"--lon": *lon,"--timezone": *tz,}); err != nil { - replacement in cmd/location.go at line 104
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak location update [options]\n\n")fmt.Fprintf(os.Stderr, "Update an existing location. Only provided fields are updated.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak location update --db ./db/skraak.duckdb --id loc123 --name \"New Name\"\n")}fs.Usage = usagePrinter(fs,"skraak location update [options]","Update an existing location. Only provided fields are updated.","skraak location update --db ./db/skraak.duckdb --id loc123 --name \"New Name\"",) - replacement in cmd/location.go at line 114
if err := checkFlags(fs, "--db", *dbPath, "--id", *id); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *id}); err != nil { - replacement in cmd/isnight.go at line 46
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak isnight --file <path> --lat <lat> --lng <lng> [--timezone <tz>] [--brief]\n\n")fmt.Fprintf(os.Stderr, "Determine if a WAV file was recorded at night based on file metadata and GPS coordinates.\n\n")fmt.Fprintf(os.Stderr, "Uses the recording midpoint (not start time) for astronomical calculations.\n")fmt.Fprintf(os.Stderr, "Timestamp resolution: AudioMoth comment → filename → file modification time.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak isnight --file recording.wav --lat -36.85 --lng 174.76\n")fmt.Fprintf(os.Stderr, " skraak isnight --file recording.wav --lat -36.85 --lng 174.76 --timezone Pacific/Auckland\n")fmt.Fprintf(os.Stderr, " skraak isnight --file recording.wav --lat 51.51 --lng -0.13 | jq '.solar_night'\n")}fs.Usage = usagePrinter(fs,"skraak isnight --file <path> --lat <lat> --lng <lng> [--timezone <tz>] [--brief]","Determine if a WAV file was recorded at night based on file metadata and GPS coordinates.\n\n"+"Uses the recording midpoint (not start time) for astronomical calculations.\n"+"Timestamp resolution: AudioMoth comment → filename → file modification time.","skraak isnight --file recording.wav --lat -36.85 --lng 174.76","skraak isnight --file recording.wav --lat -36.85 --lng 174.76 --timezone Pacific/Auckland","skraak isnight --file recording.wav --lat 51.51 --lng -0.13 | jq '.solar_night'",) - replacement in cmd/isnight.go at line 60[3.1069141]→[3.1069141:1069163](∅→∅),[3.1069221]→[3.1069221:1069234](∅→∅),[3.1069234]→[3.5931:5973](∅→∅)
if *filePath == "" {fs.Usage()return fmt.Errorf("--file is required")if err := requireFlags(fs, map[string]any{"--file": *filePath}); err != nil {return err - replacement in cmd/import.go at line 75
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak import bulk [options]\n\n")fmt.Fprintf(os.Stderr, "Bulk import WAV files across multiple locations/clusters using a CSV file.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nCSV format: location_name,location_id,directory_path,date_range,sample_rate,file_count\n")fmt.Fprintf(os.Stderr, "\nMonitor progress: tail -f <log-file>\n")}fs.Usage = usagePrinter(fs,"skraak import bulk [options]","Bulk import WAV files across multiple locations/clusters using a CSV file.\n\n"+"CSV format: location_name,location_id,directory_path,date_range,sample_rate,file_count\n\n"+"Monitor progress: tail -f <log-file>",) - replacement in cmd/import.go at line 86
// Validate required flagsif err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--csv", *csvPath, "--log", *logPath); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--csv": *csvPath,"--log": *logPath,}); err != nil { - replacement in cmd/import.go at line 149
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak import file [options]\n\n")fmt.Fprintf(os.Stderr, "Import a single WAV file into the database.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak import file --db ./db/skraak.duckdb --dataset abc123 --location loc456 --cluster clust789 --file /path/to/file.wav\n")}fs.Usage = usagePrinter(fs,"skraak import file [options]","Import a single WAV file into the database.","skraak import file --db ./db/skraak.duckdb --dataset abc123 --location loc456 --cluster clust789 --file /path/to/file.wav",) - replacement in cmd/import.go at line 159
// Validate required flagsif err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--file", *filePath); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--location": *locationID,"--cluster": *clusterID,"--file": *filePath,}); err != nil { - replacement in cmd/import.go at line 217
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak import folder [options]\n\n")fmt.Fprintf(os.Stderr, "Import all WAV files from a folder into the database.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak import folder --db ./db/skraak.duckdb --dataset abc123 --location loc456 --cluster clust789 --folder /path/to/folder\n")}fs.Usage = usagePrinter(fs,"skraak import folder [options]","Import all WAV files from a folder into the database.","skraak import folder --db ./db/skraak.duckdb --dataset abc123 --location loc456 --cluster clust789 --folder /path/to/folder",) - replacement in cmd/import.go at line 227
// Validate required flagsif err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--folder", *folderPath); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--location": *locationID,"--cluster": *clusterID,"--folder": *folderPath,}); err != nil { - replacement in cmd/import.go at line 312
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak import segments [options]\n\n")fmt.Fprintf(os.Stderr, "Import segments from AviaNZ .data files into the database.\n")fmt.Fprintf(os.Stderr, "Applies species/calltype mapping from JSON file.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nMapping file format:\n")fmt.Fprintf(os.Stderr, " {\n")fmt.Fprintf(os.Stderr, " \"GSK\": {\"species\": \"Roroa\", \"calltypes\": {\"Male\": \"Male - Solo\"}},\n")fmt.Fprintf(os.Stderr, " \"Don't Know\": {\"species\": \"Don't Know\"}\n")fmt.Fprintf(os.Stderr, " }\n")fmt.Fprintf(os.Stderr, "\nInvariants:\n")fmt.Fprintf(os.Stderr, " - All file hashes must already exist in database for the cluster\n")fmt.Fprintf(os.Stderr, " - All files must have no existing labels (fresh imports only)\n")fmt.Fprintf(os.Stderr, " - All filters, species, and calltypes must exist in database\n")fmt.Fprintf(os.Stderr, " - Bookmark flags are ignored (not stored in database)\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak import segments --db ./db/skraak.duckdb --dataset dset_id123 --location loc_id456 --cluster clust_id789 --folder /path/to/data --mapping mapping.json\n")}fs.Usage = usagePrinter(fs,"skraak import segments [options]","Import segments from AviaNZ .data files into the database.\n"+"Applies species/calltype mapping from JSON file.\n\n"+"Mapping file format:\n"+" {\n"+" \"GSK\": {\"species\": \"Roroa\", \"calltypes\": {\"Male\": \"Male - Solo\"}},\n"+" \"Don't Know\": {\"species\": \"Don't Know\"}\n"+" }\n\n"+"Invariants:\n"+" - All file hashes must already exist in database for the cluster\n"+" - All files must have no existing labels (fresh imports only)\n"+" - All filters, species, and calltypes must exist in database\n"+" - Bookmark flags are ignored (not stored in database)","skraak import segments --db ./db/skraak.duckdb --dataset dset_id123 --location loc_id456 --cluster clust_id789 --folder /path/to/data --mapping mapping.json",) - replacement in cmd/import.go at line 333
// Validate required flagsif err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--folder", *folderPath, "--mapping", *mappingPath); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--location": *locationID,"--cluster": *clusterID,"--folder": *folderPath,"--mapping": *mappingPath,}); err != nil { - replacement in cmd/import.go at line 408
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak import unstructured [options]\n\n")fmt.Fprintf(os.Stderr, "Import WAV files into an unstructured dataset.\n")fmt.Fprintf(os.Stderr, "Files are stored with minimal metadata (hash, duration, sample_rate, file modification time).\n")fmt.Fprintf(os.Stderr, "No location/cluster hierarchy required.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak import unstructured --db ./db/skraak.duckdb --dataset abc123 --folder /path/to/folder\n")fmt.Fprintf(os.Stderr, " skraak import unstructured --db ./db/skraak.duckdb --dataset abc123 --folder /path/to/folder --recursive=false\n")}fs.Usage = usagePrinter(fs,"skraak import unstructured [options]","Import WAV files into an unstructured dataset.\n"+"Files are stored with minimal metadata (hash, duration, sample_rate, file modification time).\n"+"No location/cluster hierarchy required.","skraak import unstructured --db ./db/skraak.duckdb --dataset abc123 --folder /path/to/folder","skraak import unstructured --db ./db/skraak.duckdb --dataset abc123 --folder /path/to/folder --recursive=false",) - replacement in cmd/import.go at line 421
// Validate required flagsif err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--folder", *folderPath); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--folder": *folderPath,}); err != nil { - replacement in cmd/export.go at line 58
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak export dataset --db <path> --id <dataset_id> --output <path> [options]\n\n")fmt.Fprintf(os.Stderr, "Export a dataset with all related data to a new DuckDB database.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak export dataset --db ./db/skraak.duckdb --id abc123 --output export.duckdb\n")fmt.Fprintf(os.Stderr, " skraak export dataset --db ./db/skraak.duckdb --id abc123 --output export.duckdb --dry-run\n")fmt.Fprintf(os.Stderr, " skraak export dataset --db ./db/skraak.duckdb --id abc123 --output export.duckdb --force\n")}fs.Usage = usagePrinter(fs,"skraak export dataset --db <path> --id <dataset_id> --output <path> [options]","Export a dataset with all related data to a new DuckDB database.","skraak export dataset --db ./db/skraak.duckdb --id abc123 --output export.duckdb","skraak export dataset --db ./db/skraak.duckdb --id abc123 --output export.duckdb --dry-run","skraak export dataset --db ./db/skraak.duckdb --id abc123 --output export.duckdb --force",) - replacement in cmd/export.go at line 70
if err := checkFlags(fs, "--db", *dbPath, "--id", *datasetID, "--output", *output); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *datasetID, "--output": *output}); err != nil { - edit in cmd/dataset.go at line 7
"os" - replacement in cmd/dataset.go at line 34
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak create dataset [options]\n\n")fmt.Fprintf(os.Stderr, "Create a new dataset.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak create dataset --db ./db/skraak.duckdb --name \"My Dataset\"\n")fmt.Fprintf(os.Stderr, " skraak create dataset --db ./db/skraak.duckdb --name \"Training Data\" --type train --description \"For ML training\"\n")}fs.Usage = usagePrinter(fs,"skraak create dataset [options]","Create a new dataset.","skraak create dataset --db ./db/skraak.duckdb --name \"My Dataset\"","skraak create dataset --db ./db/skraak.duckdb --name \"Training Data\" --type train --description \"For ML training\"",) - replacement in cmd/dataset.go at line 45
if err := checkFlags(fs, "--db", *dbPath, "--name", *name); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--name": *name}); err != nil { - replacement in cmd/dataset.go at line 77
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak update dataset [options]\n\n")fmt.Fprintf(os.Stderr, "Update an existing dataset. Only provided fields are updated.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak update dataset --db ./db/skraak.duckdb --id abc123 --name \"Updated Name\"\n")fmt.Fprintf(os.Stderr, " skraak update dataset --db ./db/skraak.duckdb --id abc123 --type train\n")}fs.Usage = usagePrinter(fs,"skraak update dataset [options]","Update an existing dataset. Only provided fields are updated.","skraak update dataset --db ./db/skraak.duckdb --id abc123 --name \"Updated Name\"","skraak update dataset --db ./db/skraak.duckdb --id abc123 --type train",) - replacement in cmd/dataset.go at line 88
if err := checkFlags(fs, "--db", *dbPath, "--id", *id); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *id}); err != nil { - replacement in cmd/common_test.go at line 17
func TestCheckFlags(t *testing.T) {func TestRequireFlags(t *testing.T) { - replacement in cmd/common_test.go at line 20
pairs []stringrequired map[string]any - replacement in cmd/common_test.go at line 24
{"all present", []string{"--db", "x.duckdb", "--id", "abc"}, false, nil},{"empty value", []string{"--db", "", "--id", "abc"}, true, []string{"--db"}},{"multiple missing", []string{"--db", "", "--id", ""}, true, []string{"--db", "--id"}},{"no pairs", []string{}, false, nil},{"single missing", []string{"--name", ""}, true, []string{"--name"}},{"all string present", map[string]any{"--db": "x.duckdb", "--id": "abc"}, false, nil},{"empty string", map[string]any{"--db": "", "--id": "abc"}, true, []string{"--db"}},{"multiple missing strings", map[string]any{"--db": "", "--id": ""}, true, []string{"--db", "--id"}},{"no flags", map[string]any{}, false, nil},{"all int non-zero", map[string]any{"--n": 5, "--m": 1}, false, nil},{"zero int", map[string]any{"--n": 0, "--m": 1}, true, []string{"--n"}},{"multiple zero ints", map[string]any{"--n": 0, "--m": 0}, true, []string{"--m", "--n"}},{"negative int is allowed", map[string]any{"--n": -1}, false, nil},{"mixed types", map[string]any{"--db": "x", "--n": 0}, true, []string{"--n"}}, - replacement in cmd/common_test.go at line 36
err := checkFlags(silentFlagSet(), tt.pairs...)err := requireFlags(silentFlagSet(), tt.required) - replacement in cmd/common_test.go at line 51
func TestCheckNonZeroFlags(t *testing.T) {type pair = struct {Name stringValue intfunc TestRequireFlagsUnsupportedType(t *testing.T) {err := requireFlags(silentFlagSet(), map[string]any{"--bad": 1.5})if err == nil || !strings.Contains(err.Error(), "unsupported type") {t.Fatalf("expected unsupported type error, got %v", err) - edit in cmd/common_test.go at line 56
tests := []struct {name stringpairs []pairwantErr boolwantMissing []string}{{"all non-zero", []pair{{"--n", 5}, {"--m", 1}}, false, nil},{"zero value", []pair{{"--n", 0}, {"--m", 1}}, true, []string{"--n"}},{"multiple zero", []pair{{"--n", 0}, {"--m", 0}}, true, []string{"--n", "--m"}},{"empty pairs", nil, false, nil},{"negative is allowed", []pair{{"--n", -1}}, false, nil},}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {err := checkNonZeroFlags(silentFlagSet(), tt.pairs...)if (err != nil) != tt.wantErr {t.Fatalf("err=%v, wantErr=%v", err, tt.wantErr)}if tt.wantErr {for _, name := range tt.wantMissing {if !strings.Contains(err.Error(), name) {t.Errorf("err %q missing flag name %q", err.Error(), name)}}}})} - edit in cmd/common.go at line 7
"sort" - replacement in cmd/common.go at line 26
// checkFlags checks that the given flag values are non-empty strings.// Returns an error if any are empty (does not call os.Exit).// Each pair is (flagName, flagValue) — e.g. checkFlags(fs, "--db", *dbPath, "--id", *id)func checkFlags(fs *flag.FlagSet, pairs ...string) error {// requireFlags reports an error listing any required flags whose values are// still at their zero value. Strings must be non-empty; ints must be non-zero.// Map iteration is randomised, so the returned list is sorted for stability.func requireFlags(fs *flag.FlagSet, required map[string]any) error { - replacement in cmd/common.go at line 31
for i := 0; i < len(pairs); i += 2 {if pairs[i+1] == "" {missing = append(missing, pairs[i])for name, v := range required {switch x := v.(type) {case string:if x == "" {missing = append(missing, name)}case int:if x == 0 {missing = append(missing, name)}default:return fmt.Errorf("requireFlags: unsupported type for %s: %T", name, v) - edit in cmd/common.go at line 46
sort.Strings(missing) - replacement in cmd/common.go at line 53[3.4730]→[3.8141:8334](∅→∅),[3.8334]→[3.4958:4983](∅→∅),[3.4958]→[3.4958:4983](∅→∅),[3.4983]→[3.8335:8346](∅→∅),[3.8346]→[3.4988:5094](∅→∅),[3.4988]→[3.4988:5094](∅→∅)
// checkNonZeroFlags checks that the given int flag values are non-zero.// Returns an error if any are zero (does not call os.Exit).func checkNonZeroFlags(fs *flag.FlagSet, pairs ...struct {Name stringValue int}) error {var missing []stringfor _, p := range pairs {if p.Value == 0 {missing = append(missing, p.Name)// usagePrinter returns an fs.Usage closure that prints a structured header,// the auto-generated flag table (via fs.PrintDefaults), and optional examples.// The flag table reflects the flags as registered — no manual duplication.func usagePrinter(fs *flag.FlagSet, synopsis, description string, examples ...string) func() {return func() {fmt.Fprintf(os.Stderr, "Usage: %s\n\n", synopsis)if description != "" {fmt.Fprintf(os.Stderr, "%s\n\n", description) - replacement in cmd/common.go at line 62
}if len(missing) > 0 {fs.Usage()return fmt.Errorf("missing required flags: %v", missing)fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()if len(examples) > 0 {fmt.Fprintf(os.Stderr, "\nExamples:\n")for _, ex := range examples {fmt.Fprintf(os.Stderr, " %s\n", ex)}} - edit in cmd/common.go at line 71
return nil - edit in cmd/cluster.go at line 7
"os" - replacement in cmd/cluster.go at line 41
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak cluster create [options]\n\n")fmt.Fprintf(os.Stderr, "Create a new cluster for grouping recordings.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak cluster create --db ./db/skraak.duckdb --dataset abc123 --location loc456 --name \"2024-01\" --sample-rate 250000\n")}fs.Usage = usagePrinter(fs,"skraak cluster create [options]","Create a new cluster for grouping recordings.","skraak cluster create --db ./db/skraak.duckdb --dataset abc123 --location loc456 --name \"2024-01\" --sample-rate 250000",) - replacement in cmd/cluster.go at line 51
if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--name", *name, "--sample-rate", *sampleRate); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath,"--dataset": *datasetID,"--location": *locationID,"--name": *name,"--sample-rate": *sampleRate,}); err != nil { - replacement in cmd/cluster.go at line 101
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak cluster update [options]\n\n")fmt.Fprintf(os.Stderr, "Update an existing cluster. Only provided fields are updated.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak cluster update --db ./db/skraak.duckdb --id clust123 --name \"New Name\"\n")}fs.Usage = usagePrinter(fs,"skraak cluster update [options]","Update an existing cluster. Only provided fields are updated.","skraak cluster update --db ./db/skraak.duckdb --id clust123 --name \"New Name\"",) - replacement in cmd/cluster.go at line 111
if err := checkFlags(fs, "--db", *dbPath, "--id", *id); err != nil {if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *id}); err != nil { - replacement in cmd/calls_propagate.go at line 85
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls propagate [options]\n\n")fmt.Fprintf(os.Stderr, "Propagate verified classifications from one filter to another within a .data file\n")fmt.Fprintf(os.Stderr, "or across every .data file in a folder.\n\n")fmt.Fprintf(os.Stderr, "Only source labels with certainty=100 and matching --species are considered.\n")fmt.Fprintf(os.Stderr, "Target labels (filter=--to) are updated when their certainty is 70 or 0.\n")fmt.Fprintf(os.Stderr, "Updated target labels are set to certainty=90; file reviewer is set to \"Skraak\".\n")fmt.Fprintf(os.Stderr, "Targets already at certainty=100 or 90 are left alone.\n")fmt.Fprintf(os.Stderr, "Files that do not contain both --from and --to filter labels are skipped.\n\n")fmt.Fprintf(os.Stderr, "Exactly one of --file or --folder is required.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak calls propagate --file rec.wav.data \\\n")fmt.Fprintf(os.Stderr, " --from opensoundscape-kiwi-1.2 --to opensoundscape-kiwi-1.5 --species Kiwi\n\n")fmt.Fprintf(os.Stderr, " skraak calls propagate --folder ./recordings \\\n")fmt.Fprintf(os.Stderr, " --from opensoundscape-kiwi-1.2 --to opensoundscape-kiwi-1.5 --species Kiwi\n")}fs.Usage = usagePrinter(fs,"skraak calls propagate [options]","Propagate verified classifications from one filter to another within a .data file\n"+"or across every .data file in a folder.\n\n"+"Only source labels with certainty=100 and matching --species are considered.\n"+"Target labels (filter=--to) are updated when their certainty is 70 or 0.\n"+"Updated target labels are set to certainty=90; file reviewer is set to \"Skraak\".\n"+"Targets already at certainty=100 or 90 are left alone.\n"+"Files that do not contain both --from and --to filter labels are skipped.\n\n"+"Exactly one of --file or --folder is required.","skraak calls propagate --file rec.wav.data \\"," --from opensoundscape-kiwi-1.2 --to opensoundscape-kiwi-1.5 --species Kiwi","","skraak calls propagate --folder ./recordings \\"," --from opensoundscape-kiwi-1.2 --to opensoundscape-kiwi-1.5 --species Kiwi",) - replacement in cmd/calls_propagate.go at line 111
if err := checkFlags(fs, "--from", *from, "--to", *to, "--species", *species); err != nil {if err := requireFlags(fs, map[string]any{"--from": *from, "--to": *to, "--species": *species}); err != nil { - replacement in cmd/calls_clip_labels.go at line 25
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls clip-labels [options]\n\n")fmt.Fprintf(os.Stderr, "Generate an OpenSoundScape clip_labels-format CSV from .data files.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nSegment policy:\n")fmt.Fprintf(os.Stderr, " - Real species → contributes mapped class to overlapping clips.\n")fmt.Fprintf(os.Stderr, " - Mapped to __NEGATIVE__ → clip emitted, all class columns False;\n")fmt.Fprintf(os.Stderr, " overrides positives in the same clip.\n")fmt.Fprintf(os.Stderr, " - Mapped to __IGNORE__ → segment contributes no labels to clips.\n")fmt.Fprintf(os.Stderr, " - Gaps → clip emitted with all class columns False.\n")fmt.Fprintf(os.Stderr, "\nIf --output exists: append. Column-set mismatch → hard error.\n")fmt.Fprintf(os.Stderr, "Duplicate (file, start_time, end_time) row → hard error on first.\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak calls clip-labels --folder ./recordings --mapping ./mapping.json\n")fmt.Fprintf(os.Stderr, " skraak calls clip-labels --folder ./recordings --mapping ./mapping.json \\\n")fmt.Fprintf(os.Stderr, " --filter opensoundscape-multi-1.0\n")}fs.Usage = usagePrinter(fs,"skraak calls clip-labels [options]","Generate an OpenSoundScape clip_labels-format CSV from .data files.\n\n"+"Segment policy:\n"+" - Real species → contributes mapped class to overlapping clips.\n"+" - Mapped to __NEGATIVE__ → clip emitted, all class columns False;\n"+" overrides positives in the same clip.\n"+" - Mapped to __IGNORE__ → segment contributes no labels to clips.\n"+" - Gaps → clip emitted with all class columns False.\n\n"+"If --output exists: append. Column-set mismatch → hard error.\n"+"Duplicate (file, start_time, end_time) row → hard error on first.","skraak calls clip-labels --folder ./recordings --mapping ./mapping.json","skraak calls clip-labels --folder ./recordings --mapping ./mapping.json \\"," --filter opensoundscape-multi-1.0",) - replacement in cmd/calls_clip_labels.go at line 45
if err := checkFlags(fs, "--folder", *folder, "--mapping", *mapping); err != nil {if err := requireFlags(fs, map[string]any{"--folder": *folder, "--mapping": *mapping}); err != nil { - edit in cmd/calls_clip.go at line 12[3.16068]→[3.1133178:1134304](∅→∅),[3.1133178]→[3.1133178:1134304](∅→∅),[3.1134304]→[3.2351:3028](∅→∅),[3.3028]→[3.1135310:1136301](∅→∅),[3.1135310]→[3.1135310:1136301](∅→∅),[3.1137041]→[3.1137041:1137042](∅→∅)
func printClipUsage() {fmt.Fprintf(os.Stderr, "Usage: skraak calls clip [options]\n\n")fmt.Fprintf(os.Stderr, "Generate audio clips and spectrogram images from .data file segments.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fmt.Fprintf(os.Stderr, " --file <path> Path to .data file (required if no --folder)\n")fmt.Fprintf(os.Stderr, " --folder <path> Path to folder containing .data files (required if no --file)\n")fmt.Fprintf(os.Stderr, " --output <path> Output folder for generated clips (required)\n")fmt.Fprintf(os.Stderr, " --prefix <name> Prefix for output filenames (required)\n")fmt.Fprintf(os.Stderr, " --filter <name> Filter by ML model name (optional)\n")fmt.Fprintf(os.Stderr, " --species <name> Filter by species, optionally with calltype (e.g. Kiwi, Kiwi+Duet)\n")fmt.Fprintf(os.Stderr, " --certainty <int> Filter by certainty value (0-100, optional)\n")fmt.Fprintf(os.Stderr, " --size <int> Spectrogram image size in pixels (224-896, default 224)\n")fmt.Fprintf(os.Stderr, " --color Apply L4 colormap to spectrogram (default: grayscale)\n")fmt.Fprintf(os.Stderr, " --night Only clip recordings made during solar night (requires --location)\n")fmt.Fprintf(os.Stderr, " --day Only clip recordings made during solar day (requires --location)\n")fmt.Fprintf(os.Stderr, " --location <lat,lng[,tz]> GPS coordinates and optional IANA timezone\n")fmt.Fprintf(os.Stderr, " e.g. --location \"-36.85,174.76\" or --location \"-36.85,174.76,Pacific/Auckland\"\n")fmt.Fprintf(os.Stderr, " Required with --night or --day. Timezone defaults to UTC.\n")fmt.Fprintf(os.Stderr, " Not needed for AudioMoth data (UTC from WAV comment).\n")fmt.Fprintf(os.Stderr, "\nOutput files:\n")fmt.Fprintf(os.Stderr, " <prefix>_<basename>_<start>_<end>.png # spectrogram image\n")fmt.Fprintf(os.Stderr, " <prefix>_<basename>_<start>_<end>.wav # audio clip (16kHz if downsampled)\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " # Clip all segments from a single file\n")fmt.Fprintf(os.Stderr, " skraak calls clip --file recording.data --output ./clips --prefix train\n\n")fmt.Fprintf(os.Stderr, " # Clip only Kiwi segments with color spectrograms at 448px\n")fmt.Fprintf(os.Stderr, " skraak calls clip --folder ./data --output ./clips --prefix kiwi \\\n")fmt.Fprintf(os.Stderr, " --filter opensoundscape-kiwi-1.2 --species Kiwi --size 448 --color\n\n")fmt.Fprintf(os.Stderr, " # Clip Kiwi Duet calls\n")fmt.Fprintf(os.Stderr, " skraak calls clip --folder ./data --output ./clips --prefix duet \\\n")fmt.Fprintf(os.Stderr, " --filter opensoundscape-kiwi-1.2 --species Kiwi+Duet\n")} - replacement in cmd/calls_clip.go at line 29
func parseClipArgs(args []string) clipFlags {// Returns the parsed flags and the FlagSet (whose Usage is invoked by the caller on error).func parseClipArgs(args []string) (clipFlags, *flag.FlagSet) { - replacement in cmd/calls_clip.go at line 37
species := fs.String("species", "", "Filter by species, optionally with calltype")species := fs.String("species", "", "Filter by species, optionally with calltype (e.g. Kiwi, Kiwi+Duet)") - replacement in cmd/calls_clip.go at line 39
size := fs.Int("size", 0, "Spectrogram image size in pixels (224-896)")color := fs.Bool("color", false, "Apply L4 colormap to spectrogram")night := fs.Bool("night", false, "Only clip recordings made during solar night")day := fs.Bool("day", false, "Only clip recordings made during solar day")location := fs.String("location", "", "GPS coordinates and optional timezone")size := fs.Int("size", 0, "Spectrogram image size in pixels (224-896, default 224)")color := fs.Bool("color", false, "Apply L4 colormap to spectrogram (default: grayscale)")night := fs.Bool("night", false, "Only clip recordings made during solar night (requires --location)")day := fs.Bool("day", false, "Only clip recordings made during solar day (requires --location)")location := fs.String("location", "", "GPS coordinates and optional IANA timezone, e.g. \"-36.85,174.76\" or \"-36.85,174.76,Pacific/Auckland\". Required with --night/--day. Not needed for AudioMoth data.") - replacement in cmd/calls_clip.go at line 45
fs.Usage = printClipUsagefs.Usage = usagePrinter(fs,"skraak calls clip [options]","Generate audio clips and spectrogram images from .data file segments.\n\n"+"Output files:\n"+" <prefix>_<basename>_<start>_<end>.png # spectrogram image\n"+" <prefix>_<basename>_<start>_<end>.wav # audio clip (16kHz if downsampled)","# Clip all segments from a single file","skraak calls clip --file recording.data --output ./clips --prefix train","","# Clip only Kiwi segments with color spectrograms at 448px","skraak calls clip --folder ./data --output ./clips --prefix kiwi \\"," --filter opensoundscape-kiwi-1.2 --species Kiwi --size 448 --color","","# Clip Kiwi Duet calls","skraak calls clip --folder ./data --output ./clips --prefix duet \\"," --filter opensoundscape-kiwi-1.2 --species Kiwi+Duet",) - replacement in cmd/calls_clip.go at line 82
}}, fs - replacement in cmd/calls_clip.go at line 86[3.6771]→[3.6771:6855](∅→∅),[3.6855]→[3.1140344:1140367](∅→∅),[3.28158]→[3.1140344:1140367](∅→∅),[3.1140344]→[3.1140344:1140367](∅→∅)
// Returns an error instead of exiting.func validateClipFlags(f clipFlags) error {missing := []string{}func validateClipFlags(fs *flag.FlagSet, f clipFlags) error { - replacement in cmd/calls_clip.go at line 88[3.28196]→[3.1140400:1140453](∅→∅),[3.1140400]→[3.1140400:1140453](∅→∅),[3.1140453]→[3.28197:28218](∅→∅),[3.28218]→[3.1140472:1140512](∅→∅),[3.1140472]→[3.1140472:1140512](∅→∅)
missing = append(missing, "--file or --folder")}if f.output == "" {missing = append(missing, "--output")fs.Usage()return fmt.Errorf("missing required flags: [--file or --folder]") - replacement in cmd/calls_clip.go at line 91[3.1140515]→[3.28219:28240](∅→∅),[3.28240]→[3.1140534:1140574](∅→∅),[3.1140534]→[3.1140534:1140574](∅→∅)
if f.prefix == "" {missing = append(missing, "--prefix")if err := requireFlags(fs, map[string]any{"--output": f.output, "--prefix": f.prefix}); err != nil {return err - edit in cmd/calls_clip.go at line 94[3.1140577]→[3.1140577:1140600](∅→∅),[3.1140675]→[3.1140675:1140694](∅→∅),[3.1140694]→[3.6856:6915](∅→∅),[3.6915]→[3.1140707:1140710](∅→∅),[3.1140707]→[3.1140707:1140710](∅→∅)
if len(missing) > 0 {printClipUsage()return fmt.Errorf("missing required flags: %v", missing)} - replacement in cmd/calls_clip.go at line 95
printClipUsage()fs.Usage() - replacement in cmd/calls_clip.go at line 99
printClipUsage()fs.Usage() - replacement in cmd/calls_clip.go at line 118
f := parseClipArgs(args)if err := validateClipFlags(f); err != nil {f, fs := parseClipArgs(args)if err := validateClipFlags(fs, f); err != nil { - replacement in cmd/calls.go at line 108
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls from-preds [options]\n\n")fmt.Fprintf(os.Stderr, "Extract clustered bird calls from ML predictions CSV.\n")fmt.Fprintf(os.Stderr, "Reads prediction CSV with columns: file, start_time, end_time, <ebird_codes...>\n")fmt.Fprintf(os.Stderr, "Each row is a clip with 1=present, 0=absent for each species.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nOutput:\n")fmt.Fprintf(os.Stderr, " With --dot-data=true (default): Writes .data files alongside audio files, outputs JSON summary\n")fmt.Fprintf(os.Stderr, " With --dot-data=false: Outputs JSON with clustered calls only (no .data files)\n")fmt.Fprintf(os.Stderr, "\nFilter name:\n")fmt.Fprintf(os.Stderr, " If --filter is provided, uses that value.\n")fmt.Fprintf(os.Stderr, " Otherwise, parses from CSV filename: prefix_filter_date.csv -> filter\n")fmt.Fprintf(os.Stderr, " Example: predsST_opensoundscape-kiwi-1.2_2025-11-12.csv -> opensoundscape-kiwi-1.2\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " # Write .data files (default)\n")fmt.Fprintf(os.Stderr, " skraak calls from-preds --csv predictions.csv\n")fmt.Fprintf(os.Stderr, "\n")fmt.Fprintf(os.Stderr, " # JSON output only (no .data files)\n")fmt.Fprintf(os.Stderr, " skraak calls from-preds --csv predictions.csv --dot-data=false > calls.json\n")fmt.Fprintf(os.Stderr, "\n")fmt.Fprintf(os.Stderr, " # Override filter name\n")fmt.Fprintf(os.Stderr, " skraak calls from-preds --csv preds.csv --filter my-custom-filter\n")}fs.Usage = usagePrinter(fs,"skraak calls from-preds [options]","Extract clustered bird calls from ML predictions CSV.\n"+"Reads prediction CSV with columns: file, start_time, end_time, <ebird_codes...>\n"+"Each row is a clip with 1=present, 0=absent for each species.\n\n"+"Output:\n"+" With --dot-data=true (default): Writes .data files alongside audio files, outputs JSON summary\n"+" With --dot-data=false: Outputs JSON with clustered calls only (no .data files)\n\n"+"Filter name:\n"+" If --filter is provided, uses that value.\n"+" Otherwise, parses from CSV filename: prefix_filter_date.csv -> filter\n"+" Example: predsST_opensoundscape-kiwi-1.2_2025-11-12.csv -> opensoundscape-kiwi-1.2","# Write .data files (default)","skraak calls from-preds --csv predictions.csv","","# JSON output only (no .data files)","skraak calls from-preds --csv predictions.csv --dot-data=false > calls.json","","# Override filter name","skraak calls from-preds --csv preds.csv --filter my-custom-filter",) - replacement in cmd/calls.go at line 134
// Validate required flagsif err := checkFlags(fs, "--csv", *csvPath); err != nil {if err := requireFlags(fs, map[string]any{"--csv": *csvPath}); err != nil { - replacement in cmd/calls.go at line 207
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls show-images [options]\n\n")fmt.Fprintf(os.Stderr, "Display spectrogram images for each segment in a .data file.\n")fmt.Fprintf(os.Stderr, "Images are output using the Kitty graphics protocol (or Sixel with --sixel, iTerm2 with --iterm).\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak calls show-images --file recording.wav.data\n")fmt.Fprintf(os.Stderr, " skraak calls show-images --file recording.wav.data --color\n")}fs.Usage = usagePrinter(fs,"skraak calls show-images [options]","Display spectrogram images for each segment in a .data file.\n"+"Images are output using the Kitty graphics protocol (or Sixel with --sixel, iTerm2 with --iterm).","skraak calls show-images --file recording.wav.data","skraak calls show-images --file recording.wav.data --color",) - replacement in cmd/calls.go at line 219
// Validate required flagsif err := checkFlags(fs, "--file", *filePath); err != nil {if err := requireFlags(fs, map[string]any{"--file": *filePath}); err != nil { - replacement in cmd/calls.go at line 274
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls from-birda [options]\n\n")fmt.Fprintf(os.Stderr, "Import BirdNET results to .data files.\n")fmt.Fprintf(os.Stderr, "Reads *.BirdNET.results.csv files and creates/merges .data files.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nBehavior:\n")fmt.Fprintf(os.Stderr, " - Filter is always 'BirdNET' (parsed from filename)\n")fmt.Fprintf(os.Stderr, " - If .data file exists with BirdNET filter: error (refuses to clobber)\n")fmt.Fprintf(os.Stderr, " - If .data file exists with different filter: merge segments\n")fmt.Fprintf(os.Stderr, " - Confidence (0.0-1.0) converted to certainty (0-100)\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak calls from-birda --folder ./recordings\n")fmt.Fprintf(os.Stderr, " skraak calls from-birda --file recording.BirdNET.results.csv\n")fmt.Fprintf(os.Stderr, " skraak calls from-birda --folder ./recordings --delete\n")}fs.Usage = usagePrinter(fs,"skraak calls from-birda [options]","Import BirdNET results to .data files.\n"+"Reads *.BirdNET.results.csv files and creates/merges .data files.\n\n"+"Behavior:\n"+" - Filter is always 'BirdNET' (parsed from filename)\n"+" - If .data file exists with BirdNET filter: error (refuses to clobber)\n"+" - If .data file exists with different filter: merge segments\n"+" - Confidence (0.0-1.0) converted to certainty (0-100)","skraak calls from-birda --folder ./recordings","skraak calls from-birda --file recording.BirdNET.results.csv","skraak calls from-birda --folder ./recordings --delete",) - replacement in cmd/calls.go at line 375
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls from-raven [options]\n\n")fmt.Fprintf(os.Stderr, "Import Raven selections to .data files.\n")fmt.Fprintf(os.Stderr, "Reads *.selections.txt files and creates/merges .data files.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nBehavior:\n")fmt.Fprintf(os.Stderr, " - Filter is always 'Raven' (parsed from filename)\n")fmt.Fprintf(os.Stderr, " - If .data file exists with Raven filter: error (refuses to clobber)\n")fmt.Fprintf(os.Stderr, " - If .data file exists with different filter: merge segments\n")fmt.Fprintf(os.Stderr, " - Frequency range preserved from Raven selections\n")fmt.Fprintf(os.Stderr, " - Certainty defaults to 70 (no confidence metric in Raven)\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak calls from-raven --folder ./recordings\n")fmt.Fprintf(os.Stderr, " skraak calls from-raven --file recording.Table.1.selections.txt\n")fmt.Fprintf(os.Stderr, " skraak calls from-raven --folder ./recordings --delete\n")}fs.Usage = usagePrinter(fs,"skraak calls from-raven [options]","Import Raven selections to .data files.\n"+"Reads *.selections.txt files and creates/merges .data files.\n\n"+"Behavior:\n"+" - Filter is always 'Raven' (parsed from filename)\n"+" - If .data file exists with Raven filter: error (refuses to clobber)\n"+" - If .data file exists with different filter: merge segments\n"+" - Frequency range preserved from Raven selections\n"+" - Certainty defaults to 70 (no confidence metric in Raven)","skraak calls from-raven --folder ./recordings","skraak calls from-raven --file recording.Table.1.selections.txt","skraak calls from-raven --folder ./recordings --delete",) - replacement in cmd/calls.go at line 498
fs.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: skraak calls summarise [options]\n\n")fmt.Fprintf(os.Stderr, "Summarise all .data files in a folder.\n")fmt.Fprintf(os.Stderr, "Outputs JSON with segments array and summary statistics.\n\n")fmt.Fprintf(os.Stderr, "Options:\n")fs.PrintDefaults()fmt.Fprintf(os.Stderr, "\nOutput includes:\n")fmt.Fprintf(os.Stderr, " - segments: array of all segments with labels (omitted with --brief)\n")fmt.Fprintf(os.Stderr, " - data_files_read: count of successfully parsed .data files\n")fmt.Fprintf(os.Stderr, " - data_files_skipped: list of files that failed to parse\n")fmt.Fprintf(os.Stderr, " - total_segments: total number of segments\n")fmt.Fprintf(os.Stderr, " - filters: per-filter statistics (segments, species counts)\n")fmt.Fprintf(os.Stderr, " - review_status: unreviewed/confirmed/dont_know counts\n")fmt.Fprintf(os.Stderr, " - operators/reviewers: unique values found\n")fmt.Fprintf(os.Stderr, "\nExamples:\n")fmt.Fprintf(os.Stderr, " skraak calls summarise --folder ./recordings > summary.json\n")fmt.Fprintf(os.Stderr, " skraak calls summarise --folder ./recordings --brief > summary.json # summary only\n")fmt.Fprintf(os.Stderr, " skraak calls summarise --folder ./recordings --filter opensoundscape-kiwi-1.2 --brief\n")}fs.Usage = usagePrinter(fs,"skraak calls summarise [options]","Summarise all .data files in a folder.\n"+"Outputs JSON with segments array and summary statistics.\n\n"+"Output includes:\n"+" - segments: array of all segments with labels (omitted with --brief)\n"+" - data_files_read: count of successfully parsed .data files\n"+" - data_files_skipped: list of files that failed to parse\n"+" - total_segments: total number of segments\n"+" - filters: per-filter statistics (segments, species counts)\n"+" - review_status: unreviewed/confirmed/dont_know counts\n"+" - operators/reviewers: unique values found","skraak calls summarise --folder ./recordings > summary.json","skraak calls summarise --folder ./recordings --brief > summary.json # summary only","skraak calls summarise --folder ./recordings --filter opensoundscape-kiwi-1.2 --brief",) - replacement in cmd/calls.go at line 519
// Validate required flagsif err := checkFlags(fs, "--folder", *folder); err != nil {if err := requireFlags(fs, map[string]any{"--folder": *folder}); err != nil {