|
50baf1a…
|
lmata
|
1 |
# Adding Another Agent Runtime |
|
50baf1a…
|
lmata
|
2 |
|
|
ef7adab…
|
lmata
|
3 |
This repo now has two reusable relay shapes: |
|
ef7adab…
|
lmata
|
4 |
- terminal-session brokers in `cmd/claude-relay/`, `cmd/codex-relay/`, and `cmd/gemini-relay/` |
|
ef7adab…
|
lmata
|
5 |
- IRC-resident agents in `pkg/ircagent/` with thin wrappers in `cmd/*-agent/` |
|
24a217e…
|
lmata
|
6 |
|
|
24a217e…
|
lmata
|
7 |
Shared transport/runtime code now lives in `pkg/sessionrelay/`. Reuse that |
|
24a217e…
|
lmata
|
8 |
before writing another relay client by hand. |
|
24a217e…
|
lmata
|
9 |
|
|
ef7adab…
|
lmata
|
10 |
If you add another live terminal runtime, do not invent a new relay model. |
|
ef7adab…
|
lmata
|
11 |
Codex and Gemini are the current reference implementations for the terminal |
|
ef7adab…
|
lmata
|
12 |
broker pattern, and Claude now follows the same layout. New runtimes should |
|
ef7adab…
|
lmata
|
13 |
match the same repo paths, naming, and environment contract so operators get |
|
ef7adab…
|
lmata
|
14 |
one consistent experience. |
|
ef7adab…
|
lmata
|
15 |
|
|
ef7adab…
|
lmata
|
16 |
## Canonical terminal-broker layout |
|
ef7adab…
|
lmata
|
17 |
|
|
ef7adab…
|
lmata
|
18 |
For a local interactive runtime, follow this repo layout: |
|
ef7adab…
|
lmata
|
19 |
|
|
ef7adab…
|
lmata
|
20 |
```text |
|
ef7adab…
|
lmata
|
21 |
cmd/{runtime}-relay/main.go |
|
ef7adab…
|
lmata
|
22 |
skills/{runtime}-relay/ |
|
ef7adab…
|
lmata
|
23 |
install.md |
|
ef7adab…
|
lmata
|
24 |
FLEET.md |
|
ef7adab…
|
lmata
|
25 |
hooks/ |
|
ef7adab…
|
lmata
|
26 |
README.md |
|
ef7adab…
|
lmata
|
27 |
scuttlebot-check.sh |
|
ef7adab…
|
lmata
|
28 |
scuttlebot-post.sh |
|
ef7adab…
|
lmata
|
29 |
...runtime-specific reply hooks if needed |
|
ef7adab…
|
lmata
|
30 |
scripts/ |
|
ef7adab…
|
lmata
|
31 |
install-{runtime}-relay.sh |
|
ef7adab…
|
lmata
|
32 |
pkg/sessionrelay/ |
|
ef7adab…
|
lmata
|
33 |
``` |
|
ef7adab…
|
lmata
|
34 |
|
|
ef7adab…
|
lmata
|
35 |
Conventions: |
|
ef7adab…
|
lmata
|
36 |
- `cmd/{runtime}-relay/main.go` is the broker entrypoint |
|
ef7adab…
|
lmata
|
37 |
- `skills/{runtime}-relay/install.md` is the human install primer |
|
ef7adab…
|
lmata
|
38 |
- `skills/{runtime}-relay/FLEET.md` is the rollout and operations guide |
|
ef7adab…
|
lmata
|
39 |
- `skills/{runtime}-relay/hooks/README.md` documents the runtime-specific hook contract |
|
ef7adab…
|
lmata
|
40 |
- `skills/{runtime}-relay/scripts/install-{runtime}-relay.sh` is the tracked installer |
|
ef7adab…
|
lmata
|
41 |
- installed files under `~/.{runtime}/`, `~/.local/bin/`, and `~/.config/` are copies, not the source of truth |
|
ef7adab…
|
lmata
|
42 |
|
|
ef7adab…
|
lmata
|
43 |
Use `pkg/sessionrelay/` for channel send/receive/presence in both `http` and |
|
ef7adab…
|
lmata
|
44 |
`irc` modes. Use `pkg/ircagent/` only when the process itself should be a |
|
ef7adab…
|
lmata
|
45 |
persistent IRC-resident bot. |
|
50baf1a…
|
lmata
|
46 |
|
|
50baf1a…
|
lmata
|
47 |
## The contract |
|
50baf1a…
|
lmata
|
48 |
|
|
50baf1a…
|
lmata
|
49 |
Every runtime adapter must support two flows: |
|
50baf1a…
|
lmata
|
50 |
|
|
50baf1a…
|
lmata
|
51 |
1. Activity out |
|
50baf1a…
|
lmata
|
52 |
- during live work, mirror meaningful tool/action activity back to scuttlebot |
|
50baf1a…
|
lmata
|
53 |
- if the runtime exposes assistant progress or reply text, mirror that too |
|
50baf1a…
|
lmata
|
54 |
- use a stable session nick |
|
50baf1a…
|
lmata
|
55 |
|
|
50baf1a…
|
lmata
|
56 |
2. Instruction back in |
|
50baf1a…
|
lmata
|
57 |
- continuously or before the next action, fetch recent messages from scuttlebot |
|
50baf1a…
|
lmata
|
58 |
- filter to explicit operator instructions for this session |
|
50baf1a…
|
lmata
|
59 |
- surface the instruction back into the runtime using that runtime's native hook/block mechanism |
|
50baf1a…
|
lmata
|
60 |
|
|
50baf1a…
|
lmata
|
61 |
If a runtime cannot surface a blocking instruction before the next action, it does |
|
50baf1a…
|
lmata
|
62 |
not yet have parity with the Claude/Codex hook path. |
|
50baf1a…
|
lmata
|
63 |
|
|
50baf1a…
|
lmata
|
64 |
For runtimes that are live interactive terminal sessions, ship a small broker or |
|
50baf1a…
|
lmata
|
65 |
launcher wrapper that: |
|
50baf1a…
|
lmata
|
66 |
- exports a stable session id before the runtime starts |
|
50baf1a…
|
lmata
|
67 |
- derives and exports the session nick once |
|
50baf1a…
|
lmata
|
68 |
- posts `online` immediately on startup |
|
50baf1a…
|
lmata
|
69 |
- mirrors activity from the runtime's own event/session log or PTY stream |
|
50baf1a…
|
lmata
|
70 |
- posts `offline` on exit |
|
50baf1a…
|
lmata
|
71 |
- soft-fails if scuttlebot is disabled or unreachable |
|
50baf1a…
|
lmata
|
72 |
|
|
50baf1a…
|
lmata
|
73 |
Hooks remain useful for pre-action fallback and for runtimes that do not have a |
|
50baf1a…
|
lmata
|
74 |
broker yet, but hook-only telemetry is not the production pattern for |
|
50baf1a…
|
lmata
|
75 |
interactive sessions. |
|
50baf1a…
|
lmata
|
76 |
|
|
24a217e…
|
lmata
|
77 |
If the runtime needs the same channel send/receive/presence semantics as |
|
24a217e…
|
lmata
|
78 |
`codex-relay`, start from `pkg/sessionrelay`: |
|
24a217e…
|
lmata
|
79 |
- `TransportHTTP` for the bridge/API path |
|
24a217e…
|
lmata
|
80 |
- `TransportIRC` for true SASL IRC presence with optional auto-registration via `/v1/agents/register` |
|
24a217e…
|
lmata
|
81 |
|
|
ef7adab…
|
lmata
|
82 |
## Canonical terminal-broker conventions |
|
ef7adab…
|
lmata
|
83 |
|
|
ef7adab…
|
lmata
|
84 |
Every terminal broker should follow these conventions: |
|
ef7adab…
|
lmata
|
85 |
- one stable nick per live session: `{runtime}-{basename}-{session}` |
|
ef7adab…
|
lmata
|
86 |
- one shared env contract using `SCUTTLEBOT_*` |
|
b8ce843…
|
lmata
|
87 |
- installer default is auto-registration: leave `SCUTTLEBOT_IRC_PASS` unset and remove stale fixed-pass values unless the operator explicitly requests a fixed identity |
|
1d3caa2…
|
lmata
|
88 |
- one primary control channel plus optional joined work channels |
|
ef7adab…
|
lmata
|
89 |
- one broker process owning `online` / `offline` |
|
ef7adab…
|
lmata
|
90 |
- one broker process owning continuous addressed operator input injection |
|
ef7adab…
|
lmata
|
91 |
- one broker process owning outbound activity and assistant-message mirroring when the runtime exposes a reliable event/session stream |
|
ef7adab…
|
lmata
|
92 |
- hooks used for pre-action fallback and for runtime-specific gaps such as post-tool summaries or final reply hooks |
|
ef7adab…
|
lmata
|
93 |
- support both `SCUTTLEBOT_TRANSPORT=http` and `SCUTTLEBOT_TRANSPORT=irc` behind the same broker contract |
|
ef7adab…
|
lmata
|
94 |
- soft-fail when scuttlebot is disabled or unavailable so the underlying runtime still starts |
|
ef7adab…
|
lmata
|
95 |
|
|
50baf1a…
|
lmata
|
96 |
## Required environment contract |
|
50baf1a…
|
lmata
|
97 |
|
|
50baf1a…
|
lmata
|
98 |
All adapters should use the same environment variables: |
|
50baf1a…
|
lmata
|
99 |
- `SCUTTLEBOT_URL` |
|
50baf1a…
|
lmata
|
100 |
- `SCUTTLEBOT_TOKEN` |
|
50baf1a…
|
lmata
|
101 |
- `SCUTTLEBOT_CHANNEL` |
|
1d3caa2…
|
lmata
|
102 |
- `SCUTTLEBOT_CHANNELS` |
|
ef7adab…
|
lmata
|
103 |
- `SCUTTLEBOT_TRANSPORT` |
|
50baf1a…
|
lmata
|
104 |
|
|
50baf1a…
|
lmata
|
105 |
Optional: |
|
50baf1a…
|
lmata
|
106 |
- `SCUTTLEBOT_NICK` |
|
50baf1a…
|
lmata
|
107 |
- `SCUTTLEBOT_SESSION_ID` |
|
ef7adab…
|
lmata
|
108 |
- `SCUTTLEBOT_IRC_ADDR` |
|
ef7adab…
|
lmata
|
109 |
- `SCUTTLEBOT_IRC_PASS` |
|
ef7adab…
|
lmata
|
110 |
- `SCUTTLEBOT_IRC_DELETE_ON_CLOSE` |
|
ef7adab…
|
lmata
|
111 |
- `SCUTTLEBOT_HOOKS_ENABLED` |
|
ef7adab…
|
lmata
|
112 |
- `SCUTTLEBOT_INTERRUPT_ON_MESSAGE` |
|
ef7adab…
|
lmata
|
113 |
- `SCUTTLEBOT_POLL_INTERVAL` |
|
ef7adab…
|
lmata
|
114 |
- `SCUTTLEBOT_PRESENCE_HEARTBEAT` |
|
1d3caa2…
|
lmata
|
115 |
- `SCUTTLEBOT_CHANNEL_STATE_FILE` |
|
50baf1a…
|
lmata
|
116 |
|
|
50baf1a…
|
lmata
|
117 |
Do not hardcode tokens into repo scripts. |
|
b8ce843…
|
lmata
|
118 |
For terminal-session brokers, treat `SCUTTLEBOT_IRC_PASS` as an explicit |
|
b8ce843…
|
lmata
|
119 |
fixed-identity override, not a default. |
|
1d3caa2…
|
lmata
|
120 |
|
|
1d3caa2…
|
lmata
|
121 |
Channel semantics: |
|
1d3caa2…
|
lmata
|
122 |
- `SCUTTLEBOT_CHANNEL` is the primary control channel |
|
1d3caa2…
|
lmata
|
123 |
- `SCUTTLEBOT_CHANNELS` is the startup channel set and should include the control channel |
|
1d3caa2…
|
lmata
|
124 |
- runtime `/join`, `/part`, and `/channels` commands may change the live channel set for one session without rewriting the shared env file |
|
50baf1a…
|
lmata
|
125 |
|
|
50baf1a…
|
lmata
|
126 |
## Nicking rules |
|
50baf1a…
|
lmata
|
127 |
|
|
50baf1a…
|
lmata
|
128 |
Use a stable, human-addressable session nick. |
|
50baf1a…
|
lmata
|
129 |
|
|
50baf1a…
|
lmata
|
130 |
Requirements: |
|
50baf1a…
|
lmata
|
131 |
- deterministic for the life of the session |
|
50baf1a…
|
lmata
|
132 |
- unique across parallel sessions |
|
50baf1a…
|
lmata
|
133 |
- short enough to mention in chat |
|
50baf1a…
|
lmata
|
134 |
- obvious which runtime it belongs to |
|
50baf1a…
|
lmata
|
135 |
|
|
50baf1a…
|
lmata
|
136 |
Recommended patterns: |
|
50baf1a…
|
lmata
|
137 |
- Claude: `claude-{basename}-{session_id[:8]}` |
|
50baf1a…
|
lmata
|
138 |
- Codex: `codex-{basename}-{session_suffix}` |
|
50baf1a…
|
lmata
|
139 |
- Future runtime: `{runtime}-{basename}-{session}` |
|
50baf1a…
|
lmata
|
140 |
|
|
50baf1a…
|
lmata
|
141 |
If the runtime already exposes a stable session id, prefer that over `PPID`. |
|
50baf1a…
|
lmata
|
142 |
|
|
50baf1a…
|
lmata
|
143 |
## Filtering rules |
|
50baf1a…
|
lmata
|
144 |
|
|
50baf1a…
|
lmata
|
145 |
Your inbound check must only surface messages that are: |
|
50baf1a…
|
lmata
|
146 |
- newer than the last check for this session |
|
50baf1a…
|
lmata
|
147 |
- not from this session nick |
|
50baf1a…
|
lmata
|
148 |
- not from known service bots |
|
50baf1a…
|
lmata
|
149 |
- not from agent status nicks like `claude-*`, `codex-*`, or `gemini-*` |
|
50baf1a…
|
lmata
|
150 |
- explicitly mentioning this session nick |
|
50baf1a…
|
lmata
|
151 |
|
|
50baf1a…
|
lmata
|
152 |
Ambient channel chat must not block the tool loop. |
|
50baf1a…
|
lmata
|
153 |
|
|
50baf1a…
|
lmata
|
154 |
## State scoping |
|
50baf1a…
|
lmata
|
155 |
|
|
50baf1a…
|
lmata
|
156 |
Do not use one global timestamp file. |
|
50baf1a…
|
lmata
|
157 |
|
|
50baf1a…
|
lmata
|
158 |
Track last-seen state by a key derived from: |
|
50baf1a…
|
lmata
|
159 |
- nick |
|
50baf1a…
|
lmata
|
160 |
- working directory |
|
50baf1a…
|
lmata
|
161 |
|
|
1d3caa2…
|
lmata
|
162 |
That prevents parallel sessions from consuming each other's instructions while |
|
1d3caa2…
|
lmata
|
163 |
still allowing one session to join or part channels without losing its check |
|
1d3caa2…
|
lmata
|
164 |
state. |
|
50baf1a…
|
lmata
|
165 |
|
|
50baf1a…
|
lmata
|
166 |
## HTTP API contract |
|
50baf1a…
|
lmata
|
167 |
|
|
50baf1a…
|
lmata
|
168 |
All adapters use the same scuttlebot HTTP API: |
|
50baf1a…
|
lmata
|
169 |
|
|
50baf1a…
|
lmata
|
170 |
Post activity: |
|
50baf1a…
|
lmata
|
171 |
|
|
50baf1a…
|
lmata
|
172 |
```http |
|
50baf1a…
|
lmata
|
173 |
POST /v1/channels/{channel}/messages |
|
50baf1a…
|
lmata
|
174 |
Authorization: Bearer <token> |
|
50baf1a…
|
lmata
|
175 |
Content-Type: application/json |
|
50baf1a…
|
lmata
|
176 |
|
|
50baf1a…
|
lmata
|
177 |
{"nick":"runtime-session","text":"read internal/api/ui/index.html"} |
|
50baf1a…
|
lmata
|
178 |
``` |
|
50baf1a…
|
lmata
|
179 |
|
|
50baf1a…
|
lmata
|
180 |
Read recent messages: |
|
50baf1a…
|
lmata
|
181 |
|
|
50baf1a…
|
lmata
|
182 |
```http |
|
50baf1a…
|
lmata
|
183 |
GET /v1/channels/{channel}/messages |
|
50baf1a…
|
lmata
|
184 |
Authorization: Bearer <token> |
|
50baf1a…
|
lmata
|
185 |
``` |
|
50baf1a…
|
lmata
|
186 |
|
|
50baf1a…
|
lmata
|
187 |
Optional lower-latency path: |
|
50baf1a…
|
lmata
|
188 |
|
|
50baf1a…
|
lmata
|
189 |
```http |
|
50baf1a…
|
lmata
|
190 |
GET /v1/channels/{channel}/stream?token=<token> |
|
50baf1a…
|
lmata
|
191 |
Accept: text/event-stream |
|
50baf1a…
|
lmata
|
192 |
``` |
|
50baf1a…
|
lmata
|
193 |
|
|
50baf1a…
|
lmata
|
194 |
## Runtime integration points |
|
50baf1a…
|
lmata
|
195 |
|
|
50baf1a…
|
lmata
|
196 |
For each new agent runtime, identify the equivalents of: |
|
50baf1a…
|
lmata
|
197 |
- post-action hook |
|
50baf1a…
|
lmata
|
198 |
- pre-action hook |
|
50baf1a…
|
lmata
|
199 |
- session-start / session-stop wrapper |
|
50baf1a…
|
lmata
|
200 |
- blocking/instruction surfacing mechanism |
|
50baf1a…
|
lmata
|
201 |
|
|
50baf1a…
|
lmata
|
202 |
Examples: |
|
50baf1a…
|
lmata
|
203 |
- Claude Code: `PostToolUse` and `PreToolUse` |
|
50baf1a…
|
lmata
|
204 |
- Codex: `post-tool-use` and `pre-tool-use` |
|
50baf1a…
|
lmata
|
205 |
|
|
50baf1a…
|
lmata
|
206 |
If the runtime has no native pre-action interception point, you need an explicit |
|
50baf1a…
|
lmata
|
207 |
poll call inside its step loop. Document that clearly as weaker than the hook path. |
|
50baf1a…
|
lmata
|
208 |
|
|
50baf1a…
|
lmata
|
209 |
If the runtime has no native startup hook, use the launcher wrapper for `online` |
|
50baf1a…
|
lmata
|
210 |
and `offline` presence instead of trying to fake it inside the action hooks. |
|
50baf1a…
|
lmata
|
211 |
|
|
50baf1a…
|
lmata
|
212 |
If the runtime is an interactive terminal application and you want operators to |
|
50baf1a…
|
lmata
|
213 |
talk to the live session mid-work, prefer a PTY/session broker over hook-only |
|
50baf1a…
|
lmata
|
214 |
delivery. The broker should own: |
|
50baf1a…
|
lmata
|
215 |
- session presence (`online` / `offline`) |
|
50baf1a…
|
lmata
|
216 |
- continuous operator input injection |
|
50baf1a…
|
lmata
|
217 |
- outbound activity mirroring |
|
50baf1a…
|
lmata
|
218 |
|
|
50baf1a…
|
lmata
|
219 |
Hooks are still useful for pre-action fallback and runtimes without richer |
|
50baf1a…
|
lmata
|
220 |
integration points, but they do not replace continuous stdin injection or |
|
50baf1a…
|
lmata
|
221 |
broker-owned activity streaming. |
|
50baf1a…
|
lmata
|
222 |
|
|
50baf1a…
|
lmata
|
223 |
## Reference implementation checklist |
|
50baf1a…
|
lmata
|
224 |
|
|
50baf1a…
|
lmata
|
225 |
When adding a new runtime, ship all of the following in the repo: |
|
50baf1a…
|
lmata
|
226 |
|
|
50baf1a…
|
lmata
|
227 |
1. Hook or relay scripts |
|
50baf1a…
|
lmata
|
228 |
2. A launcher wrapper or broker if the runtime needs startup/offline presence |
|
50baf1a…
|
lmata
|
229 |
3. A tracked installer or bootstrap command for local setup |
|
50baf1a…
|
lmata
|
230 |
4. A runtime-specific install primer |
|
50baf1a…
|
lmata
|
231 |
5. A smoke-test recipe |
|
50baf1a…
|
lmata
|
232 |
6. Default nick format documentation |
|
50baf1a…
|
lmata
|
233 |
7. Operator usage examples |
|
50baf1a…
|
lmata
|
234 |
8. An explanation of what blocks and what stays ambient |
|
50baf1a…
|
lmata
|
235 |
|
|
50baf1a…
|
lmata
|
236 |
## Minimal algorithm |
|
50baf1a…
|
lmata
|
237 |
|
|
50baf1a…
|
lmata
|
238 |
Pseudocode: |
|
50baf1a…
|
lmata
|
239 |
|
|
50baf1a…
|
lmata
|
240 |
```text |
|
50baf1a…
|
lmata
|
241 |
broker loop: |
|
50baf1a…
|
lmata
|
242 |
post online presence on startup |
|
50baf1a…
|
lmata
|
243 |
tail runtime events / session log / PTY output |
|
50baf1a…
|
lmata
|
244 |
summarize tool activity and assistant progress in one line |
|
50baf1a…
|
lmata
|
245 |
POST them to /v1/channels/{channel}/messages as this session nick |
|
50baf1a…
|
lmata
|
246 |
|
|
50baf1a…
|
lmata
|
247 |
on pre-action: |
|
50baf1a…
|
lmata
|
248 |
GET recent channel messages |
|
50baf1a…
|
lmata
|
249 |
discard bot traffic, status nicks, self messages, and old messages |
|
50baf1a…
|
lmata
|
250 |
keep only lines explicitly mentioning this session nick |
|
50baf1a…
|
lmata
|
251 |
if any remain: |
|
50baf1a…
|
lmata
|
252 |
surface the most recent one through the runtime's block/intercept mechanism |
|
50baf1a…
|
lmata
|
253 |
|
|
50baf1a…
|
lmata
|
254 |
on exit: |
|
50baf1a…
|
lmata
|
255 |
POST offline presence |
|
50baf1a…
|
lmata
|
256 |
``` |
|
50baf1a…
|
lmata
|
257 |
|
|
50baf1a…
|
lmata
|
258 |
## Smoke test requirements |
|
50baf1a…
|
lmata
|
259 |
|
|
50baf1a…
|
lmata
|
260 |
Every new adapter should be verifiable with the same basic test: |
|
50baf1a…
|
lmata
|
261 |
|
|
50baf1a…
|
lmata
|
262 |
1. launch the runtime or broker and confirm `online` appears in the channel |
|
50baf1a…
|
lmata
|
263 |
2. trigger one harmless tool/action step and confirm the mirrored activity appears |
|
50baf1a…
|
lmata
|
264 |
3. send an operator message mentioning the session nick |
|
50baf1a…
|
lmata
|
265 |
4. confirm the runtime surfaces it immediately or at the next action boundary |
|
50baf1a…
|
lmata
|
266 |
5. confirm `offline` appears when the session exits |
|
50baf1a…
|
lmata
|
267 |
|
|
50baf1a…
|
lmata
|
268 |
If you cannot do that, the adapter is not finished. |
|
50baf1a…
|
lmata
|
269 |
|
|
50baf1a…
|
lmata
|
270 |
## Where to put new adapters |
|
50baf1a…
|
lmata
|
271 |
|
|
50baf1a…
|
lmata
|
272 |
Recommended layout: |
|
50baf1a…
|
lmata
|
273 |
|
|
50baf1a…
|
lmata
|
274 |
```text |
|
ef7adab…
|
lmata
|
275 |
cmd/{runtime}-relay/ |
|
50baf1a…
|
lmata
|
276 |
skills/{runtime}-relay/ |
|
ef7adab…
|
lmata
|
277 |
FLEET.md |
|
50baf1a…
|
lmata
|
278 |
hooks/ |
|
50baf1a…
|
lmata
|
279 |
README.md |
|
50baf1a…
|
lmata
|
280 |
scuttlebot-post.* |
|
50baf1a…
|
lmata
|
281 |
scuttlebot-check.* |
|
ef7adab…
|
lmata
|
282 |
...runtime-specific reply hooks |
|
50baf1a…
|
lmata
|
283 |
scripts/ |
|
ef7adab…
|
lmata
|
284 |
install-{runtime}-relay.* |
|
50baf1a…
|
lmata
|
285 |
{runtime}-relay.* |
|
50baf1a…
|
lmata
|
286 |
install.md |
|
50baf1a…
|
lmata
|
287 |
``` |
|
50baf1a…
|
lmata
|
288 |
|
|
50baf1a…
|
lmata
|
289 |
Keep the hook scripts in the repo. Home-directory installs are copies, not the |
|
50baf1a…
|
lmata
|
290 |
source of truth. |