docs/guides/cli_guide.md

# CLI Reference Guide

Complete reference for the Tinkex command-line interface. The CLI provides a thin wrapper over the SDK for quick checkpoint management, text generation, and API exploration without writing Elixir code.

## Overview

The Tinkex CLI is distributed as an escript executable that bundles the entire application into a single file. It supports three main command groups:

- `checkpoint` - Save and manage model checkpoints
- `run` - Generate text completions and manage training runs
- `version` - Display version information

All commands support a consistent set of global options for API configuration, and most operations return structured output that can be saved to files or piped to other tools.

## Installation

Build the escript from source:

```bash
cd tinkex
MIX_ENV=prod mix escript.build   # emits ./tinkex
```

Optionally install to your PATH:

```bash
mix escript.install ./tinkex     # installs to ~/.mix/escripts
```

Verify the installation:

```bash
./tinkex version
# or if installed:
tinkex version
```

## Global Options

These options are available for all commands that interact with the Tinker API:

- `--api-key <key>` - API key for authentication (required, or set `TINKER_API_KEY` env var)
- `--base-url <url>` - API base URL (defaults to production endpoint)
- `--timeout <ms>` - Request timeout in milliseconds (default: 120000)

Example using global options:

```bash
./tinkex run \
  --api-key "$TINKER_API_KEY" \
  --base-url "https://tinker.thinkingmachines.dev/services/tinker-prod" \
  --timeout 60000 \
  --prompt "Hello"
```

## `tinkex checkpoint` - Checkpoint Management

The `checkpoint` command provides two modes: saving new checkpoints and managing existing ones.

### Save Checkpoints

Create and save a checkpoint for a model configuration:

```bash
./tinkex checkpoint \
  --base-model meta-llama/Llama-3.1-8B \
  --rank 32 \
  --output ./checkpoint.json \
  --api-key "$TINKER_API_KEY"
```

#### Options

**Model Configuration:**
- `--base-model <id>` - Base model identifier (required, e.g., `meta-llama/Llama-3.1-8B`)
- `--model-path <path>` - Local model path (alternative to `--base-model`)

**Output:**
- `--output <path>` - Path to write checkpoint metadata JSON (required)

**LoRA Configuration:**
- `--rank <int>` - LoRA rank (default: 32)
- `--seed <int>` - Random seed for reproducibility
- `--train-mlp` - Enable MLP training (default: true)
- `--train-attn` - Enable attention training (default: true)
- `--train-unembed` - Enable unembedding training (default: true)

#### Checkpoint Metadata Format

The checkpoint command writes a JSON metadata file to the specified `--output` path:

```json
{
  "base_model": "meta-llama/Llama-3.1-8B",
  "model_id": "run-abc123/weights/0001",
  "weights_path": "/path/to/weights",
  "saved_at": "2024-11-26T12:34:56Z",
  "response": {
    "model_id": "run-abc123/weights/0001",
    "path": "/path/to/weights"
  }
}
```

**Note:** The actual model weights are stored on the Tinker service. The local metadata file contains references and timestamps for tracking purposes.

#### Example: Save Checkpoint with Custom LoRA Config

```bash
./tinkex checkpoint \
  --base-model Qwen/Qwen3-8B \
  --rank 64 \
  --seed 42 \
  --train-mlp \
  --train-attn \
  --output checkpoints/qwen-lora-64.json \
  --api-key "$TINKER_API_KEY"
```

### List Checkpoints

List all user checkpoints with pagination and JSON output, or filter by training run:

```bash
./tinkex checkpoint list [--run-id <id>] [--limit <int>] [--offset <int>] [--format table|json]
```

**Options:**
- `--run-id <id>` - Restrict results to a single training run
- `--limit <int>` - Maximum number of checkpoints to return (`0` = fetch all; default: 20)
- `--offset <int>` - Number of checkpoints to skip (default: 0)
- `--format table|json` / `--json` - Output format (default: table); JSON includes `total` and `shown` counts

When multiple pages are fetched (`--limit 0` or large lists), progress is printed to stderr while stdout remains clean for piping/JSON.

**Examples:**

