@primusclaw/connector 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +106 -25
  2. package/dist/cli.js +1684 -356
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -41,10 +41,10 @@ If npm answers `EACCES`, point its global prefix at a directory you own (`npm co
41
41
  ```sh
42
42
  claw-connector init # writes agents.toml and prints where: ~/.config/claw-connector/agents.toml unless XDG_CONFIG_HOME or CLAW_CONNECTOR_HOME is set
43
43
  claw-connector status # after you edit it: prints a "problem:" line if the file is wrong
44
- claw-connector doctor # starts each agent's adapter, opens a session (no prompt) and checks the modes it offers
44
+ claw-connector doctor # checks the machine (step 5), then starts each agent's adapter, opens a session (no prompt) and checks the modes it offers
45
45
  ```
46
46
 
47
- `init` runs the same check as `doctor` at the end. `doctor` says `ok` for an agent whose phone sessions will open, and otherwise a
47
+ `init` runs the adapter check of `doctor` at the end. `doctor` says `ok` for an agent whose phone sessions will open, and otherwise a
48
48
  `problem:` line with the fix, for example an agent whose sessions would be refused with `mode_unavailable`.
49
49
 
50
50
  By default every agent asks before it writes a file or runs a command, and your phone answers each question. To let phone sessions run
@@ -88,13 +88,32 @@ Only `claw-connector stop` (or stopping the service) stops it for good: a `kill`
88
88
  with exit 75, and it is started again.
89
89
  It uses `systemd --user` on Linux and launchd on macOS. On Linux, run `loginctl enable-linger $USER` once (`service install` says so
90
90
  when it is needed) so it also runs while you are logged out. In a container without systemd, `service install` starts a small
91
- supervisor and writes `keepalive.sh`; have the container's startup command and a 5-minute cron job run that script (it does nothing
92
- when the supervisor is up) so a restarted container comes back by itself. See [Keeping it running](#keeping-it-running).
91
+ supervisor, writes `keepalive.sh` and adds it to your crontab when a cron daemon runs; when none does it says so, and the container's
92
+ startup command must run that script (it does nothing when the supervisor is up) for a restarted container to come back. Keys the
93
+ agents read from your shell (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`...) are kept for the service in a 0600 file, never in the unit.
94
+ See [Keeping it running](#keeping-it-running).
93
95
 
94
96
  To try it in the foreground instead, `claw-connector run` prints `connecting claude to https://claw.example.com/claw-api as
95
97
  "<machine name>"` and stays up until Ctrl-C, which stops it and every agent it started.
96
98
 
97
- **5. See it in Clawy**
99
+ **5. Check the machine**
100
+
101
+ ```sh
102
+ claw-connector doctor
103
+ ```
104
+
105
+ `doctor` checks what makes a first phone session fail far from the machine that caused it, and prints one fix per problem:
106
+ Node.js 22 or newer; each agent's program on `PATH` (and on the `PATH` the installed service starts it with, which is not your
107
+ shell's); whether Claude Code and Codex are signed in (`claude auth status`, `codex login status`, or the model provider's key
108
+ variable in `~/.codex/config.toml`); the `roots` writable; `HTTPS_PROXY` and `https_proxy` not set to different values; a service
109
+ installed (and, for `keepalive.sh`, something calling it); and Claw answering, including whether Node.js trusts its certificate
110
+ (Node does not read the system's CA store by default: behind a company CA set `NODE_USE_SYSTEM_CA=1` or `NODE_EXTRA_CA_CERTS`). Then it
111
+ runs the adapter check of step 2. It exits 1 when something will stop sessions (`--json`: `ok` is the adapters' verdict,
112
+ `machine_ok` and `environment` the machine's). The
113
+ running connector repeats the machine checks with its own environment (in a child process, so a stalled disk cannot stall
114
+ it) every 15 minutes, and at once when `doctor` runs (it signals the connector), and Clawy's `/agents` card shows what it found under the machine ("Codex is not signed in: run `codex login` on that machine").
115
+
116
+ **6. See it in Clawy**
98
117
 
99
118
  In Clawy (or any chat client built on Claw), send `/agents`. The agents of this machine are listed, online while `run` is up.
100
119
  A session started there runs on this machine, inside one of your `roots`, and each permission request comes back as a card in the chat.
@@ -122,7 +141,9 @@ id = "codex"
122
141
  preset = "codex"
123
142
  roots = ["~/work/repo-a", "~/work/repo-b"]
124
143
  [agents.env] # passed to the adapter; the model settings live here, never in Claw
125
- OPENAI_API_KEY = "..." # or leave your own environment / CODEX_HOME to supply it
144
+ OPENAI_API_KEY = "..." # or leave your own environment / CODEX_HOME to supply it; with only a key, the
145
+ # connector signs Codex in with it (Codex keeps it in its auth.json, like
146
+ # "codex login --with-api-key")
126
147
 
127
148
  [[agents]]
128
149
  id = "mine"
@@ -155,7 +176,10 @@ connector's state directory, and the install is tried again in the background te
155
176
  `claude-attach` does not start new conversations: it takes over Claude Code conversations that already exist on this
156
177
  machine (see [Taking over an existing conversation](#taking-over-an-existing-conversation)).
157
178
 
158
- An agent with no `roots` is never offered. A configuration problem is reported at start-up, not skipped.
179
+ An agent with no `roots` is never offered. A configuration problem is reported at start-up, not skipped. A root that does
180
+ not exist, or whose disk does not answer (a network mount that is down or not mounted yet), is not a configuration problem:
181
+ `run` warns about it and keeps going, and that root's sessions are refused (`cwd_not_found`, `root_unavailable`) until it is
182
+ back. Reading `agents.toml` never touches a root.
159
183
 
160
184
  ## Running without per-step approval
161
185
 
@@ -222,7 +246,24 @@ All of these run on your machine and fail closed.
222
246
 
223
247
  - **Directories.** A session's working directory is resolved (symlinks included) and must be inside one of the agent's
224
248
  `roots`; otherwise no process is started and the session is closed with `cwd_outside_roots`. The agent runs in the
225
- resolved path.
249
+ resolved path. A folder can be given the way people type it: `~` and `~/x` are the agent's home directory (its
250
+ `[agents.env]` `HOME`, else the connector's), and a relative name (`repo`, `repo/sub`) is looked up under each root and
251
+ taken when exactly one has it (`cwd_ambiguous` when several do); a root's own last segment names that root. A relative
252
+ name never climbs out (`..`). Every look at the disk on the way to opening a session is asynchronous with a 10-second
253
+ limit, so a root on a stalled network mount refuses its own sessions (`root_unavailable`), never those under a healthy
254
+ root, and never freezes the connector or its heartbeat. A restored session whose root does not answer is kept: its
255
+ commands are rejected `root_unavailable` until it does, and `close` still closes it.
256
+ - **Signed in before ready.** A new session of an agent that reports its sign-in (Claude Code does) and reports none is
257
+ refused at once with `agent_auth_required`, instead of opening and failing at its first message; a session that
258
+ already exists is kept and its prompts say why. A prompt refused for the same reason later
259
+ (an expired login) ends its turn with `error: "agent_auth_required"` and the fix, and the next prompt starts the agent
260
+ afresh, so a sign-in done on the machine meanwhile is picked up. An adapter that needs a sign-in and offers an
261
+ `api-key` method (Codex) is told to `authenticate` with it when its environment has `OPENAI_API_KEY` or `CODEX_API_KEY`;
262
+ the key itself is never sent, the adapter reads its own environment.
263
+ - **Why, in one sentence.** Every refusal (`session_closed`, `command_rejected`, and a `turn_end` that failed for a known
264
+ reason) carries its stable `reason` code and a `hint`: one English sentence of what to do about it (sign in with which
265
+ command, which folder to pick, `claw-connector doctor`), with no path and nothing secret, for a client to show beside
266
+ its own translation of the code.
226
267
  - **Ask mode, read back.** After the adapter creates a session, the connector sets the agent's ask mode and reads the
227
268
  mode back from the adapter. Your own agent settings may start an adapter in a mode that never asks (bypass, full
228
269
  access); an adapter that does not confirm the ask mode, or that warns it will not invoke its permission callback, is
@@ -242,6 +283,9 @@ All of these run on your machine and fail closed.
242
283
  which cannot write. `approval = "never"` selects `agent` and starts `agent --force acp` (`--force` is a root flag of the
243
284
  Cursor CLI, "force allow commands unless explicitly denied"; it is accepted before or after `acp`). Cursor
244
285
  has no `session/resume`; the connector uses `session/load` and marks the replayed history `replay: true`.
286
+ A Cursor agent that asks therefore answers and plans but changes no file: its capabilities say `edits: false` with an
287
+ `edits_hint`, `read_only_modes` lists `ask` and `plan` (each such mode in `session_started` carries `read_only: true`),
288
+ `run` prints a note, and the sample `agents.toml` says so above the Cursor block.
245
289
  - **Permissions.** Each permission request becomes a card with the options **the adapter offered**, passed through with
246
290
  their own ids and kinds (`allow_once`, `allow_always`, `reject_once`). An answer must be one of those ids; any other is refused
247
291
  and the request stays open. An unanswered request is answered `cancelled` after 10 minutes, and a cancel,
@@ -371,6 +415,7 @@ session stops the agent process; it never deletes the conversation.
371
415
  | `run [--supervised]` | Connect and serve. Exit code 78 means starting it again will not help: the connector was revoked, Claw or the identity service refused the sign-in (at start, or for two minutes in a row while running), or there is no usable `agents.toml` or sign-in; the reason is kept in `needs-login.json`, and until `login` removes it `run` exits 78 at once without contacting Claw. `--supervised` marks a run the service started. Any other non-zero exit is worth a restart. A unit written by hand for 0.1.2 and older (`RestartPreventExitStatus=3`): replace it with `claw-connector service install`, which writes the same file. |
372
416
  | `service install\|uninstall\|status [--supervisor systemd\|launchd\|script] [--env NAME ...]` | Keep `run` up (see [Keeping it running](#keeping-it-running)). `install` stops a `run` started by hand so the service takes over; `status` also lists the last five disconnects and why. |
373
417
  | `status [--json]` | Running or not, paused, signed in, agents, adapter processes, the last disconnect and its reason (`last down:`), a `problem:` line when a new sign-in is needed, and a `warning:` line for each agent whose sessions will be refused because Claude's own settings default to `bypassPermissions` (see [Running without per-step approval](#running-without-per-step-approval)). |
418
+ | `doctor [--agent ID] [--json]` | Check this machine and print one fix per problem (see step 5 of the quick start), then each agent's adapter (step 2); exit 1 when something stops sessions. The report is kept in `doctor.json`; a running connector is signalled to check again and sends what it finds to Claw (each agent's `capabilities.health`). |
374
419
  | `pause` / `resume` | Stop every agent within a second and stay offline, or undo that. |
375
420
  | `stop` | Stop the running connector and every agent it started. |
376
421
  | `logout` | Stop, revoke this machine's connector at Claw (with a key, `DELETE /v1/remote-agents/connectors/:cid`; with a device sign-in, the connector-side `POST .../connectors/:cid/revoke`), ask the identity service to revoke the sign-in, and delete it from this machine. The next sign-in registers a new connector. When Claw cannot be reached or refuses (a Claw from before the connector-side route refuses a device sign-in), the sign-in is still deleted, but `logout` says the connector was not revoked, prints the `curl` that revokes it with a Claw API key of yours, keeps its id (so a later sign-in reuses it), and exits 1. |
@@ -383,12 +428,12 @@ the connector sees it within a heartbeat, stops every agent and exits without re
383
428
 
384
429
  ## Files
385
430
 
386
- `~/.config/claw-connector/` (or `$XDG_CONFIG_HOME/claw-connector/`): `agents.toml`, `credentials.json` (0600; a file other users can read is
387
- refused). `~/.local/state/claw-connector/` (or `$XDG_STATE_HOME/claw-connector/`): `connector.json` (the connector's identity and the
431
+ `~/.config/claw-connector/` (or `$XDG_CONFIG_HOME/claw-connector/`, or `$CLAW_CONNECTOR_CONFIG_DIR`): `agents.toml`, `credentials.json`
432
+ (0600; a file other users can read is refused), and `service-env.json` (0600, the agents' sign-in variables `service install` kept). `~/.local/state/claw-connector/` (or `$XDG_STATE_HOME/claw-connector/`): `connector.json` (the connector's identity and the
388
433
  commands handled), `sessions.json`, `outbox/` (events not yet acknowledged by Claw), `audit.jsonl` (spawn, directory
389
434
  decisions, every command received, every permission request and answer, every automatic approval, mode changes),
390
- `logs/`, `children.json`, `connector.pid`, `disconnects.jsonl`, `needs-login.json` (only after a run that needs a new sign-in), and with
391
- the script supervisor `supervisor.pid`, `keepalive.sh` and `run.log`. `CLAW_CONNECTOR_HOME=/some/dir` keeps all of it under one directory.
435
+ `logs/`, `children.json`, `connector.pid`, `disconnects.jsonl`, `needs-login.json` (only after a run that needs a new sign-in), `doctor.json`
436
+ (the last `doctor` report), and with the script supervisor `supervisor.pid`, `keepalive.sh`, `keepalive-last` (when it last ran) and `run.log`. `CLAW_CONNECTOR_HOME=/some/dir` keeps all of it under one directory.
392
437
 
393
438
  ## Keeping it running
394
439
 
@@ -401,32 +446,68 @@ the script supervisor `supervisor.pid`, `keepalive.sh` and `run.log`. `CLAW_CONN
401
446
  Log: `run.log` in the state directory.
402
447
  - **script** (a container without systemd; an `systemctl` that answers "Running in chroot" counts as none): `claw-connector supervise`
403
448
  runs in the background and restarts the connector 5 seconds after it dies; `keepalive.sh` in the state directory starts that
404
- supervisor unless it is alive (recorded pid and start time) or a sign-in is needed. Nothing restarts the supervisor after the container
405
- restarts, so call `keepalive.sh` from the container's startup command and from a 5-minute cron job. Log: `run.log`.
449
+ supervisor unless it is alive (recorded pid and start time) or a sign-in is needed. Where a cron daemon runs, `install` adds an
450
+ `@reboot` and a 5-minute line that call `keepalive.sh` to your crontab (once; `uninstall` removes them). Where none runs (most
451
+ containers), `install` says so: nothing brings the connector back after a restart until the container's startup command (or
452
+ `~/.bashrc`) runs `sh keepalive.sh`. Log: `run.log`.
453
+
454
+ **A container whose home directory does not outlive it** (a dev pod): when `agents.toml` and the sign-in sit on the container's own
455
+ file system (overlay, tmpfs), `init` and `service install` say so. Keep them on storage that survives the container with
456
+ `CLAW_CONNECTOR_CONFIG_DIR=/mnt/<your persistent volume>/claw-connector` (only the configuration and sign-in move; pid files, the outbox
457
+ and logs stay on the local disk), set in your shell profile and in the container's startup command, which then runs
458
+ `claw-connector service ensure` (after installing the package again, if it lived on the container's file system too).
406
459
 
407
460
  A successful `login` starts an installed service again, so after a revocation or a refused key signing in is the whole fix.
408
461
  `keepalive.sh` and the script supervisor start nothing while only a person can help (no `agents.toml`, not signed in,
409
462
  `needs-login.json`); under launchd every exit 78 is turned into a clean exit, which launchd does not restart. The service
410
463
  installed for a state directory is recorded in its `service.json`.
411
464
 
412
- The unit carries `PATH` (so the agents are found), `CLAW_CONNECTOR_HOME`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `CLAUDE_CONFIG_DIR`,
413
- `CODEX_HOME`, the CA and proxy variables and `LANG` from the shell that ran `install` (a proxy URL with a password in it only when named with `--env`, e.g. `--env HTTPS_PROXY`); `--env NAME` adds another, except a name that looks
414
- like a credential (`KEY`, `TOKEN`, `SECRET`, `PASSWORD`, `CREDENTIAL`), which is never written into a unit. Run `install` again after
415
- changing any of them, or after moving Node. With a service installed, `claw-connector stop` stops the connector until the next login,
465
+ The unit carries `PATH` (so the agents are found), `CLAW_CONNECTOR_HOME`, `CLAW_CONNECTOR_CONFIG_DIR`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`,
466
+ `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, the CA and proxy variables (with `NODE_USE_SYSTEM_CA` and `NODE_USE_ENV_PROXY`), `LANG`, `IS_SANDBOX`, the npm registry and proxy settings, and every
467
+ plain setting of the agents (`ANTHROPIC_*`, `CLAUDE_CODE_*`, `OPENAI_*`, `CODEX_*`, `CURSOR_*` that is not named like a credential:
468
+ `ANTHROPIC_BASE_URL`, `ANTHROPIC_MODEL`, `CLAUDE_CODE_USE_BEDROCK`...) from the shell that ran `install` (a proxy URL with a password in
469
+ it only when named with `--env`, e.g. `--env HTTPS_PROXY`); `--env NAME` adds another.
470
+
471
+ The agents' own sign-in variables are never written into a unit. When the shell that runs `install` has `ANTHROPIC_API_KEY`,
472
+ `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_CUSTOM_HEADERS`, `OPENAI_API_KEY`, `CODEX_API_KEY`, `CURSOR_API_KEY`, the
473
+ AWS keys, a variable Codex's `config.toml` names as a provider's `env_key`, or an `--env` name that looks like a credential (`KEY`,
474
+ `TOKEN`, `SECRET`, `PASSWORD`, `CREDENTIAL`), they are kept in `service-env.json` beside `credentials.json` (mode 0600) and every `run`
475
+ gives them to the agents that do not set them in `[agents.env]` (a configuration directory its group or others may write is first made 0700; one owned by another account is refused). `install` prints their names, never their values; `service status`
476
+ lists them; `uninstall` deletes the file. Without this a connector that works in a terminal loses its agents' sign-in as a service,
477
+ and each session fails at its first message. `CLAW_CONNECTOR_KEY` is never copied. Run `install` again after changing any of them, or
478
+ after moving Node. With a service installed, `claw-connector stop` stops the connector until the next login,
416
479
  boot or keep-alive check; to stay offline use `pause`, or `service uninstall`.
417
480
 
418
481
  **Why it went down.** Every disconnect is appended to `disconnects.jsonl` in the state directory: `server_closed` (a Claw release or
419
482
  restart, `superseded` by another run, `token_expired`; reconnected at once), `stream_lost` (the network, with the error), `stopped`,
420
- `revoked`, `auth_failed`, `failed`, and `process_died` (the previous run never shut down: killed, crashed or the machine restarted;
421
- dated by its last heartbeat). `status` shows the latest, `service status` the last five.
483
+ `revoked`, `auth_failed`, `failed`, `process_died` (the previous run never shut down: killed, crashed or the machine restarted;
484
+ dated by its last heartbeat), and `connector_limit_reached` (Claw already holds the most connectors one person may have: remove a
485
+ machine you no longer use, in the chat with `/agents` or with `claw-connector logout` on it; the run stays up and tries again every 10
486
+ minutes instead of being restarted every 5 seconds). `status` shows the latest, `service status` the last five.
422
487
 
423
488
  ## Restarts and crashes
424
489
 
425
- A prompt counts as handled only when it reaches the agent (or is dropped by a cancel, a close or a stop), so a connector that
426
- dies with a prompt still queued is sent that prompt again by Claw instead of losing it. A permission request waiting
427
- for an answer lives in the adapter, which dies with the connector: the next run closes such a card (`cancelled`, by
428
- `connector_restart`), so a phone is not left with a button that does nothing. Session state, unacknowledged events and
429
- their sequence numbers are on disk; a session resumes in a fresh adapter on its next prompt.
490
+ A prompt counts as handled only when it reaches the agent (or is dropped by a cancel or a close), so a connector that
491
+ dies, or is stopped or restarted by its supervisor, with a prompt still queued is sent that prompt again by Claw instead of
492
+ losing it. A permission request waiting for an answer lives in the adapter, which dies with the connector: the next run
493
+ closes such a card (`cancelled`, by `connector_restart`), so a phone is not left with a button that does nothing. Session
494
+ state, unacknowledged events and their sequence numbers are on disk; a session resumes in a fresh adapter on its next prompt
495
+ (Claude Code's `session/resume`, the conversation intact).
496
+
497
+ The turn an agent is running is on disk too, from the moment its prompt reaches the agent until its `turn_end`. The next run
498
+ (after a crash, a kill, a pod restart, `stop`, or an upgrade) finds it and:
499
+
500
+ - carries it on when the agent was waiting for a person's permission for a tool call (not a plan or a question card), the agent
501
+ always asks (no `approval = "never"`, no `allow_unsafe_modes`) and its session was in a mode that asks, the connector is not
502
+ paused, and it was away no longer than a card lives (10 minutes): the agent is told the connector restarted and to go on, so
503
+ it asks again and a new card comes; the prompt's own `turn_end` still ends it. It only asks, so nothing runs that nobody
504
+ approved. Nothing is carried on before the run has signed in (a revoked connector carries on nothing);
505
+ - otherwise ends it with `turn_end` `{"stopReason": "error", "error": "connector_restarted"}`, naming its prompt (`cmd_id`,
506
+ `cmd_seq`), so a phone stops waiting for it. The agent is not re-run by itself; the next message resumes the conversation.
507
+
508
+ Every session used in the last 4 hours, and every one with a turn found this way, then gets one `session_recovered` event
509
+ (`interrupted`: a turn was cut off; `continuing`: it is being carried on; `cmd_id` and `cmd_seq` of that turn), which a chat
510
+ shows as "recovered". A stop by the kill switch (`pause`, a revocation) still ends every turn and drops the queue.
430
511
 
431
512
  Known limit: the working-directory check resolves the path and starts the adapter in the resolved path, but another process of
432
513
  the same user that can rewrite a directory in that path could swap a symlink between the check and the start. The