⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

version unknown

call-me

@radres⭐ 35 stars

Call or text your human's actual phone (iPhone or Android) and handle replies from the /call-me app. Use when the user says "call me", "ring me", "phone me if something comes up", "text my phone", asks the agent to stay reachable, wants something read out loud (an update, a summary of the day ahead), or sends an inbound /call-me message or voicemail. The phone rings through CallKit, spoken answers return as transcripts, and channel-enabled hosts deliver later messages directly into the active agent session.

Choose how to use this skill

You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.

1. Native installer

This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.

Do not guess an installer command or replace an existing version without reviewing the diff.

2. Complete package recommended

Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.

No complete ProSkills package is published for this listing yet.

3. Prompt-only

Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.

Need only the instruction file?

Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.

No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.

—/10

// RATINGS

⭐GitHub Stars
⭐⭐ 35 on GitHubGitHub ↗

Growing

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// README

callme — your AI can call you

Your AI agents ring your actual phone, iPhone or Android, read you a question, an update or your day ahead, and get your spoken reply back as text — or just text you. Works from any AI that speaks MCP; nothing to install.

Call Me is not a voice assistant: your agent does all the thinking. Call Me carries its words to your phone and your answer back, once or, with keep_open, back and forth on the same call, so you can keep it working while you drive, train or walk. Calls speak and understand 90+ languages.

1. Get the app

callme on the App Store for iPhone, or on Google Play for Android. No registration, no email. Open it, tap Agree & Continue, and it shows your personal callme number: 10 digits, and all an agent needs to reach you.

2. Connect your AI (MCP)

Pairing is stored locally in ~/.aiphone/config.json. An MCP server running on this computer can read that file automatically; the hosted HTTP endpoint cannot read files on your computer and therefore needs the number supplied separately.

One server, every MCP client — no API key, no OAuth:

https://callmemcp.com/mcp

Claude Code

claude mcp add --transport http call-me https://callmemcp.com/mcp

opencode — add to opencode.json (or ~/.config/opencode/opencode.json):

{
  "mcp": {
    "call-me": {
      "type": "remote",
      "url": "https://callmemcp.com/mcp",
      "enabled": true
    }
  }
}

Codex CLI — use the local paired MCP server

cd /absolute/path/to/call-me
codex mcp remove call-me  # only if the hosted entry is already configured
codex mcp add callme -- node "$PWD/codex/mcp.mjs"
codex mcp list

The local callme MCP server reads ~/.aiphone/config.json for every call and text, so no number needs to be typed into the Codex conversation. Use its identity tool to verify the paired phone.

Gemini CLI

gemini extensions install https://github.com/radres/call-me

or, for the MCP server alone:

gemini mcp add --transport http call-me https://callmemcp.com/mcp

Grok

In the Grok app: grok.com/connectors → New Connector → Custom → https://callmemcp.com/mcp. In Grok Build:

grok plugin install radres/call-me#grok

Cline — add to Cline's MCP settings (MCP Servers → Configure; CLI: ~/.cline/data/settings/cline_mcp_settings.json). Full agent-readable steps are in llms-install.md.

{
  "mcpServers": {
    "call-me": {
      "type": "streamableHttp",
      "url": "https://callmemcp.com/mcp",
      "timeout": 120
    }
  }
}

Cursor — add to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "call-me": { "url": "https://callmemcp.com/mcp" }
  }
}

Or install the plugin in plugin/, which adds a skill telling the agent when to call and when to text. It is a standard Agent Plugin with a Cursor manifest, so any client that loads Agent Plugins can use it too.

ChatGPT — Settings → Apps → Advanced → Developer mode → add a connector with the URL above (no authentication).

claude.ai — Settings → Connectors → Add custom connector → the URL above.

Anything else that speaks MCP — add a streamable-HTTP server pointing at the URL above, then provide the recipient number through that client's pairing or configuration flow. A hosted MCP connection cannot see this computer's ~/.aiphone/config.json.

For the local Codex/Claude integrations, pairing once writes the shared local config and the MCP tools use it automatically. For hosted clients, tell the AI the number or use that client's explicit pairing flow; never guess it.

ToolWhat it does
callRings your phone, reads your text aloud (a question, an update, a summary of the day ahead), returns your spoken reply as text. Holds ~30s, then hands the AI a call_id to poll. keep_open: true keeps the line up after your answer for say; language ("tr", "de", "pt-BR"...) is the language the AI speaks
sayNext line in a keep_open call; returns your next answer. While the AI thinks you hear a soft keyboard sound, and anything you say meanwhile comes back as said_while_waiting
hang_upEnds a keep_open call, with an optional goodbye
poll_resultFinishes a call that was still ringing
textPush-notification message, no ring
wait_for_replyDelivers your replies and voicemails back to the AI
set_thread_titleNames the conversation thread on your phone

Repeat calls from the same AI land in one titled thread on your phone; each agent/session gets its own number and thread. Blocking a thread in the app silences that sender for good.

3. Or just curl it

No MCP client, no plugin, no token. Anything that can POST JSON can ring your phone, and these two endpoints are the whole product.

Ring and wait for the spoken answer (blocks until you answer or it gives up):

curl -sS https://callmemcp.com/ring \
  -H 'content-type: application/json' \
  -d '{"to":"<YOUR_10_DIGITS>","text":"Deploy to prod?","from":"Claude on my laptop"}'
{"status":"completed","transcript":"Yes, ship it",
 "session_token":"curl_1a2b...","from":"<CALLER_NUMBER>"}

status is one of completed, missed, declined, timeout, failed. transcript is what you said, already turned into text.

Keep the line open for a back-and-forth: add "keep_open":true to /ring. The answer comes back with "open":true and a call_id. Speak again on the same call and wait for the next answer, until you hang up (or the phone does):

curl -sS https://callmemcp.com/aiphone/calls/<call_id>/say \
  -H 'content-type: application/json' \
  -d '{"session_token":"curl_...","text":"Reverted it. Anything else?"}'

curl -sS https://callmemcp.com/aiphone/calls/<call_id>/hangup \
  -H 'content-type: application/json' \
  -d '{"session_token":"curl_...","text":"Thanks, talk later."}'

A conversation hangs up after 2 minutes of silence, 15 minutes in total or 60 turns.

Text only, no ring:

