From ce327b35e9a1609b817ba5aca05c6061b6141e17 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 23 Mar 2026 15:10:36 -0400 Subject: [PATCH] Add --account N flag and fix credential priority in sh/lua/pl/tcl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When --account N is passed, it should bypass env vars and load accounts.csv row N directly. Previously, env vars were checked first so the explicit --account flag had no effect when UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY were set. Correct credential priority (all 4 files): 1. Explicit -p/-k key flags 2. --account N → direct CSV row lookup (bypasses env vars) 3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars 4. ~/.unsandbox/accounts.csv default (row 0) 5. ./accounts.csv default (row 0) Files updated: un.sh (bash), un.lua, un.pl (perl), un.tcl Also fixes load_accounts_csv in sh and tcl to support reading an arbitrary row index rather than always returning the first line. --- clients/bash/sync/src/un.sh | 48 ++++++++++++++++++++++++++++++--- clients/lua/sync/src/un.lua | 35 +++++++++++++++++++++--- clients/perl/sync/src/un.pl | 20 +++++++++++--- clients/tcl/sync/src/un.tcl | 53 ++++++++++++++++++++++++++++++------- 4 files changed, 137 insertions(+), 19 deletions(-) diff --git a/clients/bash/sync/src/un.sh b/clients/bash/sync/src/un.sh index 70b672a..858d730 100644 --- a/clients/bash/sync/src/un.sh +++ b/clients/bash/sync/src/un.sh @@ -21,6 +21,7 @@ VERSION="4.2.50" API_BASE="https://api.unsandbox.com" PORTAL_BASE="https://unsandbox.com" LAST_ERROR="" +ACCOUNT_INDEX=-1 # Colors BLUE='\033[34m' @@ -104,8 +105,19 @@ hmac_sign() { load_accounts_csv() { local path="${1:-$HOME/.unsandbox/accounts.csv}" + local row="${2:-0}" [ -f "$path" ] || return 1 - head -1 "$path" 2>/dev/null | grep -v '^#' + local n=0 + while IFS= read -r line; do + [[ "$line" =~ ^# ]] && continue + [ -z "$line" ] && continue + if [ "$n" -eq "$row" ]; then + echo "$line" + return 0 + fi + n=$((n + 1)) + done < "$path" + return 1 } get_credentials() { @@ -115,7 +127,20 @@ get_credentials() { return fi - # Tier 2: Environment + # Tier 2: --account N flag → bypass env vars, load CSV row N directly + if [ "$ACCOUNT_INDEX" -ge 0 ] 2>/dev/null; then + local creds + creds=$(load_accounts_csv "$HOME/.unsandbox/accounts.csv" "$ACCOUNT_INDEX" 2>/dev/null || true) + [ -z "$creds" ] && creds=$(load_accounts_csv "./accounts.csv" "$ACCOUNT_INDEX" 2>/dev/null || true) + if [ -n "$creds" ]; then + echo "$creds" + return + fi + set_error "Account index $ACCOUNT_INDEX not found in accounts.csv" + return 1 + fi + + # Tier 3: Environment if [ -n "${UNSANDBOX_PUBLIC_KEY:-}" ] && [ -n "${UNSANDBOX_SECRET_KEY:-}" ]; then echo "$UNSANDBOX_PUBLIC_KEY:$UNSANDBOX_SECRET_KEY" return @@ -127,7 +152,7 @@ get_credentials() { return fi - # Tier 3: Home directory + # Tier 4: Home directory local creds creds=$(load_accounts_csv "$HOME/.unsandbox/accounts.csv" 2>/dev/null || true) if [ -n "$creds" ]; then @@ -135,7 +160,7 @@ get_credentials() { return fi - # Tier 4: Local directory + # Tier 5: Local directory creds=$(load_accounts_csv "./accounts.csv" 2>/dev/null || true) if [ -n "$creds" ]; then echo "$creds" @@ -1230,6 +1255,21 @@ if [ "${BASH_SOURCE[0]}" = "$0" ]; then exit 1 fi + # Pre-scan for --account N before dispatching + _args=("$@") + _new_args=() + _i=0 + while [ $_i -lt ${#_args[@]} ]; do + if [ "${_args[$_i]}" = "--account" ]; then + _i=$((_i + 1)) + ACCOUNT_INDEX="${_args[$_i]}" + else + _new_args+=("${_args[$_i]}") + fi + _i=$((_i + 1)) + done + set -- "${_new_args[@]+"${_new_args[@]}"}" + case "$1" in languages) shift diff --git a/clients/lua/sync/src/un.lua b/clients/lua/sync/src/un.lua index bf3bf71..b2c469c 100644 --- a/clients/lua/sync/src/un.lua +++ b/clients/lua/sync/src/un.lua @@ -28,6 +28,7 @@ Un.API_BASE = "https://api.unsandbox.com" Un.PORTAL_BASE = "https://unsandbox.com" Un.VERSION = "4.3.4" Un.LAST_ERROR = "" +Un.ACCOUNT_INDEX = -1 -- -1 means not set; set to N to use accounts.csv row N -- Colors local BLUE = "\027[34m" @@ -116,7 +117,20 @@ function Un.get_credentials(opts) return opts.public_key, opts.secret_key end - -- Tier 2: Environment + -- Tier 2: --account N flag → bypass env vars, load CSV row N directly + local ai = opts.account_index + if (ai == nil) and Un.ACCOUNT_INDEX >= 0 then ai = Un.ACCOUNT_INDEX end + if ai and ai >= 0 then + local row = ai + 1 -- Lua tables are 1-indexed + local accounts = Un.load_accounts_csv() + if #accounts >= row then return accounts[row][1], accounts[row][2] end + accounts = Un.load_accounts_csv("./accounts.csv") + if #accounts >= row then return accounts[row][1], accounts[row][2] end + Un.set_error("Account index " .. ai .. " not found in accounts.csv") + return nil, nil + end + + -- Tier 3: Environment local pk = os.getenv("UNSANDBOX_PUBLIC_KEY") local sk = os.getenv("UNSANDBOX_SECRET_KEY") if pk and sk then return pk, sk end @@ -126,11 +140,11 @@ function Un.get_credentials(opts) return os.getenv("UNSANDBOX_API_KEY"), "" end - -- Tier 3: Home directory + -- Tier 4: Home directory local accounts = Un.load_accounts_csv() if #accounts > 0 then return accounts[1][1], accounts[1][2] end - -- Tier 4: Local directory + -- Tier 5: Local directory accounts = Un.load_accounts_csv("./accounts.csv") if #accounts > 0 then return accounts[1][1], accounts[1][2] end @@ -744,6 +758,21 @@ if arg and arg[0] then local args = arg local i = 1 + -- Pre-scan for --account N; strip it from args before dispatch + local _filtered = {} + local _j = 1 + while _j <= #args do + if args[_j] == "--account" then + _j = _j + 1 + Un.ACCOUNT_INDEX = tonumber(args[_j]) or -1 + else + table.insert(_filtered, args[_j]) + end + _j = _j + 1 + end + for k = 1, #_filtered do args[k] = _filtered[k] end + for k = #_filtered + 1, #args do args[k] = nil end + if #args == 0 then print("Usage: lua un.lua [options] ") print(" lua un.lua -s ''") diff --git a/clients/perl/sync/src/un.pl b/clients/perl/sync/src/un.pl index 470ef50..a58eca9 100644 --- a/clients/perl/sync/src/un.pl +++ b/clients/perl/sync/src/un.pl @@ -44,6 +44,7 @@ our $PORTAL_BASE = 'https://unsandbox.com'; # Thread-local error storage our $LAST_ERROR = ""; +our $ACCOUNT_INDEX = -1; # -1 means not set; set to N to use accounts.csv row N # Colors my $BLUE = "\033[34m"; @@ -122,7 +123,18 @@ sub get_credentials { # Tier 1: Arguments return ($opts{public_key}, $opts{secret_key}) if $opts{public_key} && $opts{secret_key}; - # Tier 2: Environment + # Tier 2: --account N flag → bypass env vars, load CSV row N directly + my $ai = exists $opts{account_index} ? $opts{account_index} : $Un::ACCOUNT_INDEX; + if (defined $ai && $ai >= 0) { + my $home_accounts = load_accounts_csv(); + return @{$home_accounts->[$ai]} if @$home_accounts > $ai; + my $local_accounts = load_accounts_csv("./accounts.csv"); + return @{$local_accounts->[$ai]} if @$local_accounts > $ai; + set_error("Account index $ai not found in accounts.csv"); + return (undef, undef); + } + + # Tier 3: Environment if ($ENV{UNSANDBOX_PUBLIC_KEY} && $ENV{UNSANDBOX_SECRET_KEY}) { return ($ENV{UNSANDBOX_PUBLIC_KEY}, $ENV{UNSANDBOX_SECRET_KEY}); } @@ -132,11 +144,11 @@ sub get_credentials { return ($ENV{UNSANDBOX_API_KEY}, ''); } - # Tier 3: Home directory + # Tier 4: Home directory my $home_accounts = load_accounts_csv(); return @{$home_accounts->[0]} if @$home_accounts; - # Tier 4: Local directory + # Tier 5: Local directory my $local_accounts = load_accounts_csv("./accounts.csv"); return @{$local_accounts->[0]} if @$local_accounts; @@ -1541,6 +1553,8 @@ sub main { } elsif ($arg eq 'env' && $options{command} && $options{command} eq 'service') { $options{env_action} = $ARGV[++$i]; $options{env_target} = $ARGV[++$i] if defined $ARGV[$i+1] && $ARGV[$i+1] !~ /^-/; + } elsif ($arg eq '--account') { + $Un::ACCOUNT_INDEX = int($ARGV[++$i]); } elsif ($arg eq '--help' || $arg eq '-h') { show_help(); } elsif ($arg =~ /^-/) { diff --git a/clients/tcl/sync/src/un.tcl b/clients/tcl/sync/src/un.tcl index 673989e..dbcd5b3 100755 --- a/clients/tcl/sync/src/un.tcl +++ b/clients/tcl/sync/src/un.tcl @@ -33,6 +33,7 @@ namespace eval Un { variable LANGUAGES_CACHE_TTL 3600 variable LANGUAGES_CACHE_FILE [file join $::env(HOME) ".unsandbox" "languages.json"] variable LAST_ERROR "" + variable ACCOUNT_INDEX -1 # Colors variable BLUE "\033\[34m" @@ -109,22 +110,43 @@ namespace eval Un { # Credential Management # ============================================================================ - proc load_accounts_csv {path} { + proc load_accounts_csv {path {row 0}} { if {![file exists $path]} { return "" } if {[catch {open $path r} fp]} { return "" } - set line [gets $fp] + set n 0 + set result "" + while {[gets $fp line] >= 0} { + if {[string index $line 0] eq "#" || [string trim $line] eq ""} { continue } + if {$n == $row} { set result $line; break } + incr n + } close $fp - if {[string index $line 0] eq "#"} { return "" } - return $line + return $result } - proc get_credentials {{public_key ""} {secret_key ""}} { + proc get_credentials {{public_key ""} {secret_key ""} {account_index -1}} { + variable ACCOUNT_INDEX + # Tier 1: Arguments if {$public_key ne "" && $secret_key ne ""} { return [list $public_key $secret_key] } - # Tier 2: Environment + # Tier 2: --account N flag → bypass env vars, load CSV row N directly + set ai $account_index + if {$ai < 0} { set ai $ACCOUNT_INDEX } + if {$ai >= 0} { + set creds [load_accounts_csv [file join $::env(HOME) ".unsandbox" "accounts.csv"] $ai] + if {$creds eq ""} { set creds [load_accounts_csv "./accounts.csv" $ai] } + if {$creds ne ""} { + set parts [split $creds ","] + return [list [lindex $parts 0] [lindex $parts 1]] + } + set_error "Account index $ai not found in accounts.csv" + error "Account index $ai not found in accounts.csv" + } + + # Tier 3: Environment if {[info exists ::env(UNSANDBOX_PUBLIC_KEY)] && [info exists ::env(UNSANDBOX_SECRET_KEY)]} { return [list $::env(UNSANDBOX_PUBLIC_KEY) $::env(UNSANDBOX_SECRET_KEY)] } @@ -134,14 +156,14 @@ namespace eval Un { return [list $::env(UNSANDBOX_API_KEY) ""] } - # Tier 3: Home directory + # Tier 4: Home directory set creds [load_accounts_csv [file join $::env(HOME) ".unsandbox" "accounts.csv"]] if {$creds ne ""} { set parts [split $creds ","] return [list [lindex $parts 0] [lindex $parts 1]] } - # Tier 4: Local directory + # Tier 5: Local directory set creds [load_accounts_csv "./accounts.csv"] if {$creds ne ""} { set parts [split $creds ","] @@ -1030,7 +1052,20 @@ Environment: } proc main {argv} { - variable BLUE RED RESET + variable BLUE RED RESET ACCOUNT_INDEX + + # Pre-scan for --account N; strip it from argv before dispatch + set filtered {} + for {set i 0} {$i < [llength $argv]} {incr i} { + set a [lindex $argv $i] + if {$a eq "--account"} { + incr i + set ACCOUNT_INDEX [lindex $argv $i] + } else { + lappend filtered $a + } + } + set argv $filtered if {[llength $argv] == 0} { show_help