Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/how-to/browse-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ Send `/browse` to open the project root:

Untether replies with a directory listing rendered as inline keyboard buttons. Each button is a file or directory you can tap.

<!-- SCREENSHOT: /browse showing project root with directory and file buttons -->
!!! untether "Untether"
**/ happy-gadgets**

`📁 src/` · `📁 docs/` · `📁 tests/`<br>
`📄 pyproject.toml` · `📄 README.md`<br>
`📄 CHANGELOG.md` · `📄 .gitignore`<br>
`(..)` back

## Navigate directories

Expand Down
13 changes: 12 additions & 1 deletion docs/how-to/chat-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ If you chose **handoff** during onboarding and want to switch to chat mode:

With `session_mode = "chat"`, new messages in the chat continue the current thread automatically.

<!-- SCREENSHOT: Telegram chat showing a follow-up message auto-resuming the previous session without a reply -->
!!! user "You"
explain the auth flow

!!! untether "Untether"
done · claude · 15s · step 4

The auth flow uses JWT tokens…

!!! user "You"
now add rate limiting to it

The second message automatically continues the same session — no reply needed.

## Reset a session

Expand Down
12 changes: 10 additions & 2 deletions docs/how-to/cost-budgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ After each run completes, Untether checks the reported cost against your budgets

When `auto_cancel = true` and a budget is exceeded, Untether cancels the run automatically. Otherwise, you see the alert but the run continues.

<!-- SCREENSHOT: Telegram cost warning alert message showing budget threshold exceeded notification -->
!!! untether "Untether"
⚠️ **cost warning** — run cost $1.45 is 73% of $2.00 per-run budget

### Daily reset

Expand All @@ -72,7 +73,14 @@ This shows:

The `/usage` command reads your Claude Code OAuth credentials to fetch live data from the Anthropic API. If you see "No Claude credentials found", run `claude login` in your terminal.

<!-- SCREENSHOT: Telegram /usage command output showing 5h window, weekly usage, per-model breakdown, and extra credits -->
!!! untether "Untether"
**Claude Code usage**

**5h window**: 42% used (2h 6m left)<br>
**Weekly**: 28% used (5d 2h left)

sonnet: 38% · opus: 4%<br>
extra credits: $0.00

## Subscription usage footer

Expand Down
7 changes: 6 additions & 1 deletion docs/how-to/dev-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ uv run untether # Ctrl+C first if already running
# 4. Test via @your_dev_bot in Telegram
```

<!-- SCREENSHOT: journalctl output showing untether-dev starting cleanly -->
```
$ journalctl --user -u untether-dev -f
Mar 10 09:15:23 lba-1 untether[12345]: untether.started version=0.34.0 engine=codex projects=3
Mar 10 09:15:23 lba-1 untether[12345]: telegram.connected bot=@untether_dev_bot
Mar 10 09:15:23 lba-1 untether[12345]: telegram.polling started
```

Always test via the dev bot before merging. Never send test messages to the production bot.

Expand Down
6 changes: 4 additions & 2 deletions docs/how-to/file-transfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Use `--force` to overwrite:
/file put --force docs/spec.pdf
```

<!-- SCREENSHOT: Telegram document upload with /file put caption showing the file saved confirmation message -->
!!! untether "Untether"
📄 saved `docs/spec.pdf` (42 KB)

## Fetch a file (`/file get`)

Expand All @@ -67,7 +68,8 @@ Send:

Directories are zipped automatically.

<!-- SCREENSHOT: Telegram /file get response showing the fetched file sent as a document in the chat -->
!!! untether "Untether"
📎 `src/main.py` (1.2 KB)

## Related

Expand Down
2 changes: 0 additions & 2 deletions docs/how-to/inline-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ Send `/config` in any chat:

The home page shows current values for all settings:

<!-- SCREENSHOT: /config home page showing inline keyboard buttons for Plan mode, Verbose, Engine, Model, Trigger with current values -->

