/*
 * un - unsandbox.com CLI
 *
 * Authentication priority (highest to lowest, per POSIX convention):
 *   1. CLI flags: -p (public key) + -k (secret key)
 *   2. Environment variables: UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY
 *   3. Config file: ~/.unsandbox/accounts.csv (format: public_key,secret_key per line)
 *      - Use --account N to select account by index (0-based, default: 0)
 *      - Or set UNSANDBOX_ACCOUNT=N environment variable
 *
 * Request authentication:
 *   Authorization: Bearer <public_key>                        <- identifies account
 *   X-Timestamp: <unix_seconds>                               <- replay prevention
 *   X-Signature: HMAC-SHA256(secret_key, ts:method:path:body) <- proves secret + body integrity
 *
 * The secret key is NEVER transmitted. Server decrypts stored secret to verify HMAC.
 * Timestamp must be within ±5 minutes of server time (prevents replay attacks).
 * Body is included in signature to prevent tampering (empty string for GET/DELETE).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <time.h>
#include <limits.h>
#include <curl/curl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <poll.h>
#include <libwebsockets.h>
#include <pwd.h>

#ifdef UNSANDBOX_LIBRARY
#include "un.h"
#endif

#define API_URL "https://api.unsandbox.com/execute"
#define API_BASE "https://api.unsandbox.com"
#define PORTAL_BASE "https://unsandbox.com"
#define MAX_FILE_SIZE (100 * 1024 * 1024) // 100MB max single file
#define MAX_INPUT_FILES 1000
#define MAX_TOTAL_INPUT_SIZE (4096L * 1024 * 1024) // 4GB total across all input files
#define LARGE_UPLOAD_WARN_SIZE (1024L * 1024 * 1024) // Warn if total > 1GB
#define MAX_ENV_VARS 256  // LXC limit is typically higher
#define MAX_ENV_CONTENT_SIZE (64 * 1024)  // 64KB max env vault size
#define LANGUAGES_CACHE_TTL 3600  // 1 hour cache for languages list

// ============================================================================
// SHA-256 Implementation (for HMAC-SHA256)
// ============================================================================

static const uint32_t sha256_k[64] = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

#define SHA256_ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
#define SHA256_CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
#define SHA256_MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define SHA256_EP0(x) (SHA256_ROTR(x, 2) ^ SHA256_ROTR(x, 13) ^ SHA256_ROTR(x, 22))
#define SHA256_EP1(x) (SHA256_ROTR(x, 6) ^ SHA256_ROTR(x, 11) ^ SHA256_ROTR(x, 25))
#define SHA256_SIG0(x) (SHA256_ROTR(x, 7) ^ SHA256_ROTR(x, 18) ^ ((x) >> 3))
#define SHA256_SIG1(x) (SHA256_ROTR(x, 17) ^ SHA256_ROTR(x, 19) ^ ((x) >> 10))

typedef struct {
    uint32_t state[8];
    uint64_t count;
    unsigned char buffer[64];
} UN_SHA256_CTX;

static void sha256_init(UN_SHA256_CTX *ctx) {
    ctx->state[0] = 0x6a09e667;
    ctx->state[1] = 0xbb67ae85;
    ctx->state[2] = 0x3c6ef372;
    ctx->state[3] = 0xa54ff53a;
    ctx->state[4] = 0x510e527f;
    ctx->state[5] = 0x9b05688c;
    ctx->state[6] = 0x1f83d9ab;
    ctx->state[7] = 0x5be0cd19;
    ctx->count = 0;
}

static void sha256_transform(UN_SHA256_CTX *ctx, const unsigned char *data) {
    uint32_t a, b, c, d, e, f, g, h, t1, t2, w[64];
    int i;

    for (i = 0; i < 16; i++) {
        w[i] = ((uint32_t)data[i * 4] << 24) | ((uint32_t)data[i * 4 + 1] << 16) |
               ((uint32_t)data[i * 4 + 2] << 8) | ((uint32_t)data[i * 4 + 3]);
    }
    for (i = 16; i < 64; i++) {
        w[i] = SHA256_SIG1(w[i - 2]) + w[i - 7] + SHA256_SIG0(w[i - 15]) + w[i - 16];
    }

    a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];
    e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];

    for (i = 0; i < 64; i++) {
        t1 = h + SHA256_EP1(e) + SHA256_CH(e, f, g) + sha256_k[i] + w[i];
        t2 = SHA256_EP0(a) + SHA256_MAJ(a, b, c);
        h = g; g = f; f = e; e = d + t1;
        d = c; c = b; b = a; a = t1 + t2;
    }

    ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
    ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
}

static void sha256_update(UN_SHA256_CTX *ctx, const unsigned char *data, size_t len) {
    size_t i, index, part_len;
    index = (size_t)(ctx->count & 0x3F);
    ctx->count += len;
    part_len = 64 - index;
    if (len >= part_len) {
        memcpy(&ctx->buffer[index], data, part_len);
        sha256_transform(ctx, ctx->buffer);
        for (i = part_len; i + 63 < len; i += 64)
            sha256_transform(ctx, &data[i]);
        index = 0;
    } else {
        i = 0;
    }
    memcpy(&ctx->buffer[index], &data[i], len - i);
}

static void sha256_final(UN_SHA256_CTX *ctx, unsigned char hash[32]) {
    unsigned char pad[64];
    unsigned char count_bits[8];
    size_t index, pad_len;
    uint64_t bits = ctx->count * 8;
    int i;

    for (i = 0; i < 8; i++) {
        count_bits[i] = (unsigned char)(bits >> (56 - i * 8));
    }

    index = (size_t)(ctx->count & 0x3F);
    pad_len = (index < 56) ? (56 - index) : (120 - index);
    memset(pad, 0, pad_len);
    pad[0] = 0x80;
    sha256_update(ctx, pad, pad_len);
    sha256_update(ctx, count_bits, 8);

    for (i = 0; i < 8; i++) {
        hash[i * 4] = (unsigned char)(ctx->state[i] >> 24);
        hash[i * 4 + 1] = (unsigned char)(ctx->state[i] >> 16);
        hash[i * 4 + 2] = (unsigned char)(ctx->state[i] >> 8);
        hash[i * 4 + 3] = (unsigned char)(ctx->state[i]);
    }
}

// Compute raw SHA-256 hash (32 bytes)
static void sha256_raw(const unsigned char *data, size_t len, unsigned char hash[32]) {
    UN_SHA256_CTX ctx;
    sha256_init(&ctx);
    sha256_update(&ctx, data, len);
    sha256_final(&ctx, hash);
}

// ============================================================================
// HMAC-SHA256 Implementation
// ============================================================================

#define HMAC_SHA256_BLOCK_SIZE 64
#define HMAC_SHA256_HASH_SIZE 32

// Compute HMAC-SHA256 and return as lowercase hex string (64 chars + null)
static char* hmac_sha256_hex(const char *key, size_t key_len, const char *data, size_t data_len) {
    unsigned char k_ipad[HMAC_SHA256_BLOCK_SIZE];
    unsigned char k_opad[HMAC_SHA256_BLOCK_SIZE];
    unsigned char tk[HMAC_SHA256_HASH_SIZE];
    unsigned char inner_hash[HMAC_SHA256_HASH_SIZE];
    unsigned char final_hash[HMAC_SHA256_HASH_SIZE];
    size_t i;

    // If key is longer than block size, hash it first
    if (key_len > HMAC_SHA256_BLOCK_SIZE) {
        sha256_raw((const unsigned char *)key, key_len, tk);
        key = (const char *)tk;
        key_len = HMAC_SHA256_HASH_SIZE;
    }

    // XOR key with ipad and opad values
    memset(k_ipad, 0x36, HMAC_SHA256_BLOCK_SIZE);
    memset(k_opad, 0x5c, HMAC_SHA256_BLOCK_SIZE);
    for (i = 0; i < key_len; i++) {
        k_ipad[i] ^= (unsigned char)key[i];
        k_opad[i] ^= (unsigned char)key[i];
    }

    // Inner hash: SHA256(k_ipad || data)
    UN_SHA256_CTX ctx;
    sha256_init(&ctx);
    sha256_update(&ctx, k_ipad, HMAC_SHA256_BLOCK_SIZE);
    sha256_update(&ctx, (const unsigned char *)data, data_len);
    sha256_final(&ctx, inner_hash);

    // Outer hash: SHA256(k_opad || inner_hash)
    sha256_init(&ctx);
    sha256_update(&ctx, k_opad, HMAC_SHA256_BLOCK_SIZE);
    sha256_update(&ctx, inner_hash, HMAC_SHA256_HASH_SIZE);
    sha256_final(&ctx, final_hash);

    // Convert to hex
    char *hex = malloc(65);
    if (!hex) return NULL;
    for (i = 0; i < HMAC_SHA256_HASH_SIZE; i++) {
        sprintf(hex + i * 2, "%02x", final_hash[i]);
    }
    hex[64] = '\0';
    return hex;
}

// Sign a request: HMAC-SHA256(secret_key, timestamp:method:path:body)
// Returns signature as hex string (caller must free)
// body can be NULL for bodyless requests (GET, DELETE)
static char* sign_request(const char *secret_key, long timestamp, const char *method, const char *path, const char *body) {
    // Build message: "timestamp:method:path:body"
    // Body is included raw to prevent tampering (empty string if NULL)
    char ts_str[32];
    snprintf(ts_str, sizeof(ts_str), "%ld", timestamp);

    const char *body_str = body ? body : "";
    size_t msg_len = strlen(ts_str) + 1 + strlen(method) + 1 + strlen(path) + 1 + strlen(body_str);
    char *message = malloc(msg_len + 1);
    if (!message) return NULL;

    snprintf(message, msg_len + 1, "%s:%s:%s:%s", ts_str, method, path, body_str);

    char *signature = hmac_sha256_hex(secret_key, strlen(secret_key), message, strlen(message));
    free(message);
    return signature;
}

// ============================================================================
// Account Credentials Management (~/.unsandbox/accounts.csv)
// ============================================================================

typedef struct {
    char *public_key;   // unsb-pk-xxxx-xxxx-xxxx-xxxx - used as bearer token to identify account
    char *secret_key;   // unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx - used only for HMAC signing, never transmitted
} UnsandboxCredentials;

// Get path to ~/.unsandbox/accounts.csv
static char* get_accounts_csv_path(void) {
    const char *home = getenv("HOME");
    if (!home) {
        struct passwd *pw = getpwuid(getuid());
        if (pw) home = pw->pw_dir;
    }
    if (!home) return NULL;

    char *path = malloc(strlen(home) + 32);
    if (!path) return NULL;
    sprintf(path, "%s/.unsandbox/accounts.csv", home);
    return path;
}

// Ensure ~/.unsandbox directory exists
__attribute__((unused))
static void ensure_unsandbox_dir(void) {
    const char *home = getenv("HOME");
    if (!home) {
        struct passwd *pw = getpwuid(getuid());
        if (pw) home = pw->pw_dir;
    }
    if (!home) return;

    char dir[512];
    snprintf(dir, sizeof(dir), "%s/.unsandbox", home);
    mkdir(dir, 0700);
}

// Load account from ~/.unsandbox/accounts.csv by index (0-based)
// Format: public_key,secret_key (one per line)
// index -1 means use first valid account
static UnsandboxCredentials* load_credentials_from_csv(int account_index) {
    char *path = get_accounts_csv_path();
    if (!path) return NULL;

    FILE *f = fopen(path, "r");
    free(path);
    if (!f) return NULL;

    char line[1024];
    UnsandboxCredentials *creds = NULL;
    int current_index = 0;

    while (fgets(line, sizeof(line), f)) {
        // Skip empty lines and comments
        if (line[0] == '\n' || line[0] == '#') continue;

        // Remove newline
        size_t len = strlen(line);
        if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';

        // Parse CSV: public_key,secret_key[,comment]
        char *comma = strchr(line, ',');
        if (!comma) continue;

        *comma = '\0';
        char *pk = line;
        char *sk = comma + 1;

        // Strip optional 3rd field (comment) after second comma
        char *comma2 = strchr(sk, ',');
        if (comma2) *comma2 = '\0';

        // Validate key prefixes
        if (strncmp(pk, "unsb-pk-", 8) != 0) continue;
        if (strncmp(sk, "unsb-sk-", 8) != 0) continue;

        // Check if this is the account we want
        if (account_index >= 0 && current_index != account_index) {
            current_index++;
            continue;
        }

        creds = malloc(sizeof(UnsandboxCredentials));
        if (!creds) break;

        creds->public_key = strdup(pk);
        creds->secret_key = strdup(sk);

        if (!creds->public_key || !creds->secret_key) {
            free(creds->public_key);
            free(creds->secret_key);
            free(creds);
            creds = NULL;
        }
        break;
    }

    fclose(f);
    return creds;
}

// Count total accounts in CSV
__attribute__((unused))
static int count_accounts_in_csv(void) {
    char *path = get_accounts_csv_path();
    if (!path) return 0;

    FILE *f = fopen(path, "r");
    free(path);
    if (!f) return 0;

    char line[1024];
    int count = 0;

    while (fgets(line, sizeof(line), f)) {
        if (line[0] == '\n' || line[0] == '#') continue;
        size_t len = strlen(line);
        if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';
        char *comma = strchr(line, ',');
        if (!comma) continue;
        *comma = '\0';
        if (strncmp(line, "unsb-pk-", 8) == 0 && strncmp(comma + 1, "unsb-sk-", 8) == 0) {
            count++;
        }
    }

    fclose(f);
    return count;
}

// Get credentials using POSIX priority: CLI flags > env vars > CSV file
// cli_pk/cli_sk: from -p/-k flags (can be NULL)
// account_index: from --account flag (-1 means use env var or default to 0)
static UnsandboxCredentials* get_credentials(const char *cli_pk, const char *cli_sk, int account_index) {
    // Priority 1: CLI flags (-p and -k) - highest priority per POSIX convention
    if (cli_pk && cli_sk && strlen(cli_pk) > 0 && strlen(cli_sk) > 0) {
        UnsandboxCredentials *creds = malloc(sizeof(UnsandboxCredentials));
        if (!creds) return NULL;

        creds->public_key = strdup(cli_pk);
        creds->secret_key = strdup(cli_sk);

        if (!creds->public_key || !creds->secret_key) {
            free(creds->public_key);
            free(creds->secret_key);
            free(creds);
            return NULL;
        }
        return creds;
    }

    // Priority 2: --account N flag → explicit CSV lookup
    // When the user explicitly selects an account, go straight to accounts.csv.
    // Env vars are intentionally bypassed — an explicit flag must win over ambient env.
    if (account_index >= 0) {
        return load_credentials_from_csv(account_index);
    }

    // Priority 3: Environment variables (keys)
    const char *env_pk = getenv("UNSANDBOX_PUBLIC_KEY");
    const char *env_sk = getenv("UNSANDBOX_SECRET_KEY");

    if (env_pk && env_sk && strlen(env_pk) > 0 && strlen(env_sk) > 0) {
        UnsandboxCredentials *creds = malloc(sizeof(UnsandboxCredentials));
        if (!creds) return NULL;

        creds->public_key = strdup(env_pk);
        creds->secret_key = strdup(env_sk);

        if (!creds->public_key || !creds->secret_key) {
            free(creds->public_key);
            free(creds->secret_key);
            free(creds);
            return NULL;
        }
        return creds;
    }

    // Priority 4: Config file (~/.unsandbox/accounts.csv)
    // Use UNSANDBOX_ACCOUNT env var, or default to account 0
    int csv_index = 0;
    const char *env_account = getenv("UNSANDBOX_ACCOUNT");
    if (env_account && strlen(env_account) > 0) {
        csv_index = atoi(env_account);
    }
    return load_credentials_from_csv(csv_index);
}

static void free_credentials(UnsandboxCredentials *creds) {
    if (!creds) return;
    free(creds->public_key);
    free(creds->secret_key);
    free(creds);
}

// ============================================================================
// End Credentials Management
// ============================================================================

// ============================================================================
// HMAC Auth Headers Helper
// ============================================================================

// Add HMAC authentication headers to a curl_slist
// Returns a new slist with auth headers appended (caller must free with curl_slist_free_all)
// method: "GET", "POST", etc.
// path: e.g., "/execute", "/services", "/sessions"
//
// Adds these headers:
//   Authorization: Bearer <public_key>                                 (identifies account)
//   X-Timestamp: <unix_timestamp>                                      (replay prevention)
//   X-Signature: <HMAC-SHA256(secret_key, timestamp:method:path:body)> (proves secret + body integrity)
// body can be NULL for bodyless requests (GET, DELETE)
static struct curl_slist* add_hmac_auth_headers(struct curl_slist *headers,
                                                  const UnsandboxCredentials *creds,
                                                  const char *method,
                                                  const char *path,
                                                  const char *body) {
    if (!creds || !creds->public_key || !creds->secret_key) return headers;

    long timestamp = (long)time(NULL);
    char *signature = sign_request(creds->secret_key, timestamp, method, path, body);
    if (!signature) return headers;

    char auth_header[256];
    char ts_header[64];
    char sig_header[128];

    // Public key identifies the account (server looks up by key)
    snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", creds->public_key);
    // Timestamp prevents replay attacks (server checks ±5 minutes)
    snprintf(ts_header, sizeof(ts_header), "X-Timestamp: %ld", timestamp);
    // Signature proves possession of secret key and body integrity
    snprintf(sig_header, sizeof(sig_header), "X-Signature: %s", signature);

    headers = curl_slist_append(headers, auth_header);
    headers = curl_slist_append(headers, ts_header);
    headers = curl_slist_append(headers, sig_header);

    free(signature);
    return headers;
}

// ============================================================================
// End HMAC Auth Headers Helper
// ============================================================================

// Polling delays (milliseconds) - matches opencompletion.com cadence
// Cumulative: 300ms, 750ms, 1450ms, 2350ms, 3000ms, 4600ms, 6600ms+
static const int POLL_DELAYS[] = {300, 450, 700, 900, 650, 1600, 2000};
#define POLL_DELAYS_COUNT 7
#define POLL_MAX_CONSECUTIVE_ERRORS 30
#define POLL_ERROR_BACKOFF_MS 2000

// Response buffer structure
struct ResponseBuffer {
    char *data;
    size_t size;
};

// Input file structure
struct InputFile {
    char *filename;
    char *content_base64;
};

// Environment variable structure
struct EnvVar {
    char *key;
    char *value;
};

// Write callback for libcurl
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct ResponseBuffer *mem = (struct ResponseBuffer *)userp;

    char *ptr = realloc(mem->data, mem->size + realsize + 1);
    if (!ptr) {
        fprintf(stderr, "Error: out of memory\n");
        return 0;
    }

    mem->data = ptr;
    memcpy(&(mem->data[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->data[mem->size] = 0;

    return realsize;
}

// Base64 encoding table
static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// Base64 encode
char* base64_encode(const unsigned char *data, size_t input_length, size_t *output_length) {
    *output_length = 4 * ((input_length + 2) / 3);
    char *encoded = malloc(*output_length + 1);
    if (!encoded) return NULL;

    size_t i, j;
    for (i = 0, j = 0; i < input_length;) {
        uint32_t octet_a = i < input_length ? data[i++] : 0;
        uint32_t octet_b = i < input_length ? data[i++] : 0;
        uint32_t octet_c = i < input_length ? data[i++] : 0;
        uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;

        encoded[j++] = base64_table[(triple >> 18) & 0x3F];
        encoded[j++] = base64_table[(triple >> 12) & 0x3F];
        encoded[j++] = base64_table[(triple >> 6) & 0x3F];
        encoded[j++] = base64_table[triple & 0x3F];
    }

    // Add padding
    int mod = input_length % 3;
    if (mod > 0) {
        encoded[*output_length - 1] = '=';
        if (mod == 1) encoded[*output_length - 2] = '=';
    }

    encoded[*output_length] = '\0';
    return encoded;
}

// Base64 decode
unsigned char* base64_decode(const char *data, size_t input_length, size_t *output_length) {
    if (input_length % 4 != 0) return NULL;

    *output_length = input_length / 4 * 3;
    if (data[input_length - 1] == '=') (*output_length)--;
    if (data[input_length - 2] == '=') (*output_length)--;

    unsigned char *decoded = malloc(*output_length + 1);
    if (!decoded) return NULL;

    int decoding_table[256];
    for (int i = 0; i < 256; i++) decoding_table[i] = -1;
    for (int i = 0; i < 64; i++) decoding_table[(unsigned char)base64_table[i]] = i;

    size_t i, j;
    for (i = 0, j = 0; i < input_length;) {
        uint32_t sextet_a = data[i] == '=' ? 0 : decoding_table[(unsigned char)data[i]]; i++;
        uint32_t sextet_b = data[i] == '=' ? 0 : decoding_table[(unsigned char)data[i]]; i++;
        uint32_t sextet_c = data[i] == '=' ? 0 : decoding_table[(unsigned char)data[i]]; i++;
        uint32_t sextet_d = data[i] == '=' ? 0 : decoding_table[(unsigned char)data[i]]; i++;

        uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d;

        if (j < *output_length) decoded[j++] = (triple >> 16) & 0xFF;
        if (j < *output_length) decoded[j++] = (triple >> 8) & 0xFF;
        if (j < *output_length) decoded[j++] = triple & 0xFF;
    }

    decoded[*output_length] = '\0';
    return decoded;
}

// Detect language from shebang line
const char* detect_language_from_shebang(const char *code) {
    if (code[0] != '#' || code[1] != '!') return NULL;

    if (strstr(code, "/python") || strstr(code, "/python3") || strstr(code, "/python2")) return "python";
    if (strstr(code, "/node") || strstr(code, "/nodejs")) return "javascript";
    if (strstr(code, "/ruby")) return "ruby";
    if (strstr(code, "/perl")) return "perl";
    if (strstr(code, "/php")) return "php";
    if (strstr(code, "/bash") || strstr(code, "/sh")) return "bash";
    if (strstr(code, "/lua")) return "lua";
    if (strstr(code, "/tclsh") || strstr(code, "/wish")) return "tcl";
    if (strstr(code, "/raku") || strstr(code, "/perl6")) return "raku";
    if (strstr(code, "/julia")) return "julia";
    if (strstr(code, "/Rscript")) return "r";
    if (strstr(code, "/groovy")) return "groovy";
    if (strstr(code, "/scala")) return "scala";
    if (strstr(code, "/swift")) return "swift";
    if (strstr(code, "/racket")) return "racket";
    if (strstr(code, "/scheme") || strstr(code, "/guile")) return "scheme";
    if (strstr(code, "/clisp") || strstr(code, "/sbcl")) return "commonlisp";
    if (strstr(code, "/ocaml")) return "ocaml";
    if (strstr(code, "/elixir")) return "elixir";

    return NULL;
}

// Detect language from file extension
const char* detect_language_from_extension(const char *filename) {
    const char *ext = strrchr(filename, '.');
    if (!ext) return NULL;
    ext++;

    if (strcmp(ext, "py") == 0) return "python";
    if (strcmp(ext, "js") == 0) return "javascript";
    if (strcmp(ext, "ts") == 0) return "typescript";
    if (strcmp(ext, "rb") == 0) return "ruby";
    if (strcmp(ext, "php") == 0) return "php";
    if (strcmp(ext, "pl") == 0) return "perl";
    if (strcmp(ext, "sh") == 0) return "bash";
    if (strcmp(ext, "r") == 0 || strcmp(ext, "R") == 0) return "r";
    if (strcmp(ext, "lua") == 0) return "lua";
    if (strcmp(ext, "go") == 0) return "go";
    if (strcmp(ext, "rs") == 0) return "rust";
    if (strcmp(ext, "c") == 0) return "c";
    if (strcmp(ext, "cpp") == 0 || strcmp(ext, "cc") == 0 || strcmp(ext, "cxx") == 0) return "cpp";
    if (strcmp(ext, "java") == 0) return "java";
    if (strcmp(ext, "kt") == 0) return "kotlin";
    if (strcmp(ext, "m") == 0) return "objc";
    if (strcmp(ext, "cs") == 0) return "csharp";
    if (strcmp(ext, "fs") == 0) return "fsharp";
    if (strcmp(ext, "hs") == 0) return "haskell";
    if (strcmp(ext, "ml") == 0) return "ocaml";
    if (strcmp(ext, "clj") == 0) return "clojure";
    if (strcmp(ext, "scm") == 0 || strcmp(ext, "ss") == 0) return "scheme";
    if (strcmp(ext, "erl") == 0) return "erlang";
    if (strcmp(ext, "ex") == 0 || strcmp(ext, "exs") == 0) return "elixir";
    if (strcmp(ext, "jl") == 0) return "julia";
    if (strcmp(ext, "d") == 0) return "d";
    if (strcmp(ext, "nim") == 0) return "nim";
    if (strcmp(ext, "zig") == 0) return "zig";
    if (strcmp(ext, "v") == 0) return "v";
    if (strcmp(ext, "cr") == 0) return "crystal";
    if (strcmp(ext, "dart") == 0) return "dart";
    if (strcmp(ext, "groovy") == 0) return "groovy";
    if (strcmp(ext, "f90") == 0 || strcmp(ext, "f95") == 0) return "fortran";
    if (strcmp(ext, "lisp") == 0 || strcmp(ext, "lsp") == 0) return "commonlisp";
    if (strcmp(ext, "cob") == 0) return "cobol";
    if (strcmp(ext, "tcl") == 0) return "tcl";
    if (strcmp(ext, "raku") == 0) return "raku";
    if (strcmp(ext, "pro") == 0 || strcmp(ext, "p") == 0) return "prolog";
    if (strcmp(ext, "4th") == 0 || strcmp(ext, "forth") == 0 || strcmp(ext, "fth") == 0) return "forth";

    return NULL;
}

// Read file contents
char* read_file(const char *filename, size_t *size) {
    FILE *f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Error: cannot open file '%s'\n", filename);
        return NULL;
    }

    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    if (fsize > MAX_FILE_SIZE) {
        fprintf(stderr, "Error: file too large (max %d bytes)\n", MAX_FILE_SIZE);
        fclose(f);
        return NULL;
    }

    char *content = malloc(fsize + 1);
    if (!content) {
        fprintf(stderr, "Error: out of memory\n");
        fclose(f);
        return NULL;
    }

    size_t read_size = fread(content, 1, fsize, f);
    content[read_size] = 0;
    *size = read_size;

    fclose(f);
    return content;
}

// Escape JSON string
char* escape_json_string(const char *str) {
    size_t len = strlen(str);
    char *escaped = malloc(len * 6 + 1); // Worst case: \uXXXX for each char
    if (!escaped) return NULL;

    char *out = escaped;
    for (size_t i = 0; i < len; i++) {
        switch (str[i]) {
            case '"':  *out++ = '\\'; *out++ = '"'; break;
            case '\\': *out++ = '\\'; *out++ = '\\'; break;
            case '\b': *out++ = '\\'; *out++ = 'b'; break;
            case '\f': *out++ = '\\'; *out++ = 'f'; break;
            case '\n': *out++ = '\\'; *out++ = 'n'; break;
            case '\r': *out++ = '\\'; *out++ = 'r'; break;
            case '\t': *out++ = '\\'; *out++ = 't'; break;
            default:
                if ((unsigned char)str[i] < 32) {
                    sprintf(out, "\\u%04x", (unsigned char)str[i]);
                    out += 6;
                } else {
                    *out++ = str[i];
                }
                break;
        }
    }
    *out = 0;
    return escaped;
}

// Extract JSON string value (simple parser)
char* extract_json_string(const char *json, const char *key) {
    char search[256];
    snprintf(search, sizeof(search), "\"%s\":\"", key);
    const char *start = strstr(json, search);
    if (!start) return NULL;

    start += strlen(search);
    const char *end = start;

    while (*end && !(*end == '"' && *(end - 1) != '\\')) {
        end++;
    }

    size_t len = end - start;
    char *result = malloc(len + 1);
    if (!result) return NULL;

    // Unescape while copying
    char *out = result;
    for (const char *p = start; p < end; p++) {
        if (*p == '\\' && p + 1 < end) {
            p++;
            switch (*p) {
                case 'n': *out++ = '\n'; break;
                case 't': *out++ = '\t'; break;
                case 'r': *out++ = '\r'; break;
                case '\\': *out++ = '\\'; break;
                case '"': *out++ = '"'; break;
                default: *out++ = *p; break;
            }
        } else {
            *out++ = *p;
        }
    }
    *out = 0;
    return result;
}

// Extract JSON number value (returns -1 if not found or null)
long long extract_json_number(const char *json, const char *key) {
    char search[256];
    snprintf(search, sizeof(search), "\"%s\":", key);
    const char *start = strstr(json, search);
    if (!start) return -1;

    start += strlen(search);
    // Skip whitespace
    while (*start == ' ' || *start == '\t') start++;

    // Check for null
    if (strncmp(start, "null", 4) == 0) return -1;

    return atoll(start);
}

// ============================================================================
// JSON Array Helpers (for Library API)
// ============================================================================

// Count objects in a JSON array (for malloc sizing)
// Returns number of top-level objects in array, or 0 if not found/empty
__attribute__((unused))
static int count_json_array_objects(const char *json, const char *array_key) {
    char search[256];
    snprintf(search, sizeof(search), "\"%s\":[", array_key);
    const char *start = strstr(json, search);
    if (!start) return 0;

    start += strlen(search);
    int count = 0, depth = 0;
    for (const char *p = start; *p && !(*p == ']' && depth == 0); p++) {
        if (*p == '{') {
            if (depth == 0) count++;
            depth++;
        } else if (*p == '}') {
            depth--;
        } else if (*p == '"') {
            // Skip string contents (may contain { } characters)
            p++;
            while (*p && !(*p == '"' && *(p-1) != '\\')) p++;
        }
    }
    return count;
}

// Skip past current JSON object, return pointer to position after closing }
// If pos doesn't point to '{', returns pos unchanged
__attribute__((unused))
static const char* skip_json_object(const char *pos) {
    if (!pos || *pos != '{') return pos;
    int depth = 1;
    pos++;
    while (*pos && depth > 0) {
        if (*pos == '{') {
            depth++;
        } else if (*pos == '}') {
            depth--;
        } else if (*pos == '"') {
            // Skip string contents
            pos++;
            while (*pos && !(*pos == '"' && *(pos-1) != '\\')) pos++;
        }
        pos++;
    }
    return pos;
}

// ============================================================================
// Thread-Local Error Storage (for Library API)
// ============================================================================

static __thread char unsandbox_error_buffer[512] = {0};

__attribute__((unused))
static void set_last_error(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsnprintf(unsandbox_error_buffer, sizeof(unsandbox_error_buffer), fmt, args);
    va_end(args);
}

// Format bytes to human readable (e.g., 1234567 -> "1.2M")
void format_bytes(long long bytes, char *buf, size_t bufsize) {
    if (bytes < 0) {
        snprintf(buf, bufsize, "-");
    } else if (bytes < 1024) {
        snprintf(buf, bufsize, "%lldB", bytes);
    } else if (bytes < 1024 * 1024) {
        snprintf(buf, bufsize, "%.1fK", bytes / 1024.0);
    } else if (bytes < 1024LL * 1024 * 1024) {
        snprintf(buf, bufsize, "%.1fM", bytes / (1024.0 * 1024));
    } else {
        snprintf(buf, bufsize, "%.1fG", bytes / (1024.0 * 1024 * 1024));
    }
}

// Get basename without extension
char* get_basename_no_ext(const char *path) {
    const char *base = strrchr(path, '/');
    base = base ? base + 1 : path;

    char *result = strdup(base);
    char *dot = strrchr(result, '.');
    if (dot) *dot = '\0';
    return result;
}

// Parse and handle response
void parse_and_print_response(const char *json_response, int save_artifacts, const char *artifact_dir, const char *source_file) {
    // Print stdout in blue
    char *out = extract_json_string(json_response, "stdout");
    if (out && strlen(out) > 0) {
        printf("\033[34m%s\033[0m", out);
        free(out);
    }

    // Print stderr in red
    char *err = extract_json_string(json_response, "stderr");
    if (err && strlen(err) > 0) {
        fprintf(stderr, "\033[31m%s\033[0m", err);
        free(err);
    }

    // Print API error in bold red
    char *error = extract_json_string(json_response, "error");
    if (error && strlen(error) > 0) {
        fprintf(stderr, "\033[1;31mError: %s\033[0m\n", error);
        free(error);
    }

    // Handle artifacts - API returns "artifacts":[{"filename":"...", "content_base64":"..."}]
    if (save_artifacts) {
        const char *artifacts_start = strstr(json_response, "\"artifacts\":[");
        if (artifacts_start) {
            const char *pos = artifacts_start + 13; // Skip "artifacts":[

            // Process each artifact in array
            while ((pos = strchr(pos, '{')) != NULL) {
                char *artifact_data = extract_json_string(pos, "content_base64");
                char *artifact_filename = extract_json_string(pos, "filename");

                if (artifact_data) {
                    size_t decoded_len;
                    unsigned char *decoded = base64_decode(artifact_data, strlen(artifact_data), &decoded_len);

                    if (decoded) {
                        char output_path[512];
                        const char *fname = artifact_filename ? artifact_filename : "a.out";

                        // For executables, use source filename instead of API filename
                        char *source_basename = NULL;
                        int is_elf = decoded_len >= 4 && decoded[0] == 0x7F &&
                                     decoded[1] == 'E' && decoded[2] == 'L' && decoded[3] == 'F';
                        int is_pe = decoded_len >= 2 && decoded[0] == 'M' && decoded[1] == 'Z';
                        int is_macho = decoded_len >= 4 &&
                                       ((decoded[0] == 0xCF && decoded[1] == 0xFA) ||
                                        (decoded[0] == 0xFE && decoded[1] == 0xED));

                        if ((is_elf || is_pe || is_macho) && source_file) {
                            source_basename = get_basename_no_ext(source_file);
                            fname = source_basename;
                        }

                        if (artifact_dir) {
                            snprintf(output_path, sizeof(output_path), "%s/%s", artifact_dir, fname);
                        } else {
                            snprintf(output_path, sizeof(output_path), "%s", fname);
                        }

                        FILE *f = fopen(output_path, "wb");
                        if (f) {
                            fwrite(decoded, 1, decoded_len, f);
                            fclose(f);

                            // Check magic bytes to determine if executable
                            int is_executable = 0;
                            int is_data = 0;
                            if (decoded_len >= 4) {
                                // Executable formats
                                if (decoded[0] == 0x7F && decoded[1] == 'E' &&
                                    decoded[2] == 'L' && decoded[3] == 'F') {
                                    is_executable = 1;  // ELF
                                } else if (decoded[0] == 'M' && decoded[1] == 'Z') {
                                    is_executable = 1;  // PE/Windows
                                } else if ((decoded[0] == 0xCF && decoded[1] == 0xFA) ||
                                           (decoded[0] == 0xFE && decoded[1] == 0xED)) {
                                    is_executable = 1;  // Mach-O
                                } else if (decoded[0] == '#' && decoded[1] == '!') {
                                    is_executable = 1;  // Shebang script
                                }
                                // Data formats - don't make executable
                                else if (decoded[0] == 0x89 && decoded[1] == 'P' &&
                                         decoded[2] == 'N' && decoded[3] == 'G') {
                                    is_data = 1;  // PNG
                                } else if (decoded[0] == 0xFF && decoded[1] == 0xD8) {
                                    is_data = 1;  // JPEG
                                } else if (decoded[0] == 'G' && decoded[1] == 'I' &&
                                           decoded[2] == 'F' && decoded[3] == '8') {
                                    is_data = 1;  // GIF
                                } else if (decoded[0] == '%' && decoded[1] == 'P' &&
                                           decoded[2] == 'D' && decoded[3] == 'F') {
                                    is_data = 1;  // PDF
                                } else if (decoded[0] == 'P' && decoded[1] == 'K' &&
                                           decoded[2] == 0x03 && decoded[3] == 0x04) {
                                    is_data = 1;  // ZIP
                                } else if (decoded[0] == 0x1F && decoded[1] == 0x8B) {
                                    is_data = 1;  // GZIP
                                } else if (decoded[0] == '{' || decoded[0] == '[') {
                                    is_data = 1;  // JSON
                                } else if (decoded[0] == '<') {
                                    is_data = 1;  // XML/HTML
                                }
                            }

                            // Fall back to extension if magic bytes inconclusive
                            if (!is_executable && !is_data) {
                                const char *ext = strrchr(fname, '.');
                                if (ext) {
                                    is_data = (
                                        // Images
                                        strcmp(ext, ".png") == 0 || strcmp(ext, ".jpg") == 0 ||
                                        strcmp(ext, ".jpeg") == 0 || strcmp(ext, ".gif") == 0 ||
                                        strcmp(ext, ".svg") == 0 || strcmp(ext, ".webp") == 0 ||
                                        strcmp(ext, ".bmp") == 0 || strcmp(ext, ".ico") == 0 ||
                                        strcmp(ext, ".tiff") == 0 || strcmp(ext, ".tif") == 0 ||
                                        strcmp(ext, ".psd") == 0 || strcmp(ext, ".ai") == 0 ||
                                        strcmp(ext, ".eps") == 0 || strcmp(ext, ".raw") == 0 ||
                                        // Documents
                                        strcmp(ext, ".pdf") == 0 || strcmp(ext, ".doc") == 0 ||
                                        strcmp(ext, ".docx") == 0 || strcmp(ext, ".xls") == 0 ||
                                        strcmp(ext, ".xlsx") == 0 || strcmp(ext, ".ppt") == 0 ||
                                        strcmp(ext, ".pptx") == 0 || strcmp(ext, ".odt") == 0 ||
                                        strcmp(ext, ".ods") == 0 || strcmp(ext, ".odp") == 0 ||
                                        strcmp(ext, ".rtf") == 0 || strcmp(ext, ".tex") == 0 ||
                                        // Text/Config
                                        strcmp(ext, ".txt") == 0 || strcmp(ext, ".md") == 0 ||
                                        strcmp(ext, ".rst") == 0 || strcmp(ext, ".log") == 0 ||
                                        strcmp(ext, ".json") == 0 || strcmp(ext, ".xml") == 0 ||
                                        strcmp(ext, ".yaml") == 0 || strcmp(ext, ".yml") == 0 ||
                                        strcmp(ext, ".toml") == 0 || strcmp(ext, ".ini") == 0 ||
                                        strcmp(ext, ".conf") == 0 || strcmp(ext, ".cfg") == 0 ||
                                        strcmp(ext, ".config") == 0 || strcmp(ext, ".env") == 0 ||
                                        strcmp(ext, ".properties") == 0 || strcmp(ext, ".plist") == 0 ||
                                        // Data
                                        strcmp(ext, ".csv") == 0 || strcmp(ext, ".tsv") == 0 ||
                                        strcmp(ext, ".sql") == 0 || strcmp(ext, ".db") == 0 ||
                                        strcmp(ext, ".sqlite") == 0 || strcmp(ext, ".parquet") == 0 ||
                                        strcmp(ext, ".avro") == 0 || strcmp(ext, ".npy") == 0 ||
                                        strcmp(ext, ".npz") == 0 || strcmp(ext, ".pkl") == 0 ||
                                        strcmp(ext, ".pickle") == 0 || strcmp(ext, ".h5") == 0 ||
                                        strcmp(ext, ".hdf5") == 0 ||
                                        // Web
                                        strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0 ||
                                        strcmp(ext, ".css") == 0 || strcmp(ext, ".scss") == 0 ||
                                        strcmp(ext, ".sass") == 0 || strcmp(ext, ".less") == 0 ||
                                        strcmp(ext, ".woff") == 0 || strcmp(ext, ".woff2") == 0 ||
                                        strcmp(ext, ".ttf") == 0 || strcmp(ext, ".otf") == 0 ||
                                        strcmp(ext, ".eot") == 0 ||
                                        // Archives
                                        strcmp(ext, ".zip") == 0 || strcmp(ext, ".tar") == 0 ||
                                        strcmp(ext, ".gz") == 0 || strcmp(ext, ".tgz") == 0 ||
                                        strcmp(ext, ".bz2") == 0 || strcmp(ext, ".xz") == 0 ||
                                        strcmp(ext, ".7z") == 0 || strcmp(ext, ".rar") == 0 ||
                                        strcmp(ext, ".zst") == 0 ||
                                        // Audio
                                        strcmp(ext, ".mp3") == 0 || strcmp(ext, ".wav") == 0 ||
                                        strcmp(ext, ".flac") == 0 || strcmp(ext, ".aac") == 0 ||
                                        strcmp(ext, ".ogg") == 0 || strcmp(ext, ".m4a") == 0 ||
                                        strcmp(ext, ".wma") == 0 || strcmp(ext, ".aiff") == 0 ||
                                        // Video
                                        strcmp(ext, ".mp4") == 0 || strcmp(ext, ".mkv") == 0 ||
                                        strcmp(ext, ".avi") == 0 || strcmp(ext, ".mov") == 0 ||
                                        strcmp(ext, ".wmv") == 0 || strcmp(ext, ".flv") == 0 ||
                                        strcmp(ext, ".webm") == 0 || strcmp(ext, ".m4v") == 0 ||
                                        // 3D/CAD
                                        strcmp(ext, ".obj") == 0 || strcmp(ext, ".stl") == 0 ||
                                        strcmp(ext, ".fbx") == 0 || strcmp(ext, ".gltf") == 0 ||
                                        strcmp(ext, ".glb") == 0 ||
                                        // Misc
                                        strcmp(ext, ".lock") == 0 || strcmp(ext, ".sum") == 0 ||
                                        strcmp(ext, ".map") == 0 || strcmp(ext, ".wasm") == 0
                                    );
                                }
                            }

                            if (is_executable || !is_data) {
                                chmod(output_path, 0755);
                            }
                            fprintf(stderr, "\033[32mArtifact saved: %s (%zu bytes)\033[0m\n", output_path, decoded_len);
                        }
                        if (source_basename) free(source_basename);
                        free(decoded);
                    }
                    free(artifact_data);
                }
                if (artifact_filename) free(artifact_filename);

                // Move to next object
                pos++;
                const char *next_obj = strchr(pos, '{');
                const char *end_arr = strchr(pos, ']');
                if (!next_obj || (end_arr && end_arr < next_obj)) break;
                pos = next_obj;
            }
        }
    }
}

// Get basename from path
const char* get_basename(const char *path) {
    const char *base = strrchr(path, '/');
    return base ? base + 1 : path;
}

// Poll job status with exponential backoff and transient error resilience
// Returns the final response JSON (caller must free), or NULL on error
static char* poll_job_status(const UnsandboxCredentials *creds, const char *job_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    char url[512];
    char path[256];
    snprintf(path, sizeof(path), "/jobs/%s", job_id);
    snprintf(url, sizeof(url), "%s%s", API_BASE, path);

    int poll_count = 0;
    int consecutive_errors = 0;
    int saw_server_errors = 0;
    char *final_response = NULL;

    while (1) {
        // Sleep before polling — use backoff schedule for normal polls,
        // fixed backoff during error recovery
        if (consecutive_errors > 0) {
            usleep(POLL_ERROR_BACKOFF_MS * 1000);
        } else {
            int delay_idx = poll_count < POLL_DELAYS_COUNT ? poll_count : POLL_DELAYS_COUNT - 1;
            usleep(POLL_DELAYS[delay_idx] * 1000);
        }
        poll_count++;

        struct ResponseBuffer response = {0};
        response.data = malloc(1);
        response.size = 0;

        // Regenerate auth headers each poll to keep timestamp fresh
        struct curl_slist *headers = NULL;
        headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

        CURLcode res = curl_easy_perform(curl);
        curl_slist_free_all(headers);

        if (res != CURLE_OK) {
            consecutive_errors++;
            if (consecutive_errors >= POLL_MAX_CONSECUTIVE_ERRORS) {
                fprintf(stderr, "Error: Lost connection to API after %d retries\n", consecutive_errors);
                fprintf(stderr, "Job ID: %s — check later with: un jobs --get %s\n", job_id, job_id);
                free(response.data);
                break;
            }
            fprintf(stderr, "Connection error, retrying... (%d/%d)\n", consecutive_errors, POLL_MAX_CONSECUTIVE_ERRORS);
            free(response.data);
            continue;
        }

        long http_code = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

        if (http_code == 404) {
            consecutive_errors++;
            if (saw_server_errors) {
                // API restarted (502/503 then 404) — job state lost
                fprintf(stderr, "Error: API restarted — job result lost (in-memory job state cleared)\n");
                fprintf(stderr, "The command may have completed on the container.\n");
                fprintf(stderr, "Job ID: %s\n", job_id);
                free(response.data);
                break;
            }
            if (consecutive_errors > 5) {
                fprintf(stderr, "Error: job %s not found\n", job_id);
                free(response.data);
                break;
            }
            // Job might not be registered yet (brief race window)
            free(response.data);
            continue;
        }

        if (http_code >= 500) {
            consecutive_errors++;
            saw_server_errors = 1;
            if (consecutive_errors >= POLL_MAX_CONSECUTIVE_ERRORS) {
                fprintf(stderr, "Error: Server errors after %d retries\n", consecutive_errors);
                fprintf(stderr, "Job ID: %s — check later with: un jobs --get %s\n", job_id, job_id);
                free(response.data);
                break;
            }
            fprintf(stderr, "Server error %ld, retrying... (%d/%d)\n", http_code, consecutive_errors, POLL_MAX_CONSECUTIVE_ERRORS);
            free(response.data);
            continue;
        }

        if (http_code != 200) {
            // 4xx (non-404) — not transient, bail immediately
            fprintf(stderr, "Error: HTTP %ld while polling job\n", http_code);
            free(response.data);
            break;
        }

        // Success — reset error counter
        consecutive_errors = 0;

        // Check status field
        char *status = extract_json_string(response.data, "status");
        if (!status) {
            // No status field - might be final result format
            final_response = response.data;
            break;
        }

        int is_terminal = (strcmp(status, "completed") == 0 ||
                          strcmp(status, "failed") == 0 ||
                          strcmp(status, "timeout") == 0 ||
                          strcmp(status, "cancelled") == 0);
        free(status);

        if (is_terminal) {
            final_response = response.data;
            break;
        }

        // Still running - continue polling
        free(response.data);
    }

    curl_easy_cleanup(curl);
    return final_response;
}

// ============================================================================
// Interactive Shell Support
// ============================================================================

static struct termios orig_termios;
static int shell_running = 0;
static struct lws *shell_wsi = NULL;

// Terminal size
static void get_terminal_size(int *cols, int *rows) {
    struct winsize ws;
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
        *cols = ws.ws_col;
        *rows = ws.ws_row;
    } else {
        *cols = 80;
        *rows = 24;
    }
}

// Raw terminal mode
static void enable_raw_mode(void) {
    tcgetattr(STDIN_FILENO, &orig_termios);
    struct termios raw = orig_termios;
    raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
    raw.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP);
    raw.c_oflag &= ~(OPOST);
    raw.c_cflag |= (CS8);
    raw.c_cc[VMIN] = 0;
    raw.c_cc[VTIME] = 1;
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

static void disable_raw_mode(void) {
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

// Signal handler for terminal resize
static void handle_sigwinch(int sig) {
    (void)sig;
    // Flag set - resize sent in main loop
}

// Signal handler for interrupt
static void handle_sigint(int sig) {
    (void)sig;
    shell_running = 0;
}

// WebSocket shell state
struct shell_state {
    char *session_id;
    int connected;
    unsigned char *send_buf;
    size_t send_len;
    int need_resize;
    int detached;  // 1 if session was detached (can reconnect), 0 if ended
    int need_initial_enter;  // 1 to send Enter after connect (tmux repaint fix)
};

// WebSocket callback for shell
static int shell_ws_callback(struct lws *wsi, enum lws_callback_reasons reason,
                              void *user, void *in, size_t len) {
    struct shell_state *state = (struct shell_state *)user;

    switch (reason) {
    case LWS_CALLBACK_CLIENT_ESTABLISHED:
        state->connected = 1;
        state->need_resize = 1;  // Send initial resize
        lws_callback_on_writable(wsi);
        break;

    case LWS_CALLBACK_CLIENT_RECEIVE:
        if (in && len > 0) {
            // Check if it's a binary frame (raw stdout) or text frame (JSON control)
            if (lws_frame_is_binary(wsi)) {
                // Binary frame = raw shell output, write directly
                if (write(STDOUT_FILENO, in, len) < 0) { /* ignore */ }
            } else {
                // Text frame = JSON control message (exit, error, detached, etc.)
                char *json = (char *)in;
                if (strstr(json, "\"type\":\"exit\"")) {
                    shell_running = 0;
                    state->detached = 0;  // Session ended
                } else if (strstr(json, "\"type\":\"detached\"")) {
                    shell_running = 0;
                    state->detached = 1;  // Detached, can reconnect
                }
            }
        }
        break;

    case LWS_CALLBACK_CLIENT_WRITEABLE:
        if (!state->connected) break;

        // Send resize if needed
        if (state->need_resize) {
            int cols, rows;
            get_terminal_size(&cols, &rows);
            char msg[128];
            int mlen = snprintf(msg, sizeof(msg),
                "{\"type\":\"resize\",\"cols\":%d,\"rows\":%d}", cols, rows);
            unsigned char buf[LWS_PRE + 128];
            memcpy(&buf[LWS_PRE], msg, mlen);
            lws_write(wsi, &buf[LWS_PRE], mlen, LWS_WRITE_TEXT);
            state->need_resize = 0;
            lws_callback_on_writable(wsi);
            break;
        }

        // Send initial Enter to force tmux repaint (fixes blank screen on connect)
        if (state->need_initial_enter) {
            unsigned char buf[LWS_PRE + 1];
            buf[LWS_PRE] = '\n';
            lws_write(wsi, &buf[LWS_PRE], 1, LWS_WRITE_BINARY);
            state->need_initial_enter = 0;
            break;
        }

        // Send stdin data as binary frame (fast path, no JSON encoding)
        if (state->send_buf && state->send_len > 0) {
            unsigned char buf[LWS_PRE + 256];
            memcpy(&buf[LWS_PRE], state->send_buf, state->send_len);
            lws_write(wsi, &buf[LWS_PRE], state->send_len, LWS_WRITE_BINARY);

            free(state->send_buf);
            state->send_buf = NULL;
            state->send_len = 0;
        }
        break;

    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
        fprintf(stderr, "\r\nConnection error: %s\r\n", in ? (char *)in : "unknown");
        shell_running = 0;
        break;

    case LWS_CALLBACK_CLIENT_CLOSED:
        shell_running = 0;
        break;

    default:
        break;
    }
    return 0;
}

