npm package: @kadi.build/deploy-ability
Description: Programmatic deployment library for KADI - deploy to Akash Network, local Docker, and other platforms without CLI dependencies
Author: Kassi Bertrand
Latest version: 0.0.29

# deploy-ability

> **Programmatic deployment library for KADI** - Deploy applications to Akash Network, local Docker, or other platforms with a delightful TypeScript API

[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
[![Zero Any Types](https://img.shields.io/badge/any%20types-0-green.svg)](https://www.typescriptlang.org/)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

---

## Why deploy-ability?

`deploy-ability` extracts the core deployment logic from `kadi-deploy` into a **pure TypeScript library** with zero CLI dependencies. This enables:

**Programmatic Deployments** - Use from Node.js, APIs, agents, anywhere
**Type-Safe** - Complete TypeScript support with zero `any` types
**Delightful API** - Simple for common tasks, powerful for advanced use cases
**Flexible Wallet Support** - Human approval (WalletConnect) or autonomous signing (agents)
**Autonomous Mode** - Fully automated deployments with secure secrets integration
**Well-Documented** - Comprehensive JSDoc with IntelliSense
**Production-Ready** - Handles Akash Network complexity elegantly

### Before: kadi-deploy (CLI Only)

```bash
# Can only deploy via CLI
kadi deploy --profile production
```

### After: deploy-ability (Programmatic!)

```typescript
import { deployToAkash } from 'deploy-ability';

// Deploy from your code!
const result = await deployToAkash({
  projectRoot: process.cwd(),
  profile: 'production',
  network: 'mainnet'
});

console.log(`Deployed! DSEQ: ${result.dseq}`);
```

---

## Architecture

### The Big Picture

```mermaid
graph TD
    subgraph "Core Library Layer"
        DA[deploy-ability]
        DA --> AK[Akash Deployment]
        DA --> LC[Local Deployment]
        DA --> WL[Wallet Management]
        DA --> CT[Certificate Management]
        DA --> MN[Monitoring & Queries]
    end

    subgraph "CLI Layer"
        CLI[kadi deploy command]
        KD[kadi-deploy plugin]
        CLI --> KD
        KD --> DA
    end

    subgraph "Programmatic Usage"
        AG[Custom Agents]
        SC[Scripts]
        API[API Servers]
        CI[CI/CD Pipelines]

        AG --> DA
        SC --> DA
        API --> DA
        CI --> DA
    end

    style DA fill:#6c6,stroke:#333,stroke-width:3px
    style KD fill:#69f,stroke:#333
```

**Key Benefits:**
- **Single Source of Truth** - One library, many entry points
- **Pluggable** - CLI, programmatic, or custom integrations
- **Reusable** - Share deployment logic across your entire stack
- **Testable** - Core logic independent of CLI

### Deployment Flow (Akash Network)

```mermaid
sequenceDiagram
    participant User as Your Code
    participant DA as deploy-ability
    participant Wallet as Wallet/Signer
    participant Akash as Akash Blockchain
    participant Provider as Akash Provider

    User->>DA: deployToAkash(options)

    Note over DA,Wallet: Step 1: Wallet Connection
    DA->>Wallet: Connect (WalletConnect or Signer)
    Wallet-->>DA: WalletContext with signer

    Note over DA,Akash: Step 2: Certificate Setup
    DA->>Akash: Query/Create mTLS certificate
    Akash-->>DA: Certificate for provider communication

    Note over DA,Akash: Step 3: Balance Check & Auto-Mint
    DA->>Akash: Query AKT + ACT balances
    Akash-->>DA: Wallet balances
    alt ACT balance insufficient
        DA->>Akash: BME mintACT (burn AKT → mint ACT)
        Akash-->>DA: ACT minted
    end

    Note over DA,Akash: Step 4: Create Deployment
    DA->>Wallet: Sign deployment transaction
    Wallet-->>DA: Signed transaction
    DA->>Akash: Broadcast deployment (deposit in ACT)
    Akash-->>DA: DSEQ (deployment ID)

    Note over DA,Akash: Step 5: Wait for Bids
    DA->>Akash: Query bids from providers
    Akash-->>DA: Provider bids

    Note over DA,Akash: Step 6: Create Lease
    DA->>Wallet: Sign lease transaction
    Wallet-->>DA: Signed transaction
    DA->>Akash: Accept bid, create lease

    Note over DA,Provider: Step 7: Send Manifest
    DA->>Provider: Send deployment manifest (mTLS)
    Provider-->>DA: Manifest accepted

    Note over DA,Provider: Step 8: Monitor Deployment
    DA->>Provider: Poll container status
    Provider-->>DA: Containers running!

    DA-->>User: DeploymentResult { dseq, provider, endpoints }
```

---

## Installation

```bash
npm install deploy-ability
```

**Requirements:**
- Node.js >= 18
- TypeScript >= 5.0 (optional, but recommended)

---

## Quick Start

### Deploy to Akash Network

```typescript
import { deployToAkash } from 'deploy-ability';

const result = await deployToAkash({
  projectRoot: process.cwd(),
  profile: 'production',
  network: 'mainnet'
});

if (result.success) {
  console.log(`Deployed! DSEQ: ${result.data.dseq}`);
  console.log(`Provider: ${result.data.providerUri}`);
  console.log(`Endpoints:`, result.data.endpoints);
}
```

### Autonomous Deployment (Agent-Controlled)

For fully automated deployments with secrets integration:

```typescript
import { createSecretsProvider, deployToAkash } from 'deploy-ability';

// In a KADI agent:
const secrets = createSecretsProvider(this.secrets);

const result = await deployToAkash({
  autonomous: { secrets, bidStrategy: 'cheapest' },
  projectRoot: process.cwd(),
  profile: 'production',
  network: 'mainnet',
});

if (result.success) {
  console.log(`Deployed! DSEQ: ${result.data.dseq}`);
}
```

See [Autonomous Deployment with Secrets Integration](#autonomous-deployment-with-secrets-integration) for full details.

### Deploy to Local Docker

```typescript
import { deployToLocal } from 'deploy-ability';

const result = await deployToLocal({
  projectRoot: process.cwd(),
  profile: 'local-dev',
  engine: 'docker'
});

if (result.success) {
  console.log('Deployed locally!');
  console.log('Services:', result.data.services);
}
```

---

## Core Concepts

### Storage Configuration

Understand the difference between **memory** (RAM), **ephemeralStorage** (temporary disk), and **persistentVolumes** (permanent disk) when configuring your deployments.

**Quick summary:**
- **memory**: Application runtime RAM (cleared on crash)
- **ephemeralStorage**: Container root filesystem (cleared on restart)
- **persistentVolumes**: Data that survives restarts (databases, uploads, models)

**[Read the full Storage Configuration Guide →](docs/STORAGE.md)**

---

### Placement Attributes

Control **where** your deployment runs geographically using placement attributes like region, datacenter type, timezone, and country.

**Common use cases:**
- Data residency (GDPR compliance)
- Latency optimization (deploy close to users)
- Infrastructure quality (datacenter vs home providers)

**[Read the full Placement Attributes Guide →](docs/PLACEMENT.md)**

---

### Understanding Signers

A **signer** is an interface that can sign blockchain transactions **without exposing private keys**. Think of it as a secure API to your wallet.

**Two wallet patterns:**

**1. Human Approval (WalletConnect)** - User scans QR code and approves each transaction
**2. Autonomous Signing (Agents)** - Agent has signer and signs automatically

```typescript
// Signer interface (simplified)
interface OfflineSigner {
  getAccounts(): Promise<Account[]>;
  signDirect(address: string, signDoc: SignDoc): Promise<Signature>;
}
```

**Why "Offline"?** The term "OfflineSigner" from Cosmos means the private keys stay in the wallet (hardware or software) and never travel over the network. It can sign "offline" without broadcasting.

---

## Basic Usage

### Human Users (WalletConnect)

For applications where users approve deployments from their mobile Keplr wallet:

```typescript
import { connectWallet, deployToAkash } from 'deploy-ability';
import QRCode from 'qrcode-terminal';

// Step 1: Connect wallet via WalletConnect
const walletResult = await connectWallet(
  'your-walletconnect-project-id',
  'mainnet',
  {
    onUriGenerated: (uri) => {
      QRCode.generate(uri, { small: true });
      console.log('Scan this QR code with Keplr mobile');
    },
    timeoutMs: 120000 // 2 minutes
  }
);

if (!walletResult.success) {
  console.error('Connection failed:', walletResult.error.message);
  process.exit(1);
}

// Step 2: Deploy (user approves each transaction on their phone)
const result = await deployToAkash({
  wallet: walletResult.data,
  projectRoot: './my-app',
  profile: 'production',
  network: 'mainnet'
});

if (result.success) {
  console.log(`Deployed! DSEQ: ${result.data.dseq}`);
}
```

---

### Agent-Controlled Wallets

For autonomous agents that deploy without human interaction:

```typescript
import { createWalletContextFromSigner, deployToAkash } from 'deploy-ability';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';

class SelfDeployingAgent {
  private signer: OfflineSigner;

  async initialize() {
    // Agent loads its OWN mnemonic from encrypted storage
    const mnemonic = await this.secrets.getEncryptedMnemonic();

    // Create signer from mnemonic
    this.signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
      prefix: 'akash'
    });
  }

  async deploySelf() {
    // Create wallet context from agent's signer
    const walletResult = await createWalletContextFromSigner(
      this.signer,
      'mainnet'
    );

    if (!walletResult.success) {
      throw new Error(`Wallet setup failed: ${walletResult.error.message}`);
    }

    // Deploy autonomously (no human approval needed!)
    const result = await deployToAkash({
      wallet: walletResult.data,
      projectRoot: __dirname,
      profile: 'production',
      network: 'mainnet'
    });

    if (result.success) {
      console.log(`Self-deployed! DSEQ: ${result.data.dseq}`);
    }
  }
}
```

**Security Warning:** Only use your OWN mnemonic for agent wallets! Never give your mnemonic to third-party agents.

---

## Autonomous Deployment with Secrets Integration

For fully automated deployments, deploy-ability integrates with KADI's `secret-ability` to securely retrieve wallet mnemonics from encrypted vaults. This enables agents to deploy without any user interaction while keeping secrets secure.

### How It Works

```mermaid
sequenceDiagram
    participant Agent as KADI Agent
    participant Secrets as secret-ability
    participant Keychain as OS Keychain
    participant Deploy as deploy-ability
    participant Akash as Akash Network

    Agent->>Secrets: Get mnemonic from vault
    Secrets->>Keychain: Get master key
    Keychain-->>Secrets: Master key
    Secrets-->>Agent: Decrypted mnemonic
    Agent->>Deploy: deployToAkash({ autonomous: { secrets } })
    Deploy->>Akash: Create deployment (auto-sign)
    Akash-->>Deploy: Deployment result
    Deploy-->>Agent: Success!
```

### Vault Configuration

Secrets are stored in a **global vault** at the machine level:

| Setting | Value |
|---------|-------|
| Config Path | `~/.kadi/secrets/config.toml` |
| Vault Name | `global` |
| Vault Type | `age` (ChaCha20-Poly1305 encryption) |
| Master Key | Stored in OS Keychain (macOS Keychain, GNOME Keyring, etc.) |

### Secret Keys

| Key | Description |
|-----|-------------|
| `AKASH_WALLET` | BIP39 mnemonic phrase (12 or 24 words) |
| `akash-tls-certificate` | Cached TLS certificate for provider communication |
| `akash-wallet-password` | Optional BIP39 passphrase |

### Setup: Store Your Mnemonic

Before using autonomous deployment, store your mnemonic in the global vault:

```bash
# Store Akash wallet mnemonic (encrypted, OS keychain protects master key)
kadi secret set AKASH_WALLET "your twelve or twenty four word mnemonic phrase here" -v global
```

If the global vault doesn't exist yet:

```bash
# Create the global vault at ~/.kadi/secrets/config.toml (one-time)
kadi secret create global -g
```

Or programmatically with secret-ability:

```typescript
// 1. Create the global vault (one-time)
await secrets.invoke('config.createVault', {
  configPath: '~/.kadi/secrets/config.toml',
  name: 'global',
  type: 'age'
});

// 2. Store the mnemonic
await secrets.invoke('set', {
  configPath: '~/.kadi/secrets/config.toml',
  vault: 'global',
  key: 'AKASH_WALLET',
  value: 'your 12 or 24 word mnemonic phrase here'
});
```

### Usage: Autonomous Deployment

#### Option 1: Using `createSecretsProvider` (Recommended)

Integrates directly with KADI's secret-ability:

```typescript
import { 
  createSecretsProvider, 
  deployToAkash 
} from 'deploy-ability';

// In a KADI agent context:
class DeploymentAgent {
  async deploy(profile: string) {
    // Create secrets provider from secret-ability client
    const secrets = createSecretsProvider(this.secrets);

    // Deploy autonomously - no user interaction needed!
    const result = await deployToAkash({
      autonomous: {
        secrets,
        bidStrategy: 'cheapest',  // or 'most-reliable'
      },
      projectRoot: process.cwd(),
      profile,
      network: 'mainnet',
    });

    if (result.success) {
      console.log(`Deployed! DSEQ: ${result.data.dseq}`);
      console.log(`Provider: ${result.data.providerUri}`);
    }

    return result;
  }
}
```

#### Option 2: Using `createSimpleMnemonicProvider` (Testing)

For testing or when secret-ability is not available:

```typescript
import { 
  createSimpleMnemonicProvider, 
  deployToAkash 
} from 'deploy-ability';

// ⚠️ For testing only - mnemonic stored in memory
const secrets = createSimpleMnemonicProvider(
  'test test test test test test test test test test test junk'
);

const result = await deployToAkash({
  autonomous: {
    secrets,
    bidStrategy: 'cheapest',
  },
  projectRoot: './my-app',
  profile: 'production',
  network: 'testnet',  // Use testnet for testing!
});
```

#### Option 3: Custom SecretsProvider

Implement the `SecretsProvider` interface for custom secret backends:

```typescript
import { deployToAkash, SecretsProvider } from 'deploy-ability';

// Custom provider (e.g., AWS Secrets Manager, HashiCorp Vault)
const customSecrets: SecretsProvider = {
  async getMnemonic() {
    // Fetch from your secret backend
    return await mySecretManager.getSecret('akash-wallet-mnemonic');
  },

  async getCertificate() {
    // Optional: return cached certificate
    const cert = await mySecretManager.getSecret('akash-certificate');
    return cert ? JSON.parse(cert) : null;
  },

  async storeCertificate(cert) {
    // Optional: cache the certificate
    await mySecretManager.setSecret('akash-certificate', JSON.stringify(cert));
  },
};

const result = await deployToAkash({
  autonomous: { secrets: customSecrets, bidStrategy: 'cheapest' },
  projectRoot: './my-app',
  profile: 'production',
  network: 'mainnet',
});
```

### Bid Selection Strategies

When deploying autonomously, you must specify a bid selection strategy:

| Strategy | Description |
|----------|-------------|
| `'cheapest'` | Select the lowest price bid |
| `'most-reliable'` | Select the bid with highest provider uptime |
| `'balanced'` | Balance between price and provider reliability |

You can also add filters:

```typescript
autonomous: {
  secrets,
  bidStrategy: 'cheapest',
  bidFilter: {
    maxPricePerMonth: { uakt: 5_000_000 },  // Max 5 AKT/month
    requireAudited: true,                    // Only audited providers
    requireOnline: true,                     // Only reachable providers (default: true)
    minUptime: { value: 0.95, period: '7d' }, // 95% uptime over 7 days
  },
}
```

> **Note:** `requireOnline` defaults to `true` for all strategies, filtering out providers that fail a reachability pre-check before lease creation. Set `requireOnline: false` to disable this.

### Provider Resilience

Autonomous deployments include built-in provider resilience to handle intermittent provider connectivity issues:

- **Reachability Pre-Check** — Before committing to a lease, the deployer verifies the provider is reachable via an HTTPS `/status` probe on port 8443.
- **Automatic Retry** — If manifest delivery fails with a `PROVIDER_UNREACHABLE` error, the deployer retries (with exponential back-off) before giving up on that provider.
- **Provider Fallback** — Bids are ranked, and if the top provider is unreachable, the deployer automatically falls back to the next-best bid (up to 3 attempts).
- **Escrow Cleanup** — If all provider attempts fail, any created lease is closed to prevent AKT escrow leaks.

This means transient provider outages no longer cause hard deployment failures — the system finds an available provider automatically.

### Certificate Management

deploy-ability automatically manages TLS certificates needed for Akash provider communication:

1. **Cache lookup** — Retrieves any cached certificate from the `SecretsProvider`
2. **On-chain validation** — Queries the blockchain for valid certificates and compares the cached cert PEM byte-for-byte against the on-chain cert PEM
3. **Auto-regeneration** — If no valid certificate exists on-chain or the cached cert doesn't match, revokes the stale one and generates a new certificate
4. **Persistent storage** — New certificates are written to both `~/.kadi/certificate.json` and the secrets vault to prevent unnecessary regeneration on subsequent deploys

This is fully automatic and requires no user intervention for normal deployments.

#### Force Certificate Regeneration

After chain upgrades or if you suspect certificate issues, you can force a full revoke-and-regenerate cycle using the `forceNewCertificate` flag:

```typescript
import { deployToAkash } from '@kadi.build/deploy-ability/akash';

const result = await deployToAkash({
  autonomous: {
    secrets,
    bidStrategy: 'cheapest',
    forceNewCertificate: true,  // Revoke existing cert and generate a new one
  },
  projectRoot: process.cwd(),
  profile: 'production',
  network: 'mainnet',
});
```

#### From kadi-deploy (CLI/Agent Context)

If you're building on top of kadi-deploy and need to force certificate regeneration, pass the flag when constructing the `AutonomousDeploymentConfig`:

```typescript
import type { AutonomousDeploymentConfig } from '@kadi.build/deploy-ability/types';

const autonomousConfig: AutonomousDeploymentConfig = {
  secrets: secretsProvider,
  bidStrategy: 'cheapest',
  forceNewCertificate: true,  // One-time flag to fix cert issues
};

const result = await deployToAkash({
  autonomous: autonomousConfig,
  projectRoot,
  profile: profileName,
  network: 'mainnet',
});
```

> **Tip:** You generally only need `forceNewCertificate` as a one-time fix after a chain upgrade invalidates existing certificates. For normal deployments, the on-chain validation handles stale certs automatically.

### AKT ↔ ACT Token Management (BME)

Akash Network uses a Burn-Mint Equilibrium (BME) model with two tokens:

- **AKT** (`uakt`) — Staking, governance, and gas fees
- **ACT** (`uact`) — Compute token used for deployment escrow deposits and provider payments

deploy-ability automatically handles the AKT → ACT conversion:

1. Before creating a deployment, it queries the wallet's AKT and ACT balances
2. If ACT balance is insufficient for the deposit, it auto-mints ACT by burning AKT via `sdk.akash.bme.v1.mintACT()`
3. A 10% buffer is added to cover gas/rounding
4. Balances are logged to the terminal before and after minting

#### Programmatic BME Operations

All BME operations are available on `AkashClient`:

```typescript
import { AkashClient } from '@kadi.build/deploy-ability/akash';

const client = new AkashClient({ network: 'mainnet', signer });

// Query both AKT and ACT balances
const balance = await client.getBalance('akash1...');
if (balance.success) {
  console.log(`AKT: ${balance.data.balanceAkt}`);
  console.log(`ACT: ${balance.data.balanceAct}`);
}

// Mint ACT from AKT (burns AKT, mints equivalent ACT)
const mint = await client.mintAct(5); // 5 AKT → ACT
if (mint.success) {
  console.log(`Burned: ${mint.data.burnedUakt} uAKT`);
  console.log(`Minted: ${mint.data.mintedUact} uACT`);
}

// Burn ACT back to AKT
const burn = await client.burnAct(5); // 5 ACT → AKT

// Ensure sufficient ACT balance, auto-minting from AKT if needed
const ensure = await client.ensureActBalance(5); // need 5 ACT
if (ensure.success && ensure.data.minted) {
  console.log(`Auto-minted ${ensure.data.mintedUact} uACT`);
}

// Deposit ACT to active deployment escrow
const deposit = await client.depositDeployment(dseq, 5);
```

#### BME Types

```typescript
import type {
  MintActResult,
  BurnActResult,
  EnsureActBalanceResult,
  WalletBalance,
} from '@kadi.build/deploy-ability/akash';
```

### Exported Constants

For programmatic access to configuration:

```typescript
import {
  AKASH_VAULT_NAME,      // 'global'
  GLOBAL_CONFIG_PATH,    // '~/.kadi/secrets/config.toml'
  AKASH_SECRET_KEYS,     // { WALLET_MNEMONIC: 'AKASH_WALLET', ... }
} from 'deploy-ability';
```

### Security Model

1. **Encrypted at Rest**: Secrets are encrypted with ChaCha20-Poly1305 in `config.toml`
2. **Master Key in Keychain**: The encryption key is stored in the OS keychain (never in files)
3. **No Plaintext**: Mnemonics are never written to disk in plaintext
4. **Per-Machine**: Secrets are tied to the machine's keychain - can't be copied

---

## Configuration (config.yml)

deploy-ability resolves infrastructure settings from `config.yml` using walk-up discovery (searching from CWD up through parent directories) with a global fallback at `~/.kadi/config.yml`.

### Config Sections

**`deploy:` section** — deployment and registry settings:

```yaml
deploy:
  registry_port: 3000            # Local registry port for image pushing
  container_engine: docker       # docker | podman
  auto_shutdown: true            # Shut down registry after deploy
  registry_duration: 600000      # Registry timeout in ms (10 min)
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `registry_port` | number | `3000` | Local Docker registry port when deploying local images |
| `container_engine` | string | `docker` | Container engine: `docker` or `podman` |
| `auto_shutdown` | boolean | `true` | Automatically shut down the registry after deployment completes |
| `registry_duration` | number | `600000` | Registry timeout in milliseconds (10 minutes) |

**`deploy.shell:` section** — interactive shell access settings (used by `kadi deploy shell`):

```yaml
deploy:
  shell:
    wallet_vault: global                                 # Vault containing wallet mnemonic
    wallet_key: AKASH_WALLET                             # Key name inside vault
    rpc_mainnet: https://rpc.akashnet.net:443            # Akash mainnet RPC endpoint
    rpc_testnet: https://rpc.testnet-02.aksh.pw:443      # Akash testnet RPC endpoint
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `wallet_vault` | string | `global` | Vault name containing the wallet mnemonic for shell authentication |
| `wallet_key` | string | `AKASH_WALLET` | Secret key name to retrieve from the vault |
| `rpc_mainnet` | string | `https://rpc.akashnet.net:443` | RPC endpoint for Akash mainnet deployments |
| `rpc_testnet` | string | `https://rpc.testnet-02.aksh.pw:443` | RPC endpoint for Akash testnet deployments |

These values can also be set via environment variables with the `KADI_DEPLOY_` prefix (e.g. `KADI_DEPLOY_SHELL_WALLET_VAULT`).

**`tunnel:` section** — tunnel infrastructure (used when deploying local images to Akash):

```yaml
tunnel:
  default_service: kadi
  server_addr: broker.kadi.build
  tunnel_domain: tunnel.kadi.build
  server_port: 7000
  ssh_port: 2200
  mode: frpc
  transport: wss
  wss_control_host: tunnel-control.kadi.build
  agent_id: kadi
```

| Field | Type | Default | Env Var | Description |
|-------|------|---------|---------|-------------|
| `default_service` | string | `kadi` | `TUNNEL_SERVICE` | Tunnel service to use |
| `server_addr` | string | `broker.kadi.build` | `KADI_TUNNEL_SERVER` | Tunnel server address |
| `tunnel_domain` | string | `tunnel.kadi.build` | `KADI_TUNNEL_DOMAIN` | Tunnel domain for routing |
| `server_port` | number | `7000` | `KADI_TUNNEL_PORT` | Tunnel server port |
| `ssh_port` | number | `2200` | `KADI_TUNNEL_SSH_PORT` | SSH port for tunnel access |
| `mode` | string | `frpc` | `KADI_TUNNEL_MODE` | Tunnel mode |
| `transport` | string | `wss` | `KADI_TUNNEL_TRANSPORT` | Tunnel transport protocol |
| `wss_control_host` | string | `tunnel-control.kadi.build` | `KADI_TUNNEL_WSS_HOST` | WSS control host |
| `agent_id` | string | `kadi` | `KADI_AGENT_ID` | Agent identifier for tunnel context |

See [tunnel-services config.sample.yml](../tunnel-services/config.sample.yml) and [deploy-ability config.sample.yml](config.sample.yml) for full reference.

All values have sensible defaults — you only need `config.yml` to override something.

### Secrets Setup

| Secret | Vault | Scope | Setup Command |
|--------|-------|-------|---------------|
| `AKASH_WALLET` | `global` | Global (`~/.kadi/`) | `kadi secret set AKASH_WALLET "mnemonic..." -v global` |
| `KADI_TUNNEL_TOKEN` | `tunnel` | Global (`~/.kadi/`) | `kadi secret set KADI_TUNNEL_TOKEN "token" -v tunnel` |
| `NGROK_AUTH_TOKEN` | `tunnel` | Global (`~/.kadi/`) | `kadi secret set NGROK_AUTH_TOKEN "token" -v tunnel` |

**Global vs Project** — Infrastructure secrets (wallet, tunnel tokens) live at `~/.kadi/secrets/config.toml` so they're available from any project directory. Agent-specific secrets (API keys, DB passwords) stay in the project-level `secrets.toml`.

```bash
# One-time setup for the tunnel vault at global level
kadi secret create tunnel -g
kadi secret set KADI_TUNNEL_TOKEN "your-token" -v tunnel
```

### Resolution Priority

Each setting resolves from highest to lowest priority:

1. **Function arguments** — options passed to `deployToAkash()` / `deployToLocal()`
2. **Environment variables** — `KADI_TUNNEL_SERVER`, `KADI_TUNNEL_TOKEN`, etc.
3. **Encrypted vault** — `secrets.toml` via secret-ability
4. **Project `config.yml`** — walk-up from CWD
5. **Global `~/.kadi/config.yml`** — machine-wide defaults
6. **Built-in defaults**

---

## Error Handling

deploy-ability uses a **Result type pattern** for predictable error handling:

```typescript
type Result<T, E> =
  | { success: true; data: T }
  | { success: false; error: E };
```

### Pattern 1: Check Success

```typescript
const result = await deployToAkash(options);

if (result.success) {
  console.log('Deployed:', result.data.dseq);
} else {
  console.error('Failed:', result.error.message);
  console.error('Code:', result.error.code);
  console.error('Context:', result.error.context);
}
```

### Pattern 2: Throw on Failure

```typescript
const result = await deployToAkash(options);

if (!result.success) {
  throw result.error; // DeploymentError
}

const deployment = result.data; // TypeScript knows this is safe!
```

### Common Error Codes

| Code | Type | Meaning |
|------|------|---------|
| `WALLET_NOT_FOUND` | WalletError | Keplr not installed |
| `CONNECTION_REJECTED` | WalletError | User rejected connection |
| `APPROVAL_TIMEOUT` | WalletError | User didn't approve in time |
| `INSUFFICIENT_FUNDS` | WalletError | Not enough AKT for deployment |
| `CERTIFICATE_NOT_FOUND` | CertificateError | No certificate on-chain |
| `CERTIFICATE_EXPIRED` | CertificateError | Certificate needs regeneration |
| `NO_BIDS_RECEIVED` | DeploymentError | No providers bid on deployment |
| `PROVIDER_UNREACHABLE` | ProviderError | Provider failed reachability check or manifest delivery |
| `ALL_PROVIDERS_UNREACHABLE` | DeploymentError | All ranked providers were unreachable; leases cleaned up |
| `RPC_ERROR` | DeploymentError | Blockchain RPC failure |

---

## Security Best Practices

### Safe Patterns

**1. WalletConnect for Third-Party Services**

```typescript
// GOOD - User approves each transaction
const wallet = await connectWallet(projectId, 'mainnet', { ... });
await thirdPartyService.deploy({ wallet: wallet.data });
// User sees and approves every transaction on their phone
```

**2. Agent Uses Its Own Wallet**

```typescript
// GOOD - Agent uses its own funds
class DeploymentAgent {
  async deploy(userProject) {
    const mnemonic = await this.secrets.getOwnMnemonic(); // Agent's wallet!
    const wallet = await createWalletFromMnemonic(mnemonic, 'mainnet');
    await deployToAkash({ wallet: wallet.data, ...userProject });
    // Agent pays, user pays agent separately (credit card, etc.)
  }
}
```

**3. CI/CD from Secure Secrets**

```typescript
// GOOD - Mnemonic in GitHub Secrets
const mnemonic = process.env.DEPLOYMENT_WALLET_MNEMONIC; // GitHub Secret
const wallet = await createWalletFromMnemonic(mnemonic, 'mainnet');
```

---

### Dangerous Patterns

**1. Never Give Mnemonic to Third Parties**

```typescript
// DANGEROUS - They can steal all your funds!
const thirdPartyAgent = new SomeoneElsesAgent();
await thirdPartyAgent.deploy({
  mnemonic: myMnemonic,  // They now control your wallet!
  project: './my-app'
});
```

**2. Never Hardcode Mnemonics**

```typescript
// DANGEROUS - Committed to git!
const mnemonic = "word1 word2 word3..."; // In source code!
```

---

## Shell Access

The shell module provides interactive terminal access to running deployment containers. It exposes a provider-agnostic `ShellProvider` interface with built-in implementations for Akash and local Docker/Podman targets.

### Security Model

All secrets (wallet mnemonics, API tokens) are retrieved from the KADI vault at runtime and exist only in process memory. They are **never** written to `.env` files or any unencrypted storage.

- **Akash**: Wallet mnemonic is pulled from the vault, imported into the OS keyring (macOS Keychain / Linux secret-service) under an ephemeral key name, and deleted when the session ends.
- **Local**: Docker/Podman use the local socket — no vault secrets needed.
- **Future targets**: API tokens and credentials flow through `ShellSecretsAccess` as environment variables piped to the spawned process.

### ShellProvider Interface

```typescript
import type {
  ShellProvider,
  ShellOptions,
  ShellResult,
  ShellContext,
  ShellSecretsAccess,
  AuthSession,
} from '@kadi.build/deploy-ability';

// Each target implements this interface
interface ShellProvider {
  readonly target: string;
  checkPrerequisites(context: ShellContext): Promise<string | null>;
  setupAuth?(context: ShellContext, secrets: ShellSecretsAccess): Promise<AuthSession>;
  openShell(context: ShellContext, options: ShellOptions): Promise<ShellResult>;
  getInstallMethods?(): InstallMethod[];
}

// Install methods returned by getInstallMethods()
interface InstallMethod {
  readonly label: string;                          // e.g. "Homebrew (recommended)"
  readonly platform: 'darwin' | 'linux' | 'win32' | 'all';
  readonly command: string;                        // Shell command to run
  readonly interactive?: boolean;                  // Needs a TTY (e.g. sudo)
}
```

### Prerequisite Auto-Install

Providers can expose `getInstallMethods()` so callers can offer automatic installation when `checkPrerequisites` fails. The Akash provider ships methods for Homebrew (macOS) and direct binary download (macOS/Linux). The local provider returns an empty array since Docker/Podman installs are too complex.

```typescript
const methods = akashShellProvider.getInstallMethods?.() ?? [];
const macMethods = methods.filter(m => m.platform === 'darwin');
// → [{ label: 'Homebrew (recommended)', command: 'brew install ...', ... }, ...]
```

Install-time auto-install is also handled by the KADI lifecycle `preflight` script — when you run `kadi install`, `provider-services` is installed automatically alongside other prerequisites.

### Built-in Providers

```typescript
// Akash — uses `provider-services lease-shell`
import { akashShellProvider } from '@kadi.build/deploy-ability/akash';

// Local — uses `docker exec` or `podman exec`
import { localShellProvider } from '@kadi.build/deploy-ability/local';
```

### Usage Example

```typescript
import { akashShellProvider } from '@kadi.build/deploy-ability/akash';
import type { AkashShellContext, ShellSecretsAccess } from '@kadi.build/deploy-ability';

const ctx: AkashShellContext = {
  target: 'akash',
  owner: 'akash1...',
  dseq: 26151304,
  provider: 'akash1...',
  network: 'mainnet',
};

// Check prerequisites
const error = await akashShellProvider.checkPrerequisites(ctx);
if (error) {
  console.error(error);
  process.exit(1);
}

// Set up authentication (vault → ephemeral OS keyring entry)
const secrets: ShellSecretsAccess = {
  async getSecret(key, vault) {
    // Your vault implementation here
    return process.env[key] ?? null;
  },
};

const auth = await akashShellProvider.setupAuth!(ctx, secrets);

try {
  // Open interactive shell
  const result = await akashShellProvider.openShell(ctx, {
    service: 'modelmanager',
    command: '/bin/sh',
    tty: true,
    stdin: process.stdin,
    stdout: process.stdout,
    stderr: process.stderr,
    auth, // pass the auth session
  });

  process.exit(result.exitCode);
} finally {
  // Always clean up ephemeral credentials
  await auth.cleanup();
}
```

### Adding a New Target

See **[ADDING-SHELL-TARGETS.md](ADDING-SHELL-TARGETS.md)** for a complete step-by-step guide covering every file you need to touch to add shell access for a new cloud target (DigitalOcean, AWS, Azure, GCP, etc.).

Quick version — implement the `ShellProvider` interface for your target:

```typescript
import type {
  ShellProvider,
  ShellContext,
  ShellOptions,
  ShellResult,
  ShellSecretsAccess,
  AuthSession,
} from '@kadi.build/deploy-ability';

export const myCloudShellProvider: ShellProvider = {
  target: 'mycloud',

  async checkPrerequisites(context: ShellContext): Promise<string | null> {
    // Check that required CLIs/tools are installed
    return null; // or return error string
  },

  async setupAuth(context: ShellContext, secrets: ShellSecretsAccess): Promise<AuthSession> {
    const token = await secrets.getSecret('MYCLOUD_TOKEN', 'global');
    if (!token) throw new Error('MYCLOUD_TOKEN not found in vault');
    return {
      env: { MYCLOUD_TOKEN: token },
      async cleanup() { /* revoke token if needed */ },
    };
  },

  async openShell(context: ShellContext, options: ShellOptions): Promise<ShellResult> {
    // Spawn the shell process, bridge stdin/stdout/stderr
    // Use options.auth.env / options.auth.args for credentials
    return { exitCode: 0 };
  },
};
```

---

## Migration from kadi-deploy

### Before (kadi-deploy internals)

```typescript
// Was never meant to be public API
import { AkashDeployer } from 'kadi-deploy/src/targets/akash/akash.js';
```

### After (deploy-ability)

```typescript
// Clean public API
import { deployToAkash } from 'deploy-ability';
```

### Migration Steps

1. **Replace imports**
   ```typescript
   // Before
   import { checkWallet } from 'kadi-deploy/src/targets/akash/wallet.js';

   // After
   import { connectWallet } from 'deploy-ability';
   ```

2. **Update function signatures**
   ```typescript
   // Before (kadi-deploy - CLI context required)
   const wallet = await checkWallet(logger, 'mainnet', projectId);

   // After (deploy-ability - pure functions)
   const wallet = await connectWallet(projectId, 'mainnet', {
     onUriGenerated: (uri) => console.log(uri)
   });
   ```

3. **Handle Result types**
   ```typescript
   // deploy-ability uses Result<T, E> pattern
   if (!wallet.success) {
     console.error(wallet.error.message);
     return;
   }

   const myWallet = wallet.data;
   ```

---

## Documentation

- **[Adding Shell Targets](ADDING-SHELL-TARGETS.md)** - Step-by-step guide to adding shell access for new cloud targets
- **[Storage Configuration Guide](docs/STORAGE.md)** - Understanding memory, ephemeral storage, and persistent volumes
- **[Placement Attributes Guide](docs/PLACEMENT.md)** - Geographic placement and provider selection
- **[Detailed Examples](docs/EXAMPLES.md)** - CI/CD integration, API servers, and more
- **[API Reference](https://github.com/kadi-framework/deploy-ability)** - Complete TypeScript API documentation

---

## Contributing

Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.

## Changelog

### 0.0.23

- **Feature**: AKT ↔ ACT token management via Burn-Mint Equilibrium (BME). New `AkashClient` methods: `mintAct()`, `burnAct()`, `ensureActBalance()`. Deployments auto-mint ACT from AKT when ACT balance is insufficient for the escrow deposit.
- **Feature**: Wallet balance logging during autonomous deployments — AKT/ACT balances, deposit requirement, and mint details are printed to the terminal.
- **Fix**: Certificate validation now compares cached cert PEM against on-chain cert PEM (previously only checked existence), preventing SSL `bad certificate` errors when the cached cert doesn't match the on-chain cert.
- **Fix**: Certificate storage writes to both `~/.kadi/certificate.json` and the secrets vault, preventing unnecessary cert regeneration on every deploy.
- **Fix**: `depositDeployment()` now uses correct `uact` denom for escrow deposits (was `uakt`).
- New exported types: `MintActResult`, `BurnActResult`, `EnsureActBalanceResult`.

### 0.0.16

- **Fix**: Escrow account parsing updated for chain-sdk v1 `Account` type. The `escrowAccount` field from `QueryDeploymentResponse` now uses the v1 structure (`Account → AccountState` with `funds: Balance[]`, `transferred: DecCoin[]`) instead of the old v1beta3 flat format. This fixes escrow balance/transferred/time-left displaying as zero in the deploy-agent dashboard.
- Added `findCoinAmount()` helper for extracting amounts from coin arrays by denom.
- Legacy v1beta3 escrow format retained as fallback.

### 0.0.15

- Single SDK instance optimization, autonomous deployment support, secrets provider integration.

## License

MIT © KADI Framework

---

## Resources

- **Akash Network**: https://akash.network/
- **Akash Console**: https://console.akash.network/
- **KADI Framework**: https://github.com/kadi-framework
- **Keplr Wallet**: https://www.keplr.app/
- **WalletConnect**: https://walletconnect.com/
- **CosmJS Documentation**: https://cosmos.github.io/cosmjs/

---

**Built with ❤️ by the KADI team**