Nanoclaw channel-formatting

Convert Claude's Markdown output to each channel's native text syntax before delivery. Adds zero-dependency formatting for WhatsApp, Telegram, and Slack (marker substitution). Also ships a Signal rich-text helper (parseSignalStyles) used by the Signal skill.

install
source · Clone the upstream repo
git clone https://github.com/qwibitai/nanoclaw
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/qwibitai/nanoclaw "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/channel-formatting" ~/.claude/skills/qwibitai-nanoclaw-channel-formatting && rm -rf "$T"
manifest: .claude/skills/channel-formatting/SKILL.md
source content

Channel Formatting

This skill wires channel-aware Markdown conversion into the outbound pipeline so Claude's responses render natively on each platform — no more literal

**asterisks**
in WhatsApp or Telegram.

ChannelTransformation
WhatsApp
**bold**
*bold*
,
*italic*
_italic_
, headings → bold, links →
text (url)
Telegramsame as WhatsApp, but
[text](url)
links are preserved (Markdown v1 renders them natively)
Slacksame as WhatsApp, but links become
<url|text>
Discordpassthrough (Discord already renders Markdown)
Signalpassthrough for
parseTextStyles
;
parseSignalStyles
in
src/text-styles.ts
produces plain text + native
textStyle
ranges for use by the Signal skill

Code blocks (fenced and inline) are always protected — their content is never transformed.

Phase 1: Pre-flight

Check if already applied

test -f src/text-styles.ts && echo "already applied" || echo "not yet applied"

If

already applied
, skip to Phase 3 (Verify).

Phase 2: Apply Code Changes

Ensure the upstream remote

git remote -v

If an

upstream
remote pointing to
https://github.com/qwibitai/nanoclaw.git
is missing, add it:

git remote add upstream https://github.com/qwibitai/nanoclaw.git

Merge the skill branch

git fetch upstream skill/channel-formatting
git merge upstream/skill/channel-formatting

If there are merge conflicts on

package-lock.json
, resolve them by accepting the incoming version and continuing:

git checkout --theirs package-lock.json
git add package-lock.json
git merge --continue

For any other conflict, read the conflicted file and reconcile both sides manually.

This merge adds:

  • src/text-styles.ts
    parseTextStyles(text, channel)
    for marker substitution and
    parseSignalStyles(text)
    for Signal native rich text
  • src/router.ts
    formatOutbound
    gains an optional
    channel
    parameter; when provided it calls
    parseTextStyles
    after stripping
    <internal>
    tags
  • src/index.ts
    — both outbound
    sendMessage
    paths pass
    channel.name
    to
    formatOutbound
  • src/formatting.test.ts
    — test coverage for both functions across all channels

Validate

npm install
npm run build
npx vitest run src/formatting.test.ts

All 73 tests should pass and the build should be clean before continuing.

Phase 3: Verify

Rebuild and restart

npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw   # macOS
# Linux: systemctl --user restart nanoclaw

Spot-check formatting

Send a message through any registered WhatsApp or Telegram chat that will trigger a response from Claude. Ask something that will produce formatted output, such as:

Summarise the three main advantages of TypeScript using bullet points and bold headings.

Confirm that the response arrives with native bold (

*text*
) rather than raw double asterisks.

Check logs if needed

tail -f logs/nanoclaw.log

Signal Skill Integration

If you have the Signal skill installed,

src/channels/signal.ts
can import
parseSignalStyles
from the newly present
src/text-styles.ts
:

import { parseSignalStyles, SignalTextStyle } from '../text-styles.js';

parseSignalStyles
returns
{ text: string, textStyle: SignalTextStyle[] }
where
textStyle
is an array of
{ style, start, length }
objects suitable for the
signal-cli
JSON-RPC
textStyles
parameter (format:
"start:length:STYLE"
).

Removal

# Remove the new file
rm src/text-styles.ts

# Revert router.ts to remove the channel param
git diff upstream/main src/router.ts   # review changes
git checkout upstream/main -- src/router.ts

# Revert the index.ts sendMessage call sites to plain formatOutbound(rawText)
# (edit manually or: git checkout upstream/main -- src/index.ts)

npm run build