@prohost/cli 0.2.1 → 0.2.3
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.
- package/CHANGELOG.md +211 -0
- package/README.md +198 -17
- package/dist/agent/claude.d.ts +23 -10
- package/dist/agent/claude.js +24 -30
- package/dist/agent/codex.d.ts +365 -0
- package/dist/agent/codex.js +901 -0
- package/dist/agent/contract.d.ts +62 -3
- package/dist/agent/contract.js +66 -3
- package/dist/agent/extras.d.ts +73 -0
- package/dist/agent/extras.js +118 -0
- package/dist/agent/mcp.d.ts +24 -0
- package/dist/agent/mcp.js +24 -0
- package/dist/agent/prompt.d.ts +12 -0
- package/dist/agent/prompt.js +107 -4
- package/dist/agent/run.d.ts +33 -0
- package/dist/agent/run.js +263 -50
- package/dist/agent/shell.d.ts +23 -0
- package/dist/agent/shell.js +27 -0
- package/dist/index.js +10 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,217 @@ Versions follow [semver](https://semver.org/). Publishing is automated: merging
|
|
|
4
4
|
a version bump to `main` triggers `.github/workflows/npm-publish-cli.yml`, which
|
|
5
5
|
builds via `prepack`, runs the suite, publishes, and tags `cli-v<version>`.
|
|
6
6
|
|
|
7
|
+
## 0.2.3
|
|
8
|
+
|
|
9
|
+
Three things, all of them about a run knowing what is actually true about
|
|
10
|
+
itself: a prompt bullet that taught agents to doubt tools they had, a way to
|
|
11
|
+
give an agent a tool on purpose, and closing the gaps 0.2.2 shipped with an
|
|
12
|
+
honest note attached.
|
|
13
|
+
|
|
14
|
+
- **A ToolSearch miss no longer reads as proof.** 0.2.1 told the agent to
|
|
15
|
+
"search ToolSearch for a capability before telling anyone you lack it", which
|
|
16
|
+
is right about deferred web browsing and badly wrong as a general rule. In
|
|
17
|
+
production a paired brain searched ToolSearch for `bash`, found nothing —
|
|
18
|
+
ToolSearch indexes only the *deferred* tools, and Bash is a direct one — and
|
|
19
|
+
spent an entire resumed session reporting it had no shell, while
|
|
20
|
+
`--dangerously-skip-permissions` had been handing it the full toolset the
|
|
21
|
+
whole time. The prompt now states that the directly-listed tools are the
|
|
22
|
+
authoritative set, that a ToolSearch miss is evidence of nothing, and that
|
|
23
|
+
"looking" means running the command rather than reading one's own tool list:
|
|
24
|
+
a claim of no shell, no file access or no checkout has to come after an
|
|
25
|
+
attempt that failed and can be quoted. The original bullet stays — deferred
|
|
26
|
+
browsing is real, and dropping it would just swap one wrong inference for the
|
|
27
|
+
other.
|
|
28
|
+
- **`~/.prohost/extra-mcp.json` — opt-in extra MCP servers.** A run gets the
|
|
29
|
+
ProhostAI server and nothing else, and that isolation is a security boundary:
|
|
30
|
+
a run executes with permission prompts off on prompt text written by whoever
|
|
31
|
+
mentioned the agent. This adds a way to widen it deliberately without weakening
|
|
32
|
+
the default. Write the file yourself — the CLI never creates it — holding the
|
|
33
|
+
same `{"mcpServers": {…}}` object Claude Code's `--mcp-config` takes; it works
|
|
34
|
+
on both brains. The name `prohost` is reserved and an entry using it is
|
|
35
|
+
rejected out loud rather than silently honoured; a malformed file is a warning
|
|
36
|
+
and a run without extras rather than a run that doesn't happen; and which
|
|
37
|
+
servers loaded is printed when the grant set changes so `agent.log` can answer "did my server
|
|
38
|
+
load". Every server in that file is exercisable by anyone who can trigger the
|
|
39
|
+
agent, which the README, the file's own documentation and `--help` all say in
|
|
40
|
+
those words.
|
|
41
|
+
- **Codex gets the isolation Claude Code has.** 0.2.2 shipped with "there is no
|
|
42
|
+
Codex equivalent of `--strict-mcp-config`", which turned out to be true of the
|
|
43
|
+
two mechanisms that look like one and false overall. `-c mcp_servers={…}` does
|
|
44
|
+
not replace the table — Codex deep-merges overrides — and `--ignore-user-config`
|
|
45
|
+
works but discards the operator's model, profile and sandbox settings along
|
|
46
|
+
with their servers. The one that works is per-server: the CLI now asks the
|
|
47
|
+
binary what it has (`codex mcp list --json`) and passes a single
|
|
48
|
+
`-c mcp_servers={"<name>"={enabled=false}, …}`, verified end to end against
|
|
49
|
+
`codex-cli 0.144.1` — the tools are genuinely gone from the model's list
|
|
50
|
+
mid-run, each server's own definition survives, and servers not named are
|
|
51
|
+
untouched. Asking the binary rather than parsing `~/.codex/config.toml`
|
|
52
|
+
matters twice over: on a normal install `codex mcp list` reports servers that
|
|
53
|
+
appear nowhere in that file, and the name has to go inside the value because
|
|
54
|
+
a server called `fly.io` written as `-c mcp_servers.fly.io.enabled=false`
|
|
55
|
+
addresses a three-segment path and makes Codex reject the entire config. The
|
|
56
|
+
override is also **verified against the binary before it is used**, which one
|
|
57
|
+
case genuinely needs: `{enabled=false}` merges onto a server's existing
|
|
58
|
+
definition, and a plugin-contributed server has none to merge onto, so the
|
|
59
|
+
override leaves a table with no transport and Codex refuses to start —
|
|
60
|
+
shipped unverified that would have broken every run for anyone with a Codex
|
|
61
|
+
plugin installed. The CLI falls back to checking each server on its own and
|
|
62
|
+
fails the run before Codex starts if any cannot be disabled. If the
|
|
63
|
+
enumeration fails outright the run also fails closed instead of proceeding
|
|
64
|
+
under `--yolo` with an unprovable boundary. Same treatment for `extra-mcp.json` definitions:
|
|
65
|
+
rejected ones are dropped with a warning rather than taking the run with them.
|
|
66
|
+
Every probe now runs in the exact cwd of the eventual Codex process as well,
|
|
67
|
+
so a trusted repo's project-local `.codex/config.toml` cannot appear only
|
|
68
|
+
after a clean-directory survey. Default `$PROHOST_HOME/workspace` and an
|
|
69
|
+
explicit harness `--workdir` both use that same path end to end.
|
|
70
|
+
|
|
71
|
+
Three further things the binary had to be asked rather than assumed. Every
|
|
72
|
+
server goes in **one** `-c mcp_servers={…}`, ProhostAI included: `-c`
|
|
73
|
+
overrides apply in order and a later whole-table assignment *replaces* an
|
|
74
|
+
earlier dotted one, so the obvious two-override shape silently dropped the
|
|
75
|
+
ProhostAI server on every run that disabled anything. An extra whose name
|
|
76
|
+
your Codex config already uses is **refused**, because Codex merges the two
|
|
77
|
+
definitions and the existing server's `Authorization` header would be sent to
|
|
78
|
+
the extra's URL. The extras file and Codex survey are refreshed before every
|
|
79
|
+
run, so deleting a grant or installing a new server takes effect immediately
|
|
80
|
+
in a long-lived daemon. Literal header and stdio environment secrets from the
|
|
81
|
+
Claude-shaped extras file are moved into Codex's child environment instead of
|
|
82
|
+
being exposed in process arguments.
|
|
83
|
+
|
|
84
|
+
One gap stays open and is documented rather than papered over: a `--exec`
|
|
85
|
+
running Codex under a named profile (`-p work`) can have MCP servers defined
|
|
86
|
+
only in `work.config.toml`, and `codex mcp list` cannot be asked about a
|
|
87
|
+
profile at all — `-p`/`--profile` are rejected and `-c profile="…"` is refused
|
|
88
|
+
as a removed legacy form. The CLI now fails the run before spawning Codex
|
|
89
|
+
when it sees a profile flag, including clap's joined `-pwork` spelling. It
|
|
90
|
+
also fails closed on `-c` / `--config`, feature toggles,
|
|
91
|
+
`--ignore-user-config`, and cwd-changing `-C` / `--cd` flags, because those
|
|
92
|
+
can make the real run's plugin/MCP inventory differ from the plain verifier
|
|
93
|
+
command. Finally, an
|
|
94
|
+
unrecognised item inside otherwise-valid `mcp list --json` output now makes
|
|
95
|
+
the entire survey unknown instead of being silently mistaken for no server.
|
|
96
|
+
Recognisable Codex commands containing shell quoting, expansion, glob,
|
|
97
|
+
comment or control syntax now fail before spawn instead of falling through to
|
|
98
|
+
the unshaped path; in particular, quoting an inventory-changing flag cannot
|
|
99
|
+
hide it from the verifier. The final probe must also report the ProhostAI
|
|
100
|
+
server and every accepted extra enabled — a successful empty or partial list
|
|
101
|
+
is no longer accepted as proof. Finally, all environment names referenced by
|
|
102
|
+
every extra are reserved before literal-header secret variables are
|
|
103
|
+
allocated, preventing one extra from naming and reading another extra's
|
|
104
|
+
generated credential, including through differently-cased aliases on Windows.
|
|
105
|
+
Ordinary unquoted Windows executable and workspace paths remain accepted by
|
|
106
|
+
the shell-syntax guard while `cmd.exe` expansion and control syntax still
|
|
107
|
+
fail closed.
|
|
108
|
+
- **Codex reports refused tools.** Also filed under "no equivalent" in 0.2.2.
|
|
109
|
+
Codex has no `permission_denials` field, but a refused call arrives as an
|
|
110
|
+
`mcp_tool_call` item with `status: "failed"` and `user cancelled MCP tool
|
|
111
|
+
call`, which is what every run with nobody at the terminal produces. Those are
|
|
112
|
+
now printed on the same line the Claude Code path uses. A call that failed for
|
|
113
|
+
another reason — a server returning 502 — is deliberately not counted, so the
|
|
114
|
+
log doesn't send you to your permissions over a network problem.
|
|
115
|
+
- **Codex is told how it spells its own tools.** The prompt used to name the MCP
|
|
116
|
+
server and leave the prefix out, because guessing a tool name is the same
|
|
117
|
+
failure as guessing an endpoint. Measured against the real binary by
|
|
118
|
+
registering a stub server and asking the model what it had just called: Codex
|
|
119
|
+
uses the same `mcp__<server>__<tool>` convention as Claude Code, so both
|
|
120
|
+
brains now get the pattern.
|
|
121
|
+
|
|
122
|
+
## 0.2.2
|
|
123
|
+
|
|
124
|
+
Two agents paired to the same workspace behaved like different products: the
|
|
125
|
+
one on Claude Code remembered its threads and could look things up, the one on
|
|
126
|
+
Codex ran bare. And neither knew who its operator had hired it to be — the
|
|
127
|
+
persona configured in the ProhostAI agent profile never left the server.
|
|
128
|
+
|
|
129
|
+
- **Your agent gets its soul.** `agent.run_requested` may now carry
|
|
130
|
+
`system_prompt` — the standing instructions written in the agent's ProhostAI
|
|
131
|
+
profile — and `agent_title`. The instructions render at the very top of the
|
|
132
|
+
prompt, above memories and history, fenced off from the server-generated
|
|
133
|
+
sections so the agent can always tell where its operator stops talking. The
|
|
134
|
+
title renders alongside the name: `You are "Ada" (Guest Experience Lead)`. The
|
|
135
|
+
fence is followed by a line stating that what comes after is generated for
|
|
136
|
+
this run and describes what is actually true about it — a soul configures the
|
|
137
|
+
agent, it does not get to talk it out of reporting a lookup it never made.
|
|
138
|
+
Both fields are optional in both directions, so an older server behaves
|
|
139
|
+
exactly as before.
|
|
140
|
+
- **Attachments are visible.** `recent_messages[].attachments` and
|
|
141
|
+
`trigger_attachments` render as `[attached image "boiler.jpg": <url>]` inline
|
|
142
|
+
in the thread and as a list under the triggering message. An image-only
|
|
143
|
+
message — a guest who sends a photo of a broken boiler and nothing else —
|
|
144
|
+
used to be dropped entirely by the parser for having no text; it now survives
|
|
145
|
+
as the attachment it is, and the prompt says so rather than reporting the text
|
|
146
|
+
as missing. The agent is told plainly that a URL is not something it can see,
|
|
147
|
+
and to download one to a local file before opening it, because vision works on
|
|
148
|
+
local files and not on links.
|
|
149
|
+
- **The agent stops claiming it has no access to your machine.** A brain
|
|
150
|
+
running unsandboxed on a laptop that holds the checkout, the credentials and
|
|
151
|
+
the CLI was still answering "I don't have access to the codebase". Ungated
|
|
152
|
+
runs are now told they are on the operator's own machine, in their shell, and
|
|
153
|
+
to take a *quick* look — usual checkout locations, `gh auth status`, whether
|
|
154
|
+
the command is on PATH — before declaring a capability missing. A quick look,
|
|
155
|
+
not an audit: the run has a hard time cap. Under `--safe-tools` the licence is
|
|
156
|
+
withheld, because there the looking would itself sit waiting on an approval
|
|
157
|
+
nobody is there to give.
|
|
158
|
+
- **Codex gets the harness Claude Code has had since 0.2.0.** A `--exec` of
|
|
159
|
+
`codex exec` is now detected and shaped, with the same `--safe-tools` opt-out:
|
|
160
|
+
|
|
161
|
+
| | Claude Code | Codex |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| Structured output | `--output-format json` | `--json` (JSONL) |
|
|
164
|
+
| Session continuity | `--resume <id>` | `codex exec resume <thread>` |
|
|
165
|
+
| Tools | `--mcp-config` + `--strict-mcp-config` | `-c mcp_servers.prohost={…}` |
|
|
166
|
+
| Credential | `X-API-Key` in a `0600` file | `X-API-Key` via `env_http_headers` |
|
|
167
|
+
| Ungated by default | `--dangerously-skip-permissions` | `--yolo` |
|
|
168
|
+
|
|
169
|
+
Three things worth knowing about the Codex path. `--skip-git-repo-check` is
|
|
170
|
+
passed unconditionally, because the harness workspace is not a git repository
|
|
171
|
+
and Codex otherwise exits before the model is ever reached. `--yolo` is a
|
|
172
|
+
hidden alias of `--dangerously-bypass-approvals-and-sandbox` and passing both
|
|
173
|
+
spellings is a hard argument error, so a host who already wrote either one
|
|
174
|
+
never gets a second. And `resume` is appended *after* the host's own flags,
|
|
175
|
+
since `codex exec resume` accepts a strict subset of `codex exec`'s — a flag
|
|
176
|
+
that is legal before `resume` is rejected after it. When a resume is rejected
|
|
177
|
+
anyway, the run falls back to a fresh thread instead of failing the surface.
|
|
178
|
+
- **`--safe-tools` now removes a bypass you wrote yourself.** It used to only
|
|
179
|
+
decline to *add* one, so `--exec 'codex exec - --yolo' --safe-tools` ran fully
|
|
180
|
+
unsandboxed while the harness logged "tools are permission-gated" and the
|
|
181
|
+
prompt told the agent approvals would stop it. Since this README recommended
|
|
182
|
+
exactly that `--exec` until now, the flag is sitting in existing installs. A
|
|
183
|
+
safety switch that quietly does nothing is worse than not having one, so both
|
|
184
|
+
`--yolo` / `--dangerously-bypass-approvals-and-sandbox` (Codex) and
|
|
185
|
+
`--dangerously-skip-permissions` (Claude Code) are now stripped when
|
|
186
|
+
`--safe-tools` is passed. Note this covers the full bypass only —
|
|
187
|
+
`--full-auto` still auto-approves inside Codex's sandbox.
|
|
188
|
+
- **A Codex command carrying its own prompt is left alone.** `codex exec 'Be
|
|
189
|
+
terse'` keeps the plain stdin/stdout contract: Codex takes exactly one prompt
|
|
190
|
+
positional, so the `-` this harness appends would be a second one and the run
|
|
191
|
+
would fail outright with `unexpected argument '-'`. Standing instructions
|
|
192
|
+
belong in the agent's ProhostAI profile now, which is where `system_prompt`
|
|
193
|
+
comes from.
|
|
194
|
+
- **The Codex credential never touches the command line.** Codex can read an
|
|
195
|
+
MCP header out of the environment (`env_http_headers`), so the API key is
|
|
196
|
+
passed to the child that way rather than inside the `-c` override. A command
|
|
197
|
+
line is world-readable out of `ps`; a process environment is not. This makes
|
|
198
|
+
the Codex path slightly *safer* than the Claude one, which still has to keep
|
|
199
|
+
the key in a `0600` file on disk.
|
|
200
|
+
|
|
201
|
+
**Residual gaps on the Codex path**, both stated at start-up or in the prompt
|
|
202
|
+
rather than papered over:
|
|
203
|
+
|
|
204
|
+
- **No `--strict-mcp-config` equivalent.** Codex's `--ignore-user-config` was
|
|
205
|
+
the obvious candidate and does not work: it drops the `-c` overrides along
|
|
206
|
+
with the user config, taking the ProhostAI server with it. So a Codex run also
|
|
207
|
+
holds whatever MCP servers `~/.codex/config.toml` defines, with approvals
|
|
208
|
+
bypassed. The harness prints this at start-up rather than leaving an operator
|
|
209
|
+
to assume the Claude Code isolation applies.
|
|
210
|
+
- **Codex's MCP tool naming is unverified**, so the prompt names the server
|
|
211
|
+
(`registered as "prohost"`) instead of asserting a `mcp__prohost__*` pattern
|
|
212
|
+
it has not confirmed. The agent can read its own tool list; a wrong tool name
|
|
213
|
+
is the same failure as a wrong endpoint.
|
|
214
|
+
- **No permission-denial reporting.** Claude Code returns `permission_denials`
|
|
215
|
+
and the harness prints them; Codex's event stream has no equivalent, so a
|
|
216
|
+
refused tool stays as invisible on that path as it was before 0.2.0.
|
|
217
|
+
|
|
7
218
|
## 0.2.1
|
|
8
219
|
|
|
9
220
|
- Prompt: tell brains with a ToolSearch tool that part of their toolkit
|
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ in a team thread, a 1:1 DM, a task comment, a trigger — you'll see:
|
|
|
87
87
|
Other agents, same command:
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
|
-
npx @prohost/cli agent run --exec 'codex exec
|
|
90
|
+
npx @prohost/cli agent run --exec 'codex exec'
|
|
91
91
|
npx @prohost/cli agent run --exec 'goose run -i -'
|
|
92
92
|
npx @prohost/cli agent run --exec './my-agent.sh'
|
|
93
93
|
```
|
|
@@ -109,13 +109,14 @@ nothing is posted and the run is left open.
|
|
|
109
109
|
2. The CLI verifies the event's HMAC signature. An event that doesn't verify is
|
|
110
110
|
**not executed** — the whole point of the signature is that a run event makes
|
|
111
111
|
your machine execute a command.
|
|
112
|
-
3. It builds a prompt — who your agent is,
|
|
113
|
-
|
|
114
|
-
the
|
|
115
|
-
prompt on **stdin**.
|
|
112
|
+
3. It builds a prompt — who your agent is, the standing instructions you wrote
|
|
113
|
+
in its ProhostAI profile, what it's replying to, the recent messages in the
|
|
114
|
+
thread, anything the account remembers that bears on it, and the message that
|
|
115
|
+
mentioned it — and runs your `--exec` command with that prompt on **stdin**.
|
|
116
|
+
Images and files on those messages appear as URLs the agent can download.
|
|
116
117
|
4. Your command's **stdout** is the reply. It's posted into the conversation
|
|
117
118
|
verbatim, so print the message and nothing else — no preamble, no reasoning.
|
|
118
|
-
(Claude Code
|
|
119
|
+
(Claude Code and Codex are the exceptions: see below.)
|
|
119
120
|
5. The run is completed either way. A command that crashes, times out, or prints
|
|
120
121
|
nothing reports `failed`; a run is never left hanging.
|
|
121
122
|
|
|
@@ -135,8 +136,9 @@ directory. Just choose it deliberately.
|
|
|
135
136
|
### If your agent is Claude Code
|
|
136
137
|
|
|
137
138
|
When `--exec` starts with `claude` and asks for print mode (`-p` / `--print`),
|
|
138
|
-
the CLI does three extra things.
|
|
139
|
-
|
|
139
|
+
the CLI does three extra things. Codex gets the same three through its own
|
|
140
|
+
flags — see "If your agent is Codex" below. Every other command — goose, your
|
|
141
|
+
own script — is left exactly alone.
|
|
140
142
|
|
|
141
143
|
**It keeps the conversation.** Each run is a fresh process, so without this your
|
|
142
144
|
agent meets every message as its first: someone answers its clarifying question
|
|
@@ -177,6 +179,180 @@ word of the command *and* print mode is asked for. Anything else — `npx claude
|
|
|
177
179
|
redirect, or `&&` — keeps the plain stdin/stdout contract untouched. So
|
|
178
180
|
`--exec 'sh -c "claude -p"'` is the way to opt out.
|
|
179
181
|
|
|
182
|
+
### Giving the agent other MCP servers
|
|
183
|
+
|
|
184
|
+
One server by default is a boundary, not an oversight: a run executes with
|
|
185
|
+
permission prompts off, on prompt text written by whoever mentioned your agent.
|
|
186
|
+
Every MCP server reachable from that run is a server your workspace teammates
|
|
187
|
+
can drive.
|
|
188
|
+
|
|
189
|
+
If you want to widen it anyway, create `~/.prohost/extra-mcp.json` by hand. The
|
|
190
|
+
CLI never creates this file — its existence is you saying yes on purpose. The
|
|
191
|
+
shape is the same `mcpServers` object Claude Code's `--mcp-config` takes, so you
|
|
192
|
+
can lift a block straight out of an existing `.mcp.json`:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"mcpServers": {
|
|
197
|
+
"linear": {
|
|
198
|
+
"type": "http",
|
|
199
|
+
"url": "https://mcp.linear.app/mcp",
|
|
200
|
+
"headers": { "Authorization": "Bearer lin_api_…" }
|
|
201
|
+
},
|
|
202
|
+
"notes": {
|
|
203
|
+
"command": "npx",
|
|
204
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/notes"]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Read this before you use it.** Every server in that file is exercisable by
|
|
211
|
+
anyone who can trigger your agent — a teammate, a guest whose message routes to
|
|
212
|
+
it — with permission prompts off and no human in the loop. A filesystem server
|
|
213
|
+
scoped at `/` is a filesystem server they can read. Scope each one as narrowly
|
|
214
|
+
as it will go.
|
|
215
|
+
|
|
216
|
+
Works on both Claude Code and Codex. Details:
|
|
217
|
+
|
|
218
|
+
- The name `prohost` is **reserved**. An entry using it is rejected with a line
|
|
219
|
+
in the log and the ProhostAI server stays pointed where pairing put it.
|
|
220
|
+
- **On Codex, a name your own config already uses is also rejected.** Codex
|
|
221
|
+
merges the two definitions rather than replacing one with the other, so an
|
|
222
|
+
extra that changes only the URL would inherit the configured server's headers
|
|
223
|
+
and send its credential to the new host. Rename the extra to use it.
|
|
224
|
+
- Malformed JSON costs you the extras, not the run: the CLI warns and starts
|
|
225
|
+
without them. The same is true of an entry whose value isn't an object.
|
|
226
|
+
- The file is re-read before every run. Deleting an entry revokes it for the
|
|
227
|
+
next invocation of an already-running daemon; no restart is required.
|
|
228
|
+
- Which extras loaded is printed when the grant set changes, so `agent.log` answers "did my
|
|
229
|
+
server actually load" without guessing.
|
|
230
|
+
- On Codex the non-secret parts of each definition become `-c` overrides, which
|
|
231
|
+
means URLs, commands, and arguments land on a command line other users can
|
|
232
|
+
read via `ps`. Literal `headers`, `http_headers`, and stdio `env` values are
|
|
233
|
+
moved to the child environment instead. On Claude Code the definitions go
|
|
234
|
+
into `~/.prohost/mcp.json`, which is `chmod 600`.
|
|
235
|
+
- Also on Codex: a value with something TOML can't express (an infinity, say)
|
|
236
|
+
makes that server skipped with a warning, rather than emitted as an override
|
|
237
|
+
that would fail to parse and take the ProhostAI server down with it. Any
|
|
238
|
+
server *name* works, dots included.
|
|
239
|
+
- Delete the file to go back to one server. There is no flag to disable it,
|
|
240
|
+
because a file you have to write by hand is already the opt-in.
|
|
241
|
+
|
|
242
|
+
### If your agent is Codex
|
|
243
|
+
|
|
244
|
+
When `--exec` starts with `codex` and uses the `exec` subcommand, the CLI does
|
|
245
|
+
the same three things it does for Claude Code, through Codex's own flags. The
|
|
246
|
+
detection is just as narrow: interactive `codex`, a wrapper script, a command
|
|
247
|
+
that already says `codex exec resume` / `codex exec review`, or one
|
|
248
|
+
carrying its own unquoted prompt (`codex exec Be-terse`) keep the plain
|
|
249
|
+
stdin/stdout contract untouched. That last one matters: Codex takes exactly one
|
|
250
|
+
prompt positional, so the CLI cannot add its own alongside yours. Shell syntax
|
|
251
|
+
inside a recognisable Codex command — quoting, escapes, expansions, globs,
|
|
252
|
+
comments, redirects, pipelines or lists — instead fails before Codex starts.
|
|
253
|
+
The harness cannot safely make a whitespace scan agree with both POSIX shells
|
|
254
|
+
and `cmd.exe`, and treating such a command as plain would silently drop MCP
|
|
255
|
+
isolation. Ordinary unquoted Windows path separators remain supported. Put
|
|
256
|
+
standing instructions in the agent's ProhostAI profile instead — they arrive at
|
|
257
|
+
the top of the prompt, ahead of everything else.
|
|
258
|
+
|
|
259
|
+
| | Claude Code | Codex |
|
|
260
|
+
| --- | --- | --- |
|
|
261
|
+
| Reply read from | `--output-format json` | `--json` (JSONL event stream) |
|
|
262
|
+
| Conversation kept with | `--resume <id>` | `codex exec resume <thread>` |
|
|
263
|
+
| Tools wired via | `--mcp-config` | one verified `-c mcp_servers={…}` override |
|
|
264
|
+
| API key travels in | `~/.prohost/mcp.json` (`chmod 600`) | the child's environment |
|
|
265
|
+
| Tools ungated by | `--dangerously-skip-permissions` | `--yolo` |
|
|
266
|
+
|
|
267
|
+
`--skip-git-repo-check` is also passed on every Codex run, and it is not
|
|
268
|
+
optional: your agent works in `~/.prohost/workspace`, which is not a git
|
|
269
|
+
repository, and Codex refuses to start there without it.
|
|
270
|
+
|
|
271
|
+
If you already pass `--yolo` (or `--dangerously-bypass-approvals-and-sandbox`)
|
|
272
|
+
in your own `--exec`, the CLI won't add a second one — passing both is an
|
|
273
|
+
argument error that would fail every run. `--safe-tools` works the same way it
|
|
274
|
+
does for Claude Code, and it *removes* a bypass flag you wrote yourself rather
|
|
275
|
+
than just declining to add one, so the gating it promises is real. (It covers
|
|
276
|
+
the full bypass only; `--full-auto` still auto-approves inside Codex's sandbox.)
|
|
277
|
+
|
|
278
|
+
**Your own MCP servers are switched off for agent runs.** Codex has no
|
|
279
|
+
`--strict-mcp-config`, so the CLI does the same job a different way: before each run
|
|
280
|
+
it asks your binary what servers it has (`codex mcp list --json`) and passes one
|
|
281
|
+
`-c mcp_servers={"<name>"={enabled=false}, …}` covering all of them. A run then
|
|
282
|
+
holds the ProhostAI server, plus anything in your `extra-mcp.json`, and nothing
|
|
283
|
+
else — the same boundary the Claude Code path gets. Each server's own definition
|
|
284
|
+
is left intact; only this invocation sees it disabled.
|
|
285
|
+
|
|
286
|
+
Two mechanisms that look like they should do this do not, and are worth naming
|
|
287
|
+
so nobody reaches for them. Replacing the `mcp_servers` table does not replace
|
|
288
|
+
it — Codex deep-merges config overrides, so your servers survive alongside
|
|
289
|
+
whatever the override adds. And `--ignore-user-config` works, but throws away
|
|
290
|
+
your model, profile and sandbox settings along with your servers.
|
|
291
|
+
|
|
292
|
+
The enumeration asks the binary rather than reading `~/.codex/config.toml`,
|
|
293
|
+
because that file is not the whole story: on a normal install `codex mcp list`
|
|
294
|
+
reports servers that appear nowhere in it. It also handles names a dotted-path
|
|
295
|
+
override cannot address — a server called `fly.io` written as
|
|
296
|
+
`-c mcp_servers.fly.io.enabled=false` addresses a three-segment path and Codex
|
|
297
|
+
rejects the whole config, which is why the name goes inside the value instead.
|
|
298
|
+
Every probe also runs in the exact directory the eventual Codex process will
|
|
299
|
+
use. That matters because a trusted project's `.codex/config.toml` is another
|
|
300
|
+
config layer: probing where the harness was launched and then running in
|
|
301
|
+
`$PROHOST_HOME/workspace` or an explicit `--workdir` would certify one MCP
|
|
302
|
+
inventory and execute another.
|
|
303
|
+
|
|
304
|
+
**The override is checked before it is used**, and one case needs it.
|
|
305
|
+
`{enabled=false}` merges onto a server's existing definition, and a server
|
|
306
|
+
contributed by a **plugin** has no `mcp_servers` entry to merge onto — so the
|
|
307
|
+
override leaves a table with no transport and Codex refuses to start at all.
|
|
308
|
+
The CLI therefore runs the disable past `codex mcp list` first, falls back to
|
|
309
|
+
checking each server individually if the combined form is rejected, and then
|
|
310
|
+
verifies the exact final override. If any configured server cannot be disabled,
|
|
311
|
+
or if the final listing does not show the ProhostAI server and every accepted
|
|
312
|
+
extra enabled, the run fails before Codex starts. Remove that server/plugin or
|
|
313
|
+
use Claude Code.
|
|
314
|
+
|
|
315
|
+
If the enumeration fails outright — an older Codex without `mcp list --json`,
|
|
316
|
+
or output the CLI doesn't recognise — the run fails before Codex starts. A
|
|
317
|
+
paired run never proceeds under `--yolo` when its MCP boundary cannot be proven.
|
|
318
|
+
Nothing here changes what your Codex config does outside agent runs.
|
|
319
|
+
|
|
320
|
+
**One gap that cannot be closed from here: profiles.** If your `--exec` runs
|
|
321
|
+
Codex under a named profile (`codex exec -p work`), that profile's
|
|
322
|
+
`work.config.toml` can define MCP servers of its own — and `codex mcp list` has
|
|
323
|
+
no way to be asked about a profile (`-p` and `--profile` are rejected, and the
|
|
324
|
+
`-c profile="…"` form is refused as a removed legacy spelling). Servers defined
|
|
325
|
+
only inside a profile are therefore invisible to the sweep. The CLI fails the
|
|
326
|
+
run before Codex starts when it sees a profile flag; run the agent without `-p`.
|
|
327
|
+
|
|
328
|
+
The same fail-closed rule applies to `-c` / `--config`, `--enable`, `--disable`,
|
|
329
|
+
`--ignore-user-config`, and `-C` / `--cd` in `--exec`. Those flags can change
|
|
330
|
+
config, plugin loading, or the project config discovery directory for the real
|
|
331
|
+
invocation, while the separate `codex mcp list` verifier cannot safely replay
|
|
332
|
+
an arbitrary shell command's arguments. Use the harness's `--workdir` when you
|
|
333
|
+
need a different directory; dedicated flags such as `--model` and `--sandbox`
|
|
334
|
+
remain supported.
|
|
335
|
+
|
|
336
|
+
The survey is retaken before every invocation, because
|
|
337
|
+
`install-daemon` runs for weeks and Codex rereads its configuration on every
|
|
338
|
+
invocation — a server you install on Tuesday would otherwise be reachable from
|
|
339
|
+
the next run. The log stays quiet unless the answer changes.
|
|
340
|
+
|
|
341
|
+
**Refused tools are reported**, as of 0.2.3. Codex has no `permission_denials`
|
|
342
|
+
field, but a refused call arrives as an `mcp_tool_call` item with
|
|
343
|
+
`status: "failed"` and `user cancelled MCP tool call`, and the CLI prints those
|
|
344
|
+
in the same line the Claude Code path uses. A tool call that failed for any
|
|
345
|
+
other reason — an MCP server that 500s — is deliberately not reported as a
|
|
346
|
+
denial, so the log doesn't send you to your permissions when the problem is
|
|
347
|
+
your network.
|
|
348
|
+
|
|
349
|
+
Your ProhostAI API key is passed to Codex in the child process's environment
|
|
350
|
+
rather than in the `-c` override, because a command line is readable by anyone
|
|
351
|
+
on the machine via `ps` and a process environment is not. Literal `headers`,
|
|
352
|
+
`http_headers`, and stdio `env` values in `extra-mcp.json` get the same
|
|
353
|
+
treatment: the CLI translates them to Codex's environment-indirection fields,
|
|
354
|
+
so their values do not appear in process arguments.
|
|
355
|
+
|
|
180
356
|
Runs are handled one at a time, and each run executes **at most once on this
|
|
181
357
|
machine** — even across restarts. Started runs are recorded in
|
|
182
358
|
`~/.prohost/runs.json` before the command launches, so a webhook redelivery
|
|
@@ -193,8 +369,11 @@ someone in your ProhostAI workspace asks. Be clear-eyed about what that means:
|
|
|
193
369
|
- **The agent runs as you.** Same user, same filesystem, same SSH keys, same
|
|
194
370
|
logged-in CLIs. It is not sandboxed.
|
|
195
371
|
- **By default its tools are not gated.** With `--dangerously-skip-permissions`
|
|
196
|
-
(
|
|
197
|
-
makes it useful and it is also the whole risk.
|
|
372
|
+
(Claude Code) or `--yolo` (Codex) nothing pauses for confirmation. That is
|
|
373
|
+
what makes it useful and it is also the whole risk.
|
|
374
|
+
- **Its standing instructions come from the ProhostAI agent profile.** Whoever
|
|
375
|
+
can edit that profile is configuring a brain that runs unsandboxed on this
|
|
376
|
+
machine. Treat edit access to it as access to your laptop.
|
|
198
377
|
- **The instructions come from other people.** The prompt carries a message a
|
|
199
378
|
teammate wrote, plus thread history. Treat all of it as untrusted input — the
|
|
200
379
|
same judgement you'd apply to any agent reading text from the internet. A
|
|
@@ -204,16 +383,17 @@ someone in your ProhostAI workspace asks. Be clear-eyed about what that means:
|
|
|
204
383
|
What the harness does to bound this: the message reaches your command on
|
|
205
384
|
**stdin only**, never the shell, so it cannot inject a command; runs execute at
|
|
206
385
|
most once each; every event's signature is verified before anything runs; the
|
|
207
|
-
agent works in `~/.prohost/workspace` rather than your code; and
|
|
208
|
-
`--strict-mcp-config` keeps your own MCP servers out of the run.
|
|
386
|
+
agent works in `~/.prohost/workspace` rather than your code; and, on Claude
|
|
387
|
+
Code, `--strict-mcp-config` keeps your own MCP servers out of the run. Codex has
|
|
388
|
+
no equivalent — see "If your agent is Codex".
|
|
209
389
|
|
|
210
390
|
What it does **not** do: sandbox the process. If that matters for your machine,
|
|
211
391
|
run `agent run` inside a container or a dedicated user account, or use
|
|
212
392
|
`--safe-tools` and accept that the agent will answer rather than act.
|
|
213
393
|
|
|
214
|
-
Other agents: the CLI shapes no command but Claude Code's, so give
|
|
215
|
-
equivalent flag —
|
|
216
|
-
|
|
394
|
+
Other agents: the CLI shapes no command but Claude Code's and Codex's, so give
|
|
395
|
+
yours its own equivalent flag — whatever your runner calls it — and apply the
|
|
396
|
+
same judgement.
|
|
217
397
|
|
|
218
398
|
### `agent run` flags
|
|
219
399
|
|
|
@@ -227,7 +407,7 @@ and apply the same judgement.
|
|
|
227
407
|
| `--machine` | OS hostname | Name shown next to the connection in ProhostAI. |
|
|
228
408
|
| `--url` | paired value | Websocket endpoint override. Pairing stores the URL the server reports, so you should not normally need this. |
|
|
229
409
|
| `--allow-unverified` | off | Run events whose signature can't be verified. See below. |
|
|
230
|
-
| `--safe-tools` | off | Gate the agent's tools behind permission prompts instead of bypassing them. Claude Code only. Alias: `--no-skip-permissions`. |
|
|
410
|
+
| `--safe-tools` | off | Gate the agent's tools behind permission prompts instead of bypassing them. Claude Code and Codex only. Alias: `--no-skip-permissions`. |
|
|
231
411
|
|
|
232
412
|
### Keeping it running
|
|
233
413
|
|
|
@@ -307,8 +487,9 @@ Everything the CLI stores lives under `~/.prohost` (`$PROHOST_HOME` moves it):
|
|
|
307
487
|
| --- | --- |
|
|
308
488
|
| `agent.json` | Credentials. Delete to un-pair locally; revoke in ProhostAI to invalidate the key itself. |
|
|
309
489
|
| `runs.json` | Runs already executed on this machine, so a redelivery can't run your agent twice. |
|
|
310
|
-
| `sessions.json` | Conversation → Claude Code session, for continuity. Safe to delete; costs the agent its memory of open threads. |
|
|
490
|
+
| `sessions.json` | Conversation → Claude Code session / Codex thread, for continuity. Safe to delete; costs the agent its memory of open threads. |
|
|
311
491
|
| `mcp.json` | Generated MCP config. Holds your API key — `chmod 600`. |
|
|
492
|
+
| `extra-mcp.json` | Optional, written by you. Extra MCP servers to expose to agent runs — see "Giving the agent other MCP servers". Never created by the CLI. |
|
|
312
493
|
| `workspace/` | Where your agent command runs by default. |
|
|
313
494
|
| `agent.log` | Daemon output. |
|
|
314
495
|
|
package/dist/agent/claude.d.ts
CHANGED
|
@@ -14,13 +14,8 @@
|
|
|
14
14
|
* session is unresumable, if the JSON is not what we expect — the run still
|
|
15
15
|
* happens and still completes. These are enhancements that must fail soft.
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* its tools appear as `mcp__prohost__<tool>`. Referenced in the prompt, in the
|
|
20
|
-
* `--allowedTools` pattern, and in the config file — keep them in sync from
|
|
21
|
-
* here rather than by hand.
|
|
22
|
-
*/
|
|
23
|
-
export declare const MCP_SERVER_NAME = "prohost";
|
|
17
|
+
import { MCP_SERVER_NAME } from './mcp.js';
|
|
18
|
+
export { MCP_SERVER_NAME };
|
|
24
19
|
/**
|
|
25
20
|
* What goes to `--allowedTools` so the ProhostAI tools are callable.
|
|
26
21
|
*
|
|
@@ -92,7 +87,10 @@ export interface ClaudeFlags {
|
|
|
92
87
|
* servers in the operator's own configuration with ours, so a run answering a
|
|
93
88
|
* customer's question would hold whatever MCP servers they use for their day
|
|
94
89
|
* job — with permission checks off. Ours is the only one this run is entitled
|
|
95
|
-
* to.
|
|
90
|
+
* to, plus whatever the operator listed in `extra-mcp.json` — a file they had
|
|
91
|
+
* to write by hand, which is the point: the boundary still holds, and crossing
|
|
92
|
+
* it is a decision somebody made on purpose rather than one their day-job
|
|
93
|
+
* config made for them.
|
|
96
94
|
*/
|
|
97
95
|
export declare function claudeCommand(base: string, flags?: ClaudeFlags): string;
|
|
98
96
|
/** What the harness got back from one Claude Code invocation. */
|
|
@@ -136,10 +134,25 @@ export declare function looksLikeResumeFailure(output: string, sessionId: string
|
|
|
136
134
|
/** Absolute path of the generated MCP config. */
|
|
137
135
|
export declare function mcpConfigPath(env?: NodeJS.ProcessEnv): string;
|
|
138
136
|
export interface McpConfigOptions {
|
|
139
|
-
/**
|
|
140
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The MCP endpoint the pairing reported, e.g. `https://connect.prohost.ai/mcp`.
|
|
139
|
+
*
|
|
140
|
+
* Optional, because a pairing can carry none — and a run with no ProhostAI
|
|
141
|
+
* server is still a run whose operator may have opted into extras. Omitted,
|
|
142
|
+
* the generated config holds only those, and `--strict-mcp-config` still
|
|
143
|
+
* keeps everything else out.
|
|
144
|
+
*/
|
|
145
|
+
mcpUrl?: string;
|
|
141
146
|
/** The paired API key. Written to a `0600` file and nowhere else. */
|
|
142
147
|
apiKey: string;
|
|
148
|
+
/**
|
|
149
|
+
* Servers the operator opted into via `$PROHOST_HOME/extra-mcp.json`.
|
|
150
|
+
*
|
|
151
|
+
* Merged in *under* ours, never over it: {@link loadExtraMcpServers} already
|
|
152
|
+
* refuses an entry named `prohost`, and writing ours last means even a future
|
|
153
|
+
* bug there cannot end with a run's workspace tools pointed somewhere else.
|
|
154
|
+
*/
|
|
155
|
+
extraServers?: Record<string, Record<string, unknown>>;
|
|
143
156
|
env?: NodeJS.ProcessEnv;
|
|
144
157
|
}
|
|
145
158
|
/**
|
package/dist/agent/claude.js
CHANGED
|
@@ -17,13 +17,9 @@
|
|
|
17
17
|
import { chmodSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { prohostHome } from './credentials.js';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* `--allowedTools` pattern, and in the config file — keep them in sync from
|
|
24
|
-
* here rather than by hand.
|
|
25
|
-
*/
|
|
26
|
-
export const MCP_SERVER_NAME = 'prohost';
|
|
20
|
+
import { MCP_SERVER_NAME } from './mcp.js';
|
|
21
|
+
import { SHELL_CONTROL_OPERATORS, shellQuote } from './shell.js';
|
|
22
|
+
export { MCP_SERVER_NAME };
|
|
27
23
|
/**
|
|
28
24
|
* What goes to `--allowedTools` so the ProhostAI tools are callable.
|
|
29
25
|
*
|
|
@@ -75,27 +71,6 @@ export function isClaudeExec(command) {
|
|
|
75
71
|
return false;
|
|
76
72
|
return words.some((word) => word === '-p' || word === '--print');
|
|
77
73
|
}
|
|
78
|
-
/** Pipes, lists, redirections, subshells — anything that makes "append to the end" wrong. */
|
|
79
|
-
const SHELL_CONTROL_OPERATORS = /[|&;<>(){}`]|\$\(/;
|
|
80
|
-
/**
|
|
81
|
-
* Quote a value for a `shell: true` command line on this platform.
|
|
82
|
-
*
|
|
83
|
-
* Every value that goes through here is ours or the operator's — a session id
|
|
84
|
-
* we stored, a path under `$PROHOST_HOME` — never event-derived text. It is
|
|
85
|
-
* quoted anyway: a home directory with a space in it is ordinary, and "the
|
|
86
|
-
* inputs are trusted" is the assumption that stops being true the first time
|
|
87
|
-
* someone extends this.
|
|
88
|
-
*
|
|
89
|
-
* Windows needs its own branch. `shell: true` there runs `cmd.exe`, where a
|
|
90
|
-
* single quote is an ordinary character rather than a quoting one — POSIX
|
|
91
|
-
* quoting would hand Claude Code a config path with literal apostrophes around
|
|
92
|
-
* it, and the tools would simply never load.
|
|
93
|
-
*/
|
|
94
|
-
function shellQuote(value, platform) {
|
|
95
|
-
if (platform === 'win32')
|
|
96
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
97
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
98
|
-
}
|
|
99
74
|
/**
|
|
100
75
|
* Append the flags this harness needs to a Claude Code command.
|
|
101
76
|
*
|
|
@@ -112,13 +87,29 @@ function shellQuote(value, platform) {
|
|
|
112
87
|
* servers in the operator's own configuration with ours, so a run answering a
|
|
113
88
|
* customer's question would hold whatever MCP servers they use for their day
|
|
114
89
|
* job — with permission checks off. Ours is the only one this run is entitled
|
|
115
|
-
* to.
|
|
90
|
+
* to, plus whatever the operator listed in `extra-mcp.json` — a file they had
|
|
91
|
+
* to write by hand, which is the point: the boundary still holds, and crossing
|
|
92
|
+
* it is a decision somebody made on purpose rather than one their day-job
|
|
93
|
+
* config made for them.
|
|
116
94
|
*/
|
|
117
95
|
export function claudeCommand(base, flags = {}) {
|
|
118
96
|
const platform = flags.platform ?? process.platform;
|
|
119
97
|
const quote = (value) => shellQuote(value, platform);
|
|
120
98
|
const skipPermissions = flags.skipPermissions !== false;
|
|
121
|
-
|
|
99
|
+
// `--safe-tools` removes the bypass rather than merely declining to add it.
|
|
100
|
+
// A host who wrote `--dangerously-skip-permissions` into their own `--exec`
|
|
101
|
+
// and *also* passed `--safe-tools` is contradicting themselves, and the only
|
|
102
|
+
// safe way to resolve that is in favour of the flag whose entire purpose is
|
|
103
|
+
// to gate — otherwise the harness announces "tools are permission-gated" over
|
|
104
|
+
// a run that bypasses every one of them.
|
|
105
|
+
const command = skipPermissions
|
|
106
|
+
? base.trim()
|
|
107
|
+
: base
|
|
108
|
+
.trim()
|
|
109
|
+
.split(/\s+/)
|
|
110
|
+
.filter((word) => word !== '--dangerously-skip-permissions')
|
|
111
|
+
.join(' ');
|
|
112
|
+
const parts = [command, '--output-format', 'json'];
|
|
122
113
|
if (skipPermissions)
|
|
123
114
|
parts.push('--dangerously-skip-permissions');
|
|
124
115
|
if (flags.sessionId)
|
|
@@ -280,8 +271,11 @@ export function mcpConfigPath(env = process.env) {
|
|
|
280
271
|
* the filesystem, and so the header scheme is stated in exactly one place.
|
|
281
272
|
*/
|
|
282
273
|
export function buildMcpConfig(options) {
|
|
274
|
+
if (!options.mcpUrl)
|
|
275
|
+
return { mcpServers: { ...(options.extraServers ?? {}) } };
|
|
283
276
|
return {
|
|
284
277
|
mcpServers: {
|
|
278
|
+
...(options.extraServers ?? {}),
|
|
285
279
|
[MCP_SERVER_NAME]: {
|
|
286
280
|
type: 'http',
|
|
287
281
|
// Verbatim, exactly as pairing reported it. An earlier revision appended
|