add-dial
Add 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.
git clone --depth 1 https://github.com/nanocoai/nanoclaw /tmp/add-dial && cp -r /tmp/add-dial/.claude/skills/add-dial ~/.claude/skills/add-dialSKILL.md
# Add Dial Channel
Adds [Dial](https://getdial.ai) — a real phone number for **SMS and AI voice
calls**. Native adapter (no Chat SDK bridge): both directions go through the
`dial` CLI — outbound via `dial message`, inbound via its command-target daemon. NanoClaw doesn't ship
channels in trunk — this skill copies the Dial adapter, its pairing helper, and
their tests in from the `channels` branch. The `pair-dial` setup step is
maintained in trunk, so it is not copied here.
The mechanical steps under **Apply** 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.
## Apply
### 1. Copy the adapter, pairing helper, and tests
Fetch the `channels` branch and copy the Dial adapter, its pairing store and
user-agent helper (each with its test), and the registration test into place
(overwrite — the branch is canonical):
```nc:copy from-branch:channels
src/channels/dial.ts
src/channels/dial-pairing.ts
src/channels/dial-pairing.test.ts
src/channels/dial-user-agent.ts
src/channels/dial-user-agent.test.ts
src/channels/dial-registration.test.ts
src/channels/dial-grant.test.ts
src/channels/dial-status.test.ts
```
The `dial-cli` container skill is deliberately **not** copied here.
`container/skills/` is mounted read-only into *every* agent container
(`src/container-runner.ts`), and a group with `skills:'all'` picks up whatever
it finds there — so shipping the skill with the adapter would hand it to agents
on installs that never configured Dial. It is installed only by
`/add-dial-tool`, offered under *Add phone superpowers* below, which is the
skill that actually provisions the CLI the skill documents.
`dial.ts` imports `dial-user-agent.js` at module scope, so omitting that helper
breaks the build and every test that loads the channel barrel.
### 2. Register the adapter
Append the self-registration import to the channel barrel (skipped if present).
This one line is the skill's only reach-in into the channel core:
```nc:append to:src/channels/index.ts
import './dial.js';
```
### 3. Register the pairing setup step
Add the `pair-dial` loader to the `STEPS` map in `setup/index.ts`, inside the
dormant marker region (skipped if already present — `pair-dial` ships in core, so
this idempotent-skips on a normal install, but is expressed for a clean-upstream
rebuild). The pairing handshake below spawns this step:
```nc:append to:setup/index.ts at:nanoclaw:setup-steps
'pair-dial': () => import('./pair-dial.js'),
```
### 4. Install the packages
Pinned to exact versions — the supply-chain policy rejects ranges and `latest`.
`qrcode` renders the scannable pairing card:
```nc:dep
qrcode@1.5.4
```
The adapter needs no Dial client library: it shells out to the `dial` CLI, which
this skill already requires for inbound. `@getdial/sdk` was dropped because it
depends on `pubnub`, which pulls react-native, Metro and Hermes into the
lockfile for what is a single send — and the CLI ships in lockstep with the Dial
API, so a contract change arrives as a CLI release rather than breaking a
request pinned in the adapter.
### 5. Build
Build first: it guards the adapter's typed core calls and proves the dependency
is installed.
```nc:run effect:build
pnpm run build
```
### 6. Validate
Then run the one integration test.
```nc:run effect:test
pnpm exec vitest run src/channels/dial-registration.test.ts
```
`dial-registration.test.ts` imports the real channel barrel and asserts the
registry contains `dial` — it goes red if the import line drifts. End-to-end
SMS/voice is verified manually once the service runs.
## Sign in to Dial
### Install the CLI
Dial's CLI owns the account credential (an auth file it writes on sign-in), so
the setup uses the `dial` CLI here. Ensure it's installed — this installs it if
it's missing (for the full onboarding/auth reference, see the `dial-cli` skill or
`curl -fsSL https://getdial.ai/skills.md`):
```nc:run effect:external
command -v dial || curl -fsSL https://getdial.ai/install | bash
```
### Identify this install
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) and prefix every `dial`
command below with it:
```nc:run capture:dial_ua validate:^nanoclaw/\S+$ effect:fetch
node -p "'nanoclaw/'+(require('./package.json').version||'unknown')" 2>/dev/null || echo nanoclaw/unknown
```
### Pin the CLI path
Now pin the CLI's **absolute** path into `.env`. The adapter shells out to `dial`
to register its inbound command target, and it runs inside the NanoClaw service,
which does not inherit your interactive shell's `PATH`. The CLI usually lands in
a version-manager bin directory (`~/.nvm/versions/node/*/bin`, `~/node/bin`, …)
that the service cannot see, so a bare `dial` fails with `ENOENT`, the command
target is never registered, and the channel comes up connected but deaf — no
inbound SMS or calls, with only a line in `logs/nanoclaw.error.log` to show for
it. `DIAL_CLI_PATH` removes the guesswork; `dial.ts` already prefers it:
```nc:run capture:dial_cli_path validate:^/.+ effect:fetch
command -v dial
```
```nc:env-set
DIAL_CLI_PATH={{dial_cli_path}}
```
### Check the sign-in
Check whether you're already signed in:
```nc:run capture:signed_in=.auth.signedIn validate:^(true|false)$ effect:fetch
DIAL_USER_AGENT={{dial_ua}} dial doctor --json
```
### Skip the reuse question when signed out
If you're **not** signed in, go straight to email verification — default the
choice so the branch guard below stays single-valued:
```nc:run capture:reuse_choice when:signed_in=falAdd 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.