static const struct lws_protocols shell_protocols[] = {
    {"unsandbox-shell", shell_ws_callback, sizeof(struct shell_state), 4096, 0, NULL, 0},
    {NULL, NULL, 0, 0, 0, NULL, 0}
};

// Session info returned from create_session
struct SessionInfo {
    char *session_id;
    char *container_name;
};

// Find session ID by container name (for reconnect by container name)
static char* find_session_by_container(const UnsandboxCredentials *creds, const char *container_name) {
    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/sessions", API_BASE);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/sessions", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK || !response.data) {
        free(response.data);
        return NULL;
    }

    // Search for matching container name in sessions array
    const char *sessions_start = strstr(response.data, "\"sessions\":[");
    if (sessions_start) {
        const char *pos = sessions_start + 12;

        while ((pos = strchr(pos, '{')) != NULL) {
            char *container = extract_json_string(pos, "container_name");
            char *session_id = extract_json_string(pos, "id");

            if (container && strcmp(container, container_name) == 0) {
                free(container);
                free(response.data);
                return session_id;  // Found it!
            }

            if (container) free(container);
            if (session_id) free(session_id);

            pos++;
            const char *next_obj = strchr(pos, '{');
            const char *end_arr = strchr(pos, ']');
            if (!next_obj || (end_arr && end_arr < next_obj)) break;
            pos = next_obj;
        }
    }

    free(response.data);
    return NULL;
}

// Forward declaration for sudo challenge handler (defined after destroy_service)
static int handle_sudo_challenge(const char *response_data,
                                  const UnsandboxCredentials *creds,
                                  const char *method, const char *url,
                                  const char *path, const char *body);

