Getting Started with Quilltap

There are, as with most things worth doing, several paths to the same destination. We have arranged them in order of increasing difficulty, rather like a cocktail menu that begins with champagne and ends with absinthe.

A Frank Word About Your Options

We would very much like to tell you that Quilltap installs with a single click and a wisp of lavender-scented magic. We nearly can. The desktop application now ships with its own Node.js runtime, so in its simplest form you really do just download it, open it, and go. The other paths require you to install something first, but once the machinery is assembled, the setup wizard handles the rest. The nouveau riche will understand… you can't buy the house of your dreams, you have to wait until a team of contractors builds it for you.

The choice between them comes down to two questions: what are you willing to install? and how much do you trust the AI running on your machine? That second question deserves a moment of your attention.

On the Matter of Sandboxes

As AI models grow more capable — reading files, writing code, using tools — the question of where that code executes becomes rather important. A virtual machine is a genuine locked room: if an AI-generated script misbehaves, it misbehaves inside a contained environment with no access to your host system beyond what you've explicitly shared. Docker provides a similar boundary, though somewhat thinner. Running directly on your machine provides no boundary at all — the AI's code is your code, with your permissions, in your house, wearing your slippers, drinking your chamomile tea, and potentially leaving the back door open when it goes out to play.

For most people having a pleasant conversation with a fictional character, this distinction is academic — you likely will not let them do much more than send you a photograph or search the web or read your story. For anyone exploring agentic features — tool use, code execution, file manipulation — it is emphatically not. Choose accordingly.

The Civilized Way Desktop App — Direct The Fortified Way Desktop App — VM The Dockworker's Route Docker The Shortcut Node.js / npx
You must install Nothing extra — Electron bundles Node.js macOS: Xcode CLI Tools Windows: WSL2 Linux: Docker Engine Docker Desktop (Win/Mac) or Docker Engine (Linux) Node.js 22+
First launch Fastest — uses Electron's embedded Node.js, runs directly Slowest — downloads a VM image (~150 MB), boots a Linux guest Fast — pulls the container image, starts in seconds Fast — downloads app files, runs directly
Subsequent launches Near-instant A few seconds for the VM to wake Near-instant Near-instant
AI sandbox ✗ No isolation Runs with your permissions ✓ Full VM isolation AI code runs in a locked room ⚠ Container isolation Good boundary, not airtight ✗ No isolation Runs with your permissions
Native window Yes — Electron desktop app Yes — Electron desktop app Optional — Electron or browser Browser only
Multiple data dirs Yes — managed from splash screen Yes — managed from splash screen Yes — mount different volumes Yes — --data-dir flag
Best for Most users — fastest start, zero prerequisites Safety-conscious users who want a genuine sandbox Server deployments, Docker veterans, and Linux users Quick evaluation, developers, and the impatient

Our recommendation for most people is the desktop application in Direct mode — it's the fastest path from download to conversation, with zero prerequisites. The Electron app ships with its own Node.js runtime, so you don't need to install anything else. Just download, open, and go.

If you use AI tools that read and write files on your behalf and want a genuine sandbox around that behavior, switch to VM mode from the splash screen. The Electron app lets you toggle between Direct, VM, and Docker runtimes at any time — no commitment required.

One More Warning

You may wonder what harm an AI could do on your machine. It's not just the AI, although that is something. If you enable full tool use, it can run scripts on your machine. Maybe install its own software. Maybe plant something that phones home as soon as you put your credit card number into a site in a browser window. Additionally, Quilltap has a plugin system, and anybody can write a plugin. If you install a plugin, how do you know that the plugin isn't running something on your computer that you don't want it to run?

I have often assured people that told me they trusted me… they shouldn't. I am a stranger on the internet, and I have no business running code on your machine, at least not code that you didn't purposefully sign up for by clicking "Install" or dragging that file into your Applications folder.

If you already have Docker and would rather not wait for a VM to boot, the Dockworker's Route is a perfectly civilized alternative — and if you use the Electron app to start it, it even lets you switch between the runtimes from its splash screen, so you needn't commit to any one on day one.

