본문으로 건너뛰기
cccskills
무료GitHub에 공개

add-dial-tool

Give selected NanoClaw agents access to Dial for sending SMS, making AI voice calls, and receiving verification codes. It installs the command-line tool and uses OneCLI to supply credentials and control which agents can use it.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

원문원문 설명 보기

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.

설치 방법 보기

이럴 때 유용합니다

  • Send SMS from a chat request
  • Ask an agent to place an AI voice call
  • Receive phone verification codes inside an agent container
  • Change which agents can use Dial

포함된 파일(6)

  • SKILL.md18.9 KB
  • apply-fixtures.json2.5 KB
  • container-skills/dial-cli/SKILL.md8.0 KB
  • REMOVE.md2.7 KB
  • scripts/dial-policy.test.ts22.5 KB
  • scripts/dial-policy.ts17.3 KB

SKILL.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, an OneCLI credential so in-container calls are injected keyless, and an OneCLI policy that names which agents may reach Dial. 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 is blocked by an OneCLI policy 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:

command -v onecli >/dev/null

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

This skill scopes Dial through the OneCLI policy API, which gateway 1.42 (the version NanoClaw pins) enforces. Older gateways do not enforce it, and 1.43 and later remove the agent secret-assignment command (onecli agents set-secrets) the selective-agent step still uses. Writing the Dial key first and then failing on the policy would leave every all-mode agent able to use Dial. So read the version of the gateway the onecli CLI talks to before anything is written, and stop unless it is 1.42. The Dial sign-up and credential steps below use the captured version, so they cannot run when this check fails:

U=$(onecli config get api-host | jq -r '.value // empty') && [ -n "$U" ] || { echo "could not read the onecli CLI's api-host, so the OneCLI gateway version cannot be checked. Nothing was written to OneCLI." >&2; exit 1; }; H=$(curl -fsS --max-time 10 "$U/api/health") || { echo "could not reach the OneCLI gateway at $U. Nothing was written to OneCLI." >&2; exit 1; }; V=$(printf '%s' "$H" | jq -er '.version') || V=; case "$V" in 1.42.[0-9]|1.42.[0-9][0-9]*) echo "$V"; exit 0;; 0.*|1.[0-9].*|1.[1-3][0-9].*|1.4[01].*) W="is older than 1.42 and does not enforce the OneCLI policy API this skill uses";; *) W="is newer than 1.42; OneCLI 1.43 and later remove the agent secret-assignment command (onecli agents set-secrets) this skill still uses";; esac; echo "OneCLI gateway version '${V:-unreadable}' at $U $W. Nothing was written to OneCLI. /add-dial-tool supports the pinned gateway, 1.42.0: see docs/onecli-upgrades.md to move the gateway to it." >&2; exit 1

If it fails, show the user the message as it is and stop. Do not work around it by turning off OneCLI policy enforcement or granting the Dial key by hand: the policy is what keeps agents you did not choose away from Dial.

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):

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):

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. The checked answer is captured, and every step that installs, signs in, or writes a credential below depends on the capture: a bad answer stops them all, nothing is written:

A=$(printf '%s' '{{dial_agents}}' | tr -d ' '); G=$(ncl groups list --json --limit 100000) || { echo "could not list agent groups — is the NanoClaw host running?" >&2; exit 1; }; for w in $(printf '%s' "$A" | tr ',' ' '); do case "$w" in all|none) ;; *) printf '%s' "$G" | 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; printf '%s\n' "$A"

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 the policy names agents, not groups — so create the missing ones now, exactly as the runtime would (secret mode all, nothing else touched):

