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

@carbonvoice/cv-claude-channel

v0.3.0

Published

Carbon Voice Claude Channel server - bridges Carbon Voice conversations into Claude Code sessions

Readme

Carbon Voice Claude Channel for Claude Code

npm version

A Claude Code channel that bridges Carbon Voice conversations into a running Claude Code session. Send a voice message, Claude does the work on your machine, and the reply comes back as audio.

If your computer is offline, messages queue and are delivered when the connection is restored.

Features

  • Real-time message delivery via WebSocket (primary) with polling fallback
  • Two-way communication — Claude replies back into Carbon Voice conversations
  • Sender gating — deny-by-default allowlist, managed from the terminal only
  • Permission relay — approve or deny Bash/Write/Edit prompts from your phone
  • Attachments — files sent in Carbon Voice are downloaded for Claude to read
  • Deduplication and state persistence — resumes from the last-seen cursor

Setup

Before you start: you need two Carbon Voice accounts

The Personal Access Token identifies the Claude side of the conversation. Messages from that account are treated as Claude's own and are ignored, so you cannot message the channel from the account whose token you used — nothing will arrive, and there is no error.

So either:

  • Team use (the normal case). Use your own token. Colleagues message you in Carbon Voice, Claude does the work on your machine and replies in your conversation.
  • Solo use. Create a second Carbon Voice account to act as the bot, use its token, and message it from your personal account.

Steps

  1. Get a Personal Access Token from Carbon Voice, for whichever account is the Claude side.

  2. Install the plugin.

    /plugin marketplace add PhononX/cv-claude-channel
    /plugin install cv-channel@carbonvoice

    Choose the user scope so it works across projects. If the summary says Run /reload-plugins to activate., run that.

  3. Save the token.

    /cv-channel:configure <your-personal-access-token>

    Until you do this the channel's MCP server has no token and exits — /mcp showing it as failed before this step is expected, not a bug.

  4. Restart with the channel enabled. See the table below for the flag your plan needs.

  5. Allow yourself. Every sender is denied by default, so the first message is supposed to be dropped. Send one voice message from your other account. Claude reports the sender ID; then run:

    /cv-channel:access allow <user-id>

    That takes effect on the next message — no restart. Message again and it reaches Claude.

Allowing someone lets them send messages Claude acts on and approve relayed tool prompts like Bash and Write. Only allow people you trust with that.

Troubleshooting the first run

| Symptom | Cause | | --- | --- | | Nothing arrives, no error | You messaged from the token's own account. Use a different one. | | /mcp shows the server failed | No token yet — run /cv-channel:configure. | | Messages dropped, Claude mentions an unknown sender | Working as intended. Allow the ID (step 5). | | Startup says "blocked by org policy" | Your organization has not enabled channels; no flag gets around it. | | Channel never connects, but the server is healthy | You started Claude without the channel flag, so the channel isn't registered. |

Who can run it, and how

Channels are in research preview, and which flag you need depends on your plan:

| You are | Command | Prerequisites | | --- | --- | --- | | Pro/Max, no organization | claude --dangerously-load-development-channels plugin:cv-channel@carbonvoice | none | | Team/Enterprise | claude --channels plugin:cv-channel@carbonvoice | admin sets both channelsEnabled and allowedChannelPlugins |

Two things worth knowing before you file a bug:

  • channelsEnabled is off by default on Team and Enterprise plans, and it blocks the development flag too. If you are in an organization and see "blocked by org policy" at startup, no flag will get you around it — an admin has to enable channels first.
  • This plugin is not on Anthropic's curated channel allowlist, so --channels alone will not load it outside an organization that has allowlisted it. That is expected, not a misconfiguration.

For an admin, the managed-settings entry is:

{
  "channelsEnabled": true,
  "allowedChannelPlugins": [
    { "marketplace": "carbonvoice", "plugin": "cv-channel" }
  ]
}

Without the plugin

The bare MCP server still works and is supported for one more release. Add it to .mcp.json:

{
  "mcpServers": {
    "cv-claude-channel": {
      "command": "npx",
      "args": ["@carbonvoice/cv-claude-channel"],
      "env": { "CV_PAT": "your-personal-access-token" }
    }
  }
}

and start with claude --dangerously-load-development-channels server:cv-claude-channel. New installs should prefer the plugin — /cv-channel:configure keeps your token out of .mcp.json, which usually gets committed.

Configuration

The Personal Access Token is the only required setting. /cv-channel:configure writes it to ~/.claude/channels/cv/.env (mode 0600); an explicit CV_PAT in the environment takes precedence.