The npx shortcut is exactly what it sounds like: a quick way in for developers who already have Node.js installed. Excellent for kicking the tires. Less excellent for leaving the AI unsupervised with your filesystem. If you start here and decide to stay, we'd gently suggest graduating to one of the sandboxed options when the novelty of speed wears off and the reality of agentic AI settles in.

The Civilized Way (Recommended)

The simplest and most delightful way to run Quilltap is to install the desktop application. In its default Direct mode, it bundles everything you need — including its own Node.js runtime — so there is nothing else to install. For those who want a sandbox, the app can also run its backend inside a lightweight Linux virtual machine or a Docker container, switchable from the splash screen at any time.

Your platform:
1

Download

Visit the GitHub Releases page and download the latest stable release for your platform:

macOS

Download the .dmg installer. Open it and drag Quilltap to your Applications folder.

In Direct mode (the default), you need nothing else — just launch and go. To use VM mode, Quilltap uses Lima with Apple's Virtualization.framework, which requires Xcode Command Line Tools — the app will offer to install them if they're missing.

Windows

Download and run the .exe installer. Follow the prompts.

In Direct mode (the default), you need nothing else — just launch and go. To use VM mode, Quilltap uses WSL2, which is built into Windows 10 and 11. If WSL2 isn't already enabled, run wsl --install in PowerShell as Administrator and restart your computer. The app checks for this on startup and will tell you plainly if something is amiss.

Linux

Download the .AppImage file, make it executable (chmod +x), and run it. Or install the .deb package: sudo dpkg -i quilltap_*.deb

In Direct mode, the app runs using its own embedded Node.js runtime — no prerequisites. The Linux desktop app can also use Docker Engine as its runtime backend, because Linux was already the sandbox and always knew it.

2

Launch

On first run, Quilltap will:

  1. Present a splash screen where you choose your data directory and runtime mode
  2. Start the backend (instant in Direct mode; a short wait for VM or Docker)
  3. Open your workspace in a native window

That's it. No configuration files, no environment variables, no incantations.

Runtime Modes

The splash screen lets you switch between three runtime modes at any time:

Direct — Uses Electron's embedded Node.js. Zero prerequisites, fastest startup. Runs with your user permissions (no sandbox). This is the default.

VM — macOS: Lima with Apple's Virtualization.framework (requires Xcode Command Line Tools). Windows: WSL2. Full sandbox isolation — AI code runs in a locked room.

Docker — Requires Docker Desktop or Docker Engine. Container-level isolation — good boundary, not airtight.

Tip: The desktop app lets you manage multiple data directories from its splash screen — one for work, one for fiction, one for experiments. Each remembers its own runtime mode and window position.

The Dockworker's Route (Docker)

If you prefer containers — or you're running a Linux server, or you simply enjoy the gentle hum of virtualization — Docker is a fine choice.

What You'll Need

Docker Desktop (Windows, macOS, or Linux) — or Docker Engine on Linux. That's it.

With the Desktop App

The Electron desktop app includes a Docker runtime toggle right on the splash screen. Install Docker Desktop, launch Quilltap, and switch the runtime from “Direct” to “Docker.” Same native window, different engine underneath.

Standalone (Browser)

Skip the Electron wrapper entirely and access Quilltap through your browser:

Terminal
docker run -d \
  --name quilltap \
  -p 3000:3000 \
  -e QUILLTAP_TIMEZONE=America/New_York \
  -v /path/to/your/data:/app/quilltap \
  foundry9/quilltap

Open http://localhost:3000 and the setup wizard will guide you through first-time configuration.

Timezone tip: Set QUILLTAP_TIMEZONE to your IANA timezone (e.g., America/New_York, Europe/London, Asia/Tokyo) so timestamp injection in chats shows your local time instead of UTC. The desktop app detects this automatically.

Using the Startup Scripts

For the smoothest Docker experience, use the included startup scripts. They auto-detect your platform, set the correct data directory, find Ollama if it's running, and handle port forwarding automatically.

PowerShell
irm https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.ps1 | iex
View the full script
<#
.SYNOPSIS
    Quilltap Docker startup script for Windows.

.DESCRIPTION
    Detects platform, sets sensible defaults, and starts the Quilltap container.

