Mmcp.market

add-dial-tool skill

by nanocoai·nanocoai/nanoclaw·31k stars·MIT

Give chosen NanoClaw agents a real phone number as a container tool — the `dial` CLI baked into the agent image plus OneCLI credential injection for api.getdial.ai, scoped per agent, so the agents you pick can send SMS, place AI voice calls, and receive verification codes from inside the sandbox. Independent of the Dial channel; idempotent; re-run to change which agents may use it. Use when the user wants agents to text, call, or run `dial …` from a chat, without wiring Dial as a messaging channel.

A100/100content scan

Is the add-dial-tool skill safe?

Clean: nothing in its files matched our rules. We read 4 files in the folder on 2026-09-28.

No findings.

Install the add-dial-tool skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/nanocoai/nanoclaw.git /tmp/nanoclaw
mkdir -p ~/.claude/skills
cp -r /tmp/nanoclaw/.claude/skills/add-dial-tool ~/.claude/skills/add-dial-tool
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

Add Dial Tool

Installs Dial as a container tool: the dial CLI on the agent's PATH, the dial-cli skill so the agent knows how to drive it, and an OneCLI credential so in-container calls are injected keyless. Independent of the Dial channel (/add-dial) — install this alone. Idempotent: re-run it to change which agents may use Dial.

This tool spends money and reaches real people. An agent with Dial access can text and call any number and buy more numbers, billed to the Dial account. The CLI and the skill file land in every agent's container, but the key is injected per agent by OneCLI, so the operator chooses which agents get it. Every other agent gets an OneCLI block rule and sees 403 blockedbypolicy if it tries.

Run this from the NanoClaw repo on the host (not from a chat with an agent — the container can't install itself). The mechanical steps carry nc: directive fences: an agent reads the prose and applies them, and a parser can apply them deterministically from the same document. Every directive is idempotent, so the whole skill is safe to re-run; anything a parser can't apply falls back to the prose beside it.

Pre-flight

OneCLI is required for credential injection — without it there is no way to hand the key to a container without putting it in an env var. This must succeed before anything else runs:

command -v onecli >/dev/null

If it fails, tell the user to run /init-onecli first, then retry. Stop here.

Calls this setup makes to Dial identify the install. The dial CLI prepends DIALUSERAGENT to its own token, so the account's requests stay attributable to this NanoClaw install in Dial's server-side logs. Resolve the token once (nanoclaw/; an unreadable package.json degrades to nanoclaw/unknown rather than blocking the install):

node -p "'nanoclaw/'+(require('./package.json').version||'unknown')" 2>/dev/null || echo nanoclaw/unknown

Prefix every dial command below with DIALUSERAGENT={{dial_ua}}.

Choose which agents may use Dial

List the agent groups (the NanoClaw service must be running — ncl talks to it over its socket):

ncl groups list --json | jq -r 'if (.data|length)==0 then "no agent groups yet" else [.data[] | "\(.id) (\(.name))"] | join(", ") end'

Ask the operator which of them may use Dial. Say plainly what they are granting, and ask even when there is a single agent:

Agents on this install: {{agent_groups}}. Giving an agent Dial lets it text and call any number and buy numbers, billed to your Dial account. Agents you leave out are blocked at the gateway (reversible by running /add-dial-tool again). Agents created after this run have Dial until the next run.
Which agents may use Dial? Enter agent ids separated by commas with no spaces (the `ag-…` column), `all` for every agent, or `none` to install the tool with every agent blocked for now.

all and none cannot be mixed with ids, and an empty answer is never "everyone". A typo must not silently open or close anything, so every id named must be a real agent group:

for w in $(printf '%s' '{{dial_agents}}' | tr ',' ' '); do case "$w" in all|none) ;; *) ncl groups list --json | jq -e --arg id "$w" '.data[] | select(.id==$id)' >/dev/null || { echo "unknown agent group '$w' — see: ncl groups list" >&2; exit 1; }; esac; done

Install the Dial CLI on the host

The host needs the dial CLI to sign in: dial auth login / dial auth verify-otp write the host auth file that the credential step below reads. Pinned to the same version the agent image gets, so host and sandbox agree:

command -v dial >/dev/null || npm install -g @getdial/cli@0.37.0

Sign in to Dial

Dial's CLI owns the account credential (an auth file it writes on sign-in).

Check the host sign-in

Is this host already signed in?

DIAL_USER_AGENT={{dial_ua}} dial doctor --json

Read the account

If it is, read which account — that account's key is what the chosen agents will use:

