Add --account N flag and fix credential priority in C#, dotnet, Swift
All three implementations had a defect where UNSANDBOX_PUBLIC_KEY/ UNSANDBOX_SECRET_KEY env vars were checked before --account N, making it impossible to select a specific accounts.csv row when env vars exist. Correct priority order now enforced: 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 Changes per file: - clients/csharp/sync/src/Un.cs: add LoadAccountsCSV(), rewrite GetApiKeys() with correct tier ordering, add Account=-1 to Args, parse --account N, update all GetApiKeys call sites - clients/dotnet/sync/src/Un.cs: same as above (top-level stmt style) - clients/swift/sync/src/un.swift: reorder resolveCredentials() tiers, add accountIndex to CLIArgs, parse --account N, pass to entry point Also create un.swift symlink at repo root (parallel to Un.cs symlink).
This commit is contained in:
parent
7dd16bf796
commit
b1b2c14d86
4 changed files with 170 additions and 47 deletions
|
|
@ -115,7 +115,7 @@ class Un
|
||||||
|
|
||||||
static void CmdExecute(Args args)
|
static void CmdExecute(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
string code = File.ReadAllText(args.SourceFile);
|
string code = File.ReadAllText(args.SourceFile);
|
||||||
string language = DetectLanguage(args.SourceFile);
|
string language = DetectLanguage(args.SourceFile);
|
||||||
|
|
||||||
|
|
@ -202,7 +202,7 @@ class Un
|
||||||
|
|
||||||
static void CmdSession(Args args)
|
static void CmdSession(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
if (args.SessionList)
|
if (args.SessionList)
|
||||||
{
|
{
|
||||||
|
|
@ -255,7 +255,7 @@ class Un
|
||||||
|
|
||||||
static void CmdKey(Args args)
|
static void CmdKey(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
var result = ApiRequest("/keys/validate", "POST", null, publicKey, secretKey);
|
var result = ApiRequest("/keys/validate", "POST", null, publicKey, secretKey);
|
||||||
|
|
||||||
|
|
@ -340,7 +340,7 @@ class Un
|
||||||
|
|
||||||
static void CmdService(Args args)
|
static void CmdService(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
// Handle env subcommand
|
// Handle env subcommand
|
||||||
if (!string.IsNullOrEmpty(args.EnvAction))
|
if (!string.IsNullOrEmpty(args.EnvAction))
|
||||||
|
|
@ -606,24 +606,77 @@ class Un
|
||||||
Environment.Exit(1);
|
Environment.Exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static (string, string) GetApiKeys(string argsKey)
|
static (string, string) LoadAccountsCSV(string path, int index)
|
||||||
{
|
{
|
||||||
string publicKey = Environment.GetEnvironmentVariable("UNSANDBOX_PUBLIC_KEY");
|
if (!File.Exists(path)) return (null, null);
|
||||||
string secretKey = Environment.GetEnvironmentVariable("UNSANDBOX_SECRET_KEY");
|
int row = 0;
|
||||||
|
foreach (string rawLine in File.ReadAllLines(path))
|
||||||
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
|
||||||
if (string.IsNullOrEmpty(publicKey) || string.IsNullOrEmpty(secretKey))
|
|
||||||
{
|
{
|
||||||
string legacyKey = argsKey ?? Environment.GetEnvironmentVariable("UNSANDBOX_API_KEY");
|
string line = rawLine.Trim();
|
||||||
if (string.IsNullOrEmpty(legacyKey))
|
if (line.Length == 0 || line.StartsWith("#")) continue;
|
||||||
|
if (row == index)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"{RED}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set{RESET}");
|
string[] parts = line.Split(',');
|
||||||
Environment.Exit(1);
|
if (parts.Length >= 2)
|
||||||
|
return (parts[0].Trim(), parts[1].Trim());
|
||||||
}
|
}
|
||||||
return (legacyKey, null);
|
row++;
|
||||||
|
}
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static (string, string) GetApiKeys(string argsKey, int accountIndex = -1)
|
||||||
|
{
|
||||||
|
// Tier 1: explicit -p/-k flags (argsKey covers legacy -k/--api-key)
|
||||||
|
// (handled by callers that pass explicit keys directly to ApiRequest)
|
||||||
|
|
||||||
|
// Tier 2: --account N → accounts.csv row N (bypasses env vars)
|
||||||
|
if (accountIndex >= 0)
|
||||||
|
{
|
||||||
|
string home = Environment.GetEnvironmentVariable("HOME")
|
||||||
|
?? Environment.GetEnvironmentVariable("USERPROFILE") ?? ".";
|
||||||
|
string homeCsv = Path.Combine(home, ".unsandbox", "accounts.csv");
|
||||||
|
var (pk1, sk1) = LoadAccountsCSV(homeCsv, accountIndex);
|
||||||
|
if (!string.IsNullOrEmpty(pk1) && !string.IsNullOrEmpty(sk1))
|
||||||
|
return (pk1, sk1);
|
||||||
|
var (pk2, sk2) = LoadAccountsCSV("accounts.csv", accountIndex);
|
||||||
|
if (!string.IsNullOrEmpty(pk2) && !string.IsNullOrEmpty(sk2))
|
||||||
|
return (pk2, sk2);
|
||||||
|
Console.Error.WriteLine($"{RED}Error: No account at index {accountIndex} in accounts.csv{RESET}");
|
||||||
|
Environment.Exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (publicKey, secretKey);
|
// Tier 3: environment variables
|
||||||
|
string publicKey = Environment.GetEnvironmentVariable("UNSANDBOX_PUBLIC_KEY");
|
||||||
|
string secretKey = Environment.GetEnvironmentVariable("UNSANDBOX_SECRET_KEY");
|
||||||
|
if (!string.IsNullOrEmpty(publicKey) && !string.IsNullOrEmpty(secretKey))
|
||||||
|
return (publicKey, secretKey);
|
||||||
|
|
||||||
|
// Tier 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env var)
|
||||||
|
int defaultIdx = 0;
|
||||||
|
string acctEnv = Environment.GetEnvironmentVariable("UNSANDBOX_ACCOUNT");
|
||||||
|
if (!string.IsNullOrEmpty(acctEnv) && int.TryParse(acctEnv, out int parsedIdx))
|
||||||
|
defaultIdx = parsedIdx;
|
||||||
|
string home2 = Environment.GetEnvironmentVariable("HOME")
|
||||||
|
?? Environment.GetEnvironmentVariable("USERPROFILE") ?? ".";
|
||||||
|
string homeCsv2 = Path.Combine(home2, ".unsandbox", "accounts.csv");
|
||||||
|
var (pk3, sk3) = LoadAccountsCSV(homeCsv2, defaultIdx);
|
||||||
|
if (!string.IsNullOrEmpty(pk3) && !string.IsNullOrEmpty(sk3))
|
||||||
|
return (pk3, sk3);
|
||||||
|
|
||||||
|
// Tier 5: ./accounts.csv row 0
|
||||||
|
var (pk4, sk4) = LoadAccountsCSV("accounts.csv", defaultIdx);
|
||||||
|
if (!string.IsNullOrEmpty(pk4) && !string.IsNullOrEmpty(sk4))
|
||||||
|
return (pk4, sk4);
|
||||||
|
|
||||||
|
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||||
|
string legacyKey = argsKey ?? Environment.GetEnvironmentVariable("UNSANDBOX_API_KEY");
|
||||||
|
if (string.IsNullOrEmpty(legacyKey))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"{RED}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set{RESET}");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
return (legacyKey, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static string DetectLanguage(string filename)
|
static string DetectLanguage(string filename)
|
||||||
|
|
@ -1284,6 +1337,7 @@ class Un
|
||||||
public string EnvTarget = null;
|
public string EnvTarget = null;
|
||||||
public bool KeyExtend = false;
|
public bool KeyExtend = false;
|
||||||
public bool LanguagesJson = false;
|
public bool LanguagesJson = false;
|
||||||
|
public int Account = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Args ParseArgs(string[] args)
|
static Args ParseArgs(string[] args)
|
||||||
|
|
@ -1345,6 +1399,7 @@ class Un
|
||||||
else if (arg == "--redeploy") result.ServiceRedeploy = args[++i];
|
else if (arg == "--redeploy") result.ServiceRedeploy = args[++i];
|
||||||
else if (arg == "--extend") result.KeyExtend = true;
|
else if (arg == "--extend") result.KeyExtend = true;
|
||||||
else if (arg == "--json") result.LanguagesJson = true;
|
else if (arg == "--json") result.LanguagesJson = true;
|
||||||
|
else if (arg == "--account") result.Account = int.Parse(args[++i]);
|
||||||
else if (!arg.StartsWith("-")) result.SourceFile = arg;
|
else if (!arg.StartsWith("-")) result.SourceFile = arg;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -1408,7 +1463,7 @@ class Un
|
||||||
|
|
||||||
if (languages == null)
|
if (languages == null)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
var result = ApiRequest("/languages", "GET", null, publicKey, secretKey);
|
var result = ApiRequest("/languages", "GET", null, publicKey, secretKey);
|
||||||
languages = new List<string>();
|
languages = new List<string>();
|
||||||
if (result.ContainsKey("languages") && result["languages"] is List<object> langs)
|
if (result.ContainsKey("languages") && result["languages"] is List<object> langs)
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ catch (Exception ex)
|
||||||
|
|
||||||
void CmdExecute(Args args)
|
void CmdExecute(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
var code = File.ReadAllText(args.SourceFile!);
|
var code = File.ReadAllText(args.SourceFile!);
|
||||||
var language = DetectLanguage(args.SourceFile!);
|
var language = DetectLanguage(args.SourceFile!);
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ void CmdExecute(Args args)
|
||||||
|
|
||||||
void CmdSession(Args args)
|
void CmdSession(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
if (args.SessionList)
|
if (args.SessionList)
|
||||||
{
|
{
|
||||||
|
|
@ -224,7 +224,7 @@ void CmdSession(Args args)
|
||||||
|
|
||||||
void CmdKey(Args args)
|
void CmdKey(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
var result = ApiRequest("/keys/validate", HttpMethod.Post, null, publicKey, secretKey);
|
var result = ApiRequest("/keys/validate", HttpMethod.Post, null, publicKey, secretKey);
|
||||||
|
|
||||||
if (!result.TryGetValue("valid", out var validObj) || validObj is not JsonElement validEl)
|
if (!result.TryGetValue("valid", out var validObj) || validObj is not JsonElement validEl)
|
||||||
|
|
@ -281,7 +281,7 @@ void OpenBrowser(string url)
|
||||||
|
|
||||||
void CmdService(Args args)
|
void CmdService(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(args.EnvAction))
|
if (!string.IsNullOrEmpty(args.EnvAction))
|
||||||
{
|
{
|
||||||
|
|
@ -548,7 +548,7 @@ void CmdServiceEnv(Args args, string publicKey, string secretKey)
|
||||||
|
|
||||||
void CmdSnapshot(Args args)
|
void CmdSnapshot(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
if (args.SnapshotList)
|
if (args.SnapshotList)
|
||||||
{
|
{
|
||||||
|
|
@ -620,7 +620,7 @@ void CmdSnapshot(Args args)
|
||||||
|
|
||||||
void CmdImage(Args args)
|
void CmdImage(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
if (args.ImageList)
|
if (args.ImageList)
|
||||||
{
|
{
|
||||||
|
|
@ -715,7 +715,7 @@ void CmdImage(Args args)
|
||||||
|
|
||||||
void CmdLanguages(Args args)
|
void CmdLanguages(Args args)
|
||||||
{
|
{
|
||||||
var (publicKey, secretKey) = GetApiKeys(args.ApiKey);
|
var (publicKey, secretKey) = GetApiKeys(args.ApiKey, args.Account);
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
var cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".unsandbox");
|
var cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".unsandbox");
|
||||||
|
|
@ -896,22 +896,69 @@ bool ServiceEnvSet(string serviceId, string envContent, string publicKey, string
|
||||||
catch { return false; }
|
catch { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
(string, string) GetApiKeys(string? argsKey)
|
(string, string) LoadAccountsCSV(string path, int index)
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(path)) return (null!, null!);
|
||||||
|
var row = 0;
|
||||||
|
foreach (var rawLine in File.ReadAllLines(path))
|
||||||
|
{
|
||||||
|
var line = rawLine.Trim();
|
||||||
|
if (line.Length == 0 || line.StartsWith("#")) continue;
|
||||||
|
if (row == index)
|
||||||
|
{
|
||||||
|
var parts = line.Split(',');
|
||||||
|
if (parts.Length >= 2)
|
||||||
|
return (parts[0].Trim(), parts[1].Trim());
|
||||||
|
}
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
return (null!, null!);
|
||||||
|
}
|
||||||
|
|
||||||
|
(string, string) GetApiKeys(string? argsKey, int accountIndex = -1)
|
||||||
|
{
|
||||||
|
// Tier 2: --account N → accounts.csv row N (bypasses env vars)
|
||||||
|
if (accountIndex >= 0)
|
||||||
|
{
|
||||||
|
var home = Environment.GetEnvironmentVariable("HOME")
|
||||||
|
?? Environment.GetEnvironmentVariable("USERPROFILE") ?? ".";
|
||||||
|
var homeCsv = Path.Combine(home, ".unsandbox", "accounts.csv");
|
||||||
|
var (pk1, sk1) = LoadAccountsCSV(homeCsv, accountIndex);
|
||||||
|
if (!string.IsNullOrEmpty(pk1) && !string.IsNullOrEmpty(sk1)) return (pk1, sk1);
|
||||||
|
var (pk2, sk2) = LoadAccountsCSV("accounts.csv", accountIndex);
|
||||||
|
if (!string.IsNullOrEmpty(pk2) && !string.IsNullOrEmpty(sk2)) return (pk2, sk2);
|
||||||
|
Console.Error.WriteLine($"{RED}Error: No account at index {accountIndex} in accounts.csv{RESET}");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tier 3: environment variables
|
||||||
var publicKey = Environment.GetEnvironmentVariable("UNSANDBOX_PUBLIC_KEY");
|
var publicKey = Environment.GetEnvironmentVariable("UNSANDBOX_PUBLIC_KEY");
|
||||||
var secretKey = Environment.GetEnvironmentVariable("UNSANDBOX_SECRET_KEY");
|
var secretKey = Environment.GetEnvironmentVariable("UNSANDBOX_SECRET_KEY");
|
||||||
|
if (!string.IsNullOrEmpty(publicKey) && !string.IsNullOrEmpty(secretKey))
|
||||||
|
return (publicKey, secretKey);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(publicKey) || string.IsNullOrEmpty(secretKey))
|
// Tier 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env var)
|
||||||
|
var defaultIdx = 0;
|
||||||
|
var acctEnv = Environment.GetEnvironmentVariable("UNSANDBOX_ACCOUNT");
|
||||||
|
if (!string.IsNullOrEmpty(acctEnv) && int.TryParse(acctEnv, out var parsedIdx))
|
||||||
|
defaultIdx = parsedIdx;
|
||||||
|
var home2 = Environment.GetEnvironmentVariable("HOME")
|
||||||
|
?? Environment.GetEnvironmentVariable("USERPROFILE") ?? ".";
|
||||||
|
var (pk3, sk3) = LoadAccountsCSV(Path.Combine(home2, ".unsandbox", "accounts.csv"), defaultIdx);
|
||||||
|
if (!string.IsNullOrEmpty(pk3) && !string.IsNullOrEmpty(sk3)) return (pk3, sk3);
|
||||||
|
|
||||||
|
// Tier 5: ./accounts.csv row 0
|
||||||
|
var (pk4, sk4) = LoadAccountsCSV("accounts.csv", defaultIdx);
|
||||||
|
if (!string.IsNullOrEmpty(pk4) && !string.IsNullOrEmpty(sk4)) return (pk4, sk4);
|
||||||
|
|
||||||
|
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||||
|
var legacyKey = argsKey ?? Environment.GetEnvironmentVariable("UNSANDBOX_API_KEY");
|
||||||
|
if (string.IsNullOrEmpty(legacyKey))
|
||||||
{
|
{
|
||||||
var legacyKey = argsKey ?? Environment.GetEnvironmentVariable("UNSANDBOX_API_KEY");
|
Console.Error.WriteLine($"{RED}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set{RESET}");
|
||||||
if (string.IsNullOrEmpty(legacyKey))
|
Environment.Exit(1);
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"{RED}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set{RESET}");
|
|
||||||
Environment.Exit(1);
|
|
||||||
}
|
|
||||||
return (legacyKey, "");
|
|
||||||
}
|
}
|
||||||
return (publicKey, secretKey);
|
return (legacyKey!, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
string DetectLanguage(string filename)
|
string DetectLanguage(string filename)
|
||||||
|
|
@ -1037,6 +1084,7 @@ Args ParseArgs(string[] args)
|
||||||
else if (arg == "--show-freeze-page-enabled") result.ServiceShowFreezePageEnabled = args[++i].ToLower() == "true";
|
else if (arg == "--show-freeze-page-enabled") result.ServiceShowFreezePageEnabled = args[++i].ToLower() == "true";
|
||||||
else if (arg == "--with-unfreeze-on-demand") result.ServiceCreateUnfreezeOnDemand = true;
|
else if (arg == "--with-unfreeze-on-demand") result.ServiceCreateUnfreezeOnDemand = true;
|
||||||
else if (arg == "--extend") result.KeyExtend = true;
|
else if (arg == "--extend") result.KeyExtend = true;
|
||||||
|
else if (arg == "--account") result.Account = int.Parse(args[++i]);
|
||||||
else if (arg == "--delete")
|
else if (arg == "--delete")
|
||||||
{
|
{
|
||||||
var val = args[++i];
|
var val = args[++i];
|
||||||
|
|
@ -2073,6 +2121,7 @@ class Args
|
||||||
public bool ServiceCreateUnfreezeOnDemand;
|
public bool ServiceCreateUnfreezeOnDemand;
|
||||||
public string? EnvFile, EnvAction, EnvTarget;
|
public string? EnvFile, EnvAction, EnvTarget;
|
||||||
public bool KeyExtend;
|
public bool KeyExtend;
|
||||||
|
public int Account = -1;
|
||||||
public bool SnapshotList;
|
public bool SnapshotList;
|
||||||
public string? SnapshotInfo, SnapshotDelete, SnapshotLock, SnapshotUnlock, SnapshotClone;
|
public string? SnapshotInfo, SnapshotDelete, SnapshotLock, SnapshotUnlock, SnapshotClone;
|
||||||
public string? SnapshotCloneType, SnapshotName;
|
public string? SnapshotCloneType, SnapshotName;
|
||||||
|
|
|
||||||
|
|
@ -153,30 +153,41 @@ func loadCredentialsFromCSV(_ path: URL, accountIndex: Int = 0) -> (String, Stri
|
||||||
|
|
||||||
/// Resolve credentials from 4-tier priority system
|
/// Resolve credentials from 4-tier priority system
|
||||||
func resolveCredentials(publicKey: String? = nil, secretKey: String? = nil, accountIndex: Int? = nil) throws -> (String, String) {
|
func resolveCredentials(publicKey: String? = nil, secretKey: String? = nil, accountIndex: Int? = nil) throws -> (String, String) {
|
||||||
// Tier 1: Function arguments
|
// Tier 1: Function arguments (-p/-k flags)
|
||||||
if let pk = publicKey, let sk = secretKey, !pk.isEmpty, !sk.isEmpty {
|
if let pk = publicKey, let sk = secretKey, !pk.isEmpty, !sk.isEmpty {
|
||||||
return (pk, sk)
|
return (pk, sk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tier 2: Environment variables
|
// Tier 2: --account N → accounts.csv row N (bypasses env vars)
|
||||||
|
if let idx = accountIndex {
|
||||||
|
let unsandboxDir = getUnsandboxDir()
|
||||||
|
if let creds = loadCredentialsFromCSV(unsandboxDir.appendingPathComponent("accounts.csv"), accountIndex: idx) {
|
||||||
|
return creds
|
||||||
|
}
|
||||||
|
let localPath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("accounts.csv")
|
||||||
|
if let creds = loadCredentialsFromCSV(localPath, accountIndex: idx) {
|
||||||
|
return creds
|
||||||
|
}
|
||||||
|
throw UnsandboxError.credentialsNotFound("No account at index \(idx) in accounts.csv")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tier 3: Environment variables
|
||||||
if let envPk = ProcessInfo.processInfo.environment["UNSANDBOX_PUBLIC_KEY"],
|
if let envPk = ProcessInfo.processInfo.environment["UNSANDBOX_PUBLIC_KEY"],
|
||||||
let envSk = ProcessInfo.processInfo.environment["UNSANDBOX_SECRET_KEY"],
|
let envSk = ProcessInfo.processInfo.environment["UNSANDBOX_SECRET_KEY"],
|
||||||
!envPk.isEmpty, !envSk.isEmpty {
|
!envPk.isEmpty, !envSk.isEmpty {
|
||||||
return (envPk, envSk)
|
return (envPk, envSk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine account index
|
// Tier 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env var)
|
||||||
let idx = accountIndex ?? Int(ProcessInfo.processInfo.environment["UNSANDBOX_ACCOUNT"] ?? "0") ?? 0
|
let defaultIdx = Int(ProcessInfo.processInfo.environment["UNSANDBOX_ACCOUNT"] ?? "0") ?? 0
|
||||||
|
|
||||||
// Tier 3: ~/.unsandbox/accounts.csv
|
|
||||||
let unsandboxDir = getUnsandboxDir()
|
let unsandboxDir = getUnsandboxDir()
|
||||||
if let creds = loadCredentialsFromCSV(unsandboxDir.appendingPathComponent("accounts.csv"), accountIndex: idx) {
|
if let creds = loadCredentialsFromCSV(unsandboxDir.appendingPathComponent("accounts.csv"), accountIndex: defaultIdx) {
|
||||||
return creds
|
return creds
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tier 4: ./accounts.csv
|
// Tier 5: ./accounts.csv row 0
|
||||||
let localPath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("accounts.csv")
|
let localPath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("accounts.csv")
|
||||||
if let creds = loadCredentialsFromCSV(localPath, accountIndex: idx) {
|
if let creds = loadCredentialsFromCSV(localPath, accountIndex: defaultIdx) {
|
||||||
return creds
|
return creds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,9 +195,10 @@ func resolveCredentials(publicKey: String? = nil, secretKey: String? = nil, acco
|
||||||
"""
|
"""
|
||||||
No credentials found. Please provide via:
|
No credentials found. Please provide via:
|
||||||
1. Function arguments (publicKey, secretKey)
|
1. Function arguments (publicKey, secretKey)
|
||||||
2. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
|
2. --account N (accounts.csv row N)
|
||||||
3. ~/.unsandbox/accounts.csv
|
3. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
|
||||||
4. ./accounts.csv
|
4. ~/.unsandbox/accounts.csv
|
||||||
|
5. ./accounts.csv
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1719,6 +1731,9 @@ class CLIArgs {
|
||||||
// Languages options
|
// Languages options
|
||||||
var jsonOutput: Bool = false
|
var jsonOutput: Bool = false
|
||||||
|
|
||||||
|
// Credential selection
|
||||||
|
var accountIndex: Int? = nil
|
||||||
|
|
||||||
func parse(_ args: [String]) {
|
func parse(_ args: [String]) {
|
||||||
var i = 0
|
var i = 0
|
||||||
let args = Array(args.dropFirst()) // Skip program name
|
let args = Array(args.dropFirst()) // Skip program name
|
||||||
|
|
@ -1753,6 +1768,9 @@ class CLIArgs {
|
||||||
case "-k", "--secret-key":
|
case "-k", "--secret-key":
|
||||||
i += 1
|
i += 1
|
||||||
if i < args.count { secretKey = args[i] }
|
if i < args.count { secretKey = args[i] }
|
||||||
|
case "--account":
|
||||||
|
i += 1
|
||||||
|
if i < args.count { accountIndex = Int(args[i]) }
|
||||||
case "-n", "--network":
|
case "-n", "--network":
|
||||||
i += 1
|
i += 1
|
||||||
if i < args.count { networkMode = args[i] }
|
if i < args.count { networkMode = args[i] }
|
||||||
|
|
@ -2477,7 +2495,7 @@ struct UnCLI {
|
||||||
let pk: String
|
let pk: String
|
||||||
let sk: String
|
let sk: String
|
||||||
do {
|
do {
|
||||||
(pk, sk) = try resolveCredentials(publicKey: args.publicKey, secretKey: args.secretKey)
|
(pk, sk) = try resolveCredentials(publicKey: args.publicKey, secretKey: args.secretKey, accountIndex: args.accountIndex)
|
||||||
} catch {
|
} catch {
|
||||||
fputs("Error: \(error)\n", stderr)
|
fputs("Error: \(error)\n", stderr)
|
||||||
exit(3)
|
exit(3)
|
||||||
|
|
|
||||||
1
un.swift
Symbolic link
1
un.swift
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
clients/swift/sync/src/un.swift
|
||||||
Loading…
Add table
Add a link
Reference in a new issue