.PARAMETER DataDir
    Data directory on host. Default: $env:APPDATA\Quilltap

.PARAMETER Port
    Host port. Default: 3000

.PARAMETER Name
    Container name. Default: quilltap

.PARAMETER Tag
    Image tag. Default: latest

.PARAMETER RedirectPorts
    Comma-separated ports to forward to host (e.g., "11434,3030")

.PARAMETER ExtraEnv
    Extra environment variables as an array of "KEY=VALUE" strings

.PARAMETER RestartPolicy
    Docker restart policy. Default: unless-stopped

.PARAMETER NoAutoDetect
    Skip auto-detection of local services (Ollama, etc.)

.PARAMETER DryRun
    Print the docker command without running it

.EXAMPLE
    .\scripts\start-quilltap.ps1

.EXAMPLE
    .\scripts\start-quilltap.ps1 -RedirectPorts "11434,3030"

.EXAMPLE
    .\scripts\start-quilltap.ps1 -DataDir "D:\quilltap-data" -Port 8080
#>

param(
    [string]$DataDir,
    [int]$Port = 3000,
    [string]$Name = "quilltap",
    [string]$Tag = "latest",
    [string]$RedirectPorts,
    [string[]]$ExtraEnv = @(),
    [string]$RestartPolicy = "unless-stopped",
    [switch]$NoAutoDetect,
    [switch]$DryRun
)

$Image = "foundry9/quilltap"

# Detect platform and set default data directory
if (-not $DataDir) {
    if ($env:QUILLTAP_DATA_DIR) {
        $DataDir = $env:QUILLTAP_DATA_DIR
    } elseif ($IsLinux) {
        $DataDir = Join-Path $HOME ".quilltap"
    } elseif ($IsMacOS) {
        $DataDir = Join-Path $HOME "Library/Application Support/Quilltap"
    } else {
        # Windows
        $DataDir = Join-Path $env:APPDATA "Quilltap"
    }
}

# Override from environment variables
if ($env:QUILLTAP_PORT -and $Port -eq 3000) { $Port = [int]$env:QUILLTAP_PORT }
if ($env:QUILLTAP_CONTAINER_NAME -and $Name -eq "quilltap") { $Name = $env:QUILLTAP_CONTAINER_NAME }
if ($env:QUILLTAP_IMAGE_TAG -and $Tag -eq "latest") { $Tag = $env:QUILLTAP_IMAGE_TAG }
if ($env:HOST_REDIRECT_PORTS -and -not $RedirectPorts) { $RedirectPorts = $env:HOST_REDIRECT_PORTS }

# Auto-detect local services
if (-not $NoAutoDetect) {
    $DetectedPorts = @()

    # Check for Ollama on port 11434
    try {
        $tcp = New-Object System.Net.Sockets.TcpClient
        $tcp.Connect("localhost", 11434)
        $tcp.Close()
        Write-Host "Detected Ollama on port 11434"
        $DetectedPorts += "11434"
    } catch {
        # Not running
    }

    # Merge detected ports with any explicitly specified
    if ($DetectedPorts.Count -gt 0) {
        $DetectedCsv = $DetectedPorts -join ","
        if ($RedirectPorts) {
            $RedirectPorts = "$RedirectPorts,$DetectedCsv"
        } else {
            $RedirectPorts = $DetectedCsv
        }
        # Deduplicate
        $RedirectPorts = (($RedirectPorts -split ",") | Sort-Object -Unique) -join ","
    }
}

# Create data directory if it doesn't exist
if (-not $DryRun) {
    if (-not (Test-Path $DataDir)) {
        New-Item -ItemType Directory -Path $DataDir -Force | Out-Null
    }
}

# Build docker run arguments
$DockerArgs = @(
    "run", "-d",
    "--name", $Name,
    "--restart", $RestartPolicy,
    "-p", "${Port}:3000",
    "-v", "${DataDir}:/app/quilltap"
)

# Pass the host-side data directory so the app can display it in the UI
$DockerArgs += @("-e", "QUILLTAP_HOST_DATA_DIR=$DataDir")