| Variable | Default | Purpose | | --- | --- | --- | | CV_PAT | — | Personal Access Token. Required unless set via /cv-channel:configure. | | CV_ENV_PATH | ~/.claude/channels/cv/.env | Where the token file lives | | CV_CONVERSATION_ID | all | Scope to a single conversation | | CV_PROJECT_NAME | this project | Project name shown to a newly allowed sender | | CV_ACCESS_PATH | ~/.claude/channels/cv/access.json | Allowlist file | | CV_STATE_PATH | ~/.claude/channels/cv/state.json | Cursor file | | CV_ATTACHMENTS_DIR | ~/.claude/channels/cv/attachments | Downloaded attachments | | CV_POLL_INTERVAL_MS | 5000 | Polling interval when WebSocket is down | | CV_WS_RETRY_MAX_MS | 30000 | Max WebSocket retry backoff | | CV_ATTACHMENT_TIMEOUT_MS | 600000 | How long to wait for a pending upload | | CV_ATTACHMENT_NUDGE_MS | 120000 | When to nudge about a slow upload | | CV_OWN_USER_ID | resolved via API | Skip the identity lookup at startup | | CV_PERMISSION_TTL_MS | 600000 | How long a relayed approval prompt stays answerable | | CV_PERMISSION_CONTEXT_TTL_MS | 600000 | How stale the target conversation may be before a prompt is not relayed | | CV_PERMISSION_PREVIEW_MAX | 400 | Characters of tool input shown in a relayed prompt | | CV_REACTION_ID | 👀 | Emoji used as the processed marker | | CV_PERMISSION_ALLOW_REACTION | ✅ | Emoji meaning "allow once" | | CV_PERMISSION_ALLOW_ALWAYS_REACTION | 💯 | Emoji meaning "allow for this session" | | CV_PERMISSION_DENY_REACTION | ⛔,👎 | Emoji meaning "deny" (both accepted) | | CV_LOG_FILE | stderr only | Mirror the log to a file |

Each takes a comma-separated list of emoji — any emoji, not just the curated set — or a legacy curated slug, which is normalized to its emoji. Deny accepts two by default: ⛔ is what the app's one-tap quick row shows and what the legacy negative reaction normalizes to, while 👎 is the glyph the pre-migration UI drew for that same reaction, so both muscle memories work. The prompt names every glyph it accepts, and the server warns at startup if a value is not a single emoji, if two verdicts share a glyph, or if the processed marker collides with an approval emoji.

Usage

Receiving messages

<channel source="plugin:cv-channel:cv-channel" channel_id="..." message_id="..." sender_id="..." is_reply="false" reply_to_id="...">
  transcript of what was said
</channel>

Replying

Claude calls send_message with channel_id, reply_to_message_id, and text. Carbon Voice converts the text to audio.

Permission prompts

When Claude needs approval for a tool, the prompt is relayed to Carbon Voice:

Claude wants to run Bash: Delete the build directory

{"command":"rm -rf ./build"}

✅ = allow once. 💯 = allow Bash for the rest of this session, whatever the arguments. ⛔ or 👎 = deny.
Or reply "yes abcde" or "no abcde".

React, or reply yes <id> / no <id>. The local terminal dialog stays open the whole time — whichever answer arrives first wins.

The prompt shows the tool's actual arguments, not just Claude's description, because for Bash the description is often the bare string Run shell command. Long values are truncated for playback, and Claude Code masks recognizable credentials as [REDACTED] before the server ever sees them. Note that masking can hide key names as well as values, so a displayed key may not match the real input.

A relayed prompt expires after CV_PERMISSION_TTL_MS and can no longer be answered from Carbon Voice; the terminal dialog is unaffected.

Security

Sender gating

Every sender is denied by default. Unauthorized messages are dropped silently.

Access is managed from the terminal with /cv-channel:access:

/cv-channel:access list
/cv-channel:access allow <user-id>
/cv-channel:access remove <user-id>
/cv-channel:access block <user-id>
/cv-channel:access unblock <user-id>

Allowlist changes are only made this way. The channel server reads access.json and never writes it, and it exposes no tool that can widen access — so no inbound message, forwarded message, or attachment can talk Claude into allowlisting anyone. The skill itself refuses requests that arrived over the channel. Edits take effect on the next inbound message, without a restart.

When an unknown sender messages, Claude is told once per session so it can pass the ID along to you. Acting on it is your call, at the terminal.

If the allowlist is completely empty, the sender gets one reply in Carbon Voice: "Allow Sender list is currently empty. Go to Claude to approve senders."

What allowing someone grants

An allowlisted sender can send messages Claude acts on and can approve or deny relayed tool-use prompts, including Bash, Write, and Edit. Only allow people you would trust with that. This is why the allowlist is deliberately awkward to change.

Development

npm install
npm start          # run against the TypeScript source
npm run build      # compile to dist/
npm test           # vitest
npm run smoke      # protocol smoke test (needs a build first; hits no network)

Test the plugin without publishing:

npm run build
claude --plugin-dir . --dangerously-load-development-channels plugin:cv-channel@inline
claude plugin validate . --strict

Publishing

npm publish

prepublishOnly runs the build. The published tarball is an installable plugin as well as an MCP server — the .claude-plugin/, .mcp.json, and skills/ entries are what the marketplace npm source resolves.

Requirements

  • Node.js >= 18
  • A Carbon Voice account with a Personal Access Token
  • Claude Code with channels available on your plan (see the table above)

Known incompatibility

Claude Code does not register a channel server that negotiates MCP protocol revision 2026-07-28. No published @modelcontextprotocol/sdk speaks that revision yet, and it is only reachable if you set MCP_PROTOCOL_NEGOTIATION=auto. If a future SDK adds it and the channel stops registering, leave that variable unset or set it to legacy.

License

MIT

Support

  • GitHub Issues: https://github.com/PhononX/cv-claude-channel/issues
  • Carbon Voice API: https://api.carbonvoice.app

Acknowledgments

Built following the Claude Code Channels specification.