[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6HV8RP0VPCKK1TTKA5M7844":3,"skill-same:01M6HV8RP0VPCKK1TTKA5M7844":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":50,"ownerHtmlUrl":51,"ownerBlogUrl":6,"ownerDescription":52,"updatedAt":53,"install":54,"resources":66},"01M6HV8RP0VPCKK1TTKA5M7844","channel-setup",null,"Guided channel activation for local\u002Ftmux users — adds a channel entry when none is configured, installs the plugin, configures the bot token in the project-local state dir, and walks through pairing. Run after hatch, or any time to add or re-enable a channel.","gtapps\u002Fhermitd","gtapps","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F16778396?v=4","01M6HTX8TNTPQN3NXRVA63VHTH","01M6HTX8Z23S531ZPPA5DKJVD0",85,"MIT","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fchannel-setup\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"channel","setup","channel setup","when","plugin","state","users","hatch","true","for","tmux","add","pairing","activation","token","run","bot","local","time","re","project","2026-11-10 10:12:30",false,"---\nname: channel-setup\ndescription: Guided channel activation for local\u002Ftmux users — adds a channel entry when none is configured, installs the plugin, configures the bot token in the project-local state dir, and walks through pairing. Run after hatch, or any time to add or re-enable a channel.\ndisable-model-invocation: true\n---\n\n# Channel Setup\n\nActivate a channel, adding the `config.json` entry first when there isn't one. Local\u002Ftmux pairing is this skill's own flow; a Docker hermit is routed by the check below.\n\n## Commands\n- `channel-group-add`: `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" group-add`\n\n## Plan\n\n### 1. Read config and detect channels\n\n**Runtime routing (first):** evaluate in this order.\n\n1. **Inside the container.** Run `[ -f \u002F.dockerenv ] || [ -f \u002Frun\u002F.containerenv ] && echo container || echo host`. If `container`: read `.hermit\u002Fconfig.json`. For each enabled channel object, resolve `state_dir` as step 4 does (config `state_dir`, default `.claude.local\u002Fchannels\u002F\u003Cchannel>`; if relative, make it absolute against the project root) and print that absolute path literally. Print and stop:\n   > DM the bot for a code, then run `\u002Fhermitd:channel-setup` from a normal host session at the project root.\n   If no enabled channel, print only the host-run sentence and stop. No `AskUserQuestion` on this path.\n\n2. **No compose file.** If `docker-compose.hermit.yml` is absent at the project root, continue with the local\u002Ftmux flow below.\n\n3. **Live tmux hermit.** Run `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fdocker-preflight.ts \"$(pwd)\"`. If its `live_owner` is non-null, a host tmux hermit owns this project despite the compose file: continue with the local\u002Ftmux flow below.\n\n4. **Compose present.** Build the stack in its precedence order, `COMPOSE_FILES=(-f docker-compose.hermit.yml); [ ! -f docker-compose.security.yml ] || COMPOSE_FILES+=(-f docker-compose.security.yml); [ ! -f docker-compose.override.yml ] || COMPOSE_FILES+=(-f docker-compose.override.yml)`, then run `docker compose \"${COMPOSE_FILES[@]}\" ps --status running --format '{{.Service}}'`. Non-zero exit → print its output, stop. If `hermit` is absent from the output: after channel selection, run steps 2 and 4, skip 3, then stop with `hermitd start` and \"re-run this skill to pair\". If `hermit` is present: docker-running.\n\n5. **docker-running.** After channel selection, skip step 3 (the container installs channel plugins at boot). Run step 4; a `channel-bot-id.ts` skip with `http_401` or `http_403` is reported as \"token rejected by \u003Cplatform>, fix it before restarting\". If this run created the config entry or wrote the token → stop: \"The bot is offline until the container restarts and loads it: `hermitd restart`, then DM the bot for a code and re-run this skill. Still no code after a restart: `hermitd docker logs --tail=60` shows the plugin's own error (wrong token, missing Message Content intent, install failure), and `\u002Fhermitd:hermit-doctor` checks the token.\" Otherwise run §5, §6, §6a, §6b, and §6c against the host-visible channel `\u003Cstate_dir>`, passing the absolute project `.hermit` directory as `\u003Chermit_state_dir>` to `channel-access.ts`. No restart prompt is needed for an already-running bot. Continue with §6d, §6e, then §7 for each paired channel. Add to the pairing question: \"No code from the bot? It has not loaded the token: `hermitd restart`, then re-run this skill.\"\n\nRead `.hermit\u002Fconfig.json`. Collect all entries under `channels` that are valid objects, tracking which are disabled (`enabled: false`).\n\n**Adding an entry.** Where a branch below says *create the entry*, run (`\u003Cname>` is the lowercase channel key — `discord`, `telegram` — never the capitalized option label):\n\n```\necho '{\"channels\":{\"\u003Cname>\":{\"enabled\":true}}}' | bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fhatch-config.ts \"$(pwd)\" --reinit\n```\n\nThe script fills `enabled`, `default_chat_id: null`, and `state_dir` (`.claude.local\u002Fchannels\u002F\u003Cname>`), validates the whole config, preserves every other key, and merges onto an existing entry rather than replacing it. It prints `OK`. On `error` nothing was written: report it and stop — never fall through into steps 2–6 against a channel that isn't in `config.json`.\n\n- If no channels configured: offer to add one — `AskUserQuestion` (header: \"Channel\") with options **Discord**, **Telegram**, **Cancel**. On **Cancel**, stop without writing. Otherwise create the entry and continue with that channel selected. Do not offer iMessage here: step 4 defines token vars for Discord and Telegram only.\n- If entries exist but all are disabled: name them, then ask with `AskUserQuestion` (header: \"Channel\") — the disabled channel names plus **Cancel** — which to re-enable. On **Cancel**, stop without writing. Otherwise create the entry for the chosen name (this flips `enabled` and leaves `default_chat_id`, `state_dir`, and `allowed_users` intact) and continue with it selected.\n- If exactly one enabled channel and nothing disabled: use it automatically.\n- Otherwise (several enabled, or enabled and disabled side by side): ask with `AskUserQuestion` (header: \"Channel\") — every channel name as an option, disabled ones labelled `\u003Cname> — disabled`, plus **All** (enabled channels only) — which to set up. If a disabled name is chosen, create the entry for it first (re-enabling it), then continue with it selected.\n\nRun steps 2–6 for each selected channel. On a Docker host (runtime routing 4–5), apply those overrides instead of the local\u002Ftmux default.\n\n### 2. Check prerequisites\n\nRun both checks in a single Bash call:\n\n```bash\nbun --version 2>\u002Fdev\u002Fnull; uname -s\n```\n\n- **Bun missing** (command fails \u002F no output): tell the operator —\n  > Bun is required for channel plugins but is not installed.\n  > Install: https:\u002F\u002Fbun.sh\n  > Then re-run this skill.\n\n  Stop for this channel.\n\n- **iMessage on non-macOS**: if `uname -s` is not `Darwin` and the channel is `imessage`, note it's macOS-only and skip this channel.\n\n### 3. Install plugin\n\nRun `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fresolve-siblings.ts \"$(pwd)\"` to get the project-or-local + enabled plugin list (JSON array; user-scope, managed, disabled, and cross-project entries already dropped). Each entry carries `plugin`, `marketplace_name`, `scope`, `enabled`.\n\nResolve the expected marketplace for this channel:\n\n- If `channels.\u003Cchannel>.marketplace` is set in `config.json`, use that value (third-party channel plugin path).\n- Otherwise, use `claude-plugins-official` (built-in channels: discord, telegram, imessage).\n\nThen check whether the surviving set contains an entry where:\n\n- the plugin name (substring of `id` left of `@`) equals `\u003Cchannel>`, AND\n- the marketplace name (substring of `id` right of `@`) equals the resolved marketplace.\n\n(Both clauses matter — `discord@some-other-marketplace` is a different plugin from a different source and must not satisfy this gate.)\n\n- **Found**: skip silently. The channel plugin is already installed and enabled at project or local scope for this project — the canonical filter guarantees `enabled == true`, and `*_STATE_DIR` (set by `hermitd-start` at boot) points at `.claude.local\u002Fchannels\u002F\u003Cchannel>\u002F`.\n- **Not found**: run, in order:\n  ```bash\n  claude plugin install \u003Cchannel>@\u003Cmarketplace> --scope local\n  claude plugin enable  \u003Cchannel>@\u003Cmarketplace> --scope local\n  ```\n  Explicit `enable` covers the disabled-but-installed-at-project\u002Flocal case — the filter excluded such entries (enabled-only), and `install` is a separate command from `enable` per the CLI surface, so it may no-op without re-enabling. A user-scope install elsewhere does not satisfy this gate; channel tokens and access policy are project-local.\n\nAfter any install (or if already present): tell the operator to run `\u002Freload-plugins` in this session to activate the plugin's configure and access commands before pairing.\n\n### 4. Token configuration (AskUserQuestion)\n\nBefore the token question, print the applicable platform prerequisite: Discord: Developer Portal, Bot tab: turn Message Content intent on; turn Public Bot off unless anyone may add it. Telegram: privacy mode only matters for respond-to-all; see §6c. No extra question.\n\nToken env var names: `discord` → `DISCORD_BOT_TOKEN`, `telegram` → `TELEGRAM_BOT_TOKEN`.\n\nResolve `state_dir`:\n- Read `channels.\u003Cchannel>.state_dir` from config.json.\n- If not set, default to `.claude.local\u002Fchannels\u002F\u003Cchannel>`.\n- If relative, it is relative to the project root (current directory).\n\nCheck if the token file already exists: `\u003Cstate_dir>\u002F.env` and contains the token var name.\n- If yes: \"Token already configured at `\u003Cstate_dir>\u002F.env`.\" → proceed to step 5.\n- If no: display the official setup guide reference and ask for the token:\n\n> To create your bot and get a token, follow the official guide:\n> https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fchannels\n\n```\nquestions: [\n  {\n    header: \"Bot token\",\n    question: \"Paste your bot token (or skip to add it later):\",\n    options: [\n      { label: \"Skip\", description: \"I'll add the token to \u003Cstate_dir>\u002F.env manually\" }\n    ]\n  }\n]\n```\n\nOperator pastes the token via Other, or selects Skip.\n\n**If token provided:**\n1. `mkdir -p \u003Cstate_dir>`\n2. Write `\u003CTOKEN_VAR>=\u003Cpasted-token>` to `\u003Cstate_dir>\u002F.env` (overwrite if exists)\n3. `chmod 600 \u003Cstate_dir>\u002F.env`\n4. Ensure `.claude.local\u002F` is in `.gitignore`: check if `.gitignore` exists and contains `.claude.local\u002F`; if missing, append `.claude.local\u002F`.\n5. If `channels.\u003Cchannel>.state_dir` was not set in config.json (a legacy entry, or one reached through the single-enabled-channel branch that never ran *Adding an entry*), run the same `hatch-config.ts … --reinit` one-liner for `\u003Cchannel>` now, but with an **empty** entry — `echo '{\"channels\":{\"\u003Cname>\":{}}}' | …` — so an existing `enabled: false` is preserved (the `{\"enabled\":true}` payload would flip it). It fills the conventional `state_dir`, validates, and audits, leaving every other field intact. Never write the key with Edit\u002FWrite.\n6. Validate `\u003CCHANNEL_UPPERCASE>_STATE_DIR` for the next resident start. Compute the absolute path of `state_dir`, then run:\n   ```bash\n   bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fapply-settings.ts .claude\u002Fsettings.local.json channel-env \u003CCHANNEL_UPPERCASE> \u003Cabsolute_state_dir>\n   ```\n   This validates and reports the state directory without writing settings. The configured state dir takes effect in the resident environment and launch overlay at the next start. Tokens stay in the channel's `.env`. Confirm: \"Configured `\u003CCHANNEL_UPPERCASE>_STATE_DIR` → `\u003Cabsolute_state_dir>` (takes effect at the next start).\"\n\n7. Capture the bot's own identity so the hermit recognizes mentions of itself:\n   ```bash\n   bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-bot-id.ts .hermit \u003Cchannel> --write\n   ```\n   Writes `bot_user_id` (and `bot_username`) into the channel's `config.json` entry, overwriting a stale value from a previous bot. A `skip` status is a non-event — report it and continue; setup never fails on it.\n\n**If token already configured:** also run steps 6 and 7 before proceeding to step 5.\n\n**If Skip:** print the manual command:\n```\necho '\u003CTOKEN_VAR>=your-token' > \u003Cstate_dir>\u002F.env && chmod 600 \u003Cstate_dir>\u002F.env\n```\nThen proceed to step 5 without a token (pairing will be skipped in step 5).\n\n### 5. Restart + pairing (AskUserQuestion)\n\n**If no token is configured** (skipped in step 4): print restart instructions and stop:\n> Restart Claude Code with channels active once you've added your token:\n> - With hermit: `hermitd-start` (passes `--channels` automatically)\n> - Manual: `claude --channels plugin:\u003Cchannel>@\u003Cmarketplace>`\n>   (use the same `\u003Cmarketplace>` resolved in step 3 — `claude-plugins-official` for built-in channels, or `channels.\u003Cchannel>.marketplace` for third-party plugins.)\n\n**If token is configured:** on docker-running go straight to the pairing question batch. Otherwise check whether the channel is already active in the current session by checking if the channel's reply tool is available. If active, skip the restart prompt and go straight to the pairing question batch.\n\nIf not active, display:\n> Token saved. Restart Claude Code to activate the channel:\n> - With hermit: `hermitd-start` (passes `--channels` automatically)\n> - Manual: `claude --channels plugin:\u003Cchannel>@\u003Cmarketplace>`\n>   (use the same `\u003Cmarketplace>` resolved in step 3.)\n>\n> After restarting, DM your bot: it will reply with a 6-character pairing code.\n> Only people who may approve tool use should be DM-paired, because every `allowFrom` DM receives and can answer native permission prompts; everyone else joins through a group whose admission is independent of `allowFrom`, and `allowed_users` narrows who can wake the hermit, never who can approve.\n\nThen ask:\n\n```\nquestions: [\n  {\n    header: \"Pairing\",\n    question: \"Channel state?\",\n    options: [\n      { label: \"Already paired\", description: \"Just verify access.json\" },\n      { label: \"Ready to pair\", description: \"Restarted, DM'd the bot, have the 6-char code\" },\n      { label: \"Skip\", description: \"I'll pair later\" }\n    ]\n  }\n]\n```\n\n- **Already paired**: skip pairing, go to access.json verification in step 6.\n- **Ready to pair**: proceed with pairing flow below.\n- **Skip**: stop — \"Run `\u002Fhermitd:channel-setup` again after restarting to complete pairing.\"\n\n**Pairing flow:**\n\nAsk with `AskUserQuestion`:\n\n```\nquestions: [\n  {\n    header: \"Pairing code\",\n    question: \"Paste the 6-character code your bot replied with:\",\n    options: [\n      { label: \"Skip\", description: \"Pair later\" }\n    ]\n  }\n]\n```\n\nIf code provided (via Other), use the absolute project `.hermit` directory as `\u003Chermit_state_dir>` (distinct from the channel's `\u003Cstate_dir>`):\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" pair \u003Cchannel> \u003Ccode>\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" policy \u003Cchannel> allowlist\n```\n\nRun the policy command only after pairing succeeds. Relay the result, retaining pairing's `file` (`state`, `home` or `home+state`) for §6. `home: \"set\"` means the paired DM became the briefing chat; `kept` leaves `default_chat_id` unchanged, possibly still empty. `failed` means pairing succeeded and the home chat is learned from the first message. On `{\"error\"}`, report the token and stop. Pairing and policy each raise the native permission prompt; bypass-mode callers are refused. Verify the location in §6.\n\nIf Skip: \"DM the bot later, then run `\u002Fhermitd:channel-setup` from a normal host session.\" Stop.\n\n### 6. Verify access.json location\n\nCheck if `access.json` exists at `\u003Cstate_dir>\u002Faccess.json`.\n\n- If yes: done.\n- If no: check `~\u002F.claude\u002Fchannels\u002F\u003Cchannel>\u002Faccess.json`. If found there, move it:\n  ```bash\n  mkdir -p \u003Cstate_dir>\n  mv ~\u002F.claude\u002Fchannels\u002F\u003Cchannel>\u002Faccess.json \u003Cstate_dir>\u002Faccess.json\n  ```\n  Confirm: \"Moved access.json to `\u003Cstate_dir>\u002F`.\"\n- If found in neither location: note — \"access.json not found. Pairing may not have completed. Run `\u002Fhermitd:channel-setup` after DMing your bot.\"\n\nAfter `file` `home` or `home+state`, the bot that handed out the code still reads `~\u002F.claude`; the hermit reads the state directory, which `home+state` already updated and the move above covers otherwise. Use `${CLAUDE_CONFIG_DIR}` instead of `~\u002F.claude` when that environment override is set.\n\nIf access.json is verified, continue to step 6a.\n\n### 6a. Access control\n\nOnce per channel after verifying `\u003Cstate_dir>\u002Faccess.json`, read its `allowFrom` and the current channel config. Include iMessage: these settings are hermit-owned. Ask the row in one `AskUserQuestion` call, with the default option recommended:\n\n| Header | Question | Options (`label`: description) |\n|---|---|---|\n| Access ctrl | Who may wake the hermit on this channel? | `Only \u003Cpaired id>`: use the paired id from `allowFrom` (default) \u002F `Allow everyone`: no hermit sender restriction \u002F Other: additional user ids |\n\nDrop Access ctrl when `channels.\u003Cchannel>.allowed_users` is already present, preserving it. Use the paired id from `allowFrom`; if it is ambiguous or unavailable, have the operator identify the intended ids through Other instead of guessing. Encode selected ids as a string array, including the paired id for additional ids. For Allow everyone omit `allowed_users`.\n\nMerge the answer through the existing reinit command:\n\n```bash\necho '{\"channels\":{\"\u003Cchannel>\":{\"allowed_users\":\u003Cselected_string_array>}}}' | bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fhatch-config.ts \"$(pwd)\" --reinit\n```\n\nOmit `allowed_users` when the row was dropped or Allow everyone was chosen. Never use Edit\u002FWrite on `config.json`. Stop on a non-zero merge exit as in Adding an entry. Repeating the same answers must leave the file byte-identical. Continue to §6b.\n\n### 6b. Default delivery settings\n\nOnce §6 verifies the channel state, skip iMessage; otherwise run:\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" ensure-defaults \u003Cchannel>\n```\n\nRelay `ack` (`set` or `kept`). The default is `👀` only when `ackReaction` is absent; an empty string and customized values are preserved. On `{\"error\"}`, report the token and stop.\n\n### 6c. Server channel \u002F group chat (optional)\n\nSkip for iMessage or when pairing did not complete. Run [the group questionnaire](references\u002Fgroup-enrollment.md) with the channel key, absolute `\u003Chermit_state_dir>`, and native `AskUserQuestion` prompts. Carry forward the returned group answers, then continue to §6d.\n\n### 6d. Maintainer channel check (optional)\n\nIf `channels.\u003Cchannel>.maintainer_channel_id` is set in `config.json`, this channel routes technical, operational, and spend alerts to a second outbound-only destination (same bot\u002Ftoken, a different chat) instead of the primary chat, used on client-facing installs so the person on the primary chat never sees ops detail. Doctor's channel-liveness probe only checks the primary chat, so a typo'd maintainer id would otherwise fail silently on every alert rather than at setup.\n\nWhen it's set, send **one** test message to the maintainer chat to confirm reachability:\n\n```\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-send.ts .hermit --tier maintainer --channel \u003Cchannel> -\n```\n\nwith a short line on stdin (e.g. \"Maintainer channel check: technical and spend alerts will arrive here.\"). Record the result in the §7 summary: **sent** confirms the id; a **failure** means the id is wrong or the chat isn't reachable, so surface it so the operator fixes it now. If `maintainer_channel_id` is absent, skip silently.\n\nA maintainer-send failure means **the bot isn't present or permissioned in the target server\u002Fchannel** — the fix is to invite\u002Fpermission the bot there. This chat is reached by a direct API POST with the bot token, never through `access.json`, so running `\u002F\u003Cchannel>:access` will not fix a failed send here.\n\n### 6e. Recall, operators, primary channel, and recording\n\nOnce per selected channel, re-read config after the preceding merges. Ask the applicable rows in one `AskUserQuestion` call (at most four rows); drop conditional rows whose conditions are false. Skip the call if no rows remain. The defaults below describe a fresh setup; preserve existing customization when an answer is omitted.\n\n| Header | Question | Options (`label`: description) |\n|---|---|---|\n| Recall scope | Which chats on this channel may this chat recall? | `Each chat only itself`: leave `isolate_chats` absent (default) \u002F `Every chat on this channel`: set `isolate_chats: false` |\n| Operators | Who may manage hermit-wide standing roles? | `First allowed user (\u003Cid>)`: use the first allowed id (default) \u002F Other: operator user ids |\n| Primary | Which enabled channel should receive default outbound messages? | `\u003Cthis channel>`: use this channel (default) \u002F each other enabled channel by name |\n| Record | Record chats on this channel for recall? | `Inherit global setting, currently \u003Con|off>`: leave `log_chats` absent (default) \u002F `No, never`: set `log_chats: false` \u002F `Yes, always`: set `log_chats: true` |\n\nShow Recall scope only if a group was added in §6c. Show Operators only if `channels.\u003Cchannel>.allowed_users` contains at least two ids; for the default option omit `operators` (absence already means the first allowed user); for Other write the selected ids as the `operators` string array. Show Primary only if at least two channel objects have `enabled: true` and `channels.primary` is unset. Always show Record; read `knowledge.channel_log_enabled` to render the current global setting as on or off.\n\nMerge the answered per-channel keys in one payload, omitting unselected keys and keys whose default is absence:\n\n```bash\necho '{\"channels\":{\"\u003Cchannel>\":{\u003Canswered_per_channel_keys>}}}' | bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fhatch-config.ts \"$(pwd)\" --reinit\n```\n\nNever use Edit\u002FWrite on `config.json`. Stop on a non-zero merge exit as in Adding an entry. Omission preserves existing values on reinit; do not describe an existing override as cleared by omission. If a key is already customized, show that current value as the recommendation and preserve it unless an explicit replacement was selected. When the operator selects an absence default (`Each chat only itself`, `Inherit global setting`, `First allowed user`) for a key that is currently set, clear it with `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fsettings-edit.ts .hermit\u002Fconfig.json unset channels.\u003Cchannel>.\u003Ckey>`; stop on a non-zero exit. Repeating the same answers must leave the file byte-identical.\n\nFor the Primary answer, run separately, substituting the selected enabled channel name:\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fsettings-edit.ts .hermit\u002Fconfig.json set channels.primary \u003Cname>\n```\n\nStop on a non-zero exit. Never include `primary` in a `hatch-config.ts` payload: that merger treats every entry under `channels` as a channel object. Continue to §7.\n\n### 7. Summary\n\n```\nChannel setup complete!\n\n  Channel:        \u003Cchannel>\n  Plugin:         installed (--scope local)\n  Token:          configured (\u003Cstate_dir>\u002F.env)\n  Paired:         yes \u002F skipped\n  Server channels: \u003Cid1> (mention: yes\u002Fno), \u003Cid2> (mention: no) \u002F skipped\n  State dir:      \u003Cstate_dir>\n\n  hermitd-start passes --channels automatically on next boot.\n  Later changes: \u002Fhermitd:hermit-settings channels (recall, record, operators)\n```\n\nWhen `channels.\u003Cchannel>.marketplace` is set in `config.json` (third-party channel), also print this line after the block; never for built-in channels:\n\n```\nFor additional custom channel integration, see the channel plugins guide: https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fdocs\u002Fchannel-plugins.md#threads-and-notices\n```\n\nIf anything was skipped, list the remaining steps.\n\n### 7a. Send owner welcome (once, only when exactly one channel was newly paired)\n\nRun this once, after step 7's summary, across the *whole* run — not per channel inside the steps 2–6 loop.\n\nSkip entirely if no channel selected \"Ready to pair\" in step 5 this run — an operator re-running setup against an already-paired channel (\"Already paired\") or one they skipped shouldn't get a repeat welcome.\n\nAlso skip, with a note in the summary (\"Welcome message skipped — more than one channel was newly paired this run; let the owner know directly\"), if *more than one* channel selected \"Ready to pair\" this run (the \"All\" path pairing several channels at once). `channel-send.ts` has no per-channel target — it resolves one generic outbound channel (`primary`, else first eligible in config order) — so with several freshly-paired channels there's no reliable way to know which one it would reach.\n\nOtherwise (exactly one channel newly paired), compose a short welcome in the operator's configured `language` (`config.json`), in your own voice, so the owner has something the moment the bot can reach them. Not the full guide, just an orientation pointer covering: they can talk to you anytime in plain language; when you have a suggestion or need a decision you will ask, and yes, later, or no is enough; `!pause` stops you until `!resume` (both must start with `!`); you track AI spend and warn when it nears any configured limit; if you go quiet or something is confusing, they should reach whoever set you up (who also has the full written guide). No file paths or internal jargon: it's the owner's first message, and the only chat commands allowed are the five control commands `!pause`, `!stop`, `!resume`, `!snooze`, `!status`. Send it on stdin:\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-send.ts .hermit - \u003C\u003C'HERMIT_WELCOME'\n\u003Cthe composed welcome>\nHERMIT_WELCOME\n```\n\nUse this send as the post-hatch delivery confirmation and report it explicitly in the summary. On success: \"Test message sent — check \u003Cchannel> and tell me if it didn't arrive.\" If the send fails, don't block setup: \"Channel paired, but the test message didn't arrive (`\u003Cerror>`) — check the pairing before relying on this channel.\"\n","# Channel Setup\n\nActivate a channel, adding the `config.json` entry first when there isn't one. Local\u002Ftmux pairing is this skill's own flow; a Docker hermit is routed by the check below.\n\n## Commands\n- `channel-group-add`: `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" group-add`\n\n## Plan\n\n### 1. Read config and detect channels\n\n**Runtime routing (first):** evaluate in this order.\n\n1. **Inside the container.** Run `[ -f \u002F.dockerenv ] || [ -f \u002Frun\u002F.containerenv ] && echo container || echo host`. If `container`: read `.hermit\u002Fconfig.json`. For each enabled channel object, resolve `state_dir` as step 4 does (config `state_dir`, default `.claude.local\u002Fchannels\u002F\u003Cchannel>`; if relative, make it absolute against the project root) and print that absolute path literally. Print and stop:\n   > DM the bot for a code, then run `\u002Fhermitd:channel-setup` from a normal host session at the project root.\n   If no enabled channel, print only the host-run sentence and stop. No `AskUserQuestion` on this path.\n\n2. **No compose file.** If `docker-compose.hermit.yml` is absent at the project root, continue with the local\u002Ftmux flow below.\n\n3. **Live tmux hermit.** Run `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fdocker-preflight.ts \"$(pwd)\"`. If its `live_owner` is non-null, a host tmux hermit owns this project despite the compose file: continue with the local\u002Ftmux flow below.\n\n4. **Compose present.** Build the stack in its precedence order, `COMPOSE_FILES=(-f docker-compose.hermit.yml); [ ! -f docker-compose.security.yml ] || COMPOSE_FILES+=(-f docker-compose.security.yml); [ ! -f docker-compose.override.yml ] || COMPOSE_FILES+=(-f docker-compose.override.yml)`, then run `docker compose \"${COMPOSE_FILES[@]}\" ps --status running --format '{{.Service}}'`. Non-zero exit → print its output, stop. If `hermit` is absent from the output: after channel selection, run steps 2 and 4, skip 3, then stop with `hermitd start` and \"re-run this skill to pair\". If `hermit` is present: docker-running.\n\n5. **docker-running.** After channel selection, skip step 3 (the container installs channel plugins at boot). Run step 4; a `channel-bot-id.ts` skip with `http_401` or `http_403` is reported as \"token rejected by \u003Cplatform>, fix it before restarting\". If this run created the config entry or wrote the token → stop: \"The bot is offline until the container restarts and loads it: `hermitd restart`, then DM the bot for a code and re-run this skill. Still no code after a restart: `hermitd docker logs --tail=60` shows the plugin's own error (wrong token, missing Message Content intent, install failure), and `\u002Fhermitd:hermit-doctor` checks the token.\" Otherwise run §5, §6, §6a, §6b, and §6c against the host-visible channel `\u003Cstate_dir>`, passing the absolute project `.hermit` directory as `\u003Chermit_state_dir>` to `channel-access.ts`. No restart prompt is needed for an already-running bot. Continue with §6d, §6e, then §7 for each paired channel. Add to the pairing question: \"No code from the bot? It has not loaded the token: `hermitd restart`, then re-run this skill.\"\n\nRead `.hermit\u002Fconfig.json`. Collect all entries under `channels` that are valid objects, tracking which are disabled (`enabled: false`).\n\n**Adding an entry.** Where a branch below says *create the entry*, run (`\u003Cname>` is the lowercase channel key — `discord`, `telegram` — never the capitalized option label):\n\n```\necho '{\"channels\":{\"\u003Cname>\":{\"enabled\":true}}}' | bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fhatch-config.ts \"$(pwd)\" --reinit\n```\n\nThe script fills `enabled`, `default_chat_id: null`, and `state_dir` (`.claude.local\u002Fchannels\u002F\u003Cname>`), validates the whole config, preserves every other key, and merges onto an existing entry rather than replacing it. It prints `OK`. On `error` nothing was written: report it and stop — never fall through into steps 2–6 against a channel that isn't in `config.json`.\n\n- If no channels configured: offer to add one — `AskUserQuestion` (header: \"Channel\") with options **Discord**, **Telegram**, **Cancel**. On **Cancel**, stop without writing. Otherwise create the entry and continue with that channel selected. Do not offer iMessage here: step 4 defines token vars for Discord and Telegram only.\n- If entries exist but all are disabled: name them, then ask with `AskUserQuestion` (header: \"Channel\") — the disabled channel names plus **Cancel** — which to re-enable. On **Cancel**, stop without writing. Otherwise create the entry for the chosen name (this flips `enabled` and leaves `default_chat_id`, `state_dir`, and `allowed_users` intact) and continue with it selected.\n- If exactly one enabled channel and nothing disabled: use it automatically.\n- Otherwise (several enabled, or enabled and disabled side by side): ask with `AskUserQuestion` (header: \"Channel\") — every channel name as an option, disabled ones labelled `\u003Cname> — disabled`, plus **All** (enabled channels only) — which to set up. If a disabled name is chosen, create the entry for it first (re-enabling it), then continue with it selected.\n\nRun steps 2–6 for each selected channel. On a Docker host (runtime routing 4–5), apply those overrides instead of the local\u002Ftmux default.\n\n### 2. Check prerequisites\n\nRun both checks in a single Bash call:\n\n```bash\nbun --version 2>\u002Fdev\u002Fnull; uname -s\n```\n\n- **Bun missing** (command fails \u002F no output): tell the operator —\n  > Bun is required for channel plugins but is not installed.\n  > Install: https:\u002F\u002Fbun.sh\n  > Then re-run this skill.\n\n  Stop for this channel.\n\n- **iMessage on non-macOS**: if `uname -s` is not `Darwin` and the channel is `imessage`, note it's macOS-only and skip this channel.\n\n### 3. Install plugin\n\nRun `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fresolve-siblings.ts \"$(pwd)\"` to get the project-or-local + enabled plugin list (JSON array; user-scope, managed, disabled, and cross-project entries already dropped). Each entry carries `plugin`, `marketplace_name`, `scope`, `enabled`.\n\nResolve the expected marketplace for this channel:\n\n- If `channels.\u003Cchannel>.marketplace` is set in `config.json`, use that value (third-party channel plugin path).\n- Otherwise, use `claude-plugins-official` (built-in channels: discord, telegram, imessage).\n\nThen check whether the surviving set contains an entry where:\n\n- the plugin name (substring of `id` left of `@`) equals `\u003Cchannel>`, AND\n- the marketplace name (substring of `id` right of `@`) equals the resolved marketplace.\n\n(Both clauses matter — `discord@some-other-marketplace` is a different plugin from a different source and must not satisfy this gate.)\n\n- **Found**: skip silently. The channel plugin is already installed and enabled at project or local scope for this project — the canonical filter guarantees `enabled == true`, and `*_STATE_DIR` (set by `hermitd-start` at boot) points at `.claude.local\u002Fchannels\u002F\u003Cchannel>\u002F`.\n- **Not found**: run, in order:\n  ```bash\n  claude plugin install \u003Cchannel>@\u003Cmarketplace> --scope local\n  claude plugin enable  \u003Cchannel>@\u003Cmarketplace> --scope local\n  ```\n  Explicit `enable` covers the disabled-but-installed-at-project\u002Flocal case — the filter excluded such entries (enabled-only), and `install` is a separate command from `enable` per the CLI surface, so it may no-op without re-enabling. A user-scope install elsewhere does not satisfy this gate; channel tokens and access policy are project-local.\n\nAfter any install (or if already present): tell the operator to run `\u002Freload-plugins` in this session to activate the plugin's configure and access commands before pairing.\n\n### 4. Token configuration (AskUserQuestion)\n\nBefore the token question, print the applicable platform prerequisite: Discord: Developer Portal, Bot tab: turn Message Content intent on; turn Public Bot off unless anyone may add it. Telegram: privacy mode only matters for respond-to-all; see §6c. No extra question.\n\nToken env var names: `discord` → `DISCORD_BOT_TOKEN`, `telegram` → `TELEGRAM_BOT_TOKEN`.\n\nResolve `state_dir`:\n- Read `channels.\u003Cchannel>.state_dir` from config.json.\n- If not set, default to `.claude.local\u002Fchannels\u002F\u003Cchannel>`.\n- If relative, it is relative to the project root (current directory).\n\nCheck if the token file already exists: `\u003Cstate_dir>\u002F.env` and contains the token var name.\n- If yes: \"Token already configured at `\u003Cstate_dir>\u002F.env`.\" → proceed to step 5.\n- If no: display the official setup guide reference and ask for the token:\n\n> To create your bot and get a token, follow the official guide:\n> https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fchannels\n\n```\nquestions: [\n  {\n    header: \"Bot token\",\n    question: \"Paste your bot token (or skip to add it later):\",\n    options: [\n      { label: \"Skip\", description: \"I'll add the token to \u003Cstate_dir>\u002F.env manually\" }\n    ]\n  }\n]\n```\n\nOperator pastes the token via Other, or selects Skip.\n\n**If token provided:**\n1. `mkdir -p \u003Cstate_dir>`\n2. Write `\u003CTOKEN_VAR>=\u003Cpasted-token>` to `\u003Cstate_dir>\u002F.env` (overwrite if exists)\n3. `chmod 600 \u003Cstate_dir>\u002F.env`\n4. Ensure `.claude.local\u002F` is in `.gitignore`: check if `.gitignore` exists and contains `.claude.local\u002F`; if missing, append `.claude.local\u002F`.\n5. If `channels.\u003Cchannel>.state_dir` was not set in config.json (a legacy entry, or one reached through the single-enabled-channel branch that never ran *Adding an entry*), run the same `hatch-config.ts … --reinit` one-liner for `\u003Cchannel>` now, but with an **empty** entry — `echo '{\"channels\":{\"\u003Cname>\":{}}}' | …` — so an existing `enabled: false` is preserved (the `{\"enabled\":true}` payload would flip it). It fills the conventional `state_dir`, validates, and audits, leaving every other field intact. Never write the key with Edit\u002FWrite.\n6. Validate `\u003CCHANNEL_UPPERCASE>_STATE_DIR` for the next resident start. Compute the absolute path of `state_dir`, then run:\n   ```bash\n   bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fapply-settings.ts .claude\u002Fsettings.local.json channel-env \u003CCHANNEL_UPPERCASE> \u003Cabsolute_state_dir>\n   ```\n   This validates and reports the state directory without writing settings. The configured state dir takes effect in the resident environment and launch overlay at the next start. Tokens stay in the channel's `.env`. Confirm: \"Configured `\u003CCHANNEL_UPPERCASE>_STATE_DIR` → `\u003Cabsolute_state_dir>` (takes effect at the next start).\"\n\n7. Capture the bot's own identity so the hermit recognizes mentions of itself:\n   ```bash\n   bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-bot-id.ts .hermit \u003Cchannel> --write\n   ```\n   Writes `bot_user_id` (and `bot_username`) into the channel's `config.json` entry, overwriting a stale value from a previous bot. A `skip` status is a non-event — report it and continue; setup never fails on it.\n\n**If token already configured:** also run steps 6 and 7 before proceeding to step 5.\n\n**If Skip:** print the manual command:\n```\necho '\u003CTOKEN_VAR>=your-token' > \u003Cstate_dir>\u002F.env && chmod 600 \u003Cstate_dir>\u002F.env\n```\nThen proceed to step 5 without a token (pairing will be skipped in step 5).\n\n### 5. Restart + pairing (AskUserQuestion)\n\n**If no token is configured** (skipped in step 4): print restart instructions and stop:\n> Restart Claude Code with channels active once you've added your token:\n> - With hermit: `hermitd-start` (passes `--channels` automatically)\n> - Manual: `claude --channels plugin:\u003Cchannel>@\u003Cmarketplace>`\n>   (use the same `\u003Cmarketplace>` resolved in step 3 — `claude-plugins-official` for built-in channels, or `channels.\u003Cchannel>.marketplace` for third-party plugins.)\n\n**If token is configured:** on docker-running go straight to the pairing question batch. Otherwise check whether the channel is already active in the current session by checking if the channel's reply tool is available. If active, skip the restart prompt and go straight to the pairing question batch.\n\nIf not active, display:\n> Token saved. Restart Claude Code to activate the channel:\n> - With hermit: `hermitd-start` (passes `--channels` automatically)\n> - Manual: `claude --channels plugin:\u003Cchannel>@\u003Cmarketplace>`\n>   (use the same `\u003Cmarketplace>` resolved in step 3.)\n>\n> After restarting, DM your bot: it will reply with a 6-character pairing code.\n> Only people who may approve tool use should be DM-paired, because every `allowFrom` DM receives and can answer native permission prompts; everyone else joins through a group whose admission is independent of `allowFrom`, and `allowed_users` narrows who can wake the hermit, never who can approve.\n\nThen ask:\n\n```\nquestions: [\n  {\n    header: \"Pairing\",\n    question: \"Channel state?\",\n    options: [\n      { label: \"Already paired\", description: \"Just verify access.json\" },\n      { label: \"Ready to pair\", description: \"Restarted, DM'd the bot, have the 6-char code\" },\n      { label: \"Skip\", description: \"I'll pair later\" }\n    ]\n  }\n]\n```\n\n- **Already paired**: skip pairing, go to access.json verification in step 6.\n- **Ready to pair**: proceed with pairing flow below.\n- **Skip**: stop — \"Run `\u002Fhermitd:channel-setup` again after restarting to complete pairing.\"\n\n**Pairing flow:**\n\nAsk with `AskUserQuestion`:\n\n```\nquestions: [\n  {\n    header: \"Pairing code\",\n    question: \"Paste the 6-character code your bot replied with:\",\n    options: [\n      { label: \"Skip\", description: \"Pair later\" }\n    ]\n  }\n]\n```\n\nIf code provided (via Other), use the absolute project `.hermit` directory as `\u003Chermit_state_dir>` (distinct from the channel's `\u003Cstate_dir>`):\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" pair \u003Cchannel> \u003Ccode>\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" policy \u003Cchannel> allowlist\n```\n\nRun the policy command only after pairing succeeds. Relay the result, retaining pairing's `file` (`state`, `home` or `home+state`) for §6. `home: \"set\"` means the paired DM became the briefing chat; `kept` leaves `default_chat_id` unchanged, possibly still empty. `failed` means pairing succeeded and the home chat is learned from the first message. On `{\"error\"}`, report the token and stop. Pairing and policy each raise the native permission prompt; bypass-mode callers are refused. Verify the location in §6.\n\nIf Skip: \"DM the bot later, then run `\u002Fhermitd:channel-setup` from a normal host session.\" Stop.\n\n### 6. Verify access.json location\n\nCheck if `access.json` exists at `\u003Cstate_dir>\u002Faccess.json`.\n\n- If yes: done.\n- If no: check `~\u002F.claude\u002Fchannels\u002F\u003Cchannel>\u002Faccess.json`. If found there, move it:\n  ```bash\n  mkdir -p \u003Cstate_dir>\n  mv ~\u002F.claude\u002Fchannels\u002F\u003Cchannel>\u002Faccess.json \u003Cstate_dir>\u002Faccess.json\n  ```\n  Confirm: \"Moved access.json to `\u003Cstate_dir>\u002F`.\"\n- If found in neither location: note — \"access.json not found. Pairing may not have completed. Run `\u002Fhermitd:channel-setup` after DMing your bot.\"\n\nAfter `file` `home` or `home+state`, the bot that handed out the code still reads `~\u002F.claude`; the hermit reads the state directory, which `home+state` already updated and the move above covers otherwise. Use `${CLAUDE_CONFIG_DIR}` instead of `~\u002F.claude` when that environment override is set.\n\nIf access.json is verified, continue to step 6a.\n\n### 6a. Access control\n\nOnce per channel after verifying `\u003Cstate_dir>\u002Faccess.json`, read its `allowFrom` and the current channel config. Include iMessage: these settings are hermit-owned. Ask the row in one `AskUserQuestion` call, with the default option recommended:\n\n| Header | Question | Options (`label`: description) |\n|---|---|---|\n| Access ctrl | Who may wake the hermit on this channel? | `Only \u003Cpaired id>`: use the paired id from `allowFrom` (default) \u002F `Allow everyone`: no hermit sender restriction \u002F Other: additional user ids |\n\nDrop Access ctrl when `channels.\u003Cchannel>.allowed_users` is already present, preserving it. Use the paired id from `allowFrom`; if it is ambiguous or unavailable, have the operator identify the intended ids through Other instead of guessing. Encode selected ids as a string array, including the paired id for additional ids. For Allow everyone omit `allowed_users`.\n\nMerge the answer through the existing reinit command:\n\n```bash\necho '{\"channels\":{\"\u003Cchannel>\":{\"allowed_users\":\u003Cselected_string_array>}}}' | bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fhatch-config.ts \"$(pwd)\" --reinit\n```\n\nOmit `allowed_users` when the row was dropped or Allow everyone was chosen. Never use Edit\u002FWrite on `config.json`. Stop on a non-zero merge exit as in Adding an entry. Repeating the same answers must leave the file byte-identical. Continue to §6b.\n\n### 6b. Default delivery settings\n\nOnce §6 verifies the channel state, skip iMessage; otherwise run:\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-access.ts \"\u003Chermit_state_dir>\" ensure-defaults \u003Cchannel>\n```\n\nRelay `ack` (`set` or `kept`). The default is `👀` only when `ackReaction` is absent; an empty string and customized values are preserved. On `{\"error\"}`, report the token and stop.\n\n### 6c. Server channel \u002F group chat (optional)\n\nSkip for iMessage or when pairing did not complete. Run [the group questionnaire](references\u002Fgroup-enrollment.md) with the channel key, absolute `\u003Chermit_state_dir>`, and native `AskUserQuestion` prompts. Carry forward the returned group answers, then continue to §6d.\n\n### 6d. Maintainer channel check (optional)\n\nIf `channels.\u003Cchannel>.maintainer_channel_id` is set in `config.json`, this channel routes technical, operational, and spend alerts to a second outbound-only destination (same bot\u002Ftoken, a different chat) instead of the primary chat, used on client-facing installs so the person on the primary chat never sees ops detail. Doctor's channel-liveness probe only checks the primary chat, so a typo'd maintainer id would otherwise fail silently on every alert rather than at setup.\n\nWhen it's set, send **one** test message to the maintainer chat to confirm reachability:\n\n```\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-send.ts .hermit --tier maintainer --channel \u003Cchannel> -\n```\n\nwith a short line on stdin (e.g. \"Maintainer channel check: technical and spend alerts will arrive here.\"). Record the result in the §7 summary: **sent** confirms the id; a **failure** means the id is wrong or the chat isn't reachable, so surface it so the operator fixes it now. If `maintainer_channel_id` is absent, skip silently.\n\nA maintainer-send failure means **the bot isn't present or permissioned in the target server\u002Fchannel** — the fix is to invite\u002Fpermission the bot there. This chat is reached by a direct API POST with the bot token, never through `access.json`, so running `\u002F\u003Cchannel>:access` will not fix a failed send here.\n\n### 6e. Recall, operators, primary channel, and recording\n\nOnce per selected channel, re-read config after the preceding merges. Ask the applicable rows in one `AskUserQuestion` call (at most four rows); drop conditional rows whose conditions are false. Skip the call if no rows remain. The defaults below describe a fresh setup; preserve existing customization when an answer is omitted.\n\n| Header | Question | Options (`label`: description) |\n|---|---|---|\n| Recall scope | Which chats on this channel may this chat recall? | `Each chat only itself`: leave `isolate_chats` absent (default) \u002F `Every chat on this channel`: set `isolate_chats: false` |\n| Operators | Who may manage hermit-wide standing roles? | `First allowed user (\u003Cid>)`: use the first allowed id (default) \u002F Other: operator user ids |\n| Primary | Which enabled channel should receive default outbound messages? | `\u003Cthis channel>`: use this channel (default) \u002F each other enabled channel by name |\n| Record | Record chats on this channel for recall? | `Inherit global setting, currently \u003Con|off>`: leave `log_chats` absent (default) \u002F `No, never`: set `log_chats: false` \u002F `Yes, always`: set `log_chats: true` |\n\nShow Recall scope only if a group was added in §6c. Show Operators only if `channels.\u003Cchannel>.allowed_users` contains at least two ids; for the default option omit `operators` (absence already means the first allowed user); for Other write the selected ids as the `operators` string array. Show Primary only if at least two channel objects have `enabled: true` and `channels.primary` is unset. Always show Record; read `knowledge.channel_log_enabled` to render the current global setting as on or off.\n\nMerge the answered per-channel keys in one payload, omitting unselected keys and keys whose default is absence:\n\n```bash\necho '{\"channels\":{\"\u003Cchannel>\":{\u003Canswered_per_channel_keys>}}}' | bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fhatch-config.ts \"$(pwd)\" --reinit\n```\n\nNever use Edit\u002FWrite on `config.json`. Stop on a non-zero merge exit as in Adding an entry. Omission preserves existing values on reinit; do not describe an existing override as cleared by omission. If a key is already customized, show that current value as the recommendation and preserve it unless an explicit replacement was selected. When the operator selects an absence default (`Each chat only itself`, `Inherit global setting`, `First allowed user`) for a key that is currently set, clear it with `bun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fsettings-edit.ts .hermit\u002Fconfig.json unset channels.\u003Cchannel>.\u003Ckey>`; stop on a non-zero exit. Repeating the same answers must leave the file byte-identical.\n\nFor the Primary answer, run separately, substituting the selected enabled channel name:\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fsettings-edit.ts .hermit\u002Fconfig.json set channels.primary \u003Cname>\n```\n\nStop on a non-zero exit. Never include `primary` in a `hatch-config.ts` payload: that merger treats every entry under `channels` as a channel object. Continue to §7.\n\n### 7. Summary\n\n```\nChannel setup complete!\n\n  Channel:        \u003Cchannel>\n  Plugin:         installed (--scope local)\n  Token:          configured (\u003Cstate_dir>\u002F.env)\n  Paired:         yes \u002F skipped\n  Server channels: \u003Cid1> (mention: yes\u002Fno), \u003Cid2> (mention: no) \u002F skipped\n  State dir:      \u003Cstate_dir>\n\n  hermitd-start passes --channels automatically on next boot.\n  Later changes: \u002Fhermitd:hermit-settings channels (recall, record, operators)\n```\n\nWhen `channels.\u003Cchannel>.marketplace` is set in `config.json` (third-party channel), also print this line after the block; never for built-in channels:\n\n```\nFor additional custom channel integration, see the channel plugins guide: https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fdocs\u002Fchannel-plugins.md#threads-and-notices\n```\n\nIf anything was skipped, list the remaining steps.\n\n### 7a. Send owner welcome (once, only when exactly one channel was newly paired)\n\nRun this once, after step 7's summary, across the *whole* run — not per channel inside the steps 2–6 loop.\n\nSkip entirely if no channel selected \"Ready to pair\" in step 5 this run — an operator re-running setup against an already-paired channel (\"Already paired\") or one they skipped shouldn't get a repeat welcome.\n\nAlso skip, with a note in the summary (\"Welcome message skipped — more than one channel was newly paired this run; let the owner know directly\"), if *more than one* channel selected \"Ready to pair\" this run (the \"All\" path pairing several channels at once). `channel-send.ts` has no per-channel target — it resolves one generic outbound channel (`primary`, else first eligible in config order) — so with several freshly-paired channels there's no reliable way to know which one it would reach.\n\nOtherwise (exactly one channel newly paired), compose a short welcome in the operator's configured `language` (`config.json`), in your own voice, so the owner has something the moment the bot can reach them. Not the full guide, just an orientation pointer covering: they can talk to you anytime in plain language; when you have a suggestion or need a decision you will ask, and yes, later, or no is enough; `!pause` stops you until `!resume` (both must start with `!`); you track AI spend and warn when it nears any configured limit; if you go quiet or something is confusing, they should reach whoever set you up (who also has the full written guide). No file paths or internal jargon: it's the owner's first message, and the only chat commands allowed are the five control commands `!pause`, `!stop`, `!resume`, `!snooze`, `!status`. Send it on stdin:\n\n```bash\nbun ${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Fchannel-send.ts .hermit - \u003C\u003C'HERMIT_WELCOME'\n\u003Cthe composed welcome>\nHERMIT_WELCOME\n```\n\nUse this send as the post-hatch delivery confirmation and report it explicitly in the summary. On success: \"Test message sent — check \u003Cchannel> and tell me if it didn't arrive.\" If the send fails, don't block setup: \"Channel paired, but the test message didn't arrive (`\u003Cerror>`) — check the pairing before relying on this channel.\"\n",{"name":5,"description":7,"disable-model-invocation":44},true,"main","72c04e1dc580a31742c23d0ed2c957dbf842f08d","plugins\u002Fhermitd\u002Fskills\u002Fchannel-setup",16,"https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd","https:\u002F\u002Fhermitd.dev","https:\u002F\u002Fgithub.com\u002Fgtapps","Software engineer from Portugal.\r\nBuilding and deploying agents for real companies.","2026-11-15 03:26:28",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":44},[56,59,62],{"tool":57,"command":58},"npx","npx skills add gtapps\u002Fhermitd --skill channel-setup",{"tool":60,"command":61},"bunx","bunx skills add gtapps\u002Fhermitd --skill channel-setup",{"tool":63,"command":64},"pnpm","pnpm dlx skills add gtapps\u002Fhermitd --skill channel-setup","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fchannel-setup\u002FSKILL.md",[67],{"path":68,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":71,"sourceHtmlUrl":72},"references\u002Fgroup-enrollment.md","REFERENCE","参考文档",4394,"https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fchannel-setup\u002Freferences\u002Fgroup-enrollment.md",[74,90,104,129,145,154],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":39},"01M718AB18DTYHVGXA6VF1VVR2","dream","Nightly memory maintenance from transcript ground truth. Verifies, enriches and tidies auto-memory and topic pages with snapshot validation and undo. Also handles 'undo the dream'.","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fdream\u002FSKILL.md",[],[76,81,82,83,84,85,86,87,88,89],"with","maintenance","memory","validation","snapshot","pages","auto","memory maintenance","transcript",{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":96,"publishedAt":39},"01M6HVD4WX8RB5EKH49HM66M8X","when-done-switch-to","Arm a deferred model or effort switch. Use for \"when done switch to\", \"after this drop to sonnet\", or \"switch model when idle\".","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fwhen-done-switch-to\u002FSKILL.md",[],[21,97,98,99,27,100,101,102,103],"done","switch","when done switch to","model","use","drop","sonnet",{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":39},"01M6HVD0ZA2Q1RD7CA0X8VWF3T","weekly-review","Generate the weekly review report for the current ISO week. Writes to .hermit\u002Fcompiled\u002Freview-weekly-YYYY-Www.md (dev-facing detail) and sends a plain-language channel summary (Delivered \u002F Decisions \u002F Waiting on you \u002F Spend). Runs every Sunday at 23:00 via routine.","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fweekly-review\u002FSKILL.md",[],[111,112,113,114,115,116,18,117,118,119,120,121,122,123,124,27,125,126,127,128],"weekly review","review","weekly","hermit","plain","routine","summary","report","at","generate","via","decisions","you","detail","iso","review report","dev","plain-language",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":135,"publishedAt":39},"01M6HVCW8PHZXSPYAZQBZXB9DX","watch","Background events via the config watch monitor or ad-hoc Monitor tool. Quiet watches cost zero tokens.","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fwatch\u002FSKILL.md",[],[131,136,137,138,139,140,141,142,143,121,144],"monitor","ZeRO","tokens","tool","cost","ad","config","background","events",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":151,"publishedAt":39},"01M6HVCRMG53K3Y63WAH59YVFJ","watch-activate","Activate the config watch monitor.","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fwatch-activate\u002FSKILL.md",[],[131,152,153,136,142],"activate","watch activate",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":165,"publishedAt":39},"01M6HVCK6782RD4D9ADQY0ENKD","voice-notes","Transcribe and respond to voice notes. Activates when any channel message carries an audio attachment, or when asked to transcribe an inbound voice message.","https:\u002F\u002Fgithub.com\u002Fgtapps\u002Fhermitd\u002Fblob\u002Fmain\u002Fplugins\u002Fhermitd\u002Fskills\u002Fvoice-notes\u002FSKILL.md",[160],{"categoryNo":161,"categoryName":162,"taxonomyType":163,"taxonomyTypeDesc":164},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[166,167,168,169,170,171,172,173,174,175,114,21,176,177,178,179,180,181,123,182],"notes","voice","voice notes","SET","Audio","transcribe","operator","understand","transcription","my","read","message","can","telegram","channel message","discord","up"]