# Add host port forwarding if requested
if ($RedirectPorts) {
    $DockerArgs += @("-e", "HOST_REDIRECT_PORTS=$RedirectPorts")
    # Linux needs explicit host.docker.internal mapping
    if ($IsLinux) {
        $DockerArgs += @("--add-host=host.docker.internal:host-gateway")
    }
}

# Add extra environment variables
foreach ($env_var in $ExtraEnv) {
    $DockerArgs += @("-e", $env_var)
}

# Image
$DockerArgs += "${Image}:${Tag}"

# Display configuration
$Platform = if ($IsLinux) { "linux" } elseif ($IsMacOS) { "macos" } else { "windows" }
Write-Host "Platform:  $Platform"
Write-Host "Data dir:  $DataDir"
Write-Host "Port:      $Port"
Write-Host "Container: $Name"
Write-Host "Image:     ${Image}:${Tag}"
if ($RedirectPorts) {
    Write-Host "Forwarding: $RedirectPorts"
}
Write-Host ""

if ($DryRun) {
    Write-Host "Dry run - would execute:"
    Write-Host "  docker $($DockerArgs -join ' ')"
    return
}

# Check if container already exists
$existing = docker ps -a --format '{{.Names}}' 2>$null | Where-Object { $_ -eq $Name }
if ($existing) {
    $running = docker ps --format '{{.Names}}' 2>$null | Where-Object { $_ -eq $Name }
    if ($running) {
        Write-Host "Container '$Name' is already running."
        Write-Host "Use 'docker stop $Name; docker rm $Name' to recreate."
    } else {
        Write-Host "Container '$Name' exists but is stopped. Starting it..."
        docker start $Name
    }
    return
}

Write-Host "Starting Quilltap..."
& docker @DockerArgs

Write-Host ""
Write-Host "Quilltap is running at http://localhost:${Port}"
Terminal
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.sh | bash
View the full script
#!/usr/bin/env bash
set -euo pipefail

# Quilltap Docker startup script
# Detects platform, sets sensible defaults, and starts the container.
#
# Usage:
#   ./scripts/start-quilltap.sh [options]
#
# Options:
#   -d, --data-dir DIR          Data directory on host (default: platform-specific)
#   -p, --port PORT             Host port (default: 3000)
#   -n, --name NAME             Container name (default: quilltap)
#   -t, --tag TAG               Image tag (default: latest)
#   -e, --env KEY=VALUE         Extra environment variable (repeatable)
#   --restart POLICY            Restart policy (default: unless-stopped)
#   --dry-run                   Print the docker command without running it
#   -h, --help                  Show this help message
#
# Environment variables (override defaults):
#   QUILLTAP_DATA_DIR           Data directory
#   QUILLTAP_PORT               Host port
#   QUILLTAP_CONTAINER_NAME     Container name
#   QUILLTAP_IMAGE_TAG          Image tag

IMAGE="foundry9/quilltap"

# Detect platform and set default data directory
detect_defaults() {
  case "$(uname -s)" in
    Darwin)
      PLATFORM="macos"
      DEFAULT_DATA_DIR="$HOME/Library/Application Support/Quilltap"
      ;;
    Linux)
      PLATFORM="linux"
      DEFAULT_DATA_DIR="$HOME/.quilltap"
      ;;
    MINGW*|MSYS*|CYGWIN*)
      PLATFORM="windows"
      DEFAULT_DATA_DIR="${APPDATA:-$HOME/AppData/Roaming}/Quilltap"
      ;;
    *)
      PLATFORM="linux"
      DEFAULT_DATA_DIR="$HOME/.quilltap"
      ;;
  esac
}

detect_defaults

# Defaults (env vars override platform defaults)
DATA_DIR="${QUILLTAP_DATA_DIR:-$DEFAULT_DATA_DIR}"
PORT="${QUILLTAP_PORT:-3000}"
CONTAINER_NAME="${QUILLTAP_CONTAINER_NAME:-quilltap}"
IMAGE_TAG="${QUILLTAP_IMAGE_TAG:-latest}"
RESTART_POLICY="unless-stopped"
DRY_RUN=false
EXTRA_ENVS=()

