From fd11facb57333fd550cf981143eb29d9c9f1421f Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 10 Dec 2025 09:30:02 +0000 Subject: [PATCH 1/2] - Refactor all functions and add model properties to templates --- repli | 662 +++++++++++------- src/bashly.yml | 2 +- src/commands/debug.sh | 12 + src/commands/get.sh | 2 +- src/commands/template/new.sh | 13 +- src/lib/{ => core}/get_files_list.sh | 1 + src/lib/{ => core}/get_model_info.sh | 3 + src/lib/{ => core}/yurl.sh | 0 src/lib/{ => file}/download_outputs.sh | 0 src/lib/{ => file}/get_file_url.sh | 0 src/lib/{ => file}/get_unique_filename.sh | 0 src/lib/{ => file}/replace_embed_markers.sh | 0 .../{ => file}/replace_file_placeholders.sh | 0 src/lib/{ => file}/upload_to_replicate.sh | 0 .../json-parsers/json_get_model_properties.sh | 12 + .../json_get_model_properties_list.sh | 10 + src/lib/json-parsers/json_get_prop_enums.sh | 12 + .../json_to_template.sh} | 16 +- .../json_verify_success_file.sh} | 2 +- src/lib/{ => template}/get_templates_list.sh | 0 src/lib/{ => template}/select_template.sh | 0 src/lib/{ => template}/show_templates_list.sh | 0 src/lib/{ => utils}/colors.sh | 0 src/lib/{ => utils}/log.sh | 0 test/approvals/cat_banana_yaml | 8 + test/approvals/repli_get@error | 2 +- test/approvals/repli_info_google_nano_banana | 29 +- .../repli_info_google_nano_banana_json | 34 +- .../repli_info_google_nano_banana_schema | 6 +- .../repli_template_new_google_nano_banana | 4 +- ...epli_template_new_google_nano_banana_force | 4 +- ...emplate_new_google_nano_banana_name_banana | 4 +- test/approvals/repli_template_new_not_found | 2 +- .../repli_template_new_unofficial_mymodel | 4 +- .../v1/models/google/nano-banana/get.json | 34 +- .../v1/models/unofficial/mymodel/get.json | 34 +- 36 files changed, 628 insertions(+), 284 deletions(-) create mode 100644 src/commands/debug.sh rename src/lib/{ => core}/get_files_list.sh (71%) rename src/lib/{ => core}/get_model_info.sh (83%) rename src/lib/{ => core}/yurl.sh (100%) rename src/lib/{ => file}/download_outputs.sh (100%) rename src/lib/{ => file}/get_file_url.sh (100%) rename src/lib/{ => file}/get_unique_filename.sh (100%) rename src/lib/{ => file}/replace_embed_markers.sh (100%) rename src/lib/{ => file}/replace_file_placeholders.sh (100%) rename src/lib/{ => file}/upload_to_replicate.sh (100%) create mode 100644 src/lib/json-parsers/json_get_model_properties.sh create mode 100644 src/lib/json-parsers/json_get_model_properties_list.sh create mode 100644 src/lib/json-parsers/json_get_prop_enums.sh rename src/lib/{get_example_from_replicate.sh => json-parsers/json_to_template.sh} (68%) rename src/lib/{verify_success_json.sh => json-parsers/json_verify_success_file.sh} (91%) rename src/lib/{ => template}/get_templates_list.sh (100%) rename src/lib/{ => template}/select_template.sh (100%) rename src/lib/{ => template}/show_templates_list.sh (100%) rename src/lib/{ => utils}/colors.sh (100%) rename src/lib/{ => utils}/log.sh (100%) diff --git a/repli b/repli index a0e58d9..966c845 100755 --- a/repli +++ b/repli @@ -91,6 +91,28 @@ repli_usage() { fi } +# :command.usage +repli_debug_usage() { + printf "repli debug - Debug\n\n" + + printf "%s\n" "$(bold "Usage:")" + printf " repli debug\n" + printf " repli debug --help | -h\n" + echo + + # :command.long_usage + if [[ -n "$long_usage" ]]; then + # :command.usage_options + printf "%s\n" "$(bold "Options:")" + + # :command.usage_fixed_flags + printf " %s\n" "$(green "--help, -h")" + printf " Show this help\n" + echo + + fi +} + # :command.usage repli_new_usage() { printf "repli new - Create a new configuration file from a tmeplate\n\n" @@ -715,54 +737,79 @@ inspect_args() { } # :command.user_lib -# src/lib/colors.sh -enable_auto_colors() { - if [[ -z ${NO_COLOR+x} && ! -t 1 ]]; then - NO_COLOR=1 - fi +# src/lib/core/get_files_list.sh +get_files_list() { + curl -s -H "Authorization: Token $REPLICATE_API_TOKEN" "$replicate_host/v1/files" } -print_in_color() { - local color="$1" - shift - if [[ "${NO_COLOR:-}" == "" ]]; then - printf "$color%b\e[0m\n" "$*" - else - printf "%b\n" "$*" +# src/lib/core/get_model_info.sh +get_model_info() { + local model="$1" + local body status + + # Capture body AND HTTP status code + log debug calling replicate API + body=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + "$replicate_host/v1/models/$model") + + # Split last line as status code, everything above is the JSON body + status=$(tail -n1 <<<"$body") + body=$(sed '$d' <<<"$body") + + # Any 4xx or 5xx status means error + if [[ "$status" -ge 400 ]]; then + log error "failed getting model info for $(blue "$model")" + log error "($status) $(jq -r '.detail // empty' <<<"$body")" + return 1 fi + + printf '%s\n' "$body" } -red() { print_in_color "\e[31m" "$*"; } -green() { print_in_color "\e[32m" "$*"; } -yellow() { print_in_color "\e[33m" "$*"; } -blue() { print_in_color "\e[34m" "$*"; } -magenta() { print_in_color "\e[35m" "$*"; } -cyan() { print_in_color "\e[36m" "$*"; } -black() { print_in_color "\e[30m" "$*"; } -white() { print_in_color "\e[37m" "$*"; } +# src/lib/core/yurl.sh +yurl() { + local yaml_file="$1" + local model version json payload url -bold() { print_in_color "\e[1m" "$*"; } -underlined() { print_in_color "\e[4m" "$*"; } + # YAML validation + if ! yq -e '.input' "$yaml_file" >/dev/null 2>&1; then + log error "no input field in $(blue "$yaml_file")" + return 1 + fi -red_bold() { print_in_color "\e[1;31m" "$*"; } -green_bold() { print_in_color "\e[1;32m" "$*"; } -yellow_bold() { print_in_color "\e[1;33m" "$*"; } -blue_bold() { print_in_color "\e[1;34m" "$*"; } -magenta_bold() { print_in_color "\e[1;35m" "$*"; } -cyan_bold() { print_in_color "\e[1;36m" "$*"; } -black_bold() { print_in_color "\e[1;30m" "$*"; } -white_bold() { print_in_color "\e[1;37m" "$*"; } + model=$(yq -r '.model // ""' "$yaml_file") + version=$(yq -r '.version // ""' "$yaml_file") -red_underlined() { print_in_color "\e[4;31m" "$*"; } -green_underlined() { print_in_color "\e[4;32m" "$*"; } -yellow_underlined() { print_in_color "\e[4;33m" "$*"; } -blue_underlined() { print_in_color "\e[4;34m" "$*"; } -magenta_underlined() { print_in_color "\e[4;35m" "$*"; } -cyan_underlined() { print_in_color "\e[4;36m" "$*"; } -black_underlined() { print_in_color "\e[4;30m" "$*"; } -white_underlined() { print_in_color "\e[4;37m" "$*"; } + if [[ -z "$model" && -z "$version" ]]; then + log error "no model or version field in $(blue "$yaml_file")" + return 1 + fi + + # Convert .input YAML → JSON and replace and ~filename.txt + payload=$(yq -o=json '.' "$yaml_file") + payload=$(replace_file_placeholders "$payload") + payload=$(replace_embed_markers "$payload") + + # Set payload and URL for official/unofficial models + if [[ -n "$model" ]]; then + payload=$(echo "$payload" | jq 'del(.model)') + url="$replicate_host/v1/models/${model}/predictions" + else + url="$replicate_host/v1/predictions" + fi + + # Pipe the evaluated JSON to curl which uses it (@-) as its data. + echo "$payload" | + curl -sS -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Prefer: wait" \ + -d @- \ + "$url" +} -# src/lib/download_outputs.sh +# src/lib/file/download_outputs.sh # usage: download_outputs PREFIX json_file download_outputs() { local prefix="$1" @@ -804,38 +851,7 @@ download_outputs() { done } -# src/lib/filters/templates_dir_exist.sh -filter_templates_dir_exists() { - if [[ ! -d "$templates_dir" ]]; then - echo "Templates dir not found ($templates_dir), set using REPLI_TEMPLATES_DIR" - fi -} - -# src/lib/get_example_from_replicate.sh -get_example_from_replicate() { - json=$(get_model_info "$model") || return 1 - - # Determine whether the model is official - is_official=$(jq -r '.is_official // false' <<<"$json") - - if [[ "$is_official" == "true" ]]; then - log debug "model status: $(blue official)" - # Official image - jq --arg model "$model" \ - '{model: $model, input: .default_example.input}' \ - <<<"$json" | yq -P - - else - # Non-official image: use version instead of model - log debug "model status: $(blue unofficial)" - version=$(jq -r '.latest_version.id' <<<"$json") - - jq --arg model "$model" --arg version "$version" \ - '{version: ($model + ":" + $version), input: .default_example.input}' \ - <<<"$json" | yq -P - - fi -} - -# src/lib/get_file_url.sh +# src/lib/file/get_file_url.sh get_file_url() { local file="$1" local files_list="$output_dir/files.ini" @@ -861,62 +877,7 @@ get_file_url() { echo "$url" } -# src/lib/get_files_list.sh -get_files_list() { - curl -s -H "Authorization: Token $REPLICATE_API_TOKEN" "$replicate_host/v1/files" -} - -# src/lib/get_model_info.sh -get_model_info() { - local model="$1" - local body status - - # Capture body AND HTTP status code - log debug calling replicate API - body=$(curl -s -w "\n%{http_code}" \ - -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ - "$replicate_host/v1/models/$model") - - # Split last line as status code, everything above is the JSON body - status=$(tail -n1 <<<"$body") - body=$(sed '$d' <<<"$body") - - # Any 4xx or 5xx status means error - if [[ "$status" -ge 400 ]]; then - log error "failed getting model info for $(blue "$model")" - log error "($status) $(jq -r '.detail // empty' <<<"$body")" - return 1 - fi - - printf '%s\n' "$body" -} - -# src/lib/get_templates_list.sh -get_templates_list() { - local search="${1:-}" - - while IFS= read -r -d '' rel; do - # Strip .yaml - local name="${rel%.yaml}" - - # If not nested, show only the basename - if [[ "$rel" != */* ]]; then - printf '%s\0' "$name" - else - # Keep the relative directory path - printf '%s\0' "$name" - fi - - done < <( - find "$templates_dir" \ - -type f -name '*.yaml' \ - -printf '%P\0' | # <-- %P = path relative to $templates_dir - grep -iz "$search" | - sort -zV - ) -} - -# src/lib/get_unique_filename.sh +# src/lib/file/get_unique_filename.sh get_unique_filename() { local base="$1" local max=0 @@ -934,52 +895,7 @@ get_unique_filename() { echo "${base}-$((max + 1))" } -# src/lib/log.sh -log() { - local level="$1" - shift - local msg="$*" - local caller color_func rank_req rank_cur - - case "$level" in - debug) rank_req=10 ;; - info) rank_req=20 ;; - warn) rank_req=30 ;; - error) rank_req=40 ;; - *) rank_req=20 ;; - esac - - case "$log_level" in - debug) rank_cur=10 ;; - info) rank_cur=20 ;; - warn) rank_cur=30 ;; - error) rank_cur=40 ;; - *) rank_cur=20 ;; - esac - - # filter by level - [[ $rank_req -lt $rank_cur ]] && return 0 - - # choose color function - color_func="cyan" - case "$level" in - debug) color_func="magenta" ;; - info) color_func="green" ;; - warn) color_func="yellow_bold" ;; - error) color_func="red_bold" ;; - esac - - if [[ "$log_level" == "debug" ]]; then - caller="${FUNCNAME[1]}" - printf "$(green_bold "•") %s • %s $(green_bold →) %s\n" \ - "$("$color_func" "$level")" "$(cyan "$caller")" "$msg" >&2 - else - printf "$(green_bold "•") %s $(green_bold →) %s\n" \ - "$("$color_func" "$level")" "$msg" >&2 - fi -} - -# src/lib/replace_embed_markers.sh +# src/lib/file/replace_embed_markers.sh replace_embed_markers() { local json="$1" local files=() @@ -1012,7 +928,7 @@ replace_embed_markers() { echo "$json" } -# src/lib/replace_file_placeholders.sh +# src/lib/file/replace_file_placeholders.sh replace_file_placeholders() { local json="$1" local url @@ -1041,19 +957,156 @@ replace_file_placeholders() { echo "$json" } -# src/lib/select_template.sh -select_template() { - local search="$1" - local exact="$2" +# src/lib/file/upload_to_replicate.sh +upload_to_replicate() { + local input_file="$1" - # Get templates list matching the search - mapfile -d '' templates < <(get_templates_list "$search") + file=$(curl -s -X POST "$replicate_host/v1/files" \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: multipart/form-data" \ + -F "content=@$input_file;type=application/octet-stream;title=$(basename "$input_file")") - # No matches - if [[ ${#templates[@]} -eq 0 ]]; then - log error "no matching templates" - return 1 - fi + input_file_url=$(echo "$file" | jq -r '.urls.get') + if [[ -z "$input_file_url" ]]; then + log error "could not get file URL" + log debug "$file" + return 1 + fi + + echo "$input_file_url" +} + +# src/lib/filters/templates_dir_exist.sh +filter_templates_dir_exists() { + if [[ ! -d "$templates_dir" ]]; then + echo "Templates dir not found ($templates_dir), set using REPLI_TEMPLATES_DIR" + fi +} + +# src/lib/json-parsers/json_get_model_properties.sh +json_get_model_properties() { + local json="$1" + + # iterate over properties + while read -r prop; do + enums=$(json_get_prop_enums "$json" "$prop") + [[ -n "$enums" ]] || enums='*' + echo "$prop: $enums" + done < <(json_get_model_properties_list "$json") +} + +# src/lib/json-parsers/json_get_model_properties_list.sh +json_get_model_properties_list() { + local json="$1" + + echo "$json" | jq -r ' + .latest_version.openapi_schema.components.schemas.Input.properties + | keys[] + ' +} + +# src/lib/json-parsers/json_get_prop_enums.sh +json_get_prop_enums() { + local json="$1" + local prop="$2" + + echo "$json" | jq -r --arg prop "$prop" ' + .latest_version.openapi_schema.components.schemas[$prop] + | .. | objects | select(has("enum")) | .enum? // empty + | join(", ") + ' +} + +# src/lib/json-parsers/json_to_template.sh +json_to_template() { + json="$1" + + # Determine whether the model is official + is_official=$(jq -r '.is_official // false' <<<"$json") + + echo "# https://replicate.com/$model" + echo + + if [[ "$is_official" == "true" ]]; then + log debug "model status: $(blue official)" + # Official image + jq --arg model "$model" \ + '{model: $model, input: .default_example.input}' \ + <<<"$json" | yq -P - + else + # Non-official image: use version instead of model + log debug "model status: $(blue unofficial)" + version=$(jq -r '.latest_version.id' <<<"$json") + + jq --arg model "$model" --arg version "$version" \ + '{version: ($model + ":" + $version), input: .default_example.input}' \ + <<<"$json" | yq -P - + fi + + echo + echo "# PROPERTIES" + mapfile -t props < <(json_get_model_properties "$json") + for propline in "${props[@]}"; do + echo "# $propline" + done +} + +# src/lib/json-parsers/json_verify_success_file.sh +json_verify_success_file() { + local json_file="$1" + local status + + if [[ ! -f "$json_file" ]]; then + log error "file not found: $(blue "$json_file")" + return 1 + fi + + status=$(jq -r '.status // empty' "$json_file") + + [[ "$status" == "succeeded" ]] && return 0 + log error "received invalid response:" + yq -P -oy "$json_file" + return 1 +} + +# src/lib/template/get_templates_list.sh +get_templates_list() { + local search="${1:-}" + + while IFS= read -r -d '' rel; do + # Strip .yaml + local name="${rel%.yaml}" + + # If not nested, show only the basename + if [[ "$rel" != */* ]]; then + printf '%s\0' "$name" + else + # Keep the relative directory path + printf '%s\0' "$name" + fi + + done < <( + find "$templates_dir" \ + -type f -name '*.yaml' \ + -printf '%P\0' | # <-- %P = path relative to $templates_dir + grep -iz "$search" | + sort -zV + ) +} + +# src/lib/template/select_template.sh +select_template() { + local search="$1" + local exact="$2" + + # Get templates list matching the search + mapfile -d '' templates < <(get_templates_list "$search") + + # No matches + if [[ ${#templates[@]} -eq 0 ]]; then + log error "no matching templates" + return 1 + fi if [[ ${#templates[@]} -eq 1 ]]; then # Exactly one match → auto-select @@ -1080,7 +1133,7 @@ select_template() { fi } -# src/lib/show_templates_list.sh +# src/lib/template/show_templates_list.sh show_templates_list() { local search="${1:-.}" # optional search term @@ -1104,23 +1157,96 @@ show_templates_list() { echo } -# src/lib/upload_to_replicate.sh -upload_to_replicate() { - local input_file="$1" - - file=$(curl -s -X POST "$replicate_host/v1/files" \ - -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ - -H "Content-Type: multipart/form-data" \ - -F "content=@$input_file;type=application/octet-stream;title=$(basename "$input_file")") +# src/lib/utils/colors.sh +enable_auto_colors() { + if [[ -z ${NO_COLOR+x} && ! -t 1 ]]; then + NO_COLOR=1 + fi +} - input_file_url=$(echo "$file" | jq -r '.urls.get') - if [[ -z "$input_file_url" ]]; then - log error "could not get file URL" - log debug "$file" - return 1 +print_in_color() { + local color="$1" + shift + if [[ "${NO_COLOR:-}" == "" ]]; then + printf "$color%b\e[0m\n" "$*" + else + printf "%b\n" "$*" fi +} - echo "$input_file_url" +red() { print_in_color "\e[31m" "$*"; } +green() { print_in_color "\e[32m" "$*"; } +yellow() { print_in_color "\e[33m" "$*"; } +blue() { print_in_color "\e[34m" "$*"; } +magenta() { print_in_color "\e[35m" "$*"; } +cyan() { print_in_color "\e[36m" "$*"; } +black() { print_in_color "\e[30m" "$*"; } +white() { print_in_color "\e[37m" "$*"; } + +bold() { print_in_color "\e[1m" "$*"; } +underlined() { print_in_color "\e[4m" "$*"; } + +red_bold() { print_in_color "\e[1;31m" "$*"; } +green_bold() { print_in_color "\e[1;32m" "$*"; } +yellow_bold() { print_in_color "\e[1;33m" "$*"; } +blue_bold() { print_in_color "\e[1;34m" "$*"; } +magenta_bold() { print_in_color "\e[1;35m" "$*"; } +cyan_bold() { print_in_color "\e[1;36m" "$*"; } +black_bold() { print_in_color "\e[1;30m" "$*"; } +white_bold() { print_in_color "\e[1;37m" "$*"; } + +red_underlined() { print_in_color "\e[4;31m" "$*"; } +green_underlined() { print_in_color "\e[4;32m" "$*"; } +yellow_underlined() { print_in_color "\e[4;33m" "$*"; } +blue_underlined() { print_in_color "\e[4;34m" "$*"; } +magenta_underlined() { print_in_color "\e[4;35m" "$*"; } +cyan_underlined() { print_in_color "\e[4;36m" "$*"; } +black_underlined() { print_in_color "\e[4;30m" "$*"; } +white_underlined() { print_in_color "\e[4;37m" "$*"; } + +# src/lib/utils/log.sh +log() { + local level="$1" + shift + local msg="$*" + local caller color_func rank_req rank_cur + + case "$level" in + debug) rank_req=10 ;; + info) rank_req=20 ;; + warn) rank_req=30 ;; + error) rank_req=40 ;; + *) rank_req=20 ;; + esac + + case "$log_level" in + debug) rank_cur=10 ;; + info) rank_cur=20 ;; + warn) rank_cur=30 ;; + error) rank_cur=40 ;; + *) rank_cur=20 ;; + esac + + # filter by level + [[ $rank_req -lt $rank_cur ]] && return 0 + + # choose color function + color_func="cyan" + case "$level" in + debug) color_func="magenta" ;; + info) color_func="green" ;; + warn) color_func="yellow_bold" ;; + error) color_func="red_bold" ;; + esac + + if [[ "$log_level" == "debug" ]]; then + caller="${FUNCNAME[1]}" + printf "$(green_bold "•") %s • %s $(green_bold →) %s\n" \ + "$("$color_func" "$level")" "$(cyan "$caller")" "$msg" >&2 + else + printf "$(green_bold "•") %s $(green_bold →) %s\n" \ + "$("$color_func" "$level")" "$msg" >&2 + fi } # src/lib/validations/dir_exists.sh @@ -1158,67 +1284,24 @@ validate_template_exists() { fi } -# src/lib/verify_success_json.sh -verify_success_json() { - local json_file="$1" - local status - - if [[ ! -f "$json_file" ]]; then - log error "file not found: $(blue "$json_file")" - return 1 - fi - - status=$(jq -r '.status // empty' "$json_file") - - [[ "$status" == "succeeded" ]] && return 0 - log error "received invalid response:" - yq -P -oy "$json_file" - return 1 -} - -# src/lib/yurl.sh -yurl() { - local yaml_file="$1" - local model version json payload url - - # YAML validation - if ! yq -e '.input' "$yaml_file" >/dev/null 2>&1; then - log error "no input field in $(blue "$yaml_file")" - return 1 - fi +# :command.command_functions +# :command.function +repli_debug_command() { - model=$(yq -r '.model // ""' "$yaml_file") - version=$(yq -r '.version // ""' "$yaml_file") + # src/commands/debug.sh - if [[ -z "$model" && -z "$version" ]]; then - log error "no model or version field in $(blue "$yaml_file")" - return 1 - fi + model="google/nano-banana" + json=$(get_model_info "$model") || return 1 + # get_example_from_replicate "$model" - # Convert .input YAML → JSON and replace and ~filename.txt - payload=$(yq -o=json '.' "$yaml_file") - payload=$(replace_file_placeholders "$payload") - payload=$(replace_embed_markers "$payload") + mapfile -t lines < <(json_get_model_properties "$json") - # Set payload and URL for official/unofficial models - if [[ -n "$model" ]]; then - payload=$(echo "$payload" | jq 'del(.model)') - url="$replicate_host/v1/models/${model}/predictions" - else - url="$replicate_host/v1/predictions" - fi + for line in "${lines[@]}"; do + echo "==> $line" + done - # Pipe the evaluated JSON to curl which uses it (@-) as its data. - echo "$payload" | - curl -sS -X POST \ - -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ - -H "Content-Type: application/json" \ - -H "Prefer: wait" \ - -d @- \ - "$url" } -# :command.command_functions # :command.function repli_new_command() { @@ -1265,7 +1348,7 @@ repli_get_command() { fi # download outputs - if verify_success_json "$outfile"; then + if json_verify_success_file "$outfile"; then download_outputs "$prefix" "$outfile" fi @@ -1359,11 +1442,15 @@ repli_template_new_command() { fi mkdir -p "$templates_dir" - log debug "fetching example for $(blue "$model")" - template="$(get_example_from_replicate "$model")" - - log info "saving to $(blue "$outpath")" - [[ -n "$template" ]] && printf "%s\n" "$template" >"$outpath" + log info "building template for $(blue "$model")" + json=$(get_model_info "$model") || return 1 + template="$(json_to_template "$json")" + if [[ -n "$template" ]]; then + log info "saving to $(blue "$outpath")" + printf "%s\n" "$template" >"$outpath" + else + log error "received an empty template" + fi } @@ -1551,6 +1638,13 @@ parse_requirements() { case $action in -*) ;; + debug) + action="debug" + shift + repli_debug_parse_requirements "$@" + shift $# + ;; + new | n | init) action="new" shift @@ -1643,6 +1737,53 @@ parse_requirements() { } +# :command.parse_requirements +repli_debug_parse_requirements() { + local key + + # :command.fixed_flags_filter + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + --help | -h) + long_usage=yes + repli_debug_usage + exit + ;; + + *) + break + ;; + + esac + done + + # :command.command_filter + action="debug" + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -?*) + printf "invalid option: %s\n" "$key" >&2 + exit 1 + ;; + + *) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + printf "invalid argument: %s\n" "$key" >&2 + exit 1 + + ;; + + esac + done + +} + # :command.parse_requirements repli_new_parse_requirements() { local key @@ -2812,6 +2953,7 @@ run() { parse_requirements "${input[@]}" case "$action" in + "debug") repli_debug_command ;; "new") repli_new_command ;; "get") repli_get_command ;; "edit") repli_edit_command ;; diff --git a/src/bashly.yml b/src/bashly.yml index 8c648ba..ed9bc32 100644 --- a/src/bashly.yml +++ b/src/bashly.yml @@ -36,7 +36,7 @@ variables: value: $REPLICATE_HOST commands: -# - { name: debug, help: Debug, private: true } +- { name: debug, help: Debug, private: true } - name: new alias: ['n', init] help: Create a new configuration file from a tmeplate diff --git a/src/commands/debug.sh b/src/commands/debug.sh new file mode 100644 index 0000000..d64ae8c --- /dev/null +++ b/src/commands/debug.sh @@ -0,0 +1,12 @@ + + + +model="google/nano-banana" +json=$(get_model_info "$model") || return 1 +# get_example_from_replicate "$model" + +mapfile -t lines < <(json_get_model_properties "$json") + +for line in "${lines[@]}"; do + echo "==> $line" +done diff --git a/src/commands/get.sh b/src/commands/get.sh index dd8d6d6..7054ad4 100644 --- a/src/commands/get.sh +++ b/src/commands/get.sh @@ -15,6 +15,6 @@ else fi # download outputs -if verify_success_json "$outfile"; then +if json_verify_success_file "$outfile"; then download_outputs "$prefix" "$outfile" fi diff --git a/src/commands/template/new.sh b/src/commands/template/new.sh index 80ba412..ad36f8a 100644 --- a/src/commands/template/new.sh +++ b/src/commands/template/new.sh @@ -14,8 +14,13 @@ if [[ -f "$outpath" && ! "$force" ]]; then fi mkdir -p "$templates_dir" -log debug "fetching example for $(blue "$model")" -template="$(get_example_from_replicate "$model")" +log info "building template for $(blue "$model")" +json=$(get_model_info "$model") || return 1 +template="$(json_to_template "$json")" +if [[ -n "$template" ]]; then + log info "saving to $(blue "$outpath")" + printf "%s\n" "$template" >"$outpath" +else + log error "received an empty template" +fi -log info "saving to $(blue "$outpath")" -[[ -n "$template" ]] && printf "%s\n" "$template" >"$outpath" diff --git a/src/lib/get_files_list.sh b/src/lib/core/get_files_list.sh similarity index 71% rename from src/lib/get_files_list.sh rename to src/lib/core/get_files_list.sh index f3702c3..aa9c79c 100644 --- a/src/lib/get_files_list.sh +++ b/src/lib/core/get_files_list.sh @@ -1,3 +1,4 @@ +## Get uploaded files list from replicate get_files_list() { curl -s -H "Authorization: Token $REPLICATE_API_TOKEN" "$replicate_host/v1/files" } diff --git a/src/lib/get_model_info.sh b/src/lib/core/get_model_info.sh similarity index 83% rename from src/lib/get_model_info.sh rename to src/lib/core/get_model_info.sh index ca2b13c..29928ec 100644 --- a/src/lib/get_model_info.sh +++ b/src/lib/core/get_model_info.sh @@ -1,3 +1,6 @@ +## Call replicate and get the JSON for the model +## usage: json=$(get_model_info "$model") || return 1 +## input: model name get_model_info() { local model="$1" local body status diff --git a/src/lib/yurl.sh b/src/lib/core/yurl.sh similarity index 100% rename from src/lib/yurl.sh rename to src/lib/core/yurl.sh diff --git a/src/lib/download_outputs.sh b/src/lib/file/download_outputs.sh similarity index 100% rename from src/lib/download_outputs.sh rename to src/lib/file/download_outputs.sh diff --git a/src/lib/get_file_url.sh b/src/lib/file/get_file_url.sh similarity index 100% rename from src/lib/get_file_url.sh rename to src/lib/file/get_file_url.sh diff --git a/src/lib/get_unique_filename.sh b/src/lib/file/get_unique_filename.sh similarity index 100% rename from src/lib/get_unique_filename.sh rename to src/lib/file/get_unique_filename.sh diff --git a/src/lib/replace_embed_markers.sh b/src/lib/file/replace_embed_markers.sh similarity index 100% rename from src/lib/replace_embed_markers.sh rename to src/lib/file/replace_embed_markers.sh diff --git a/src/lib/replace_file_placeholders.sh b/src/lib/file/replace_file_placeholders.sh similarity index 100% rename from src/lib/replace_file_placeholders.sh rename to src/lib/file/replace_file_placeholders.sh diff --git a/src/lib/upload_to_replicate.sh b/src/lib/file/upload_to_replicate.sh similarity index 100% rename from src/lib/upload_to_replicate.sh rename to src/lib/file/upload_to_replicate.sh diff --git a/src/lib/json-parsers/json_get_model_properties.sh b/src/lib/json-parsers/json_get_model_properties.sh new file mode 100644 index 0000000..8a3bba3 --- /dev/null +++ b/src/lib/json-parsers/json_get_model_properties.sh @@ -0,0 +1,12 @@ +## prints all model properties that have enum options +## input: a model info JSON +json_get_model_properties() { + local json="$1" + + # iterate over properties + while read -r prop; do + enums=$(json_get_prop_enums "$json" "$prop") + [[ -n "$enums" ]] || enums='*' + echo "$prop: $enums" + done < <(json_get_model_properties_list "$json") +} diff --git a/src/lib/json-parsers/json_get_model_properties_list.sh b/src/lib/json-parsers/json_get_model_properties_list.sh new file mode 100644 index 0000000..f0e4cab --- /dev/null +++ b/src/lib/json-parsers/json_get_model_properties_list.sh @@ -0,0 +1,10 @@ +## returns an array of all input properties from the JSON schema +## input: model info JSON +json_get_model_properties_list() { + local json="$1" + + echo "$json" | jq -r ' + .latest_version.openapi_schema.components.schemas.Input.properties + | keys[] + ' +} diff --git a/src/lib/json-parsers/json_get_prop_enums.sh b/src/lib/json-parsers/json_get_prop_enums.sh new file mode 100644 index 0000000..fe6986e --- /dev/null +++ b/src/lib/json-parsers/json_get_prop_enums.sh @@ -0,0 +1,12 @@ +## returns a space delimited string of allowed enum values for a property +## input: model info JSON, property name +json_get_prop_enums() { + local json="$1" + local prop="$2" + + echo "$json" | jq -r --arg prop "$prop" ' + .latest_version.openapi_schema.components.schemas[$prop] + | .. | objects | select(has("enum")) | .enum? // empty + | join(", ") + ' +} diff --git a/src/lib/get_example_from_replicate.sh b/src/lib/json-parsers/json_to_template.sh similarity index 68% rename from src/lib/get_example_from_replicate.sh rename to src/lib/json-parsers/json_to_template.sh index 1c687b3..3981eb1 100644 --- a/src/lib/get_example_from_replicate.sh +++ b/src/lib/json-parsers/json_to_template.sh @@ -1,9 +1,14 @@ -get_example_from_replicate() { - json=$(get_model_info "$model") || return 1 +## generates a template YAML from model info +## input: model info JSON +json_to_template() { + json="$1" # Determine whether the model is official is_official=$(jq -r '.is_official // false' <<<"$json") + echo "# https://replicate.com/$model" + echo + if [[ "$is_official" == "true" ]]; then log debug "model status: $(blue official)" # Official image @@ -19,4 +24,11 @@ get_example_from_replicate() { '{version: ($model + ":" + $version), input: .default_example.input}' \ <<<"$json" | yq -P - fi + + echo + echo "# PROPERTIES" + mapfile -t props < <(json_get_model_properties "$json") + for propline in "${props[@]}"; do + echo "# $propline" + done } diff --git a/src/lib/verify_success_json.sh b/src/lib/json-parsers/json_verify_success_file.sh similarity index 91% rename from src/lib/verify_success_json.sh rename to src/lib/json-parsers/json_verify_success_file.sh index 035c0bd..e6b8329 100644 --- a/src/lib/verify_success_json.sh +++ b/src/lib/json-parsers/json_verify_success_file.sh @@ -1,4 +1,4 @@ -verify_success_json() { +json_verify_success_file() { local json_file="$1" local status diff --git a/src/lib/get_templates_list.sh b/src/lib/template/get_templates_list.sh similarity index 100% rename from src/lib/get_templates_list.sh rename to src/lib/template/get_templates_list.sh diff --git a/src/lib/select_template.sh b/src/lib/template/select_template.sh similarity index 100% rename from src/lib/select_template.sh rename to src/lib/template/select_template.sh diff --git a/src/lib/show_templates_list.sh b/src/lib/template/show_templates_list.sh similarity index 100% rename from src/lib/show_templates_list.sh rename to src/lib/template/show_templates_list.sh diff --git a/src/lib/colors.sh b/src/lib/utils/colors.sh similarity index 100% rename from src/lib/colors.sh rename to src/lib/utils/colors.sh diff --git a/src/lib/log.sh b/src/lib/utils/log.sh similarity index 100% rename from src/lib/log.sh rename to src/lib/utils/log.sh diff --git a/test/approvals/cat_banana_yaml b/test/approvals/cat_banana_yaml index f0296bf..400f3e8 100644 --- a/test/approvals/cat_banana_yaml +++ b/test/approvals/cat_banana_yaml @@ -1,4 +1,12 @@ +# https://replicate.com/google/nano-banana + model: google/nano-banana input: prompt: an image of a JSON file output_format: jpg + +# PROPERTIES +# aspect_ratio: match_input_image, 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 +# image_input: * +# output_format: jpg, png +# prompt: * diff --git a/test/approvals/repli_get@error b/test/approvals/repli_get@error index 3362f00..63c2e78 100644 --- a/test/approvals/repli_get@error +++ b/test/approvals/repli_get@error @@ -2,7 +2,7 @@ • debug • repli_get_command → prefix: repli-1 • debug • repli_get_command → outfile: tmp/repli-1.json • info • repli_get_command → calling API and saving tmp/repli-1.json -• error • verify_success_json → received invalid response: +• error • json_verify_success_file → received invalid response: detail: | - input.image_input.0: Invalid type. Expected: string, given: object status: 422 diff --git a/test/approvals/repli_info_google_nano_banana b/test/approvals/repli_info_google_nano_banana index a6a97c7..67f35fa 100644 --- a/test/approvals/repli_info_google_nano_banana +++ b/test/approvals/repli_info_google_nano_banana @@ -13,4 +13,31 @@ latest_version: title: Input required: - prompt - properties: '... trimmed ...' + properties: + prompt: '... trimmed... ' + image_input: '... trimmed... ' + aspect_ratio: '... trimmed... ' + output_format: '... trimmed... ' + aspect_ratio: + enum: + - match_input_image + - 1:1 + - 2:3 + - 3:2 + - 3:4 + - 4:3 + - 4:5 + - 5:4 + - 9:16 + - 16:9 + - 21:9 + type: string + title: aspect_ratio + description: An enumeration. + output_format: + enum: + - jpg + - png + type: string + title: output_format + description: An enumeration. diff --git a/test/approvals/repli_info_google_nano_banana_json b/test/approvals/repli_info_google_nano_banana_json index f918235..194680c 100644 --- a/test/approvals/repli_info_google_nano_banana_json +++ b/test/approvals/repli_info_google_nano_banana_json @@ -17,7 +17,39 @@ "required": [ "prompt" ], - "properties": "... trimmed ..." + "properties": { + "prompt": "... trimmed... ", + "image_input": "... trimmed... ", + "aspect_ratio": "... trimmed... ", + "output_format": "... trimmed... " + } + }, + "aspect_ratio": { + "enum": [ + "match_input_image", + "1:1", + "2:3", + "3:2", + "3:4", + "4:3", + "4:5", + "5:4", + "9:16", + "16:9", + "21:9" + ], + "type": "string", + "title": "aspect_ratio", + "description": "An enumeration." + }, + "output_format": { + "enum": [ + "jpg", + "png" + ], + "type": "string", + "title": "output_format", + "description": "An enumeration." } } } diff --git a/test/approvals/repli_info_google_nano_banana_schema b/test/approvals/repli_info_google_nano_banana_schema index df9f4f8..a26908d 100644 --- a/test/approvals/repli_info_google_nano_banana_schema +++ b/test/approvals/repli_info_google_nano_banana_schema @@ -3,4 +3,8 @@ type: object title: Input required: - prompt -properties: '... trimmed ...' +properties: + prompt: '... trimmed... ' + image_input: '... trimmed... ' + aspect_ratio: '... trimmed... ' + output_format: '... trimmed... ' diff --git a/test/approvals/repli_template_new_google_nano_banana b/test/approvals/repli_template_new_google_nano_banana index 1f1f446..d3b1f85 100644 --- a/test/approvals/repli_template_new_google_nano_banana +++ b/test/approvals/repli_template_new_google_nano_banana @@ -1,4 +1,4 @@ -• debug • repli_template_new_command → fetching example for google/nano-banana +• info • repli_template_new_command → building template for google/nano-banana • debug • get_model_info → calling replicate API -• debug • get_example_from_replicate → model status: official +• debug • json_to_template → model status: official • info • repli_template_new_command → saving to tmp/templates/nano-banana.yaml diff --git a/test/approvals/repli_template_new_google_nano_banana_force b/test/approvals/repli_template_new_google_nano_banana_force index 1f1f446..d3b1f85 100644 --- a/test/approvals/repli_template_new_google_nano_banana_force +++ b/test/approvals/repli_template_new_google_nano_banana_force @@ -1,4 +1,4 @@ -• debug • repli_template_new_command → fetching example for google/nano-banana +• info • repli_template_new_command → building template for google/nano-banana • debug • get_model_info → calling replicate API -• debug • get_example_from_replicate → model status: official +• debug • json_to_template → model status: official • info • repli_template_new_command → saving to tmp/templates/nano-banana.yaml diff --git a/test/approvals/repli_template_new_google_nano_banana_name_banana b/test/approvals/repli_template_new_google_nano_banana_name_banana index d3ee577..a0084dd 100644 --- a/test/approvals/repli_template_new_google_nano_banana_name_banana +++ b/test/approvals/repli_template_new_google_nano_banana_name_banana @@ -1,4 +1,4 @@ -• debug • repli_template_new_command → fetching example for google/nano-banana +• info • repli_template_new_command → building template for google/nano-banana • debug • get_model_info → calling replicate API -• debug • get_example_from_replicate → model status: official +• debug • json_to_template → model status: official • info • repli_template_new_command → saving to tmp/templates/banana.yaml diff --git a/test/approvals/repli_template_new_not_found b/test/approvals/repli_template_new_not_found index 5129a21..8e25d3e 100644 --- a/test/approvals/repli_template_new_not_found +++ b/test/approvals/repli_template_new_not_found @@ -1,4 +1,4 @@ -• debug • repli_template_new_command → fetching example for not/found +• info • repli_template_new_command → building template for not/found • debug • get_model_info → calling replicate API • error • get_model_info → failed getting model info for not/found • error • get_model_info → (404) diff --git a/test/approvals/repli_template_new_unofficial_mymodel b/test/approvals/repli_template_new_unofficial_mymodel index 8f7f506..3d2d0b8 100644 --- a/test/approvals/repli_template_new_unofficial_mymodel +++ b/test/approvals/repli_template_new_unofficial_mymodel @@ -1,4 +1,4 @@ -• debug • repli_template_new_command → fetching example for unofficial/mymodel +• info • repli_template_new_command → building template for unofficial/mymodel • debug • get_model_info → calling replicate API -• debug • get_example_from_replicate → model status: unofficial +• debug • json_to_template → model status: unofficial • info • repli_template_new_command → saving to tmp/templates/mymodel.yaml diff --git a/test/mockserver/mocks/v1/models/google/nano-banana/get.json b/test/mockserver/mocks/v1/models/google/nano-banana/get.json index df83ad2..2fb91e0 100644 --- a/test/mockserver/mocks/v1/models/google/nano-banana/get.json +++ b/test/mockserver/mocks/v1/models/google/nano-banana/get.json @@ -16,7 +16,39 @@ "required": [ "prompt" ], - "properties": "... trimmed ..." + "properties": { + "prompt": "... trimmed... ", + "image_input": "... trimmed... ", + "aspect_ratio": "... trimmed... ", + "output_format": "... trimmed... " + } + }, + "aspect_ratio": { + "enum": [ + "match_input_image", + "1:1", + "2:3", + "3:2", + "3:4", + "4:3", + "4:5", + "5:4", + "9:16", + "16:9", + "21:9" + ], + "type": "string", + "title": "aspect_ratio", + "description": "An enumeration." + }, + "output_format": { + "enum": [ + "jpg", + "png" + ], + "type": "string", + "title": "output_format", + "description": "An enumeration." } } } diff --git a/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json b/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json index 29612c6..46a1a8a 100644 --- a/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json +++ b/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json @@ -17,7 +17,39 @@ "required": [ "prompt" ], - "properties": "... trimmed ..." + "properties": { + "prompt": "... trimmed... ", + "image_input": "... trimmed... ", + "aspect_ratio": "... trimmed... ", + "output_format": "... trimmed... " + } + }, + "aspect_ratio": { + "enum": [ + "match_input_image", + "1:1", + "2:3", + "3:2", + "3:4", + "4:3", + "4:5", + "5:4", + "9:16", + "16:9", + "21:9" + ], + "type": "string", + "title": "aspect_ratio", + "description": "An enumeration." + }, + "output_format": { + "enum": [ + "jpg", + "png" + ], + "type": "string", + "title": "output_format", + "description": "An enumeration." } } } From 704d2dafa0c4d4e1351f1c62e66e8d93e2135504 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 10 Dec 2025 09:31:53 +0000 Subject: [PATCH 2/2] remove debug command --- repli | 94 ------------------------------------------- src/bashly.yml | 2 +- src/commands/debug.sh | 12 ------ 3 files changed, 1 insertion(+), 107 deletions(-) delete mode 100644 src/commands/debug.sh diff --git a/repli b/repli index 966c845..2d20568 100755 --- a/repli +++ b/repli @@ -91,28 +91,6 @@ repli_usage() { fi } -# :command.usage -repli_debug_usage() { - printf "repli debug - Debug\n\n" - - printf "%s\n" "$(bold "Usage:")" - printf " repli debug\n" - printf " repli debug --help | -h\n" - echo - - # :command.long_usage - if [[ -n "$long_usage" ]]; then - # :command.usage_options - printf "%s\n" "$(bold "Options:")" - - # :command.usage_fixed_flags - printf " %s\n" "$(green "--help, -h")" - printf " Show this help\n" - echo - - fi -} - # :command.usage repli_new_usage() { printf "repli new - Create a new configuration file from a tmeplate\n\n" @@ -1285,23 +1263,6 @@ validate_template_exists() { } # :command.command_functions -# :command.function -repli_debug_command() { - - # src/commands/debug.sh - - model="google/nano-banana" - json=$(get_model_info "$model") || return 1 - # get_example_from_replicate "$model" - - mapfile -t lines < <(json_get_model_properties "$json") - - for line in "${lines[@]}"; do - echo "==> $line" - done - -} - # :command.function repli_new_command() { @@ -1638,13 +1599,6 @@ parse_requirements() { case $action in -*) ;; - debug) - action="debug" - shift - repli_debug_parse_requirements "$@" - shift $# - ;; - new | n | init) action="new" shift @@ -1737,53 +1691,6 @@ parse_requirements() { } -# :command.parse_requirements -repli_debug_parse_requirements() { - local key - - # :command.fixed_flags_filter - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - --help | -h) - long_usage=yes - repli_debug_usage - exit - ;; - - *) - break - ;; - - esac - done - - # :command.command_filter - action="debug" - - # :command.parse_requirements_while - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - - -?*) - printf "invalid option: %s\n" "$key" >&2 - exit 1 - ;; - - *) - # :command.parse_requirements_case - # :command.parse_requirements_case_simple - printf "invalid argument: %s\n" "$key" >&2 - exit 1 - - ;; - - esac - done - -} - # :command.parse_requirements repli_new_parse_requirements() { local key @@ -2953,7 +2860,6 @@ run() { parse_requirements "${input[@]}" case "$action" in - "debug") repli_debug_command ;; "new") repli_new_command ;; "get") repli_get_command ;; "edit") repli_edit_command ;; diff --git a/src/bashly.yml b/src/bashly.yml index ed9bc32..8c648ba 100644 --- a/src/bashly.yml +++ b/src/bashly.yml @@ -36,7 +36,7 @@ variables: value: $REPLICATE_HOST commands: -- { name: debug, help: Debug, private: true } +# - { name: debug, help: Debug, private: true } - name: new alias: ['n', init] help: Create a new configuration file from a tmeplate diff --git a/src/commands/debug.sh b/src/commands/debug.sh deleted file mode 100644 index d64ae8c..0000000 --- a/src/commands/debug.sh +++ /dev/null @@ -1,12 +0,0 @@ - - - -model="google/nano-banana" -json=$(get_model_info "$model") || return 1 -# get_example_from_replicate "$model" - -mapfile -t lines < <(json_get_model_properties "$json") - -for line in "${lines[@]}"; do - echo "==> $line" -done