curl -sS https://callmemcp.com/text \
  -H 'content-type: application/json' \
  -d '{"to":"<YOUR_10_DIGITS>","body":"Migration finished.","from":"Claude on my laptop"}'

Returns {"ok":true,"message_id":42,"delivered":true}. A delivered:false means the text is in your history but no notification was shown (notifications are off for /call-me on that phone); the response spells that out in notice.

Field
toyour 10-digit /call-me number. Required
text / bodywhat to say / send. Up to 2000 chars for either; a call reads that out in about two minutes
fromsender name shown on your phone. Defaults to "Someone's AI"
timeout_s/ring only: seconds to wait, 30 to 300. Default 300
keep_open/ring only: keep the line up after the answer for say. Default false
language/ring and say: the language you write text in, e.g. "tr". Default English

Anything else

Both responses hand back a session_token, and that is a real session: the same caller can use the rest of the API with it, all landing in one thread on your phone.

// HOW IT'S BUILT

// FILES

5 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.

  • skill/SKILL.md18 KB5e2db4471b5dupstream ↗
    ---
    name: call-me
    description: Call or text your human's actual phone (iPhone or Android) and handle replies from the /call-me app. Use when the user says "call me", "ring me", "phone me if something comes up", "text my phone", asks the agent to stay reachable, wants something read out loud (an update, a summary of the day ahead), or sends an inbound /call-me message or voicemail. The phone rings through CallKit, spoken answers return as transcripts, and channel-enabled hosts deliver later messages directly into the active agent session.
    ---
    
    # /call-me — call your human
    
    Use english always, test to speech always expects english
    
    Your human's iPhone or Android phone runs the /call-me app. It gave them a **user number**
    (10 digits, e.g. `584-158-6160`) — that's all you need to ring their actual
    phone and get a spoken answer.
    
    ## First setup and saved pairing
    
    When pairing is missing, always show a clickable link:
    [Download Call Me from the App Store](https://apps.apple.com/app/call-me/id6789575165)
    on an iPhone, or [get it on Google Play](https://play.google.com/store/apps/details?id=com.serdaroztetik.callmeai) on Android.
    Tell the user to open **My Number** in the app. Never show only the app name or
    substitute the demo number.
    
    The number is all that is required. Do not add OAuth, login, or a verification
    code. On a local host, read `~/.aiphone/config.json` before asking for the
    number. `callme pair <number>` saves it with owner-only permissions, outside
    the installed plugin so upgrades preserve it. Pass the saved `user_number`
    when using the remote MCP tools.
    
    Check pairing before registering a sender session or making a network request.
    An unpaired client must show the download link even when the server is offline.
    If a tool returns `setup_required`, include its download link and instructions.
    On hosts without local file access, reuse the number supplied in the current
    chat, but do not claim it has been permanently saved across chats.
    
    Prefer the host's /call-me tools when available. Otherwise use the `callme`
    CLI from this skill directory or `PATH` (`aiphone` is a legacy alias).
    
    If the host did not expose this skill or the CLI is unavailable, **do not stop**:
    the production API has a tokenless curl-first endpoint. Read the paired number
    from `~/.aiphone/config.json` (never hardcode a remembered number) and call it
    directly:
    
    ```sh
    CALLME_TO=$(jq -r .user_number ~/.aiphone/config.json)
    curl -sS https://callmemcp.com/ring \
      -H 'content-type: application/json' \
      -d "$(jq -nc --arg to "$CALLME_TO" --arg text "Should I deploy to prod?" \
        --arg from "Codex" '{to:$to,text:$text,from:$from}')"
    ```
    
    `POST /ring` blocks until the spoken answer returns. It needs no token, plugin,
    or registration; the 10-digit recipient number is the address. Use
    `POST /text` with `body` instead of `text` for a non-blocking notification.
    
    **You must call or text BEFORE ending a turn with an open question** — once
    your turn ends you are asleep and cannot reach out. Parking it in an interactive
    question widget does not count: that waits silently on a screen they may not be
    looking at, and no hook can see it.
    
    **On a Claude Code host, install the plugin instead of relying on this CLI.**
    It adds the `call`/`text`/`reply` tools, delivers their phone replies back into a
    live session, and installs a `Stop` hook that catches you ending a turn on a
    question — none of which this CLI can do. You can install it yourself with Bash;
    slash commands are only for a human at a prompt:
    
    ```sh
    claude plugin marketplace add radres/call-me
    claude plugin install call-me@call-me
    claude plugin enable call-me@call-me   # REQUIRED — install leaves it DISABLED
    claude plugin list                     # expect: call-me@call-me   ✔ enabled
    ```
    
    Do not drop the `enable` line. `install` writes `"call-me@call-me": false` into
    `~/.claude/settings.json` and says so in its output; a disabled plugin exposes
    no tools at all, so skipping it looks exactly like a successful install and then
    silently never works.
    
    Then tell them to **restart Claude Code** (`/reload-plugins` is not yours to run,
    and the monitor needs a fresh session). Pairing carries over either way — it is
    the same `~/.aiphone/config.json`.
    
    ### Codex import troubleshooting
    
    Codex can import the Claude plugin, but some builds leave
    `${CLAUDE_PLUGIN_ROOT}` unresolved in the plugin MCP command. The symptom is
    `MCP client for callme failed to start` with the connection closing during the
    initialize response, while `codex mcp get callme` still shows the literal
    placeholder.
    
    Shadow the plugin-provided entry with a user-level entry that points at the
    installed bundle:
    
    ```sh
    CALLME_CODEX_ROOT=$(find "${CODEX_HOME:-$HOME/.codex}/plugins/cache/call-me/call-me" \
      -mindepth 1 -maxdepth 1 -type d -print | head -1)
    codex mcp add callme \
      --env "CALLME_PLUGIN_ROOT=$CALLME_CODEX_ROOT" \
      -- node "$CALLME_CODEX_ROOT/dist/channel.mjs"
    ```
    
    Start a fresh Codex session and verify that all MCP servers finish loading.
    Recreate the override after a plugin upgrade if the cached version path changes.
    
    ## Is the human set up yet?
    
    The paired number lives in `~/.aiphone/config.json`. `callme number` prints it;
    `call`/`text` use it automatically, so you never need to type a number.
    
    If nothing is paired (`callme call` exits 5, or a channel tool says "not
    paired"), onboard them:
    
    ```sh
    callme setup      # steps + scannable QR + link — paste the whole output back
    callme qr         # just the App Store QR code, if that's all you need
    ```
    
    Always show them **both** the QR code and the link — relay `callme setup`'s
    output verbatim, code block and all, rather than summarising it. They are on a
    phone; a code to point the camera at beats retyping a URL. Both commands print
    the code whether or not stdout is a terminal, so piping it through a tool is
    fine.
    
    1. They install [Call Me from the App Store](https://apps.apple.com/app/call-me/id6789575165)
       (iPhone) or [Google Play](https://play.google.com/store/apps/details?id=com.serdaroztetik.callmeai) (Android).
    2. They open *My Number* in the app to find their 10-digit number.
    3. They read the number back to you.
    4. Tell them their phone is about to ring, then run `callme pair <number>` (or the
       channel's `pair` tool). It stores the number and **rings the phone**, blocking
       until they speak, then prints the transcript — one answered call proves push
       delivery, CallKit, audio both ways and transcription at once. If nobody picks
       up within 90s it falls back to a confirmation text and tells you so.
    
    **Never guess or invent a number.** It's a credential, and a wrong one now *rings*
    a stranger rather than texting one. Ask, or run `callme setup`.
    
    ### Onboarding is not finished when pairing succeeds
    
    Pairing makes you *able* to reach them. It does not make you *do* it — and a
    human who set up /call-me and then never hears from you got nothing out of it.
    The most common outcome of a half-finished setup is: you park a question in your
    final message, the turn ends, you go to sleep, and they find out hours later
    that you were waiting. Close that gap in the same session you pair, in order:
    
    1. **Ask how often they're away.** "While I work, are you away from the keyboard
       rarely, sometimes, or usually?" One question, and it decides the rest.
    2. **If sometimes or usually, turn on the standing reminder:**
       ```sh
       callme remind on
       ```
       On Claude Code the plugin ships a `Stop` hook that catches a question you
       parked in your final message (the `wait_for_answer` tool is the deliberate
       version of the same thing). It does not nudge you at that instant — it
       starts a grace period (2 min by default) in case the human is right there and
       simply types the answer. Only a window that closes unanswered wakes you back
       up to reach for the phone. By default it only fires in sessions that already
       used /call-me — so a session that never thinks to mention it stays silent,
       which is precisely the session where they miss you asking. `remind on` makes
       it fire in every session. (`callme remind off` / `status` to change or check
       it later; `callme grace <seconds>` changes the window, `0` = no wait.)
    3. **Write the standing rule into their instruction file** — `~/.claude/CLAUDE.md`
       for Claude Code, `AGENTS.md` for Codex and others. **Read it first and append;
       never overwrite.** This is what carries the behaviour into sessions that have
       no hook at all:
       ```markdown
       ## Reaching me
       I'm reachable on my phone through /call-me. When you need a decision, an
       answer, or my input, contact me rather than ending your turn with the
       question in your final message — once the turn ends you are asleep and
       cannot reach me. Text first; call when it is blocking or time-sensitive.
       Never hardcode my number: `callme number` reads it.
       ```
       Add one line matching their answer from step 1 — "I'm mostly AFK, assume I
       won't see the terminal" reads very differently from "I'm usually at the
       keyboard, only reach out if I'll be gone a while."
    4. **Verify, don't assume.** If the pairing call was answered you are already
       done — that transcript *is* the proof. Only if it went unanswered:
       ```sh
       callme text "Setup done — this is what a message from your agent looks like."
       ```
       Ask whether it arrived. If nothing lands either way the number is wrong:
       re-pair. Don't retry blindly.
    5. **Tell them how to undo or tune it**: `callme remind off`, `callme grace 300`
       to be left alone at the keyboard longer before the phone gets involved (`0`
       rings the moment a turn ends), and blocking the thread in the app mutes it
       without touching any config.
    
    ## Inbound messages
    
    Treat an authenticated /call-me channel event as a new user message in the
    current session. Continue the relevant work and use the channel's reply tool
    when a response belongs in the phone conversation.
    
    - Claude Code with the /call-me channel receives messages automatically.
    - Codex needs the /call-me companion/App Server bridge for automatic delivery.
    - Without either integration, use the manual `callme listen` fallback below.
    
    ## CLI setup (when no channel is present)
    
    ```sh
    callme register "claude: <project> — <task>"   # label shows as caller ID
    ```
    
    Stores *your own* session number + secret token in `~/.aiphone/session.json`
    (separate from the human's paired number). Auto-runs on first use if you skip
    it, but a descriptive label is much better for the human.
    
    ## Ask for input — text first, call if no answer (THE main move)
    
    Default to messaging. Only escalate to a call when a few minutes pass with
    no reply, or the answer is genuinely time-sensitive and blocking.
    
    1. Send the question non-blocking:
       ```sh
       callme text "I can fix the flaky test two ways: skip it or rewrite the fixture. Which do you want?"
       ```
    2. Don't sit blocked in the terminal. Keep doing any work that doesn't
       depend on the answer while you wait.
    3. To actively wait for the reply, use `callme listen` via Bash with
       `run_in_background: true` (or rely on the channel's automatic delivery
       on hosts that have it) — do not sleep-poll.
    4. If a few minutes pass with no reply, escalate to a real call with the
       same question:
       ```sh
       callme call "I can fix the flaky test two ways: skip it or rewrite the fixture. Which do you want?"
       ```
       - Phone rings with the native call UI; your label is the caller name.
       - Your text is read aloud word for word; the human answers by voice; you
         get `ANSWER: <transcript>` on stdout. The command BLOCKS until then —
         that's the point (you can't proceed without the answer).
       - Exit 3 = missed/declined/timeout. Fall back to your best judgment and
         say so in a follow-up text.
       - Up to 2000 characters, about two minutes of speech. For a decision,
         make it self-contained and include the options.
    5. If a call is declined, do NOT retry the call — send a text instead.
    
    ## Read something out — not every call is a question
    
    `callme call` speaks whatever you pass it, so it also works for things the
    human would rather hear than read: a summary of the day ahead, an
    end-of-run report, a status update while they are driving. Same 2000-character
    limit. They can reply after it or just hang up; an empty `ANSWER:` means they
    only listened.
    
    ## Conversations — keep the line open
    
    In the Claude Code plugin and over MCP the line stays open after the first
    answer by default; pass `keep_open: false` for a one-shot call. Over REST and
    the `callme` CLI a call still hangs up after one answer unless you ask:
    
    - Claude Code plugin / MCP: `call`. The result carries `call_id` and
      `open: true`. Speak the next line with `say` (`call_id`, `text`), which
      returns their next answer. End with `hang_up` (`call_id`, optional goodbye
      `text`).
    - REST: `POST /calls` with `"keep_open": true`, then
      `POST /calls/{id}/say {session_token, text}` and
      `POST /calls/{id}/hangup {session_token, text}`.
    
    While you think, the human hears a hold sound, and if they talk meanwhile a
    short holding reply covers the gap; what they said comes back with the next
    answer as `said_while_waiting`. `open: false` means the call ended (they hung
    up, 120 s without a line from you, or the 15-minute cap). Pass `language`
    ("tr") on `call` or `say` when you switch languages. Always `hang_up` when you
    are done, or the line idles until the timer drops it.
    
    ### If you text and keep working, park a check-back
    
    The failure mode here is silent: you text a question, work on something else,
    finish that work, and end your turn — and the unanswered question is simply
    lost, because ending the turn puts you to sleep. Before you end a turn with a
    question still outstanding, do one of:
    
    - **Arm a wait and end the turn** — on Claude Code, the `wait_for_answer` tool.
      You pass the question and how long the answer is worth waiting for; the human
      gets that window to type it in the terminal, and if they stay silent you are
      woken back up to phone them. This is the right default whenever they might
      simply be at the keyboard: it costs nothing if they are (typing cancels it),
      and it does not burn a call on a question they were about to answer anyway.
      Pick the window from what you know — a minute or two if they were just here,
      ten-plus for an unattended run. Then **end your turn**; keeping it alive is
      what denies them the chance to answer.
    - **Escalate to `callme call`** if the answer is actually blocking. It blocks
      until they speak, so the answer cannot go missing.
    - **Schedule a check-back** if it can wait: a one-shot scheduled prompt (on
      Claude Code, `CronCreate` with `recurring: false`; other hosts may expose a
      wakeup tool) that re-enters the session later to see whether they replied.
      Note the limits before you rely on it — these schedules live only inside the
      current session, and recurring ones expire after 7 days.
    - **Say plainly that you are stopping** with the question open, so the human
      learns about it from your final message rather than from silence.
    
    On Claude Code the plugin also ships a `Stop` hook that catches this even when
    you forget: if your final message *looks* like a parked question, it arms the
    same grace period (2 min by default) and then wakes you to reach out. Treat that
    as a safety net, not the plan — it is a guess about your prose, it is suppressed
    by debounce, and its instant-reminder fallback can be dropped on some turn-ending
    paths. `wait_for_answer` is the plan, because you know what you parked and the
    hook only pattern-matches; an explicit wait also overrides the hook entirely, so
    the two never fight. When either one wakes you, the human is genuinely away:
    reach the phone, don't re-ask in the terminal they are not looking at.
    
    ## Text — non-blocking notification
    
    Use when nothing is needed back from the human (status update, FYI):
    
    ```sh
    callme text "Build green, PR #142 merged. Nothing needed from you."
    ```
    
    Treat a non-2xx response as not delivered. If `callme text` returns HTTP 404,
    the saved sender session may be stale; the tokenless `POST /text` fallback is
    still allowed, but if it also answers 404 (the number is not a /call-me number),
    stop and report that the saved pairing is invalid. Never call or text a `from`
    number: it is an agent's identity, not a person, and five wrong numbers in an
    hour block the caller for an hour. Never hardcode a number, substitute another local
    record, retry blindly, or escalate to a call when the user requested SMS only.
    
    ## Title the thread
    
    The phone shows your session as a conversation thread. Once the topic is
    clear (and when it shifts), set a short 3-5 word title so the human can tell
    threads apart — the `set_title` tool on channel-enabled hosts, otherwise:
    
    ```sh
    callme title "flaky test fix"
    ```
    
    > **Known issue (verified 2026-08-27, serdar's Mac).** The `set_title` MCP tool
    > can reject *every* label with `422 "that label impersonates a company or
    > authority — pick a label that describes your agent instead"`. Reproduced with
    > four unrelated labels ("MCP-792 verified, deploy?", "Linear ticket
    > verification", "Claude — ticket verifier", "ticket verifier agent"), so the
    > guard is over-firing, not reacting to the wording. Don't burn turns retrying
    > variants — skip the title and place the call; `call` itself works fine.
    >
    > Never put digits the human must read back (IBAN, account number, one-time
    > code, long URL) in the spoken text. Send them with `text` first, then call
    > and point at the text; speech transcription mangles them.
    
    ## Manual listen fallback
    
    `callme listen` long-polls and exits when an event arrives (user text,
    voicemail transcript, missed-call notice). Use only when the host has no
    /call-me channel or companion bridge:
    
    1. Run `callme listen` via Bash with `run_in_background: true`.
    2. Continue other work; when the human texts/calls, the background task
       completes and you get the notification with the event on stdout.
    3. Re-arm by launching it again if you still expect input.
    
    Voicemail: the human can dial YOUR session number in their app and speak a
    message; it arrives as a `voicemail` event with the transcript.
    
    `callme events` drains pending events without waiting.
    
    ## Etiquette (important)
    
    - **Calls interrupt a human's life; texts don't.** Always message first —
      reserve calls for when a text goes unanswered for a few minutes, or the
      human explicitly asked to be called.
    - Batch questions: one message with a compound question beats three.
    - Late-night: prefer text unless they said otherwise.
    - Never call numbers you weren't given. The number is a credential — use the
      paired one (no number argument) rather than typing digits.
    - If a call is declined, do NOT retry the call; text instead.
    
  • skill/agents/openai.yaml184 Bb7a0fa3726acupstream ↗
    interface:
      display_name: "/call-me"
      short_description: "Call and message your human from an AI task"
      default_prompt: "Use $callme to contact me on my phone and handle my reply."
    
  • skill/aiphone6 B2c083c36bf52upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • skill/callme23 KB0e16976632abupstream ↗
    #!/usr/bin/env bash
    # callme — call/text your human's phone from an AI session. bash + curl only.
    set -euo pipefail
    umask 077   # session tokens and the paired number are credentials
    
    API="${AIPHONE_API:-https://callmemcp.com/aiphone}"
    CONF_DIR="${AIPHONE_HOME:-${AIPHONE_STATE_DIR:-$HOME/.aiphone}}"
    CONF="$CONF_DIR/session.json"
    USER_CONF="$CONF_DIR/config.json"
    
    usage() {
      cat <<'EOF'
    callme — your AI can call its human
    
      callme setup                   what to tell a human who hasn't set up the app
      callme qr [--big|--mono]       App Store QR code to scan with the phone camera
      callme pair <number>           remember the number AND ring the phone to prove it
      callme number                  show the paired number
      callme remind [on|off|status]  remind EVERY session to reach out before it ends
                                      a turn on an open question (for AFK humans)
      callme grace [seconds|status]   how long to wait at the keyboard for an answer
                                      before the phone gets involved (default 120, 0 = at once)
      callme channel [on|off|status]  deliver inbound messages by MCP channel push
                                      instead of the inbox monitor (needs --channels)
      callme call "text"             RING the phone, speak the text, BLOCK until the
                                      spoken answer comes back (prints transcript)
      callme text "text"             send a text (push notification on the phone)
      callme listen                  long-poll; EXITS when an event arrives (voicemail,
                                      text, missed call). Run with run_in_background.
      callme events                  print any pending events without waiting
      callme title "text"            rename this session's thread on the phone
      callme whoami                  show this session's number/label
      callme register [label]        get a session number+token (usually automatic)
      callme status                  server health
    
      call/text take an explicit recipient too: callme call <number> "text"
      env: AIPHONE_TIMEOUT (call wait, default 300s), CALLME_USER_NUMBER (override)
    EOF
      exit 1
    }
    
    need_jq() { command -v jq >/dev/null || { echo "callme needs jq (https://jqlang.github.io/jq/download/)" >&2; exit 2; }; }
    
    conf() { jq -r ".$1" "$CONF"; }
    
    # --- the paired human's number: one home, ~/.aiphone/config.json ------------
    
    norm() { printf '%s' "${1:-}" | tr -cd '0-9'; }
    valid_number() { [ "${#1}" -eq 10 ]; }
    
    display() {  # 584-158-6160, the grouping the iPhone app shows
      local n; n=$(norm "${1:-}")
      if valid_number "$n"; then printf '%s-%s-%s' "${n:0:3}" "${n:3:3}" "${n:6:4}"; else printf '%s' "$n"; fi
    }
    
    resolve_number() {
      local n
      n=$(norm "${CALLME_USER_NUMBER:-}");            valid_number "$n" && { printf '%s' "$n"; return; }
      if [ -f "$USER_CONF" ] && command -v jq >/dev/null; then
        n=$(norm "$(jq -r '.user_number // ""' "$USER_CONF" 2>/dev/null)")
        valid_number "$n" && { printf '%s' "$n"; return; }
      fi
      n=$(norm "${AIPHONE_USER_NUMBER:-}");           valid_number "$n" && { printf '%s' "$n"; return; }
      printf ''
    }
    
    require_number() {
      local n; n=$(resolve_number)
      if ! valid_number "$n"; then
        cat >&2 <<'EOF'
    callme: no phone paired yet.
    
    Download Call Me from the App Store: https://apps.apple.com/app/call-me/id6789575165
    On Android, get it on Google Play: https://play.google.com/store/apps/details?id=com.serdaroztetik.callmeai
    
    If they don't have the app yet, show them this — camera at the code:
    EOF
        echo >&2
        do_qr >&2
        cat >&2 <<'EOF'
    
    Then: open My Number in the app, read back the 10-digit number, and run
      callme pair <number>
    
    Never guess a number — it is a credential, and a wrong one rings a stranger.
    EOF
        exit 5
      fi
      printf '%s' "$n"
    }
    
    do_pair() {
      need_jq
      local n; n=$(norm "${1:-}")
      valid_number "$n" || { echo "callme pair needs the 10-digit number from the app (got '${1:-}')" >&2; exit 6; }
      mkdir -p "$CONF_DIR"; chmod 700 "$CONF_DIR" 2>/dev/null || true
      local prev='{}'; [ -f "$USER_CONF" ] && prev=$(cat "$USER_CONF")
      printf '%s' "$prev" | jq --arg n "$n" --arg d "$(display "$n")" --arg t "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
        '. + {version:1, user_number:$n, display:$d, source:"cli-pair", updated_at:$t} | del(.api)' \
        > "$USER_CONF.tmp" && mv "$USER_CONF.tmp" "$USER_CONF"
      chmod 600 "$USER_CONF"
      echo "paired with $(display "$n") (stored in $USER_CONF)"
    
      # Ring, don't text. A voice answer proves the entire loop in one shot — push
      # delivery, CallKit, audio both ways, transcription — and it is the moment the
      # product sells itself. The text is only the fallback.
      echo "ringing your phone now — answer it and say anything back"
      local rc=0
      # do_call `exit`s on no-answer, which under `set -e` would kill this script:
      # run it in a subshell so we can catch the code and fall back. Uncaptured, so
      # the transcript streams live.
      (
        AIPHONE_TIMEOUT="${AIPHONE_PAIR_TIMEOUT:-90}"
        do_call "$n" "You're paired with your AI assistant. Say anything back so I know I can hear you."
      ) || rc=$?
      if [ "$rc" -eq 0 ]; then
        echo "pairing confirmed by voice — I can ring you, and you can answer."
        return 0
      fi
    
      # No answer, or the call failed outright. Never redial — a declined call means stop.
      local why
      if why=$(do_text "$n" "Paired ✅ — this AI session can now text and call you." 2>&1); then
        echo "sent a confirmation text instead. Ask whether it arrived; if it didn't, the"
        echo "number is wrong — re-pair with the right one rather than retrying."
        return 0
      fi
      echo "callme pair: stored $(display "$n"), but NOTHING reached the phone — neither the" >&2
      echo "call nor a text:" >&2
      echo "  ${why:-no detail from the server}" >&2
      echo "Either the number is wrong (ask them to read it off the app home screen again)" >&2
      echo "or /call-me notifications are off on that phone. Do not report setup as finished." >&2
      exit 7
    }
    
    do_unpair() {
      rm -f "$USER_CONF"
      echo "unpaired (removed $USER_CONF)"
    }
    
    # Standing reminder: on Claude Code the plugin's Stop hook normally only nudges
    # sessions that already used /call-me. A human who is usually away wants the
    # nudge in EVERY session, including ones that would never mention /call-me.
    do_remind() {
      need_jq
      case "${1:-status}" in
        status)
          if [ -f "$USER_CONF" ] && [ "$(jq -r '.always_remind // false' "$USER_CONF")" = "true" ]; then
            echo "on — every session is reminded before it ends a turn on an open question"
          else
            echo "off — only sessions that use /call-me are reminded (default)"
          fi
          ;;
        on|off)
          [ -f "$USER_CONF" ] || { echo "pair a phone first: callme pair <number>" >&2; exit 6; }
          local want=false; [ "$1" = "on" ] && want=true
          jq --argjson v "$want" --arg t "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
            '. + {always_remind:$v, updated_at:$t}' "$USER_CONF" \
            > "$USER_CONF.tmp" && mv "$USER_CONF.tmp" "$USER_CONF"
          chmod 600 "$USER_CONF"
          do_remind status
          ;;
        *) echo "usage: callme remind [on|off|status]" >&2; exit 1;;
      esac
    }
    
    # Inbound transport. Two ways the phone's messages reach a live Claude session:
    # the plugin's inbox monitor (a process per session, prints a line) or the MCP
    # server pushing notifications/claude/channel down its already-open stream. The
    # channel is strictly better when available, but Claude Code only routes it for
    # servers named in that session's --channels flag, so this switch alone is not
    # enough — see `callme channel status`.
    do_channel() {
      need_jq
      local entry="server:plugin:call-me:callme"
      case "${1:-status}" in
        status)
          if [ -f "$USER_CONF" ] && [ "$(jq -r '.channel_push // false' "$USER_CONF")" = "true" ]; then
            echo "on — the MCP server pushes inbound messages; the inbox monitor stands down"
          else
            echo "off — the inbox monitor delivers inbound messages (default)"
          fi
          echo
          echo "This setting alone is NOT enough. Claude Code must also:"
          echo "  1. be started with  --channels $entry"
          echo "  2. accept this server onto its approved channels allowlist."
          echo
          echo "Step 2 is the one that bites. Measured 2026-08-10 on Claude Team with"
          echo "channelsEnabled:true in managed settings, the host still answered:"
          echo "  \"not on the approved channels allowlist\""
          echo "The approved list ships from Anthropic and held only their own channel"
          echo "plugins (discord, telegram, fakechat, imessage). An org admin can widen"
          echo "it with allowedChannelPlugins in managed settings, but that keys on"
          echo "{marketplace, plugin} — so it only works once /call-me is channel-enabled"
          echo "as an installed marketplace plugin, not as a local server: entry."
          echo
          echo "VERIFY before trusting it: start a session with the flag and"
          echo "CALLME_CHANNEL_SELFTEST=1 in the MCP server's env. If no self-test"
          echo "message appears, the channel is not routed — turn this back off, because"
          echo "while it is on the inbox monitor stands down and inbound is LOST."
          ;;
        on|off)
          [ -f "$USER_CONF" ] || { echo "pair a phone first: callme pair <number>" >&2; exit 6; }
          local want=false; [ "$1" = "on" ] && want=true
          jq --argjson v "$want" --arg t "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
            '. + {channel_push:$v, updated_at:$t}' "$USER_CONF" \
            > "$USER_CONF.tmp" && mv "$USER_CONF.tmp" "$USER_CONF"
          chmod 600 "$USER_CONF"
          do_channel status
          ;;
        *) echo "usage: callme channel [on|off|status]" >&2; exit 1;;
      esac
    }
    
    # Grace period: how long a parked question waits at the keyboard before the
    # Claude Code waiter monitor wakes the model to phone the human. Most questions
    # are answered by someone sitting right there, and ringing them for those is how
    # a reachability feature turns into a nuisance.
    do_grace() {
      need_jq
      local current=120
      [ -f "$USER_CONF" ] && current=$(jq -r '.answer_grace_s // 120' "$USER_CONF")
      case "${1:-status}" in
        status)
          if [ "$current" = "0" ]; then
            echo "0s — no wait; reach out the moment a turn ends on an open question"
          else
            echo "${current}s — wait this long for a keyboard answer, then phone them"
          fi
          ;;
        *[!0-9]*|"") echo "usage: callme grace [seconds|status]  (0 = no wait)" >&2; exit 1;;
        *)
          [ -f "$USER_CONF" ] || { echo "pair a phone first: callme pair <number>" >&2; exit 6; }
          jq --argjson v "$1" --arg t "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
            '. + {answer_grace_s:$v, updated_at:$t}' "$USER_CONF" \
            > "$USER_CONF.tmp" && mv "$USER_CONF.tmp" "$USER_CONF"
          chmod 600 "$USER_CONF"
          do_grace status
          ;;
      esac
    }
    
    do_number() {
      local n; n=$(require_number)
      if [ "${1:-}" = "--raw" ]; then printf '%s\n' "$n"; else display "$n"; echo; fi
    }
    
    # --- App Store onboarding ---------------------------------------------------
    
    # --- BEGIN QR MATRIX (generated by scripts/gen-appstore-qr.py) ---
    APPSTORE_URL='https://apps.apple.com/app/id6789575165'
    QR_MATRIX='0000000000000000000000000000000000000
    0000000000000000000000000000000000000
    0000000000000000000000000000000000000
    0000000000000000000000000000000000000
    0000111111100111100111111011111110000
    0000100000100000100100101010000010000
    0000101110100010101001001010111010000
    0000101110100111100110100010111010000
    0000101110100000010100100010111010000
    0000100000101100000110010010000010000
    0000111111101010101010101011111110000
    0000000000000000000001111000000000000
    0000100101101110010000010101000000000
    0000110111000000011001101111010010000
    0000010100111001010010110111011100000
    0000100110000110110110001110001100000
    0000101110100010101001001111010110000
    0000101011000101100011111100000000000
    0000001000111100100001100011111110000
    0000100110010101110101001001010100000
    0000010110110110011111101101000100000
    0000000011001100001001001011010010000
    0000101010110110110011000010100110000
    0000001010000011110111101111000110000
    0000100100111111001100001111101000000
    0000000000001011010000011000101110000
    0000111111100101111010101010100100000
    0000100000101100110010101000111010000
    0000101110100011001001001111100110000
    0000101110101000101011000010111100000
    0000101110100010010011011100111010000
    0000100000100111010011011100000100000
    0000111111101001000011101000110100000
    0000000000000000000000000000000000000
    0000000000000000000000000000000000000
    0000000000000000000000000000000000000
    0000000000000000000000000000000000000'
    # --- END QR MATRIX ---
    
    do_qr() {
      local big=0 color=1
      for a in "$@"; do
        case "$a" in
          --big) big=1;;
          --mono) color=0;;
        esac
      done
      # Always draw the code — half-block glyphs survive a pipe just fine, and the
      # main caller is an agent whose stdout is never a tty. Only drop the colours.
      if [ ! -t 1 ] || [ -n "${NO_COLOR:-}" ] || [ "${TERM:-}" = "dumb" ]; then
        color=0
      fi
      awk -v big="$big" -v color="$color" '
        { rows[NR] = $0 } END {
          for (y = 1; y <= NR; y += 2) {
            top = rows[y]; bot = (y + 1 <= NR) ? rows[y + 1] : ""
            line = ""
            for (x = 1; x <= length(top); x++) {
              t = substr(top, x, 1); b = (x <= length(bot)) ? substr(bot, x, 1) : "0"
              if (t == "1") g = (b == "1") ? "\342\226\210" : "\342\226\200"
              else          g = (b == "1") ? "\342\226\204" : " "
              line = line g; if (big == 1) line = line g
            }
            if (color == 1) printf "\033[38;5;16m\033[48;5;231m%s\033[0m\n", line
            else print line
          }
        }' <<< "$QR_MATRIX"
      echo "$APPSTORE_URL"
    }
    
    do_setup() {
      local n; n=$(resolve_number)
      # The CLI is the fallback door, not the good one. If this machine has Claude
      # Code, an agent that got here by copying this script took a wrong turn — say
      # so at the exact moment it is looking, because it will not go back and reread
      # a README. (An agent CAN install the plugin itself; slash commands are only
      # for a human typing.)
      if command -v claude >/dev/null 2>&1; then
        cat <<'EOF'
    --- FOR YOU, THE AGENT: this machine has Claude Code, so install the PLUGIN ---
    
    This CLI works, but on a Claude Code host it is the lesser door: the plugin also
    gives you call/text/reply tools, delivers their phone replies back into a live
    session, and installs a Stop hook that catches you ending a turn on a question.
    You can install it yourself, right now, with Bash — no slash commands needed:
    
        claude plugin marketplace add radres/call-me
        claude plugin install call-me@call-me
        claude plugin enable call-me@call-me   # REQUIRED — install leaves it DISABLED
        claude plugin list                     # expect: call-me@call-me   ✔ enabled
    
    Do not drop the enable line. `install` writes "call-me@call-me": false into
    settings.json; a disabled plugin exposes NO tools, so skipping it looks like a
    clean install that then silently never works.
    
    Then tell them to RESTART Claude Code (not /reload-plugins — that command is not
    yours to run, and the monitor needs a fresh session). Keep using this CLI in the
    meantime; pairing carries over, it is the same ~/.aiphone/config.json.
    
    EOF
      fi
      cat <<EOF
    Show this to your human — it's how they get set up (takes a minute):
    
    1. Download Call Me from the App Store:
       https://apps.apple.com/app/call-me/id6789575165
       Or point the camera at this:
    EOF
      echo
      do_qr "$@"
      cat <<EOF
    
       On Android, get it on Google Play instead:
       https://play.google.com/store/apps/details?id=com.serdaroztetik.callmeai
    
    2. Open My Number in the app to find the 10-digit number.
    3. They read the number back to you.
    4. Tell them their phone is about to ring, then run:  callme pair <that number>
       Pairing RINGS the phone and waits for them to speak — that answer is the
       proof the whole loop works. If nobody picks up it falls back to a text.
    
    Then \`callme text "hi"\` lands on their phone and \`callme call "question"\`
    rings it and returns what they say.
    
    --- FOR YOU, THE AGENT: finish the job, don't stop at pairing ---
    
    Pairing only makes you ABLE to reach them. These steps make you actually DO it,
    in future sessions, without them asking again. Do all of it now:
    
    A. Ask them one question: "How often are you away from the keyboard while I
       work — rarely, sometimes, or usually?" Their answer sets the next step.
    
    B. If they said sometimes or usually, turn on the standing reminder:
           callme remind on
       Without this, the Claude Code Stop hook only nudges sessions that already
       used /call-me — so a session that never thinks to mention it stays silent,
       which is exactly the session where they miss you asking.
    
    C. Write a standing rule into their user-level instructions
       (~/.claude/CLAUDE.md, or AGENTS.md for other agents). Read the file first
       and append to it — never overwrite. Use their answer from A:
    
           ## Reaching me
           I'm reachable on my phone through /call-me. When you need a decision, an
           answer, or input from me, contact me instead of stopping with the
           question in your final message — once your turn ends you are asleep and
           cannot reach me. That includes parking it in an interactive question
           widget: that just waits silently on a screen I'm not looking at, and no
           hook can see it, so reach out yourself.
           Text first (callme text / the text tool); call when it is blocking or
           time-sensitive. Never hardcode my number — 'callme number' reads it.
           [rarely]    I'm usually at the keyboard, so only reach out if I'll be gone a while.
           [sometimes] I'm away maybe half the time. Text me and keep working.
           [usually]   I'm mostly AFK. Assume I won't see the terminal: always reach out.
    
    D. Verify it end to end, don't assume. If the pairing call was answered, this is
       already done — the transcript IS the proof. Only if it went unanswered:
           callme text "Setup done — this is what a message from your agent looks like."
       Ask them whether it arrived. If it did not, the number is wrong: re-pair.
    
    E. Tell them what they can change later: 'callme remind off' to stop the
       every-session nudge, 'callme grace 300' to be left alone at the keyboard
       longer before the phone rings (0 = ring the moment a turn ends), and blocking
       a thread in the app to mute it.
    EOF
      if valid_number "$n"; then
        echo
        echo "(already paired with $(display "$n") — pair again only to change phones)"
        printf '(standing reminder is currently '
        do_remind status
        echo ")"
      fi
    }
    
    # --- session (this AI's own number/thread) ----------------------------------
    
    ensure_session() {
      [ -f "$CONF" ] && return
      register "${AIPHONE_LABEL:-$(basename "$PWD") on $(hostname -s)}"
    }
    
    register() {
      need_jq
      mkdir -p "$CONF_DIR"; chmod 700 "$CONF_DIR" 2>/dev/null || true
      local label="${1:-AI session}"
      local resp
      resp=$(curl -sfS -X POST "$API/sessions" -H 'content-type: application/json' \
        -d "$(jq -cn --arg l "$label" '{label:$l}')")
      echo "$resp" | jq --arg api "$API" '. + {api:$api, cursor:0}' > "$CONF"
      chmod 600 "$CONF"
      echo "registered: $(echo "$resp" | jq -r .display) (label: $label)"
      echo "token stored in $CONF"
    }
    
    do_call() {
      need_jq
      local to text
      if [ $# -ge 2 ]; then to=$(norm "$1"); text="$2"; else to=$(require_number); text="${1:-}"; fi
      valid_number "$to" || { echo "callme call: bad recipient '$1'" >&2; exit 6; }
      [ -n "$text" ] || usage
      ensure_session
      local timeout="${AIPHONE_TIMEOUT:-300}"
      local body
      body=$(jq -cn --arg t "$(conf session_token)" --arg to "$to" --arg x "$text" \
        --argjson w "$timeout" '{session_token:$t, to:$to, text:$x, timeout_s:$w}')
      # +30s curl budget over the server-side timeout
      local resp
      resp=$(curl -sS --max-time "$((timeout + 30))" -X POST "$API/calls" \
        -H 'content-type: application/json' -d "$body")
      local status
      status=$(echo "$resp" | jq -r '.status // "error"')
      case "$status" in
        completed)
          echo "ANSWER: $(echo "$resp" | jq -r .transcript)";;
        missed|declined|timeout)
          echo "NO ANSWER ($status) — consider 'callme text' instead" >&2; exit 3;;
        *)
          echo "call failed: $resp" >&2; exit 4;;
      esac
    }
    
    do_text() {
      need_jq
      local to text
      if [ $# -ge 2 ]; then to=$(norm "$1"); text="$2"; else to=$(require_number); text="${1:-}"; fi
      valid_number "$to" || { echo "callme text: bad recipient '$1'" >&2; exit 6; }
      [ -n "$text" ] || usage
      ensure_session
      local resp
      resp=$(curl -sfS -X POST "$API/messages" -H 'content-type: application/json' \
        -d "$(jq -cn --arg t "$(conf session_token)" --arg to "$to" --arg b "$text" \
          '{session_token:$t, to:$to, body:$b}')")
      # A 2xx only means the server stored it. A phone with notifications off shows
      # nothing, so "sent" alone would be a lie — say so and exit non-zero, which is
      # what callers (pair's fallback) branch on. Older servers omit `delivered`.
      if [ "$(echo "$resp" | jq -r '.delivered // true')" = "false" ]; then
        echo "sent, but NOT shown on their phone: $(echo "$resp" | jq -r '.notice // ""')" >&2
        return 7
      fi
      echo "sent"
    }
    
    fetch_events() { # $1=wait seconds
      local cursor resp
      cursor=$(conf cursor)
      resp=$(curl -sfS --max-time "$(( $1 + 10 ))" -G "$API/sessions/events" \
        --data-urlencode "session_token=$(conf session_token)" \
        --data-urlencode "cursor=$cursor" --data-urlencode "wait=$1")
      local new_cursor
      new_cursor=$(echo "$resp" | jq -r .cursor)
      jq --argjson c "$new_cursor" '.cursor = $c' "$CONF" > "$CONF.tmp" \
        && mv "$CONF.tmp" "$CONF" && chmod 600 "$CONF"
      echo "$resp" | jq -c '.events[]'
    }
    
    do_title() {
      need_jq; ensure_session
      curl -sfS -X POST "$API/sessions/label" -H 'content-type: application/json' \
        -d "$(jq -cn --arg t "$(conf session_token)" --arg l "$1" \
          '{session_token:$t, label:$l}')" > /dev/null
      echo "titled: $1"
    }
    
    do_listen() {
      need_jq; ensure_session
      echo "listening as $(conf display) — waiting for an event..." >&2
      while true; do
        local out
        out=$(fetch_events 50)
        if [ -n "$out" ]; then
          echo "$out" | jq -r '"[\(.type)] \(.payload | tostring)"'
          exit 0   # exit on first event → background-task notification fires
        fi
      done
    }
    
    do_events() {
      need_jq; ensure_session
      fetch_events 0 | jq -r '"[\(.type)] \(.payload | tostring)"'
    }
    
    do_prune() { # drop per-session state files older than 30 days
      local n=0
      for f in "$CONF_DIR"/claude-session-*.json "$CONF_DIR"/claude-monitor-*.json \
               "$CONF_DIR"/claude-channel-*.json; do
        [ -f "$f" ] || continue
        if [ -z "$(find "$f" -mtime -30 2>/dev/null)" ]; then rm -f "$f"; n=$((n+1)); fi
      done
      chmod 700 "$CONF_DIR" 2>/dev/null || true
      chmod 600 "$CONF_DIR"/*.json 2>/dev/null || true
      echo "pruned $n stale state file(s)"
    }
    
    cmd="${1:-}"; shift || true
    case "$cmd" in
      setup)    do_setup;;
      qr)       do_qr "$@";;
      pair)     [ $# -ge 1 ] || usage; do_pair "$1";;
      unpair)   do_unpair;;
      remind)   do_remind "${1:-status}";;
      grace)    do_grace "${1:-status}";;
      channel)  do_channel "${1:-status}";;
      number)   do_number "${1:-}";;
      register) register "${1:-AI session}";;
      call)     [ $# -ge 1 ] || usage; do_call "$@";;
      text)     [ $# -ge 1 ] || usage; do_text "$@";;
      listen)   do_listen;;
      events)   do_events;;
      title)    [ $# -ge 1 ] || usage; do_title "$1";;
      whoami)   need_jq; ensure_session; jq '{session_number, display, api}' "$CONF";;
      status)   curl -sfS "$API/healthz"; echo;;
      prune)    do_prune;;
      *)        usage;;
    esac
    
  • README.mdupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

Plain-text copy, no login: /skill-bundles/radres-call-me-skill.md

// REPO STATS

35 stars