# Parse arguments
while [[ $# -gt 0 ]]; do
  case "$1" in
    -d|--data-dir)
      DATA_DIR="$2"; shift 2 ;;
    -p|--port)
      PORT="$2"; shift 2 ;;
    -n|--name)
      CONTAINER_NAME="$2"; shift 2 ;;
    -t|--tag)
      IMAGE_TAG="$2"; shift 2 ;;
    -e|--env)
      EXTRA_ENVS+=("$2"); shift 2 ;;
    --restart)
      RESTART_POLICY="$2"; shift 2 ;;
    --dry-run)
      DRY_RUN=true; shift ;;
    -h|--help)
      sed -n '2,/^$/{ s/^# \?//; p }' "$0"
      exit 0 ;;
    *)
      echo "Unknown option: $1" >&2
      echo "Run with --help for usage." >&2
      exit 1 ;;
  esac
done

# Create data directory if it doesn't exist
if [ "$DRY_RUN" = false ]; then
  mkdir -p "$DATA_DIR"
fi

# Build docker run command
CMD=(docker run -d
  --name "$CONTAINER_NAME"
  --restart "$RESTART_POLICY"
  -p "${PORT}:3000"
  -v "$DATA_DIR:/app/quilltap"
)

# Pass the host-side data directory so the app can display it in the UI
CMD+=(-e "QUILLTAP_HOST_DATA_DIR=$DATA_DIR")

# Linux needs explicit host.docker.internal mapping for localhost URL rewriting
if [ "$PLATFORM" = "linux" ]; then
  CMD+=(--add-host=host.docker.internal:host-gateway)
fi

# Add extra environment variables
if [ ${#EXTRA_ENVS[@]} -gt 0 ]; then
  for env in "${EXTRA_ENVS[@]}"; do
    CMD+=(-e "$env")
  done
fi

# Image
CMD+=("${IMAGE}:${IMAGE_TAG}")

# Run or print
echo "Platform:  $PLATFORM"
echo "Data dir:  $DATA_DIR"
echo "Port:      $PORT"
echo "Container: $CONTAINER_NAME"
echo "Image:     ${IMAGE}:${IMAGE_TAG}"
echo ""

if [ "$DRY_RUN" = true ]; then
  echo "Dry run — would execute:"
  echo "  ${CMD[*]}"
else
  # Check if container already exists
  if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER_NAME"; then
    echo "Container '$CONTAINER_NAME' already exists."
    if docker ps --format '{{.Names}}' | grep -qx "$CONTAINER_NAME"; then
      echo "It's already running. Use 'docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME' to recreate."
    else
      echo "Starting existing container..."
      docker start "$CONTAINER_NAME"
    fi
    exit 0
  fi

  echo "Starting Quilltap..."
  "${CMD[@]}"
  echo ""
  echo "Quilltap is running at http://localhost:${PORT}"
fi
Terminal
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.sh | bash
View the full script
#!/usr/bin/env bash
set -euo pipefail

# Quilltap Docker startup script
# Detects platform, sets sensible defaults, and starts the container.
#
# Usage:
#   ./scripts/start-quilltap.sh [options]
#
# Options:
#   -d, --data-dir DIR          Data directory on host (default: platform-specific)
#   -p, --port PORT             Host port (default: 3000)
#   -n, --name NAME             Container name (default: quilltap)
#   -t, --tag TAG               Image tag (default: latest)
#   -e, --env KEY=VALUE         Extra environment variable (repeatable)
#   --restart POLICY            Restart policy (default: unless-stopped)
#   --dry-run                   Print the docker command without running it
#   -h, --help                  Show this help message
#
# Environment variables (override defaults):
#   QUILLTAP_DATA_DIR           Data directory
#   QUILLTAP_PORT               Host port
#   QUILLTAP_CONTAINER_NAME     Container name
#   QUILLTAP_IMAGE_TAG          Image tag

IMAGE="foundry9/quilltap"

# Detect platform and set default data directory
detect_defaults() {
  case "$(uname -s)" in
    Darwin)
      PLATFORM="macos"
      DEFAULT_DATA_DIR="$HOME/Library/Application Support/Quilltap"
      ;;
    Linux)
      PLATFORM="linux"
      DEFAULT_DATA_DIR="$HOME/.quilltap"
      ;;
    MINGW*|MSYS*|CYGWIN*)
      PLATFORM="windows"
      DEFAULT_DATA_DIR="${APPDATA:-$HOME/AppData/Roaming}/Quilltap"
      ;;
    *)
      PLATFORM="linux"
      DEFAULT_DATA_DIR="$HOME/.quilltap"
      ;;
  esac
}

