npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tell-ai/web

v0.3.2

Published

Tell AI web interface — installs the tell CLI alongside it

Readme

Tell Web

Tell AI web sandbox: chat with models, file explorer/viewer, command execution and PTY terminal — all anchored to a working directory (--cwd).

Requirements

Node.js >=22 (see engines in package.json).

Install

npm i -g --ignore-scripts=false @tell-ai/web

--ignore-scripts=false is required: node-pty is a native module and its install script builds the binary. With scripts disabled (npm's default when ignore-scripts=true is set globally or in .npmrc), the install "succeeds" but every PTY terminal fails to load.

The sandbox needs a working directory to anchor to:

tell-web --cwd ~/projects/my-app

Run (from source)

npm i
npm run dev      # vite frontend + tsx server (development)
npm run build    # tsup (server.js) + vite build (assets) → dist/
npm start        # node dist/server.js (production)

CLI

tell-web --cwd <path> --prompt <text> -m <model> --chain --stream --think --require-approval -y/--no-exec
tell-web --help

| Flag | Default | Description | |---|---|---| | --cwd <path> | current cwd | Sandbox directory | | --prompt <text> | — | Fill the chat inbox (never auto-sent, never a message) | | -m, --model <id> | TELL_MODEL or l | Model shortcode or spec | | --port <n> | PORT or 3000 | TCP port | | --host <addr> | 127.0.0.1 | Bind address (0.0.0.0 for LAN) | | --exec-timeout <ms> | 120000 (max 600000) | Timeout per command | | --max-chain-steps <n> | server default (clamped 1..8; the CLI accepts up to 128, larger forwarded values clamp down here) | Server chain round cap override (forwarded by tell --web --max-chain) | | --max-buffer-bytes <n> | 33554432 | Internal exec buffer override (forwarded by tell --web --max-buffer) | | --output-max-bytes <n> | 204800 | Delivered output cap in bytes; truncated once over the combined stdout+stderr with the core [output truncated at N bytes] marker (forwarded by tell --web --output-max-bytes) | | --context-chars <n> | — | Context chars override, exposed on /api/config (forwarded by tell --web --context-max-chars) | | --profile <name> | — | Tellrc profile name, exposed on /api/config (forwarded by tell --web --profile) | | --system-file <path> | — | Custom instructions file (64 KB cap), composed into the system prompt (forwarded by tell --web --system-file) | | --temperature <n> | provider default | Sampling temperature 0..2, applied to both the one-shot and the streamed reply and exposed on /api/config (forwarded by tell --web --temperature; out-of-range values are dropped, never clamped) | | --stdin-timeout-ms <n> | — | Stdin timeout override, exposed on /api/config (web has no stdin; a disabled deadline is omitted) | | --chain | off | Continue after output until final answer | | --stream | off | Seed the Stream toggle (Settings → Agent Runtime) and keep NDJSON as the server fallback | | --think | off | Start reasoning headers expanded (manual collapse/expand persists) | | --require-approval | off | Seed the Require Approval toggle (classification stays client-side) | | -y, --yes | off | Seed Auto-Run on the first visit (off by default: a fresh browser never auto-executes) | | --no-exec | off | Server-enforced: /api/execute returns 403 and the No-Exec toggle is locked | | -h, --help | — | Help |

Streaming (--stream)

/api/tell replies become application/x-ndjson with one event per line: reasoning, reasoning_end, text, done, error. The client appends text to the assistant message as it arrives (throttled) and shows a collapsible reasoning header — 🧠 Thinking… Ns while the model reasons, then 🧠 Thought for N seconds ›. Reasoning headers are collapsed by default; --think seeds them expanded. Expanding/collapsing a header persists the choice for future messages.

The transport itself is a runtime choice: Settings → Agent Runtime has a Stream / No Stream toggle. The client sends stream: <boolean> in the /api/tell body, the server honors it per request, and the choice persists in localStorage — --stream only seeds it on the first visit and stays the fallback for requests that omit the field (curl/API clients). Without streaming the model still runs, but /api/tell returns the single-JSON response (no live reasoning timer; the header falls back to the static Thought for N seconds label). Log, context and <RUN> extraction are identical in both modes.

Execution modes (chat toggles)

The chat header has execution toggles (client-side). Their last choice persists in localStorage across page reloads; server flags (--chain, -y, --require-approval, --no-exec) seed the default on the first visit only:

| Toggle | Effect | |---|---| | Chain Mode (–chain) | Continue iterating through <RUN> steps until the final answer | | Auto-Run (-y) | AI-requested <RUN> scripts execute immediately, no confirmation card | | Require Approval | Risky commands show the confirmation card; safe ones follow Auto-Run (run directly when on, confirm card when off) | | No-Exec | Nothing is ever executed — the confirm card records what would have run |

Auto-Run is opt-in: unless the server was started with -y, a fresh browser asks before every command. Precedence: No-Exec > per-command risk gate, and --no-exec is a server invariant (/api/execute answers 403), so the locked toggle wins over an older stored Auto-Run preference. Enabling No-Exec switches Auto-Run off. With --chain, Auto-Run executes the safe commands server-side (no confirmation card), Require Approval still pauses for each command, and No-Exec records it as not-run while the loop continues with the feedback. The risk gate is fail-closed: if classification fails, the command goes to manual approval.

Sidebar panes

The sidebar stacks the file explorer (top) and the settings panel (bottom). Each pane folds to its own title bar through the chevron in its header, and the collapsed/expanded state persists in localStorage (theme-config-v3) next to sidebarCollapsed/threadsCollapsed. Collapsing the explorer hands its height to settings (and vice versa), and the settings resize handle only exists while the settings pane is open. The sidebar wrapper owns the single outer divider, so the Files header stays aligned on either side and in both collapse states. The navbar's global toggle still hides the whole sidebar at once.

Agent notifications and feed

The navbar badge lives on the Agent button: ! while a command awaits authorization, or the number of retained Agent Feed errors. It disappears while the Agent view is active and stays on the Agent (not on Terminal) otherwise.

Settings → Appearance & Theme → Custom layout → Agent Feed accepts Top, Bottom, Left, Right, and Hidden. Hidden only suppresses the panel: the retained lines and the navbar badge are preserved, so restoring a position shows the same log. The choice persists in theme-config-v3 (customAgentFeed).

Envs (see .env.example)

PORT, TELL_MODEL, TELL_TOKEN (enables the secure mode below), TELL_ALLOWED_HOSTS (comma-separated public hostnames for tunnels/proxies; localhost and IP literals are always accepted) + vendor keys (GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, XAI_API_KEY, DEEPSEEK_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, ALIBABA_API_KEY, ZHIPU_API_KEY, META_API_KEY, MIMO_API_KEY, …).

A user-supplied endpoint works too, with the same variables the CLI reads: CUSTOM_BASE_URL, CUSTOM_API_KEY, plus the optional CUSTOM_MODEL, CUSTOM_API (chat/responses/messages) and CUSTOM_HEADERS (JSON object). Start with tell-web -m custom:<model>; an invalid CUSTOM_API/CUSTOM_HEADERS fails the boot rather than degrading silently.

A workspace .env is ignored by default. Set TELL_TRUST_WORKSPACE_ENV=true before starting the server only when that workspace is trusted; otherwise a repository can redirect provider keys or authentication settings.

TELL_SCROLLBACK_MAX (bytes/chars per terminal pane, default 262144 = 256KB) caps the PTY scrollback buffer. Panes running a program (htop, opencode, vim, background jobs) survive the 5min idle GC — it re-arms while a child process or a foreign foreground process is detected (Linux procfs); explicit pane/tab close still kills immediately. Daemonized (double-forked) processes are not detected.

Secure mode (TELL_TOKEN)

Without TELL_TOKEN the sandbox is open to anything that can reach the port, so it is meant for 127.0.0.1 and nothing more. Set a token to put the whole surface behind one credential:

openssl rand -hex 32                      # keep the output
export TELL_TOKEN=<paste it here>
tell-web --cwd ~/project
# open http://127.0.0.1:3000 and paste the token in the login screen
  • API — every /api/* route requires Authorization: Bearer <token>, including /api/config (it would otherwise leak cwd and the model). The route match is case-insensitive, so /API/config needs the same token as /api/config, and the comparison is constant-time. Missing or wrong token → 401 plus WWW-Authenticate: Bearer realm="tell-web".
  • Bootstrap routes — only GET /api/auth/status (tells the client whether a token is required, so it can show the isolated login screen) and POST /api/auth/verify are public. The verify endpoint is same-origin only and rate-limited to 5 attempts per 15 min per IP; only failed or malformed attempts count, so a reload with the correct token never locks you out. 429 carries Retry-After: 900. The token itself is never logged.
  • Browser — the token lives in memory only (never localStorage), so every reload asks for it again; file downloads go through the authenticated API fetch path instead of a bare link.
  • Terminal — WebSocket upgrades cannot carry headers, so panes pass ?token=<value> on /api/terminal (same constant-time check).
  • Children — commands and PTY panes inherit a scrubbed environment without TELL_TOKEN, provider keys, or loader-injection variables, so a model-requested <RUN> cannot read the credential back out.

Host and origin checks (DNS rebinding)

Independently of the token, the server only answers to hosts it recognizes:

  • TELL_ALLOWED_HOSTS lists the public hostnames you serve it under (tunnels, reverse proxies). localhost and IP literals are always accepted, as is the address bound with --host.
  • An unlisted Host is refused with 403 Forbidden: Host is not allowed on both HTTP and the terminal upgrade; a cross-site Origin is rejected on the WS upgrade and on the auth endpoints. Without this, a malicious page on your network could point a browser at the sandbox and ride your session (DNS rebinding).

Bind to 0.0.0.0 only together with both of these: TELL_TOKEN and TELL_ALLOWED_HOSTS.

Endpoints /api/*

| Method | Route | Notes | |---|---|---| | GET | /api/status | Workspace file tree | | GET | /api/file?path= | Content + mtime (403 traversal/sensitive, 413 >10MB) | | GET | /api/file/raw?path= | Download bytes | | POST | /api/save-file | {path, content, expectedMtime} (409 if changed on disk) | | POST | /api/execute | {command} → {output, exit_code} — blocks high-risk patterns, 429 (10/min, max 2 concurrent). The exit code lets the browser-side loop report a failed command through the core's feedback wording | | POST | /api/risk-check | {command} → {highRisk} — classify without executing (drives Require Approval gating) | | GET | /api/models | Models/aliases + keysStatus per vendor | | GET | /api/auth/status | Public: {authRequired} only (drives the isolated login screen) | | POST | /api/auth/verify | Public + rate-limited (5/15min/IP): {token} → 200/401 generic/429 + Retry-After | | GET | /api/config | defaultModel, autoExecute, requireApproval, noExec, chain, yes, stream, think, cwd, initialPrompt, plus the forwarded profile, temperature, limits and hasCustomSystem (requires auth when TELL_TOKEN is set) | | GET | /api/context | Generated system prompt (tree + README + conventions) | | POST | /api/tell | {messages, modelAlias?, systemPrompt?, stream?} (400 invalid payload, 429); NDJSON event stream when stream is true, JSON otherwise. Missing stream falls back to the server's boot --stream | | POST | /api/chain | Starts a server-side chain run: same payload shape as /api/tell (same 400/429 validation). Runs the model loop on the server (≤8 rounds) and pauses with a server-kept approval instead of returning scripts to the browser | | GET | /api/chain/:runId | Replay: {runId, status, version, rounds, text, events, pending} (404 unknown/expired, 429). Event seq is monotonic for incremental clients | | POST | /api/chain/:runId/confirm | {decisionId\|approvalId, expectedVersion, decision: 'approve'\|'skip'} — any script/command/path field is 400. Same id + same verdict replays without re-executing; different verdict, stale version or unknown id is 409; high-risk/--no-exec is 403 without execution; cancel wins over in-flight confirms | | POST | /api/chain/:runId/cancel | Invalidates the run and its pending approval (idempotent; 404 unknown/expired) | | GET/PUT | /api/session | Persisted state + server facts + live scrollbacks | | GET | /api/session/history | List snapshots | | GET/DELETE | /api/session/history/:name | Read/delete snapshot (:name must end in .json) | | POST | /api/session/snapshot | {success, name, gitChanges, history} | | WS | /api/terminal?paneId=&cols=&rows= | PTY (?token= when TELL_TOKEN) |

Sensitive files never exposed: .env* (except the .env.example template), .tell/**, .git/**, *.key, *.pem. Workspace symlinks are rejected by file, context, and session APIs; .tell is stored with private directory/file modes.

.tell/ layout (local, git-ignored)

.tell/
  session.json   # current session
  history/       # snapshots *.json
  latest -> history/<snapshot>  # symlink (repaired if dangling)

License

License: GPL v3

This package is licensed under the GNU General Public License v3.0 — see the LICENSE file for more details.