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_URLenv →creds.apiUrl→ literalhttps://tiny.technology. Every authenticated call isAuthorization: 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 allowlistMEDIA_ORIGINS(src/agent/device-invoke.ts:39) which already honoursTINY_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/tagsis 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/tagsprobe and the/api/wallet/balancecall are kept verbatim (rule 3); the latter is logged as DRIFT #36 because the route does not exist on either backend.