detect_defaults

# Defaults (env vars override platform defaults)
DATA_DIR="${QUILLTAP_DATA_DIR:-$DEFAULT_DATA_DIR}"
PORT="${QUILLTAP_PORT:-3000}"
CONTAINER_NAME="${QUILLTAP_CONTAINER_NAME:-quilltap}"
IMAGE_TAG="${QUILLTAP_IMAGE_TAG:-latest}"
RESTART_POLICY="unless-stopped"
DRY_RUN=false
EXTRA_ENVS=()

# Parse arguments
while [[ $# -gt 0 ]]; do
  case "$1" in
    -d|--data-dir)
      DATA_DIR="$2"; shift 2 ;;
    -p|--port)
      PORT="$2"; shift 2 ;;
    -n|--name)
      CONTAINER_NAME="$2"; shift 2 ;;
    -t|--tag)
      IMAGE_TAG="$2"; shift 2 ;;
    -e|--env)
      EXTRA_ENVS+=("$2"); shift 2 ;;
    --restart)
      RESTART_POLICY="$2"; shift 2 ;;
    --dry-run)
      DRY_RUN=true; shift ;;
    -h|--help)
      sed -n '2,/^$/{ s/^# \?//; p }' "$0"
      exit 0 ;;
    *)
      echo "Unknown option: $1" >&2
      echo "Run with --help for usage." >&2
      exit 1 ;;
  esac
done

# Create data directory if it doesn't exist
if [ "$DRY_RUN" = false ]; then
  mkdir -p "$DATA_DIR"
fi

# Build docker run command
CMD=(docker run -d
  --name "$CONTAINER_NAME"
  --restart "$RESTART_POLICY"
  -p "${PORT}:3000"
  -v "$DATA_DIR:/app/quilltap"
)

# Pass the host-side data directory so the app can display it in the UI
CMD+=(-e "QUILLTAP_HOST_DATA_DIR=$DATA_DIR")

# Linux needs explicit host.docker.internal mapping for localhost URL rewriting
if [ "$PLATFORM" = "linux" ]; then
  CMD+=(--add-host=host.docker.internal:host-gateway)
fi

