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
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ SUMMARIZER_MODEL=claude-sonnet-4-6
SCROLLBACK_LINES=500
INTERMEDIATE_INTERVAL=10

# Boot notification — when set to 1 (default), LiteClaw sends a one-shot
# "🚀 LiteClaw ready" message to your Telegram chat after the daemon
# finishes initializing. Set to 0 to disable.
BOOT_NOTIFY=1

# OpenClaw-style memory layout. Per-day transcripts, per-day markdown
# summaries, rolling strategic compact, and a synthesized startup primer
# all live under LITECLAW_DIR.
# LITECLAW_DIR=~/.liteclaw
# PRIMER_RECENT_TURNS=20

# Pin the cwd Claude Code launches in so `claude --continue` consistently
# resumes the same project bucket under ~/.claude/projects/<encoded-cwd>/.
# Defaults to $HOME if unset.
# CLAUDE_CWD=/Users/you

# File transfer
STAGING_DIR=~/liteclaw-files

Expand Down
48 changes: 48 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: Bug report
about: Something isn't working as expected
title: "[bug] "
labels: bug
assignees: ''
---

## What happened?

<!-- A clear, one-sentence description of the bug. -->

## Environment

- **OS**: <!-- macOS 14 / Ubuntu 22.04 / WSL2 / etc. -->
- **Python**: <!-- python3 --version -->
- **tmux**: <!-- tmux -V -->
- **Claude Code CLI**: <!-- claude --version -->
- **LiteClaw commit**: <!-- git rev-parse --short HEAD -->

## How to reproduce

1. ...
2. ...
3. ...

## Expected vs actual

- **Expected**: ...
- **Actual**: ...

## Logs

<details>
<summary>Relevant lines from <code>/tmp/liteclaw_run.log</code></summary>

```
(paste log excerpt here — redact BOT_TOKEN if present)
```

</details>

<!-- If the issue is about a cron: also paste the relevant entry from
~/.liteclaw/cron-error-capture.md -->

## Additional context

<!-- Screenshots, unusual config in .env (redact secrets), anything else. -->
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Feature request
about: Suggest an improvement or new capability
title: "[feat] "
labels: enhancement
assignees: ''
---

## The problem

<!-- What friction, limitation, or missing capability prompted this? -->

## Proposed solution

<!-- Your preferred shape of the fix/feature. Concrete > abstract.
Example: "/recall skill <name> that surfaces all turns touching a given
skill" — not "better recall". -->

## Alternatives considered

<!-- Other approaches you thought about and why they're worse. -->

## Scope / impact

- [ ] Config-only (new env var, toggle)
- [ ] Single-file Python change in `liteclaw.py`
- [ ] Touches shell scripts (`start.sh`, `setup.sh`, `bin/liteclaw`)
- [ ] Touches storage layout (`~/.liteclaw/` or `~/.claude/projects/...`)
- [ ] New Telegram command
- [ ] Needs external integration (API proxy, bot webhook, etc.)

## Additional context

<!-- Screenshots, links, related issues. -->
62 changes: 62 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: CI

