Add --account N flag and fix credential priority in sh/lua/pl/tcl

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.
This commit is contained in:
russell@unturf.com 2026-03-23 15:10:36 -04:00
parent ed6c52b001
commit ce327b35e9
4 changed files with 137 additions and 19 deletions

View file

@ -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

View file

@ -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] <source_file>")
print(" lua un.lua -s <language> '<code>'")

View file

@ -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 =~ /^-/) {

View file

@ -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