DuckVizBeta
Product

CLI

Scaffold an embedded DuckViz app, or push local files into your browser session from a terminal.

The duckviz CLI is a small Node binary published on npm. Its primary job is scaffolding an embedded DuckViz app for developers; a secondary subcommand pushes local files into the live app for analysts.

  1. create-app — scaffold a Next.js starter wired to the DuckViz packages (Explorer, Dashboard, Report, Deck) with the SDK proxy already set up. Added in 0.3.0. Start here if you're embedding DuckViz.
  2. <paths...> — push local files into the browser session you're signed into, without an internet round-trip. Bytes move browser ↔ localhost, never through any server. Useful for analyst workflows on the hosted app.

Current version: 0.3.2. See the npm changelog for the full release history.

Upgrade to 0.3.2 if you scaffolded earlier

Scaffolds from 0.3.0 and 0.3.1 shipped a working layout but the AI calls (/api/widget-flow/*, /api/generate-report-sections, etc.) hit your own origin and 404'd — the SDK proxy was wired but unreachable. 0.3.2 adds lib/custom-fetch.ts and threads customFetch through Explorer / Report / Deck so the proxy is actually called. Re-scaffold or backport the change.

Install

Zero-install via npx:

npx duckviz create-app my-app   # scaffold a starter
npx duckviz ./data.csv          # ingest a file

Or global:

npm install -g duckviz
duckviz --help

Scaffold a starter app

npx duckviz create-app my-app
cd my-app
cp .env.local.example .env.local      # paste your DUCKVIZ_TOKEN
npm run dev

Open http://localhost:3000/explorer — Explorer renders against a demo dataset, the AI widget panel populates, and you can navigate to /dashboard, /report, or /deck to see the same data on each surface.

What the nextjs template ships

File / routeWhat it does
app/dashboard, app/explorer, app/report, app/deckOne route per surface. All four share the same demo dataset.
app/api/users/route.tsDemo endpoint returning fake user rows. Replace with your real data source.
app/api/duckviz/[...route]/route.tsSDK proxy. Reads DUCKVIZ_TOKEN from .env.local and forwards Explorer / Report / Deck AI calls upstream with the bearer attached server-side. The token never reaches the browser.
lib/custom-fetch.tsRewrites /api/*/api/duckviz/* so the components hit the proxy instead of 404'ing on your own origin. Threaded into <Explorer>, <ReportBuilder>, <DeckBuilder> via their customFetch prop.
lib/use-users.ts + lib/demo-dashboard.tsOne-shot ingest into a t_users DuckDB table + a 4-widget demo dashboard (KPI, KPI, bar, line) shared by every surface.

Stack: Next.js 15 + App Router + Tailwind v4 + React 19.

Options

npx duckviz create-app my-app --template nextjs   # only template today
npx duckviz create-app my-app --pm bun            # force pkg manager (npm|pnpm|yarn|bun)
npx duckviz create-app my-app --no-install        # skip post-scaffold install
npx duckviz create-app my-app --no-git            # skip `git init`
npx duckviz create-app my-app --force             # overwrite non-empty dir

The package manager is auto-detected from npm_config_user_agent (whichever pm you ran npx/pnpm dlx/bunx with). --pm overrides the detection.

After scaffolding

DUCKVIZ_TOKEN=dvz_live_your_token_here

…in .env.local. Mint a token at app.duckviz.com/settings/tokens. Then run dev as you normally would.

See the Next.js Integration Guide for the same wiring explained step-by-step — useful when you want to bolt DuckViz onto an existing Next.js app instead of scaffolding fresh.

Push files to the live app

duckviz <paths...> [options]

Options:
  -r, --recursive         Walk directories recursively
      --fresh             Replace the current session (instead of appending)
      --url <url>         Custom web app URL (default: https://app.duckviz.com)
  -p, --port <port>       Pin the local server port (default: random)
      --no-open           Print the URL; skip opening the default browser
      --ignore <pattern>  Glob to skip files/folders (repeatable)
  -h, --help              Show help

As of 0.2.0, the default target is app.duckviz.com (moved from duckviz.com when the product split into three domains). --port, --no-open, and --ignore were also added in 0.2.0.

File allowlist

The CLI only reads extensions the web app knows how to ingest:

.csv  .tsv  .json  .jsonl  .ndjson
.xlsx  .xls
.xml
.log  .syslog  .clf  .evtx
.txt                    (since 0.2.0 — parser catalog matches many .txt formats)
.parquet                (since 0.2.0 — zero-copy ingest)

Anything else is silently skipped when walking directories.

Examples

# Single CSV
npx duckviz data.csv

# Walk a folder, drop the current session first
npx duckviz ./logs -r --fresh

# Multiple files
npx duckviz sales.csv inventory.xlsx users.json

# Skip node_modules and build output
npx duckviz . -r --ignore "node_modules/**" --ignore "dist/**"

# Pin the port (useful behind a reverse proxy or local firewall rule)
npx duckviz data.csv --port 7777

# Headless: print the URL, don't open a browser
npx duckviz data.csv --no-open

# Self-hosted / local dev
npx duckviz . --url http://localhost:3001

How it works

Build a manifest

The CLI walks your input paths, applies the allowlist + --ignore globs, and builds a manifest (path, size, content hash).

Start an ephemeral localhost server

An HTTP server binds to 127.0.0.1 on a random (or --port) port. A one-shot bearer token is generated. The server enforces a DNS-rebinding guard: the Host header must match the bound loopback port — malicious pages can't hit your CLI server even if they guess the port.

Open the browser

The default browser opens app.duckviz.com/cli-receive?endpoint=...&token=.... The receive page immediately scrubs the token from the URL bar, so it never lands in history or a Referer header.

Use --no-open to get the URL and open it manually (helpful for remote SSH / containers).

Transfer

The tab authenticates to your local server with the bearer token, downloads each file from the manifest, and pipes it into the ingestion worker. Path traversal is prevented: only manifest-registered relative paths are served.

Done

The CLI exits once every file is confirmed. If you don't open the browser within 10 minutes, the server shuts itself down.

No internet upload

The transfer is browser ↔ localhost. Nothing leaves your machine. The app.duckviz.com tab is only the UI shell — it pulls files from http://127.0.0.1:<port>.

Security

GuardPrevents
Bearer-only authToken never leaves a header — no ?token= query fallback, no logs/Referer leaks
DNS-rebinding guardHost must be 127.0.0.1:<port> — blocks websites spoofing the CLI port
Path traversal guardOnly manifest-registered paths are served — no .. escapes
Manifest-gated --freshA crafted ?mode=fresh link can't wipe your session until the CLI token is accepted
Idle timeoutServer auto-shuts after 10 minutes

Troubleshooting

Browser doesn't open

The CLI prints the URL. Paste it into the browser where you're signed in to DuckViz — the transfer picks up from there.

"Connection refused"

The server binds to 127.0.0.1, not 0.0.0.0. A firewall or VPN intercepting loopback traffic is the usual cause. Also check for another process already holding --port.

Server times out

10-minute idle budget expired — re-run the command.

.parquet files show up as binary

Needs CLI 0.2.0+. Older CLI versions skipped .parquet because the allowlist didn't include it.