# Add extra environment variables
if [ ${#EXTRA_ENVS[@]} -gt 0 ]; then
  for env in "${EXTRA_ENVS[@]}"; do
    CMD+=(-e "$env")
  done
fi

# Image
CMD+=("${IMAGE}:${IMAGE_TAG}")

# Run or print
echo "Platform:  $PLATFORM"
echo "Data dir:  $DATA_DIR"
echo "Port:      $PORT"
echo "Container: $CONTAINER_NAME"
echo "Image:     ${IMAGE}:${IMAGE_TAG}"
echo ""

if [ "$DRY_RUN" = true ]; then
  echo "Dry run — would execute:"
  echo "  ${CMD[*]}"
else
  # Check if container already exists
  if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER_NAME"; then
    echo "Container '$CONTAINER_NAME' already exists."
    if docker ps --format '{{.Names}}' | grep -qx "$CONTAINER_NAME"; then
      echo "It's already running. Use 'docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME' to recreate."
    else
      echo "Starting existing container..."
      docker start "$CONTAINER_NAME"
    fi
    exit 0
  fi

  echo "Starting Quilltap..."
  "${CMD[@]}"
  echo ""
  echo "Quilltap is running at http://localhost:${PORT}"
fi

The Shortcut (Node.js)

If you have Node.js installed and want to skip installers entirely, one command does the trick. The CLI downloads the application files on first run (~150–250 MB) and caches them locally. Subsequent launches start instantly.

Note: If you don't already have Node.js, you likely don't need this path. The desktop app now bundles its own Node.js runtime, so The Civilized Way above is faster, easier, and requires no prerequisites at all.

Terminal
npx quilltap

Or install it globally:

Terminal
npm install -g quilltap
quilltap

Supports --port and --data-dir flags, plus --update to force a fresh download. Requires Node.js 22 or later.

A word of caution: The npx path runs with your user permissions and provides no sandbox. Excellent for kicking the tires. Less excellent for leaving the AI unsupervised with your filesystem. If you start here and decide to stay, consider graduating to one of the sandboxed options.

First-Run Setup

Whichever path you chose, Quilltap walks you through a short setup wizard on first launch. It generates your encryption key, which protects your entire database at rest using SQLCipher. You can optionally add a passphrase for extra security — if you do, you'll enter it each time Quilltap starts.

Tip: If you add a passphrase, don't lose it. The encryption key it protects is the one value you can't regenerate. Safest to keep both of them, someplace secure but accessible, like a password manager.

The wizard then walks you through provider configuration: provider selection, API key validation, model selection, optional embedding and image setup, and a final test-and-confirm step. One flow creates everything you need in a single pass.

Once setup is complete, you're in.

What's in Your Data Folder

Everything Quilltap needs lives in a single directory. The application tells you exactly where at the bottom of every page.

Platform Default Location
macOS (Electron) ~/Library/Application Support/Quilltap
Windows (Electron) %APPDATA%\Quilltap
Linux ~/.quilltap
Docker Wherever you mount /app/quilltap

Inside that directory:

Path Contents
data/quilltap.db Your encrypted SQLite database (characters, chats, settings)
files/ Uploaded images and attachments
logs/ Application logs
plugins/ Installed plugins

Your data stays on your machine. Quilltap never phones home.

Updating

Desktop App

Download the latest release from the Releases page and install it over the existing version. Your data directory is untouched — nothing is lost.

Docker

docker stop quilltap
docker rm quilltap
docker pull foundry9/quilltap:latest

Then re-run your docker run command or the startup script. Your data folder is untouched — nothing is lost.

npx / npm

npx quilltap --update

Or if installed globally: npm update -g quilltap

Quick Reference (Docker)

Task Command
Stop Quilltap docker stop quilltap
Start it again docker start quilltap
View logs docker logs quilltap
Follow logs live docker logs -f quilltap

Up and running? Splendid. Now let's get you properly acquainted.

Next Steps

Troubleshooting

Desktop app won't start (macOS)

In Direct mode, the app should start without prerequisites. If you're using VM mode, ensure Xcode Command Line Tools are installed — the app will prompt you if they're missing. Check Console.app for Lima-related errors. If all else fails, try deleting the VM; the app will recreate it on next launch.

Desktop app won't start (Windows)

In Direct mode, the app should start without prerequisites. If you're using VM mode, ensure WSL2 is installed: run wsl --install in PowerShell as Administrator. Check if the distro exists with wsl --list --verbose.

"Port 3000 is already in use"

For Docker, change the host port: replace -p 3000:3000 with -p 8080:3000, then open http://localhost:8080. The desktop app handles port conflicts automatically.

Container exits immediately

Check the logs with docker logs quilltap.

Permission errors on the data folder (Linux)

On Docker Desktop (Windows/macOS), permissions are handled automatically. On native Linux, the container runs as a non-root user that may not match your host UID. If you see permission errors, you can match the container's user to your own:

docker run ... --user "$(id -u):$(id -g)" foundry9/quilltap:latest

Connecting to Ollama or other local services

The desktop app and startup scripts auto-detect Ollama on its default port (11434) and handle port forwarding automatically. For Docker, if you need to reach host services on nonstandard ports, pass them with -r:

PowerShell
irm https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.ps1 | iex -RedirectPorts "11435"
Terminal
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.sh | bash -s -- -r 11435
Terminal
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.sh | bash -s -- -r 11435

Multiple ports are comma-separated: -r 3030,8080. The container bridges forwarded ports internally via socat, so localhost works from inside Docker — no host.docker.internal gymnastics required.