G=$(ncl groups list --json --limit 100000) || { 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 policy

The one switch is an OneCLI policy rule, Dial: blocked agents, a block on api.getdial.ai whose identities are every agent you did not choose, kept by scripts/dial-policy.ts. It is a block and not an allow on purpose: OneCLI drops a deleted agent from a rule's identities, and a block left with none blocks every agent, where an allow left with none would open Dial to every agent. all means no rule at all. The pinned onecli CLI has no policy commands, so the script calls the policy API of the gateway the CLI is configured for, with the CLI's own key. On every run it creates the rule anew, moves it to the top of the order, deletes the earlier one and any Dial: blocked for <group> rule a legacy block was migrated to, publishes the policy, and reads the published generation back. The block sits first, so under first-match no operator allow can let a blocked agent through; the operator's own rules keep their relative order and are otherwise left alone. Agents another NanoClaw install on the same gateway blocked stay blocked. Publishing applies the whole policy draft, so finish or discard any edit left open in the OneCLI console first. The policy lands before any Dial sign-in or key write, and the credential step below depends on this capture, so the key can never exist without the block. The capture is ids and the word published only (names stay on stderr), since it is interpolated into that step. If this step fails, show the message and stop; do not write the Dial key by hand. A 403 blocked_by_policy in a container means "not chosen", not "broken":

pnpm exec tsx .claude/skills/add-dial-tool/scripts/dial-policy.ts scope --agents {{dial_scope}}

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:

: "OneCLI gateway {{onecli_gateway}}, Dial agents {{dial_scope}}"; 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):

: "OneCLI gateway {{onecli_gateway}}, Dial agents {{dial_scope}}"; 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:

: "OneCLI gateway {{onecli_gateway}}, Dial agents {{dial_scope}}"; 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. The step depends on the policy capture above, so the key is only written once the block is published. Selective-mode agents pick the new id up in the merge step below:

: "OneCLI gateway {{onecli_gateway}}, Dial agents {{dial_scope}}, policy {{dial_policy}}"; 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

Merge secrets for selective agents

Secret lists are left alone, with one exception. An agent in selective mode only gets the secrets on its list, so a chosen selective agent has the Dial secret merged into it. onecli agents set-secrets switches an agent to selective mode, so it is never called on an all-mode agent — that would silently cut the agent off from every credential not on its list. Blocked agents keep their lists untouched in either mode; the policy alone blocks:

A=$(printf '%s' '{{dial_scope}}' | tr -d ' '); case ",$A," in *,all,*) A=$(ncl groups list --json --limit 100000 | jq -r '[.data[].id] | join(",")');; esac; S=$(onecli secrets list | jq -r 'first(.data[] | select(.name | test("(?i)dial"))) | .id // empty'); [ -n "$S" ] || { echo "no Dial secret in the OneCLI vault — the credential step above did not complete" >&2; exit 1; }; onecli agents list | jq -r '.data[] | select(.secretMode=="selective") | "\(.id)\t\(.identifier)"' | while IFS="$(printf '\t')" read -r aid gid; do case ",$A," in *,"$gid",*) onecli agents set-secrets --id "$aid" --secret-ids "$(onecli agents secrets --id "$aid" | jq -r --arg s "$S" '[.data[], $s] | unique | join(",")')" >/dev/null || { echo "could not add the Dial secret to $gid" >&2; exit 1; }; echo "Dial secret added to the list of $gid";; esac; done

Hand the tool to running agents

container/skills/ is mounted read-only into every agent container, and each group's .claude-shared/skills/ holds symlinks into that mount that are synced when the container spawns — so nothing is copied per session. A running agent keeps its old image until it respawns, so restart every group; without a --message each one comes back on its next message, on the new image, with the CLI on PATH and the skill in place. This is a restart effect, so it does not fire after an earlier step bounced — agents keep the image they have until the gap above is fixed and the skill is re-applied:

ncl groups list --json --limit 100000 | jq -r '.data[].id' | while read -r gid; do ncl groups restart --id "$gid" >/dev/null || { echo "could not restart $gid" >&2; exit 1; }; done

Done

The chosen agents can now use Dial from inside their containers; the others are blocked at the gateway. Auth is injected by OneCLI; a 403 blocked_by_policy means the agent was not chosen (run /add-dial-tool again to change that); a 401 means the Dial secret needs (re)connecting — not a login. The policy is visible in the OneCLI console under Policy (Dial: blocked agents). Verify from a chat with a chosen agent: "run dial doctor" or "text +1… hi".