```bash
# Fetch all checkpoints with JSON output
./tinkex checkpoint list --limit 0 --format json --api-key "$TINKER_API_KEY"

# List checkpoints for a single run
./tinkex checkpoint list --run-id run-123 --limit 5 --api-key "$TINKER_API_KEY"
```

**JSON shape (truncated):**

```json
{
  "total": 3,
  "shown": 3,
  "checkpoints": [
    {
      "checkpoint_id": "ckpt-1",
      "checkpoint_type": "weights",
      "training_run_id": "run-123",
      "size_bytes": 1024,
      "public": true,
      "time": "2025-11-26T00:00:00Z",
      "tinker_path": "tinker://run-123/weights/0001"
    }
  ]
}
```

### Get Checkpoint Info

Retrieve detailed information about a specific checkpoint, including size, visibility, timestamps, training run ID, and base model/LoRA metadata:

```bash
./tinkex checkpoint info <tinker_path> [--format table|json]
```

**Arguments:**
- `<tinker_path>` - Checkpoint path (e.g., `tinker://run-123/weights/0001`)

**Example output (table):**
```
Checkpoint ID: ckpt-1
Training run ID: run-123
Type: weights
Path: tinker://run-123/weights/0001
Size: 1.0 KB
Public: true
Created: 2025-11-26T00:00:00Z
Base model: meta-llama/Llama-3.1-8B
LoRA: true
LoRA rank: 32
```

Pass `--format json` (or `--json`) to receive the full checkpoint + weights metadata as a JSON object.

**Example:**

```bash
./tinkex checkpoint info tinker://run-abc123/weights/0001 \
  --api-key "$TINKER_API_KEY"
```

### Publish Checkpoint

Make a checkpoint publicly accessible:

```bash
./tinkex checkpoint publish <tinker_path>
```

**Example:**

```bash
./tinkex checkpoint publish tinker://run-123/weights/0001 \
  --api-key "$TINKER_API_KEY"
# Output: Published tinker://run-123/weights/0001
```

### Unpublish Checkpoint

Remove public access from a checkpoint:

```bash
./tinkex checkpoint unpublish <tinker_path>
```

**Example:**

```bash
./tinkex checkpoint unpublish tinker://run-123/weights/0001 \
  --api-key "$TINKER_API_KEY"
# Output: Unpublished tinker://run-123/weights/0001
```

### Delete Checkpoint

Permanently delete one or more checkpoints with a single confirmation:

```bash
./tinkex checkpoint delete <tinker_path> [<tinker_path> ...] [--yes]
```

**Warning:** This operation is irreversible. Ensure you have backups if needed. Use `--yes` to
skip the interactive confirmation prompt.

**Example:**

```bash
./tinkex checkpoint delete tinker://run-old/weights/0001 tinker://run-old/weights/0002 \
  --api-key "$TINKER_API_KEY"
# Output: Preparing to delete 2 checkpoints...
#         Deleted tinker://run-old/weights/0001
#         Deleted tinker://run-old/weights/0002
```

For an end-to-end live flow that creates two checkpoints and deletes both with a
single `--yes` confirmation, see `examples/checkpoint_multi_delete_live.exs`.

### Download Checkpoint

Download and extract checkpoint files locally:

```bash
./tinkex checkpoint download <tinker_path> [--output <dir>] [--force]
```

**Options:**
- `--output <dir>` - Output directory for extracted files (default: current directory)
- `--force` - Overwrite existing files if present

**Example:**

```bash
./tinkex checkpoint download tinker://run-123/weights/0001 \
  --output ./models/checkpoint-001 \
  --force \
  --api-key "$TINKER_API_KEY"
# Output: Downloaded to ./models/checkpoint-001
```

### Help for Checkpoint Commands

```bash
./tinkex checkpoint --help
./tinkex checkpoint list --help
```

## `tinkex run` - Text Generation

The `run` command generates text completions using the Tinker sampling API and manages training runs.

### Generate Text

Sample text completions from a model:

```bash
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "Hello there" \
  --max-tokens 64 \
  --temperature 0.7 \
  --num-samples 2 \
  --api-key "$TINKER_API_KEY"
```