```
Settings

Expand Down
46 changes: 40 additions & 6 deletions docs/how-to/interactive-approval.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,39 @@ When a permission request arrives, you see a message with the tool name and a co

Buttons clear immediately when you tap them — no waiting for a spinner.

<!-- SCREENSHOT: Telegram approval message showing Approve / Deny / Pause & Outline Plan inline buttons beneath a tool call summary -->
<div markdown>

!!! untether "Untether"
▸ Permission Request [CanUseTool] - tool: Edit (file_path=src/main.py)<br>
📝 src/main.py<br>
`- import sys`<br>
`+ import sys`<br>
`+ from pathlib import Path`

<div class="tg-buttons">
<span class="tg-btn">Approve</span>
<span class="tg-btn">Deny</span>
<span class="tg-btn">Pause &amp; Outline Plan</span>
</div>

</div>

## Diff previews

For tools that modify files, the approval message includes a compact diff so you can see what's about to change before deciding:

- **Edit**: shows removed lines (`- old`) and added lines (`+ new`), up to 4 lines each
- **Write**: shows the first 8 lines of content to be written
- **Bash**: shows the command to be run (up to 200 characters)
- **Edit**: 📝 file path, removed lines (`- old`) and added lines (`+ new`), up to 4 lines each
- **Write**: 📝 file path, then the first 8 lines of content to be written
- **Bash**: `$ command` (up to 200 characters)

This lets you make informed approve/deny decisions without leaving Telegram.

<!-- SCREENSHOT: Telegram approval message with a compact diff preview showing removed and added lines for an Edit tool call -->
!!! untether "Untether"
▸ Permission Request [CanUseTool] - tool: Edit (file_path=src/main.py)<br>
📝 src/main.py<br>
`- import sys`<br>
`+ import sys`<br>
`+ from pathlib import Path`

## Answering questions

Expand All @@ -50,7 +70,21 @@ When Claude Code calls `AskUserQuestion`, Untether renders the question with int

Toggle ask mode on or off via `/config` → Ask mode. When off, questions are auto-denied and Claude Code proceeds with defaults.

<!-- SCREENSHOT: Telegram AskUserQuestion message showing option buttons and "Other (type reply)" -->
<div markdown>

!!! untether "Untether"
❓ Which test framework should I use?

<div class="tg-buttons">
<span class="tg-btn">pytest</span>
<span class="tg-btn">unittest</span>
</div>
<div class="tg-buttons">
<span class="tg-btn">Other (type reply)</span>
<span class="tg-btn">Deny</span>
</div>

</div>

## Push notifications

Expand Down
41 changes: 38 additions & 3 deletions docs/how-to/plan-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,18 @@ When Claude Code tries to exit plan mode (ExitPlanMode), you see three buttons i
- **Deny** — block and ask Claude Code to explain
- **Pause & Outline Plan** — require a written plan first

<!-- SCREENSHOT: Telegram ExitPlanMode message with Approve / Deny / Pause & Outline Plan inline buttons -->
<div markdown>

!!! untether "Untether"
▸ Permission Request [CanUseTool] - tool: ExitPlanMode

<div class="tg-buttons">
<span class="tg-btn">Approve</span>
<span class="tg-btn">Deny</span>
<span class="tg-btn">Pause &amp; Outline Plan</span>
</div>

</div>

Tapping "Pause & Outline Plan" tells Claude Code to stop and write a comprehensive plan as a visible message in the chat. The plan must include:

Expand All @@ -53,7 +64,21 @@ This is useful when you want to review the approach before Claude Code starts ma

After Claude Code writes the outline, **Approve Plan / Deny** buttons appear automatically in Telegram. Tap "Approve Plan" to let Claude Code proceed, or "Deny" to stop and provide feedback. You no longer need to type "approved" — the buttons handle it.

<!-- SCREENSHOT: Telegram message showing Claude Code's written outline plan with Approve Plan / Deny inline buttons below -->
<div markdown>

!!! untether "Untether"
Here's my plan:

1. **Read** `src/main.py` to understand current structure
2. **Edit** `src/main.py` to refactor the `process()` function
3. **Run** tests to verify no regressions

<div class="tg-buttons">
<span class="tg-btn">Approve Plan</span>
<span class="tg-btn">Deny</span>
</div>

</div>

## Progressive cooldown

Expand All @@ -70,7 +95,17 @@ During the cooldown, any ExitPlanMode attempt is automatically denied, but **App

This prevents the agent from bulldozing through when you've asked it to slow down and explain its approach, while still giving you a one-tap way to approve once you're satisfied.

<!-- SCREENSHOT: Telegram message showing auto-denied ExitPlanMode during cooldown with Approve Plan / Deny buttons -->
<div markdown>

!!! untether "Untether"
▸ Plan outlined — approve to proceed

<div class="tg-buttons">
<span class="tg-btn">Approve Plan</span>
<span class="tg-btn">Deny</span>
</div>

</div>

## Related

Expand Down
4 changes: 3 additions & 1 deletion docs/how-to/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ cd ~/dev/happy-gadgets
untether init happy-gadgets
```

<!-- SCREENSHOT: Terminal output of untether init showing project registration confirmation -->
```
saved project 'happy-gadgets' to ~/.untether/untether.toml
```

This adds a project to your config:

Expand Down
11 changes: 10 additions & 1 deletion docs/how-to/route-by-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ Then send any message in the target chat. Untether captures the `chat_id` and up

Messages from that chat now default to the project.

<!-- SCREENSHOT: Telegram chat bound to a project, showing a message routed to the correct repo with project context in the footer -->
!!! user "You"
fix the failing tests

!!! untether "Untether"
done · codex · 8s · step 3