To uninstall: see REMOVE.md. To wire Dial as a messaging channel too, run /add-dial.

Troubleshooting

command -v onecli fails. OneCLI is not installed or not on PATH. Run /init-onecli, then re-run this skill.

ncl can't reach the host. The agent list and the scoping steps talk to the running NanoClaw service. Start it (pnpm run dev, or restart the service) and re-run.

unknown agent group. An id in your answer is not in ncl groups list. Copy the ag-… id exactly; names are not accepted.

no Dial API key in the host auth file. The sign-in did not complete. Run dial auth login <email> --force, then dial auth verify-otp --code <code>, and re-run.

A chosen agent gets 401. The vault secret is stale (a different account's key, or a rotated one). Re-run this skill — it always rewrites the secret with the key the host is signed in with.

An agent you left out can still use Dial. It was created after the last run (a new OneCLI agent starts in all mode and is not in the block). Re-run this skill; it only touches its own policy rule.

no published policy yet. The gateway's 1.42 migration of this project did not publish a policy, so it still runs on legacy rules; publishing now would pre-empt that migration. Check the gateway log for policy-oss-cutover.

A blocked agent still reaches Dial on gateway 1.42. Two causes. A rule was moved above Dial: blocked agents in the OneCLI console: re-run this skill, which moves the block back to the top. Or the gateway runs with the operator kill switch POLICY_ENFORCE_V2=0 in its environment, which makes it ignore the published policy; nothing in the API shows it. Check the gateway's compose environment and unset it.

dial: command not found inside a container. The image predates the manifest entry. Run ./container/build.sh, then ncl groups restart --id <group-id> so the agent respawns on it.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Equip NanoClaw agents to convert attached office documents into Markdown locally using Firecrawl AnyDoc. Agents can read Word, PowerPoint, Excel, and text-based PDF files without uploading them to an external parsing service.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Reading and summarizing attached office documents
  • Converting documents without uploading them to an external service
  • Extracting text from text-based PDFs
nanocoai/nanoclaw3.1만2026년 10월 18일 업데이트

Let a NanoClaw container agent call local models running in Atomic Chat. The skill adds an MCP server, a connection that exposes model listing and text generation as tools, while Claude continues to coordinate the work.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Delegate part of Claude's work to a local model
  • List models available in Atomic Chat
  • Generate an answer with a specified local model
nanocoai/nanoclaw3.1만2026년 10월 18일 업데이트

Add a read-only web dashboard for browsing NanoClaw agents, sessions, and other resources. It builds tables from command-line JSON output and includes message activity charts, recent logs, and a viewer for skills, profiles, and CLAUDE.md files.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Check agent and session status in a browser
  • Review message counts and daily activity
  • Read recent host logs
nanocoai/nanoclaw3.1만2026년 10월 18일 업데이트

add-codex

무료

Add Codex as the agent that plans tasks and coordinates tools in NanoClaw. Choose it for individual groups alongside Claude, with conversation history and session resume using a ChatGPT subscription or OpenAI API key.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Adding Codex as a NanoClaw agent provider
  • Choosing Claude or Codex for each group
  • Setting up ChatGPT or API key authentication
nanocoai/nanoclaw3.1만2026년 10월 18일 업데이트

Install a local NanoClaw dashboard to inspect agents, sessions, channels, messages, and users. It displays token usage, context usage, activity, and live logs using periodic data snapshots and a log forwarding module.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Checking agent and session status in one place
  • Reviewing token and context usage
  • Following live logs with level filters
nanocoai/nanoclaw3.1만2026년 10월 18일 업데이트

Connect NanoClaw agents to DeltaChat for email-based messaging with end-to-end encryption. Set up direct chats or small group conversations, exchange file attachments, and link each chat to the appropriate agent group.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Chatting with a personal assistant through DeltaChat
  • Using an agent in a small DeltaChat group
  • Exchanging files with an agent in chat
nanocoai/nanoclaw3.1만2026년 10월 18일 업데이트

nanocoai의 스킬 모두 보기

이 스킬의 문제 신고