From b2eba75f09904c1645a5da88bbc22619dff9a585 Mon Sep 17 00:00:00 2001 From: Jeremie Fraeys Date: Wed, 18 Feb 2026 21:27:38 -0500 Subject: [PATCH] feat: CLI shell completion for new commands Update bash and zsh completion scripts to include: - compare, find, export, outcome commands - privacy command and subcommands - All new narrative field flags (--hypothesis, --context, etc.) - Sandboxing options (--network, --read-only, --secret) --- cli/scripts/ml_completion.bash | 121 +++++++++++++++++++++++++++++---- cli/scripts/ml_completion.zsh | 108 +++++++++++++++++++++++++++-- 2 files changed, 209 insertions(+), 20 deletions(-) diff --git a/cli/scripts/ml_completion.bash b/cli/scripts/ml_completion.bash index 9e7fa16..262c2bd 100644 --- a/cli/scripts/ml_completion.bash +++ b/cli/scripts/ml_completion.bash @@ -11,11 +11,11 @@ _ml_completions() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - # Global options - global_opts="--help --verbose --quiet --monitor" - # Top-level subcommands - cmds="init sync queue requeue status monitor cancel prune watch dataset experiment" + cmds="init sync queue requeue status monitor cancel prune watch dataset experiment narrative outcome info logs annotate validate compare find export" + + # Global options + global_opts="--help --verbose --quiet --monitor --json" # If completing the subcommand itself if [[ ${COMP_CWORD} -eq 1 ]]; then @@ -41,7 +41,7 @@ _ml_completions() COMPREPLY=( $(compgen -d -- "${cur}") ) ;; queue) - queue_opts="--commit --priority --cpu --memory --gpu --gpu-memory --snapshot-id --snapshot-sha256 --args -- ${global_opts}" + queue_opts="--commit --priority --cpu --memory --gpu --gpu-memory --snapshot-id --snapshot-sha256 --args --note --hypothesis --context --intent --expected-outcome --experiment-group --tags --dry-run --validate --explain --force --mlflow --mlflow-uri --tensorboard --wandb-key --wandb-project --wandb-entity -- ${global_opts}" case "${prev}" in --priority) COMPREPLY=( $(compgen -W "0 1 2 3 4 5 6 7 8 9 10" -- "${cur}") ) @@ -52,7 +52,7 @@ _ml_completions() --gpu-memory) COMPREPLY=( $(compgen -W "4 8 16 24 32 48" -- "${cur}") ) ;; - --commit|--snapshot-id|--snapshot-sha256|--args) + --commit|--snapshot-id|--snapshot-sha256|--args|--note|--hypothesis|--context|--intent|--expected-outcome|--experiment-group|--tags|--mlflow-uri|--wandb-key|--wandb-project|--wandb-entity) # Free-form; no special completion ;; *) @@ -60,7 +60,7 @@ _ml_completions() COMPREPLY=( $(compgen -W "${queue_opts}" -- "${cur}") ) else # Suggest common job names (static for now) - COMPREPLY=( $(compgen -W "train evaluate deploy" -- "${cur}") ) + COMPREPLY=( $(compgen -W "train evaluate deploy test baseline" -- "${cur}") ) fi ;; esac @@ -114,16 +114,111 @@ _ml_completions() COMPREPLY=( $(compgen -d -- "${cur}") ) ;; dataset) - COMPREPLY=( $(compgen -W "list upload download delete info search" -- "${cur}") ) + COMPREPLY=( $(compgen -W "list register info search verify" -- "${cur}") ) ;; experiment) - COMPREPLY=( $(compgen -W "log show" -- "${cur}") ) + COMPREPLY=( $(compgen -W "log show list" -- "${cur}") ) ;; - *) - # Fallback to global options - COMPREPLY=( $(compgen -W "${global_opts}" -- "${cur}") ) + narrative) + narrative_opts="set --hypothesis --context --intent --expected-outcome --parent-run --experiment-group --tags --base --json --help" + case "${prev}" in + set) + COMPREPLY=( $(compgen -W "${narrative_opts}" -- "${cur}") ) + ;; + --hypothesis|--context|--intent|--expected-outcome|--parent-run|--experiment-group|--tags|--base) + # Free-form completion + ;; + *) + if [[ "${cur}" == --* ]]; then + COMPREPLY=( $(compgen -W "${narrative_opts}" -- "${cur}") ) + fi + ;; + esac + ;; + outcome) + outcome_opts="set --outcome --summary --learning --next-step --validation-status --surprise --base --json --help" + case "${prev}" in + set) + COMPREPLY=( $(compgen -W "${outcome_opts}" -- "${cur}") ) + ;; + --outcome) + COMPREPLY=( $(compgen -W "validates refutes inconclusive partial" -- "${cur}") ) + ;; + --validation-status) + COMPREPLY=( $(compgen -W "validates refutes inconclusive" -- "${cur}") ) + ;; + --summary|--learning|--next-step|--surprise|--base) + # Free-form completion + ;; + *) + if [[ "${cur}" == --* ]]; then + COMPREPLY=( $(compgen -W "${outcome_opts}" -- "${cur}") ) + fi + ;; + esac + ;; + info|logs|annotate|validate) + # These commands take a path/id and various options + info_opts="--base --json --help" + case "${prev}" in + --base) + COMPREPLY=( $(compgen -d -- "${cur}") ) + ;; + *) + if [[ "${cur}" == --* ]]; then + COMPREPLY=( $(compgen -W "${info_opts}" -- "${cur}") ) + fi + ;; + esac + ;; + compare) + compare_opts="--json --csv --all --fields --help" + case "${prev}" in + --fields) + COMPREPLY=( $(compgen -W "narrative,metrics,metadata,outcome" -- "${cur}") ) + ;; + *) + if [[ "${cur}" == --* ]]; then + COMPREPLY=( $(compgen -W "${compare_opts}" -- "${cur}") ) + fi + ;; + esac + ;; + find) + find_opts="--json --csv --limit --tag --outcome --dataset --experiment-group --author --after --before --help" + case "${prev}" in + --limit) + COMPREPLY=( $(compgen -W "10 20 50 100" -- "${cur}") ) + ;; + --outcome) + COMPREPLY=( $(compgen -W "validates refutes inconclusive partial" -- "${cur}") ) + ;; + --tag|--dataset|--experiment-group|--author|--after|--before) + # Free-form completion + ;; + *) + if [[ "${cur}" == --* ]]; then + COMPREPLY=( $(compgen -W "${find_opts}" -- "${cur}") ) + fi + ;; + esac + ;; + export) + export_opts="--bundle --anonymize --anonymize-level --base --json --help" + case "${prev}" in + --anonymize-level) + COMPREPLY=( $(compgen -W "metadata-only full" -- "${cur}") ) + ;; + --bundle|--base) + COMPREPLY=( $(compgen -f -- "${cur}") ) + ;; + *) + if [[ "${cur}" == --* ]]; then + COMPREPLY=( $(compgen -W "${export_opts}" -- "${cur}") ) + fi + ;; + esac ;; - esac return 0 } diff --git a/cli/scripts/ml_completion.zsh b/cli/scripts/ml_completion.zsh index 0f0058e..d1d71d2 100644 --- a/cli/scripts/ml_completion.zsh +++ b/cli/scripts/ml_completion.zsh @@ -17,7 +17,14 @@ _ml() { 'prune:Prune old experiments' 'watch:Watch directory for auto-sync' 'dataset:Manage datasets' - 'experiment:Manage experiments' + 'find:Search experiments by tags/outcome/dataset' + 'export:Export experiment for sharing' + 'compare:Compare two runs narrative fields' + 'outcome:Set post-run outcome' + 'info:Show run info' + 'logs:Fetch job logs' + 'annotate:Add annotation to run' + 'validate:Validate provenance' ) local -a global_opts @@ -26,6 +33,7 @@ _ml() { '--verbose:Enable verbose output' '--quiet:Suppress non-error output' '--monitor:Monitor progress of long-running operations' + '--json:Output structured JSON' ) local curcontext="$curcontext" state line @@ -53,7 +61,7 @@ _ml() { '--help[Show queue help]' \ '--verbose[Enable verbose output]' \ '--quiet[Suppress non-error output]' \ - '--monitor[Monitor progress]' \ + '--json[Output JSON]' \ '--commit[Commit id (40-hex) or unique prefix (>=7)]:commit id:' \ '--priority[Priority (0-255)]:priority:' \ '--cpu[CPU cores]:cpu:' \ @@ -63,6 +71,23 @@ _ml() { '--snapshot-id[Snapshot id]:snapshot id:' \ '--snapshot-sha256[Snapshot sha256]:snapshot sha256:' \ '--args[Runner args string]:args:' \ + '--note[Human notes]:note:' \ + '--hypothesis[Research hypothesis]:hypothesis:' \ + '--context[Background context]:context:' \ + '--intent[What you are trying to accomplish]:intent:' \ + '--expected-outcome[What you expect to happen]:expected outcome:' \ + '--experiment-group[Group related experiments]:experiment group:' \ + '--tags[Comma-separated tags]:tags:' \ + '--dry-run[Show what would be queued]' \ + '--validate[Validate without queuing]' \ + '--explain[Explain what will happen]' \ + '--force[Queue even if duplicate exists]' \ + '--mlflow[Enable MLflow]' \ + '--mlflow-uri[MLflow tracking URI]:uri:' \ + '--tensorboard[Enable TensorBoard]' \ + '--wandb-key[Wandb API key]:key:' \ + '--wandb-project[Wandb project]:project:' \ + '--wandb-entity[Wandb entity]:entity:' \ '1:job name:' \ '*:args separator:(--)' ;; @@ -124,16 +149,85 @@ _ml() { dataset) _values 'dataset subcommand' \ 'list[list datasets]' \ - 'upload[upload dataset]' \ - 'download[download dataset]' \ - 'delete[delete dataset]' \ + 'register[register dataset]' \ 'info[dataset info]' \ - 'search[search datasets]' + 'search[search datasets]' \ + 'verify[verify dataset]' ;; experiment) _values 'experiment subcommand' \ 'log[log metrics]' \ - 'show[show experiment]' + 'show[show experiment]' \ + 'list[list experiments]' + ;; + narrative) + _arguments -C \ + '1:subcommand:(set)' \ + '--hypothesis[Research hypothesis]:hypothesis:' \ + '--context[Background context]:context:' \ + '--intent[What you are trying to accomplish]:intent:' \ + '--expected-outcome[What you expect to happen]:expected outcome:' \ + '--parent-run[Parent run ID]:parent run:' \ + '--experiment-group[Group related experiments]:experiment group:' \ + '--tags[Comma-separated tags]:tags:' \ + '--base[Base path]:base:_directories' \ + '--json[Output JSON]' \ + '--help[Show help]' + ;; + outcome) + _arguments -C \ + '1:subcommand:(set)' \ + '--outcome[Outcome status]:outcome:(validates refutes inconclusive partial)' \ + '--summary[Summary of results]:summary:' \ + '--learning[A learning from this run]:learning:' \ + '--next-step[Suggested next step]:next step:' \ + '--validation-status[Did results validate hypothesis]:(validates refutes inconclusive)' \ + '--surprise[Unexpected finding]:surprise:' \ + '--base[Base path]:base:_directories' \ + '--json[Output JSON]' \ + '--help[Show help]' + ;; + info|logs|annotate|validate) + _arguments -C \ + '--base[Base path]:base:_directories' \ + '--json[Output JSON]' \ + '--help[Show help]' \ + '1:run id or path:' + ;; + compare) + _arguments -C \ + '--help[Show compare help]' \ + '--json[Output JSON]' \ + '--csv[Output CSV for spreadsheets]' \ + '--all[Show all fields]' \ + '--fields[Specify fields to compare]:fields:(narrative metrics metadata outcome)' \ + '1:run a:' \ + '2:run b:' + ;; + find) + _arguments -C \ + '--help[Show find help]' \ + '--json[Output JSON]' \ + '--csv[Output CSV for spreadsheets]' \ + '--limit[Max results]:limit:(10 20 50 100)' \ + '--tag[Filter by tag]:tag:' \ + '--outcome[Filter by outcome]:outcome:(validates refutes inconclusive partial)' \ + '--dataset[Filter by dataset]:dataset:' \ + '--experiment-group[Filter by group]:group:' \ + '--author[Filter by author]:author:' \ + '--after[After date]:date:' \ + '--before[Before date]:date:' \ + '::query:' + ;; + export) + _arguments -C \ + '--help[Show export help]' \ + '--json[Output JSON]' \ + '--bundle[Create bundle at path]:path:_files' \ + '--anonymize[Enable anonymization]' \ + '--anonymize-level[Anonymization level]:level:(metadata-only full)' \ + '--base[Base path]:base:_directories' \ + '1:run id or path:' ;; *) _arguments -C "${global_opts[@]}"