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

@shaik-noor/web-terminal-helper

v0.1.2

Published

Local helper daemon for web-terminal

Readme

@shaik-noor/web-terminal-helper

Local helper daemon for Web Terminal. Runs locally on your laptop/machine to connect the Web Terminal interface with your local Claude Code CLI and tools.

Requires Node.js 22.13+ and Claude Code installed and logged in (run claude once).

Terminal chat

Chat with your local Claude Code CLI straight from the terminal, no web app or setup needed:

npx @shaik-noor/web-terminal-helper chat            # works in ~/web-terminal-workspace (created automatically)
npx @shaik-noor/web-terminal-helper chat .          # or the current folder
npx @shaik-noor/web-terminal-helper chat C:\my\repo # or any project folder

/new starts a fresh session, /exit quits, Ctrl+C stops a running reply. Claude asks before running tools; answer y or n. If a helper is already running (via start), chat connects to it instead of starting its own.

Web app helper

You can run the helper without installing anything permanently:

npx @shaik-noor/web-terminal-helper start --origin https://your-web-terminal-domain.com

Local Development

For local web app testing on http://localhost:5173:

npx @shaik-noor/web-terminal-helper start --dev

Options

  • -p, --port <number>: Port to bind (default: 7777)
  • -o, --origin <url>: Configured Web App origin (e.g. https://my-vps.com)
  • --dev: Development mode (allows origin http://localhost:5173)

Commands

  • start (default): Start the helper WebSocket server and automatically launch the pairing URL in your browser.
  • chat [dir]: Interactive terminal chat (see above).
  • pair: Print and open the pairing URL in your browser.
  • reset-key: Rotate the pairing key and invalidate previous sessions.

Integrating with your web app

A web page cannot start programs on a user's machine. The user starts the helper, it listens on ws://127.0.0.1:7777 (local only), and your website connects to it from the browser.

User's laptop                                   Browser tab (https://your-app.com)
┌──────────────────────────────┐   WebSocket   ┌──────────────────────────────┐
│ web-terminal-helper          │◄─────────────►│ your web app                 │
│  ws://127.0.0.1:7777         │  hello + key  │  stores key in localStorage  │
│  └─ Claude Code CLI          │               │                              │
└──────────────────────────────┘               └──────────────────────────────┘

1. The user starts the helper once

Show this command on your app's onboarding page, with your exact origin:

npx @shaik-noor/web-terminal-helper start --origin https://your-app.com

The helper:

  • saves the origin, port and a random pairing key locally, so later runs only need npx @shaik-noor/web-terminal-helper start.
  • opens the user's browser at https://your-app.com/#key=<pairingKey>&port=7777.

Users who run it often can install it globally: npm i -g @shaik-noor/web-terminal-helper, then web-terminal-helper start.

2. Your app saves the pairing key

Read the key from the URL fragment when the page loads, store it, and remove it from the address bar. Browsers never send the fragment to your server, so the key stays out of your server logs.

const params = new URLSearchParams(location.hash.slice(1));
if (params.get('key')) {
  localStorage.setItem('helperKey', params.get('key'));
  localStorage.setItem('helperPort', params.get('port') ?? '7777');
  history.replaceState(null, '', location.pathname + location.search);
}

3. Your app connects and sends hello within 5 seconds

import { DEFAULT_PORT } from '@shaik-noor/web-terminal-helper/protocol';

const port = localStorage.getItem('helperPort') ?? DEFAULT_PORT;
const ws = new WebSocket(`ws://127.0.0.1:${port}`);

ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'hello', key: localStorage.getItem('helperKey') }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  switch (msg.type) {
    case 'hello.ok':
      // msg.claude.available === false means Claude Code is not installed or logged in
      ws.send(JSON.stringify({ reqId: '1', type: 'chats.list' }));
      break;
    case 'auth.error':
      // wrong or rotated key: ask the user to run `web-terminal-helper pair`
      break;
  }
};

ws.onerror = () => {
  // helper is not running: show the `start` command from step 1
};

On every later visit, connect with the saved key. If the connection fails, show the user the start command.

4. Talk to Claude

Every request can include an optional reqId. The reply echoes it, so you can match replies to requests.

| Request | Fields | Reply | | --- | --- | --- | | chats.list | | chats.list.ok { chats } | | chat.create | cwd (existing folder), title? | chat.create.ok { chat } | | chat.open | chatId | chat.open.ok { chat, messages, pendingPermissions, running, runId?, partialText? } | | chat.rename | chatId, title | chat.rename.ok { chat } | | chat.delete | chatId | chat.delete.ok { chatId } | | message.send | chatId, text | message.send.ok { chatId } | | run.stop | chatId | run.stop.ok { chatId } | | permission.respond | requestId, allow, message? | permission.respond.ok { requestId } | | folders.recent | | folders.recent.ok { folders } |

