Add --account N flag and CSV credential resolution to C++, D, V, and Kotlin SDKs

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
This commit is contained in:
russell@unturf.com 2026-03-23 15:13:39 -04:00
parent 6860871128
commit a839ffcd12
4 changed files with 338 additions and 22 deletions

View file

@ -103,6 +103,27 @@ string read_file(const string& filename) {
return buf.str();
}
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
pair<string,string> loadAccountsCSV(const string& path, int index) {
ifstream f(path);
if (!f) return {"", ""};
string line;
int row = 0;
while (getline(f, line)) {
// Trim trailing carriage return
if (!line.empty() && line.back() == '\r') line.pop_back();
if (line.empty() || line[0] == '#') continue;
if (row == index) {
size_t comma = line.find(',');
if (comma == string::npos) return {"", ""};
return {line.substr(0, comma), line.substr(comma + 1)};
}
row++;
}
return {"", ""};
}
string escape_json(const string& s) {
ostringstream o;
for (char c : s) {
@ -1781,12 +1802,62 @@ void cmd_validate_key(bool extend, const string& public_key, const string& secre
}
int main(int argc, char* argv[]) {
string public_key = getenv("UNSANDBOX_PUBLIC_KEY") ? getenv("UNSANDBOX_PUBLIC_KEY") : "";
string secret_key = getenv("UNSANDBOX_SECRET_KEY") ? getenv("UNSANDBOX_SECRET_KEY") : "";
string public_key;
string secret_key;
int account_index = -1; // -1 = not set
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
if (public_key.empty()) {
public_key = getenv("UNSANDBOX_API_KEY") ? getenv("UNSANDBOX_API_KEY") : "";
// First pass: scan for --account N and -p/-k flags before full arg parsing
for (int i = 1; i < argc; i++) {
string a = argv[i];
if (a == "--account" && i+1 < argc) {
account_index = atoi(argv[++i]);
} else if (a == "-p" && i+1 < argc) {
public_key = argv[++i];
}
}
if (account_index >= 0) {
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
const char* home = getenv("HOME");
string csv_path = string(home ? home : ".") + "/.unsandbox/accounts.csv";
auto creds = loadAccountsCSV(csv_path, account_index);
if (creds.first.empty()) {
// fall back to ./accounts.csv
creds = loadAccountsCSV("accounts.csv", account_index);
}
if (!creds.first.empty()) {
if (public_key.empty()) public_key = creds.first;
secret_key = creds.second;
}
} else {
// Priority: env vars, then ~/.unsandbox/accounts.csv row 0, then ./accounts.csv row 0
if (public_key.empty()) {
public_key = getenv("UNSANDBOX_PUBLIC_KEY") ? getenv("UNSANDBOX_PUBLIC_KEY") : "";
}
secret_key = getenv("UNSANDBOX_SECRET_KEY") ? getenv("UNSANDBOX_SECRET_KEY") : "";
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
if (public_key.empty()) {
public_key = getenv("UNSANDBOX_API_KEY") ? getenv("UNSANDBOX_API_KEY") : "";
}
// Try UNSANDBOX_ACCOUNT env var to pick a row
int env_account = -1;
const char* env_acct = getenv("UNSANDBOX_ACCOUNT");
if (env_acct) env_account = atoi(env_acct);
if (public_key.empty()) {
const char* home = getenv("HOME");
string csv_path = string(home ? home : ".") + "/.unsandbox/accounts.csv";
auto creds = loadAccountsCSV(csv_path, env_account >= 0 ? env_account : 0);
if (creds.first.empty()) {
creds = loadAccountsCSV("accounts.csv", env_account >= 0 ? env_account : 0);
}
if (!creds.first.empty()) {
public_key = creds.first;
secret_key = creds.second;
}
}
}
if (argc < 2) {
@ -1824,6 +1895,7 @@ int main(int argc, char* argv[]) {
else if (arg == "--name" && i+1 < argc) name = argv[++i];
else if (arg == "--ports" && i+1 < argc) ports = argv[++i];
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
}
cmd_image(list, info, del, lock, unlock, publish, source_type, visibility_id, visibility, spawn, clone, name, ports, public_key, secret_key);
@ -1848,6 +1920,7 @@ int main(int argc, char* argv[]) {
else if (arg == "--tmux") tmux = true;
else if (arg == "--screen") screen = true;
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
}
cmd_session(list, kill, shell, network, vcpu, tmux, screen, files, public_key, secret_key);
@ -1910,6 +1983,7 @@ int main(int argc, char* argv[]) {
unfreeze_on_demand = (val == "true") ? 1 : 0;
}
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
}
cmd_service(name, ports, type, bootstrap, bootstrap_file, files, list, info, logs, tail, sleep, wake, destroy, resize, execute, command, dump_bootstrap, dump_file, redeploy, network, vcpu, envs, env_file, env_action, env_target, set_unfreeze_on_demand_id, set_unfreeze_on_demand_enabled, unfreeze_on_demand, public_key, secret_key);
@ -1923,6 +1997,7 @@ int main(int argc, char* argv[]) {
string arg = argv[i];
if (arg == "--extend") extend = true;
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
}
cmd_validate_key(extend, public_key, secret_key);
@ -1936,6 +2011,7 @@ int main(int argc, char* argv[]) {
string arg = argv[i];
if (arg == "--json") json_output = true;
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
}
cmd_languages(json_output, public_key, secret_key);
@ -1956,6 +2032,7 @@ int main(int argc, char* argv[]) {
else if (arg == "-n" && i+1 < argc) network = argv[++i];
else if (arg == "-v" && i+1 < argc) vcpu = stoi(argv[++i]);
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
else if (arg[0] == '-') {
cerr << RED << "Unknown option: " << arg << RESET << endl;
return 1;

View file

@ -52,6 +52,7 @@ import std.string;
import std.conv;
import std.array;
import std.algorithm;
import std.typecons;
immutable string API_BASE = "https://api.unsandbox.com";
immutable string PORTAL_BASE = "https://unsandbox.com";
@ -1555,13 +1556,82 @@ void validateKey(string publicKey, string secretKey, bool extend) {
}
}
int main(string[] args) {
string publicKey = environment.get("UNSANDBOX_PUBLIC_KEY", "");
string secretKey = environment.get("UNSANDBOX_SECRET_KEY", "");
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
Tuple!(string, string) loadAccountsCSV(string path, int index) {
import std.file : exists, readText;
import std.range : empty;
if (!exists(path)) return tuple("", "");
string content = readText(path);
int row = 0;
foreach (line; content.splitLines()) {
string stripped = line.strip();
if (stripped.empty || stripped[0] == '#') continue;
if (row == index) {
auto parts = stripped.findSplit(",");
if (!parts[1].empty) return tuple(parts[0], parts[2]);
return tuple("", "");
}
row++;
}
return tuple("", "");
}
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
if (publicKey.empty) {
publicKey = environment.get("UNSANDBOX_API_KEY", "");
int main(string[] args) {
string publicKey;
string secretKey;
int accountIndex = -1; // -1 = not set
string explicitPublicKey;
// First pass: scan for --account N and -p flags
for (size_t i = 1; i < args.length; i++) {
if (args[i] == "--account" && i+1 < args.length) {
accountIndex = to!int(args[++i]);
} else if (args[i] == "-p" && i+1 < args.length) {
explicitPublicKey = args[++i];
}
}
if (accountIndex >= 0) {
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
string home = environment.get("HOME", ".");
string csvPath = home ~ "/.unsandbox/accounts.csv";
auto creds = loadAccountsCSV(csvPath, accountIndex);
if (creds[0].empty) {
creds = loadAccountsCSV("accounts.csv", accountIndex);
}
if (!creds[0].empty) {
publicKey = explicitPublicKey.empty ? creds[0] : explicitPublicKey;
secretKey = creds[1];
}
} else {
publicKey = explicitPublicKey.empty
? environment.get("UNSANDBOX_PUBLIC_KEY", "")
: explicitPublicKey;
secretKey = environment.get("UNSANDBOX_SECRET_KEY", "");
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
if (publicKey.empty) {
publicKey = environment.get("UNSANDBOX_API_KEY", "");
}
// Try UNSANDBOX_ACCOUNT env var to pick a row
int envAccount = -1;
string envAcct = environment.get("UNSANDBOX_ACCOUNT", "");
if (!envAcct.empty) envAccount = to!int(envAcct);
if (publicKey.empty) {
string home = environment.get("HOME", ".");
string csvPath = home ~ "/.unsandbox/accounts.csv";
auto creds = loadAccountsCSV(csvPath, envAccount >= 0 ? envAccount : 0);
if (creds[0].empty) {
creds = loadAccountsCSV("accounts.csv", envAccount >= 0 ? envAccount : 0);
}
if (!creds[0].empty) {
publicKey = creds[0];
secretKey = creds[1];
}
}
}
if (args.length < 2) {
@ -1598,6 +1668,7 @@ int main(string[] args) {
else if (args[i] == "--screen") screen = true;
else if (args[i] == "-f" && i+1 < args.length) inputFiles ~= args[++i];
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
}
cmdSession(list, kill, shell, network, vcpu, tmux, screen, inputFiles, publicKey, secretKey);
@ -1625,6 +1696,7 @@ int main(string[] args) {
if (args[i] == "-e" && i+1 < args.length) svcEnvs ~= args[++i];
else if (args[i] == "--env-file" && i+1 < args.length) svcEnvFile = args[++i];
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
}
cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, unfreezeOnDemandEnabled, createUnfreezeOnDemand, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey);
return 0;
@ -1658,6 +1730,7 @@ int main(string[] args) {
else if (args[i] == "-e" && i+1 < args.length) svcEnvs ~= args[++i];
else if (args[i] == "--env-file" && i+1 < args.length) svcEnvFile = args[++i];
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
}
cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, unfreezeOnDemandEnabled, createUnfreezeOnDemand, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey);
@ -1670,6 +1743,7 @@ int main(string[] args) {
for (size_t i = 2; i < args.length; i++) {
if (args[i] == "--extend") extend = true;
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
}
if (publicKey.empty) {
@ -1687,6 +1761,7 @@ int main(string[] args) {
for (size_t i = 2; i < args.length; i++) {
if (args[i] == "--json") jsonOutput = true;
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
}
if (publicKey.empty) {
@ -1720,6 +1795,7 @@ int main(string[] args) {
else if (args[i] == "--name" && i+1 < args.length) name = args[++i];
else if (args[i] == "--ports" && i+1 < args.length) ports = args[++i];
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
}
if (publicKey.empty) {
@ -1743,6 +1819,7 @@ int main(string[] args) {
else if (args[i] == "-n" && i+1 < args.length) network = args[++i];
else if (args[i] == "-v" && i+1 < args.length) vcpu = to!int(args[++i]);
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
else if (args[i].startsWith("-")) {
stderr.writefln("%sUnknown option: %s%s", RED, args[i], RESET);
return 1;

View file

@ -124,7 +124,8 @@ data class Args(
var imageSpawn: String? = null,
var imageClone: String? = null,
var imageName: String? = null,
var imagePorts: String? = null
var imagePorts: String? = null,
var accountIndex: Int = -1
)
fun main(args: Array<String>) {
@ -151,7 +152,7 @@ fun main(args: Array<String>) {
}
fun cmdExecute(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
val code = File(args.sourceFile!!).readText()
val language = detectLanguage(args.sourceFile!!)
@ -226,7 +227,7 @@ fun cmdExecute(args: Args) {
}
fun cmdSession(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
if (args.sessionList) {
val result = apiRequest("/sessions", "GET", null, publicKey, secretKey)
@ -287,7 +288,7 @@ fun cmdSession(args: Args) {
}
fun cmdService(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
// Handle env subcommand
if (args.envAction != null) {
@ -541,7 +542,7 @@ fun saveLanguagesCache(languages: List<String>) {
}
fun cmdLanguages(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
// Try cache first
var languages = loadLanguagesCache()
@ -563,7 +564,7 @@ fun cmdLanguages(args: Args) {
}
fun cmdImage(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
if (args.imageList) {
val result = apiRequest("/images", "GET", null, publicKey, secretKey)
@ -654,7 +655,7 @@ fun cmdImage(args: Args) {
}
fun cmdKey(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
val result = validateKey(publicKey, secretKey)
val valid = result["valid"] as? Boolean ?: false
@ -733,11 +734,41 @@ fun validateKey(publicKey: String?, secretKey: String): Map<String, Any> {
return parseJson(response)
}
fun getApiKeys(argsKey: String?): Pair<String?, String> {
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row, or null.
fun loadAccountsCSV(path: String, index: Int): Pair<String, String>? {
val file = java.io.File(path)
if (!file.exists()) return null
var row = 0
for (line in file.readLines()) {
val stripped = line.trim()
if (stripped.isEmpty() || stripped.startsWith("#")) continue
if (row == index) {
val comma = stripped.indexOf(',')
if (comma < 0) return null
return Pair(stripped.substring(0, comma), stripped.substring(comma + 1))
}
row++
}
return null
}
fun getApiKeys(argsKey: String?, accountIndex: Int = -1): Pair<String?, String> {
var publicKey: String? = null
var secretKey: String? = null
if (argsKey != null) {
if (accountIndex >= 0) {
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
val home = System.getenv("HOME") ?: System.getProperty("user.home") ?: "."
var creds = loadAccountsCSV("$home/.unsandbox/accounts.csv", accountIndex)
if (creds == null) {
creds = loadAccountsCSV("accounts.csv", accountIndex)
}
if (creds != null) {
publicKey = if (argsKey != null) argsKey else creds.first
secretKey = creds.second
}
} else if (argsKey != null) {
secretKey = argsKey
publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
} else {
@ -750,6 +781,22 @@ fun getApiKeys(argsKey: String?): Pair<String?, String> {
secretKey = apiKey
}
}
// Try UNSANDBOX_ACCOUNT env var to pick a row
val envAcct = System.getenv("UNSANDBOX_ACCOUNT")
val envAccount = envAcct?.toIntOrNull() ?: -1
if (publicKey.isNullOrEmpty()) {
val home = System.getenv("HOME") ?: System.getProperty("user.home") ?: "."
var creds = loadAccountsCSV("$home/.unsandbox/accounts.csv", if (envAccount >= 0) envAccount else 0)
if (creds == null) {
creds = loadAccountsCSV("accounts.csv", if (envAccount >= 0) envAccount else 0)
}
if (creds != null) {
publicKey = creds.first
secretKey = creds.second
}
}
}
if (secretKey.isNullOrEmpty()) {
@ -1005,7 +1052,7 @@ fun serviceEnvDelete(serviceId: String, publicKey: String?, secretKey: String):
}
fun cmdServiceEnv(args: Args) {
val (publicKey, secretKey) = getApiKeys(args.apiKey)
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
val action = args.envAction
val target = args.envTarget
@ -1260,6 +1307,7 @@ fun parseArgs(args: Array<String>): Args {
"--dump-bootstrap" -> result.serviceDumpBootstrap = args[++i]
"--dump-file" -> result.serviceDumpFile = args[++i]
"--extend" -> result.keyExtend = true
"--account" -> result.accountIndex = args[++i].toInt()
"--env-file" -> result.envFile = args[++i]
"--info" -> {
when (result.command) {

View file

@ -687,6 +687,26 @@ fn cmd_service(name string, ports string, service_type string, bootstrap string,
exit(1)
}
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
fn load_accounts_csv(path string, index int) ?(string, string) {
content := os.read_file(path) or { return none }
lines := content.split('\n')
mut row := 0
for raw_line in lines {
line := raw_line.trim_space()
if line == '' || line.starts_with('#') {
continue
}
if row == index {
idx := line.index(',') or { return none }
return line[..idx], line[idx + 1..]
}
row++
}
return none
}
fn get_public_key() string {
pub_key := os.getenv('UNSANDBOX_PUBLIC_KEY')
if pub_key != '' {
@ -1171,8 +1191,82 @@ fn cmd_languages(json_output bool, api_key string) {
}
}
fn resolve_credentials(account_index int, explicit_pub_key string) (string, string) {
if account_index >= 0 {
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
home := os.getenv('HOME')
csv_path := (if home != '' { home } else { '.' }) + '/.unsandbox/accounts.csv'
if creds := load_accounts_csv(csv_path, account_index) {
pk := if explicit_pub_key != '' { explicit_pub_key } else { creds.0 }
return pk, creds.1
}
// fall back to ./accounts.csv
if creds := load_accounts_csv('accounts.csv', account_index) {
pk := if explicit_pub_key != '' { explicit_pub_key } else { creds.0 }
return pk, creds.1
}
return explicit_pub_key, ''
}
mut pub_key := if explicit_pub_key != '' { explicit_pub_key } else { os.getenv('UNSANDBOX_PUBLIC_KEY') }
mut sec_key := os.getenv('UNSANDBOX_SECRET_KEY')
if pub_key == '' {
api_key := os.getenv('UNSANDBOX_API_KEY')
if api_key != '' {
pub_key = api_key
}
}
// Try UNSANDBOX_ACCOUNT env var to pick a row
mut env_account := -1
env_acct := os.getenv('UNSANDBOX_ACCOUNT')
if env_acct != '' {
env_account = env_acct.int()
}
if pub_key == '' {
home := os.getenv('HOME')
csv_path := (if home != '' { home } else { '.' }) + '/.unsandbox/accounts.csv'
row := if env_account >= 0 { env_account } else { 0 }
if creds := load_accounts_csv(csv_path, row) {
pub_key = creds.0
sec_key = creds.1
}
if pub_key == '' {
if creds2 := load_accounts_csv('accounts.csv', row) {
pub_key = creds2.0
sec_key = creds2.1
}
}
}
if pub_key == '' {
eprintln('${red}Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set${reset}')
exit(1)
}
return pub_key, sec_key
}
fn main() {
mut api_key := get_public_key()
// First pass: scan for --account N and -p flags
mut account_index := -1
mut explicit_pub_key := ''
for i := 1; i < os.args.len; i++ {
if os.args[i] == '--account' && i + 1 < os.args.len {
account_index = os.args[i + 1].int()
i++
} else if os.args[i] == '-p' && i + 1 < os.args.len {
explicit_pub_key = os.args[i + 1]
i++
}
}
pub_key, sec_key := resolve_credentials(account_index, explicit_pub_key)
// Inject resolved credentials into env so get_public_key/get_secret_key pick them up
os.setenv('UNSANDBOX_PUBLIC_KEY', pub_key, true)
os.setenv('UNSANDBOX_SECRET_KEY', sec_key, true)
mut api_key := pub_key
if os.args.len < 2 {
eprintln('Usage: ${os.args[0]} [options] <source_file>')
@ -1238,6 +1332,9 @@ fn main() {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
'-f' {
i++
f := os.args[i]
@ -1392,6 +1489,9 @@ fn main() {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
'-f' {
i++
f := os.args[i]
@ -1429,6 +1529,9 @@ fn main() {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {}
}
i++
@ -1449,6 +1552,9 @@ fn main() {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {}
}
i++
@ -1559,6 +1665,9 @@ fn main() {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {}
}
i++
@ -1600,6 +1709,7 @@ fn main() {
'--ports' { i++; ports = os.args[i] }
'--hot' { hot = true }
'-k' { i++; api_key = os.args[i] }
'--account' { i++ /* already handled in first pass */ }
else {}
}
i++
@ -1625,6 +1735,7 @@ fn main() {
'--grep' { i++; grep = os.args[i] }
'--follow', '-f' { follow = true }
'-k' { i++; api_key = os.args[i] }
'--account' { i++ /* already handled in first pass */ }
else {}
}
i++
@ -1671,6 +1782,9 @@ fn main() {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {
if os.args[i].starts_with('-') {
eprintln('${red}Unknown option: ${os.args[i]}${reset}')