@notiamo/runner 0.1.2 → 0.2.1

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 +80 -6
  2. package/dist/cli.js +1142 -263
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,9 @@ npx @notiamo/runner init
10
10
  and a name for the runner. It checks that `claude --version` works, runs `claude auth login` into the runner's own config
11
11
  folder, then installs the service and starts it. When it ends, the runner is running and starts again by itself.
12
12
 
13
- Needs Node.js 22 or later and [Claude Code](https://docs.claude.com/en/docs/claude-code) installed on the machine.
13
+ Needs Node.js 22 or later and [Claude Code](https://docs.claude.com/en/docs/claude-code) 2.1.314 or newer installed on
14
+ the machine. On Linux and WSL2 the shell sandbox also needs `bubblewrap` and `socat` (Debian/Ubuntu:
15
+ `sudo apt install bubblewrap socat`); `init` checks for them. On macOS there is nothing more to install.
14
16
 
15
17
  From a checkout of the Notiamo repository: `pnpm --filter @notiamo/runner build`, then
16
18
  `node packages/runner/dist/cli.js init`.
@@ -20,6 +22,7 @@ From a checkout of the Notiamo repository: `pnpm --filter @notiamo/runner build`
20
22
  | Command | What |
21
23
  | --- | --- |
22
24
  | `notiamo-runner init` | Set up: token, Claude Code sign-in, install and start the service. |
25
+ | `notiamo-runner update` | Move a runner that is set up to the version you run it from: the token, name and sign-in stay; the checks run again; the service starts the new version. `npx @notiamo/runner@latest update` (add `--profile <name>` for a named runner). |
23
26
  | `notiamo-runner status` | Whether the service is installed and running, and the last log line. |
24
27
  | `notiamo-runner logs` | The last 50 lines of the log. |
25
28
  | `notiamo-runner stop` | Stop the service until the next login or boot. |
@@ -54,6 +57,69 @@ Notiamo's MCP server, sends Notiamo what the agent does, and reports when it end
54
57
  claude.ai login or rate limits for their products." The runner only runs your own Claude Code, on your machine, for
55
58
  your own work.
56
59
 
60
+ ## What the agent can and cannot touch on this machine
61
+
62
+ Each run gets its own Claude Code settings file, and Claude Code runs with `--permission-mode acceptEdits`. Three
63
+ things do the work:
64
+
65
+ - **File tools stay in the run folder.** `permissions.blockReadsOutsideWorkingDirectories` and
66
+ `blockWritesOutsideWorkingDirectories` make Claude's own file tools (read, edit, write, search) refuse anything
67
+ outside the folder of the run. The permission mode accepts edits only inside that folder; any other call that would
68
+ ask for permission is refused (the runner runs Claude Code in print mode, where nobody can answer). Edits are also
69
+ denied by rule in the runner's own files, your credential and config folders (`~/.ssh`, `~/.aws`, `~/.config`,
70
+ `~/.npm`, `~/.local`, `~/.claude`, `~/Library` on macOS, …), your shell start files, and every folder holding a
71
+ program the runner itself runs (each folder on the `PATH`, the folders of `git`, `claude` and `node`, the runner's
72
+ own install).
73
+ - **Shell commands run in the OS sandbox.** On macOS (Seatbelt) and on Linux or WSL2 (bubblewrap and socat), every
74
+ command the agent runs in a shell is confined. If the sandbox cannot start, the run does not start.
75
+ - **No git config, credential helper, credential file or token of the machine reaches the agent.** Claude Code gets
76
+ only a short list of the runner's environment variables (`PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `LANG`,
77
+ `LC_ALL`, `LC_CTYPE`, `TERM`, `TMPDIR`, `TZ`, `XDG_*`, the proxy settings `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`, and
78
+ `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `SSL_CERT_DIR`) plus the run's own: never `SSH_AUTH_SOCK`, `GH_TOKEN`,
79
+ `GITHUB_TOKEN`, `NPM_TOKEN`, `AWS_*` or `ANTHROPIC_*`. The agent's git sees no global or system git config
80
+ (`GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`) and commits as "Notiamo agent". Commands that name
81
+ `credential`, `osxkeychain`, `libsecret`, `security find-`, `secret-tool`, `gh auth` or `ssh-add` are denied.
82
+ - **The keychain service itself is reachable from the sandbox on macOS.** The programs that read it are also denied
83
+ to the shell as defense in depth: git's credential helpers (`git-credential-*` in `git --exec-path`),
84
+ `/usr/bin/security`, `secret-tool` and `gnome-keyring` on Linux, and `gh`. That is not a wall: a script the agent
85
+ writes can still start system programs such as `/usr/bin/security`. This is why the runner needs no credential of
86
+ the machine: keep no GitHub login in this user's keychain (`notiamo-runner init` warns when it finds one), or run
87
+ the runner as a dedicated macOS user with its own keychain, holding nothing but what the runner needs.
88
+
89
+ What the shell sandbox allows and denies:
90
+
91
+ - **Reads denied:** the other projects' folders (other runs at the same time included), the runner's own files
92
+ (`config.json` with the runner token, `state.json`, `runs/`, `claude/`, `git/`, the log) and your credential files
93
+ and folders that exist: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.netrc`, `~/.docker`, `~/.config/gh`,
94
+ `~/.git-credentials`, `~/.config/git`, `~/.npmrc`, `~/.pypirc`, `~/.config/gcloud`, `~/.config/hub`.
95
+ - **Writes allowed:** only in the run folder. The run's `.git/hooks` and `.git/config` are read-only.
96
+ - **Network allowed:** GitHub, npm, Notiamo, the WordPress site (for a WordPress run) and any extra hosts you add on
97
+ the Runners card in Notiamo. Everything else is refused.
98
+
99
+ The agent never pushes and holds no credentials. The runner pushes the run branch itself after the agent's work.
100
+
101
+ When a run ends, the runner kills every process left in its process group (a dev server or a watcher the agent
102
+ started). A process that detached itself with `setsid` has left that group and can outlive the run.
103
+
104
+ **Windows.** Plain Windows has no shell sandbox: only the file-tool rules above apply, and `init` says so. For the
105
+ full sandbox, run the runner inside WSL2.
106
+
107
+ ## Several runs at once
108
+
109
+ On the Runners card in Notiamo you set how many runs this runner does at the same time (1 to 3). Notiamo hands the
110
+ number to the runner when it connects. Each run uses your Claude plan, so keep it to what the plan has room for. One
111
+ project never runs twice at once: a second run on the same project waits for the first (and the runner refuses one
112
+ that arrives while the first still runs here).
113
+
114
+ Each run at the same time has its own slot folder, `projects/0`, `projects/1` or `projects/2`, made when the runner
115
+ starts: a run's sandbox cannot read the other slots at all, even a project cloned there after the run began.
116
+
117
+ ## A runner for one client
118
+
119
+ When you make a runner in Notiamo, you can limit it to one client (workspace). It then takes only that client's runs.
120
+ To set it up on the client's PC: install Claude Code there, run `npx @notiamo/runner init`, and paste the runner's
121
+ token. The Claude login on that PC is the one that pays for the runs, so sign in with the account that should pay.
122
+
57
123
  ## WordPress sites
58
124
 
59
125
  The connection is on the project's Website access card in Notiamo: an application password, and optionally SSH or
@@ -89,17 +155,25 @@ Everything lives in `~/.notiamo-runner` (or the folder in `NOTIAMO_RUNNER_HOME`)
89
155
  | --- | --- |
90
156
  | `config.json` | Notiamo's address, the runner's name and the runner token (`ntr_…`), nothing else. Readable by you only (mode 0600). |
91
157
  | `claude/` | Claude Code's config folder for the runner (`CLAUDE_CONFIG_DIR`): its sign-in and sessions. |
92
- | `projects/` | The working copies of your projects, one folder per project. |
158
+ | `projects/<slot>/<project>/` | The working copies of your projects, one folder per project in each slot (`0`, `1`, `2`: one per run at the same time). A WordPress run works in a temporary folder inside it, removed after the run. A project not used for the number of days set on the Runners card is removed; the next run clones it again. |
159
+ | `git/` | The runner's own copies of your repositories: `<project>.git` (a bare mirror, the only copy that talks to GitHub; removed with the project's last working copy), `<project>.<slot>.worktree.json` (what the working copy's `.git` looked like after the runner's own git, checked before the runner uses it again) and `<project>.last-run` (its time is the project's last run). The agent can neither read nor write this folder. |
160
+ | `runs/<id>.json` | The Claude Code settings of a run in progress (permission rules, the sandbox's folders and hosts; no secret). Deleted when the run ends. |
93
161
  | `runner.log` | What the runner did (no secrets), rotated at 10 MB. |
94
- | `state.json` | The run in progress, so a restart can carry on. |
162
+ | `state.json` | The ids of the runs in progress. After a restart, each is reported to Notiamo as failed (`runner_restarted`). |
95
163
 
96
- The secrets of a run (WordPress passwords, the run's MCP token) come from Notiamo with the run and
164
+ The secrets of a run (WordPress passwords, the run's MCP token, its GitHub token) come from Notiamo with the run and
97
165
  live only in memory: the agent gets them as environment variables (the MCP config on its command line only names them),
98
166
  never in a file. The two MCP tokens are `NOTIAMO_RUN_TOKEN` and `NOTIAMO_WP_TOKEN`. What the agent prints is scrubbed
99
167
  of them before the transcript is uploaded.
100
168
 
101
- The runner never gets a GitHub token. The machine's own git credentials clone and push; Notiamo opens and merges the
102
- pull request through its GitHub App.
169
+ The agent has no git credentials at all, and the runner never uses the machine's git credentials: its own git runs
170
+ with no user or system git config (`GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`), so no credential helper or
171
+ keychain of the machine is asked. Instead each run gets from Notiamo a one-hour GitHub App token for the run's
172
+ repository only. The runner keeps it in memory and uses it for its own fetch and push, handed to git through its
173
+ environment (never on a command line, never in a file); close to the hour's end it asks Notiamo for a fresh one. The
174
+ runner clones only from `https://github.com/…` and pushes the run branch itself after the agent's work. It refuses to
175
+ push if the run changed the clone's `.git` folder. Notiamo opens and merges the pull request through its GitHub App.
176
+ Keep no GitHub login in this user's keychain: the runner does not need it, and a run could reach it.
103
177
 
104
178
  The SSH host key of a WordPress server is not verified yet.
105
179