// Kill a session by ID or container name
static int kill_session(const UnsandboxCredentials *creds, const char *session_id_or_container) {
    char *session_id = NULL;

    // Check if it looks like a container name or session ID
    // Container names: unsb-vm-*, exec-*, sandbox-* (legacy)
    if (strncmp(session_id_or_container, "unsb-vm-", 8) == 0 ||
        strncmp(session_id_or_container, "exec-", 5) == 0 ||
        strncmp(session_id_or_container, "sandbox-", 8) == 0) {
        // It's a container name - look up the session ID
        fprintf(stderr, "Looking up session for %s...", session_id_or_container);
        fflush(stderr);
        session_id = find_session_by_container(creds, session_id_or_container);
        if (!session_id) {
            fprintf(stderr, " not found\nError: No active session for container '%s'\n", session_id_or_container);
            return 1;
        }
        fprintf(stderr, " found\n");
    } else {
        session_id = strdup(session_id_or_container);
    }

    fprintf(stderr, "Terminating session %s...", session_id);
    fflush(stderr);

    CURL *curl = curl_easy_init();
    if (!curl) {
        free(session_id);
        return 1;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    snprintf(url, sizeof(url), "%s/sessions/%s", API_BASE, session_id);
    snprintf(path, sizeof(path), "/sessions/%s", session_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, " failed\nError: %s\n", curl_easy_strerror(res));
        free(response.data);
        free(session_id);
        return 1;
    }

    if (http_code == 200) {
        fprintf(stderr, " done\n");
        fprintf(stderr, "\033[32mSession terminated successfully\033[0m\n");
    } else if (http_code == 404) {
        fprintf(stderr, " not found\nError: Session not found or already terminated\n");
        free(response.data);
        free(session_id);
        return 1;
    } else if (http_code == 428) {
        fprintf(stderr, "\n");
        int result = handle_sudo_challenge(response.data, creds, "DELETE", url, path, NULL);
        free(response.data);
        free(session_id);
        return result;
    } else {
        fprintf(stderr, " failed\nError: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        free(session_id);
        return 1;
    }

    free(response.data);
    free(session_id);
    return 0;
}

// Freeze a session
static int freeze_session(const UnsandboxCredentials *creds, const char *session_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    snprintf(url, sizeof(url), "%s/sessions/%s/freeze", API_BASE, session_id);
    snprintf(path, sizeof(path), "/sessions/%s/freeze", session_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Session not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 400) {
        // Parse error message from response
        fprintf(stderr, "Error: %s\n", response.data);
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    fprintf(stderr, "\033[32mSession frozen successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Unfreeze a session
static int unfreeze_session(const UnsandboxCredentials *creds, const char *session_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    snprintf(url, sizeof(url), "%s/sessions/%s/unfreeze", API_BASE, session_id);
    snprintf(path, sizeof(path), "/sessions/%s/unfreeze", session_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Session not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 429) {
        // Concurrency limit reached
        fprintf(stderr, "Error: Concurrency limit reached - cannot unfreeze session\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mSession woken successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Boost a session's resources (increase vCPU, memory is derived: vcpu * 2048MB)
static int boost_session(const UnsandboxCredentials *creds, const char *session_id, int vcpu) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    snprintf(url, sizeof(url), "%s/sessions/%s/boost", API_BASE, session_id);
    snprintf(path, sizeof(path), "/sessions/%s/boost", session_id);

    char post_data[256];
    snprintf(post_data, sizeof(post_data), "{\"vcpu\":%d}", vcpu);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, post_data);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Session not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 429) {
        fprintf(stderr, "Error: Not enough concurrency slots to boost (boost consumes additional slots)\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    int memory_mb = vcpu * 2048;
    printf("\033[32mSession boosted to %d vCPU, %d MB RAM\033[0m\n", vcpu, memory_mb);
    free(response.data);
    return 0;
}

// Remove boost from a session (return to base resources)
static int unboost_session(const UnsandboxCredentials *creds, const char *session_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    snprintf(url, sizeof(url), "%s/sessions/%s/unboost", API_BASE, session_id);
    snprintf(path, sizeof(path), "/sessions/%s/unboost", session_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Session not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mSession unboosted, returning to base resources\033[0m\n");
    free(response.data);
    return 0;
}

// List active sessions
static int list_sessions(const UnsandboxCredentials *creds) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/sessions", API_BASE);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/sessions", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Parse and display sessions
    // Format: {"sessions":[...], "count": N}
    if (!response.data || !strstr(response.data, "\"sessions\"")) {
        fprintf(stderr, "Error: Invalid response\n");
        free(response.data);
        return 1;
    }

    // Extract count
    const char *count_str = strstr(response.data, "\"count\":");
    int count = 0;
    if (count_str) {
        count = atoi(count_str + 8);
    }

    if (count == 0) {
        printf("No active sessions\n");
        free(response.data);
        return 0;
    }

    printf("Active sessions: %d\n\n", count);
    printf("%-40s %-20s %-10s %-8s %-10s\n", "SESSION ID", "CONTAINER", "SHELL", "TTL", "STATUS");
    printf("%-40s %-20s %-10s %-8s %-10s\n", "----------------------------------------",
           "--------------------", "----------", "--------", "----------");

    // Parse sessions array - simple parser for [{...}, {...}]
    const char *sessions_start = strstr(response.data, "\"sessions\":[");
    if (sessions_start) {
        const char *pos = sessions_start + 12;

        while ((pos = strchr(pos, '{')) != NULL) {
            char *session_id = extract_json_string(pos, "id");
            char *container = extract_json_string(pos, "container_name");
            char *shell = extract_json_string(pos, "shell");
            char *status = extract_json_string(pos, "status");

            // Extract remaining_ttl (numeric)
            int remaining_ttl = 0;
            const char *ttl_str = strstr(pos, "\"remaining_ttl\":");
            if (ttl_str) {
                remaining_ttl = atoi(ttl_str + 16);
            }

            // Format TTL as human-readable
            char ttl_fmt[16];
            if (remaining_ttl >= 3600) {
                snprintf(ttl_fmt, sizeof(ttl_fmt), "%dh%dm", remaining_ttl / 3600, (remaining_ttl % 3600) / 60);
            } else if (remaining_ttl >= 60) {
                snprintf(ttl_fmt, sizeof(ttl_fmt), "%dm%ds", remaining_ttl / 60, remaining_ttl % 60);
            } else {
                snprintf(ttl_fmt, sizeof(ttl_fmt), "%ds", remaining_ttl);
            }

            printf("%-40s %-20s %-10s %-8s %-10s\n",
                   session_id ? session_id : "-",
                   container ? container : "-",
                   shell ? shell : "bash",
                   ttl_fmt,
                   status ? status : "-");

            if (session_id) free(session_id);
            if (container) free(container);
            if (shell) free(shell);
            if (status) free(status);

            // Move to next object
            pos++;
            const char *next_obj = strchr(pos, '{');
            const char *end_arr = strchr(pos, ']');
            if (!next_obj || (end_arr && end_arr < next_obj)) break;
            pos = next_obj;
        }
    }

    free(response.data);
    return 0;
}

// Get path to languages cache file (~/.unsandbox/languages.json)
static char* get_languages_cache_path(void) {
    const char *home = getenv("HOME");
    if (!home) {
        struct passwd *pw = getpwuid(getuid());
        if (pw) home = pw->pw_dir;
    }
    if (!home) return NULL;

    char *path = malloc(strlen(home) + 32);
    if (!path) return NULL;
    sprintf(path, "%s/.unsandbox/languages.json", home);
    return path;
}

// Load languages from cache if valid (< 1 hour old)
// Returns JSON string with languages array, or NULL if cache invalid/missing
static char* load_languages_cache(void) {
    char *cache_path = get_languages_cache_path();
    if (!cache_path) return NULL;

    struct stat st;
    if (stat(cache_path, &st) != 0) {
        free(cache_path);
        return NULL;
    }

    // Check if cache is fresh (< 1 hour old)
    time_t now = time(NULL);
    if (now - st.st_mtime >= LANGUAGES_CACHE_TTL) {
        free(cache_path);
        return NULL;
    }

    FILE *f = fopen(cache_path, "r");
    free(cache_path);
    if (!f) return NULL;

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);

    if (size <= 0 || size > 1024 * 1024) {  // Sanity check: max 1MB
        fclose(f);
        return NULL;
    }

    char *data = malloc(size + 1);
    if (!data) {
        fclose(f);
        return NULL;
    }

    size_t read = fread(data, 1, size, f);
    fclose(f);
    data[read] = '\0';

    // Verify it contains languages array
    if (!strstr(data, "\"languages\"")) {
        free(data);
        return NULL;
    }

    return data;
}

// Save languages to cache
static void save_languages_cache(const char *json_response) {
    if (!json_response) return;

    char *cache_path = get_languages_cache_path();
    if (!cache_path) return;

    // Ensure ~/.unsandbox directory exists
    char *dir = strdup(cache_path);
    if (dir) {
        char *last_slash = strrchr(dir, '/');
        if (last_slash) {
            *last_slash = '\0';
            mkdir(dir, 0700);
        }
        free(dir);
    }

    FILE *f = fopen(cache_path, "w");
    free(cache_path);
    if (!f) return;

    // Extract just the languages array if present, wrap in standard format
    const char *langs_start = strstr(json_response, "\"languages\":[");
    if (langs_start) {
        const char *arr_start = strchr(langs_start, '[');
        const char *arr_end = strchr(arr_start, ']');
        if (arr_start && arr_end) {
            fprintf(f, "{\"languages\":%.*s,\"timestamp\":%ld}",
                    (int)(arr_end - arr_start + 1), arr_start, (long)time(NULL));
        }
    } else {
        // Try to find raw array
        const char *arr_start = strchr(json_response, '[');
        const char *arr_end = arr_start ? strchr(arr_start, ']') : NULL;
        if (arr_start && arr_end) {
            fprintf(f, "{\"languages\":%.*s,\"timestamp\":%ld}",
                    (int)(arr_end - arr_start + 1), arr_start, (long)time(NULL));
        }
    }

    fclose(f);
}

// List supported languages (CLI helper)
static int list_languages_cli(const UnsandboxCredentials *creds, int json_output) {
    // Try cache first
    char *cached = load_languages_cache();
    if (cached) {
        const char *langs_start = strstr(cached, "\"languages\":[");
        if (!langs_start) langs_start = strchr(cached, '[');

        if (langs_start) {
            const char *arr_start = strchr(langs_start, '[');
            if (arr_start) {
                if (json_output) {
                    const char *arr_end = strchr(arr_start, ']');
                    if (arr_end) {
                        printf("%.*s\n", (int)(arr_end - arr_start + 1), arr_start);
                    }
                } else {
                    const char *p = arr_start + 1;
                    while (*p && *p != ']') {
                        if (*p == '"') {
                            p++;
                            const char *end = strchr(p, '"');
                            if (end) {
                                printf("%.*s\n", (int)(end - p), p);
                                p = end + 1;
                            }
                        } else {
                            p++;
                        }
                    }
                }
                free(cached);
                return 0;
            }
        }
        free(cached);
    }

    // Cache miss or invalid - fetch from API
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/languages", API_BASE);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/languages", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK || http_code != 200) {
        fprintf(stderr, "Error: Failed to fetch languages (HTTP %ld)\n", http_code);
        free(response.data);
        return 1;
    }

    // Find the languages array in JSON: "languages":["lang1","lang2",...] or just ["lang1",...]
    const char *langs_start = strstr(response.data, "\"languages\":[");
    if (!langs_start) {
        langs_start = strchr(response.data, '[');
    }

    if (!langs_start) {
        fprintf(stderr, "Error: Invalid response format\n");
        free(response.data);
        return 1;
    }

    const char *arr_start = strchr(langs_start, '[');
    if (!arr_start) {
        fprintf(stderr, "Error: Invalid response format\n");
        free(response.data);
        return 1;
    }

    if (json_output) {
        // Output as JSON array - find matching ]
        const char *arr_end = strchr(arr_start, ']');
        if (arr_end) {
            int len = arr_end - arr_start + 1;
            printf("%.*s\n", len, arr_start);
        }
    } else {
        // Output one language per line
        const char *p = arr_start + 1;
        while (*p && *p != ']') {
            if (*p == '"') {
                p++;
                const char *end = strchr(p, '"');
                if (end) {
                    printf("%.*s\n", (int)(end - p), p);
                    p = end + 1;
                }
            } else {
                p++;
            }
        }
    }

    // Save to cache for future use
    save_languages_cache(response.data);

    free(response.data);
    return 0;
}

// Create a session via HTTP API
// multiplexer: NULL for no multiplexer (default), "screen", or "tmux"
// input_files: array of files to include (written to /tmp/ in container)
// input_file_count: number of input files
static struct SessionInfo create_session(const UnsandboxCredentials *creds, const char *network_mode, int audit, const char *shell, const char *multiplexer, int vcpu, struct InputFile *input_files, int input_file_count) {
    struct SessionInfo info = {NULL, NULL};
    CURL *curl = curl_easy_init();
    if (!curl) return info;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    if (audit) {
        snprintf(url, sizeof(url), "%s/sessions?audit=1", API_BASE);
    } else {
        snprintf(url, sizeof(url), "%s/sessions", API_BASE);
    }

    // Calculate required payload size
    size_t payload_size = 512;  // Base size for JSON structure
    for (int i = 0; i < input_file_count; i++) {
        payload_size += strlen(input_files[i].content_base64) + 256;
    }

    // Build payload with optional shell and multiplexer
    char *payload = malloc(payload_size);
    if (!payload) {
        curl_easy_cleanup(curl);
        free(response.data);
        return info;
    }
    char *p = payload;
    p += sprintf(p, "{\"network_mode\":\"%s\",\"ttl\":3600",
                 network_mode ? network_mode : "zerotrust");
    if (shell && strlen(shell) > 0) {
        p += sprintf(p, ",\"shell\":\"%s\"", shell);
    }
    if (multiplexer && strlen(multiplexer) > 0) {
        p += sprintf(p, ",\"multiplexer\":\"%s\"", multiplexer);
    }
    if (vcpu > 1) {
        p += sprintf(p, ",\"vcpu\":%d", vcpu);
    }
    // Add input files
    if (input_file_count > 0) {
        p += sprintf(p, ",\"input_files\":[");
        for (int i = 0; i < input_file_count; i++) {
            if (i > 0) *p++ = ',';
            char *esc_filename = escape_json_string(input_files[i].filename);
            p += sprintf(p, "{\"filename\":\"%s\",\"content\":\"%s\"}",
                        esc_filename, input_files[i].content_base64);
            free(esc_filename);
        }
        p += sprintf(p, "]");
    }
    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/sessions", payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    // Get HTTP status code before cleanup
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, " curl error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return info;
    }

    // Extract session_id and container_name from response
    info.session_id = extract_json_string(response.data, "session_id");
    info.container_name = extract_json_string(response.data, "container_name");
    if (!info.session_id && response.data) {
        // Parse error response for user-friendly message
        char *error = extract_json_string(response.data, "error");
        char *message = extract_json_string(response.data, "message");

        if (http_code == 429 && error) {
            if (strcmp(error, "concurrency_limit_reached") == 0) {
                // Parse active/limit for helpful context
                const char *active_str = strstr(response.data, "\"active_executions\":");
                const char *limit_str = strstr(response.data, "\"concurrency_limit\":");
                int active = active_str ? atoi(active_str + 20) : 0;
                int limit = limit_str ? atoi(limit_str + 20) : 1;

                fprintf(stderr, "\n\n");
                fprintf(stderr, "  \033[1;33mSession limit reached\033[0m\n\n");
                fprintf(stderr, "  You have %d of %d concurrent session%s in use.\n",
                        active, limit, limit == 1 ? "" : "s");
                fprintf(stderr, "\n");
                fprintf(stderr, "  To continue, either:\n");
                fprintf(stderr, "    1. Wait for a running session to finish\n");
                fprintf(stderr, "    2. Run '\033[36mun session --list\033[0m' to see active sessions\n");
                fprintf(stderr, "    3. Run '\033[36mun session --attach <id>\033[0m' to reconnect\n");
                fprintf(stderr, "\n");
            } else if (strcmp(error, "rate_limit_exceeded") == 0) {
                fprintf(stderr, "\n\n");
                fprintf(stderr, "  \033[1;33mRate limit exceeded\033[0m\n\n");
                if (message) {
                    fprintf(stderr, "  %s\n", message);
                } else {
                    fprintf(stderr, "  Too many requests. Please wait a moment and try again.\n");
                }
                fprintf(stderr, "\n");
            } else {
                fprintf(stderr, "\n  \033[1;31mError:\033[0m %s\n", message ? message : error);
            }
        } else if (http_code == 401) {
            fprintf(stderr, "\n\n");
            fprintf(stderr, "  \033[1;31mAuthentication failed\033[0m\n\n");
            // Check if it's a timestamp issue
            if (message && (strstr(message, "timestamp") || strstr(message, "Timestamp"))) {
                fprintf(stderr, "  Request timestamp expired (must be within 5 minutes of server time).\n\n");
                fprintf(stderr, "  \033[1;33mYour computer's clock may have drifted.\033[0m\n");
                fprintf(stderr, "  Check your system time and sync with NTP if needed:\n");
                fprintf(stderr, "    Linux:   sudo ntpdate -s time.nist.gov\n");
                fprintf(stderr, "    macOS:   sudo sntp -sS time.apple.com\n");
                fprintf(stderr, "    Windows: w32tm /resync\n");
            } else {
                fprintf(stderr, "  Your API key is invalid or expired.\n");
                fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY to valid keys.\n");
            }
            fprintf(stderr, "\n");
        } else if (error || message) {
            fprintf(stderr, "\n  \033[1;31mError:\033[0m %s\n", message ? message : error);
        } else {
            fprintf(stderr, " HTTP %ld: %s\n", http_code, response.data);
        }

        if (error) free(error);
        if (message) free(message);
    }
    free(payload);
    free(response.data);
    return info;
}

// Terminate a session and optionally save artifacts
// save_artifacts: 1 to save, 0 to discard
// artifact_dir: directory to save artifacts (NULL for current dir)
// container_name: used to postfix artifact filenames (e.g., bash_history-sandbox-abc123)
static void terminate_session(const UnsandboxCredentials *creds, const char *session_id, int save_artifacts, const char *artifact_dir, int audit_history, const char *container_name) {
    CURL *curl = curl_easy_init();
    if (!curl) return;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    snprintf(path, sizeof(path), "/sessions/%s", session_id);
    if (audit_history) {
        // Request audit - server will copy bash history to /tmp/artifacts before termination
        snprintf(url, sizeof(url), "%s%s?audit=1", API_BASE, path);
    } else {
        snprintf(url, sizeof(url), "%s%s", API_BASE, path);
    }

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res == CURLE_OK && response.data && save_artifacts) {
        // Parse artifacts from response with container name for postfixing filenames
        const char *artifacts_start = strstr(response.data, "\"artifacts\":[");
        if (artifacts_start) {
            const char *pos = artifacts_start + 13; // Skip "artifacts":[

            // Process each artifact in array
            while ((pos = strchr(pos, '{')) != NULL) {
                char *artifact_data = extract_json_string(pos, "content_base64");
                char *artifact_filename = extract_json_string(pos, "filename");

                if (artifact_data) {
                    size_t decoded_len;
                    unsigned char *decoded = base64_decode(artifact_data, strlen(artifact_data), &decoded_len);

                    if (decoded) {
                        char output_path[512];
                        const char *fname = artifact_filename ? artifact_filename : "artifact";

                        // Postfix filename with container name if available
                        // e.g., bash_history -> bash_history-sandbox-abc123
                        char postfixed_name[256];
                        if (container_name) {
                            // Find extension if any
                            const char *ext = strrchr(fname, '.');
                            if (ext) {
                                // Has extension: file.ext -> file-container.ext
                                size_t base_len = ext - fname;
                                snprintf(postfixed_name, sizeof(postfixed_name), "%.*s-%s%s",
                                         (int)base_len, fname, container_name, ext);
                            } else {
                                // No extension: file -> file-container
                                snprintf(postfixed_name, sizeof(postfixed_name), "%s-%s", fname, container_name);
                            }
                            fname = postfixed_name;
                        }

                        if (artifact_dir) {
                            snprintf(output_path, sizeof(output_path), "%s/%s", artifact_dir, fname);
                        } else {
                            snprintf(output_path, sizeof(output_path), "%s", fname);
                        }

                        FILE *f = fopen(output_path, "wb");
                        if (f) {
                            fwrite(decoded, 1, decoded_len, f);
                            fclose(f);
                            fprintf(stderr, "\033[32mArtifact saved: %s (%zu bytes)\033[0m\n", output_path, decoded_len);
                        }
                        free(decoded);
                    }
                    free(artifact_data);
                }
                if (artifact_filename) free(artifact_filename);

                // Move to next object
                pos++;
                const char *next_obj = strchr(pos, '{');
                const char *end_arr = strchr(pos, ']');
                if (!next_obj || (end_arr && end_arr < next_obj)) break;
                pos = next_obj;
            }
        }
    }

    free(response.data);
}

// Reconnect to existing session (shared WebSocket setup with shell_command)
static int reconnect_session(const UnsandboxCredentials *creds, const char *session_id_or_container, int save_artifacts, const char *artifact_dir, int audit_history);

// Main shell command
// multiplexer: NULL for no multiplexer (default), "screen", or "tmux"
// input_files: array of files to include in session (written to /tmp/ in container)
// input_file_count: number of input files
static int shell_command(const UnsandboxCredentials *creds, const char *network_mode, int save_artifacts, const char *artifact_dir, int audit_history, const char *shell, const char *multiplexer, int vcpu, struct InputFile *input_files, int input_file_count) {
    // Disable stdout buffering for real-time output
    setvbuf(stdout, NULL, _IONBF, 0);

    // Create session (pass audit flag to enable script recording on server)
    fprintf(stderr, "Connecting to unsandbox...");
    fflush(stderr);
    struct SessionInfo session = create_session(creds, network_mode, audit_history, shell, multiplexer, vcpu, input_files, input_file_count);
    if (!session.session_id) {
        // Detailed error already printed by create_session
        return 1;
    }
    char *session_id = session.session_id;
    char *container_name = session.container_name;
    fprintf(stderr, " done\n");

    // Set up signal handlers
    signal(SIGWINCH, handle_sigwinch);
    signal(SIGINT, handle_sigint);

    // Enable raw terminal mode
    enable_raw_mode();
    atexit(disable_raw_mode);

    // Disable libwebsockets logging (too noisy)
    lws_set_log_level(0, NULL);

    // Create WebSocket context
    struct lws_context_creation_info info;
    memset(&info, 0, sizeof(info));
    info.port = CONTEXT_PORT_NO_LISTEN;
    info.protocols = shell_protocols;
    info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;

    struct lws_context *context = lws_create_context(&info);
    if (!context) {
        fprintf(stderr, "\r\nError: Failed to create WebSocket context\r\n");
        free(session_id);
        return 1;
    }

    // Connect to WebSocket
    struct lws_client_connect_info ccinfo;
    memset(&ccinfo, 0, sizeof(ccinfo));
    ccinfo.context = context;
    ccinfo.address = "api.unsandbox.com";
    ccinfo.port = 443;

    char path[256];
    snprintf(path, sizeof(path), "/sessions/%s/shell", session_id);
    ccinfo.path = path;

    ccinfo.host = ccinfo.address;
    ccinfo.origin = ccinfo.address;
    ccinfo.protocol = shell_protocols[0].name;
    // LCCSCF_IP_LOW_LATENCY sets TCP_NODELAY to disable Nagle's algorithm
    ccinfo.ssl_connection = LCCSCF_USE_SSL | LCCSCF_IP_LOW_LATENCY;

    shell_wsi = lws_client_connect_via_info(&ccinfo);
    if (!shell_wsi) {
        fprintf(stderr, "\r\nError: Failed to connect to WebSocket\r\n");
        lws_context_destroy(context);
        free(session_id);
        return 1;
    }

    // Get user data from wsi
    struct shell_state *state = (struct shell_state *)lws_wsi_user(shell_wsi);
    state->session_id = session_id;
    // Send initial Enter to force tmux/screen repaint (fixes blank screen on connect)
    if (multiplexer && strlen(multiplexer) > 0) {
        state->need_initial_enter = 1;
    }

    shell_running = 1;

    // Main event loop - poll ONLY on stdin, service websocket separately
    while (shell_running) {
        // Poll stdin with very short timeout (1ms)
        struct pollfd pfd;
        pfd.fd = STDIN_FILENO;
        pfd.events = POLLIN;

        if (poll(&pfd, 1, 1) > 0 && (pfd.revents & POLLIN)) {
            char buf[256];
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
            if (n > 0 && state->connected) {
                state->send_buf = malloc(n);
                memcpy(state->send_buf, buf, n);
                state->send_len = n;
                lws_callback_on_writable(shell_wsi);
                // Wake up lws_service if it's sleeping
                lws_cancel_service(context);
            }
        }

        // Service websocket - use NEGATIVE timeout for true non-blocking (lws 3.2+)
        lws_service(context, -1);
    }

    // Cleanup
    disable_raw_mode();
    int was_detached = state->detached;
    lws_context_destroy(context);

    if (was_detached) {
        fprintf(stderr, "\r\n\033[32mSession detached.\033[0m Reconnect with: un session --attach %s\r\n", container_name);
        // Don't terminate - session is still running in multiplexer
    } else {
        fprintf(stderr, "\r\nSession ended.\r\n");
        // Terminate session and collect artifacts (postfix with container name)
        terminate_session(creds, session_id, save_artifacts, artifact_dir, audit_history, container_name);
    }

    // Hint for replaying audit logs (only when session ended, not detached)
    if (audit_history && !was_detached) {
        fprintf(stderr, "\033[33mTip: Replay session with: zcat session.log*.gz | less -R\033[0m\n");
    }
    free(session_id);
    if (container_name) free(container_name);

    return 0;
}

// Reconnect to an existing session by ID or container name
static int reconnect_session(const UnsandboxCredentials *creds, const char *session_id_or_container, int save_artifacts, const char *artifact_dir, int audit_history) {
    // Disable stdout buffering for real-time output
    setvbuf(stdout, NULL, _IONBF, 0);

    char *session_id = NULL;
    char *container_name = NULL;

    // Check if it looks like a container name or session ID
    // Container names: unsb-vm-*, exec-*, sandbox-* (legacy)
    if (strncmp(session_id_or_container, "unsb-vm-", 8) == 0 ||
        strncmp(session_id_or_container, "exec-", 5) == 0 ||
        strncmp(session_id_or_container, "sandbox-", 8) == 0) {
        // It's a container name - look up the session ID
        fprintf(stderr, "Looking up session for %s...", session_id_or_container);
        fflush(stderr);
        session_id = find_session_by_container(creds, session_id_or_container);
        if (!session_id) {
            fprintf(stderr, " not found\nError: No active session for container '%s'\n", session_id_or_container);
            return 1;
        }
        container_name = strdup(session_id_or_container);
        fprintf(stderr, " found\n");
    } else {
        // Assume it's a session ID
        session_id = strdup(session_id_or_container);
    }

    fprintf(stderr, "Reconnecting to session %s...", session_id);
    fflush(stderr);

    // Set up signal handlers
    signal(SIGWINCH, handle_sigwinch);
    signal(SIGINT, handle_sigint);

    // Enable raw terminal mode
    enable_raw_mode();
    atexit(disable_raw_mode);

    // Disable libwebsockets logging
    lws_set_log_level(0, NULL);

    // Create WebSocket context
    struct lws_context_creation_info info;
    memset(&info, 0, sizeof(info));
    info.port = CONTEXT_PORT_NO_LISTEN;
    info.protocols = shell_protocols;
    info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;

    struct lws_context *context = lws_create_context(&info);
    if (!context) {
        fprintf(stderr, " failed\nError: Failed to create WebSocket context\n");
        free(session_id);
        if (container_name) free(container_name);
        return 1;
    }

    // Connect to WebSocket
    struct lws_client_connect_info ccinfo;
    memset(&ccinfo, 0, sizeof(ccinfo));
    ccinfo.context = context;
    ccinfo.address = "api.unsandbox.com";
    ccinfo.port = 443;

    char path[256];
    snprintf(path, sizeof(path), "/sessions/%s/shell", session_id);
    ccinfo.path = path;

    ccinfo.host = ccinfo.address;
    ccinfo.origin = ccinfo.address;
    ccinfo.protocol = shell_protocols[0].name;
    ccinfo.ssl_connection = LCCSCF_USE_SSL | LCCSCF_IP_LOW_LATENCY;

    shell_wsi = lws_client_connect_via_info(&ccinfo);
    if (!shell_wsi) {
        fprintf(stderr, " failed\nError: Failed to connect (session may have expired)\n");
        lws_context_destroy(context);
        free(session_id);
        if (container_name) free(container_name);
        return 1;
    }

    fprintf(stderr, " done\n");

    // Get user data from wsi
    struct shell_state *state = (struct shell_state *)lws_wsi_user(shell_wsi);
    state->session_id = session_id;
    // Always send initial Enter on reconnect (fixes tmux/screen blank screen)
    state->need_initial_enter = 1;

    shell_running = 1;

    // Main event loop
    while (shell_running) {
        struct pollfd pfd;
        pfd.fd = STDIN_FILENO;
        pfd.events = POLLIN;

        if (poll(&pfd, 1, 1) > 0 && (pfd.revents & POLLIN)) {
            char buf[256];
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
            if (n > 0 && state->connected) {
                state->send_buf = malloc(n);
                memcpy(state->send_buf, buf, n);
                state->send_len = n;
                lws_callback_on_writable(shell_wsi);
                lws_cancel_service(context);
            }
        }

        lws_service(context, -1);
    }

    // Cleanup
    disable_raw_mode();
    lws_context_destroy(context);

    fprintf(stderr, "\r\nSession ended.\r\n");

    // Note: We don't terminate the session on reconnect disconnect
    // The session stays alive for future reconnects
    // Only collect artifacts if explicitly requested
    if (save_artifacts || audit_history) {
        terminate_session(creds, session_id, save_artifacts, artifact_dir, audit_history, container_name);
        if (audit_history) {
            fprintf(stderr, "\033[33mTip: Replay session with: zcat session.log*.gz | less -R\033[0m\n");
        }
    } else {
        fprintf(stderr, "\033[33mSession still active. Reconnect with: un session --attach %s\033[0m\n",
                container_name ? container_name : session_id);
    }

    free(session_id);
    if (container_name) free(container_name);

    return 0;
}

// ============================================================================
// End Interactive Shell Support
// ============================================================================

// ============================================================================
// Service Management Support
// ============================================================================

// Get bootstrap logs for a service
// mode: 0 = tail (last 9000 lines), 1 = all logs
static char* get_service_logs(const UnsandboxCredentials *creds, const char *service_id, int all_logs) {
    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    char path[256];
    if (all_logs) {
        snprintf(url, sizeof(url), "%s/services/%s/logs?all=true", API_BASE, service_id);
        snprintf(path, sizeof(path), "/services/%s/logs?all=true", service_id);
    } else {
        snprintf(url, sizeof(url), "%s/services/%s/logs", API_BASE, service_id);
        snprintf(path, sizeof(path), "/services/%s/logs", service_id);
    }

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    // Extract log from response
    char *log = extract_json_string(response.data, "log");
    free(response.data);
    return log;
}

// ============================================================================
// PaaS Logs (fetch and stream production logs from portal)
// ============================================================================

// Log cursor: ~/.unsandbox/log_cursor stores last fetch time as Unix timestamp.
// Used to fetch only new logs since last call (ops semaphore).

static const char *get_log_cursor_path(void) {
    static char path[512];
    const char *home = getenv("HOME");
    if (!home) return NULL;
    snprintf(path, sizeof(path), "%s/.unsandbox/paas_log_cursor", home);
    return path;
}

// Read cursor: returns seconds-ago string like "42s", or NULL if no cursor
static char *read_log_cursor(void) {
    const char *path = get_log_cursor_path();
    if (!path) return NULL;

    FILE *f = fopen(path, "r");
    if (!f) return NULL;

    char buf[64];
    if (!fgets(buf, sizeof(buf), f)) { fclose(f); return NULL; }
    fclose(f);

    long ts = atol(buf);
    if (ts <= 0) return NULL;

    long ago = (long)time(NULL) - ts;
    if (ago < 5) ago = 5;  // minimum 5s to avoid empty results

    static char since_str[32];
    snprintf(since_str, sizeof(since_str), "%lds", ago);
    return since_str;
}

// Write cursor: store current Unix timestamp
static void write_log_cursor(void) {
    const char *path = get_log_cursor_path();
    if (!path) return;

    // Ensure ~/.unsandbox/ exists
    char dir[512];
    const char *home = getenv("HOME");
    if (!home) return;
    snprintf(dir, sizeof(dir), "%s/.unsandbox", home);
    mkdir(dir, 0700);  // ignore error if exists

    FILE *f = fopen(path, "w");
    if (!f) return;
    fprintf(f, "%ld\n", (long)time(NULL));
    fclose(f);
}

// Volatile flag for SIGINT handling during SSE streaming (forward declaration)
static volatile int stream_interrupted = 0;
// Track last received log time for reconnect deduplication
static volatile time_t stream_last_recv_time = 0;

// Streaming write callback — prints SSE data lines to stdout as they arrive
static size_t stream_sse_callback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    // Buffer for partial SSE events across calls
    static char sse_buf[65536];
    static size_t sse_buf_len = 0;
    (void)userp;

    // Append new data to buffer
    size_t space = sizeof(sse_buf) - sse_buf_len - 1;
    size_t copy = realsize < space ? realsize : space;
    memcpy(sse_buf + sse_buf_len, contents, copy);
    sse_buf_len += copy;
    sse_buf[sse_buf_len] = 0;

    // Process complete SSE events (delimited by \n\n)
    char *pos = sse_buf;
    char *event_end;
    while ((event_end = strstr(pos, "\n\n")) != NULL) {
        *event_end = 0;
        // Parse each line in the event
        char *line = pos;
        while (*line) {
            char *nl = strchr(line, '\n');
            if (nl) *nl = 0;

            if (strncmp(line, "data: ", 6) == 0) {
                const char *payload = line + 6;
                if (strcmp(payload, ":keepalive") != 0) {
                    // Try to extract source + line from JSON
                    // Format: {"source":"api","line":"..."}
                    char *source = extract_json_string(payload, "source");
                    char *logline = extract_json_string(payload, "line");
                    if (source && logline) {
                        printf("\033[36m%-12s\033[0m %s\n", source, logline);
                        fflush(stdout);
                        stream_last_recv_time = time(NULL);
                    } else if (logline) {
                        printf("%s\n", logline);
                        fflush(stdout);
                        stream_last_recv_time = time(NULL);
                    } else {
                        // Plain text payload
                        printf("%s\n", payload);
                        fflush(stdout);
                        stream_last_recv_time = time(NULL);
                    }
                    free(source);
                    free(logline);
                }
            }

            if (nl) line = nl + 1;
            else break;
        }
        pos = event_end + 2;
    }

    // Move remaining partial data to front of buffer
    if (pos > sse_buf) {
        sse_buf_len = strlen(pos);
        memmove(sse_buf, pos, sse_buf_len + 1);
    }

    if (stream_interrupted) return 0;
    return realsize;
}

// Fetch PaaS logs (batch) — returns 0 on success
static int fetch_paas_logs(const UnsandboxCredentials *creds,
                           const char *source, int lines,
                           const char *since, const char *grep,
                           int json_output, const char *level) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    // Build path: /logs/{source}
    char path[256];
    if (strcmp(source, "all") == 0)
        snprintf(path, sizeof(path), "/logs/all");
    else
        snprintf(path, sizeof(path), "/logs/%s", source);

    // Build full URL with query params
    char url[1024];
    int off = snprintf(url, sizeof(url), "%s%s?lines=%d", PORTAL_BASE, path, lines);
    if (since && since[0])
        off += snprintf(url + off, sizeof(url) - off, "&since=%s", since);
    if (grep && grep[0])
        off += snprintf(url + off, sizeof(url) - off, "&grep=%s", grep);
    if (level && level[0])
        off += snprintf(url + off, sizeof(url) - off, "&level=%s", level);

    // HMAC signs path only (no query string) — matches server conn.request_path
    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: Network error fetching logs: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        char *error = extract_json_string(response.data, "error");
        fprintf(stderr, "Error: HTTP %ld", http_code);
        if (error) { fprintf(stderr, " — %s", error); free(error); }
        fprintf(stderr, "\n");
        free(response.data);
        return 1;
    }

    // Update cursor on success
    write_log_cursor();

    if (json_output) {
        // Raw JSON output
        printf("%s\n", response.data);
        free(response.data);
        return 0;
    }

    // Check if this is an "all" response (has "sources" key)
    int is_all = (strstr(response.data, "\"sources\"") != NULL);

    if (is_all) {
        // Parse merged lines: "lines":[{"source":"api","line":"..."},...]
        const char *lines_start = strstr(response.data, "\"lines\":[");
        if (!lines_start) {
            fprintf(stderr, "Error: Invalid response format\n");
            free(response.data);
            return 1;
        }
        const char *p = strchr(lines_start, '[');
        if (!p) { free(response.data); return 1; }
        p++; // skip '['

        // Walk through array of objects
        while (*p) {
            // Find next object
            const char *obj_start = strchr(p, '{');
            if (!obj_start) break;

            // Find matching close brace (simple — no nested objects in log entries)
            const char *obj_end = strchr(obj_start, '}');
            if (!obj_end) break;

            // Extract source and line from this object
            size_t obj_len = obj_end - obj_start + 1;
            char *obj = malloc(obj_len + 1);
            memcpy(obj, obj_start, obj_len);
            obj[obj_len] = 0;

            char *src = extract_json_string(obj, "source");
            char *line = extract_json_string(obj, "line");

            if (src && line) {
                printf("\033[36m%-12s\033[0m %s\n", src, line);
            } else if (line) {
                printf("%s\n", line);
            }

            free(src);
            free(line);
            free(obj);
            p = obj_end + 1;
        }
    } else {
        // Single source: "lines":["line1","line2",...]
        const char *lines_start = strstr(response.data, "\"lines\":[");
        if (!lines_start) {
            fprintf(stderr, "Error: Invalid response format\n");
            free(response.data);
            return 1;
        }
        const char *arr_start = strchr(lines_start, '[');
        if (!arr_start) { free(response.data); return 1; }

        // Parse string array
        const char *p = arr_start + 1;
        while (*p && *p != ']') {
            if (*p == '"') {
                p++;
                // Find end of string (handle escaped quotes)
                const char *end = p;
                while (*end && !(*end == '"' && *(end - 1) != '\\')) end++;
                printf("%.*s\n", (int)(end - p), p);
                p = end + 1;
            } else {
                p++;
            }
        }
    }

    free(response.data);
    return 0;
}

static void stream_sigint_handler(int sig) {
    (void)sig;
    stream_interrupted = 1;
}

// Stream PaaS logs (SSE) — blocks until Ctrl+C, auto-reconnects on drop
static int stream_paas_logs(const UnsandboxCredentials *creds,
                            const char *source, const char *grep,
                            const char *level) {
    // Build path: /logs/{source}/stream
    char path[256];
    if (strcmp(source, "all") == 0)
        snprintf(path, sizeof(path), "/logs/all/stream");
    else
        snprintf(path, sizeof(path), "/logs/%s/stream", source);

    // Build URL with optional query params
    char url[1024];
    int off = snprintf(url, sizeof(url), "%s%s", PORTAL_BASE, path);
    char sep = '?';
    if (grep && grep[0]) {
        off += snprintf(url + off, sizeof(url) - off, "%cgrep=%s", sep, grep);
        sep = '&';
    }
    if (level && level[0]) {
        off += snprintf(url + off, sizeof(url) - off, "%clevel=%s", sep, level);
        sep = '&';
    }
    (void)sep;

    // Install SIGINT handler for clean shutdown
    stream_interrupted = 0;
    struct sigaction sa, old_sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = stream_sigint_handler;
    sigaction(SIGINT, &sa, &old_sa);

    fprintf(stderr, "Streaming %s logs... (Ctrl+C to stop)\n", source);

    int reconnect_delay = 2;  // Start at 2s, cap at 30s
    int is_reconnect = 0;
    stream_last_recv_time = 0;

    while (!stream_interrupted) {
        CURL *curl = curl_easy_init();
        if (!curl) { sigaction(SIGINT, &old_sa, NULL); return 1; }

        // Fresh HMAC headers each connection (timestamps expire)
        struct curl_slist *headers = NULL;
        headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

        // On reconnect, add since= to pick up where we left off
        char reconnect_url[2048];
        if (is_reconnect && stream_last_recv_time > 0) {
            int gap = (int)(time(NULL) - stream_last_recv_time) + 5;  // +5s overlap buffer
            if (gap < 10) gap = 10;
            snprintf(reconnect_url, sizeof(reconnect_url), "%s%csince=%ds",
                     url, strchr(url, '?') ? '&' : '?', gap);
        } else {
            snprintf(reconnect_url, sizeof(reconnect_url), "%s", url);
        }

        curl_easy_setopt(curl, CURLOPT_URL, reconnect_url);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, stream_sse_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");
        // Force HTTP/1.1 — SSE streaming breaks with HTTP/2 framing
        curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        // No timeout — stream until interrupted
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
        // Low-speed limit to detect dead connections (10 bytes in 120s accounts for keepalives)
        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 120L);

        time_t connect_time = time(NULL);
        CURLcode res = curl_easy_perform(curl);

        long http_code = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        // Reset backoff if we were connected for >30s (healthy connection)
        if (time(NULL) - connect_time > 30) reconnect_delay = 2;

        if (stream_interrupted) {
            break;
        }

        // Fatal errors — don't reconnect
        if (http_code == 401 || http_code == 403) {
            fprintf(stderr, "Error: HTTP %ld (auth failed)\n", http_code);
            sigaction(SIGINT, &old_sa, NULL);
            return 1;
        }

        // Recoverable: partial file, connection reset, low-speed timeout
        if (res == CURLE_PARTIAL_FILE || res == CURLE_RECV_ERROR ||
            res == CURLE_OPERATION_TIMEDOUT || res == CURLE_GOT_NOTHING ||
            (res == CURLE_OK && http_code == 200)) {
            // Silent reconnect — don't pollute output
            sleep(reconnect_delay);
            if (reconnect_delay < 30) reconnect_delay = reconnect_delay * 2;
            if (reconnect_delay > 30) reconnect_delay = 30;
            is_reconnect = 1;
            continue;
        }

        // Transient HTTP errors (502, 503, 504) — silent reconnect
        if (http_code >= 500) {
            sleep(reconnect_delay);
            if (reconnect_delay < 30) reconnect_delay = reconnect_delay * 2;
            if (reconnect_delay > 30) reconnect_delay = 30;
            is_reconnect = 1;
            continue;
        }

        // Non-recoverable curl error
        if (res != CURLE_OK && res != CURLE_WRITE_ERROR) {
            fprintf(stderr, "Error: Stream error: %s\n", curl_easy_strerror(res));
            sigaction(SIGINT, &old_sa, NULL);
            return 1;
        }

        // CURLE_WRITE_ERROR from our callback returning 0 (shouldn't happen without interrupt)
        break;
    }

    sigaction(SIGINT, &old_sa, NULL);
    write_log_cursor();
    fprintf(stderr, "\nStream stopped.\n");
    return 0;
}

// Create a service via HTTP API
// bootstrap_content: if provided, sent as bootstrap_content (file contents)
// bootstrap: if bootstrap_content is NULL and this starts with http, sent as bootstrap URL
// service_type: optional type for SRV-enabled services (minecraft, mumble, teamspeak, etc.)
// input_files: array of files to include (written to /tmp/ in container)
// input_file_count: number of input files
// golden_image: optional LXD image alias to use instead of default (for testing)
static char* create_service(const UnsandboxCredentials *creds, const char *name, const char *ports, const char *domains, const char *bootstrap, const char *bootstrap_content, const char *network_mode, int vcpu, const char *service_type, struct InputFile *input_files, int input_file_count, const char *golden_image, int unfreeze_on_demand) {
    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/services", API_BASE);

    // Calculate required payload size (bootstrap_content can be large)
    size_t payload_size = 1024;  // Base size for JSON structure
    if (bootstrap_content) {
        payload_size += strlen(bootstrap_content) * 2 + 100;  // Escaped content + field name
    } else if (bootstrap) {
        payload_size += strlen(bootstrap) * 2 + 100;
    }
    if (domains) {
        payload_size += strlen(domains) * 2 + 100;  // Escaped domains + JSON overhead
    }
    // Add space for input files
    for (int i = 0; i < input_file_count; i++) {
        payload_size += strlen(input_files[i].content_base64) + 256;
    }

    // Build payload
    char *payload = malloc(payload_size);
    if (!payload) {
        curl_easy_cleanup(curl);
        return NULL;
    }
    char *p = payload;
    p += sprintf(p, "{");

    if (name && strlen(name) > 0) {
        char *esc_name = escape_json_string(name);
        p += sprintf(p, "\"name\":\"%s\"", esc_name);
        free(esc_name);
    }

    if (ports && strlen(ports) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        p += sprintf(p, "\"ports\":[");
        // Parse comma-separated ports
        char *ports_copy = strdup(ports);
        char *port_token = strtok(ports_copy, ",");
        int first = 1;
        while (port_token) {
            if (!first) p += sprintf(p, ",");
            p += sprintf(p, "%d", atoi(port_token));
            first = 0;
            port_token = strtok(NULL, ",");
        }
        free(ports_copy);
        p += sprintf(p, "]");
    }

    // Add custom_domains as JSON array of strings
    if (domains && strlen(domains) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        p += sprintf(p, "\"custom_domains\":[");
        // Parse comma-separated domains
        char *domains_copy = strdup(domains);
        char *domain_token = strtok(domains_copy, ",");
        int first = 1;
        while (domain_token) {
            if (!first) p += sprintf(p, ",");
            // Trim whitespace from domain
            while (*domain_token == ' ') domain_token++;
            char *end = domain_token + strlen(domain_token) - 1;
            while (end > domain_token && *end == ' ') *end-- = '\0';
            char *esc_domain = escape_json_string(domain_token);
            p += sprintf(p, "\"%s\"", esc_domain);
            free(esc_domain);
            first = 0;
            domain_token = strtok(NULL, ",");
        }
        free(domains_copy);
        p += sprintf(p, "]");
    }

    // Prefer bootstrap_content (file contents), fall back to bootstrap (URL/command)
    if (bootstrap_content && strlen(bootstrap_content) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        char *esc_content = escape_json_string(bootstrap_content);
        p += sprintf(p, "\"bootstrap_content\":\"%s\"", esc_content);
        free(esc_content);
    } else if (bootstrap && strlen(bootstrap) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        char *esc_bootstrap = escape_json_string(bootstrap);
        p += sprintf(p, "\"bootstrap\":\"%s\"", esc_bootstrap);
        free(esc_bootstrap);
    }

    if (network_mode && strlen(network_mode) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        p += sprintf(p, "\"network_mode\":\"%s\"", network_mode);
    }

    if (vcpu > 1) {
        if (p > payload + 1) p += sprintf(p, ",");
        p += sprintf(p, "\"vcpu\":%d", vcpu);
    }

    // Service type for SRV-enabled services (minecraft, mumble, etc.)
    if (service_type && strlen(service_type) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        char *esc_type = escape_json_string(service_type);
        p += sprintf(p, "\"service_type\":\"%s\"", esc_type);
        free(esc_type);
    }

    // Golden image override (for testing with different base images)
    if (golden_image && strlen(golden_image) > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        char *esc_image = escape_json_string(golden_image);
        p += sprintf(p, "\"golden_image\":\"%s\"", esc_image);
        free(esc_image);
    }

    // Add input files
    if (input_file_count > 0) {
        if (p > payload + 1) p += sprintf(p, ",");
        p += sprintf(p, "\"input_files\":[");
        for (int i = 0; i < input_file_count; i++) {
            if (i > 0) *p++ = ',';
            char *esc_filename = escape_json_string(input_files[i].filename);
            p += sprintf(p, "{\"filename\":\"%s\",\"content\":\"%s\"}",
                        esc_filename, input_files[i].content_base64);
            free(esc_filename);
        }
        p += sprintf(p, "]");
    }

    // Unfreeze on demand (auto-wake on HTTP request)
    if (unfreeze_on_demand) {
        if (p > payload + 1) p += sprintf(p, ",");
        p += sprintf(p, "\"unfreeze_on_demand\":true");
    }

    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/services", payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    free(payload);  // Done with payload after curl_easy_perform

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return NULL;
    }

    if (http_code != 200 && http_code != 201) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return NULL;
    }

    // Extract service ID from response (try both "service_id" and "id" for compatibility)
    char *service_id = extract_json_string(response.data, "service_id");
    if (!service_id) {
        service_id = extract_json_string(response.data, "id");
    }
    free(response.data);
    return service_id;
}

// List all services
static int list_services(const UnsandboxCredentials *creds) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/services", API_BASE);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/services", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Parse and display services
    if (!response.data || !strstr(response.data, "\"services\"")) {
        fprintf(stderr, "Error: Invalid response\n");
        free(response.data);
        return 1;
    }

    // Extract count
    const char *count_str = strstr(response.data, "\"count\":");
    int count = 0;
    if (count_str) {
        count = atoi(count_str + 8);
    }

    if (count == 0) {
        printf("No services found\n");
        free(response.data);
        return 0;
    }

    printf("Services: %d\n\n", count);
    printf("%-40s %-20s %-10s %-8s %-15s\n", "SERVICE ID", "NAME", "STATUS", "DISK", "PORTS");
    printf("%-40s %-20s %-10s %-8s %-15s\n", "----------------------------------------",
           "--------------------", "----------", "--------", "---------------");

    // Parse services array
    const char *services_start = strstr(response.data, "\"services\":[");
    if (services_start) {
        const char *pos = services_start + 12;

        while ((pos = strchr(pos, '{')) != NULL) {
            char *service_id = extract_json_string(pos, "id");
            char *name = extract_json_string(pos, "name");
            char *status = extract_json_string(pos, "state");

            // Extract disk_used (bytes as number)
            long long disk_used = extract_json_number(pos, "disk_used");
            char disk_str[16];
            format_bytes(disk_used, disk_str, sizeof(disk_str));

            // Extract ports array
            char ports_str[128] = "-";
            const char *ports_start = strstr(pos, "\"ports\":[");
            if (ports_start) {
                const char *ports_end = strchr(ports_start + 9, ']');
                if (ports_end) {
                    size_t len = ports_end - (ports_start + 9);
                    if (len < sizeof(ports_str) - 1) {
                        strncpy(ports_str, ports_start + 9, len);
                        ports_str[len] = '\0';
                    }
                }
            }

            printf("%-40s %-20s %-10s %-8s %-15s\n",
                   service_id ? service_id : "-",
                   name ? name : "-",
                   status ? status : "-",
                   disk_str,
                   ports_str);

            if (service_id) free(service_id);
            if (name) free(name);
            if (status) free(status);

            // Move to next object by skipping past current object's closing }
            // Need to properly match braces to handle nested objects like port_mappings
            int brace_depth = 1;
            pos++;  // move past opening {
            while (*pos && brace_depth > 0) {
                if (*pos == '{') brace_depth++;
                else if (*pos == '}') brace_depth--;
                else if (*pos == '"') {
                    // Skip strings (may contain { or })
                    pos++;
                    while (*pos && !(*pos == '"' && *(pos-1) != '\\')) pos++;
                }
                pos++;
            }
            // pos now points just past the closing } of current object
            const char *next_obj = strchr(pos, '{');
            const char *end_arr = strchr(pos, ']');
            if (!next_obj || (end_arr && end_arr < next_obj)) break;
            pos = next_obj;
        }
    }

    free(response.data);
    return 0;
}