#### Options

**Model Configuration:**
- `--base-model <id>` - Base model identifier (required, e.g., `meta-llama/Llama-3.1-8B`)
- `--model-path <path>` - Local model path (alternative to `--base-model`)

**Prompt Input (choose one):**
- `--prompt <text>` - Prompt text directly on command line
- `--prompt-file <path>` - Path to file containing prompt (see [Prompt Input Formats](#prompt-input-formats))

**Sampling Parameters:**
- `--max-tokens <int>` - Maximum tokens to generate
- `--temperature <float>` - Sampling temperature (default: 1.0)
- `--top-k <int>` - Top-k sampling parameter (default: -1, disabled)
- `--top-p <float>` - Nucleus sampling parameter (default: 1.0)
- `--num-samples <int>` - Number of samples to return (default: 1)

**Output Control:**
- `--output <path>` - Write output to file instead of stdout
- `--json` - Output full response as JSON instead of plain text

**Advanced:**
- `--http-pool <name>` - HTTP pool name to use for connection pooling

#### Plain Text Output

By default, `tinkex run` decodes tokens and prints human-readable text:

```bash
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "The capital of France is" \
  --max-tokens 10 \
  --api-key "$TINKER_API_KEY"
```

**Output:**
```
Starting sampling...
Sample 1:
Paris, which is located in the northern

stop_reason=length | avg_logprob=-1.234
Sampling complete (1 sequences)
```

#### JSON Output

Use `--json` to get the full structured response:

```bash
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "Hello" \
  --max-tokens 5 \
  --json \
  --api-key "$TINKER_API_KEY"
```

**Output:**
```json
{
  "sequences": [
    {
      "tokens": [1245, 345, 678, 901, 234],
      "logprobs": [-0.123, -0.456, -0.789, -0.234, -0.567],
      "stop_reason": "length"
    }
  ],
  "prompt_logprobs": null,
  "topk_prompt_logprobs": null,
  "type": "sample"
}
```

#### Prompt Input Formats

The CLI supports multiple prompt input formats via `--prompt-file`:

**Plain Text File:**

```bash
# Create a text file
echo "Write a haiku about coding" > prompt.txt

./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt-file prompt.txt \
  --max-tokens 50 \
  --api-key "$TINKER_API_KEY"
```

**JSON Token Array:**

For precise control, provide pre-tokenized input as a JSON array of integers:

```bash
# Create a JSON file with token IDs
echo '[1, 2, 3, 4, 5]' > tokens.json

./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt-file tokens.json \
  --max-tokens 20 \
  --api-key "$TINKER_API_KEY"
```

**JSON Token Object:**

Alternatively, wrap tokens in an object:

```json
{
  "tokens": [1, 2, 3, 4, 5]
}
```

The CLI automatically detects the format:
- If the file parses as JSON and contains an integer array (or `{"tokens": [...]}`), it's treated as token IDs
- Otherwise, it's treated as plain text

#### Writing Output to Files

Use `--output` to write results to a file instead of stdout:

```bash
# Plain text output
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "Generate a story" \
  --max-tokens 200 \
  --output story.txt \
  --api-key "$TINKER_API_KEY"

# JSON output
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "Hello world" \
  --max-tokens 50 \
  --json \
  --output response.json \
  --api-key "$TINKER_API_KEY"
```

#### Multiple Samples

Generate multiple completions in a single request:

```bash
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "Once upon a time" \
  --max-tokens 50 \
  --num-samples 3 \
  --temperature 0.9 \
  --api-key "$TINKER_API_KEY"
```

**Output:**
```
Starting sampling...
Sample 1:
, there was a brave knight...
stop_reason=length | avg_logprob=-1.234

Sample 2:
, in a land far away...
stop_reason=length | avg_logprob=-1.456

Sample 3:
, a young wizard discovered...
stop_reason=length | avg_logprob=-1.123

Sampling complete (3 sequences)
```

### List Training Runs

List all training runs with pagination and JSON output:

```bash
./tinkex run list [--limit <int>] [--offset <int>] [--format table|json]
```

**Options:**
- `--limit <int>` - Maximum number of runs to return (`0` = fetch all; default: 20)
- `--offset <int>` - Number of runs to skip (default: 0)
- `--format table|json` / `--json` - Output format (default: table); JSON includes `total`/`shown` plus full run objects

Progress is printed to stderr when multiple pages are fetched (e.g., `--limit 0`).

**Example (JSON):**

```bash
./tinkex run list --limit 0 --format json --api-key "$TINKER_API_KEY"
```

```json
{
  "total": 3,
  "shown": 3,
  "runs": [
    {
      "training_run_id": "run-123",
      "base_model": "meta-llama/Llama-3.1-8B",
      "model_owner": "owner@example.com",
      "is_lora": true,
      "lora_rank": 16,
      "corrupted": false,
      "last_request_time": "2025-11-26T00:00:00Z",
      "last_checkpoint": {...},
      "last_sampler_checkpoint": {...},
      "user_metadata": {"stage": "prod"}
    }
  ]
}
```

### Get Training Run Info

Retrieve detailed information about a specific training run:

```bash
./tinkex run info <run_id> [--format table|json]
```

**Arguments:**
- `<run_id>` - Training run identifier

**Table output:**
```
run-abc123 (meta-llama/Llama-3.1-8B)
Owner: user@example.com
LoRA: Yes
LoRA rank: 16
Status: Active
Last update: 2025-11-26T00:00:00Z
Last training checkpoint: ckpt-123
  Time: 2025-11-26T00:00:00Z
  Path: tinker://run-abc123/weights/0001
Metadata: stage=prod
```

`--format json` (or `--json`) returns the full training run object, including owner, LoRA rank, last training/sampler checkpoints, and user metadata.

### Help for Run Commands

```bash
./tinkex run --help
./tinkex run list --help
```

## `tinkex version` - Version Information

Display version and build information:

```bash
./tinkex version
# Output: tinkex 0.1.8 (abc1234)

./tinkex --version  # alias
```

### JSON Output

Get structured version information:

```bash
./tinkex version --json
```

**Output:**
```json
{
  "version": "0.1.8",
  "commit": "abc1234"
}
```

The commit hash is the short Git SHA from the build environment (7 characters). If Git is unavailable or the build is not from a Git repository, the commit field will be `null`.

## Programmatic CLI Invocation

You can invoke the CLI from Elixir scripts using `Tinkex.CLI.run/1`:

```elixir
# examples/cli_run_text.exs
defmodule MyScript do
  alias Tinkex.CLI

  def run do
    {:ok, _} = Application.ensure_all_started(:tinkex)

    args = [
      "run",
      "--base-model", "meta-llama/Llama-3.1-8B",
      "--prompt", "Hello from Elixir",
      "--max-tokens", "64",
      "--temperature", "0.7",
      "--api-key", System.fetch_env!("TINKER_API_KEY")
    ]

    case CLI.run(args) do
      {:ok, %{response: response}} ->
        IO.inspect(response, label: "sampling response")

      {:error, reason} ->
        IO.puts(:stderr, "CLI failed: #{inspect(reason)}")
    end
  end
end

MyScript.run()
```

### Using Prompt Files Programmatically

```elixir
# examples/cli_run_prompt_file.exs
defmodule MyScript do
  alias Tinkex.CLI

  def run do
    {:ok, _} = Application.ensure_all_started(:tinkex)

    # Create temporary prompt file
    tmp_dir = System.tmp_dir!()
    prompt_path = Path.join(tmp_dir, "prompt.txt")
    output_path = Path.join(tmp_dir, "output.json")

    File.write!(prompt_path, "Hello from a prompt file")

    args = [
      "run",
      "--base-model", "meta-llama/Llama-3.1-8B",
      "--prompt-file", prompt_path,
      "--json",
      "--output", output_path,
      "--api-key", System.fetch_env!("TINKER_API_KEY")
    ]

    case CLI.run(args) do
      {:ok, _} ->
        IO.puts("JSON output written to #{output_path}")
        IO.puts(File.read!(output_path))

      {:error, reason} ->
        IO.puts(:stderr, "CLI failed: #{inspect(reason)}")
    end
  end
end

MyScript.run()
```

### Return Values

`Tinkex.CLI.run/1` returns:

- `{:ok, result}` - Success, where `result` is a map with command-specific data
- `{:error, reason}` - Failure, with error details

The `result` map structure varies by command:

**Checkpoint save:**
```elixir
{:ok, %{
  command: :checkpoint,
  metadata: %{
    "base_model" => "meta-llama/Llama-3.1-8B",
    "model_id" => "run-123/weights/0001",
    "saved_at" => "2024-11-26T12:34:56Z",
    ...
  }
}}
```

**Run (sampling):**
```elixir
{:ok, %{
  command: :run,
  response: %Tinkex.Types.SampleResponse{
    sequences: [...],
    ...
  }
}}
```

**Version:**
```elixir
{:ok, %{
  command: :version,
  version: "0.1.8",
  commit: "abc1234",
  options: %{json: false}
}}
```

## Complete Examples

### Example 1: Save Checkpoint and Generate Text

```bash
#!/bin/bash
set -e

API_KEY="$TINKER_API_KEY"
MODEL="meta-llama/Llama-3.1-8B"

# Save checkpoint
echo "Saving checkpoint..."
./tinkex checkpoint \
  --base-model "$MODEL" \
  --rank 32 \
  --output checkpoint.json \
  --api-key "$API_KEY"

# Generate text
echo "Generating text..."
./tinkex run \
  --base-model "$MODEL" \
  --prompt "The meaning of life is" \
  --max-tokens 100 \
  --temperature 0.8 \
  --output generation.txt \
  --api-key "$API_KEY"

echo "Done! Check checkpoint.json and generation.txt"
```

### Example 2: Batch Text Generation with JSON Output

```bash
#!/bin/bash
API_KEY="$TINKER_API_KEY"
MODEL="meta-llama/Llama-3.1-8B"

# Create prompts directory
mkdir -p prompts outputs

# Create multiple prompt files
echo "Write a haiku about code" > prompts/haiku.txt
echo "Explain recursion simply" > prompts/recursion.txt
echo "List 5 programming languages" > prompts/languages.txt

# Process each prompt
for prompt_file in prompts/*.txt; do
  base=$(basename "$prompt_file" .txt)
  echo "Processing: $base"

  ./tinkex run \
    --base-model "$MODEL" \
    --prompt-file "$prompt_file" \
    --max-tokens 100 \
    --temperature 0.7 \
    --json \
    --output "outputs/${base}.json" \
    --api-key "$API_KEY"
done

echo "All prompts processed! Results in outputs/"
```

### Example 3: Checkpoint Management Workflow

```bash
#!/bin/bash
API_KEY="$TINKER_API_KEY"

# List all checkpoints
echo "=== Your Checkpoints ==="
./tinkex checkpoint list --limit 20 --api-key "$API_KEY"

# Get info on specific checkpoint
CHECKPOINT_PATH="tinker://run-123/weights/0001"
echo ""
echo "=== Checkpoint Info ==="
./tinkex checkpoint info "$CHECKPOINT_PATH" --api-key "$API_KEY"

# Download checkpoint
echo ""
echo "=== Downloading Checkpoint ==="
./tinkex checkpoint download "$CHECKPOINT_PATH" \
  --output ./models/checkpoint-001 \
  --force \
  --api-key "$API_KEY"

echo ""
echo "Checkpoint saved to ./models/checkpoint-001"
```

### Example 4: Using Token IDs for Precise Control

```bash
#!/bin/bash
API_KEY="$TINKER_API_KEY"
MODEL="meta-llama/Llama-3.1-8B"

# Create a JSON file with specific token IDs
# (These would be actual token IDs from your tokenizer)
cat > tokens.json <<EOF
{
  "tokens": [1, 450, 3783, 315, 2324, 374]
}
EOF

# Generate text from token IDs
./tinkex run \
  --base-model "$MODEL" \
  --prompt-file tokens.json \
  --max-tokens 50 \
  --temperature 0.7 \
  --json \
  --output output.json \
  --api-key "$API_KEY"

echo "Generated text from token IDs:"
cat output.json | jq .
```

### Example 5: Environment-Based Configuration

```bash
#!/bin/bash
# Set environment variables for cleaner command lines
export TINKER_API_KEY="tml-your-api-key"
export TINKER_BASE_URL="https://tinker.thinkingmachines.dev/services/tinker-prod"

# Now you can omit --api-key and --base-url
./tinkex run \
  --base-model meta-llama/Llama-3.1-8B \
  --prompt "Hello world" \
  --max-tokens 20

# Or use them programmatically
./tinkex checkpoint \
  --base-model Qwen/Qwen3-8B \
  --output checkpoint.json
```

## Error Handling

The CLI provides clear error messages for common issues:

**Missing API Key:**
```
Checkpoint failed. Please check your inputs: Missing --api-key
```

**Missing Required Options:**
```
Checkpoint failed. Please check your inputs: --output is required for checkpoint command
```

**Invalid Options:**
```
Invalid option(s) for {:checkpoint, :save}: --invalid-flag
```

**Server Errors:**
```
Sampling failed due to server or transient error. Consider retrying: API request failed
```

**Timeout:**
```
Sampling failed due to server or transient error. Consider retrying: Timed out while awaiting sampling
```

## Exit Codes

The CLI uses standard exit codes:

- `0` - Success
- `1` - Error (validation, server error, or timeout)

This allows for shell scripting:

```bash
#!/bin/bash
if ./tinkex run --prompt "Test" --base-model meta-llama/Llama-3.1-8B; then
  echo "Success!"
else
  echo "Failed with exit code: $?"
  exit 1
fi
```

## Performance Tips

1. **Connection Pooling**: The CLI automatically uses HTTP/2 connection pools. For batch operations, consider using the SDK directly with `ServiceClient` to reuse connections.

2. **Timeouts**: Adjust `--timeout` for large generation requests:
   ```bash
   ./tinkex run --timeout 300000 --max-tokens 2000 ...
   ```

3. **Parallel Processing**: For multiple independent requests, use shell parallelization:
   ```bash
   # Generate 4 samples in parallel
   for i in {1..4}; do
     ./tinkex run --prompt "Sample $i" ... &
   done
   wait
   ```

4. **Output Formats**: Use `--format json` (or `--json`) on checkpoint/run management commands when you need to parse output programmatically; `--json` remains available on sampling commands. Plain text is more efficient for human reading.

## Troubleshooting

### Command Not Found

If `tinkex` is not found after building:

```bash
# Use relative path
./tinkex version

# Or add to PATH
export PATH="$PATH:$PWD"
tinkex version

# Or install globally
mix escript.install ./tinkex
# Then ensure ~/.mix/escripts is in PATH
export PATH="$PATH:$HOME/.mix/escripts"
```

### SSL/TLS Errors

If you encounter certificate verification errors:

```bash
# Set base URL explicitly
./tinkex run \
  --base-url "https://tinker.thinkingmachines.dev/services/tinker-prod" \
  ...
```

### Large Prompts

For very large prompts, use `--prompt-file` instead of `--prompt`:

```bash
# This may fail if the prompt is too large for command line
./tinkex run --prompt "$(cat large_prompt.txt)" ...

# Use prompt file instead
./tinkex run --prompt-file large_prompt.txt ...
```

### JSON Parsing

When using `--json`, ensure you have `jq` or similar tools for parsing:

```bash
./tinkex run --json ... | jq '.sequences[0].tokens'
```

## See Also

- [Getting Started Guide](getting_started.md) - Installation and setup
- [API Reference](api_reference.md) - SDK API documentation
- [Troubleshooting Guide](troubleshooting.md) - Common issues and solutions
- [Training Loop Guide](training_loop.md) - End-to-end training workflows
- Examples Directory (`examples/`) - Runnable example scripts

## Help Commands

All commands support `--help` or `-h`:

```bash
./tinkex --help
./tinkex checkpoint --help
./tinkex checkpoint list --help
./tinkex run --help
./tinkex run list --help
./tinkex version --help
```