Skip to content

CLI — npx tiny-vercel (inventory of the source, tiny-tech)·

The template ships the same CLI the operator uses every day. Source of truth for this port: ~/tinyai-id/tiny-tech — a nested git checkout of github.com/cagataycali/tiny-tech at b8b9397 (2026-09-12), package tiny-tech@0.13.7. npm view tiny-tech version → 0.13.7: no drift between the checkout and the published package.

Target: apps/cli in this monorepo, package tiny-vercel, bin tiny-vercel (tiny-tech had one bin, tiny-tech; no tiny alias existed, so none is added — an alias the source never had would be a feature, not a port). Rungs C1–C6 are tracked in docs/PARITY.md § cli.

What it is·

One Node ≥ 18 ESM program (dist/cli.js) with four faces:

Face Entry What happens
MCP server tiny-vercel serve or spawned over stdio by any MCP client src/server.ts — 30+ tiny_* tools (memory, DMs, tinys, jobs, tools, wallet, devices) proxied to the web app's /api/* with the user's CLI JWT. stdout is the MCP transport; every human line goes to stderr.
TUI / REPL agent tiny-vercel (tty), tiny-vercel tui, tiny-vercel repl, tiny-vercel "one question" src/agent/* — a Strands SDK agent running LOCALLY with a BYO model (Bedrock / OpenAI / Anthropic / Ollama / Gemini) or, with no key, proxying every turn through the web app's /api/chat. Local tools: shell, files, http, computer control, Apple/Spotify/Google/WhatsApp/Telegram/Flipper/ADB, npm/pypi/OpenAPI/MCP bridges, memory, notify, render, loops, spawn_agents, mesh.
Daemon / device tiny-vercel mesh, daemon install\|status\|logs\|restart\|uninstall\|show, tray <action> src/daemon.ts (launchd / systemd --user), src/mesh/zenoh.ts (devduck-compatible LAN mesh), src/mesh/relay-poller.ts (cloud relay: the web agent's use_device reaches this machine), src/tray.ts (control socket a menu-bar helper speaks), src/device.ts (device identity + heartbeat).
Account plumbing login, logout, whoami, devices, onboard, sync, connect [app] src/auth.ts (RFC 8252 loopback browser flow → 90-day JWT at ~/.tiny/credentials.json), src/onboard.ts (model providers + voice, synced to the account), src/integrations.ts (Google / Spotify / Telegram / WhatsApp secrets at ~/.tiny/integrations.json).

Plus menubar/ — a Swift package (tiny-menubar, TinyMenuKit) that is a client of the tray socket; it is not part of the npm package (files: ["dist","README.md"]) and is ported as source under apps/cli/menubar with its own swift test (macOS only, not in the Node CI matrix).

Commands (from src/cli.ts switch (cmd))·

Command Source line Reaches
login cli.ts:56 web /auth/cli?port&state → POST /api/auth/cli/token
logout cli.ts:93 local only (credentials + device.json)
whoami cli.ts:101 GET /api/me
devices cli.ts:122 GET /api/devices
connect [google\|spotify\|telegram\|whatsapp] cli.ts:146 local; Telegram token verified against api.telegram.org
onboard [status] cli.ts:152 GET/POST /api/model-providers, /api/model-config, /api/account-voice
sync cli.ts:173 same three routes (pull, then push)
serve cli.ts:182 MCP over stdio → all /api/* below
repl / tui / "text" / (none) cli.ts:193/199/… local agent; /api/chat when no model key
daemon install\|show\|status\|logs\|restart\|uninstall cli.ts:206 launchd plist / systemd unit that runs mesh
mesh [peers\|send\|broadcast] cli.ts:228 zenoh LAN mesh + POST /api/devices (enroll) + /api/devices/heartbeat + /api/devices/relay poll
tray status\|tasks\|result\|ask\|cancel\|logs\|reload\|ping cli.ts:558 ~/.tiny/tray.sock
help cli.ts:590 —

How it reaches the web app vs the worker·

  • Web app (TinyApi.baseUrl, src/api.ts:48): TINY_API_URL env → creds.apiUrl → literal https://tiny.technology. Every authenticated call is Authorization: Bearer <cli JWT> against the Next.js /api/* routes. This is the ONLY credentialed origin.
  • Worker — three public, unauthenticated reads bypass the app and hit the worker directly, via the constant WORKER_PUBLIC = 'https://plugin.tiny.technology' (src/server.ts:19, src/agent/tiny-tools.ts:14): GET /retrieve?text=, GET /list?…, GET /tools/browse. The fourth worker touch is the media-origin allowlist MEDIA_ORIGINS (src/agent/device-invoke.ts:39) which already honours TINY_WORKER_URL.
  • Web routes the CLI calls (27, all present in apps/web): /api/account-voice, /api/archives, /api/auth/cli/token, /api/control, /api/delete, /api/devices, /api/devices/endpoint/chat, /api/devices/heartbeat, /api/devices/relay, /api/events, /api/follow, /api/graph, /api/jobs, /api/learnings, /api/me, /api/media, /api/messages, /api/model-config, /api/model-providers, /api/share, /api/tiny, /api/tools, /api/tools/install, /api/tools/run, /api/wallet, /api/wallet/faucet, /api/x402/pay, plus the page /auth/cli. Two greps are false positives: /api/tags is Ollama's local API (model.ts:69), and /api/wallet/balance (cli.ts:417, the tray ticker's best-effort balance probe, swallowed on 404) does not exist upstream either — see DRIFT #36.

Resolution in the template (C3): ONE setting, the app URL. --api <url> → TINY_API_URL → ~/.tiny/config.json ({ "api": "https://…" }, written by tiny-vercel init <url>) → interactive prompt on first run. The worker URL is derived: GET <app>/api/health already reports workerConfigured; the template adds workerUrl to that payload (the one web-side change the CLI contract needs) and the CLI caches it in config.json as worker, with TINY_WORKER_URL as the override. The three public reads and the media allowlist use that derived value.

Every tiny.technology reference in src/ (54) — and what each becomes·

# file:line Text Becomes
1 api.ts:2 doc comment "client for tiny.technology /api/*" prose → "the web app"
2 api.ts:48 \|\| 'https://tiny.technology' fallback resolveConfig().api (no literal)
3 auth.ts:6 doc: open https://tiny.technology/auth/cli prose → <api>/auth/cli
4 auth.ts:21 DEFAULT_API_URL = 'https://tiny.technology' removed; loadCredentials() env path uses resolveConfig()
5 cli.ts:3 header comment prose
6 cli.ts:115 "create one … at tiny.technology" at ${api}
7–8 cli.ts:328, 345 systemPromptSummary: 'tiny — tiny.technology personal AI' tiny — ${apiHost} personal AI (host of the configured app URL)
9 cli.ts:593 help banner "tiny-vercel — your tiny deployment, on every surface"
10 cli.ts:615 help: "sync with tiny.technology" "sync with your deployment"
11 cli.ts:628 help: TINY_API_URL override https://tiny.technology TINY_API_URL override the deployment URL (or tiny-vercel init <url>)
12 daemon.ts:140 systemd Description=… (tiny.technology mesh node) "(tiny-vercel mesh node)"
13 integrations.ts:4 doc prose
14–16 tui/onboard-app.tsx:40, 198, 236 TAGLINE + two status strings tagline "your identity, on every surface"; status strings use ${apiHost}
17 agent/image.ts:186 User-Agent (+https://tiny.technology) tiny-vercel use_image (+${api})
18 agent/agent.ts:186 modelLabel = 'server (tiny.technology /api/chat)' server (${apiHost} /api/chat)
19–20 agent/agent.ts:678, 686 system prompt "personal AI from tiny.technology … the tiny.technology platform" "from ${apiHost} … the platform at ${apiHost}"
21 agent/device-invoke.ts:3 doc prose
22 agent/device-invoke.ts:39 MEDIA_ORIGINS = ['https://plugin.tiny.technology', TINY_WORKER_URL] [resolveConfig().worker]
23–24 agent/model.ts:131, 203 label server (tiny.technology /api/chat) as #18
25 agent/tiny-tools.ts:2 doc prose
26 agent/tiny-tools.ts:14 WORKER_PUBLIC = 'https://plugin.tiny.technology' resolveConfig().worker
27–31 agent/tiny-tools.ts:21, 28, 67, 75, 110 tool descriptions "on tiny.technology" "on your tiny deployment" (descriptions are read by the model; the brand is not load-bearing)
32 agent/tiny-tools.ts:172 example URL in tiny_pay_quote schema https://<your-app>/api/x402/chat/<slug>
33 agent/repl.ts:46 promptSummary as #7
34 agent/repl.ts:124 "proxying via tiny.technology" proxying via ${apiHost}
35–36 agent/memory.ts:5, 423 doc + tool description prose
37 server.ts:2 doc prose
38 server.ts:19 WORKER_PUBLIC resolveConfig().worker
39–49 server.ts:241, 247, 269, 403, 715, 756, 798, 826, 838, 853, 1029 MCP tool descriptions "your tiny deployment"; tiny.technology/<name> → <app>/<name>; example URL as #32
50 server.ts:1000 "Not logged in to tiny.technology" Not logged in to ${apiHost}
51–53 onboard.ts:300, 306, 396 "pushed/pulled/synced to tiny.technology" ${apiHost}
54 mesh/relay-poller.ts:44 \|\| 'https://tiny.technology' fallback resolveConfig().api

Also scrubbed (not brand URLs but operator-specific): agent/mcp.ts:147, 231 and agent/notify.ts:6 comments naming /Users/cagatay/… / cagatay-mac → neutral wording. Test fixtures (29 refs in test/*.mjs) keep example hosts but move to https://tiny.example / https://worker.tiny.example so a stranger's grep of the tree finds zero tiny.technology. menubar/main.swift:293 "open tiny.technology" → opens the configured app URL read from the tray status reply.

Environment variables (72 read via process.env)·

Deployment: TINY_API_URL (7 reads), TINY_WORKER_URL (1; override of the derived worker), TINY_TOKEN (headless bearer), TINY_HOME (16; default ~/.tiny), TINY_NO_BROWSER, TINY_DEVICE_NAME. Model: TINY_MODEL_{PROVIDER,ID,API_KEY,BASE_URL}, OPENAI_API_KEY, ANTHROPIC_API_KEY, ANTHROPIC_DEFAULT_OPUS_MODEL, GEMINI_API_KEY, AWS_{REGION,PROFILE,ACCESS_KEY_ID,BEARER_TOKEN_BEDROCK}, BEDROCK_{REGION,MODEL_ID}, OLLAMA_{HOST,MODEL_ID}, STRANDS_{MODEL_ID,MAX_TOKENS,ADDITIONAL_REQUEST_FIELDS}, TINY_MAX_TOKENS, TINY_ADDITIONAL_REQUEST_FIELDS. Voice: TINY_VOICE, TINY_VOICE_{BACKEND,MODEL,INPUT,HALF_DUPLEX,FULL_DUPLEX,ECHO_LEARN,NOISE_REDUCTION,TURN_STALL_MS,BARGE_MARGIN,BARGE_FRAMES,BARGE_FLOOR}. Mesh/daemon: TINY_MESH, TINY_MESH_MAX_HOPS, ZENOH_{CONNECT,LISTEN}, TINY_TRAY_SOCK. Agent dirs: TINY_{TOOLS,SPAWNS,LOOPS,MEMORY,NPM,PYPI,OPENAPI}_DIR, TINY_MEMORY, TINY_SANDBOX_FILE, TINY_MARQUEE_{FILE,AUTHOR}, TINY_DISABLE_LOAD_TOOL. Limits: TINY_MAX_LOOPS, TINY_MAX_CONCURRENT, TINY_LOOP_{ID,MAX_ITERATIONS,ITER_TIMEOUT_MS,MAX_WALL_HOURS}, TINY_MCP_{CLIENT,DEVICE_TOOLS,DEVICE_TIMEOUT_MS}, TINY_OPENAPI, TINY_OPENAPI_{TIMEOUT_MS,OUTPUT_MAX}. Integrations: GOOGLE_{API_KEY,API_SCOPES,APPLICATION_CREDENTIALS,IMPERSONATE_SUBJECT,OAUTH_CLIENT,OAUTH_CLIENT_ID,OAUTH_CLIENT_SECRET,OAUTH_CREDENTIALS}, SPOTIFY_{CLIENT_ID,CLIENT_SECRET,REDIRECT_URI}, SPOTIPY_CACHE_PATH, TELEGRAM_BOT_TOKEN, WACLI_{BINARY,STORE,STORE_DIR}, FLIPPER_PORT. Runtime: CI, HOME, USER, USERNAME, PATH.

None of these is a tiny.technology constant; they are all kept as-is. TINY_API_URL keeps its name so an existing device config repoints with one variable (README § Devices).

Modules (72 files, 28,579 lines)·

src/cli.ts 681 · src/server.ts 1130 · src/api.ts 253 · src/auth.ts 242 · src/device.ts 254 · src/daemon.ts 262 · src/tray.ts 1110 · src/onboard.ts 403 · src/integrations.ts 375 · src/files.ts 123 · src/wallet.ts 202 · src/mesh/{zenoh 506, registry 206, relay-poller 140, tools 83} · src/tui/* 15 files 4,078 (Ink) · src/agent/* 41 files ≈19,400 (agent loop 1149, github 1232, openapi 1281, flipper 876, realtime 792, mcp 766, spotify 708, google 705, loop 653, computer 633, speech 644, yaml 596, windows 577, memory 552, manage-messages 508, local-tools 502, …).

Dependencies (14): @strands-agents/sdk, @modelcontextprotocol/sdk, zod, ink, ink-spinner, ink-text-input, react, @types/react, marked, marked-terminal, string-width, openai, @opentelemetry/api, @diskette/dialtone. Dev: typescript, @types/node, @types/marked-terminal. Build is plain tsc (ES2022 / Node16 modules / react-jsx), no bundler.

Tests (61 files, node --test test/*.test.mjs against dist/)·

Unit suites cover agent, ambient, auth, components, conversations, daemon, device(-tools), event-kinds, exec, files, fork, github, hardware, history, human-type, image, integrations(-store), layout, local-tools, loop(-strip), manage-messages, manage-tools, marquee(-markup), max-tokens-recovery, mcp, media, memory, mesh(-registry), model(-spec), notify(-context), npm-pypi, onboard, openapi, realtime, relay-poller, render, server(-device), slash, spawn(-state,-strip), speech, tool-icons, tray(-notify), tui, use-device, voice-call, wallet, windows, yaml. Two e2e scripts (live.e2e.mjs, voice.e2e.mjs) need a real account / microphone and stay opt-in (npm run test:live, test:voice). Upstream CI runs the suite on Node 18/20/22; the template's root CI adds apps/cli to its single Node 22 job (the monorepo engine is >=22).

Things that are NOT ported, and why·

  • tiny-tech-0.8.2.tgz, cache/, IMPROVEMENTS.md, PORT_STATE.md, .claude/, .screen_monitor/, telegram_events/, whatsapp_events/ — build artefacts and the author's working notes, none tracked as product (git ls-files = 157 files).
  • menubar/.build/ — Swift build output.
  • The Ollama /api/tags probe and the /api/wallet/balance call are kept verbatim (rule 3); the latter is logged as DRIFT #36 because the route does not exist on either backend.