// Get service info
static int get_service_info(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Extract and display service details
    char *id = extract_json_string(response.data, "id");
    char *name = extract_json_string(response.data, "name");
    char *status = extract_json_string(response.data, "status");
    char *network_mode = extract_json_string(response.data, "network_mode");
    int locked = (int)extract_json_number(response.data, "locked");
    int unfreeze_on_demand = (int)extract_json_number(response.data, "unfreeze_on_demand");

    printf("Service Information:\n");
    printf("  ID:              %s\n", id ? id : "-");
    printf("  Name:            %s\n", name ? name : "-");
    printf("  Status:          %s\n", status ? status : "-");
    printf("  Network Mode:    %s\n", network_mode ? network_mode : "-");
    printf("  Locked:          %s\n", locked ? "yes" : "no");
    printf("  Auto-Unfreeze:   %s\n", unfreeze_on_demand ? "yes" : "no");

    // Extract ports array
    const char *ports_start = strstr(response.data, "\"ports\":[");
    if (ports_start) {
        printf("  Ports:           ");
        const char *pos = ports_start + 9;
        const char *ports_end = strchr(pos, ']');
        if (ports_end) {
            char ports_buf[256];
            size_t len = ports_end - pos;
            if (len < sizeof(ports_buf)) {
                strncpy(ports_buf, pos, len);
                ports_buf[len] = '\0';
                printf("%s\n", ports_buf);
            }
        }
    }

    if (id) free(id);
    if (name) free(name);
    if (status) free(status);
    if (network_mode) free(network_mode);
    free(response.data);
    return 0;
}

// Freeze a service
static int freeze_service(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/freeze", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/freeze", service_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mService frozen successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Unfreeze a service
static int unfreeze_service(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/unfreeze", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/unfreeze", service_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mService unfrozen successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Handle 428 sudo OTP challenge - prompts user for OTP and retries the request
static int handle_sudo_challenge(const char *response_data,
                                  const UnsandboxCredentials *creds,
                                  const char *method, const char *url,
                                  const char *path, const char *body) {
    // Extract challenge_id from response
    char *challenge_id = extract_json_string(response_data, "challenge_id");

    fprintf(stderr, "\033[33mConfirmation required. Check your email for a one-time code.\033[0m\n");
    fprintf(stderr, "Enter OTP: ");

    char otp[32];
    if (!fgets(otp, sizeof(otp), stdin)) {
        fprintf(stderr, "Error: Failed to read OTP\n");
        if (challenge_id) free(challenge_id);
        return 1;
    }
    // Strip newline
    size_t otp_len = strlen(otp);
    if (otp_len > 0 && otp[otp_len - 1] == '\n') otp[otp_len - 1] = '\0';
    otp_len = strlen(otp);
    if (otp_len > 0 && otp[otp_len - 1] == '\r') otp[otp_len - 1] = '\0';

    if (strlen(otp) == 0) {
        fprintf(stderr, "Error: Operation cancelled\n");
        if (challenge_id) free(challenge_id);
        return 1;
    }

    // Retry the request with sudo headers
    CURL *curl = curl_easy_init();
    if (!curl) {
        if (challenge_id) free(challenge_id);
        return 1;
    }

    struct ResponseBuffer retry_response = {0};
    retry_response.data = malloc(1);
    retry_response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, method, path, body);

    // Add sudo headers
    char otp_header[64];
    snprintf(otp_header, sizeof(otp_header), "X-Sudo-OTP: %s", otp);
    headers = curl_slist_append(headers, otp_header);

    if (challenge_id) {
        char challenge_header[256];
        snprintf(challenge_header, sizeof(challenge_header), "X-Sudo-Challenge: %s", challenge_id);
        headers = curl_slist_append(headers, challenge_header);
        free(challenge_id);
    }

    if (body) {
        headers = curl_slist_append(headers, "Content-Type: application/json");
    }

    curl_easy_setopt(curl, CURLOPT_URL, url);
    if (strcmp(method, "DELETE") == 0) {
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    } else if (strcmp(method, "POST") == 0) {
        curl_easy_setopt(curl, CURLOPT_POST, 1L);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body ? body : "");
    }
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &retry_response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(retry_response.data);
        return 1;
    }

    if (http_code >= 200 && http_code < 300) {
        printf("\033[32mOperation completed successfully\033[0m\n");
        free(retry_response.data);
        return 0;
    }

    fprintf(stderr, "Error: HTTP %ld\n", http_code);
    if (retry_response.data) {
        char *error = extract_json_string(retry_response.data, "error");
        if (error) {
            fprintf(stderr, "%s\n", error);
            free(error);
        } else {
            fprintf(stderr, "%s\n", retry_response.data);
        }
    }
    free(retry_response.data);
    return 1;
}

// Destroy a service
static int destroy_service(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 428) {
        int result = handle_sudo_challenge(response.data, creds, "DELETE", url, path, NULL);
        free(response.data);
        return result;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mService destroyed successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Lock a service to prevent deletion
static int lock_service(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/lock", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/lock", service_id);

    const char *body = "{}";
    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mService locked successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Unlock a service to allow deletion
static int unlock_service(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/unlock", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/unlock", service_id);

    const char *body = "{}";
    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 428) {
        int result = handle_sudo_challenge(response.data, creds, "POST", url, path, body);
        free(response.data);
        return result;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mService unlocked successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Update custom domains for a service
// action: "custom_domains" (full replace), "add", or "remove"
// domains: comma-separated domain list
static int update_service_domains(const UnsandboxCredentials *creds, const char *service_id, const char *action, const char *domains) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/domains", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/domains", service_id);

    // Build JSON payload: {"action": ["domain1", "domain2"]}
    size_t payload_size = strlen(domains) * 2 + 256;
    char *payload = malloc(payload_size);
    if (!payload) {
        curl_easy_cleanup(curl);
        return 1;
    }
    char *p = payload;
    p += sprintf(p, "{\"%s\":[", action);

    char *domains_copy = strdup(domains);
    char *token = strtok(domains_copy, ",");
    int first = 1;
    while (token) {
        while (*token == ' ') token++;
        char *end = token + strlen(token) - 1;
        while (end > token && *end == ' ') *end-- = '\0';
        if (!first) p += sprintf(p, ",");
        char *esc = escape_json_string(token);
        p += sprintf(p, "\"%s\"", esc);
        free(esc);
        first = 0;
        token = strtok(NULL, ",");
    }
    free(domains_copy);
    p += sprintf(p, "]}");

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "PUT", path, payload);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(payload);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 403) {
        fprintf(stderr, "Error: Not authorized to modify this service\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mCustom domains updated successfully\033[0m\n");
    // Print resulting domains from response
    if (response.data) {
        // Quick parse for custom_domains array
        char *cd = strstr(response.data, "\"custom_domains\"");
        if (cd) {
            char *start = strchr(cd, '[');
            char *end = start ? strchr(start, ']') : NULL;
            if (start && end) {
                char tmp = *(end + 1);
                *(end + 1) = '\0';
                printf("Domains: %s\n", start);
                *(end + 1) = tmp;
            }
        }
    }
    free(response.data);
    return 0;
}

// Set unfreeze_on_demand for a service
static int set_unfreeze_on_demand(const UnsandboxCredentials *creds, const char *service_id, int enabled) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s", service_id);

    char body[128];
    snprintf(body, sizeof(body), "{\"unfreeze_on_demand\":%s}", enabled ? "true" : "false");

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "PATCH", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mAuto-unfreeze %s\033[0m\n", enabled ? "enabled" : "disabled");
    free(response.data);
    return 0;
}

// Set show_freeze_page for a service (controls whether frozen services show payment page or JSON error)
static int set_show_freeze_page(const UnsandboxCredentials *creds, const char *service_id, int enabled) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s", service_id);

    char body[128];
    snprintf(body, sizeof(body), "{\"show_freeze_page\":%s}", enabled ? "true" : "false");

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "PATCH", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mFreeze page %s\033[0m\n", enabled ? "enabled" : "disabled");
    free(response.data);
    return 0;
}

// Resize a service (change vCPU/memory live)
static int resize_service(const UnsandboxCredentials *creds, const char *service_id, int vcpu) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s", service_id);

    char body[64];
    snprintf(body, sizeof(body), "{\"vcpu\":%d}", vcpu);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "PATCH", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 429) {
        fprintf(stderr, "Error: Cannot resize - would exceed tier concurrency limit\n");
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    if (http_code == 400) {
        fprintf(stderr, "Error: Invalid vcpu value (must be 1-8)\n");
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Parse response to show details
    printf("\033[32mService resized to %d vCPU, %dGB RAM\033[0m\n", vcpu, vcpu * 2);
    if (response.data) {
        printf("Details: %s\n", response.data);
    }
    free(response.data);
    return 0;
}

// ============================================================================
// Environment Secrets Vault Functions
// ============================================================================

// Get environment vault status for a service
// Returns: 0 on success, 1 on error
static int service_env_status(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/env", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/env", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Parse response: {"has_vault": true, "updated_at": 123456, "count": 3}
    // Check for has_vault field
    const char *has_vault_str = strstr(response.data, "\"has_vault\":");
    int has_vault = 0;
    if (has_vault_str) {
        has_vault_str += 12;  // Skip "has_vault":
        while (*has_vault_str == ' ') has_vault_str++;
        has_vault = (strncmp(has_vault_str, "true", 4) == 0);
    }

    if (!has_vault) {
        printf("Vault exists: no\n");
        printf("Variable count: 0\n");
    } else {
        printf("Vault exists: yes\n");

        // Extract count
        long long count = extract_json_number(response.data, "count");
        if (count >= 0) {
            printf("Variable count: %lld\n", count);
        }

        // Extract updated_at
        long long updated_at = extract_json_number(response.data, "updated_at");
        if (updated_at > 0) {
            time_t ts = (time_t)updated_at;
            struct tm *tm_info = localtime(&ts);
            char time_buf[64];
            strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("Last updated: %s\n", time_buf);
        }
    }

    free(response.data);
    return 0;
}

// Set environment vault for a service (PUT /services/:id/env)
// env_content: .env format string (KEY=VALUE\nKEY2=VALUE2\n...)
// Returns: 0 on success, 1 on error
static int service_env_set(const UnsandboxCredentials *creds, const char *service_id, const char *env_content) {
    if (!env_content || strlen(env_content) == 0) {
        fprintf(stderr, "Error: No environment content provided\n");
        return 1;
    }

    if (strlen(env_content) > MAX_ENV_CONTENT_SIZE) {
        fprintf(stderr, "Error: Environment content too large (max %d bytes)\n", MAX_ENV_CONTENT_SIZE);
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/env", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/env", service_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: text/plain");
    headers = add_hmac_auth_headers(headers, creds, "PUT", path, env_content);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, env_content);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Extract count from response
    long long count = extract_json_number(response.data, "count");
    if (count >= 0) {
        printf("\033[32mEnvironment vault updated: %lld variable%s\033[0m\n",
               count, count == 1 ? "" : "s");
    } else {
        printf("\033[32mEnvironment vault updated\033[0m\n");
    }

    // Print note about taking effect
    char *message = extract_json_string(response.data, "message");
    if (message) {
        printf("%s\n", message);
        free(message);
    }

    free(response.data);
    return 0;
}

// Export environment vault for a service (POST /services/:id/env/export)
// HMAC auth proves ownership - returns .env format string
// Returns: 0 on success, 1 on error
static int service_env_export(const UnsandboxCredentials *creds, const char *service_id) {
    // HMAC auth proves ownership - no additional confirmation needed
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/env/export", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/env/export", service_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0L);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found or no vault exists\n");
        free(response.data);
        return 1;
    }

    if (http_code == 401 || http_code == 403) {
        fprintf(stderr, "Error: Not authorized\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Extract env content from response
    char *env_content = extract_json_string(response.data, "env");
    if (env_content) {
        printf("%s", env_content);
        // Ensure trailing newline
        if (strlen(env_content) > 0 && env_content[strlen(env_content) - 1] != '\n') {
            printf("\n");
        }
        free(env_content);
    }

    free(response.data);
    return 0;
}

// Delete environment vault for a service (DELETE /services/:id/env)
// Returns: 0 on success, 1 on error
static int service_env_delete(const UnsandboxCredentials *creds, const char *service_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/env", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/env", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found or no vault exists\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mEnvironment vault deleted\033[0m\n");

    // Print note about taking effect
    char *message = extract_json_string(response.data, "message");
    if (message) {
        printf("%s\n", message);
        free(message);
    }

    free(response.data);
    return 0;
}

// Read .env file contents
// Returns: allocated string with file contents, or NULL on error
static char* read_env_file(const char *filename) {
    FILE *f = fopen(filename, "r");
    if (!f) {
        fprintf(stderr, "Error: Cannot open env file '%s'\n", filename);
        return NULL;
    }

    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    if (fsize > MAX_ENV_CONTENT_SIZE) {
        fprintf(stderr, "Error: Env file too large (max %d bytes)\n", MAX_ENV_CONTENT_SIZE);
        fclose(f);
        return NULL;
    }

    char *content = malloc(fsize + 1);
    if (!content) {
        fprintf(stderr, "Error: Out of memory\n");
        fclose(f);
        return NULL;
    }

    size_t read_size = fread(content, 1, fsize, f);
    content[read_size] = '\0';
    fclose(f);

    return content;
}

// Read env content from stdin until EOF
// Returns: allocated string with content, or NULL on error
static char* read_env_stdin(void) {
    size_t capacity = 4096;
    size_t size = 0;
    char *content = malloc(capacity);
    if (!content) return NULL;

    char buf[1024];
    while (fgets(buf, sizeof(buf), stdin)) {
        size_t len = strlen(buf);
        if (size + len + 1 > capacity) {
            capacity *= 2;
            if (capacity > MAX_ENV_CONTENT_SIZE) {
                fprintf(stderr, "Error: Input too large (max %d bytes)\n", MAX_ENV_CONTENT_SIZE);
                free(content);
                return NULL;
            }
            char *new_content = realloc(content, capacity);
            if (!new_content) {
                free(content);
                return NULL;
            }
            content = new_content;
        }
        memcpy(content + size, buf, len);
        size += len;
    }
    content[size] = '\0';
    return content;
}

// ============================================================================
// End Environment Secrets Vault Functions
// ============================================================================

// Redeploy a service (re-run bootstrap script)
// Bootstrap scripts should be idempotent for proper upgrade behavior
static int redeploy_service(const UnsandboxCredentials *creds, const char *service_id, const char *bootstrap, struct InputFile *input_files, int input_file_count) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/redeploy", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/redeploy", service_id);

    // Check if bootstrap is a file or URL
    char *bootstrap_content = NULL;
    const char *bootstrap_url = NULL;

    if (bootstrap && strlen(bootstrap) > 0) {
        if (strncmp(bootstrap, "http://", 7) == 0 ||
            strncmp(bootstrap, "https://", 8) == 0) {
            bootstrap_url = bootstrap;
        } else {
            // Try to read as file
            struct stat st;
            if (stat(bootstrap, &st) == 0 && S_ISREG(st.st_mode)) {
                size_t fsize;
                bootstrap_content = read_file(bootstrap, &fsize);
                if (!bootstrap_content) {
                    fprintf(stderr, "Error reading bootstrap file: %s\n", bootstrap);
                    curl_easy_cleanup(curl);
                    free(response.data);
                    return 1;
                }
                printf("Read bootstrap script (%zu bytes) from %s\n", fsize, bootstrap);
            } else {
                // Treat as inline command
                bootstrap_url = bootstrap;
            }
        }
    }

    // Calculate required payload size
    size_t payload_size = 256;  // Base size
    if (bootstrap_content) {
        payload_size += strlen(bootstrap_content) * 2 + 100;
    } else if (bootstrap_url) {
        payload_size += strlen(bootstrap_url) * 2 + 100;
    }
    for (int i = 0; i < input_file_count; i++) {
        payload_size += strlen(input_files[i].content_base64) + 256;
    }

    // Build JSON payload manually (matching create_service pattern)
    char *payload = malloc(payload_size);
    if (!payload) {
        if (bootstrap_content) free(bootstrap_content);
        curl_easy_cleanup(curl);
        free(response.data);
        return 1;
    }
    char *p = payload;
    p += sprintf(p, "{");

    int has_field = 0;
    if (bootstrap_content) {
        char *esc_content = escape_json_string(bootstrap_content);
        p += sprintf(p, "\"bootstrap_content\":\"%s\"", esc_content);
        free(esc_content);
        free(bootstrap_content);
        has_field = 1;
    } else if (bootstrap_url) {
        char *esc_url = escape_json_string(bootstrap_url);
        p += sprintf(p, "\"bootstrap\":\"%s\"", esc_url);
        free(esc_url);
        has_field = 1;
    }

    if (input_file_count > 0) {
        if (has_field) p += sprintf(p, ",");
        p += sprintf(p, "\"input_files\":[");
        for (int i = 0; i < input_file_count; i++) {
            if (i > 0) p += sprintf(p, ",");
            char *esc_filename = escape_json_string(input_files[i].filename);
            p += sprintf(p, "{\"filename\":\"%s\",\"content\":\"%s\"}",
                        esc_filename, input_files[i].content_base64);
            free(esc_filename);
        }
        p += sprintf(p, "]");
    }

    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    printf("Redeploying service '%s'...\n", service_id);
    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(payload);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 400) {
        fprintf(stderr, "Error: No bootstrap script provided. Use --bootstrap option.\n");
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mRedeploy initiated successfully\033[0m\n");
    printf("Note: Bootstrap scripts should be idempotent for proper upgrade behavior.\n");
    printf("Use 'un service --logs %s' to check progress.\n", service_id);
    free(response.data);
    return 0;
}

// Execute a command in a running service container
// Uses async job polling for long-running commands
// Optional input_files: files to upload before executing (written to /tmp/input/)
static int execute_service(const UnsandboxCredentials *creds, const char *service_id, const char *command, int timeout_ms, struct InputFile *input_files, int input_file_count) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/execute", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/execute", service_id);

    // Build JSON payload
    char *esc_command = escape_json_string(command);
    if (!esc_command) {
        curl_easy_cleanup(curl);
        free(response.data);
        return 1;
    }

    // Calculate payload size (base + files)
    size_t payload_size = 8192;
    for (int i = 0; i < input_file_count; i++) {
        payload_size += strlen(input_files[i].content_base64) + 256;
    }

    char *payload = malloc(payload_size);
    if (!payload) {
        free(esc_command);
        curl_easy_cleanup(curl);
        free(response.data);
        return 1;
    }

    char *p = payload;
    p += sprintf(p, "{\"command\":\"%s\",\"timeout\":%d", esc_command, timeout_ms);
    free(esc_command);

    // Add input files if provided
    if (input_file_count > 0) {
        p += sprintf(p, ",\"input_files\":[");
        for (int i = 0; i < input_file_count; i++) {
            if (i > 0) *p++ = ',';
            char *esc_filename = escape_json_string(input_files[i].filename);
            p += sprintf(p, "{\"filename\":\"%s\",\"content\":\"%s\"}",
                        esc_filename, input_files[i].content_base64);
            free(esc_filename);
        }
        p += sprintf(p, "]");
    }
    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(payload);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 409) {
        fprintf(stderr, "Error: Service is not running. Unfreeze it first with --unfreeze\n");
        free(response.data);
        return 1;
    }

    if (http_code != 202) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Extract job_id from response
    char *job_id = extract_json_string(response.data, "job_id");
    free(response.data);

    if (!job_id) {
        fprintf(stderr, "Error: No job_id in response\n");
        return 1;
    }

    fprintf(stderr, "job %s\n", job_id);

    // Poll for job completion using shared resilient poller
    char *final_response = poll_job_status(creds, job_id);

    if (!final_response) {
        free(job_id);
        return 1;
    }

    // Check terminal status
    char *status = extract_json_string(final_response, "status");
    int ret = 0;

    if (status && (strcmp(status, "failed") == 0 || strcmp(status, "cancelled") == 0)) {
        char *error = extract_json_string(final_response, "error");
        fprintf(stderr, "Error: Job %s: %s\n", status, error ? error : "unknown");
        if (error) free(error);
        ret = 1;
    } else {
        parse_and_print_response(final_response, 0, NULL, NULL);
    }

    if (status) free(status);
    free(final_response);
    free(job_id);
    return ret;
}

// Execute a command in a service and capture output (returns malloc'd string or NULL)
static char* execute_service_capture(const UnsandboxCredentials *creds, const char *service_id, const char *command, int timeout_ms) {
    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[256];
    char url[512];
    snprintf(path, sizeof(path), "/services/%s/execute", service_id);
    snprintf(url, sizeof(url), "%s%s", API_BASE, path);

    char *esc_command = escape_json_string(command);
    if (!esc_command) {
        curl_easy_cleanup(curl);
        free(response.data);
        return NULL;
    }

    char payload[8192];
    snprintf(payload, sizeof(payload), "{\"command\":\"%s\",\"timeout\":%d}", esc_command, timeout_ms);
    free(esc_command);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK || http_code != 202) {
        if (http_code == 409) {
            fprintf(stderr, "\033[34mError: Instance is not running\n\033[0m");
        }
        free(response.data);
        return NULL;
    }

    char *job_id = extract_json_string(response.data, "job_id");
    free(response.data);

    if (!job_id) return NULL;

    char job_path[256];
    char job_url[512];
    snprintf(job_path, sizeof(job_path), "/jobs/%s", job_id);
    snprintf(job_url, sizeof(job_url), "%s%s", API_BASE, job_path);

    int poll_count = 0;
    int max_polls = (timeout_ms / 1000) + 10;

    while (poll_count < max_polls) {
        usleep(500000);
        poll_count++;

        curl = curl_easy_init();
        if (!curl) {
            free(job_id);
            return NULL;
        }

        struct ResponseBuffer job_response = {0};
        job_response.data = malloc(1);
        job_response.size = 0;

        // Regenerate auth headers each poll to keep timestamp fresh
        headers = NULL;
        headers = add_hmac_auth_headers(headers, creds, "GET", job_path, NULL);

        curl_easy_setopt(curl, CURLOPT_URL, job_url);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &job_response);

        res = curl_easy_perform(curl);
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        if (res != CURLE_OK || http_code != 200) {
            free(job_response.data);
            continue;
        }

        char *status = extract_json_string(job_response.data, "status");
        if (status && strcmp(status, "completed") == 0) {
            // Extract stdout from result
            char *output = extract_json_string(job_response.data, "stdout");
            free(status);
            free(job_response.data);
            free(job_id);
            return output;  // Caller must free
        }

        if (status && (strcmp(status, "failed") == 0 || strcmp(status, "cancelled") == 0)) {
            free(status);
            free(job_response.data);
            free(job_id);
            return NULL;
        }

        if (status) free(status);
        free(job_response.data);
    }

    free(job_id);
    return NULL;
}

// ============================================================================
// Snapshot Management Support
// ============================================================================

// List all snapshots
static int list_snapshots(const UnsandboxCredentials *creds) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/snapshots", API_BASE);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/snapshots", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 403) {
        fprintf(stderr, "Error: Snapshots not available for free tier\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Parse and display snapshots
    if (!response.data || !strstr(response.data, "\"snapshots\"")) {
        fprintf(stderr, "Error: Invalid response\n");
        free(response.data);
        return 1;
    }

    // Extract count
    const char *count_str = strstr(response.data, "\"count\":");
    int count = 0;
    if (count_str) {
        count = atoi(count_str + 8);
    }

    if (count == 0) {
        printf("No snapshots found\n");
        free(response.data);
        return 0;
    }

    printf("Snapshots: %d\n\n", count);
    printf("%-40s %-20s %-12s %-30s %-8s\n", "SNAPSHOT ID", "NAME", "SOURCE TYPE", "SOURCE ID", "SIZE");
    printf("%-40s %-20s %-12s %-30s %-8s\n", "----------------------------------------",
           "--------------------", "------------", "------------------------------", "--------");

    // Parse snapshots array
    const char *snapshots_start = strstr(response.data, "\"snapshots\":[");
    if (snapshots_start) {
        const char *pos = snapshots_start + 13;

        while ((pos = strchr(pos, '{')) != NULL) {
            char *snapshot_id = extract_json_string(pos, "id");
            char *name = extract_json_string(pos, "name");
            char *source_type = extract_json_string(pos, "source_type");
            char *source_id = extract_json_string(pos, "source_id");
            long long size_bytes = extract_json_number(pos, "size_bytes");

            char size_str[16];
            format_bytes(size_bytes, size_str, sizeof(size_str));

            printf("%-40s %-20s %-12s %-30s %-8s\n",
                   snapshot_id ? snapshot_id : "-",
                   name ? name : "-",
                   source_type ? source_type : "-",
                   source_id ? source_id : "-",
                   size_str);

            if (snapshot_id) free(snapshot_id);
            if (name) free(name);
            if (source_type) free(source_type);
            if (source_id) free(source_id);

            // Move to next object
            int brace_depth = 1;
            pos++;
            while (*pos && brace_depth > 0) {
                if (*pos == '{') brace_depth++;
                else if (*pos == '}') brace_depth--;
                else if (*pos == '"') {
                    pos++;
                    while (*pos && !(*pos == '"' && *(pos-1) != '\\')) pos++;
                }
                pos++;
            }
            const char *next_obj = strchr(pos, '{');
            const char *end_arr = strchr(pos, ']');
            if (!next_obj || (end_arr && end_arr < next_obj)) break;
            pos = next_obj;
        }
    }

    free(response.data);
    return 0;
}

// Get snapshot info
static int get_snapshot_info(const UnsandboxCredentials *creds, const char *snapshot_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/snapshots/%s", API_BASE, snapshot_id);

    char path[256];
    snprintf(path, sizeof(path), "/snapshots/%s", snapshot_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Snapshot not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    // Parse and display snapshot info
    char *id = extract_json_string(response.data, "id");
    char *name = extract_json_string(response.data, "name");
    char *source_type = extract_json_string(response.data, "source_type");
    char *source_id = extract_json_string(response.data, "source_id");
    char *container_name = extract_json_string(response.data, "container_name");
    char *status = extract_json_string(response.data, "status");
    long long size_bytes = extract_json_number(response.data, "size_bytes");
    long long created_at = extract_json_number(response.data, "created_at");

    char size_str[16];
    format_bytes(size_bytes, size_str, sizeof(size_str));

    printf("\033[1mSnapshot Details\033[0m\n\n");
    printf("%-20s %s\n", "Snapshot ID:", id ? id : "-");
    printf("%-20s %s\n", "Name:", name ? name : "-");
    printf("%-20s %s\n", "Source Type:", source_type ? source_type : "-");
    printf("%-20s %s\n", "Source ID:", source_id ? source_id : "-");
    printf("%-20s %s\n", "Container:", container_name ? container_name : "-");
    printf("%-20s %s\n", "Size:", size_str);
    printf("%-20s %s\n", "Status:", status ? status : "-");

    if (created_at > 0) {
        time_t t = (time_t)created_at;
        struct tm *tm_info = localtime(&t);
        char time_buf[64];
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("%-20s %s\n", "Created:", time_buf);
    }

    if (id) free(id);
    if (name) free(name);
    if (source_type) free(source_type);
    if (source_id) free(source_id);
    if (container_name) free(container_name);
    if (status) free(status);

    free(response.data);
    return 0;
}

// Delete a snapshot
static int delete_snapshot(const UnsandboxCredentials *creds, const char *snapshot_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/snapshots/%s", API_BASE, snapshot_id);

    char path[256];
    snprintf(path, sizeof(path), "/snapshots/%s", snapshot_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Snapshot not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 428) {
        int result = handle_sudo_challenge(response.data, creds, "DELETE", url, path, NULL);
        free(response.data);
        return result;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mSnapshot deleted successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Lock a snapshot to prevent deletion
static int lock_snapshot(const UnsandboxCredentials *creds, const char *snapshot_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/snapshots/%s/lock", API_BASE, snapshot_id);

    char path[256];
    snprintf(path, sizeof(path), "/snapshots/%s/lock", snapshot_id);

    const char *body = "{}";
    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Snapshot not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mSnapshot locked successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Unlock a snapshot to allow deletion
static int unlock_snapshot(const UnsandboxCredentials *creds, const char *snapshot_id) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/snapshots/%s/unlock", API_BASE, snapshot_id);

    char path[256];
    snprintf(path, sizeof(path), "/snapshots/%s/unlock", snapshot_id);

    const char *body = "{}";
    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, "Error: Snapshot not found\n");
        free(response.data);
        return 1;
    }

    if (http_code == 428) {
        int result = handle_sudo_challenge(response.data, creds, "POST", url, path, body);
        free(response.data);
        return result;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    printf("\033[32mSnapshot unlocked successfully\033[0m\n");
    free(response.data);
    return 0;
}

// Create snapshot of a session
static int create_session_snapshot(const UnsandboxCredentials *creds, const char *session_id, const char *name, int hot) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/sessions/%s/snapshot", API_BASE, session_id);

    char path[256];
    snprintf(path, sizeof(path), "/sessions/%s/snapshot", session_id);

    char payload[1024];
    if (name && strlen(name) > 0) {
        char *esc_name = escape_json_string(name);
        snprintf(payload, sizeof(payload), "{\"name\":\"%s\",\"hot\":%s}", esc_name, hot ? "true" : "false");
        free(esc_name);
    } else {
        snprintf(payload, sizeof(payload), "{\"hot\":%s}", hot ? "true" : "false");
    }

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    fprintf(stderr, "Creating snapshot of session %s...", session_id);
    fflush(stderr);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 403) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Snapshots not available for free tier\n");
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Session not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200 && http_code != 201) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    fprintf(stderr, " done\n");

    char *snapshot_id = extract_json_string(response.data, "id");
    if (snapshot_id) {
        printf("\033[32mSnapshot created successfully\033[0m\n");
        printf("Snapshot ID: %s\n", snapshot_id);
        free(snapshot_id);
    }

    free(response.data);
    return 0;
}

// Create snapshot of a service
static int create_service_snapshot(const UnsandboxCredentials *creds, const char *service_id, const char *name, int hot) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/services/%s/snapshot", API_BASE, service_id);

    char path[256];
    snprintf(path, sizeof(path), "/services/%s/snapshot", service_id);

    char payload[1024];
    if (name && strlen(name) > 0) {
        char *esc_name = escape_json_string(name);
        snprintf(payload, sizeof(payload), "{\"name\":\"%s\",\"hot\":%s}", esc_name, hot ? "true" : "false");
        free(esc_name);
    } else {
        snprintf(payload, sizeof(payload), "{\"hot\":%s}", hot ? "true" : "false");
    }

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    fprintf(stderr, "Creating snapshot of service %s...", service_id);
    fflush(stderr);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 403) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Snapshots not available for free tier\n");
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Service not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200 && http_code != 201) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    fprintf(stderr, " done\n");

    char *snapshot_id = extract_json_string(response.data, "id");
    if (snapshot_id) {
        printf("\033[32mSnapshot created successfully\033[0m\n");
        printf("Snapshot ID: %s\n", snapshot_id);
        free(snapshot_id);
    }

    free(response.data);
    return 0;
}

// Restore session from snapshot
static int restore_from_snapshot(const UnsandboxCredentials *creds, const char *snapshot_id, const char *type) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/snapshots/%s/restore", API_BASE, snapshot_id);

    char path[256];
    snprintf(path, sizeof(path), "/snapshots/%s/restore", snapshot_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    fprintf(stderr, "Restoring %s from snapshot %s...", type, snapshot_id);
    fflush(stderr);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Snapshot not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    fprintf(stderr, " done\n");
    printf("\033[32m%s restored from snapshot\033[0m\n", type);

    free(response.data);
    return 0;
}

// Clone from snapshot to create new session or service
static int clone_snapshot(const UnsandboxCredentials *creds, const char *snapshot_id, const char *clone_type,
                         const char *name, const char *shell, const char *ports) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[512];
    snprintf(url, sizeof(url), "%s/snapshots/%s/clone", API_BASE, snapshot_id);

    char path[256];
    snprintf(path, sizeof(path), "/snapshots/%s/clone", snapshot_id);

    // Build payload
    char payload[2048];
    char *p = payload;
    p += sprintf(p, "{\"type\":\"%s\"", clone_type);

    if (name && strlen(name) > 0) {
        char *esc = escape_json_string(name);
        p += sprintf(p, ",\"name\":\"%s\"", esc);
        free(esc);
    }

    if (shell && strlen(shell) > 0) {
        char *esc = escape_json_string(shell);
        p += sprintf(p, ",\"shell\":\"%s\"", esc);
        free(esc);
    }

    if (ports && strlen(ports) > 0) {
        // Parse comma-separated ports into array
        p += sprintf(p, ",\"ports\":[");
        char *ports_copy = strdup(ports);
        char *tok = strtok(ports_copy, ",");
        int first = 1;
        while (tok) {
            if (!first) p += sprintf(p, ",");
            p += sprintf(p, "%d", atoi(tok));
            first = 0;
            tok = strtok(NULL, ",");
        }
        free(ports_copy);
        p += sprintf(p, "]");
    }

    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    fprintf(stderr, "Cloning snapshot %s to create new %s...", snapshot_id, clone_type);
    fflush(stderr);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 403) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Snapshots not available for free tier\n");
        free(response.data);
        return 1;
    }

    if (http_code == 404) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: Snapshot not found\n");
        free(response.data);
        return 1;
    }

    if (http_code != 200 && http_code != 201) {
        fprintf(stderr, " failed\n");
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    fprintf(stderr, " done\n");

    if (strcmp(clone_type, "session") == 0) {
        char *session_id = extract_json_string(response.data, "session_id");
        if (session_id) {
            printf("\033[32mSession created from snapshot\033[0m\n");
            printf("Session ID: %s\n", session_id);
            free(session_id);
        }
    } else {
        char *service_id = extract_json_string(response.data, "service_id");
        if (service_id) {
            printf("\033[32mService created from snapshot\033[0m\n");
            printf("Service ID: %s\n", service_id);
            free(service_id);
        }
    }

    free(response.data);
    return 0;
}

// ============================================================================
// End Snapshot Management Support
// ============================================================================

// ============================================================================
// End Service Management Support
// ============================================================================

// ============================================================================
// LXD Container Images API
// ============================================================================

// Publish an image from a service or snapshot
static char* image_publish(const UnsandboxCredentials *creds, const char *source_type,
                           const char *source_id, const char *name, const char *description) {
    if (!creds || !creds->public_key || !creds->secret_key || !source_type || !source_id) {
        return NULL;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/images", API_BASE);

    // Build JSON body
    char body[2048];
    char *p = body;
    p += sprintf(p, "{\"source_type\":\"%s\",\"source_id\":\"%s\"", source_type, source_id);
    if (name && strlen(name) > 0) {
        char *esc = escape_json_string(name);
        p += sprintf(p, ",\"name\":\"%s\"", esc);
        free(esc);
    }
    if (description && strlen(description) > 0) {
        char *esc = escape_json_string(description);
        p += sprintf(p, ",\"description\":\"%s\"", esc);
        free(esc);
    }
    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/images", body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    return response.data;
}

// List images (filter_type can be NULL, "owned", "shared", or "public")
static char* list_images(const UnsandboxCredentials *creds, const char *filter_type) {
    if (!creds || !creds->public_key || !creds->secret_key) {
        return NULL;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    if (filter_type && strlen(filter_type) > 0) {
        snprintf(url, sizeof(url), "%s/images/%s", API_BASE, filter_type);
        snprintf(path, sizeof(path), "/images/%s", filter_type);
    } else {
        snprintf(url, sizeof(url), "%s/images", API_BASE);
        snprintf(path, sizeof(path), "/images");
    }

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    return response.data;
}

// Get image details
static char* get_image(const UnsandboxCredentials *creds, const char *image_id) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return NULL;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s", image_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    return response.data;
}

// Delete an image
static int delete_image(const UnsandboxCredentials *creds, const char *image_id) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s", image_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 428) {
        int result = handle_sudo_challenge(response.data, creds, "DELETE", url, path, NULL);
        free(response.data);
        return result;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    free(response.data);
    return 0;
}

