diff --git a/VERSION b/VERSION index be14282..7d85683 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.3 +0.5.4 diff --git a/larry.sh b/larry.sh index 7185e68..c7a9226 100755 --- a/larry.sh +++ b/larry.sh @@ -36,7 +36,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.5.3" +LARRY_VERSION="0.5.4" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_BASE_URL="${LARRY_BASE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main}" LARRY_UPDATE_URL="${LARRY_UPDATE_URL:-${LARRY_BASE_URL}/larry.sh}" @@ -453,22 +453,26 @@ log_append() { printf '%s\n' "$1" >> "$LOG_FILE"; } # ───────────────────────────────────────────────────────────────────────────── # Message store helpers # ───────────────────────────────────────────────────────────────────────────── +# NOTE on jq file IO: pass files to jq via stdin redirection, not as argv. +# On MobaXterm/Cygwin the bundled jq is a Windows-native binary that can't +# resolve Cygwin paths like /home/mobaxterm/... when they come in as argv. +# Stdin redirection always works because bash does the path open() itself. add_user_text() { local content="$1" local tmp; tmp=$(mktemp) - jq --arg c "$content" '. + [{"role":"user","content":[{"type":"text","text":$c}]}]' "$MESSAGES_FILE" > "$tmp" \ + jq --arg c "$content" '. + [{"role":"user","content":[{"type":"text","text":$c}]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" } add_assistant_blocks() { local blocks="$1" local tmp; tmp=$(mktemp) - jq --argjson b "$blocks" '. + [{"role":"assistant","content":$b}]' "$MESSAGES_FILE" > "$tmp" \ + jq --argjson b "$blocks" '. + [{"role":"assistant","content":$b}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" } add_user_tool_results() { local blocks="$1" local tmp; tmp=$(mktemp) - jq --argjson b "$blocks" '. + [{"role":"user","content":$b}]' "$MESSAGES_FILE" > "$tmp" \ + jq --argjson b "$blocks" '. + [{"role":"user","content":$b}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" } diff --git a/lib/oauth.sh b/lib/oauth.sh index 53683fa..c1092f7 100755 --- a/lib/oauth.sh +++ b/lib/oauth.sh @@ -48,6 +48,17 @@ command -v openssl >/dev/null 2>&1 || die "openssl required (for PKCE sha256)" b64url() { base64 | tr '/+' '_-' | tr -d '=' | tr -d '\n'; } +# jqf — run jq against a file, but pipe the file via stdin so bash handles +# the path translation. Needed because on MobaXterm/Cygwin the bundled jq +# may be a Windows-native binary that doesn't understand Cygwin paths like +# /home/mobaxterm/... when they come in as argv. Stdin redirection always +# works because bash does the open() itself. +# Usage: jqf +jqf() { + local file="$1"; shift + jq "$@" < "$file" +} + urlenc() { # Minimal RFC3986-ish URL encoder for the bits we need (spaces, /, :) local s="$1" @@ -164,7 +175,7 @@ EOF cmd_refresh() { [ -f "$OAUTH_FILE" ] || die "no oauth file at $OAUTH_FILE — run 'larry-auth.sh login' first" - local refresh_token; refresh_token=$(jq -r '.refresh_token // empty' "$OAUTH_FILE") + local refresh_token; refresh_token=$(jqf "$OAUTH_FILE" -r '.refresh_token // empty') [ -n "$refresh_token" ] || die "no refresh_token in $OAUTH_FILE — please run login again" local resp @@ -182,27 +193,30 @@ cmd_refresh() { fi local now; now=$(date +%s) + # Pre-read the old refresh_token so we don't need --slurpfile (which would + # take a file path argv-style and break on MobaXterm's Windows-native jq). + local prev_refresh; prev_refresh=$(jqf "$OAUTH_FILE" -r '.refresh_token // empty') printf '%s' "$resp" \ - | jq --arg now "$now" --slurpfile prev "$OAUTH_FILE" \ - '. + {fetched_at: ($now|tonumber), refresh_token: (.refresh_token // $prev[0].refresh_token)}' \ + | jq --arg now "$now" --arg prev "$prev_refresh" \ + '. + {fetched_at: ($now|tonumber), refresh_token: (.refresh_token // $prev)}' \ > "$OAUTH_FILE.new" mv "$OAUTH_FILE.new" "$OAUTH_FILE" chmod 600 "$OAUTH_FILE" - jq -r '.access_token' "$OAUTH_FILE" + jqf "$OAUTH_FILE" -r '.access_token' } cmd_ensure() { [ -f "$OAUTH_FILE" ] || return 1 local fetched_at expires_in - fetched_at=$(jq -r '.fetched_at // 0' "$OAUTH_FILE") - expires_in=$(jq -r '.expires_in // 3600' "$OAUTH_FILE") + fetched_at=$(jqf "$OAUTH_FILE" -r '.fetched_at // 0') + expires_in=$(jqf "$OAUTH_FILE" -r '.expires_in // 3600') local now; now=$(date +%s) local expires_at=$((fetched_at + expires_in)) if [ "$now" -ge $((expires_at - 300)) ]; then cmd_refresh >/dev/null 2>&1 || return 1 - jq -r '.access_token' "$OAUTH_FILE" + jqf "$OAUTH_FILE" -r '.access_token' else - jq -r '.access_token' "$OAUTH_FILE" + jqf "$OAUTH_FILE" -r '.access_token' fi } @@ -212,9 +226,9 @@ cmd_status() { return 1 fi local fetched_at expires_in scope - fetched_at=$(jq -r '.fetched_at // 0' "$OAUTH_FILE") - expires_in=$(jq -r '.expires_in // 3600' "$OAUTH_FILE") - scope=$(jq -r '.scope // "(unknown)"' "$OAUTH_FILE") + fetched_at=$(jqf "$OAUTH_FILE" -r '.fetched_at // 0') + expires_in=$(jqf "$OAUTH_FILE" -r '.expires_in // 3600') + scope=$(jqf "$OAUTH_FILE" -r '.scope // "(unknown)"') local now; now=$(date +%s) local expires_at=$((fetched_at + expires_in)) local left=$((expires_at - now))