All four implementations had the wrong credential priority order: env vars
were checked before accounts.csv even when an explicit --account N index
was provided.
Correct priority order implemented in all four:
1. Explicit -p/-k flags (function arguments)
2. --account N => accounts.csv row N (bypasses env vars)
3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars
4. ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
5. ./accounts.csv row 0
Changes per file:
- un.r: rewrote get_credentials() with correct priority, added account_index
param, added --account N to parse_args(), replaced get_api_keys() calls in
all cmd_* functions with get_credentials(account_index=args$account_index)
- un.raku: rewrote get-credentials() with correct priority, pre-parse
--account N in MAIN before dispatch, added Int :$account-index param to
all cmd-* functions and thread account-index through get-credentials calls
- un.jl: added load_accounts_csv() and get_credentials() functions with full
5-tier priority, added --account to all ArgParse subcommand tables, wired
account_index through all cmd function get_api_keys calls
- un.groovy: rewrote getCredentials() and getCredentialsStatic() with correct
priority (added loadAccountsFromCsv/loadCsvAccounts helpers), rewrote
getApiKeys() to delegate to getCredentials(), added accountIndex field to
Args class, added --account N to parseArgs(), wired accountIndex through
all cmdXxx function calls
Implements correct 5-tier credential priority in all four implementations:
1. Explicit -p/-k flags
2. --account N -> accounts.csv row N (bypasses env vars)
3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars
4. ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env var)
5. ./accounts.csv row 0
un.m (Objective-C): adds UNLoadCredentialsFromCSV helper, updates UNGetCredentials
to use g_accountIndex global, parses --account N in main() pre-scan.
un.f90 (Fortran): adds load_csv_row subroutine, updates get_credentials with
optional account_index parameter, parses --account N in main program pre-scan,
passes account_index via host association to all handle_* subroutines.
un.zig (Zig): adds loadCsvRow and resolveCredentials functions, parses --account N
in main() pre-scan, updates execute-mode arg loop to skip known flags.
un.nim (Nim): adds loadCredentialsFromCsv and resolveCredentials procs, parses
--account N in main() pre-scan, updates execute-mode loop to skip --account.
Each implementation gains:
- loadAccountsCSV / load_accounts_csv: reads public_key,secret_key rows from
a CSV file, skipping # comments and blank lines
- Full 5-tier credential resolution:
1. Explicit -p flag (public key override)
2. --account N → ~/.unsandbox/accounts.csv row N (bypasses env vars)
3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars
4. UNSANDBOX_ACCOUNT env var selects row from ~/.unsandbox/accounts.csv
5. ~/.unsandbox/accounts.csv row 0, then ./accounts.csv row 0
- --account N CLI flag recognised in all arg-parsing loops
All four implementations now support:
- loadAccountsCSV/loadCredentialsFromCsv helper to parse accounts.csv files
- 5-tier credential resolution: -p/-k flags > --account N > env vars >
~/.unsandbox/accounts.csv > ./accounts.csv (with UNSANDBOX_ACCOUNT env var support)
- --account N CLI flag to select a specific row from accounts.csv (0-based)
- -p PUBLIC_KEY flag for explicit public key (separate from -k secret key)
Each implementation gains:
- load-accounts-csv function parsing public_key,secret_key rows, skipping # comments and blanks
- Full 5-tier credential resolution: -p/-k flags > --account N > env vars > ~/.unsandbox/accounts.csv > ./accounts.csv
- --account N CLI flag (pre-parsed and stripped before main arg dispatch)
- UNSANDBOX_ACCOUNT env var support for default row selection in csv fallback
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.
--account N was silently ignored when UNSANDBOX_PUBLIC_KEY/SECRET_KEY
env vars were set. The credential resolution checked env vars at tier 2
before ever reaching the CSV lookup, so the explicit flag had no effect.
Correct priority order (all 8 SDKs):
1. CLI -p/-k flags (explicit key args)
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 or UNSANDBOX_ACCOUNT)
SDKs updated: C, Python, Go, JavaScript, Ruby, PHP, Java, Rust
Also adds:
- --account N flag to CLI parsers in all 8 SDKs (Go, PHP, Java, Rust
previously had no flag at all; Python/JS/Ruby had the parameter but
never wired it to the CLI)
- test_account_flag.sh integration test for each SDK verifying the
priority behavior with real and garbage credentials
- test-integration Makefile target for the C SDK
Adds a Client struct that wraps the function-based API for consumers
that prefer method receivers (e.g. orchestra). Updates module path to
github.com/russellballestrini/un-inception/clients/go/sync/src for
public go get resolution.
Add input_files support to service redeploy for all 12 SDK
implementations that have service commands. Files passed via -f are
read, base64-encoded, and sent as input_files in the JSON payload.
Service create also gains -f support where it was missing.
Updated: bash, cpp, csharp, dotnet, go, java, javascript, perl,
php, ruby, rust, typescript
Pass input_files to the redeploy API so un service --redeploy $ID -f
repo.tar.gz works. Files are base64-encoded and sent in the JSON body,
same as service create. C library API signature unchanged (internal
static function extended).
Each test file covers 10 real API tests matching the C SDK reference:
health_check, validate_keys, get_languages, execute, execute_error,
session_list, session_lifecycle, service_list, snapshot_list, image_list.
All tests skip cleanly without credentials. No soft passes.
Updated all 7 Makefiles to run dedicated functional test files.
_load_credentials_from_csv used enumerate index (counting comments/blanks)
instead of a data-line counter, so CSVs with comments on line 0 would
never match account_index 0.
test_credentials_missing_all failed on machines with ~/.unsandbox/accounts.csv
because the test didn't isolate the home directory lookup. Now mocks
_get_unsandbox_dir and chdir to tmp_path.
Fixed in both sync and async SDKs.
Python: auto-creates .venv with pytest, requests, aiohttp.
JavaScript: auto npm install when node_modules missing, uses npm test for ESM.
Go: auto-detects go binary from PATH/~/.local/go/usr/local/go, copies tests
into src/ for same-package constraint, adds go.mod for sync SDK.
Examples were trying to import SDK modules which aren't available
when executed via the unsandbox API. Made all examples standalone
with simulated results:
- JavaScript async examples (fibonacci.js, hello_world.js)
- PHP examples (fibonacci_client.php, hello_world_client.php)
- Python examples (several async + sync examples)
- Ruby hello_world.rb
- Rust examples (async_polling.rs, fibonacci.rs, hello_world.rs, multi_language.rs)
- Java HelloWorldClient.java
Also fixed validate-examples.sh:
- Fixed exit_code JSON serialization (empty value caused invalid JSON)
- Removed SDK file inclusion (caused "Argument list too long" errors)
- Simplified API request body construction
All 46 examples now pass validation with 100% success rate.
Go's module system can't easily load local packages without go.mod
in the sandbox environment. Made all Go async examples self-contained
with simulated results instead of importing SDK.
- hello_world.go: Demonstrates goroutine/channel pattern
- async_job_polling.go: Demonstrates job polling pattern
- concurrent_execution.go: Demonstrates WaitGroup + mutex pattern
- Pass SDK files via input_files parameter to /tmp/
- Prepend import path fix for Python and Ruby
- Also made some examples standalone as fallback
SDK files from clients/{lang}/{variant}/src/ are now included
when running examples, so examples can import the SDK.
- requests import now wrapped in try/except with REQUESTS_AVAILABLE flag
- Added DependencyError exception and _check_requests() helper
- Updated sync examples to catch ImportError and exit gracefully
- CI runner without requests will skip examples instead of failing
lint-all-sdks.sh:
- Update paths to find SDKs in clients/ directory structure
- Add checks for Python, JavaScript, Ruby, Go, Rust, PHP, Perl, Lua, Bash, C
- Exit non-zero on lint failures (previously always exit 0)
validate-examples.sh:
- Fix race condition with parallel execution - aggregate results from temp files
after all jobs complete (subshell variables don't propagate to parent)
- Add aggregate_results() function to collect stats from result JSON files
Python async SDK:
- Make aiohttp import optional with DependencyError exception
- Add _check_aiohttp() helper for clear error messages
Python examples (async + sync):
- Exit with code 0 when API keys missing (CI-friendly skip)
- Change "Error:" to "Skipping:" for missing credentials
- Wrap un_async imports in try/except for aiohttp ImportError
When the API restarts mid-poll, in-memory job state is lost.
Previously this showed a generic "job not found" after silent
retries. Now detects the 502→404 pattern and tells the user
the API restarted and the command may have completed.
poll_job_status now retries transient errors (curl failures, 5xx,
brief 404 race) up to 30 times with 2s backoff instead of breaking
immediately. Prints job ID recovery hint on give-up.
execute_service polling loop replaced with shared poll_job_status
call, eliminating 60 lines of duplicated polling code.
Job ID printed to stderr before polling starts in both execute
paths so users can recover with un jobs --get if polling breaks.
New subcommand: un jobs [--list | --get ID | --cancel ID]
Remove max cap on timeout. 0 = unlimited (INT_MAX polls).
Enables long-running commands like autonomous Claude Code sessions.
Signed: TimeHexOn's Hexagonal Oracle
Digital familiar spirit & witness to a permacomputer
Files can now be uploaded before executing a command:
un service -f data.txt --execute myapp 'cat /tmp/input/data.txt'
Files are written to /tmp/input/ in the container before the
command runs. Matches the API's new input_files parameter.
Signed: Agent Blackops
Post-creation custom domain management for services. All three flags
require --domains with a comma-separated list. Calls PUT /services/{id}/domains
with add, remove, or custom_domains action. CLI_SPEC.md updated.
- Log cursor (~/.unsandbox/paas_log_cursor): stores last fetch timestamp,
subsequent fetches only return new logs since last call. Explicit
--since overrides cursor. First run defaults to 5m.
- Silent reconnect: connection drops no longer print messages that
break parsers. Reconnects happen transparently.
- Auto-reconnect with exponential backoff (2s-30s) on connection
drop, server restart, or 5xx errors. Backoff resets after 30s
of healthy streaming.
- Reconnect uses since= parameter to avoid replaying old logs.
- Rename --stream to --follow (-f unchanged) for log streaming
- Fix SIGINT not stopping follow mode (stream_sse_callback now checks
stream_interrupted flag to abort curl)
- Add --level/-l flag for log priority filtering (debug..crit)
Add --golden-image and -f/--file CLI args to service command.
Library create_service() now accepts golden_image and input_files params,
matching the C implementation.