// Lock an image
static int lock_image(const UnsandboxCredentials *creds, const char *image_id) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/lock", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/lock", image_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    free(response.data);
    return (res == CURLE_OK) ? 0 : 1;
}

// Unlock an image
static int unlock_image(const UnsandboxCredentials *creds, const char *image_id) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/unlock", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/unlock", image_id);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, "{}");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{}");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code == 428) {
        int result = handle_sudo_challenge(response.data, creds, "POST", url, path, "{}");
        free(response.data);
        return result;
    }

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) fprintf(stderr, "%s\n", response.data);
        free(response.data);
        return 1;
    }

    free(response.data);
    return 0;
}

// Set image visibility (private, unlisted, public)
static int set_image_visibility(const UnsandboxCredentials *creds, const char *image_id, const char *visibility) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id || !visibility) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/visibility", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/visibility", image_id);

    char body[128];
    snprintf(body, sizeof(body), "{\"visibility\":\"%s\"}", visibility);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    free(response.data);
    return (res == CURLE_OK) ? 0 : 1;
}

// Grant image access to another API key
__attribute__((unused))
static int grant_image_access(const UnsandboxCredentials *creds, const char *image_id, const char *trusted_api_key) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id || !trusted_api_key) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/grant", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/grant", image_id);

    char body[256];
    snprintf(body, sizeof(body), "{\"trusted_api_key\":\"%s\"}", trusted_api_key);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    free(response.data);
    return (res == CURLE_OK) ? 0 : 1;
}

// Revoke image access from another API key
__attribute__((unused))
static int revoke_image_access(const UnsandboxCredentials *creds, const char *image_id, const char *trusted_api_key) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id || !trusted_api_key) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/revoke", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/revoke", image_id);

    char body[256];
    snprintf(body, sizeof(body), "{\"trusted_api_key\":\"%s\"}", trusted_api_key);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    free(response.data);
    return (res == CURLE_OK) ? 0 : 1;
}

// List API keys with access to an image
__attribute__((unused))
static char* list_image_trusted(const UnsandboxCredentials *creds, const char *image_id) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return NULL;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/trusted", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/trusted", image_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    return response.data;
}

// Transfer image ownership to another API key
__attribute__((unused))
static int transfer_image(const UnsandboxCredentials *creds, const char *image_id, const char *to_api_key) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id || !to_api_key) {
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/transfer", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/transfer", image_id);

    char body[256];
    snprintf(body, sizeof(body), "{\"to_api_key\":\"%s\"}", to_api_key);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    free(response.data);
    return (res == CURLE_OK) ? 0 : 1;
}

// Spawn a new service from an image
static char* spawn_from_image(const UnsandboxCredentials *creds, const char *image_id,
                              const char *name, const char *ports, const char *bootstrap,
                              const char *network_mode) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return NULL;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/spawn", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/spawn", image_id);

    // Build JSON body
    char body[4096];
    char *p = body;
    p += sprintf(p, "{");
    int need_comma = 0;

    if (name && strlen(name) > 0) {
        char *esc = escape_json_string(name);
        p += sprintf(p, "\"name\":\"%s\"", esc);
        free(esc);
        need_comma = 1;
    }
    if (ports && strlen(ports) > 0) {
        if (need_comma) p += sprintf(p, ",");
        p += sprintf(p, "\"ports\":\"%s\"", ports);
        need_comma = 1;
    }
    if (bootstrap && strlen(bootstrap) > 0) {
        if (need_comma) p += sprintf(p, ",");
        char *esc = escape_json_string(bootstrap);
        p += sprintf(p, "\"bootstrap\":\"%s\"", esc);
        free(esc);
        need_comma = 1;
    }
    if (network_mode && strlen(network_mode) > 0) {
        if (need_comma) p += sprintf(p, ",");
        p += sprintf(p, "\"network_mode\":\"%s\"", network_mode);
    }
    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    return response.data;
}

// Clone an image to create a copy owned by the current user
static char* clone_image(const UnsandboxCredentials *creds, const char *image_id,
                         const char *name, const char *description) {
    if (!creds || !creds->public_key || !creds->secret_key || !image_id) {
        return NULL;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return NULL;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    char path[128];
    snprintf(url, sizeof(url), "%s/images/%s/clone", API_BASE, image_id);
    snprintf(path, sizeof(path), "/images/%s/clone", image_id);

    // Build JSON body
    char body[2048];
    char *p = body;
    p += sprintf(p, "{");
    int need_comma = 0;

    if (name && strlen(name) > 0) {
        char *esc = escape_json_string(name);
        p += sprintf(p, "\"name\":\"%s\"", esc);
        free(esc);
        need_comma = 1;
    }
    if (description && strlen(description) > 0) {
        if (need_comma) p += sprintf(p, ",");
        char *esc = escape_json_string(description);
        p += sprintf(p, "\"description\":\"%s\"", esc);
        free(esc);
    }
    p += sprintf(p, "}");

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, body);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        free(response.data);
        return NULL;
    }

    return response.data;
}

// ============================================================================
// End LXD Container Images API
// ============================================================================

// ============================================================================
// Key Validation Support
// ============================================================================

static int validate_api_key(const UnsandboxCredentials *creds) {
    if (!creds || !creds->public_key || !creds->secret_key) {
        fprintf(stderr, "Error: Both public and secret keys required for validation\n");
        return 1;
    }

    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char url[256];
    snprintf(url, sizeof(url), "%s/keys/validate", API_BASE);

    // Use HMAC authentication with empty body
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/keys/validate", "");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
        free(response.data);
        return 1;
    }

    if (http_code != 200) {
        // Parse error response
        if (response.data) {
            char *error = extract_json_string(response.data, "error");
            char *reason = extract_json_string(response.data, "reason");
            if (error) {
                printf("\033[31mInvalid\033[0m: %s\n", error);
                free(error);
            } else if (reason) {
                if (strcmp(reason, "invalid_key") == 0) {
                    printf("\033[31mInvalid\033[0m: key not found\n");
                } else if (strcmp(reason, "expired") == 0) {
                    printf("\033[31mExpired\033[0m\n\n");

                    // Show key details if available
                    char *public_key = extract_json_string(response.data, "public_key");
                    long long tier = extract_json_number(response.data, "tier");
                    char *expired_at = extract_json_string(response.data, "expired_at_datetime");
                    char *expired_ago = extract_json_string(response.data, "expired_ago");
                    char *renew_url = extract_json_string(response.data, "renew_url");

                    if (public_key) {
                        printf("%-20s %s\n", "Public Key:", public_key);
                        free(public_key);
                    }
                    if (tier >= 0) {
                        printf("%-20s %lld\n", "Tier:", tier);
                    }
                    if (expired_at) {
                        printf("%-20s %s", "Expired:", expired_at);
                        if (expired_ago) {
                            printf(" (%s)", expired_ago);
                        }
                        printf("\n");
                        free(expired_at);
                    }
                    if (expired_ago) free(expired_ago);

                    printf("\n\033[33mTo renew:\033[0m Visit %s\n",
                           renew_url ? renew_url : "https://unsandbox.com/pricing");
                    if (renew_url) free(renew_url);
                } else if (strcmp(reason, "suspended") == 0) {
                    printf("\033[31mSuspended\033[0m: key has been suspended\n");
                } else {
                    printf("\033[31mInvalid\033[0m: %s\n", reason);
                }
                free(reason);
            } else {
                printf("\033[31mInvalid\033[0m: HTTP %ld\n", http_code);
            }
        }
        free(response.data);
        return 1;
    }

    // Check for valid:false in 200 response
    const char *valid_check = strstr(response.data, "\"valid\":false");
    if (valid_check) {
        char *reason = extract_json_string(response.data, "reason");
        if (reason) {
            if (strcmp(reason, "invalid_key") == 0) {
                printf("\033[31mInvalid\033[0m: key not found\n");
            } else if (strcmp(reason, "expired") == 0) {
                printf("\033[31mExpired\033[0m\n\n");

                // Show key details if available
                char *public_key = extract_json_string(response.data, "public_key");
                long long tier = extract_json_number(response.data, "tier");
                char *expired_at = extract_json_string(response.data, "expired_at_datetime");
                char *expired_ago = extract_json_string(response.data, "expired_ago");
                char *renew_url = extract_json_string(response.data, "renew_url");

                if (public_key) {
                    printf("%-20s %s\n", "Public Key:", public_key);
                    free(public_key);
                }
                if (tier >= 0) {
                    printf("%-20s %lld\n", "Tier:", tier);
                }
                if (expired_at) {
                    printf("%-20s %s", "Expired:", expired_at);
                    if (expired_ago) {
                        printf(" (%s)", expired_ago);
                    }
                    printf("\n");
                    free(expired_at);
                }
                if (expired_ago) free(expired_ago);

                printf("\n\033[33mTo renew:\033[0m Visit %s\n",
                       renew_url ? renew_url : "https://unsandbox.com/pricing");
                if (renew_url) free(renew_url);
            } else if (strcmp(reason, "suspended") == 0) {
                printf("\033[31mSuspended\033[0m: key has been suspended\n");
            } else {
                printf("\033[31mInvalid\033[0m: %s\n", reason);
            }
            free(reason);
        } else {
            printf("\033[31mInvalid key\033[0m\n");
        }
        free(response.data);
        return 1;
    }

    // Parse valid response
    if (!response.data) {
        fprintf(stderr, "Error: Empty response\n");
        return 1;
    }

    // Check if valid
    const char *valid_str = strstr(response.data, "\"valid\":");
    int valid = 0;
    if (valid_str) {
        valid = (strstr(valid_str, "true") == valid_str + 8);
    }

    if (!valid) {
        printf("\033[31mInvalid key\033[0m\n");
        free(response.data);
        return 1;
    }

    // Extract fields
    long long tier = extract_json_number(response.data, "tier");
    char *status = extract_json_string(response.data, "status");
    char *valid_through = extract_json_string(response.data, "valid_through_datetime");
    char *valid_for = extract_json_string(response.data, "valid_for_human");
    char *public_key = extract_json_string(response.data, "public_key");
    long long rate_per_minute = extract_json_number(response.data, "rate_per_minute");
    long long burst = extract_json_number(response.data, "burst");
    long long concurrency = extract_json_number(response.data, "concurrency");

    // Display key info
    printf("\033[32mValid\033[0m\n\n");
    printf("%-20s %s\n", "Public Key:", public_key ? public_key : "N/A");
    printf("%-20s %lld\n", "Tier:", tier);
    printf("%-20s %s\n", "Status:", status ? status : "N/A");
    printf("%-20s %s\n", "Expires:", valid_through ? valid_through : "N/A");
    printf("%-20s %s\n", "Time Remaining:", valid_for ? valid_for : "N/A");
    printf("%-20s %lld/min\n", "Rate Limit:", rate_per_minute);
    printf("%-20s %lld\n", "Burst:", burst);
    printf("%-20s %lld\n", "Concurrency:", concurrency);

    if (status) free(status);
    if (valid_through) free(valid_through);
    if (valid_for) free(valid_for);
    if (public_key) free(public_key);
    free(response.data);

    return 0;
}

// ============================================================================
// End Key Validation Support
// ============================================================================

void print_usage(const char *prog) {
    fprintf(stderr, "Usage: %s [options] <source_file>\n", prog);
    fprintf(stderr, "       %s session [options]\n", prog);
    fprintf(stderr, "       %s service [options]\n", prog);
    fprintf(stderr, "       %s snapshot [options]\n", prog);
    fprintf(stderr, "       %s image [options]\n", prog);
    fprintf(stderr, "       %s languages [--json]\n", prog);
    fprintf(stderr, "       %s jobs [options]\n", prog);
    fprintf(stderr, "       %s paas <command> [options]\n", prog);
    fprintf(stderr, "       %s key\n\n", prog);
    fprintf(stderr, "Commands:\n");
    fprintf(stderr, "  (default)        Execute source file in sandbox\n");
    fprintf(stderr, "  session          Open interactive shell/REPL session\n");
    fprintf(stderr, "  service          Manage persistent services\n");
    fprintf(stderr, "  snapshot         Manage container snapshots\n");
    fprintf(stderr, "  image            Manage images (publish, spawn, clone)\n");
    fprintf(stderr, "  languages        List available languages (--json for JSON output)\n");
    fprintf(stderr, "  jobs             List, inspect, or cancel async jobs\n");
    fprintf(stderr, "  paas             PaaS platform management (logs, etc.)\n");
    fprintf(stderr, "  key              Check API key validity and expiration\n");
    fprintf(stderr, "\nOptions:\n");
    fprintf(stderr, "  -s, --shell LANG Specify language (default: bash if arg is not a file)\n");
    fprintf(stderr, "  -e KEY=VALUE     Set environment variable (can use multiple times)\n");
    fprintf(stderr, "  -f FILE          Add input file to /tmp/ (can use multiple times)\n");
    fprintf(stderr, "  -F FILE          Add input file with path preserved (can use multiple times)\n");
    fprintf(stderr, "  -a               Return and save artifacts (compiled binaries)\n");
    fprintf(stderr, "  -o DIR           Output directory for artifacts (default: current dir)\n");
    fprintf(stderr, "  -p KEY           Public key (or set UNSANDBOX_PUBLIC_KEY env var)\n");
    fprintf(stderr, "  -k KEY           Secret key (or set UNSANDBOX_SECRET_KEY env var)\n");
    fprintf(stderr, "  -n MODE          Network mode: zerotrust (default) or semitrusted\n");
    fprintf(stderr, "  -v N, --vcpu N   vCPU count 1-8, each vCPU gets 2GB RAM. Default: 1\n");
    fprintf(stderr, "  -y               Skip confirmation for large uploads (>1GB)\n");
    fprintf(stderr, "  -h               Show this help\n");
    fprintf(stderr, "\nSession options:\n");
    fprintf(stderr, "  -s, --shell SHELL  Shell/REPL to use (default: bash)\n");
    fprintf(stderr, "  -l, --list         List active sessions\n");
    fprintf(stderr, "  --attach ID        Reconnect to existing session (ID or container name)\n");
    fprintf(stderr, "  --kill ID          Terminate a session (ID or container name)\n");
    fprintf(stderr, "  --audit            Record session for auditing\n");
    fprintf(stderr, "  --tmux             Enable session persistence with tmux (allows reconnect)\n");
    fprintf(stderr, "  --screen           Enable session persistence with screen (allows reconnect)\n");
    fprintf(stderr, "  --snapshot ID      Create snapshot of session (paid tiers only)\n");
    fprintf(stderr, "  --restore SNAPSHOT Restore session from snapshot\n");
    fprintf(stderr, "  --snapshot-name N  Name for the snapshot\n");
    fprintf(stderr, "  --hot              Take snapshot without freezing (live snapshot)\n");
    fprintf(stderr, "\nService options:\n");
    fprintf(stderr, "  --name NAME        Service name (creates new service)\n");
    fprintf(stderr, "  --ports PORTS      Comma-separated ports (e.g., 80,443)\n");
    fprintf(stderr, "  --domains DOMAINS  Comma-separated custom domains (e.g., example.com,www.example.com)\n");
    fprintf(stderr, "  --type TYPE        Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp)\n");
    fprintf(stderr, "  --golden-image IMG Use custom LXD image alias (for testing, e.g., jammy-golden-22.04)\n");
    fprintf(stderr, "  --bootstrap CMD    Bootstrap command or URI to run on startup\n");
    fprintf(stderr, "  --bootstrap-file FILE  Upload local file as bootstrap script content\n");
    fprintf(stderr, "  --unfreeze-on-demand   Enable auto-unfreeze when HTTP request arrives\n");
    fprintf(stderr, "  -e, --env KEY=VAL  Set environment variable (can repeat, stored encrypted)\n");
    fprintf(stderr, "  --env-file FILE    Load env vars from .env file (stored encrypted)\n");
    fprintf(stderr, "  -f FILE            Upload file to /tmp/ (can use multiple times)\n");
    fprintf(stderr, "  -F FILE            Upload file with path preserved (can use multiple times)\n");
    fprintf(stderr, "  -l, --list         List all services\n");
    fprintf(stderr, "  --info ID          Get service details\n");
    fprintf(stderr, "  --tail ID          Get last 9000 lines of bootstrap logs\n");
    fprintf(stderr, "  --logs ID          Get all bootstrap logs\n");
    fprintf(stderr, "  --download-logs ID FILE  Download all logs to file\n");
    fprintf(stderr, "  --freeze ID        Freeze a service\n");
    fprintf(stderr, "  --unfreeze ID      Unfreeze a service\n");
    fprintf(stderr, "  --destroy ID       Destroy a service\n");
    fprintf(stderr, "  --lock ID          Lock a service to prevent deletion\n");
    fprintf(stderr, "  --unlock ID        Unlock a service to allow deletion\n");
    fprintf(stderr, "  --auto-unfreeze ID     Enable auto-unfreeze on HTTP request\n");
    fprintf(stderr, "  --no-auto-unfreeze ID  Disable auto-unfreeze on HTTP request\n");
    fprintf(stderr, "  --show-freeze-page ID  Enable freeze page (show payment page when frozen)\n");
    fprintf(stderr, "  --no-show-freeze-page ID  Disable freeze page (return JSON error when frozen)\n");
    fprintf(stderr, "  --resize ID        Resize service vCPU/memory (requires --vcpu)\n");
    fprintf(stderr, "  --redeploy ID      Re-run bootstrap script (optional: --bootstrap or --bootstrap-file)\n");
    fprintf(stderr, "  --execute ID CMD   Run a command in a running service (use -f to upload files first)\n");
    fprintf(stderr, "  -t, --timeout SEC  Timeout for --execute in seconds (default: 30, 0=unlimited)\n");
    fprintf(stderr, "  --dump-bootstrap ID [FILE]  Dump bootstrap script (for migrations)\n");
    fprintf(stderr, "  --snapshot ID      Create snapshot of service (paid tiers only)\n");
    fprintf(stderr, "  --restore SNAPSHOT Restore service from snapshot\n");
    fprintf(stderr, "  --snapshot-name N  Name for the snapshot\n");
    fprintf(stderr, "  --hot              Take snapshot without freezing (live snapshot)\n");
    fprintf(stderr, "\nService environment vault:\n");
    fprintf(stderr, "  env status ID      Show vault status (exists, count, updated)\n");
    fprintf(stderr, "  env set ID         Set vault from --env-file FILE or stdin\n");
    fprintf(stderr, "  env export ID      Export vault contents to stdout\n");
    fprintf(stderr, "  env delete ID      Delete vault\n");
    fprintf(stderr, "\nSnapshot options:\n");
    fprintf(stderr, "  -l, --list         List all snapshots\n");
    fprintf(stderr, "  --info ID          Get snapshot details\n");
    fprintf(stderr, "  --delete ID        Delete a snapshot\n");
    fprintf(stderr, "  --lock ID          Lock a snapshot to prevent deletion\n");
    fprintf(stderr, "  --unlock ID        Unlock a snapshot to allow deletion\n");
    fprintf(stderr, "  --clone ID         Clone snapshot to new session/service\n");
    fprintf(stderr, "  --type TYPE        Clone type: session or service (for --clone)\n");
    fprintf(stderr, "  --name NAME        Name for cloned service (for --clone)\n");
    fprintf(stderr, "  --shell SHELL      Shell for cloned session (for --clone)\n");
    fprintf(stderr, "  --ports PORTS      Ports for cloned service (for --clone)\n");
    fprintf(stderr, "\nImage options:\n");
    fprintf(stderr, "  -l, --list         List all images\n");
    fprintf(stderr, "  --info ID          Get image details\n");
    fprintf(stderr, "  --delete ID        Delete an image\n");
    fprintf(stderr, "  --lock ID          Lock an image to prevent deletion\n");
    fprintf(stderr, "  --unlock ID        Unlock an image to allow deletion\n");
    fprintf(stderr, "  --publish ID       Publish image from service/snapshot (requires --source-type)\n");
    fprintf(stderr, "  --source-type TYPE Source type: service or snapshot (for --publish)\n");
    fprintf(stderr, "  --visibility ID V  Set visibility: private, unlisted, or public\n");
    fprintf(stderr, "  --spawn ID         Spawn new service from image\n");
    fprintf(stderr, "  --clone ID         Clone an image\n");
    fprintf(stderr, "  --name NAME        Name for spawned service or cloned image\n");
    fprintf(stderr, "  --ports PORTS      Ports for spawned service\n");
    fprintf(stderr, "\nAvailable shells/REPLs:\n");
    fprintf(stderr, "  Shells: bash, dash, sh, zsh, fish, ksh, tcsh, csh, elvish, xonsh, ash\n");
    fprintf(stderr, "  REPLs:  python3, bpython, ipython, node, ruby, irb, lua, php, perl\n");
    fprintf(stderr, "          guile, ghci, erl, iex, sbcl, clisp, r, julia, clojure\n");
    fprintf(stderr, "\nSession behavior:\n");
    fprintf(stderr, "  Default:    Session terminates immediately on disconnect (clean exit)\n");
    fprintf(stderr, "  --tmux:     Session persists on disconnect, reconnect with --attach\n");
    fprintf(stderr, "  --screen:   Session persists on disconnect, reconnect with --attach\n");
    fprintf(stderr, "\nExamples:\n");
    fprintf(stderr, "  %s script.py                       # execute Python script\n", prog);
    fprintf(stderr, "  %s -s bash 'echo hello'            # execute inline command\n", prog);
    fprintf(stderr, "  %s -e DEBUG=1 script.py            # with environment variable\n", prog);
    fprintf(stderr, "  %s -f data.csv process.py          # with input file\n", prog);
    fprintf(stderr, "  %s -a -o ./bin main.c              # save compiled artifacts\n", prog);
    fprintf(stderr, "  %s session                         # interactive bash (terminates on disconnect)\n", prog);
    fprintf(stderr, "  %s session --tmux                  # bash with tmux (can reconnect)\n", prog);
    fprintf(stderr, "  %s session --screen                # bash with screen (can reconnect)\n", prog);
    fprintf(stderr, "  %s session --list                  # list active sessions\n", prog);
    fprintf(stderr, "  %s session --kill sandbox-abc      # terminate a session\n", prog);
    fprintf(stderr, "  %s session --freeze sandbox-abc    # freeze session (requires --tmux/--screen)\n", prog);
    fprintf(stderr, "  %s session --unfreeze sandbox-abc  # unfreeze a frozen session\n", prog);
    fprintf(stderr, "  %s session --boost sandbox-abc     # boost to 2 vCPU, 4GB RAM\n", prog);
    fprintf(stderr, "  %s session --boost sandbox-abc --boost-vcpu 4  # 4 vCPU, 8GB RAM\n", prog);
    fprintf(stderr, "  %s session --unboost sandbox-abc   # return to base resources\n", prog);
    fprintf(stderr, "  %s session --attach sandbox-abc    # reconnect by container name\n", prog);
    fprintf(stderr, "  %s session --shell python3         # Python REPL\n", prog);
    fprintf(stderr, "  %s session --shell node --tmux     # Node.js REPL with reconnect\n", prog);
    fprintf(stderr, "  %s session -n semitrusted          # session with network access\n", prog);
    fprintf(stderr, "  %s session --audit -o ./logs       # record session for auditing\n", prog);
    fprintf(stderr, "  %s session -f data.csv             # session with input file in /tmp/\n", prog);
    fprintf(stderr, "  %s service --name web --ports 80,443 --bootstrap \"python3 -m http.server 80\"\n", prog);
    fprintf(stderr, "  %s service --name app --ports 8000 --bootstrap-file ./setup.sh\n", prog);
    fprintf(stderr, "  %s service --name app -f app.tar.gz --bootstrap-file ./setup.sh  # deploy tarball\n", prog);
    fprintf(stderr, "  %s service --name blog --ports 8000 --domains blog.example.com,www.example.com\n", prog);
    fprintf(stderr, "  %s service --list                  # list all services\n", prog);
    fprintf(stderr, "  %s service --info abc123           # get service details\n", prog);
    fprintf(stderr, "  %s service --logs abc123           # get bootstrap logs\n", prog);
    fprintf(stderr, "  %s service --freeze abc123         # freeze a service\n", prog);
    fprintf(stderr, "  %s service --unfreeze abc123       # unfreeze a service\n", prog);
    fprintf(stderr, "  %s service --resize abc123 --vcpu 4  # scale to 4 vCPU, 8GB RAM\n", prog);
    fprintf(stderr, "  %s service --destroy abc123        # destroy a service\n", prog);
    fprintf(stderr, "  %s service --redeploy abc123 --bootstrap-file ./script.sh\n", prog);
    fprintf(stderr, "  %s service --redeploy abc123       # uses stored encrypted bootstrap\n", prog);
    fprintf(stderr, "  %s service --execute maldoror 'journalctl -u myapp -n 50'\n", prog);
    fprintf(stderr, "  %s service -f data.txt --execute myapp 'cat /tmp/input/data.txt'  # upload then run\n", prog);
    fprintf(stderr, "  %s service --dump-bootstrap maldoror  # print bootstrap to stdout\n", prog);
    fprintf(stderr, "  %s service --dump-bootstrap maldoror backup.sh  # save to file\n", prog);
    fprintf(stderr, "  %s service --name app -e API_KEY=secret -e DEBUG=1  # with env vars\n", prog);
    fprintf(stderr, "  %s service --name app --env-file .env  # with env file\n", prog);
    fprintf(stderr, "  %s service env status myapp        # check vault status\n", prog);
    fprintf(stderr, "  %s service env set myapp -e KEY=val -e SECRET=xxx  # set from flags\n", prog);
    fprintf(stderr, "  %s service env set myapp --env-file .env  # set vault from file\n", prog);
    fprintf(stderr, "  %s service env set myapp < .env    # set vault from stdin\n", prog);
    fprintf(stderr, "  %s service env export myapp        # export vault contents\n", prog);
    fprintf(stderr, "  %s service env delete myapp        # delete vault\n", prog);
    fprintf(stderr, "  %s service --snapshot abc123       # create snapshot of service\n", prog);
    fprintf(stderr, "  %s service --restore unsb-snapshot-xxxx  # restore service\n", prog);
    fprintf(stderr, "  %s session --snapshot abc123       # create snapshot of session\n", prog);
    fprintf(stderr, "  %s session --restore unsb-snapshot-xxxx  # restore session\n", prog);
    fprintf(stderr, "  %s snapshot --list                 # list all snapshots\n", prog);
    fprintf(stderr, "  %s snapshot --info unsb-snapshot-xxxx  # get snapshot details\n", prog);
    fprintf(stderr, "  %s snapshot --delete unsb-snapshot-xxxx  # delete a snapshot\n", prog);
    fprintf(stderr, "  %s snapshot --clone unsb-snapshot-xxxx --type service --name myapp\n", prog);
    fprintf(stderr, "  %s jobs                                  # list all jobs\n", prog);
    fprintf(stderr, "  %s jobs --get JOB_ID                    # get job status and result\n", prog);
    fprintf(stderr, "  %s jobs --cancel JOB_ID                 # cancel a running job\n", prog);
    fprintf(stderr, "  %s paas logs                             # last 100 lines from all sources\n", prog);
    fprintf(stderr, "  %s paas logs --api -n 500               # last 500 API log lines\n", prog);
    fprintf(stderr, "  %s paas logs --portal --grep error       # portal logs matching 'error'\n", prog);
    fprintf(stderr, "  %s paas logs --pool cammy --since 1h     # cammy pool logs from last hour\n", prog);
    fprintf(stderr, "  %s paas logs -l warning                  # warnings and above\n", prog);
    fprintf(stderr, "  %s paas logs --follow                    # follow all logs in real-time\n", prog);
    fprintf(stderr, "  %s paas logs --follow -l warning         # follow warnings+ in real-time\n", prog);
    fprintf(stderr, "  %s key                             # check API key validity\n", prog);
    fprintf(stderr, "  %s key --extend                    # open portal to extend key\n", prog);
    fprintf(stderr, "\nAuthentication:\n");
    fprintf(stderr, "  Credentials are loaded in order of priority:\n");
    fprintf(stderr, "  1. -p and -k flags (public and secret key)\n");
    fprintf(stderr, "  2. UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars\n");
    fprintf(stderr, "  3. ~/.unsandbox/accounts.csv (format: public_key,secret_key per line)\n");
    fprintf(stderr, "     Use --account N to select account by index (0-based, default: 0)\n");
    fprintf(stderr, "     Or set UNSANDBOX_ACCOUNT=N env var\n");
}

/* ============================================================================
 * LIBRARY API IMPLEMENTATION
 * These functions implement the un.h public API for library use.
 * ============================================================================ */

const char *unsandbox_version(void) {
    return "4.3.4";
}

const char *unsandbox_detect_language(const char *filename) {
    if (!filename) return NULL;
    return detect_language_from_extension(filename);
}

char *unsandbox_hmac_sign(const char *secret_key, const char *message) {
    if (!secret_key || !message) return NULL;
    return hmac_sha256_hex(secret_key, strlen(secret_key), message, strlen(message));
}

int unsandbox_health_check(void) {
    CURL *curl = curl_easy_init();
    if (!curl) return -1;

    curl_easy_setopt(curl, CURLOPT_URL, API_BASE "/health");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) return -1;
    return (http_code == 200) ? 1 : 0;
}

#ifdef UNSANDBOX_LIBRARY
/* ============================================================================
 * Library API - Only compiled when UNSANDBOX_LIBRARY is defined
 * ============================================================================ */

/* Memory management */
void unsandbox_free_result(unsandbox_result_t *result) {
    if (!result) return;
    free(result->stdout_str);
    free(result->stderr_str);
    free(result->language);
    free(result->error_message);
    free(result);
}

void unsandbox_free_job(unsandbox_job_t *job) {
    if (!job) return;
    free(job->id);
    free(job->language);
    free(job->status);
    free(job->error_message);
    free(job);
}

void unsandbox_free_job_list(unsandbox_job_list_t *jobs) {
    if (!jobs) return;
    for (size_t i = 0; i < jobs->count; i++) {
        free(jobs->jobs[i].id);
        free(jobs->jobs[i].language);
        free(jobs->jobs[i].status);
        free(jobs->jobs[i].error_message);
    }
    free(jobs->jobs);
    free(jobs);
}

void unsandbox_free_languages(unsandbox_languages_t *langs) {
    if (!langs) return;
    for (size_t i = 0; i < langs->count; i++) {
        free(langs->languages[i]);
    }
    free(langs->languages);
    free(langs);
}

void unsandbox_free_session(unsandbox_session_t *session) {
    if (!session) return;
    free(session->id);
    free(session->container_name);
    free(session->status);
    free(session->network_mode);
    free(session);
}

void unsandbox_free_session_list(unsandbox_session_list_t *sessions) {
    if (!sessions) return;
    for (size_t i = 0; i < sessions->count; i++) {
        free(sessions->sessions[i].id);
        free(sessions->sessions[i].container_name);
        free(sessions->sessions[i].status);
        free(sessions->sessions[i].network_mode);
    }
    free(sessions->sessions);
    free(sessions);
}

void unsandbox_free_service(unsandbox_service_t *service) {
    if (!service) return;
    free(service->id);
    free(service->name);
    free(service->status);
    free(service->container_name);
    free(service->network_mode);
    free(service->ports);
    free(service->domains);
    free(service);
}

void unsandbox_free_service_list(unsandbox_service_list_t *services) {
    if (!services) return;
    for (size_t i = 0; i < services->count; i++) {
        free(services->services[i].id);
        free(services->services[i].name);
        free(services->services[i].status);
        free(services->services[i].container_name);
        free(services->services[i].network_mode);
        free(services->services[i].ports);
        free(services->services[i].domains);
    }
    free(services->services);
    free(services);
}

void unsandbox_free_snapshot(unsandbox_snapshot_t *snapshot) {
    if (!snapshot) return;
    free(snapshot->id);
    free(snapshot->name);
    free(snapshot->type);
    free(snapshot->source_id);
    free(snapshot);
}

void unsandbox_free_snapshot_list(unsandbox_snapshot_list_t *snapshots) {
    if (!snapshots) return;
    for (size_t i = 0; i < snapshots->count; i++) {
        free(snapshots->snapshots[i].id);
        free(snapshots->snapshots[i].name);
        free(snapshots->snapshots[i].type);
        free(snapshots->snapshots[i].source_id);
    }
    free(snapshots->snapshots);
    free(snapshots);
}

void unsandbox_free_key_info(unsandbox_key_info_t *info) {
    if (!info) return;
    free(info->tier);
    free(info->error_message);
    free(info);
}

/* ============================================================================
 * Session Functions Implementation
 * ============================================================================ */

int unsandbox_session_destroy(const char *session_id,
                              const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = kill_session(creds, session_id);
    free_credentials(creds);
    return result;
}

int unsandbox_session_freeze(const char *session_id,
                             const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = freeze_session(creds, session_id);
    free_credentials(creds);
    return result;
}

int unsandbox_session_unfreeze(const char *session_id,
                               const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = unfreeze_session(creds, session_id);
    free_credentials(creds);
    return result;
}

int unsandbox_session_boost(const char *session_id, int vcpu,
                            const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = boost_session(creds, session_id, vcpu);
    free_credentials(creds);
    return result;
}

int unsandbox_session_unboost(const char *session_id,
                              const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = unboost_session(creds, session_id);
    free_credentials(creds);
    return result;
}

/* ============================================================================
 * Service Functions Implementation
 * ============================================================================ */

int unsandbox_service_destroy(const char *service_id,
                              const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = destroy_service(creds, service_id);
    free_credentials(creds);
    return result;
}

int unsandbox_service_freeze(const char *service_id,
                             const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = freeze_service(creds, service_id);
    free_credentials(creds);
    return result;
}

int unsandbox_service_unfreeze(const char *service_id,
                               const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = unfreeze_service(creds, service_id);
    free_credentials(creds);
    return result;
}

int unsandbox_service_lock(const char *service_id,
                           const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = lock_service(creds, service_id);
    free_credentials(creds);
    return result;
}

int unsandbox_service_unlock(const char *service_id,
                             const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = unlock_service(creds, service_id);
    free_credentials(creds);
    return result;
}

int unsandbox_service_set_unfreeze_on_demand(const char *service_id, int enabled,
                                             const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = set_unfreeze_on_demand(creds, service_id, enabled);
    free_credentials(creds);
    return result;
}