The helper also sends these events to every connected client:

| Event | Meaning | | --- | --- | | run.started { chatId, runId } | Claude started working on a message | | stream.text { chatId, runId, delta } | A piece of the reply text as it streams in | | message.added { chatId, message } | A finished message (user, assistant, tool_use, tool_result, system, error) | | permission.request { chatId, requestId, toolName, input } | Claude wants to run a tool. Answer with permission.respond | | permission.resolved { chatId, requestId, allow } | A permission request was answered | | run.finished { chatId, runId, status, costUsd?, durationMs?, error? } | status is success, error, stopped or interrupted | | chat.created / chat.updated / chat.deleted | The chat list changed | | error { reqId?, code, message } | A request failed, e.g. INVALID_CWD, CHAT_NOT_FOUND, INVALID_FRAME |

All schemas, TypeScript types and constants can be imported from @shaik-noor/web-terminal-helper/protocol (ClientMessageSchema, ServerMessageSchema, ClientMessage, ServerMessage, DEFAULT_PORT, ...). The main entry also exports HelperServer and runChat.

Things to know

  • The origin must match exactly. The browser's Origin header must equal the configured origin: same scheme, host and port, and no trailing slash. https://your-app.com and https://www.your-app.com are different origins.
  • One origin per machine. The helper stores a single origin. Starting it with a different --origin replaces the old one.
  • Only local connections are accepted. The helper binds to 127.0.0.1 and only accepts the Host values 127.0.0.1:<port> and localhost:<port>.
  • Browser support. Chrome and Firefox let https:// pages connect to ws://127.0.0.1. Newer Chrome versions may show a "local network access" permission prompt the first time. Test Safari separately.
  • Security. Anyone with the pairing key and an allowed origin can run Claude Code on the user's machine. Keep the key out of logs and analytics. reset-key rotates it and invalidates old keys.

Testing the helper

You can test the WebSocket protocol without building a web app.

Get a pairing key

npx @shaik-noor/web-terminal-helper start --dev

The startup output prints a pairing URL such as http://localhost:5173/#key=AbC123...&port=7777. The text between key= and &port is your key. Run npx @shaik-noor/web-terminal-helper pair to print it again.

--dev allows the origin http://localhost:5173. If you started with --origin https://your-app.com, use that origin in the Origin header below.

With Postman

Use the Postman desktop app. The web version can't reach 127.0.0.1 without the Postman Agent.

  1. Click New → WebSocket.

  2. URL: ws://127.0.0.1:7777

  3. In the Headers tab, add Origin = http://localhost:5173. Without it the connection is rejected with 403 Forbidden.

  4. The helper closes the connection if hello doesn't arrive within 5 seconds. Paste this into the message box before clicking Connect, then click Connect and Send right away:

    { "type": "hello", "key": "PASTE_YOUR_KEY" }

    You should get back { "type": "hello.ok", "helperVersion": "...", "claude": { "available": true, ... } }.

  5. Send requests one at a time:

    { "reqId": "1", "type": "chats.list" }
    { "reqId": "2", "type": "chat.create", "cwd": "C:\\path\\to\\project", "title": "Postman test" }

    Double the backslashes in Windows paths, and use a folder that exists. Copy chat.id from chat.create.ok:

    { "reqId": "3", "type": "message.send", "chatId": "PASTE_CHAT_ID", "text": "list the files in this folder" }

    The reply streams back as run.started, several stream.text events, message.added, then run.finished.

  6. If you get a permission.request, answer it with its requestId:

    { "reqId": "4", "type": "permission.respond", "requestId": "PASTE_REQUEST_ID", "allow": true }

    To cancel a running reply: { "reqId": "5", "type": "run.stop", "chatId": "PASTE_CHAT_ID" }

With wscat

From a terminal there is no 5-second rush, because you type each message yourself after connecting:

npx wscat -c ws://127.0.0.1:7777 -H "Origin: http://localhost:5173"
> { "type": "hello", "key": "PASTE_YOUR_KEY" }
> { "reqId": "1", "type": "chats.list" }

Troubleshooting

| What you see | Cause | | --- | --- | | 403 Forbidden on connect | The Origin header is missing or doesn't exactly match the configured origin | | Connection refused | The helper isn't running, or it's on a different port | | Handshake timed out (close code 4001) | hello wasn't sent within 5 seconds of connecting | | Invalid pairing key | Wrong key, or the key was rotated with reset-key. Run pair to get the current key | | INVALID_FRAME | The message doesn't match the protocol (misspelled type, missing field) | | INVALID_CWD | The cwd folder doesn't exist or isn't a directory | | hello.ok with claude.available: false | Claude Code isn't installed or logged in. Run claude once |

License

MIT