Agents / Claude Code
Claude Code
The lwis plugin delivers your captures into the Claude Code sessions you choose. It ships inside the lwis binary, stays inert until you attach a session, and never relays tool approvals from a device.
Set up the plugin
lwis setup claude # install the plugin for every project, start lwisd once
lwis install-service --enable # keep lwisd running across logins
lwis pair --label laptop # pair your phone or browser, if you haven't yet
lwis setup claude writes the plugin as a local marketplace (lwis-local) under ~/.local/share/lwis/claude and installs lwis@lwis-local at user scope. Its hooks and MCP server call this lwis binary by absolute path with the daemon's runtime directory, so PATH and XDG_RUNTIME_DIR don't matter inside Claude Code.
| Flag | Effect |
|---|---|
--channels | Prefer channels over the session inbox. Only useful where your organisation enables channels; start Claude with claude --channels plugin:lwis@lwis-local. |
--no-install | Only write the plugin files; load them with claude --plugin-dir <dir>. |
--claude <path> | The claude executable to use (default: from PATH). |
--remove | Uninstall the plugin and delete its files. |
Plugins load when a session starts. Exit sessions that were already running, then claude --continue reopens the same conversation.
The hooks never start lwisd. If the daemon isn't running, the plugin's tools answer "lwisd not running" and the hooks stay silent. Run it as a service, or by hand with
lwis up(lwis up --idle-exit 60exits after an hour with nothing attached).
After an update, the next session start asks a running lwisd to re-exec itself from the updated binary (only the same file, only a newer version; logged in lwis log).
Attach a session
A new session is not reachable: your devices don't list it, and captures addressed to it are refused. Once per project, the session start shows "lwis: this session isn't attached; /lwis:on to receive captures".
| Command | Effect |
|---|---|
/lwis:on | Attach this session. Captures arrive on your next prompt, with a desktop notification. |
/lwis:on --wake | Also let a capture start a turn while the session is idle. |
/lwis:on --allow-replies | Send lwis_reply without asking you each time. |
/lwis:off | Detach. Waiting captures and replies are dropped. |
lwis allow <dir> | Attach every session started in that directory or below (--wake, --allow-replies accepted). |
lwis disallow <dir>, lwis allowed | Remove a directory, list the allowlist. |
lwis attach [sid], lwis detach [sid] | The same from a terminal, by session id. |
LWIS_ATTACH=1 claude | Attach from the start (LWIS_ATTACH=wake also wakes). |
An explicit /lwis:on or /lwis:off is remembered per Claude session for 30 days (it follows /clear and /resume), and an explicit detach beats the allowlist. lwis sessions lists every session lwisd knows, attached or not.
The slash commands carry disable-model-invocation, so Claude doesn't run them by itself. An agent allowed to run shell commands without asking could still run lwis directly: your permission prompts are that gate.
How captures reach a session
lwisd picks a delivery path ("rung") per capture and per attached session. lwis doctor shows which one each session is on, and why.
| Rung | How | Starts a turn when idle? |
|---|---|---|
| R0 | Channel notification: the first choice with "ladder_channels": "prefer" (set by lwis setup claude --channels), and the fallback for --wake sessions without a usable inbox socket (R2 as backup) | Yes, where channels are enabled |
| R1 | The session's own inbox socket (Claude Code 2.1.224 or later), only for sessions attached with --wake | Yes |
| R2 | The UserPromptSubmit hook adds the capture to your next prompt. The default. | No |
| R3 | A desktop notification and the statusline segment | No, you act |
- Without
--wake, a capture never starts a turn: it waits for your next prompt and a desktop notification tells you it's there (wherenotify-sendorosascriptand a display are available). The exception islwis setup claude --channels: with channels preferred, R0 delivers to every attached session. - Claude Code labels an inbox message as coming from another process. A session that bypasses permission prompts holds it for your approval, and
crossSessionInbound: "hold"in your Claude settings does the same. A held message counts as delivered: lwisd never steps around a hold. crossSessionInbound: "refuse"turns R1 off: lwisd tries the channel (if your organisation enables channels), with R2 and R3 as backup.- Team and Enterprise organisations have to enable channels (
channelsEnabled: truein managed settings). Without that, Claude Code logsChannel notifications skipped: channels not enabled by org policy.
Inside Claude, the plugin provides four tools: lwis_get_capture (view a capture's images, gestures and page targets), lwis_list_captures (captures still available to this session, for up to 24 hours or until the session ends), lwis_reply (a plain-text reply to the device, at most 1000 characters; to an agent pairing's report, up to 8000 and one file) and lwis_request_capture (ask you to take a capture on a paired device).
Held captures and strict mode
Some sessions only get a stub instead of the capture. Claude can view it after you accept it:
/lwis:accept c_…
Captures are held when:
- the session's permission mode approves tools by itself (bypass,
acceptEdits,auto,dontAsk, …). Onlydefaultandplan(or a session that hasn't reported a mode yet, such aslwis mcpsessions) receive captures directly; - you started the session with
LWIS_STRICT=1 claude …, or ranlwis sessions strict <sid> on; - the capture comes from a device with
confirmtrust, such as the hosted web app. See Your devices.
For a capture from a confirm device, lwis accept <cid> in a terminal also works: the capture is then delivered to its session through the usual rung.
Reply approval
When Claude calls lwis_reply, lwisd keeps the reply until you approve it, and shows a desktop notification.
-
Review the waiting replies in a terminal, not through Claude:
lwis repliesIt prints each reply's id (
ra_…), project, recipient and text. Ids are never shown inside Claude, so the model can't approve its own replies. -
Send one with
/lwis:approve-reply <id>in the session, orlwis reply-approve <id>in a terminal. -
Drop one with
lwis reply-deny <id>.
A reply nobody approves expires after 1 hour; lwis log records it as reply_expired.
/lwis:approve-reply without an id only says how many replies wait. Attach with --allow-replies to skip approval for a session.
Statusline
lwis statusline prints a segment for your status line. Compose it into your own command rather than replacing it:
"statusLine": {"type": "command", "command": "input=$(cat); echo \"$(echo \"$input\" | your-statusline) $(echo \"$input\" | lwis statusline)\""}
| Segment | Meaning |
|---|---|
📷 lwis | This session is attached (nothing is printed for an unattached one while lwisd runs). |
📷 lwis 2 | Two captures wait for your next prompt. |
📷 lwis 1⏸ | One capture waits for /lwis:accept. |
📷 lwis ✉1 | One reply waits for your approval. |
📷 lwis paused | Delivery is paused (lwis resume). |
📷 lwisd off | The daemon isn't running. |
Audit log and pause
lwis loglists every delivery (device, session, rung, size, held or confirm), reply, attach and detach, pause and resume, and trust change. Add--json, or-n 100for more lines.lwis pausestops delivery to every session and hosted agent. Captures are acked as queued and kept for up to 24 hours;lwis resumedelivers them. You can also pause from a paired device: see Your devices.
Troubleshooting
Start with:
lwis doctor # dirs, lwisd, pairings, service, browsers, and each session's rung
lwis sessions # attached or not, strict or held, waiting replies
| Symptom | Fix |
|---|---|
/mcp shows the lwis server with "tools fetch failed" | Update to 0.8.2 or later (lwis self-update), then restart the session. Earlier versions failed Claude Code's newer MCP handshake. |
| Tools answer "lwisd not running" | lwis up, or lwis install-service --enable. |
| Your devices don't list the session | Attach it: /lwis:on, or lwis allow <dir>. |
| Captures only arrive when you type | That's the default. Attach with /lwis:on --wake to wake idle sessions. |
| doctor says "inbox socket: not registered" | Claude Code older than 2.1.224, bare mode, a container boundary, or the SessionStart hook didn't run. R2 still works. |
| doctor says lwisd runs inside a Claude Code process tree | Restart it outside Claude: lwis down, then lwis install-service or lwis up. R1 is refused until then. |
| The session doesn't have the plugin | It started before lwis setup claude: exit and run claude --continue. |
XDG_RUNTIME_DIR is not set (a root shell, a container) | Create it as the message says: mkdir -p -m 700 /run/user/$(id -u) && export XDG_RUNTIME_DIR=/run/user/$(id -u). |