Fixed the two failing assertions in test_auth.py…

dir: happy-gadgets<br>
`codex resume abc123`

## Rules for chat ids

Expand Down
3 changes: 2 additions & 1 deletion docs/how-to/schedule-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ In Telegram, long-press the send button and choose **Schedule Message** to run t

This is the simplest approach — no server or config changes needed.

<!-- SCREENSHOT: Telegram scheduled message picker showing the Schedule Message option for a task -->
!!! tip "How to schedule"
In Telegram, **long-press the send button** (iOS) or tap the **clock icon** (Android/Desktop) and choose **Schedule Message**. Pick a date and time, then tap **Send**. Untether receives the message at the scheduled time and starts the run automatically.

## Cron triggers

Expand Down
7 changes: 6 additions & 1 deletion docs/how-to/switch-engines.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ Prefix the first non-empty line with an engine directive:

Directives are only parsed at the start of the first non-empty line.

<!-- SCREENSHOT: Telegram chat showing an engine directive message (e.g. /codex) with the engine name in the progress footer -->
!!! untether "Untether"
working · codex · 5s · step 1

✓ Read `src/timeline.py`

🏷 codex

## Set a default engine for the current scope

Expand Down
5 changes: 4 additions & 1 deletion docs/how-to/topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ Examples:

Untether will bind the topic and rename it to match the context.

<!-- SCREENSHOT: Telegram forum topic bound to a project and branch, showing the renamed topic title and context footer -->
!!! untether "Untether"
topic bound: **backend** @feat/api

Topic renamed to `backend @feat/api`

## Inspect or change the binding

Expand Down
23 changes: 21 additions & 2 deletions docs/how-to/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ untether --debug # start with debug logging → writes debug.log
untether doctor # preflight check: token, chat, topics, files, voice, engines
```

<!-- SCREENSHOT: untether doctor output showing check results -->
```
$ untether doctor
✓ bot token valid (@my_untether_bot)
✓ chat 123456789 reachable
✓ engine codex found at /usr/local/bin/codex
✓ engine claude found at /usr/local/bin/claude
✗ engine opencode not found
✓ voice transcription configured
✓ file transfer directory exists
```

## Bot not responding

Expand Down Expand Up @@ -258,7 +267,17 @@ It validates:
- Voice transcription configuration (API reachability)
- Engine CLI availability (on PATH)

<!-- SCREENSHOT: untether doctor output with all checks passing -->
```
$ untether doctor
✓ bot token valid (@my_untether_bot)
✓ chat 123456789 reachable
✓ engine codex found at /usr/local/bin/codex
✓ engine claude found at /usr/local/bin/claude
✓ engine opencode found at /usr/local/bin/opencode
✓ voice transcription configured
✓ file transfer directory exists
all checks passed
```

## Checking logs

Expand Down
2 changes: 0 additions & 2 deletions docs/how-to/verbose-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ Compact mode shows only the action status and title — no extra detail. This is

Here's the same action shown in both modes:

<!-- SCREENSHOT: Side-by-side or sequential comparison of compact vs verbose progress for the same tool action in Telegram -->

!!! note "Compact"
```
...tool: edit: Update import order
Expand Down
10 changes: 9 additions & 1 deletion docs/how-to/voice-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ requires a specific model name, set `voice_transcription_model` (for example,
When you send a voice note, Untether transcribes it and runs the result as a normal text message.
If transcription fails, you’ll get an error message and the run is skipped.

<!-- SCREENSHOT: Telegram voice note message followed by the transcribed text and agent run output -->
!!! user "You"
🎤 *(voice note — 0:12)*

!!! untether "Untether"
📝 *"Add error handling to the upload function and make sure it retries on timeout"*

working · claude · 0s

▸ Read `src/upload.py`

## Related

Expand Down
7 changes: 6 additions & 1 deletion docs/how-to/webhooks-and-cron.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ curl -X POST http://127.0.0.1:9876/hooks/github \

A `202 Accepted` response means the run was dispatched.

<!-- SCREENSHOT: Telegram notification from a webhook-triggered run showing the rendered prompt and agent progress -->
!!! untether "Untether"
🔔 **webhook** · github-push

Review push to refs/heads/main by alice

working · claude · 4s · step 1

## Set up a cron schedule

Expand Down
8 changes: 7 additions & 1 deletion docs/how-to/worktrees.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ Send a message like:
/happy-gadgets @feat/memory-box freeze artifacts forever
```

<!-- SCREENSHOT: Telegram message showing a worktree run with the @branch directive and project context in the footer -->
!!! untether "Untether"
working · codex · 5s · step 2

✓ Read `src/memory.py`<br>
… Edit `src/memory.py`

dir: happy-gadgets @feat/memory-box

## Ignore `.worktrees/` in git status

Expand Down
Loading
Loading