add-dial-tool
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.
git clone --depth 1 https://github.com/nanocoai/nanoclaw /tmp/add-dial-tool && cp -r /tmp/add-dial-tool/.claude/skills/add-dial-tool ~/.claude/skills/add-dial-toolSKILL.md
# 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 blocked_by_policy` 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:
```nc:run effect:check
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
`DIAL_USER_AGENT` 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/<version>`; an unreadable `package.json` degrades to
`nanoclaw/unknown` rather than blocking the install):
```nc:run capture:dial_ua validate:^nanoclaw/\S+$ effect:fetch
node -p "'nanoclaw/'+(require('./package.json').version||'unknown')" 2>/dev/null || echo nanoclaw/unknown
```
Prefix every `dial` command below with `DIAL_USER_AGENT={{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):
```nc:run capture:agent_groups effect:fetch
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:
```nc:operator
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.
```
```nc:prompt dial_agents validate:^(all|none|ag-[A-Za-z0-9-]+(,ag-[A-Za-z0-9-]+)*)$ normalize:trim
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:
```nc:run effect:check
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:
```nc:run effect:external
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?
```nc:run capture:signed_in=.auth.signedIn validate:^(true|false)$ effect:fetch
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:
```nc:run capture:connected_email=.auth.email when:signed_in=true effect:fetch
DIAL_USER_AGENT={{dial_ua}} dial doctor --json
```
```nc:operator when:signed_in=true
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:
```nc:prompt owner_email validate:^[^@\s]+@[^@\s]+\.[^@\s]+$ when:signed_in=false
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):
```nc:run effect:external when:signed_in=false
DIAL_USER_AGENT={{dial_ua}} dial auth login {{owner_email}} --force
```
### Verify the code
Collect the code:
```nc:prompt otp validate:^\d{6}$ when:signed_in=false
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:
```nc:run effect:external when:signed_in=false
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 — iAdd Atomic Chat MCP server so the container agent can call local models served by the Atomic Chat desktop app via its OpenAI-compatible API.
Use 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 OneCLI. Per-group via `ncl groups config update --provider codex`. Distinct from using OpenAI as an MCP tool (where Claude remains the planner).
Add a monitoring dashboard to NanoClaw. Installs @nanoco/nanoclaw-dashboard and a pusher that sends periodic JSON snapshots.
Add DeltaChat channel integration via @deltachat/stdio-rpc-server. Native adapter — no Chat SDK bridge. Email-based messaging with end-to-end encryption.
Add Discord bot channel integration via Chat SDK.
Add 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.
Add Google Calendar as an MCP tool (list calendars, list/search/create events, free/busy queries) using OneCLI-managed OAuth. Multi-calendar and multi-account supported. Mirrors /add-gmail-tool's stub pattern — no raw credentials ever reach the container; OneCLI injects real tokens at request time.
Add Google Chat channel integration via Chat SDK.