DIAL_USER_AGENT={{dial_ua}} dial doctor --json
This host is signed in to Dial as {{connected_email}}; the agents you chose will use that account. To give them a different account, run `dial auth login <email> --force` and `dial auth verify-otp --code <code>` on the host first, then run /add-dial-tool again.

Send the code

If it is not, verify an email with a one-time code. Collect the email:

What's your email? Dial sends a one-time code to verify it. By continuing you create a Dial account and agree to Dial's Terms of Service (https://getdial.ai/terms) and Privacy Policy (https://getdial.ai/privacy).

Send the code (--force re-sends even if a prior code is pending):

DIAL_USER_AGENT={{dial_ua}} dial auth login {{owner_email}} --force

Verify the code

Collect the code:

Enter the 6-digit code from your email

Verify it. Do not pass --agent nanoclaw here: this skill owns the container dial-cli skill, and --agent would drop a second, unmanaged copy next to it:

DIAL_USER_AGENT={{dial_ua}} dial auth verify-otp --code {{otp}}

Put the CLI and its skill in the agent image

The agent's global Node CLIs install from container/cli-tools.json, not from hand-edited Dockerfile layers. Add the pinned Dial CLI — idempotent on name, so a re-run is a no-op. @getdial/cli has no native postinstall, so no onlyBuilt:

{ "name": "@getdial/cli", "version": "0.37.0" }

The version (0.37.0) is the canonical pin — this document is the source of truth; the host install above uses the same one.

Mount the sandbox-aware dial-cli skill so the agent knows the CLI runs keyless in there and never asks for credentials. container/skills/ is mounted read-only into every agent container (at /app/skills) — which is why the key, not the skill file, is what gets scoped per agent:

container-skills/dial-cli/SKILL.md -> container/skills/dial-cli/SKILL.md

Rebuild the image so the CLI lands. On an install that fetches a published image this adds Dial as a layer on top of it; on one that builds its own it rebuilds:

./container/build.sh

Register the credential with OneCLI

Read the API key from the host auth file — the single source of truth, written by dial auth login / dial auth verify-otp — and put it in the OneCLI vault for api.getdial.ai. Always replace: the vault is keyed by name, so an existing "Dial API" secret is not necessarily this account's (re-onboarding, switching accounts, or rotating the key all leave a secret whose value points at the previous account, and a sandboxed agent then lists that account's numbers). A stale secret is deleted and a fresh one created rather than updated in place: onecli secrets update accepts a new value only on the command line, and the key must never sit on one. It travels through a 0600 temp file that is removed right after (--file), so it is never on argv or in a captured variable. Selective-mode agents pick the new id up in the merge step below:

T=$(mktemp) && chmod 600 "$T" && jq -r '.apiKey // empty' "${XDG_DATA_HOME:-$HOME/.local/share}/dial/auth.v1.json" > "$T" 2>/dev/null; [ -s "$T" ] || { rm -f "$T"; echo "no Dial API key in the host auth file — sign in with dial auth login / verify-otp, then re-run" >&2; exit 1; }; S=$(onecli secrets list | jq -r 'first(.data[] | select(.name | test("(?i)dial"))) | .id // empty'); if [ -n "$S" ]; then onecli secrets delete --id "$S" >/dev/null || { rm -f "$T"; echo "could not remove the previous Dial secret $S" >&2; exit 1; }; fi; onecli secrets create --name "Dial API" --type generic --file "$T" --host-pattern api.getdial.ai --header-name Authorization --value-format "Bearer {value}" >/dev/null; rc=$?; rm -f "$T"; exit $rc

Scope it to the chosen agents

Create the OneCLI agents

NanoClaw gives every agent group its own OneCLI agent whose identifier is the group id, created on the group's first spawn. A group that has never spawned has no OneCLI agent yet, and a block rule needs one to attach to — so create the missing ones now, exactly as the runtime would (secret mode all, nothing else touched):

G=$(ncl groups list --json) || { echo "could not list agent groups — is the NanoClaw host running?" >&2; exit 1; }; AG=$(onecli agents list) || { echo "could not list OneCLI agents" >&2; exit 1; }; printf '%s' "$G" | jq -r '.data[] | "\(.id)\t\(.name)"' | while IFS="$(printf '\t')" read -r gid gname; do printf '%s' "$AG" | jq -e --arg g "$gid" '.data[] | select(.identifier==$g)' >/dev/null || onecli agents create --name "$gname" --identifier "$gid" >/dev/null || { echo "could not create an OneCLI agent for $gname ($gid)" >&2; exit 1; }; done