on:
push:
branches: [main, feat/*]
pull_request:
branches: [main]

jobs:
syntax-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Python syntax check (liteclaw.py)
run: python -c "import ast; ast.parse(open('liteclaw.py').read()); print('liteclaw.py syntax OK')"

- name: Bash syntax check (shell entry points)
run: |
bash -n setup.sh
bash -n start.sh
bash -n bin/liteclaw
echo "bash syntax OK"

- name: Install requirements (import check)
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
python -c "
import importlib
for mod in ('httpx', 'telegram', 'dotenv', 'apscheduler'):
importlib.import_module(mod)
print(f'import {mod}: OK')
"

- name: Static import check on liteclaw.py
run: |
# `python -c "import liteclaw"` would try to spin up the bot; we only
# want to verify the module parses and top-level imports resolve.
python -c "
import ast, sys
src = open('liteclaw.py').read()
tree = ast.parse(src)
top_imports = [n for n in tree.body if isinstance(n, (ast.Import, ast.ImportFrom))]
print(f'{len(top_imports)} top-level imports parse cleanly')
"

lint-ruff:
runs-on: ubuntu-latest
continue-on-error: true # linting is advisory; don't fail the build while codebase catches up
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install ruff
- run: ruff check liteclaw.py --output-format=github || true
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
# Changelog

## v0.6.1 (2026-04-23) — Daemon-restart resilience, /tell-summarizer, community hygiene

### Added
- **Daemon-restart resilience** — user replies no longer disappear when the daemon is restarted mid-poll.
- `~/.liteclaw/pending_replies.json` records every user message whose placeholder ("⏳ 작업 중…") was sent but not yet answered (user_msg_id, placeholder_msg_id, chat_id, jsonl offset, sent timestamp, target).
- `_resume_pending_on_boot` runs from `Application.post_init`. For each pending entry it either (a) reads the session jsonl and edits the placeholder with the now-completed answer, (b) spawns a background task to finish polling, or (c) marks the placeholder as "재기동으로 유실됨" if older than `PENDING_MAX_AGE_SEC` (default 900 s).
- `run_polling` now passes `stop_signals=(SIGINT, SIGTERM)` explicitly so `pkill liteclaw` triggers PTB's graceful drain. Hard kills / crashes are covered by the persistent-pending path.
- New env: `PENDING_MAX_AGE_SEC` (default 900).
- **`/tell-summarizer` command** (alias `/tellsum`) — runtime control over the summarizer system prompt. `/tellsum <instruction>` appends, `/tellsum show` displays, `/tellsum clear` resets. Persists in-memory only.
- **Community hygiene** — `CONTRIBUTING.md`, `.github/workflows/ci.yml` (AST parse + `bash -n` + requirements import smoke + advisory ruff lint), `.github/ISSUE_TEMPLATE/{bug_report,feature_request}.md`, shields.io badges in both READMEs.

### Changed
- **`SUMMARIZE_PROMPT`** gained a **PRESERVE-AS-IS** block — numbered / lettered option lists ("B / C / D 선택") must reach Telegram verbatim. The summarizer previously kept "Pick one" while dropping the option bodies.

---

## v0.6.0 (2026-04-20 → 2026-04-21) — jsonl response path, session memory, global CLI, cron hardening

(Merged via https://github.com/breaktheready/liteclaw/pull/1)

### Added
- **Global `liteclaw` CLI** — `liteclaw start|stop|restart|status|logs|attach` from any directory. `setup.sh` installs a symlink at `~/.local/bin/liteclaw`.
- **Pinned Claude Code session via `--session-id <uuid>`** — `start.sh` allocates / adopts a stable UUID in `~/.liteclaw/sessions.json.liteclaw_session_id` and always resumes the same conversation, unaffected by other Claude Code windows in the same cwd.
- **JSONL response path** (Phase A) — responses are lifted from Claude Code's own session log (`~/.claude/projects/<encoded-cwd>/<id>.jsonl`) instead of scraping the tmux pane. No ANSI chrome, no scrollback truncation, no summarizer over-compression. Automatic fallback to pane-scrape on jsonl failure.
- **OpenClaw-style memory layout** (`~/.liteclaw/`) — per-day transcripts, daily markdown digests (LLM-compacted), rolling strategic summary, startup primer. Legacy `~/.liteclaw-history.jsonl` auto-migrates.
- **Compact session-id alias (`sid`)** in transcripts — integer index into `sessions.json.history[]`, ~70 % shorter per row than embedding the full UUID.
- **`/recall session [uuid]`** — session-scoped recall.
- **Boot-ready Telegram ping** — one-shot "🚀 LiteClaw ready" after init, rate-limited to 5 min.
- **`~/.liteclaw/cron-error-capture.md`** — forensic log of failed cron runs (job config + pane snapshot).

### Fixed
- **`is_idle_prompt` false-positive** on Claude Code UI chrome ("· Claude Max", "· Share Claude Code…") that jammed the cron busy-wait at 120 s. Removed `·` and `*` from `_SPINNER_CHARS`, anchored `_ACTIVITY_PATTERNS` to line start.
- **Cron trust prompt** auto-accept in `_run_cron_job` for unattended project dirs.
- **APScheduler weekday indexing** — `1-5` was Tue-Sat instead of Mon-Fri.
- **Mid-poll status edits** leaking "Thinking (Crystalizing)…" into Telegram. Gated behind `SHOW_POLLING_STATUS` (off by default).
- **`_followup_edit` overwriting delivered messages** with stale status edits when jsonl path had already returned a complete turn. Now skipped when `_jsonl_delivered_complete` is set.

### Changed
- Placeholder text "📤 Sent. Waiting for response…" → "⏳ 작업 중…".
- `setup.sh` plants sensible `~/.tmux.conf` defaults when missing (mouse on, history-limit 50000).

---

## v0.5.0 (2026-04-17) — CLI Mirror, Draft Streaming, Reasoning Lane & Skill System

### Added
Expand Down
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,10 @@ Ensures response delivery even when infrastructure is degraded.
## Auto-Recovery Mechanisms

### API Proxy Recovery
- On Tier 1 failure: runs `docker compose up -d` in the proxy directory
- On Tier 1 failure: runs `docker compose up -d` in the proxy directory (Linux hosts)
- Retries connection after restart
- Notifies user via Telegram on successful recovery
- **macOS note**: proxy runs as LaunchAgent, not Docker — recovery would no-op. launchd's `KeepAlive` handles crash restarts automatically. See [MAC-OPS.md](./MAC-OPS.md).

### Session Auth Recovery (401)
- Detects "401" + auth keywords in tmux pane output
Expand Down Expand Up @@ -350,10 +351,11 @@ Direct `tmux send-keys -l` crashes on quotes, newlines, and special characters.

### Infrastructure: API Proxy
LiteClaw's Tier 1 summarizer depends on an OpenAI-compatible API proxy.
- If down: auto-recovery attempts `docker compose up -d`
- If down: auto-recovery attempts `docker compose up -d` (Linux/auto-recovery path only)
- Manual check: `curl -s http://localhost:3456/v1/models`
- Recommended proxy: [mattschwen/claude-max-api-proxy](https://github.com/mattschwen/claude-max-api-proxy) — Docker compose, reuses Claude Max subscription
- Recommended proxy: [mattschwen/claude-max-api-proxy](https://github.com/mattschwen/claude-max-api-proxy) — reuses Claude Max subscription
- **Degraded without proxy** — Tier 2/3 still work but quality drops
- **macOS**: Docker cannot access the user keychain where Claude stores OAuth tokens. Run the proxy as a LaunchAgent instead — see [MAC-OPS.md](./MAC-OPS.md).

### Startup Sequence

Expand Down
85 changes: 85 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Contributing to LiteClaw

Thanks for considering a contribution. LiteClaw is a small, opinionated
single-file Python bridge — changes should match that shape.

## Ground rules

- **Keep the core a single file.** `liteclaw.py` intentionally ships as one
file so operators can read / fork / deploy with zero ceremony. Factor into
separate modules only when the piece is genuinely standalone (e.g., a new
executable under `bin/` or an isolated helper).
- **No new Anthropic API key.** The project's reason-for-being is "reuse
your existing Claude Code CLI session." A patch that introduces a hard
dependency on `ANTHROPIC_API_KEY` or a specific vendor SDK will be closed.
- **Fallbacks are non-negotiable.** Anything that relies on an external
service (summarizer proxy, jsonl path, pyannote, etc.) must degrade
gracefully, not crash, when the external piece is unavailable.
- **Prefer environment variables over new CLI flags.** LiteClaw is
configured via `.env`. New knobs should land in `.env.example` with a
one-line description and a sane default.

## Project structure

```
liteclaw/
├── liteclaw.py # core daemon (~5k lines, single file)
├── start.sh # tmux session + session-id pin
├── setup.sh # one-shot install (deps, venv, CLI symlink)
├── bin/liteclaw # global CLI dispatcher
├── .env.example # all configuration knobs
├── README.md / README_KO.md # user docs
├── MAC-OPS.md # macOS proxy/launchd notes
└── DEVNOTES.md # NOT shipped — session-scoped devnotes (gitignored)
```

## Local sanity checks

Before opening a PR run at least:

```bash
python3 -c "import ast; ast.parse(open('liteclaw.py').read()); print('OK')"
bash -n setup.sh start.sh bin/liteclaw
```

The GitHub Actions workflow `.github/workflows/ci.yml` runs the same checks
plus a dependency import test on every push / PR.

## Commit style

- Small, logical commits. Split a feature from refactors from docs.
- Title prefix: `[feat]`, `[fix]`, `[docs]`, `[chore]`, `[refactor]`.
- Body: wrap at ~80 chars, explain the **why** and cite line numbers for
non-obvious changes.

## Telegram-side behavior changes

If your change affects what ends up in the user's Telegram chat (summarizer
prompt, follow-up edits, jsonl path, etc.), include a short "user-visible
diff" section in the PR description — ideally a before/after screenshot or
transcript snippet. Regressions in Telegram UX have been subtle and hard to
notice in code review alone.

## Issues and discussion

- Bug report → [template](.github/ISSUE_TEMPLATE/bug_report.md). Please
include `/tmp/liteclaw_run.log` excerpts (redact `BOT_TOKEN`).
- Feature request → [template](.github/ISSUE_TEMPLATE/feature_request.md).
Concrete proposals move faster than abstract wishes.

## Security

If you find a security issue (credential leak, RCE surface, etc.), please
do NOT open a public issue. Contact the author directly via GitHub
(@breaktheready) with details.

## Release flow

Maintainer-only:

1. Merge PRs into `main`.
2. Tag `vX.Y.Z` on `main`.
3. GitHub Release with notes summarizing the "What's new in vX.Y" section
from the README.

Thanks for making LiteClaw sharper.
Loading
Loading