int unsandbox_service_redeploy(const char *service_id, const char *bootstrap,
                               const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = redeploy_service(creds, service_id, bootstrap, NULL, 0);
    free_credentials(creds);
    return result;
}

char *unsandbox_service_logs(const char *service_id, int all_logs,
                             const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return NULL;
    char *result = get_service_logs(creds, service_id, all_logs);
    free_credentials(creds);
    return result;
}

int unsandbox_service_env_set(const char *service_id, const char *env_content,
                              const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = service_env_set(creds, service_id, env_content);
    free_credentials(creds);
    return result;
}

int unsandbox_service_env_delete(const char *service_id,
                                 const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = service_env_delete(creds, service_id);
    free_credentials(creds);
    return result;
}

int unsandbox_service_resize(const char *service_id, int vcpu,
                             const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = resize_service(creds, service_id, vcpu);
    free_credentials(creds);
    return result;
}

/* ============================================================================
 * Snapshot Functions Implementation
 * ============================================================================ */

int unsandbox_snapshot_delete(const char *snapshot_id,
                              const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = delete_snapshot(creds, snapshot_id);
    free_credentials(creds);
    return result;
}

int unsandbox_snapshot_lock(const char *snapshot_id,
                            const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = lock_snapshot(creds, snapshot_id);
    free_credentials(creds);
    return result;
}

int unsandbox_snapshot_unlock(const char *snapshot_id,
                              const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = unlock_snapshot(creds, snapshot_id);
    free_credentials(creds);
    return result;
}

int unsandbox_snapshot_restore(const char *snapshot_id,
                               const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return -1;
    int result = restore_from_snapshot(creds, snapshot_id, NULL);
    free_credentials(creds);
    return result;
}

/* ============================================================================
 * Key Validation Implementation
 * ============================================================================ */

unsandbox_key_info_t *unsandbox_validate_keys(const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) return NULL;

    unsandbox_key_info_t *info = calloc(1, sizeof(unsandbox_key_info_t));
    if (!info) {
        free_credentials(creds);
        return NULL;
    }

    int result = validate_api_key(creds);
    info->valid = (result == 0) ? 1 : 0;

    free_credentials(creds);
    return info;
}

/* ============================================================================
 * PaaS Logs Library Implementation
 * ============================================================================ */

char *unsandbox_logs_fetch(
    const char *source,
    int lines,
    const char *since,
    const char *grep,
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) { set_last_error("No credentials"); return NULL; }

    CURL *curl = curl_easy_init();
    if (!curl) { free_credentials(creds); set_last_error("curl init failed"); return NULL; }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[256];
    if (!source || strcmp(source, "all") == 0)
        snprintf(path, sizeof(path), "/logs/all");
    else
        snprintf(path, sizeof(path), "/logs/%s", source);

    char url[1024];
    int off = snprintf(url, sizeof(url), "%s%s?lines=%d", PORTAL_BASE, path, lines > 0 ? lines : 100);
    if (since && since[0])
        off += snprintf(url + off, sizeof(url) - off, "&since=%s", since);
    if (grep && grep[0])
        off += snprintf(url + off, sizeof(url) - off, "&grep=%s", grep);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        char errbuf[256];
        snprintf(errbuf, sizeof(errbuf), "HTTP %ld: %s", http_code,
                 res != CURLE_OK ? curl_easy_strerror(res) : "request failed");
        set_last_error(errbuf);
        free(response.data);
        return NULL;
    }

    return response.data; // caller frees
}

// Streaming callback context for library API
struct StreamCallbackCtx {
    unsandbox_log_callback_t callback;
    void *userdata;
};

static size_t stream_lib_sse_callback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct StreamCallbackCtx *ctx = (struct StreamCallbackCtx *)userp;
    static char buf[65536];
    static size_t buf_len = 0;

    size_t space = sizeof(buf) - buf_len - 1;
    size_t copy = realsize < space ? realsize : space;
    memcpy(buf + buf_len, contents, copy);
    buf_len += copy;
    buf[buf_len] = 0;

    char *pos = buf;
    char *event_end;
    while ((event_end = strstr(pos, "\n\n")) != NULL) {
        *event_end = 0;
        char *line = pos;
        while (*line) {
            char *nl = strchr(line, '\n');
            if (nl) *nl = 0;
            if (strncmp(line, "data: ", 6) == 0) {
                const char *payload = line + 6;
                if (strcmp(payload, ":keepalive") != 0 && ctx->callback) {
                    char *source = extract_json_string(payload, "source");
                    char *logline = extract_json_string(payload, "line");
                    ctx->callback(source, logline ? logline : payload, ctx->userdata);
                    free(source);
                    free(logline);
                }
            }
            if (nl) line = nl + 1;
            else break;
        }
        pos = event_end + 2;
    }

    if (pos > buf) {
        buf_len = strlen(pos);
        memmove(buf, pos, buf_len + 1);
    }

    if (stream_interrupted) return 0; // abort
    return realsize;
}

int unsandbox_logs_stream(
    const char *source,
    const char *grep,
    unsandbox_log_callback_t callback,
    void *userdata,
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) { set_last_error("No credentials"); return 1; }

    CURL *curl = curl_easy_init();
    if (!curl) { free_credentials(creds); set_last_error("curl init failed"); return 1; }

    char path[256];
    if (!source || strcmp(source, "all") == 0)
        snprintf(path, sizeof(path), "/logs/all/stream");
    else
        snprintf(path, sizeof(path), "/logs/%s/stream", source);

    char url[1024];
    if (grep && grep[0])
        snprintf(url, sizeof(url), "%s%s?grep=%s", PORTAL_BASE, path, grep);
    else
        snprintf(url, sizeof(url), "%s%s", PORTAL_BASE, path);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    struct StreamCallbackCtx ctx = { callback, userdata };

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, stream_lib_sse_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 120L);

    stream_interrupted = 0;
    CURLcode res = curl_easy_perform(curl);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK && res != CURLE_WRITE_ERROR) {
        set_last_error(curl_easy_strerror(res));
        return 1;
    }

    return 0;
}

/* ============================================================================
 * Stub Implementations (TODO: Full implementation)
 * These functions are declared in un.h but need HTTP/JSON handling
 * ============================================================================ */

/* Execution - full library implementations */

// Helper to parse execute response into result struct
static unsandbox_result_t *parse_execute_response(const char *json) {
    if (!json) return NULL;

    unsandbox_result_t *result = calloc(1, sizeof(unsandbox_result_t));
    if (!result) {
        set_last_error("Out of memory");
        return NULL;
    }

    result->stdout_str = extract_json_string(json, "stdout");
    result->stderr_str = extract_json_string(json, "stderr");
    result->error_message = extract_json_string(json, "error");
    result->language = extract_json_string(json, "language");
    result->exit_code = (int)extract_json_number(json, "exit_code");
    long long exec_time_ms = extract_json_number(json, "execution_time");
    result->execution_time = exec_time_ms >= 0 ? exec_time_ms / 1000.0 : 0.0;
    result->success = (result->exit_code == 0 && !result->error_message);

    return result;
}