Set the block rules

The one switch is a per-agent block rule on api.getdial.ai, named Dial: blocked for so only this skill's rules are ever read or written (an operator's own rules on the host are left alone). A chosen agent has its rule removed; every other agent has one present and enabled. A 403 blockedbypolicy in a container means "not chosen", not "broken":

A=$(printf '%s' '{{dial_agents}}' | tr -d ' '); G=$(ncl groups list --json) || { echo "could not list agent groups — is the NanoClaw host running?" >&2; exit 1; }; case ",$A," in *,all,*) A=$(printf '%s' "$G" | jq -r '[.data[].id] | join(",")');; esac; AG=$(onecli agents list) || { echo "could not list OneCLI agents" >&2; exit 1; }; RL=$(onecli rules list) || { echo "could not list OneCLI rules" >&2; exit 1; }; printf '%s' "$G" | jq -r '.data[] | "\(.id)\t\(.name)"' | while IFS="$(printf '\t')" read -r gid gname; do aid=$(printf '%s' "$AG" | jq -r --arg g "$gid" 'first(.data[] | select(.identifier==$g)) | .id // empty'); [ -n "$aid" ] || { echo "no OneCLI agent for $gname ($gid)" >&2; exit 1; }; rid=$(printf '%s' "$RL" | jq -r --arg a "$aid" 'first(.data[] | select(.hostPattern=="api.getdial.ai" and .action=="block" and .agentId==$a and (.name | startswith("Dial: blocked for ")) and ((.pathPattern // "")=="") and ((.method // "")==""))) | .id // empty'); case ",$A," in *,"$gid",*) if [ -n "$rid" ]; then onecli rules delete --id "$rid" >/dev/null || { echo "could not remove the Dial block for $gname ($gid)" >&2; exit 1; }; fi; echo "allowed: $gname ($gid)";; *) if [ -z "$rid" ]; the

Merge secrets for selective agents

More skills from nanocoai/nanoclaw

  • Aadd-anydocAdd local office-document-to-Markdown conversion to NanoClaw agent containers with the pinned Firecrawl AnyDoc CLI. Use when agents need to read attached Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, or text-based PDF files without uploading them to a hosted parser.
  • Aadd-atomic-chat-toolAdd Atomic Chat MCP server so the container agent can call local models served by the Atomic Chat desktop app via its OpenAI-compatible API.
  • Fadd-clidashAdd clidash — a zero-dependency, read-only web dashboard that derives its tabs and tables at runtime from any CLI that lists resources as JSON. Ships pre-wired for NanoClaw's ncl CLI (agent groups, sessions, channels, users, roles), plus message-activity charts, a log tail, and a read-only file viewer for group skills/CLAUDE.md/profiles.
  • Aadd-codexUse Codex (OpenAI's codex app-server) as a full agent provider — planning, tool orchestration, MCP tools, server-side history, session resume — alongside or instead of Claude. ChatGPT subscription or OpenAI API key, vault-only via the selected gateway. Per-group via `ncl groups config update --provider codex`. Distinct from using OpenAI as an MCP tool (where Claude remains the planner).
  • Aadd-dashboardAdd a monitoring dashboard to NanoClaw. Installs @nanoco/nanoclaw-dashboard and a pusher that sends periodic JSON snapshots.
  • Aadd-deltachatAdd DeltaChat channel integration via @deltachat/stdio-rpc-server. Native adapter — no Chat SDK bridge. Email-based messaging with end-to-end encryption.
  • Cadd-dialAdd Dial channel integration — a real phone number for SMS and AI voice calls via the Dial platform (getdial.ai). Native adapter — no Chat SDK bridge.
  • Aadd-dial-numberAdd another phone number to an existing Dial channel — a second (or third) public line for the agent, so one NanoClaw install answers SMS and AI voice calls on multiple numbers. Use when Dial is already installed and the operator wants an additional number (e.g. a personal line plus a support line). Requires the Dial channel to already be installed (see /add-dial).
  • Aadd-discordAdd Discord bot channel integration via Chat SDK.
  • Aadd-emacsAdd Emacs as a channel. Opens an interactive chat buffer and org-mode integration so you can talk to NanoClaw from within Emacs (Doom, Spacemacs, or vanilla). Local HTTP bridge — no bot token or external service needed.
  • Aadd-gchatAdd Google Chat channel integration via Chat SDK.
  • Aadd-githubAdd GitHub channel integration via Chat SDK. PR and issue comment threads as conversations.

All agent skills → · MCP servers