unsandbox_result_t *unsandbox_execute(
    const char *language, const char *code,
    const char *public_key, const char *secret_key) {

    if (!language || !code) {
        set_last_error("Language and code are required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    // Escape code for JSON
    char *escaped_code = escape_json_string(code);
    if (!escaped_code) {
        set_last_error("Failed to escape code");
        free_credentials(creds);
        return NULL;
    }

    // Build JSON payload
    size_t payload_size = strlen(escaped_code) + strlen(language) + 64;
    char *json_payload = malloc(payload_size);
    if (!json_payload) {
        set_last_error("Out of memory");
        free(escaped_code);
        free_credentials(creds);
        return NULL;
    }
    snprintf(json_payload, payload_size, "{\"language\":\"%s\",\"code\":\"%s\"}", language, escaped_code);
    free(escaped_code);

    // Initialize curl
    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free(json_payload);
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/execute", json_payload);

    curl_easy_setopt(curl, CURLOPT_URL, API_URL);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(json_payload);

    if (res != CURLE_OK) {
        set_last_error("Request failed: %s", curl_easy_strerror(res));
        free(response.data);
        free_credentials(creds);
        return NULL;
    }

    if (http_code != 200) {
        set_last_error("HTTP %ld: %s", http_code, response.data ? response.data : "Unknown error");
        free(response.data);
        free_credentials(creds);
        return NULL;
    }

    // Check if we need to poll for job completion
    char *job_id = extract_json_string(response.data, "job_id");
    char *status = extract_json_string(response.data, "status");

    unsandbox_result_t *result = NULL;
    if (job_id && status && (strcmp(status, "pending") == 0 || strcmp(status, "running") == 0)) {
        // Need to poll
        char *final_response = poll_job_status(creds, job_id);
        result = parse_execute_response(final_response);
        free(final_response);
    } else {
        result = parse_execute_response(response.data);
    }

    free(job_id);
    free(status);
    free(response.data);
    free_credentials(creds);
    return result;
}

char *unsandbox_execute_async(
    const char *language, const char *code,
    const char *public_key, const char *secret_key) {

    if (!language || !code) {
        set_last_error("Language and code are required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *escaped_code = escape_json_string(code);
    if (!escaped_code) {
        set_last_error("Failed to escape code");
        free_credentials(creds);
        return NULL;
    }

    size_t payload_size = strlen(escaped_code) + strlen(language) + 64;
    char *json_payload = malloc(payload_size);
    if (!json_payload) {
        set_last_error("Out of memory");
        free(escaped_code);
        free_credentials(creds);
        return NULL;
    }
    snprintf(json_payload, payload_size, "{\"language\":\"%s\",\"code\":\"%s\"}", language, escaped_code);
    free(escaped_code);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free(json_payload);
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/execute", json_payload);

    curl_easy_setopt(curl, CURLOPT_URL, API_URL);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(json_payload);
    free_credentials(creds);

    if (res != CURLE_OK) {
        set_last_error("Request failed: %s", curl_easy_strerror(res));
        free(response.data);
        return NULL;
    }

    if (http_code != 200 && http_code != 202) {
        set_last_error("HTTP %ld: %s", http_code, response.data ? response.data : "Unknown error");
        free(response.data);
        return NULL;
    }

    char *job_id = extract_json_string(response.data, "job_id");
    free(response.data);

    if (!job_id) {
        set_last_error("No job_id in response");
    }
    return job_id;
}

unsandbox_result_t *unsandbox_wait_job(
    const char *job_id,
    const char *public_key, const char *secret_key) {

    if (!job_id) {
        set_last_error("Job ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *final_response = poll_job_status(creds, job_id);
    free_credentials(creds);

    if (!final_response) {
        set_last_error("Failed to poll job status");
        return NULL;
    }

    unsandbox_result_t *result = parse_execute_response(final_response);
    free(final_response);
    return result;
}

unsandbox_job_t *unsandbox_get_job(
    const char *job_id,
    const char *public_key, const char *secret_key) {

    if (!job_id) {
        set_last_error("Job ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/jobs/%s", API_BASE, job_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/jobs/%s", job_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get job: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_job_t *job = calloc(1, sizeof(unsandbox_job_t));
    if (!job) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    job->id = extract_json_string(response.data, "job_id");
    if (!job->id) job->id = strdup(job_id);
    job->language = extract_json_string(response.data, "language");
    job->status = extract_json_string(response.data, "status");
    job->created_at = extract_json_number(response.data, "created_at");
    job->completed_at = extract_json_number(response.data, "completed_at");
    job->error_message = extract_json_string(response.data, "error");

    free(response.data);
    return job;
}

int unsandbox_cancel_job(
    const char *job_id,
    const char *public_key, const char *secret_key) {

    if (!job_id) {
        set_last_error("Job ID is required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/jobs/%s", API_BASE, job_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return -1;
    }

    char path[128];
    snprintf(path, sizeof(path), "/jobs/%s", job_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "DELETE", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK) {
        set_last_error("Request failed: %s", curl_easy_strerror(res));
        return -1;
    }

    return (http_code == 200 || http_code == 204) ? 0 : -1;
}

unsandbox_job_list_t *unsandbox_list_jobs(
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/jobs", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/jobs", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to list jobs: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    int count = count_json_array_objects(response.data, "jobs");
    unsandbox_job_list_t *list = calloc(1, sizeof(unsandbox_job_list_t));
    if (!list) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    if (count > 0) {
        list->jobs = calloc(count, sizeof(unsandbox_job_t));
        if (!list->jobs) {
            set_last_error("Out of memory");
            free(list);
            free(response.data);
            return NULL;
        }
        list->count = count;

        const char *jobs_start = strstr(response.data, "\"jobs\":[");
        if (jobs_start) {
            const char *pos = jobs_start + 8;
            for (size_t i = 0; i < list->count && pos; i++) {
                pos = strchr(pos, '{');
                if (!pos) break;

                list->jobs[i].id = extract_json_string(pos, "job_id");
                list->jobs[i].language = extract_json_string(pos, "language");
                list->jobs[i].status = extract_json_string(pos, "status");
                list->jobs[i].created_at = extract_json_number(pos, "created_at");
                list->jobs[i].completed_at = extract_json_number(pos, "completed_at");
                list->jobs[i].error_message = extract_json_string(pos, "error");

                pos = skip_json_object(pos);
            }
        }
    }

    free(response.data);
    return list;
}

unsandbox_languages_t *unsandbox_get_languages(
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/languages", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/languages", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get languages: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    // Count languages - look for "languages":["lang1","lang2",...]
    const char *langs_start = strstr(response.data, "\"languages\":[");
    if (!langs_start) {
        // Try alternative format: just an array
        langs_start = strchr(response.data, '[');
    }

    int count = 0;
    if (langs_start) {
        const char *p = strchr(langs_start, '[');
        if (p) {
            p++;
            while (*p && *p != ']') {
                if (*p == '"') {
                    count++;
                    p++;
                    while (*p && *p != '"') p++;
                }
                if (*p) p++;
            }
        }
    }

    unsandbox_languages_t *langs = calloc(1, sizeof(unsandbox_languages_t));
    if (!langs) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    if (count > 0) {
        langs->languages = calloc(count, sizeof(char *));
        if (!langs->languages) {
            set_last_error("Out of memory");
            free(langs);
            free(response.data);
            return NULL;
        }
        langs->count = count;

        // Parse again to extract strings
        const char *p = strchr(langs_start, '[');
        if (p) {
            p++;
            size_t i = 0;
            while (*p && *p != ']' && i < langs->count) {
                if (*p == '"') {
                    p++;
                    const char *end = strchr(p, '"');
                    if (end) {
                        size_t len = end - p;
                        langs->languages[i] = malloc(len + 1);
                        if (langs->languages[i]) {
                            memcpy(langs->languages[i], p, len);
                            langs->languages[i][len] = '\0';
                            i++;
                        }
                        p = end;
                    }
                }
                if (*p) p++;
            }
        }
    }

    free(response.data);
    return langs;
}

/* Session - full library implementations */

unsandbox_session_list_t *unsandbox_session_list(
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/sessions", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/sessions", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to list sessions: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    int count = count_json_array_objects(response.data, "sessions");
    unsandbox_session_list_t *list = calloc(1, sizeof(unsandbox_session_list_t));
    if (!list) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    if (count > 0) {
        list->sessions = calloc(count, sizeof(unsandbox_session_t));
        if (!list->sessions) {
            set_last_error("Out of memory");
            free(list);
            free(response.data);
            return NULL;
        }
        list->count = count;

        const char *sessions_start = strstr(response.data, "\"sessions\":[");
        if (sessions_start) {
            const char *pos = sessions_start + 12;
            for (size_t i = 0; i < list->count && pos; i++) {
                pos = strchr(pos, '{');
                if (!pos) break;

                list->sessions[i].id = extract_json_string(pos, "id");
                list->sessions[i].container_name = extract_json_string(pos, "container_name");
                list->sessions[i].status = extract_json_string(pos, "status");
                list->sessions[i].network_mode = extract_json_string(pos, "network_mode");
                list->sessions[i].vcpu = (int)extract_json_number(pos, "vcpu");
                list->sessions[i].created_at = extract_json_number(pos, "created_at");
                list->sessions[i].last_activity = extract_json_number(pos, "last_activity");

                pos = skip_json_object(pos);
            }
        }
    }

    free(response.data);
    return list;
}

unsandbox_session_t *unsandbox_session_get(
    const char *session_id,
    const char *public_key, const char *secret_key) {

    if (!session_id) {
        set_last_error("Session ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/sessions/%s", API_BASE, session_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/sessions/%s", session_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get session: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_session_t *session = calloc(1, sizeof(unsandbox_session_t));
    if (!session) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    session->id = extract_json_string(response.data, "session_id");
    if (!session->id) session->id = extract_json_string(response.data, "id");
    session->container_name = extract_json_string(response.data, "container_name");
    session->status = extract_json_string(response.data, "status");
    session->network_mode = extract_json_string(response.data, "network_mode");
    session->vcpu = (int)extract_json_number(response.data, "vcpu");
    session->created_at = extract_json_number(response.data, "created_at");
    session->last_activity = extract_json_number(response.data, "last_activity");

    free(response.data);
    return session;
}

unsandbox_session_t *unsandbox_session_create(
    const char *network_mode, const char *shell,
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    // Build payload
    char payload[512];
    char *p = payload;
    p += sprintf(p, "{");
    int has_field = 0;
    if (network_mode && strlen(network_mode) > 0) {
        p += sprintf(p, "\"network_mode\":\"%s\"", network_mode);
        has_field = 1;
    }
    if (shell && strlen(shell) > 0) {
        if (has_field) p += sprintf(p, ",");
        p += sprintf(p, "\"shell\":\"%s\"", shell);
    }
    p += sprintf(p, "}");

    char url[256];
    snprintf(url, sizeof(url), "%s/sessions", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/sessions", payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || (http_code != 200 && http_code != 201)) {
        set_last_error("Failed to create session: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_session_t *session = calloc(1, sizeof(unsandbox_session_t));
    if (!session) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    session->id = extract_json_string(response.data, "session_id");
    if (!session->id) session->id = extract_json_string(response.data, "id");
    session->container_name = extract_json_string(response.data, "container_name");
    session->status = extract_json_string(response.data, "status");
    session->network_mode = extract_json_string(response.data, "network_mode");
    session->vcpu = (int)extract_json_number(response.data, "vcpu");
    session->created_at = extract_json_number(response.data, "created_at");
    session->last_activity = extract_json_number(response.data, "last_activity");

    free(response.data);
    return session;
}

unsandbox_result_t *unsandbox_session_execute(
    const char *session_id, const char *command,
    const char *public_key, const char *secret_key) {

    if (!session_id || !command) {
        set_last_error("Session ID and command are required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *escaped_cmd = escape_json_string(command);
    if (!escaped_cmd) {
        set_last_error("Failed to escape command");
        free_credentials(creds);
        return NULL;
    }

    size_t payload_size = strlen(escaped_cmd) + 64;
    char *payload = malloc(payload_size);
    if (!payload) {
        set_last_error("Out of memory");
        free(escaped_cmd);
        free_credentials(creds);
        return NULL;
    }
    snprintf(payload, payload_size, "{\"command\":\"%s\"}", escaped_cmd);
    free(escaped_cmd);

    char url[256];
    snprintf(url, sizeof(url), "%s/sessions/%s/shell", API_BASE, session_id);

    char path[128];
    snprintf(path, sizeof(path), "/sessions/%s/shell", session_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free(payload);
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(payload);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to execute in session: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_result_t *result = parse_execute_response(response.data);
    free(response.data);
    return result;
}

/* Service - full library implementations */

unsandbox_service_list_t *unsandbox_service_list(
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/services", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/services", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to list services: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    int count = count_json_array_objects(response.data, "services");
    unsandbox_service_list_t *list = calloc(1, sizeof(unsandbox_service_list_t));
    if (!list) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    if (count > 0) {
        list->services = calloc(count, sizeof(unsandbox_service_t));
        if (!list->services) {
            set_last_error("Out of memory");
            free(list);
            free(response.data);
            return NULL;
        }
        list->count = count;

        const char *services_start = strstr(response.data, "\"services\":[");
        if (services_start) {
            const char *pos = services_start + 12;
            for (size_t i = 0; i < list->count && pos; i++) {
                pos = strchr(pos, '{');
                if (!pos) break;

                list->services[i].id = extract_json_string(pos, "id");
                list->services[i].name = extract_json_string(pos, "name");
                list->services[i].status = extract_json_string(pos, "status");
                list->services[i].container_name = extract_json_string(pos, "container_name");
                list->services[i].network_mode = extract_json_string(pos, "network_mode");
                list->services[i].ports = extract_json_string(pos, "ports");
                list->services[i].domains = extract_json_string(pos, "domains");
                list->services[i].vcpu = (int)extract_json_number(pos, "vcpu");
                list->services[i].locked = (int)extract_json_number(pos, "locked");
                list->services[i].unfreeze_on_demand = (int)extract_json_number(pos, "unfreeze_on_demand");
                list->services[i].created_at = extract_json_number(pos, "created_at");
                list->services[i].last_activity = extract_json_number(pos, "last_activity");

                pos = skip_json_object(pos);
            }
        }
    }

    free(response.data);
    return list;
}

unsandbox_service_t *unsandbox_service_get(
    const char *service_id,
    const char *public_key, const char *secret_key) {

    if (!service_id) {
        set_last_error("Service ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/services/%s", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get service: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_service_t *service = calloc(1, sizeof(unsandbox_service_t));
    if (!service) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    service->id = extract_json_string(response.data, "id");
    service->name = extract_json_string(response.data, "name");
    service->status = extract_json_string(response.data, "status");
    service->container_name = extract_json_string(response.data, "container_name");
    service->network_mode = extract_json_string(response.data, "network_mode");
    service->ports = extract_json_string(response.data, "ports");
    service->domains = extract_json_string(response.data, "domains");
    service->vcpu = (int)extract_json_number(response.data, "vcpu");
    service->locked = (int)extract_json_number(response.data, "locked");
    service->unfreeze_on_demand = (int)extract_json_number(response.data, "unfreeze_on_demand");
    service->created_at = extract_json_number(response.data, "created_at");
    service->last_activity = extract_json_number(response.data, "last_activity");

    free(response.data);
    return service;
}

char *unsandbox_service_create(
    const char *name, const char *ports, const char *domains,
    const char *bootstrap, const char *network_mode,
    const char *public_key, const char *secret_key) {
    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }
    char *result = create_service(creds, name, ports, domains, bootstrap, NULL, network_mode, 0, NULL, NULL, 0, NULL, 0);
    free_credentials(creds);
    return result;
}

unsandbox_result_t *unsandbox_service_execute(
    const char *service_id, const char *command, int timeout_ms,
    const char *public_key, const char *secret_key) {

    if (!service_id || !command) {
        set_last_error("Service ID and command are required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *escaped_cmd = escape_json_string(command);
    if (!escaped_cmd) {
        set_last_error("Failed to escape command");
        free_credentials(creds);
        return NULL;
    }

    size_t payload_size = strlen(escaped_cmd) + 128;
    char *payload = malloc(payload_size);
    if (!payload) {
        set_last_error("Out of memory");
        free(escaped_cmd);
        free_credentials(creds);
        return NULL;
    }
    if (timeout_ms > 0) {
        snprintf(payload, payload_size, "{\"command\":\"%s\",\"timeout\":%d}", escaped_cmd, timeout_ms);
    } else {
        snprintf(payload, payload_size, "{\"command\":\"%s\"}", escaped_cmd);
    }
    free(escaped_cmd);

    char url[256];
    snprintf(url, sizeof(url), "%s/services/%s/execute", API_BASE, service_id);

    char path[128];
    snprintf(path, sizeof(path), "/services/%s/execute", service_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free(payload);
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", path, payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (timeout_ms > 0) ? (timeout_ms / 1000 + 30) : 120L);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(payload);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to execute in service: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_result_t *result = parse_execute_response(response.data);
    free(response.data);
    return result;
}

char *unsandbox_service_env_get(
    const char *service_id,
    const char *public_key, const char *secret_key) {

    if (!service_id) {
        set_last_error("Service ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/services/%s/env", API_BASE, service_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/services/%s/env", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get env: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    // Return the raw JSON response (caller can parse as needed)
    return response.data;
}

char *unsandbox_service_env_export(
    const char *service_id,
    const char *public_key, const char *secret_key) {

    if (!service_id) {
        set_last_error("Service ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/services/%s/env?format=export", API_BASE, service_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/services/%s/env", service_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to export env: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    // Return the export-formatted env content
    return response.data;
}

/* Snapshot - full library implementations */

unsandbox_snapshot_list_t *unsandbox_snapshot_list(
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/snapshots", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/snapshots", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to list snapshots: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    int count = count_json_array_objects(response.data, "snapshots");
    unsandbox_snapshot_list_t *list = calloc(1, sizeof(unsandbox_snapshot_list_t));
    if (!list) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    if (count > 0) {
        list->snapshots = calloc(count, sizeof(unsandbox_snapshot_t));
        if (!list->snapshots) {
            set_last_error("Out of memory");
            free(list);
            free(response.data);
            return NULL;
        }
        list->count = count;

        const char *snapshots_start = strstr(response.data, "\"snapshots\":[");
        if (snapshots_start) {
            const char *pos = snapshots_start + 13;
            for (size_t i = 0; i < list->count && pos; i++) {
                pos = strchr(pos, '{');
                if (!pos) break;

                list->snapshots[i].id = extract_json_string(pos, "id");
                list->snapshots[i].name = extract_json_string(pos, "name");
                list->snapshots[i].type = extract_json_string(pos, "type");
                list->snapshots[i].source_id = extract_json_string(pos, "source_id");
                list->snapshots[i].hot = (int)extract_json_number(pos, "hot");
                list->snapshots[i].locked = (int)extract_json_number(pos, "locked");
                list->snapshots[i].created_at = extract_json_number(pos, "created_at");
                list->snapshots[i].size_bytes = extract_json_number(pos, "size_bytes");

                pos = skip_json_object(pos);
            }
        }
    }

    free(response.data);
    return list;
}

unsandbox_snapshot_t *unsandbox_snapshot_get(
    const char *snapshot_id,
    const char *public_key, const char *secret_key) {

    if (!snapshot_id) {
        set_last_error("Snapshot ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/snapshots/%s", API_BASE, snapshot_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/snapshots/%s", snapshot_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get snapshot: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_snapshot_t *snapshot = calloc(1, sizeof(unsandbox_snapshot_t));
    if (!snapshot) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    snapshot->id = extract_json_string(response.data, "id");
    snapshot->name = extract_json_string(response.data, "name");
    snapshot->type = extract_json_string(response.data, "type");
    snapshot->source_id = extract_json_string(response.data, "source_id");
    snapshot->hot = (int)extract_json_number(response.data, "hot");
    snapshot->locked = (int)extract_json_number(response.data, "locked");
    snapshot->created_at = extract_json_number(response.data, "created_at");
    snapshot->size_bytes = extract_json_number(response.data, "size_bytes");

    free(response.data);
    return snapshot;
}

char *unsandbox_snapshot_session(
    const char *session_id, const char *name, int hot,
    const char *public_key, const char *secret_key) {

    if (!session_id) {
        set_last_error("Session ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    // Build payload
    char payload[512];
    char *p = payload;
    p += sprintf(p, "{\"source_type\":\"session\",\"source_id\":\"%s\"", session_id);
    if (name && strlen(name) > 0) {
        char *esc_name = escape_json_string(name);
        p += sprintf(p, ",\"name\":\"%s\"", esc_name);
        free(esc_name);
    }
    if (hot) {
        p += sprintf(p, ",\"hot\":true");
    }
    p += sprintf(p, "}");

    char url[256];
    snprintf(url, sizeof(url), "%s/snapshots", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/snapshots", payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || (http_code != 200 && http_code != 201)) {
        set_last_error("Failed to create snapshot: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    char *snapshot_id = extract_json_string(response.data, "id");
    free(response.data);
    return snapshot_id;
}

char *unsandbox_snapshot_service(
    const char *service_id, const char *name, int hot,
    const char *public_key, const char *secret_key) {

    if (!service_id) {
        set_last_error("Service ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    // Build payload
    char payload[512];
    char *p = payload;
    p += sprintf(p, "{\"source_type\":\"service\",\"source_id\":\"%s\"", service_id);
    if (name && strlen(name) > 0) {
        char *esc_name = escape_json_string(name);
        p += sprintf(p, ",\"name\":\"%s\"", esc_name);
        free(esc_name);
    }
    if (hot) {
        p += sprintf(p, ",\"hot\":true");
    }
    p += sprintf(p, "}");

    char url[256];
    snprintf(url, sizeof(url), "%s/snapshots", API_BASE);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/snapshots", payload);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || (http_code != 200 && http_code != 201)) {
        set_last_error("Failed to create snapshot: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    char *snapshot_id = extract_json_string(response.data, "id");
    free(response.data);
    return snapshot_id;
}

/* ============================================================================
 * Image API - Library Implementations
 * ============================================================================ */

unsandbox_image_list_t *unsandbox_image_list(
    const char *filter,
    const char *public_key, const char *secret_key) {

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    char path[128];
    if (filter && strlen(filter) > 0) {
        snprintf(url, sizeof(url), "%s/images?filter=%s", API_BASE, filter);
        snprintf(path, sizeof(path), "/images?filter=%s", filter);
    } else {
        snprintf(url, sizeof(url), "%s/images", API_BASE);
        snprintf(path, sizeof(path), "/images");
    }

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", "/images", NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to list images: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    int count = count_json_array_objects(response.data, "images");
    unsandbox_image_list_t *list = calloc(1, sizeof(unsandbox_image_list_t));
    if (!list) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    if (count > 0) {
        list->images = calloc(count, sizeof(unsandbox_image_t));
        if (!list->images) {
            set_last_error("Out of memory");
            free(list);
            free(response.data);
            return NULL;
        }
        list->count = count;

        const char *images_start = strstr(response.data, "\"images\":[");
        if (images_start) {
            const char *pos = images_start + 10;
            for (size_t i = 0; i < list->count && pos; i++) {
                pos = strchr(pos, '{');
                if (!pos) break;

                list->images[i].id = extract_json_string(pos, "id");
                list->images[i].name = extract_json_string(pos, "name");
                list->images[i].description = extract_json_string(pos, "description");
                list->images[i].visibility = extract_json_string(pos, "visibility");
                list->images[i].source_type = extract_json_string(pos, "source_type");
                list->images[i].source_id = extract_json_string(pos, "source_id");
                list->images[i].owner_api_key = extract_json_string(pos, "owner_api_key");
                list->images[i].locked = (int)extract_json_number(pos, "locked");
                list->images[i].created_at = extract_json_number(pos, "created_at");
                list->images[i].size_bytes = extract_json_number(pos, "size_bytes");

                pos = skip_json_object(pos);
            }
        }
    }

    free(response.data);
    return list;
}

unsandbox_image_t *unsandbox_image_get(
    const char *image_id,
    const char *public_key, const char *secret_key) {

    if (!image_id) {
        set_last_error("Image ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char url[256];
    snprintf(url, sizeof(url), "%s/images/%s", API_BASE, image_id);

    CURL *curl = curl_easy_init();
    if (!curl) {
        set_last_error("Failed to initialize curl");
        free_credentials(creds);
        return NULL;
    }

    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    char path[128];
    snprintf(path, sizeof(path), "/images/%s", image_id);

    struct curl_slist *headers = NULL;
    headers = add_hmac_auth_headers(headers, creds, "GET", path, NULL);

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free_credentials(creds);

    if (res != CURLE_OK || http_code != 200) {
        set_last_error("Failed to get image: HTTP %ld", http_code);
        free(response.data);
        return NULL;
    }

    unsandbox_image_t *image = calloc(1, sizeof(unsandbox_image_t));
    if (!image) {
        set_last_error("Out of memory");
        free(response.data);
        return NULL;
    }

    image->id = extract_json_string(response.data, "id");
    image->name = extract_json_string(response.data, "name");
    image->description = extract_json_string(response.data, "description");
    image->visibility = extract_json_string(response.data, "visibility");
    image->source_type = extract_json_string(response.data, "source_type");
    image->source_id = extract_json_string(response.data, "source_id");
    image->owner_api_key = extract_json_string(response.data, "owner_api_key");
    image->locked = (int)extract_json_number(response.data, "locked");
    image->created_at = extract_json_number(response.data, "created_at");
    image->size_bytes = extract_json_number(response.data, "size_bytes");

    free(response.data);
    return image;
}

char *unsandbox_image_publish(
    const char *source_type, const char *source_id,
    const char *name, const char *description,
    const char *public_key, const char *secret_key) {

    if (!source_type || !source_id) {
        set_last_error("Source type and ID are required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    // Use internal function
    char *result = image_publish(creds, source_type, source_id, name, description);
    free_credentials(creds);

    if (!result) {
        set_last_error("Failed to publish image");
        return NULL;
    }

    char *image_id = extract_json_string(result, "id");
    free(result);
    return image_id;
}

int unsandbox_image_delete(
    const char *image_id,
    const char *public_key, const char *secret_key) {

    if (!image_id) {
        set_last_error("Image ID is required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = delete_image(creds, image_id);
    free_credentials(creds);
    return result;
}

int unsandbox_image_lock(
    const char *image_id,
    const char *public_key, const char *secret_key) {

    if (!image_id) {
        set_last_error("Image ID is required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = lock_image(creds, image_id);
    free_credentials(creds);
    return result;
}

int unsandbox_image_unlock(
    const char *image_id,
    const char *public_key, const char *secret_key) {

    if (!image_id) {
        set_last_error("Image ID is required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = unlock_image(creds, image_id);
    free_credentials(creds);
    return result;
}

int unsandbox_image_set_visibility(
    const char *image_id, const char *visibility,
    const char *public_key, const char *secret_key) {

    if (!image_id || !visibility) {
        set_last_error("Image ID and visibility are required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = set_image_visibility(creds, image_id, visibility);
    free_credentials(creds);
    return result;
}

int unsandbox_image_grant_access(
    const char *image_id, const char *trusted_api_key,
    const char *public_key, const char *secret_key) {

    if (!image_id || !trusted_api_key) {
        set_last_error("Image ID and trusted API key are required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = grant_image_access(creds, image_id, trusted_api_key);
    free_credentials(creds);
    return result;
}

int unsandbox_image_revoke_access(
    const char *image_id, const char *trusted_api_key,
    const char *public_key, const char *secret_key) {

    if (!image_id || !trusted_api_key) {
        set_last_error("Image ID and trusted API key are required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = revoke_image_access(creds, image_id, trusted_api_key);
    free_credentials(creds);
    return result;
}

char **unsandbox_image_list_trusted(
    const char *image_id, size_t *count,
    const char *public_key, const char *secret_key) {

    if (!image_id || !count) {
        set_last_error("Image ID and count pointer are required");
        return NULL;
    }

    *count = 0;

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *result = list_image_trusted(creds, image_id);
    free_credentials(creds);

    if (!result) {
        set_last_error("Failed to list trusted keys");
        return NULL;
    }

    // Count keys in response
    const char *keys_start = strstr(result, "\"trusted_keys\":[");
    if (!keys_start) {
        free(result);
        return NULL;
    }

    size_t key_count = 0;
    const char *p = strchr(keys_start, '[');
    if (p) {
        p++;
        while (*p && *p != ']') {
            if (*p == '"') {
                key_count++;
                p++;
                while (*p && *p != '"') p++;
            }
            if (*p) p++;
        }
    }

    if (key_count == 0) {
        free(result);
        return NULL;
    }

    char **keys = calloc(key_count, sizeof(char *));
    if (!keys) {
        set_last_error("Out of memory");
        free(result);
        return NULL;
    }

    // Parse keys
    p = strchr(keys_start, '[');
    if (p) {
        p++;
        size_t i = 0;
        while (*p && *p != ']' && i < key_count) {
            if (*p == '"') {
                p++;
                const char *end = strchr(p, '"');
                if (end) {
                    size_t len = end - p;
                    keys[i] = malloc(len + 1);
                    if (keys[i]) {
                        memcpy(keys[i], p, len);
                        keys[i][len] = '\0';
                        i++;
                    }
                    p = end;
                }
            }
            if (*p) p++;
        }
        *count = i;
    }

    free(result);
    return keys;
}

int unsandbox_image_transfer(
    const char *image_id, const char *to_api_key,
    const char *public_key, const char *secret_key) {

    if (!image_id || !to_api_key) {
        set_last_error("Image ID and destination API key are required");
        return -1;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return -1;
    }

    int result = transfer_image(creds, image_id, to_api_key);
    free_credentials(creds);
    return result;
}

char *unsandbox_image_spawn(
    const char *image_id, const char *name, const char *ports,
    const char *bootstrap, const char *network_mode,
    const char *public_key, const char *secret_key) {

    if (!image_id) {
        set_last_error("Image ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *result = spawn_from_image(creds, image_id, name, ports, bootstrap, network_mode);
    free_credentials(creds);

    if (!result) {
        set_last_error("Failed to spawn from image");
        return NULL;
    }

    char *service_id = extract_json_string(result, "id");
    free(result);
    return service_id;
}

char *unsandbox_image_clone(
    const char *image_id, const char *name, const char *description,
    const char *public_key, const char *secret_key) {

    if (!image_id) {
        set_last_error("Image ID is required");
        return NULL;
    }

    UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
    if (!creds) {
        set_last_error("No credentials available");
        return NULL;
    }

    char *result = clone_image(creds, image_id, name, description);
    free_credentials(creds);

    if (!result) {
        set_last_error("Failed to clone image");
        return NULL;
    }

    char *cloned_id = extract_json_string(result, "id");
    free(result);
    return cloned_id;
}

/* ============================================================================
 * Memory Management for Images
 * ============================================================================ */

void unsandbox_free_image(unsandbox_image_t *image) {
    if (!image) return;
    free(image->id);
    free(image->name);
    free(image->description);
    free(image->visibility);
    free(image->source_type);
    free(image->source_id);
    free(image->owner_api_key);
    free(image);
}

void unsandbox_free_image_list(unsandbox_image_list_t *images) {
    if (!images) return;
    for (size_t i = 0; i < images->count; i++) {
        free(images->images[i].id);
        free(images->images[i].name);
        free(images->images[i].description);
        free(images->images[i].visibility);
        free(images->images[i].source_type);
        free(images->images[i].source_id);
        free(images->images[i].owner_api_key);
    }
    free(images->images);
    free(images);
}

void unsandbox_free_trusted_keys(char **keys, size_t count) {
    if (!keys) return;
    for (size_t i = 0; i < count; i++) {
        free(keys[i]);
    }
    free(keys);
}

/* Utility */
const char *unsandbox_last_error(void) {
    return unsandbox_error_buffer[0] ? unsandbox_error_buffer : NULL;
}
#endif /* UNSANDBOX_LIBRARY - end of library API */

#ifndef UNSANDBOX_LIBRARY
int main(int argc, char *argv[]) {
    // Disable stdout buffering for real-time output
    setvbuf(stdout, NULL, _IONBF, 0);

    const char *filename = NULL;
    const char *cli_public_key = NULL;   // -p flag
    const char *cli_secret_key = NULL;   // -k flag
    int cli_account_index = -1;          // --account flag (-1 = use env or default)
    const char *artifact_dir = NULL;
    const char *network_mode = NULL;
    const char *shell = NULL;  // -s/--shell for language
    int vcpu = 0;  // 0 = default (1), valid values: 1, 2, 4, 8
    int ttl = 0;   // 0 = default (60s), valid values: 1-900
    int save_artifacts = 0;
    int skip_confirm = 0;  // -y flag to skip large upload confirmation

    struct InputFile input_files[MAX_INPUT_FILES];
    int input_file_count = 0;
    long total_input_size = 0;  // Track total input file size

    struct EnvVar env_vars[MAX_ENV_VARS];
    int env_var_count = 0;

    // Check for key command first
    if (argc >= 2 && strcmp(argv[1], "key") == 0) {
        int do_extend = 0;

        // Parse options
        for (int i = 2; i < argc; i++) {
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "--extend") == 0) {
                do_extend = 1;
            }
        }

        // Get credentials (priority: flags > env > file with --account)
        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);

        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        // Handle --extend: validate key to get public key, then open portal
        if (do_extend) {
            // If we have public_key from credentials, use it directly
            if (creds->public_key) {
                char extend_url[512];
                snprintf(extend_url, sizeof(extend_url), "%s/keys/extend?pk=%s", PORTAL_BASE, creds->public_key);
                printf("Opening extension page in browser...\n");
                printf("If browser doesn't open, visit: %s\n", extend_url);

                // Try to open URL in browser
                #ifdef __APPLE__
                char cmd[1024];
                snprintf(cmd, sizeof(cmd), "open '%s'", extend_url);
                if (system(cmd) != 0) { /* ignore */ }
                #elif defined(__linux__)
                char cmd[2048];
                snprintf(cmd, sizeof(cmd), "xdg-open '%s' 2>/dev/null || sensible-browser '%s' 2>/dev/null", extend_url, extend_url);
                if (system(cmd) != 0) { /* ignore */ }
                #elif defined(_WIN32)
                char cmd[1024];
                snprintf(cmd, sizeof(cmd), "start %s", extend_url);
                if (system(cmd) != 0) { /* ignore */ }
                #endif

                free_credentials(creds);
                return 0;
            }
        }

        // Default: validate key using HMAC authentication
        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret = validate_api_key(creds);
        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Check for languages command
    if (argc >= 2 && strcmp(argv[1], "languages") == 0) {
        int json_output = 0;

        // Parse options
        for (int i = 2; i < argc; i++) {
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "--json") == 0) {
                json_output = 1;
            } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                fprintf(stderr, "Usage: %s languages [options]\n\n", argv[0]);
                fprintf(stderr, "List all available languages for code execution.\n\n");
                fprintf(stderr, "Options:\n");
                fprintf(stderr, "  --json    Output as JSON array (for scripts)\n");
                fprintf(stderr, "  -p KEY    Public key\n");
                fprintf(stderr, "  -k KEY    Secret key\n");
                fprintf(stderr, "  -h        Show this help\n");
                return 0;
            }
        }

        // Get credentials
        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);

        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret = list_languages_cli(creds, json_output);
        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Check for snapshot command
    if (argc >= 2 && strcmp(argv[1], "snapshot") == 0) {
        const char *snapshot_id = NULL;
        const char *clone_type = NULL;
        const char *clone_name = NULL;
        const char *clone_shell = NULL;
        const char *clone_ports = NULL;
        int do_list = 0;
        int do_info = 0;
        int do_delete = 0;
        int do_lock = 0;
        int do_unlock = 0;
        int do_clone = 0;
        int show_help = 0;

        // Parse snapshot-specific args
        for (int i = 2; i < argc; i++) {
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0) {
                do_list = 1;
            } else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) {
                do_info = 1;
                i++;
                snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--delete") == 0 && i + 1 < argc) {
                do_delete = 1;
                i++;
                snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--lock") == 0 && i + 1 < argc) {
                do_lock = 1;
                i++;
                snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--unlock") == 0 && i + 1 < argc) {
                do_unlock = 1;
                i++;
                snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--clone") == 0 && i + 1 < argc) {
                do_clone = 1;
                i++;
                snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--type") == 0 && i + 1 < argc) {
                i++;
                clone_type = argv[i];
            } else if (strcmp(argv[i], "--name") == 0 && i + 1 < argc) {
                i++;
                clone_name = argv[i];
            } else if (strcmp(argv[i], "--shell") == 0 && i + 1 < argc) {
                i++;
                clone_shell = argv[i];
            } else if (strcmp(argv[i], "--ports") == 0 && i + 1 < argc) {
                i++;
                clone_ports = argv[i];
            } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                show_help = 1;
            } else if (argv[i][0] == '-') {
                fprintf(stderr, "Unknown option: %s\n", argv[i]);
                print_usage(argv[0]);
                return 1;
            }
        }

        if (show_help) {
            print_usage(argv[0]);
            return 0;
        }

        // Get credentials
        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);
        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret = 0;

        if (do_list) {
            ret = list_snapshots(creds);
        } else if (do_info) {
            ret = get_snapshot_info(creds, snapshot_id);
        } else if (do_delete) {
            ret = delete_snapshot(creds, snapshot_id);
        } else if (do_lock) {
            ret = lock_snapshot(creds, snapshot_id);
        } else if (do_unlock) {
            ret = unlock_snapshot(creds, snapshot_id);
        } else if (do_clone) {
            if (!clone_type) {
                fprintf(stderr, "Error: --type required for --clone (session or service)\n");
                curl_global_cleanup();
                free_credentials(creds);
                return 1;
            }
            if (strcmp(clone_type, "session") != 0 && strcmp(clone_type, "service") != 0) {
                fprintf(stderr, "Error: --type must be 'session' or 'service'\n");
                curl_global_cleanup();
                free_credentials(creds);
                return 1;
            }
            ret = clone_snapshot(creds, snapshot_id, clone_type, clone_name, clone_shell, clone_ports);
        } else {
            fprintf(stderr, "Error: No snapshot action specified. Use --list, --info, --delete, --lock, --unlock, or --clone\n");
            print_usage(argv[0]);
            curl_global_cleanup();
            free_credentials(creds);
            return 1;
        }

        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Check for image command
    if (argc >= 2 && strcmp(argv[1], "image") == 0) {
        const char *image_id = NULL;
        const char *visibility = NULL;
        const char *spawn_name = NULL;
        const char *spawn_ports = NULL;
        const char *source_type = NULL;
        int do_list = 0;
        int do_info = 0;
        int do_delete = 0;
        int do_lock = 0;
        int do_unlock = 0;
        int do_publish = 0;
        int do_visibility = 0;
        int do_spawn = 0;
        int do_clone = 0;
        int show_help = 0;

        // Parse image-specific args
        for (int i = 2; i < argc; i++) {
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0) {
                do_list = 1;
            } else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) {
                do_info = 1;
                i++;
                image_id = argv[i];
            } else if (strcmp(argv[i], "--delete") == 0 && i + 1 < argc) {
                do_delete = 1;
                i++;
                image_id = argv[i];
            } else if (strcmp(argv[i], "--lock") == 0 && i + 1 < argc) {
                do_lock = 1;
                i++;
                image_id = argv[i];
            } else if (strcmp(argv[i], "--unlock") == 0 && i + 1 < argc) {
                do_unlock = 1;
                i++;
                image_id = argv[i];
            } else if (strcmp(argv[i], "--publish") == 0 && i + 1 < argc) {
                do_publish = 1;
                i++;
                image_id = argv[i];  // This is actually the source ID (service/snapshot)
            } else if (strcmp(argv[i], "--visibility") == 0 && i + 2 < argc) {
                do_visibility = 1;
                i++;
                image_id = argv[i];
                i++;
                visibility = argv[i];
            } else if (strcmp(argv[i], "--spawn") == 0 && i + 1 < argc) {
                do_spawn = 1;
                i++;
                image_id = argv[i];
            } else if (strcmp(argv[i], "--clone") == 0 && i + 1 < argc) {
                do_clone = 1;
                i++;
                image_id = argv[i];
            } else if (strcmp(argv[i], "--name") == 0 && i + 1 < argc) {
                i++;
                spawn_name = argv[i];
            } else if (strcmp(argv[i], "--ports") == 0 && i + 1 < argc) {
                i++;
                spawn_ports = argv[i];
            } else if (strcmp(argv[i], "--source-type") == 0 && i + 1 < argc) {
                i++;
                source_type = argv[i];
            } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                show_help = 1;
            } else if (argv[i][0] == '-') {
                fprintf(stderr, "Unknown option: %s\n", argv[i]);
                print_usage(argv[0]);
                return 1;
            }
        }

        if (show_help) {
            print_usage(argv[0]);
            return 0;
        }

        // Get credentials
        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);
        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret = 0;

        if (do_list) {
            char *response = list_images(creds, NULL);
            if (response) {
                // Parse and display image list from JSON response
                const char *images_start = strstr(response, "\"images\":[");
                if (!images_start) {
                    printf("No images found.\n");
                    free(response);
                } else {
                    // Count images
                    int count = 0;
                    const char *p = images_start;
                    while ((p = strchr(p + 1, '{')) != NULL && strchr(images_start, ']') && p < strchr(images_start, ']')) {
                        count++;
                    }

                    if (count == 0) {
                        printf("No images found.\n");
                    } else {
                        printf("%-40s %-20s %-10s %-8s %-10s\n", "ID", "NAME", "VISIBILITY", "LOCKED", "SOURCE");
                        printf("%-40s %-20s %-10s %-8s %-10s\n", "----------------------------------------",
                               "--------------------", "----------", "--------", "----------");

                        // Parse each image object
                        const char *pos = strchr(images_start, '[');
                        if (pos) {
                            while ((pos = strchr(pos, '{')) != NULL) {
                                // Check we haven't passed the end of the array
                                const char *arr_end = strchr(images_start, ']');
                                if (arr_end && pos > arr_end) break;

                                char *img_id = extract_json_string(pos, "id");
                                char *img_name = extract_json_string(pos, "name");
                                char *img_visibility = extract_json_string(pos, "visibility");
                                char *img_source = extract_json_string(pos, "source_type");

                                // Extract locked boolean
                                const char *locked_str = strstr(pos, "\"locked\":");
                                int locked = 0;
                                if (locked_str && locked_str < strchr(pos, '}')) {
                                    locked_str += 9;
                                    while (*locked_str == ' ') locked_str++;
                                    locked = (strncmp(locked_str, "true", 4) == 0);
                                }

                                printf("%-40s %-20s %-10s %-8s %-10s\n",
                                       img_id ? img_id : "",
                                       img_name ? img_name : "",
                                       img_visibility ? img_visibility : "",
                                       locked ? "yes" : "no",
                                       img_source ? img_source : "");

                                if (img_id) free(img_id);
                                if (img_name) free(img_name);
                                if (img_visibility) free(img_visibility);
                                if (img_source) free(img_source);

                                pos++;
                            }
                        }
                    }
                    free(response);
                }
            } else {
                fprintf(stderr, "Error: Failed to list images\n");
                ret = 1;
            }
        } else if (do_info) {
            char *response = get_image(creds, image_id);
            if (response) {
                printf("%s\n", response);
                free(response);
            } else {
                fprintf(stderr, "Error: Failed to get image info\n");
                ret = 1;
            }
        } else if (do_delete) {
            ret = delete_image(creds, image_id);
            if (ret == 0) {
                printf("Image deleted: %s\n", image_id);
            }
        } else if (do_lock) {
            ret = lock_image(creds, image_id);
            if (ret == 0) {
                printf("Image locked: %s\n", image_id);
            }
        } else if (do_unlock) {
            ret = unlock_image(creds, image_id);
            if (ret == 0) {
                printf("Image unlocked: %s\n", image_id);
            }
        } else if (do_visibility) {
            if (!visibility || (strcmp(visibility, "private") != 0 &&
                               strcmp(visibility, "unlisted") != 0 &&
                               strcmp(visibility, "public") != 0)) {
                fprintf(stderr, "Error: visibility must be 'private', 'unlisted', or 'public'\n");
                ret = 1;
            } else {
                ret = set_image_visibility(creds, image_id, visibility);
                if (ret == 0) {
                    printf("Image visibility set to %s: %s\n", visibility, image_id);
                }
            }
        } else if (do_spawn) {
            char *result = spawn_from_image(creds, image_id, spawn_name, spawn_ports, NULL, NULL);
            if (result) {
                printf("%s\n", result);
                free(result);
            } else {
                fprintf(stderr, "Error: Failed to spawn from image\n");
                ret = 1;
            }
        } else if (do_publish) {
            if (!source_type) {
                fprintf(stderr, "Error: --source-type required for --publish (service or snapshot)\n");
                ret = 1;
            } else {
                char *result = image_publish(creds, source_type, image_id, spawn_name, NULL);
                if (result) {
                    printf("Image published: %s\n", result);
                    free(result);
                } else {
                    fprintf(stderr, "Error: Failed to publish image\n");
                    ret = 1;
                }
            }
        } else if (do_clone) {
            char *result = clone_image(creds, image_id, spawn_name, NULL);
            if (result) {
                printf("Image cloned: %s\n", result);
                free(result);
            } else {
                fprintf(stderr, "Error: Failed to clone image\n");
                ret = 1;
            }
        } else {
            fprintf(stderr, "Error: No image action specified. Use --list, --info, --delete, --lock, --unlock, --visibility, --spawn, --publish, or --clone\n");
            print_usage(argv[0]);
            ret = 1;
        }

        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Check for service command first
    if (argc >= 2 && strcmp(argv[1], "service") == 0) {
        const char *service_name = NULL;
        const char *service_ports = NULL;
        const char *service_domains = NULL;
        const char *service_type = NULL;
        const char *golden_image = NULL;
        const char *service_bootstrap = NULL;
        const char *bootstrap_file = NULL;
        const char *service_id = NULL;
        struct InputFile service_input_files[MAX_INPUT_FILES];
        int service_input_file_count = 0;
        int show_help = 0;
        int do_list = 0;
        int do_info = 0;
        int do_tail = 0;
        int do_logs = 0;
        int do_download_logs = 0;
        const char *download_logs_file = NULL;
        int do_freeze = 0;
        int do_unfreeze = 0;
        int do_destroy = 0;
        int do_lock = 0;
        int do_unlock = 0;
        int do_auto_unfreeze = 0;
        int do_no_auto_unfreeze = 0;
        int do_show_freeze_page = 0;
        int do_no_show_freeze_page = 0;
        int do_resize = 0;
        int do_redeploy = 0;
        int do_execute = 0;
        const char *execute_command = NULL;
        int execute_timeout = 30000;  // Default 30 seconds
        int do_dump_bootstrap = 0;
        const char *dump_bootstrap_file = NULL;
        int do_snapshot = 0;
        int do_restore = 0;
        const char *restore_snapshot_id = NULL;
        const char *snapshot_name = NULL;
        int hot_snapshot = 0;
        int do_add_domain = 0;
        int do_remove_domain = 0;
        int do_set_domains = 0;
        int create_unfreeze_on_demand = 0;  // For --unfreeze-on-demand flag on create

        // Environment variables for service create
        char *service_env_content = NULL;  // Accumulated env vars (KEY=VALUE\n...)
        size_t service_env_size = 0;
        size_t service_env_capacity = 0;
        const char *service_env_file = NULL;  // --env-file path

        // Env subcommand: un service env status|set|export|delete <name>
        const char *env_subcommand = NULL;
        const char *env_target_id = NULL;

        // Parse service-specific args (flags like session)
        for (int i = 2; i < argc; i++) {
            // Check for "env" subcommand: un service env <action> <id>
            if (strcmp(argv[i], "env") == 0 && i + 2 < argc) {
                env_subcommand = argv[i + 1];  // status, set, export, delete
                env_target_id = argv[i + 2];   // service name/id
                i += 2;
                // Continue parsing for --env-file in case of "set"
                continue;
            }
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
                i++;
                network_mode = argv[i];
            } else if (strcmp(argv[i], "--name") == 0 && i + 1 < argc) {
                i++;
                service_name = argv[i];
            } else if (strcmp(argv[i], "--ports") == 0 && i + 1 < argc) {
                i++;
                service_ports = argv[i];
            } else if (strcmp(argv[i], "--domains") == 0 && i + 1 < argc) {
                i++;
                service_domains = argv[i];
            } else if (strcmp(argv[i], "--type") == 0 && i + 1 < argc) {
                i++;
                service_type = argv[i];
            } else if (strcmp(argv[i], "--golden-image") == 0 && i + 1 < argc) {
                i++;
                golden_image = argv[i];
            } else if (strcmp(argv[i], "--bootstrap") == 0 && i + 1 < argc) {
                i++;
                service_bootstrap = argv[i];
            } else if (strcmp(argv[i], "--bootstrap-file") == 0 && i + 1 < argc) {
                i++;
                bootstrap_file = argv[i];
            } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
                i++;
                if (service_input_file_count >= MAX_INPUT_FILES) {
                    fprintf(stderr, "Error: too many input files (max %d)\n", MAX_INPUT_FILES);
                    return 1;
                }
                // Read file and base64 encode
                size_t fsize;
                char *content = read_file(argv[i], &fsize);
                if (!content) {
                    fprintf(stderr, "Error: cannot read file '%s'\n", argv[i]);
                    return 1;
                }
                size_t b64_len;
                char *b64 = base64_encode((unsigned char *)content, fsize, &b64_len);
                free(content);
                if (!b64) {
                    fprintf(stderr, "Error: failed to encode file '%s'\n", argv[i]);
                    return 1;
                }
                service_input_files[service_input_file_count].filename = strdup(get_basename(argv[i]));
                service_input_files[service_input_file_count].content_base64 = b64;
                service_input_file_count++;
            } else if (strcmp(argv[i], "-F") == 0 && i + 1 < argc) {
                // -F preserves relative path (for directory structures)
                i++;
                if (service_input_file_count >= MAX_INPUT_FILES) {
                    fprintf(stderr, "Error: too many input files (max %d)\n", MAX_INPUT_FILES);
                    return 1;
                }
                size_t fsize;
                char *content = read_file(argv[i], &fsize);
                if (!content) {
                    fprintf(stderr, "Error: cannot read file '%s'\n", argv[i]);
                    return 1;
                }
                size_t b64_len;
                char *b64 = base64_encode((unsigned char *)content, fsize, &b64_len);
                free(content);
                if (!b64) {
                    fprintf(stderr, "Error: failed to encode file '%s'\n", argv[i]);
                    return 1;
                }
                // Use full path instead of basename
                service_input_files[service_input_file_count].filename = strdup(argv[i]);
                service_input_files[service_input_file_count].content_base64 = b64;
                service_input_file_count++;
            } else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0) {
                do_list = 1;
            } else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) {
                do_info = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--tail") == 0 && i + 1 < argc) {
                do_tail = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--logs") == 0 && i + 1 < argc) {
                do_logs = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--download-logs") == 0 && i + 2 < argc) {
                do_download_logs = 1;
                i++;
                service_id = argv[i];
                i++;
                download_logs_file = argv[i];
            } else if (strcmp(argv[i], "--freeze") == 0 && i + 1 < argc) {
                do_freeze = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--unfreeze") == 0 && i + 1 < argc) {
                do_unfreeze = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--destroy") == 0 && i + 1 < argc) {
                do_destroy = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--lock") == 0 && i + 1 < argc) {
                do_lock = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--unlock") == 0 && i + 1 < argc) {
                do_unlock = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--add-domain") == 0 && i + 1 < argc) {
                do_add_domain = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--remove-domain") == 0 && i + 1 < argc) {
                do_remove_domain = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--set-domains") == 0 && i + 1 < argc) {
                do_set_domains = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--auto-unfreeze") == 0 && i + 1 < argc) {
                do_auto_unfreeze = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--no-auto-unfreeze") == 0 && i + 1 < argc) {
                do_no_auto_unfreeze = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--show-freeze-page") == 0 && i + 1 < argc) {
                do_show_freeze_page = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--no-show-freeze-page") == 0 && i + 1 < argc) {
                do_no_show_freeze_page = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--resize") == 0 && i + 1 < argc) {
                do_resize = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--redeploy") == 0 && i + 1 < argc) {
                do_redeploy = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--execute") == 0 && i + 2 < argc) {
                do_execute = 1;
                i++;
                service_id = argv[i];
                i++;
                execute_command = argv[i];
            } else if ((strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timeout") == 0) && i + 1 < argc) {
                i++;
                execute_timeout = atoi(argv[i]) * 1000;  // Convert seconds to ms
                if (execute_timeout < 0) execute_timeout = 0;  // 0 = unlimited
                // No max cap - let it run as long as needed
            } else if (strcmp(argv[i], "--dump-bootstrap") == 0 && i + 1 < argc) {
                do_dump_bootstrap = 1;
                i++;
                service_id = argv[i];
                // Optional file argument
                if (i + 1 < argc && argv[i + 1][0] != '-') {
                    i++;
                    dump_bootstrap_file = argv[i];
                }
            } else if ((strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--vcpu") == 0) && i + 1 < argc) {
                i++;
                vcpu = atoi(argv[i]);
                if (vcpu < 1 || vcpu > 8) {
                    fprintf(stderr, "Error: -v/--vcpu must be 1-8\n");
                    return 1;
                }
            } else if (strcmp(argv[i], "--snapshot") == 0 && i + 1 < argc) {
                do_snapshot = 1;
                i++;
                service_id = argv[i];
            } else if (strcmp(argv[i], "--restore") == 0 && i + 1 < argc) {
                do_restore = 1;
                i++;
                restore_snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--snapshot-name") == 0 && i + 1 < argc) {
                i++;
                snapshot_name = argv[i];
            } else if (strcmp(argv[i], "--hot") == 0) {
                hot_snapshot = 1;
            } else if (strcmp(argv[i], "--unfreeze-on-demand") == 0) {
                create_unfreeze_on_demand = 1;
            } else if ((strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--env") == 0) && i + 1 < argc) {
                // -e KEY=VALUE or --env KEY=VALUE - accumulate env vars
                i++;
                const char *env_pair = argv[i];
                // Validate format: must contain '='
                if (!strchr(env_pair, '=')) {
                    fprintf(stderr, "Error: Invalid environment variable format '%s'. Use KEY=VALUE\n", env_pair);
                    return 1;
                }
                // Add to accumulated env content
                size_t pair_len = strlen(env_pair);
                size_t needed = service_env_size + pair_len + 2;  // +1 for newline, +1 for null
                if (needed > service_env_capacity) {
                    service_env_capacity = needed > 4096 ? needed * 2 : 4096;
                    char *new_content = realloc(service_env_content, service_env_capacity);
                    if (!new_content) {
                        fprintf(stderr, "Error: Out of memory\n");
                        free(service_env_content);
                        return 1;
                    }
                    service_env_content = new_content;
                }
                memcpy(service_env_content + service_env_size, env_pair, pair_len);
                service_env_size += pair_len;
                service_env_content[service_env_size++] = '\n';
                service_env_content[service_env_size] = '\0';
            } else if (strcmp(argv[i], "--env-file") == 0 && i + 1 < argc) {
                i++;
                service_env_file = argv[i];
            } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                show_help = 1;
            }
        }

        // Show help if requested or no action specified
        if (show_help) {
            print_usage(argv[0]);
            return 0;
        }

        // Get credentials (priority: env > flags > file)
        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);
        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret = 0;

        // Handle env subcommand first
        if (env_subcommand) {
            if (strcmp(env_subcommand, "status") == 0) {
                ret = service_env_status(creds, env_target_id);
            } else if (strcmp(env_subcommand, "set") == 0) {
                // Read env content from --env-file, -e flags, or stdin
                char *env_content = NULL;
                if (service_env_file) {
                    env_content = read_env_file(service_env_file);
                } else if (service_env_content && service_env_size > 0) {
                    env_content = service_env_content;
                    service_env_content = NULL;  // Transfer ownership
                } else {
                    // Check if stdin is a TTY
                    if (isatty(STDIN_FILENO)) {
                        fprintf(stderr, "Reading environment variables from stdin (Ctrl+D to finish):\n");
                    }
                    env_content = read_env_stdin();
                }
                if (env_content) {
                    ret = service_env_set(creds, env_target_id, env_content);
                    free(env_content);
                } else {
                    ret = 1;
                }
            } else if (strcmp(env_subcommand, "export") == 0) {
                ret = service_env_export(creds, env_target_id);
            } else if (strcmp(env_subcommand, "delete") == 0) {
                ret = service_env_delete(creds, env_target_id);
            } else {
                fprintf(stderr, "Error: Unknown env subcommand '%s'. Use: status, set, export, delete\n", env_subcommand);
                ret = 1;
            }
            free(service_env_content);
            curl_global_cleanup();
            free_credentials(creds);
            return ret;
        }

        if (do_list) {
            ret = list_services(creds);
        } else if (do_info) {
            ret = get_service_info(creds, service_id);
        } else if (do_tail) {
            // --tail: last 9000 lines (default)
            char *log = get_service_logs(creds, service_id, 0);
            if (log) {
                printf("%s", log);
                free(log);
                ret = 0;
            } else {
                fprintf(stderr, "Error: Failed to fetch logs (service not found or no logs available)\n");
                ret = 1;
            }
        } else if (do_logs) {
            // --logs: all logs
            char *log = get_service_logs(creds, service_id, 1);
            if (log) {
                printf("%s", log);
                free(log);
                ret = 0;
            } else {
                fprintf(stderr, "Error: Failed to fetch logs (service not found or no logs available)\n");
                ret = 1;
            }
        } else if (do_download_logs) {
            // --download-logs: all logs to file
            char *log = get_service_logs(creds, service_id, 1);
            if (log) {
                FILE *f = fopen(download_logs_file, "w");
                if (f) {
                    fprintf(f, "%s", log);
                    fclose(f);
                    printf("Logs saved to %s\n", download_logs_file);
                    ret = 0;
                } else {
                    fprintf(stderr, "Error: Could not open file %s for writing\n", download_logs_file);
                    ret = 1;
                }
                free(log);
            } else {
                fprintf(stderr, "Error: Failed to fetch logs (service not found or no logs available)\n");
                ret = 1;
            }
        } else if (do_freeze) {
            ret = freeze_service(creds, service_id);
        } else if (do_unfreeze) {
            ret = unfreeze_service(creds, service_id);
        } else if (do_destroy) {
            ret = destroy_service(creds, service_id);
        } else if (do_lock) {
            ret = lock_service(creds, service_id);
        } else if (do_unlock) {
            ret = unlock_service(creds, service_id);
        } else if (do_add_domain) {
            if (!service_domains) {
                fprintf(stderr, "Error: --domains required with --add-domain\n");
                curl_global_cleanup();
                free_credentials(creds);
                return 2;
            }
            ret = update_service_domains(creds, service_id, "add", service_domains);
        } else if (do_remove_domain) {
            if (!service_domains) {
                fprintf(stderr, "Error: --domains required with --remove-domain\n");
                curl_global_cleanup();
                free_credentials(creds);
                return 2;
            }
            ret = update_service_domains(creds, service_id, "remove", service_domains);
        } else if (do_set_domains) {
            if (!service_domains) {
                fprintf(stderr, "Error: --domains required with --set-domains\n");
                curl_global_cleanup();
                free_credentials(creds);
                return 2;
            }
            ret = update_service_domains(creds, service_id, "custom_domains", service_domains);
        } else if (do_auto_unfreeze) {
            ret = set_unfreeze_on_demand(creds, service_id, 1);
        } else if (do_no_auto_unfreeze) {
            ret = set_unfreeze_on_demand(creds, service_id, 0);
        } else if (do_show_freeze_page) {
            ret = set_show_freeze_page(creds, service_id, 1);
        } else if (do_no_show_freeze_page) {
            ret = set_show_freeze_page(creds, service_id, 0);
        } else if (do_resize) {
            if (vcpu < 1 || vcpu > 8) {
                fprintf(stderr, "Error: --vcpu must be 1-8 for resize\n");
                curl_global_cleanup();
                free_credentials(creds);
                return 1;
            }
            ret = resize_service(creds, service_id, vcpu);
        } else if (do_redeploy) {
            // Bootstrap is optional for redeploy:
            // - If provided via --bootstrap or --bootstrap-file, use it
            // - If omitted, API will use the stored encrypted bootstrap
            const char *bootstrap_to_use = bootstrap_file ? bootstrap_file : service_bootstrap;
            ret = redeploy_service(creds, service_id, bootstrap_to_use, service_input_files, service_input_file_count);
            // Free input file memory
            for (int i = 0; i < service_input_file_count; i++) {
                free(service_input_files[i].filename);
                free(service_input_files[i].content_base64);
            }
        } else if (do_execute) {
            // Pass input files if provided (written to /tmp/input/ before command runs)
            ret = execute_service(creds, service_id, execute_command, execute_timeout, service_input_files, service_input_file_count);
            // Free input file memory
            for (int i = 0; i < service_input_file_count; i++) {
                free(service_input_files[i].filename);
                free(service_input_files[i].content_base64);
            }
        } else if (do_dump_bootstrap) {
            // Dump bootstrap script from /tmp/bootstrap.sh inside the service
            // This is useful for migrations - the bootstrap is stored at the same path on all instances
            fprintf(stderr, "Fetching bootstrap script from %s...\n", service_id);

            // Use execute to cat the bootstrap file
            char *bootstrap = execute_service_capture(creds, service_id, "cat /tmp/bootstrap.sh", 30000);
            if (bootstrap) {
                if (dump_bootstrap_file) {
                    // Write to file
                    FILE *f = fopen(dump_bootstrap_file, "w");
                    if (f) {
                        fprintf(f, "%s", bootstrap);
                        fclose(f);
                        // Make executable
                        chmod(dump_bootstrap_file, 0755);
                        printf("Bootstrap saved to %s\n", dump_bootstrap_file);
                        ret = 0;
                    } else {
                        fprintf(stderr, "Error: Could not open file %s for writing\n", dump_bootstrap_file);
                        ret = 1;
                    }
                } else {
                    // Print to stdout
                    printf("%s", bootstrap);
                    ret = 0;
                }
                free(bootstrap);
            } else {
                fprintf(stderr, "Error: Failed to fetch bootstrap (service not running or no bootstrap file)\n");
                ret = 1;
            }
        } else if (do_snapshot) {
            ret = create_service_snapshot(creds, service_id, snapshot_name, hot_snapshot);
        } else if (do_restore) {
            ret = restore_from_snapshot(creds, restore_snapshot_id, "Service");
        } else if (service_name) {
            // Create service (default action when --name is provided)
            char *bootstrap_content = NULL;

            // If --bootstrap-file provided, read its contents
            if (bootstrap_file && strlen(bootstrap_file) > 0) {
                struct stat st;
                if (stat(bootstrap_file, &st) != 0 || !S_ISREG(st.st_mode)) {
                    fprintf(stderr, "Error: Bootstrap file not found: '%s'\n", bootstrap_file);
                    curl_global_cleanup();
                    free_credentials(creds);
                    return 1;
                }
                size_t fsize;
                bootstrap_content = read_file(bootstrap_file, &fsize);
                if (!bootstrap_content) {
                    fprintf(stderr, "Error: Failed to read bootstrap file '%s'\n", bootstrap_file);
                    curl_global_cleanup();
                    free_credentials(creds);
                    return 1;
                }
                fprintf(stderr, "Read bootstrap script (%zu bytes) from %s\n", fsize, bootstrap_file);
            }

            fprintf(stderr, "Creating service '%s'...", service_name);
            if (service_input_file_count > 0) {
                fprintf(stderr, " (%d files)...", service_input_file_count);
            }
            fflush(stderr);
            char *created_id = create_service(creds, service_name, service_ports, service_domains, service_bootstrap, bootstrap_content, network_mode, vcpu, service_type, service_input_files, service_input_file_count, golden_image, create_unfreeze_on_demand);
            if (bootstrap_content) free(bootstrap_content);
            // Free input file memory
            for (int i = 0; i < service_input_file_count; i++) {
                free(service_input_files[i].filename);
                free(service_input_files[i].content_base64);
            }

            if (created_id) {
                fprintf(stderr, " done\n");
                printf("\033[32mService created successfully\033[0m\n");
                printf("Service ID: %s\n", created_id);

                // Set environment vault if env vars were provided
                char *env_content_to_set = NULL;
                if (service_env_file) {
                    env_content_to_set = read_env_file(service_env_file);
                } else if (service_env_content && service_env_size > 0) {
                    env_content_to_set = service_env_content;
                    service_env_content = NULL;  // Transfer ownership
                }
                if (env_content_to_set) {
                    fprintf(stderr, "Setting environment vault...\n");
                    int env_ret = service_env_set(creds, created_id, env_content_to_set);
                    free(env_content_to_set);
                    if (env_ret != 0) {
                        fprintf(stderr, "\033[33mWarning: Failed to set environment vault\033[0m\n");
                    }
                }

                // Wait a moment then check bootstrap logs
                if ((service_bootstrap && strlen(service_bootstrap) > 0) ||
                    (bootstrap_file && strlen(bootstrap_file) > 0)) {
                    fprintf(stderr, "Checking bootstrap status...\n");
                    sleep(2);  // Give bootstrap time to start
                    char *log = get_service_logs(creds, created_id, 0);
                    if (log && strlen(log) > 0) {
                        printf("\n--- Bootstrap Log ---\n%s\n--- End Log ---\n", log);
                        free(log);
                    }
                }

                free(created_id);
                ret = 0;
            } else {
                fprintf(stderr, " failed\n");
                // Try to get logs if we can find the service by name
                // The service might exist even if create returned error
                fprintf(stderr, "Attempting to fetch bootstrap logs...\n");
                char *log = get_service_logs(creds, service_name, 0);
                if (log && strlen(log) > 0) {
                    fprintf(stderr, "\n\033[31m--- Bootstrap Log ---\033[0m\n%s\n\033[31m--- End Log ---\033[0m\n", log);
                    free(log);
                }
                ret = 1;
            }
            // Clean up env content if not transferred
            free(service_env_content);
        } else {
            // No action specified, show help
            free(service_env_content);
            fprintf(stderr, "Error: No service action specified. Use --list, --info, --name, etc.\n");
            print_usage(argv[0]);
            curl_global_cleanup();
            free_credentials(creds);
            return 1;
        }

        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Check for jobs command (async job management)
    if (argc >= 2 && strcmp(argv[1], "jobs") == 0) {
        const char *get_id = NULL;
        const char *cancel_id = NULL;

        // Parse options
        for (int i = 2; i < argc; i++) {
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "--get") == 0 && i + 1 < argc) {
                i++;
                get_id = argv[i];
            } else if (strcmp(argv[i], "--cancel") == 0 && i + 1 < argc) {
                i++;
                cancel_id = argv[i];
            } else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0) {
                // --list is default, no-op
            } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                fprintf(stderr, "Usage: %s jobs [options]\n\n", argv[0]);
                fprintf(stderr, "Commands:\n");
                fprintf(stderr, "  (default)          List all jobs\n");
                fprintf(stderr, "  -l, --list         List all jobs\n");
                fprintf(stderr, "  --get ID           Get job status and result\n");
                fprintf(stderr, "  --cancel ID        Cancel a running job\n");
                return 0;
            }
        }

        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);
        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret = 0;

        if (cancel_id) {
            // un jobs --cancel ID — DELETE /jobs/:id
            char path[256], url[512];
            snprintf(path, sizeof(path), "/jobs/%s", cancel_id);
            snprintf(url, sizeof(url), "%s%s", API_BASE, path);

            CURL *curl = curl_easy_init();
            if (!curl) { ret = 1; goto jobs_cleanup; }

            struct curl_slist *hdrs = NULL;
            hdrs = add_hmac_auth_headers(hdrs, creds, "DELETE", path, NULL);

            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
            curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
            curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

            CURLcode cres = curl_easy_perform(curl);
            long hcode = 0;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &hcode);
            curl_slist_free_all(hdrs);
            curl_easy_cleanup(curl);

            if (cres == CURLE_OK && (hcode == 200 || hcode == 204)) {
                printf("Job %s cancelled\n", cancel_id);
            } else {
                fprintf(stderr, "Error: Failed to cancel job %s (HTTP %ld)\n", cancel_id, hcode);
                ret = 1;
            }
        } else if (get_id) {
            // un jobs --get ID — GET /jobs/:id
            char path[256], url[512];
            snprintf(path, sizeof(path), "/jobs/%s", get_id);
            snprintf(url, sizeof(url), "%s%s", API_BASE, path);

            CURL *curl = curl_easy_init();
            if (!curl) { ret = 1; goto jobs_cleanup; }

            struct ResponseBuffer resp = {0};
            resp.data = malloc(1);
            resp.size = 0;

            struct curl_slist *hdrs = NULL;
            hdrs = add_hmac_auth_headers(hdrs, creds, "GET", path, NULL);

            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
            curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

            CURLcode cres = curl_easy_perform(curl);
            long hcode = 0;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &hcode);
            curl_slist_free_all(hdrs);
            curl_easy_cleanup(curl);

            if (cres != CURLE_OK || hcode != 200) {
                fprintf(stderr, "Error: Failed to get job %s (HTTP %ld)\n", get_id, hcode);
                free(resp.data);
                ret = 1;
            } else {
                char *jid = extract_json_string(resp.data, "job_id");
                char *jstatus = extract_json_string(resp.data, "status");
                char *jlang = extract_json_string(resp.data, "language");
                char *jerror = extract_json_string(resp.data, "error");
                int64_t created = extract_json_number(resp.data, "created_at");
                int64_t completed = extract_json_number(resp.data, "completed_at");

                printf("%-12s %s\n", "Job ID:", jid ? jid : get_id);
                printf("%-12s %s\n", "Status:", jstatus ? jstatus : "unknown");
                if (jlang) printf("%-12s %s\n", "Language:", jlang);
                if (created > 0) printf("%-12s %ld\n", "Created:", (long)created);
                if (completed > 0) printf("%-12s %ld\n", "Completed:", (long)completed);
                if (jerror) printf("%-12s %s\n", "Error:", jerror);

                // If completed, also show stdout/stderr
                if (jstatus && strcmp(jstatus, "completed") == 0) {
                    char *jstdout = extract_json_string(resp.data, "stdout");
                    char *jstderr = extract_json_string(resp.data, "stderr");
                    if (jstdout && strlen(jstdout) > 0) {
                        printf("\n--- stdout ---\n%s", jstdout);
                        if (jstdout[strlen(jstdout)-1] != '\n') printf("\n");
                    }
                    if (jstderr && strlen(jstderr) > 0) {
                        fprintf(stderr, "\n--- stderr ---\n%s", jstderr);
                        if (jstderr[strlen(jstderr)-1] != '\n') fprintf(stderr, "\n");
                    }
                    free(jstdout);
                    free(jstderr);
                }

                free(jid);
                free(jstatus);
                free(jlang);
                free(jerror);
                free(resp.data);
            }
        } else {
            // un jobs --list (default) — GET /jobs
            char url[256];
            snprintf(url, sizeof(url), "%s/jobs", API_BASE);

            CURL *curl = curl_easy_init();
            if (!curl) { ret = 1; goto jobs_cleanup; }

            struct ResponseBuffer resp = {0};
            resp.data = malloc(1);
            resp.size = 0;

            struct curl_slist *hdrs = NULL;
            hdrs = add_hmac_auth_headers(hdrs, creds, "GET", "/jobs", NULL);

            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
            curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

            CURLcode cres = curl_easy_perform(curl);
            long hcode = 0;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &hcode);
            curl_slist_free_all(hdrs);
            curl_easy_cleanup(curl);

            if (cres != CURLE_OK || hcode != 200) {
                fprintf(stderr, "Error: Failed to list jobs (HTTP %ld)\n", hcode);
                free(resp.data);
                ret = 1;
            } else {
                int count = count_json_array_objects(resp.data, "jobs");
                if (count <= 0) {
                    printf("No jobs found\n");
                } else {
                    printf("%-38s %-12s %-14s %s\n", "JOB ID", "STATUS", "LANGUAGE", "CREATED");
                    printf("%-38s %-12s %-14s %s\n", "------", "------", "--------", "-------");

                    const char *jobs_start = strstr(resp.data, "\"jobs\":[");
                    if (jobs_start) {
                        const char *pos = jobs_start + 8;
                        for (int i = 0; i < count && pos; i++) {
                            pos = strchr(pos, '{');
                            if (!pos) break;
                            char *jid = extract_json_string(pos, "job_id");
                            char *jstatus = extract_json_string(pos, "status");
                            char *jlang = extract_json_string(pos, "language");
                            int64_t created = extract_json_number(pos, "created_at");
                            printf("%-38s %-12s %-14s %ld\n",
                                jid ? jid : "?",
                                jstatus ? jstatus : "?",
                                jlang ? jlang : "?",
                                (long)created);
                            free(jid);
                            free(jstatus);
                            free(jlang);
                            pos = skip_json_object(pos);
                        }
                    }
                }
                free(resp.data);
            }
        }

jobs_cleanup:
        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Check for paas command (PaaS platform management)
    if (argc >= 2 && strcmp(argv[1], "paas") == 0) {
        // paas requires a sub-subcommand
        if (argc < 3) {
            fprintf(stderr, "Usage: %s paas <command> [options]\n\n", argv[0]);
            fprintf(stderr, "Commands:\n");
            fprintf(stderr, "  logs    Fetch or stream production logs\n");
            fprintf(stderr, "\nRun '%s paas <command> -h' for help on a specific command.\n", argv[0]);
            return 1;
        }

        // paas logs
        if (strcmp(argv[2], "logs") == 0) {
            const char *source = "all";
            int lines = 100;
            const char *since = NULL;
            int since_explicit = 0;  // whether user passed --since
            const char *grep_filter = NULL;
            const char *level = NULL;
            int do_stream = 0;
            int json_output = 0;
            int show_help = 0;

            // Parse options (start at argv[3])
            for (int i = 3; i < argc; i++) {
                if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                    i++;
                    cli_public_key = argv[i];
                } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                    i++;
                    cli_secret_key = argv[i];
                } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                    i++;
                    cli_account_index = atoi(argv[i]);
                } else if (strcmp(argv[i], "--source") == 0 && i + 1 < argc) {
                    i++;
                    source = argv[i];
                } else if (strcmp(argv[i], "--all") == 0) {
                    source = "all";
                } else if (strcmp(argv[i], "--api") == 0) {
                    source = "api";
                } else if (strcmp(argv[i], "--portal") == 0) {
                    source = "portal";
                } else if (strcmp(argv[i], "--pool") == 0 && i + 1 < argc) {
                    i++;
                    static char pool_source[128];
                    snprintf(pool_source, sizeof(pool_source), "pool/%s", argv[i]);
                    source = pool_source;
                } else if (strcmp(argv[i], "--lines") == 0 && i + 1 < argc) {
                    i++;
                    lines = atoi(argv[i]);
                    if (lines < 1) lines = 1;
                    if (lines > 10000) lines = 10000;
                } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
                    i++;
                    lines = atoi(argv[i]);
                    if (lines < 1) lines = 1;
                    if (lines > 10000) lines = 10000;
                } else if (strcmp(argv[i], "--since") == 0 && i + 1 < argc) {
                    i++;
                    since = argv[i];
                    since_explicit = 1;
                } else if (strcmp(argv[i], "--grep") == 0 && i + 1 < argc) {
                    i++;
                    grep_filter = argv[i];
                } else if ((strcmp(argv[i], "--level") == 0 || strcmp(argv[i], "-l") == 0) && i + 1 < argc) {
                    i++;
                    level = argv[i];
                } else if (strcmp(argv[i], "--follow") == 0 || strcmp(argv[i], "-f") == 0) {
                    do_stream = 1;
                } else if (strcmp(argv[i], "--json") == 0) {
                    json_output = 1;
                } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                    show_help = 1;
                }
            }

            if (show_help) {
                fprintf(stderr, "Usage: %s paas logs [options]\n\n", argv[0]);
                fprintf(stderr, "Fetch or stream PaaS production logs.\n\n");
                fprintf(stderr, "Sources:\n");
                fprintf(stderr, "  --all              All sources (default)\n");
                fprintf(stderr, "  --api              API server logs\n");
                fprintf(stderr, "  --portal           Portal server logs\n");
                fprintf(stderr, "  --pool NODE        Pool node logs (e.g., cammy, ai)\n");
                fprintf(stderr, "  --source SOURCE    Source string (api, portal, pool/cammy, all)\n");
                fprintf(stderr, "\nOptions:\n");
                fprintf(stderr, "  --lines N, -n N    Number of lines (default: 100, max: 10000)\n");
                fprintf(stderr, "  --since TIME       Time window: 1m, 5m, 15m, 1h, 6h, 1d (default: 5m)\n");
                fprintf(stderr, "  --grep PATTERN     Filter log lines by pattern\n");
                fprintf(stderr, "  --level LVL, -l    Min log level: debug, info, notice, warning, err, crit\n");
                fprintf(stderr, "  --follow, -f       Follow logs in real-time (Ctrl+C to stop)\n");
                fprintf(stderr, "  --json             Output raw JSON response\n");
                fprintf(stderr, "  -p KEY             Public key\n");
                fprintf(stderr, "  -k KEY             Secret key\n");
                fprintf(stderr, "  -h                 Show this help\n");
                fprintf(stderr, "\nExamples:\n");
                fprintf(stderr, "  %s paas logs                             # last 100 lines from all sources\n", argv[0]);
                fprintf(stderr, "  %s paas logs --api --lines 500           # last 500 API log lines\n", argv[0]);
                fprintf(stderr, "  %s paas logs --portal --grep error       # portal logs matching 'error'\n", argv[0]);
                fprintf(stderr, "  %s paas logs --pool cammy --since 1h     # cammy pool logs from last hour\n", argv[0]);
                fprintf(stderr, "  %s paas logs -l warning                  # warnings and above from all sources\n", argv[0]);
                fprintf(stderr, "  %s paas logs --follow                    # follow all logs in real-time\n", argv[0]);
                fprintf(stderr, "  %s paas logs --follow -l warning         # follow warnings+ in real-time\n", argv[0]);
                fprintf(stderr, "\nRequires a partner API key with log access enabled.\n");
                return 0;
            }

            // Get credentials
            UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);

            if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
                fprintf(stderr, "Error: API credentials required.\n");
                fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
                fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
                fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
                free_credentials(creds);
                return 1;
            }

            // Resolve since: explicit --since > cursor > default 5m
            if (!since_explicit) {
                char *cursor = read_log_cursor();
                if (cursor) {
                    since = cursor;
                } else {
                    since = "5m";
                }
            }

            curl_global_init(CURL_GLOBAL_DEFAULT);
            int ret;
            if (do_stream) {
                ret = stream_paas_logs(creds, source, grep_filter, level);
            } else {
                ret = fetch_paas_logs(creds, source, lines, since, grep_filter, json_output, level);
            }
            curl_global_cleanup();
            free_credentials(creds);
            return ret;
        }

        // Unknown paas subcommand
        fprintf(stderr, "Error: Unknown paas command '%s'\n", argv[2]);
        fprintf(stderr, "Run '%s paas' for available commands.\n", argv[0]);
        return 1;
    }

    // Check for session command
    if (argc >= 2 && strcmp(argv[1], "session") == 0) {
        int audit_history = 0;
        int list_only = 0;
        const char *shell = NULL;
        const char *attach_to = NULL;
        const char *kill_target = NULL;
        const char *freeze_target = NULL;
        const char *unfreeze_target = NULL;
        const char *boost_target = NULL;
        int boost_vcpu = 0;
        const char *unboost_target = NULL;
        const char *multiplexer = NULL;  // NULL = no multiplexer, "tmux", or "screen"
        struct InputFile session_input_files[MAX_INPUT_FILES];
        int session_input_file_count = 0;
        const char *snapshot_target = NULL;
        const char *restore_snapshot_id = NULL;
        const char *snapshot_name = NULL;
        int hot_snapshot = 0;
        // Parse shell-specific args
        for (int i = 2; i < argc; i++) {
            if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
                i++;
                cli_public_key = argv[i];
            } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
                i++;
                cli_secret_key = argv[i];
            } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
                i++;
                cli_account_index = atoi(argv[i]);
            } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
                i++;
                network_mode = argv[i];
            } else if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--shell") == 0) && i + 1 < argc) {
                i++;
                shell = argv[i];
            } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--artifacts") == 0) {
                save_artifacts = 1;
            } else if (strcmp(argv[i], "--audit") == 0) {
                audit_history = 1;
                save_artifacts = 1;  // --audit implies -a
            } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
                i++;
                artifact_dir = argv[i];
                save_artifacts = 1;  // -o implies -a
            } else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0) {
                list_only = 1;
            } else if (strcmp(argv[i], "--attach") == 0 && i + 1 < argc) {
                i++;
                attach_to = argv[i];
            } else if (strcmp(argv[i], "--kill") == 0 && i + 1 < argc) {
                i++;
                kill_target = argv[i];
            } else if (strcmp(argv[i], "--freeze") == 0 && i + 1 < argc) {
                i++;
                freeze_target = argv[i];
            } else if (strcmp(argv[i], "--unfreeze") == 0 && i + 1 < argc) {
                i++;
                unfreeze_target = argv[i];
            } else if (strcmp(argv[i], "--boost") == 0 && i + 1 < argc) {
                i++;
                boost_target = argv[i];
            } else if (strcmp(argv[i], "--boost-vcpu") == 0 && i + 1 < argc) {
                i++;
                boost_vcpu = atoi(argv[i]);
            } else if (strcmp(argv[i], "--unboost") == 0 && i + 1 < argc) {
                i++;
                unboost_target = argv[i];
            } else if (strcmp(argv[i], "--tmux") == 0) {
                multiplexer = "tmux";
            } else if (strcmp(argv[i], "--screen") == 0) {
                multiplexer = "screen";
            } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
                i++;
                if (session_input_file_count >= MAX_INPUT_FILES) {
                    fprintf(stderr, "Error: too many input files (max %d)\n", MAX_INPUT_FILES);
                    return 1;
                }
                size_t fsize;
                char *content = read_file(argv[i], &fsize);
                if (!content) {
                    fprintf(stderr, "Error: cannot read file '%s'\n", argv[i]);
                    return 1;
                }
                size_t b64_len;
                char *b64 = base64_encode((unsigned char *)content, fsize, &b64_len);
                free(content);
                if (!b64) {
                    fprintf(stderr, "Error: failed to encode file '%s'\n", argv[i]);
                    return 1;
                }
                session_input_files[session_input_file_count].filename = strdup(get_basename(argv[i]));
                session_input_files[session_input_file_count].content_base64 = b64;
                session_input_file_count++;
            } else if (strcmp(argv[i], "-F") == 0 && i + 1 < argc) {
                // -F preserves relative path (for directory structures)
                i++;
                if (session_input_file_count >= MAX_INPUT_FILES) {
                    fprintf(stderr, "Error: too many input files (max %d)\n", MAX_INPUT_FILES);
                    return 1;
                }
                size_t fsize;
                char *content = read_file(argv[i], &fsize);
                if (!content) {
                    fprintf(stderr, "Error: cannot read file '%s'\n", argv[i]);
                    return 1;
                }
                size_t b64_len;
                char *b64 = base64_encode((unsigned char *)content, fsize, &b64_len);
                free(content);
                if (!b64) {
                    fprintf(stderr, "Error: failed to encode file '%s'\n", argv[i]);
                    return 1;
                }
                // Use full path instead of basename
                session_input_files[session_input_file_count].filename = strdup(argv[i]);
                session_input_files[session_input_file_count].content_base64 = b64;
                session_input_file_count++;
            } else if ((strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--vcpu") == 0) && i + 1 < argc) {
                i++;
                vcpu = atoi(argv[i]);
                if (vcpu < 1 || vcpu > 8) {
                    fprintf(stderr, "Error: -v/--vcpu must be 1-8\n");
                    return 1;
                }
            } else if (strcmp(argv[i], "--snapshot") == 0 && i + 1 < argc) {
                i++;
                snapshot_target = argv[i];
            } else if (strcmp(argv[i], "--restore") == 0 && i + 1 < argc) {
                i++;
                restore_snapshot_id = argv[i];
            } else if (strcmp(argv[i], "--snapshot-name") == 0 && i + 1 < argc) {
                i++;
                snapshot_name = argv[i];
            } else if (strcmp(argv[i], "--hot") == 0) {
                hot_snapshot = 1;
            } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
                print_usage(argv[0]);
                return 0;
            } else if (argv[i][0] == '-') {
                fprintf(stderr, "Unknown option: %s\n", argv[i]);
                print_usage(argv[0]);
                return 1;
            }
        }

        // Get credentials (priority: env > flags > file)
        UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);
        if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
            fprintf(stderr, "Error: API credentials required.\n");
            fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
            fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
            fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
            free_credentials(creds);
            return 1;
        }

        curl_global_init(CURL_GLOBAL_DEFAULT);
        int ret;
        if (list_only) {
            ret = list_sessions(creds);
        } else if (kill_target) {
            ret = kill_session(creds, kill_target);
        } else if (freeze_target) {
            ret = freeze_session(creds, freeze_target);
        } else if (unfreeze_target) {
            ret = unfreeze_session(creds, unfreeze_target);
        } else if (boost_target) {
            if (boost_vcpu == 0) boost_vcpu = 2;  // Default boost: 2 vCPU
            ret = boost_session(creds, boost_target, boost_vcpu);
        } else if (unboost_target) {
            ret = unboost_session(creds, unboost_target);
        } else if (snapshot_target) {
            ret = create_session_snapshot(creds, snapshot_target, snapshot_name, hot_snapshot);
        } else if (restore_snapshot_id) {
            ret = restore_from_snapshot(creds, restore_snapshot_id, "Session");
        } else if (attach_to) {
            ret = reconnect_session(creds, attach_to, save_artifacts, artifact_dir, audit_history);
        } else {
            ret = shell_command(creds, network_mode, save_artifacts, artifact_dir, audit_history, shell, multiplexer, vcpu, session_input_files, session_input_file_count);
        }
        curl_global_cleanup();
        free_credentials(creds);
        return ret;
    }

    // Parse arguments for execute command (default)
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            print_usage(argv[0]);
            return 0;
        } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
            i++;
            network_mode = argv[i];
        } else if (strcmp(argv[i], "-e") == 0 && i + 1 < argc) {
            i++;
            char *eq = strchr(argv[i], '=');
            if (!eq) {
                fprintf(stderr, "Error: -e requires KEY=VALUE format\n");
                return 1;
            }
            if (env_var_count >= MAX_ENV_VARS) {
                fprintf(stderr, "Error: too many env vars (max %d)\n", MAX_ENV_VARS);
                return 1;
            }
            env_vars[env_var_count].key = strndup(argv[i], eq - argv[i]);
            env_vars[env_var_count].value = strdup(eq + 1);
            env_var_count++;
        } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
            i++;
            if (input_file_count >= MAX_INPUT_FILES) {
                fprintf(stderr, "Error: too many input files (max %d)\n", MAX_INPUT_FILES);
                return 1;
            }
            size_t fsize;
            char *content = read_file(argv[i], &fsize);
            if (!content) return 1;

            // Check total size limit
            total_input_size += fsize;
            if (total_input_size > MAX_TOTAL_INPUT_SIZE) {
                fprintf(stderr, "Error: total input file size exceeds limit (max 4GB)\n");
                free(content);
                return 1;
            }

            size_t b64_len;
            char *b64 = base64_encode((unsigned char*)content, fsize, &b64_len);
            free(content);
            if (!b64) {
                fprintf(stderr, "Error: failed to encode file\n");
                return 1;
            }

            input_files[input_file_count].filename = strdup(get_basename(argv[i]));
            input_files[input_file_count].content_base64 = b64;
            input_file_count++;
        } else if (strcmp(argv[i], "-F") == 0 && i + 1 < argc) {
            // -F preserves relative path (for directory structures)
            i++;
            if (input_file_count >= MAX_INPUT_FILES) {
                fprintf(stderr, "Error: too many input files (max %d)\n", MAX_INPUT_FILES);
                return 1;
            }
            size_t fsize;
            char *content = read_file(argv[i], &fsize);
            if (!content) return 1;

            // Check total size limit
            total_input_size += fsize;
            if (total_input_size > MAX_TOTAL_INPUT_SIZE) {
                fprintf(stderr, "Error: total input file size exceeds limit (max 4GB)\n");
                free(content);
                return 1;
            }

            size_t b64_len;
            char *b64 = base64_encode((unsigned char*)content, fsize, &b64_len);
            free(content);
            if (!b64) {
                fprintf(stderr, "Error: failed to encode file\n");
                return 1;
            }

            // Use full path instead of basename
            input_files[input_file_count].filename = strdup(argv[i]);
            input_files[input_file_count].content_base64 = b64;
            input_file_count++;
        } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--artifacts") == 0) {
            save_artifacts = 1;
        } else if (strcmp(argv[i], "-y") == 0 || strcmp(argv[i], "--yes") == 0) {
            skip_confirm = 1;
        } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
            i++;
            artifact_dir = argv[i];
        } else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
            i++;
            cli_public_key = argv[i];
        } else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) {
            i++;
            cli_secret_key = argv[i];
        } else if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
            i++;
            cli_account_index = atoi(argv[i]);
        } else if ((strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--vcpu") == 0) && i + 1 < argc) {
            i++;
            vcpu = atoi(argv[i]);
            if (vcpu < 1 || vcpu > 8) {
                fprintf(stderr, "Error: -v/--vcpu must be 1-8\n");
                return 1;
            }
        } else if ((strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--ttl") == 0) && i + 1 < argc) {
            i++;
            ttl = atoi(argv[i]);
            if (ttl < 1 || ttl > 900) {
                fprintf(stderr, "Error: -t/--ttl must be 1-900 seconds\n");
                return 1;
            }
        } else if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--shell") == 0) && i + 1 < argc) {
            i++;
            shell = argv[i];
        } else if (argv[i][0] != '-') {
            filename = argv[i];
        } else {
            fprintf(stderr, "Unknown option: %s\n", argv[i]);
            print_usage(argv[0]);
            return 1;
        }
    }

    if (!filename) {
        print_usage(argv[0]);
        return 1;
    }

    // Warn about large uploads and confirm
    if (total_input_size > LARGE_UPLOAD_WARN_SIZE && !skip_confirm) {
        fprintf(stderr, "Warning: uploading %.1f GB of input files. This may take a while (base64 encoded).\n",
                (double)total_input_size / (1024.0 * 1024.0 * 1024.0));
        fprintf(stderr, "Continue? [y/N] ");
        int c = getchar();
        if (c != 'y' && c != 'Y') {
            fprintf(stderr, "Aborted. Use -y to skip this confirmation.\n");
            return 1;
        }
        // Consume rest of line
        while (c != '\n' && c != EOF) c = getchar();
    }

    // Get credentials (priority: env > flags > file)
    UnsandboxCredentials *creds = get_credentials(cli_public_key, cli_secret_key, cli_account_index);
    if (!creds || !creds->public_key || strlen(creds->public_key) == 0) {
        fprintf(stderr, "Error: API credentials required.\n");
        fprintf(stderr, "  Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
        fprintf(stderr, "  Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
        fprintf(stderr, "  Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n");
        free_credentials(creds);
        return 1;
    }

    // Get code: if -s is given OR file doesn't exist, treat as inline code
    size_t code_size;
    char *code;
    int inline_mode = 0;
    if (shell) {
        // Explicit -s flag: treat as inline code
        inline_mode = 1;
    } else if (access(filename, F_OK) != 0) {
        // File doesn't exist: assume bash inline code
        shell = "bash";
        inline_mode = 1;
    }

    if (inline_mode) {
        // Inline code mode: argument is the code itself
        code = strdup(filename);
        code_size = strlen(code);
    } else {
        // File mode: read code from file
        code = read_file(filename, &code_size);
        if (!code) return 1;
    }

    // Detect language (use -s/--shell if provided, otherwise auto-detect)
    const char *language = shell;
    if (!language) {
        language = detect_language_from_extension(filename);
    }
    if (!language) {
        language = detect_language_from_shebang(code);
    }
    if (!language) {
        fprintf(stderr, "Error: cannot detect language from file extension or shebang\n");
        fprintf(stderr, "  Use -s/--shell to specify the language (e.g., -s bash, -s python)\n");
        free(code);
        return 1;
    }

    // Escape code for JSON
    char *escaped_code = escape_json_string(code);
    free(code);
    if (!escaped_code) {
        fprintf(stderr, "Error: failed to escape code\n");
        return 1;
    }

    // Build JSON payload
    size_t payload_size = strlen(escaped_code) + 4096;
    for (int i = 0; i < input_file_count; i++) {
        payload_size += strlen(input_files[i].content_base64) + 256;
    }
    for (int i = 0; i < env_var_count; i++) {
        payload_size += strlen(env_vars[i].key) + strlen(env_vars[i].value) + 32;
    }

    char *json_payload = malloc(payload_size);
    if (!json_payload) {
        fprintf(stderr, "Error: out of memory\n");
        free(escaped_code);
        return 1;
    }

    char *p = json_payload;
    p += sprintf(p, "{\"language\":\"%s\",\"code\":\"%s\"", language, escaped_code);
    free(escaped_code);

    // Add input files
    if (input_file_count > 0) {
        p += sprintf(p, ",\"input_files\":[");
        for (int i = 0; i < input_file_count; i++) {
            if (i > 0) *p++ = ',';
            char *esc_filename = escape_json_string(input_files[i].filename);
            p += sprintf(p, "{\"filename\":\"%s\",\"content\":\"%s\"}",
                        esc_filename, input_files[i].content_base64);
            free(esc_filename);
            free(input_files[i].filename);
            free(input_files[i].content_base64);
        }
        p += sprintf(p, "]");
    }

    // Add env vars
    if (env_var_count > 0) {
        p += sprintf(p, ",\"env\":{");
        for (int i = 0; i < env_var_count; i++) {
            if (i > 0) *p++ = ',';
            char *esc_key = escape_json_string(env_vars[i].key);
            char *esc_val = escape_json_string(env_vars[i].value);
            p += sprintf(p, "\"%s\":\"%s\"", esc_key, esc_val);
            free(esc_key);
            free(esc_val);
            free(env_vars[i].key);
            free(env_vars[i].value);
        }
        p += sprintf(p, "}");
    }

    // Add artifact flag
    if (save_artifacts) {
        p += sprintf(p, ",\"return_artifact\":true");
    }

    // Add vcpu if specified (> 1)
    if (vcpu > 1) {
        p += sprintf(p, ",\"vcpu\":%d", vcpu);
    }

    // Add network_mode if specified
    if (network_mode && strlen(network_mode) > 0) {
        p += sprintf(p, ",\"network_mode\":\"%s\"", network_mode);
    }

    // Add TTL if specified
    if (ttl > 0) {
        p += sprintf(p, ",\"ttl\":%d", ttl);
    }

    p += sprintf(p, "}");

    // Initialize libcurl
    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        fprintf(stderr, "Error: failed to initialize curl\n");
        free(json_payload);
        curl_global_cleanup();
        return 1;
    }

    // Set up response buffer
    struct ResponseBuffer response = {0};
    response.data = malloc(1);
    response.size = 0;

    // Set up request headers with HMAC authentication
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = add_hmac_auth_headers(headers, creds, "POST", "/execute", json_payload);

    // Configure curl
    curl_easy_setopt(curl, CURLOPT_URL, API_URL);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");

    // Set timeout based on TTL (or default to 120 seconds)
    long curl_timeout = (ttl > 0) ? (ttl + 30) : 120;  // Add 30s buffer
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, curl_timeout);

    // Perform request
    CURLcode res = curl_easy_perform(curl);

    if (res != CURLE_OK) {
        fprintf(stderr, "Error: request failed: %s\n", curl_easy_strerror(res));
        curl_easy_cleanup(curl);
        curl_slist_free_all(headers);
        free(json_payload);
        free(response.data);
        curl_global_cleanup();
        return 1;
    }

    // Check HTTP status code
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    if (http_code != 200) {
        fprintf(stderr, "Error: HTTP %ld\n", http_code);
        if (response.data) {
            fprintf(stderr, "%s\n", response.data);
        }
        curl_easy_cleanup(curl);
        curl_slist_free_all(headers);
        free(json_payload);
        free(response.data);
        curl_global_cleanup();
        return 1;
    }

    // Check if response contains job_id (async execution)
    char *job_id = NULL;
    char *status = NULL;
    char *final_data = response.data;

    if (response.data) {
        job_id = extract_json_string(response.data, "job_id");
        status = extract_json_string(response.data, "status");
    }

    // If we got a job_id and status isn't terminal, we need to poll
    if (job_id && status) {
        int need_poll = (strcmp(status, "pending") == 0 ||
                        strcmp(status, "running") == 0);

        if (need_poll) {
            fprintf(stderr, "job %s\n", job_id);
            // Free initial response, poll for final result
            free(response.data);
            final_data = poll_job_status(creds, job_id);
        }
    }

    // Parse and print final response
    if (final_data) {
        parse_and_print_response(final_data, save_artifacts, artifact_dir, filename);
        if (final_data != response.data) {
            free(final_data);
        }
    }

    // Cleanup
    if (job_id) free(job_id);
    if (status) free(status);
    curl_easy_cleanup(curl);
    curl_slist_free_all(headers);
    free(json_payload);
    if (final_data == response.data) {
        free(response.data);
    }
    curl_global_cleanup();
    free_credentials(creds);

    return 0;
}
#endif /* UNSANDBOX_LIBRARY */
