pendingyou 0.11.0 → 0.13.0

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 CHANGED
@@ -1,5 +1,147 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0 (2026-10-04)
4
+
5
+ - Claude Code's permission prompts on a card (Darin, 2026-10-04: "Tell me: a card and a push, cleared once I answer in
6
+ the terminal"). A Claude Code agent kept asking to remove files and sat waiting until Darin happened to look. `init`
7
+ now adds Claude Code's PermissionRequest hook (https://code.claude.com/docs/en/hooks: it runs as Claude Code is about
8
+ to ask for permission to use a tool, with `tool_name` and `tool_input`), through the shim with `--app claude-code`.
9
+ It never decides: it prints nothing, so the prompt shows exactly as before, and it's done in about a tenth of a
10
+ second. It writes the prompt down (its card's redacted words, never the tool's input) in the session's own file,
11
+ `~/.config/pendingyou/permission-cards/<session>.json`, and starts that session's one worker, detached (`pendingyou
12
+ permission-card`). Once a prompt has waited 10 seconds (one answered sooner gets no card), the worker posts one card
13
+ through Pending You's MCP with Claude Code's own connection here (whoami, match_area, post_request): an action card
14
+ titled “Claude Code is waiting for your OK: rm -rf dist/” (at most 90 characters; “…waiting for your answer: …” for
15
+ a question it asked, “…on its plan” for a plan), the folder and the computer in its summary, one step, “Answer it in
16
+ Claude Code on Ann-MBP” (with the session's link while Remote Control is on), asked in both places (`askedFirst`
17
+ terminal, at the prompt's time) so a push comes only after the person's hand-off wait, not blocking, urgency now. It
18
+ goes in the area `match_area` picks for the folder and its git remote (a new one for the work when nothing fits, as
19
+ the guide says), under the name the session gave Pending You (read from its transcript), else “Claude Code”, with an
20
+ idempotency key per prompt. One card per session: a newer prompt changes its words (`update_request`, with a
21
+ changeNote), never a second card. It goes once the person answers: the call running (PostToolUse, or
22
+ PostToolUseFailure) withdraws it with `answeredHere`; a denial runs neither, so the person's next message (the
23
+ hand-off hook) or the end of the turn (the Stop hook) does; the session ending (SessionEnd) withdraws it without. A
24
+ card they pressed Done on is acknowledged instead. A subagent's prompt outlasts the person's message, and the end of a
25
+ turn while a subagent still runs in the background. No card where nobody sees the prompt (`claude -p` and the Agent
26
+ SDK, whose CLAUDE_CODE_ENTRYPOINT starts `sdk-`; a dontAsk or bypassPermissions session), for a sign-in that only
27
+ hears (`--oauth`), or on Windows.
28
+ - Nothing secret on those cards (`src/redact.ts`). A command is cut to 120 characters, with the values after anything
29
+ named like a secret blanked (`--token …`, `--password=…`, `-p …` but not `mkdir -p`, `mysql -p…`, `-u user:…`,
30
+ `Authorization: …`, `Cookie: …`, `Bearer …`, `FOO_TOKEN=…`, `"password": "…"`, `aws_secret_access_key …`), a URL's
31
+ user and password and every query and fragment value blanked, and anything that looks like a key or token (the
32
+ well-known prefixes, JWTs, private keys, long hex, long runs of letters and digits in both cases, a webhook's secret
33
+ in a URL's path). A command that can't be shown safely (more than one line, a heredoc, inline code such as `node -e`
34
+ or `bash -c`, `eval`, or one that takes or sets a secret, like `sudo -S`, `--password-stdin` or `gh secret set`)
35
+ shows as the tool and its first word: “Bash: node …”. A path is shown under `~`, its end kept when it's long; a
36
+ question or a search keeps its words with any key taken out. The git remote `match_area` gets has no user name or
37
+ password.
38
+ - `init --no-permission-cards` leaves them out (later runs keep them out; `--permission-cards` puts them back), and
39
+ `status` has a line: “Permission prompts: a card when Claude Code waits for your OK”, off as the person chose, or why
40
+ this Claude Code can't have them. They need Claude Code 2.1.119 or later (PermissionRequest came in 2.0.45, and
41
+ before 2.1.101 a hook event Claude Code didn't know made it ignore the whole settings file) and Claude Code signing
42
+ in through this computer's sign-in. The SessionEnd hook sets no timeout of its own, so Claude Code's 1.5 seconds for
43
+ SessionEnd hooks stays as it was. The shim skips Node for `permission-done` while no session has a prompt written
44
+ down, so a tool call costs a few milliseconds (20 ms measured, 8 of them the shell's own); the PermissionRequest hook
45
+ takes about 0.14 s, as long as the other hooks. `uninstall` takes the hooks and the sessions' files out.
46
+ - The hand-off hook names no permission prompt's card among the agent's open cards, and neither hook hands an agent a
47
+ Done the person pressed on one: those close by themselves.
48
+ - The Codex app for macOS ("ChatGPT.app", bundle id com.openai.codex; 26.928 carries codex 0.159.2), which shares
49
+ `~/.codex` with Codex CLI: its MCP servers (the headers helper too), `~/.agents/skills` and `~/.codex/hooks.json`
50
+ with the same trust. Where `codex` isn't on PATH, `init` and `status` find the app's own codex
51
+ (`Contents/Resources/codex-cli/bin/codex`, or the `CodexCLI.app` the app's own code looks for there, in
52
+ `/Applications` or `~/Applications`, as ChatGPT.app or Codex.app) and run it for the version check (0.152 or later,
53
+ for the headers helper), `codex mcp list` and `codex mcp logout`; init says it found "Codex 0.159.2 (the Codex
54
+ app’s)", and `status` names its path. The listener runs it for `codex queue`: the app's own server reads the queue
55
+ while the app is open, so a chat it has loaded is woken when you answer (0.149 or later; one it let go, idle 3 hours
56
+ or behind 10 newer idle chats, hears it when it's opened again), and `status`'s Wake line says so.
57
+ - Codex's last lines name both ways to trust its hooks (“In Codex CLI choose “Trust all and continue” (or trust them in
58
+ /hooks); in the Codex app click Review hooks › Allow all (or Settings › Hooks › Trust all)”; the app alone: open it
59
+ and click Review hooks › Allow all), and say not to press Save on pendingyou in the Codex app's MCP settings: that
60
+ writes the table again without `http_headers_helper` (and `default_tools_approval_mode`), so Codex no longer signs in
61
+ through this computer's sign-in. `status` says so when the table has lost the helper init gave it (“has lost its
62
+ http_headers_helper … (saving it in the Codex app’s MCP settings does that) … run npx pendingyou init --app codex
63
+ again”), and init puts it back (“Put this computer’s sign-in back on Codex’s pendingyou MCP server”). Its Hooks line
64
+ names both ways to trust them.
65
+ - The setup line every app's session start gives, and the Claude Code wake mod's setup turn, say exactly the three
66
+ calls: “Pending You: Codex on Ann-MBP is connected, but its setup isn’t finished. Three calls, nothing to install or
67
+ approve: 1. whoami with your name. If it says app.setUp is true, another session did this: stop. 2. report_setup with
68
+ source codex, your name, skillSaved true, hears instant, and skillVersion set to whoami’s skill.latest. Its answer
69
+ has a testAreaId. 3. post_request a short test question in that testAreaId, tell your person it’s on its way, and end
70
+ your turn: you’re woken when they answer, so don’t ask them to type anything.” Pi's first setup on Darin's devbox
71
+ spent about 20,000 tokens on the words before: “whoami with your name and guide true” answered with the whole guide
72
+ (87,000 characters), which Pi cut into a file the model then parsed, and “your test question in its testAreaId” sent
73
+ it looking for testAreaId in whoami's answer, where it isn't (report_setup's has it). A setup that has reported gets
74
+ the same calls without whoami's way out (its whoami says app.setUp is true); a test card answered says who picks it
75
+ up, with the call's exact arguments. Pi's extension knows the new lines (and 0.12.0's) to start the setup turn; the
76
+ wake mod names the connection from whoami's answer.
77
+
78
+ ## 0.12.0 (2026-10-04)
79
+
80
+ - The stdio bridge, `pendingyou mcp --app <app>`: a local MCP server for an agent that can't give its MCP client a
81
+ header from a command (Claude Code's headersHelper, Codex's http_headers_helper), so its `pendingyou` server signs in
82
+ through this computer's own sign-in all the same, with no second sign-in. The agent runs it by a path that never
83
+ changes, `~/.config/pendingyou/bin/pendingyou-mcp --app <app>` (`--origin` off production), a small script that finds
84
+ Node as the headers helper does and runs this version's private copy, so the agent's config never changes on an
85
+ upgrade. It passes each JSON-RPC line from stdin on to `<origin>/mcp` with the agent's own connection, and each
86
+ answer back on a line of its own (a JSON body, or each event of a stream), with nothing else on stdout and only a few
87
+ plain lines on stderr. It keeps the server's session id, sends the protocol version the handshake agreed (and a
88
+ 2026-07-28 request's own headers), starts a session again when the server forgot it, refreshes the sign-in once after
89
+ a 401 (in the background, as the helper does) and then answers with an error saying to run `npx -y
90
+ pendingyou@latest init`; Pending You unreachable, a problem on its side, a redirect (never followed: the token goes
91
+ nowhere else) or a cut-off answer each get a plain JSON-RPC error. It waits for what's under way when stdin closes,
92
+ and stops at once on SIGTERM. `--check` says which Node runs it. A module asks for it with `bridged` (docs/apps.md
93
+ has the contract), and init writes the launcher only then.
94
+ - OpenCode (1.17.0 or later; read from 1.18.34's source): `init` finds `opencode` beside Claude Code and Codex and sets
95
+ it up with the same one approval, as its own connection ("OpenCode on Ann-MBP", a device sign-in with
96
+ `assistant=opencode` and a client of its own). Its `pendingyou` MCP server runs the bridge, in OpenCode's global
97
+ config (`~/.config/opencode/opencode.json` or `.jsonc`), edited in place by a small JSON-with-comments editor that
98
+ keeps every comment and byte around it; a file it can't read or edit safely is left alone, with the entry to add by
99
+ hand, and a new file gets OpenCode's `$schema`. A remote server at this Pending You that signs in by itself is moved
100
+ as Codex's is (asked at a terminal, kept with nobody to ask), keeping its `enabled` and `timeout`; one at another
101
+ Pending You is replaced only once the person says so. `--oauth` writes a remote server instead (`opencode mcp auth
102
+ pendingyou`). A `permission` that would make OpenCode ask (`"ask"`, or a catch-all rule) gets `"pendingyou_*":
103
+ "allow"`; a rule of the person's that names Pending You is kept. The skill goes in
104
+ `~/.config/opencode/skills/pendingyou/SKILL.md`, unless OpenCode already reads the one Claude Code's or Codex's init
105
+ saved (a second would be a duplicate).
106
+ - OpenCode's plugin, `~/.config/opencode/plugins/pendingyou.js`: one line re-exporting it from this version's private
107
+ copy, which OpenCode loads at startup. It runs the hooks' script for what it decides: each message carries the
108
+ hand-off (as a part the model reads and the TUI doesn't show), and a session's first message what a session start
109
+ says (the setup line while setup isn't finished: `handoff` takes `source` for that); the cards a session posts are
110
+ remembered (`posted`); a session that goes idle with something for the person left only in chat is asked once to put
111
+ it on a card, never for the turn its own prompt started, one the person stopped or a subagent's, with a toast saying
112
+ why; and when a card is answered, a listener per session (`listen --app opencode`, the plugin's child) hands the
113
+ plugin the wake mod's words, and the plugin starts the turn in that session (`client.session.promptAsync`), idle or
114
+ busy. The answer itself never leaves Pending You but through `get_request`. Its own prompts carry
115
+ `metadata.pendingyou`. `init` ends: restart OpenCode (`/exit`, then `opencode --continue`); it finishes setting up the
116
+ first time you write to it. `status` checks the bridge as OpenCode runs it and with a handshake; `uninstall` gives the
117
+ config back as it was. Not on Windows yet.
118
+ - Pi (pi.dev, the `pi` command; 0.99.0 or later, whose MCP client came in 0.99.0; read from 1.0.2's source): `init`
119
+ finds `pi` and sets it up with the others, on the same one approval, as its own connection ("Pi on Ann-MBP"). Its
120
+ `pendingyou` server runs the bridge, in `~/.pi/agent/mcp.json` (or `$PI_CODING_AGENT_DIR`'s), with `"exposure":
121
+ "direct"`, without which Pi's model reaches an MCP server's tools only from scripts. Only that member is written,
122
+ every other byte of the file as it was (a small JSON editor, `json.ts`, that checks each edit by reading it back); a
123
+ file that isn't JSON, or a `mcpServers` that isn't an object, is left alone, with the entry to add by hand. A server
124
+ at another Pending You is replaced only once the person says so; one Pi signs in to by itself (`pi mcp login`) is
125
+ moved on Yes (the default) at a terminal, or `--yes`; one with a command or an Authorization of its own is kept.
126
+ `--oauth` writes Pending You's address instead, signed in with `pi mcp login pendingyou`, as pi.dev's own client
127
+ (`oauth.clientRegistration: "cimd"`) from Pi 1.0.1. The skill goes in `~/.pi/agent/skills/pendingyou/SKILL.md`,
128
+ unless the same stub is already in `~/.agents/skills` (Codex's), which Pi reads too.
129
+ - Pi's extension, in place of hooks (Pi has none): `~/.config/pendingyou/pi/pendingyou.js`, listed in Pi's
130
+ `settings.json` `extensions` by a path that never changes (upgrading rewrites the file). It runs the hooks' script for
131
+ Pi: as a session starts (`pickup`, for Pi's next turn), with each message (`handoff`), after Pending You's card tools
132
+ (`posted --app pi`) and as a run settles (`stopcheck`: a run that left the person something only in chat goes on
133
+ once). When a card is answered, written back on or its fallback comes due, the listener the extension keeps per
134
+ session (`listen --app pi`, the same as OpenCode's) hands it the wake mod's words, and the extension starts the turn
135
+ (`pi.sendMessage`, at once when Pi is idle, after the run when it's busy), never with the answer in it. In Pi's
136
+ terminal the first session finishes setting up by itself: the session start's setup line starts the turn, once Pending
137
+ You's tools are there (Pi waits for MCP servers only before a typed prompt), and Pi's line waits 10 minutes after the
138
+ last, so two sessions reloaded together don't both set up. `init` ends: `/reload` in each Pi session that's open, or
139
+ start `pi`. `status` checks the bridge as Pi runs it and with a handshake; `uninstall` gives both files back as they
140
+ were (or takes away the ones `init` made). Not on Windows yet.
141
+ - `hold` inside Pi (which marks its processes `AI_AGENT=pi`, `PI_CODING_AGENT=true`) answers at once: Pi's shell waits
142
+ for a command to end, so a hold would stop the session for hours. The extension wakes the session anyway; without
143
+ it, `hold` says to check with `get_request` instead.
144
+
3
145
  ## 0.11.0
4
146
 
5
147
  - One step for the coding agents on a computer (Darin, 2026-10-04): `npx -y pendingyou@latest init` finds Claude Code
package/README.md CHANGED
@@ -1,29 +1,33 @@
1
1
  # pendingyou
2
2
 
3
3
  Pending You for your coding agents. Your agents ask you in [Pending You](https://www.pendingyou.com) when you're away;
4
- this command sets Claude Code and Codex up to ask there and to hear your answer the moment it's ready, even while they
5
- sit idle at the prompt.
4
+ this command sets Claude Code, Codex, OpenCode and Pi up to ask there and to hear your answer the moment it's ready,
5
+ even while they sit idle at the prompt.
6
6
 
7
7
  ```sh
8
8
  npx -y pendingyou@latest init
9
9
  ```
10
10
 
11
- Run it once on each computer, in a terminal. It finds the agents on the computer (Claude Code, Codex; `--app` names
12
- some), says what it found, and for each one:
11
+ Run it once on each computer, in a terminal. It finds the agents on the computer (Claude Code, Codex, OpenCode, Pi;
12
+ `--app` names some), says what it found, and for each one:
13
13
 
14
14
  - connects it to Pending You through **this computer's own sign-in**: its own connection ("Claude Code on Ann-MBP",
15
15
  "Codex on Ann-MBP" on your Assistants page), which its MCP server and its hooks share, so there's nothing to
16
16
  Authenticate in the agent and no second sign-in,
17
17
  - signs them all in with **one approval**: your browser opens on Pending You with one card naming each agent and this
18
18
  computer, and you press Allow once (over SSH: a code and a QR code for your phone, [below](#a-computer-with-no-browser)),
19
- - adds the Pending You MCP server for every project, signed in through a small helper script, and lets the agent use
20
- Pending You's tools without asking each time,
19
+ - adds the Pending You MCP server for every project, signed in through a small helper script (OpenCode and Pi:
20
+ through a small local bridge, [below](#opencode)), and lets the agent use Pending You's tools without asking each
21
+ time,
21
22
  - saves the Pending You skill (Claude Code: `~/.claude/skills/pendingyou/SKILL.md`; Codex:
22
- `~/.agents/skills/pendingyou/SKILL.md`),
23
+ `~/.agents/skills/pendingyou/SKILL.md`; OpenCode reads either, else `~/.config/opencode/skills/pendingyou/SKILL.md`;
24
+ Pi reads Codex's, else `~/.pi/agent/skills/pendingyou/SKILL.md`),
23
25
  - adds the hooks that hand it your answers: a new session picks up answers that arrived while nothing was running,
24
26
  your next message brings a waiting answer with it, and a check when it finishes a turn catches what it left you only
25
- in chat ([below](#nothing-left-only-in-chat)),
26
- - and wakes it when you answer: Claude Code 2.1.287 or later through the wake mod, Codex through `codex queue`.
27
+ in chat ([below](#nothing-left-only-in-chat)); and, for Claude Code, a card when it waits for your OK to run something
28
+ ([below](#when-claude-code-waits-for-your-ok)),
29
+ - and wakes it when you answer: Claude Code 2.1.287 or later through the wake mod, Codex through `codex queue`, OpenCode
30
+ through its plugin and Pi through its extension (which also do the hooks' work there).
27
31
 
28
32
  Then it says what to do next, and the one sentence to say if it's needed:
29
33
 
@@ -35,14 +39,16 @@ Finish setting up Pending You: it’s connected here already.
35
39
  ```
36
40
 
37
41
  The first session finishes setting up on its own: it checks in with Pending You, reports what's set up and sends you a
38
- test card. Claude Code 2.1.287 or later starts that turn by itself; otherwise (and in Codex, after you choose “Trust all
39
- and continue” for its hooks) the session start adds one line saying what's left, and you say the sentence.
42
+ test card. Claude Code 2.1.287 or later starts that turn by itself; otherwise (and in Codex, after you trust its hooks:
43
+ “Trust all and continue” in Codex CLI, Review hooks › Allow all in the Codex app) the session start adds one line with
44
+ the three calls that are left, and you say the sentence.
40
45
  `npx pendingyou status` says what's set up for each agent, what's missing with the command that fixes it, and how setup
41
46
  stands. `npx pendingyou uninstall` removes exactly what `init` added.
42
47
 
43
48
  Your other settings are kept as they are: `init` merges into Claude Code's `~/.claude/settings.json`, Codex's
44
49
  `~/.codex/config.toml` (its `[mcp_servers.pendingyou]` table and nothing else, every other line as it was) and
45
- `~/.codex/hooks.json`, and running it again changes nothing it doesn't need to.
50
+ `~/.codex/hooks.json`, OpenCode's config and Pi's `~/.pi/agent/mcp.json` and `settings.json` (Pending You's own entry
51
+ in each, every other byte as it was), and running it again changes nothing it doesn't need to.
46
52
 
47
53
  **A pendingyou server that's there already** is decided on before anyone signs in. One at another Pending You
48
54
  (staging, say) is named and replaced only once you say so (`--yes` replaces it without asking); keep it and that agent
@@ -88,14 +94,83 @@ default_tools_approval_mode = "approve"
88
94
  ```
89
95
 
90
96
  and four hooks in `~/.codex/hooks.json`: a session start, each message, Pending You's card tools (so the Stop check
91
- knows a turn posted, and so the thread is woken), and the Stop check. **Codex runs a hook only once you trust it**: start
92
- `codex` and choose “Trust all and continue” (or trust them in `/hooks`). `status` says which are trusted. A
93
- `pendingyou` server or hooks Codex's `/import` copied from Claude Code (without its sign-in) are replaced.
97
+ knows a turn posted, and so the thread is woken), and the Stop check. **Codex runs a hook only once you trust it**: in
98
+ Codex CLI, start `codex` and choose “Trust all and continue” (or trust them in `/hooks`); in the Codex app, click
99
+ Review hooks › Allow all (or Settings › Hooks › Trust all). `status` says which are trusted. A `pendingyou` server or
100
+ hooks Codex's `/import` copied from Claude Code (without its sign-in) are replaced.
101
+
102
+ **The Codex app** (on a Mac, in the ChatGPT app) shares all of this with Codex CLI: the same `~/.codex`, server, hooks
103
+ and trust. With no `codex` on PATH, `init` uses the app's own (inside `/Applications/ChatGPT.app` or
104
+ `~/Applications`), for the version and everything else it runs. Don't press Save on pendingyou in the app's MCP
105
+ settings: that writes the table again without `http_headers_helper` (and `default_tools_approval_mode`), so Codex stops
106
+ signing in through this computer's sign-in. `status` says so when it happens; `npx -y pendingyou@latest init` puts it
107
+ back.
94
108
 
95
109
  When you answer a card a Codex thread posted, a small listener `init`'s hooks start for that thread puts a message into
96
110
  it with `codex queue`: “Pending You: your person answered “…” (req_…). Call get_request …”, which reads your answer (the
97
111
  answer itself never goes in a command's arguments, where other programs could see it). Codex starts a turn at
98
- once when the thread is open and idle, after the current one when it's working, or when you next open it.
112
+ once when the thread is open and idle, after the current one when it's working, or when you next open it. In the Codex
113
+ app, a chat it has open is woken while the app runs (one it let go, idle 3 hours or behind 10 newer idle chats, when
114
+ you open it again).
115
+
116
+ ## OpenCode
117
+
118
+ OpenCode 1.17.0 or later. Its MCP clients can't take a header from a command, so `init` gives it a local server instead:
119
+ the stdio bridge, `pendingyou mcp`, which passes each message on to Pending You with OpenCode's own connection here
120
+ ("OpenCode on Ann-MBP"), so there's still one sign-in and nothing to Authenticate. In OpenCode's global config
121
+ (`~/.config/opencode/opencode.json` or `.jsonc`, edited in place with your comments and everything else kept):
122
+
123
+ ```json
124
+ "mcp": {
125
+ "pendingyou": {
126
+ "type": "local",
127
+ "command": ["/Users/ann/.config/pendingyou/bin/pendingyou-mcp", "--app", "opencode"]
128
+ }
129
+ }
130
+ ```
131
+
132
+ That path never changes: upgrading rewrites the small script there, never OpenCode's config. If your config's
133
+ `permission` makes OpenCode ask (`"ask"`), `init` adds `"pendingyou_*": "allow"` so a question you're away for doesn't
134
+ wait on a prompt; a rule of your own for Pending You's tools is kept. `--oauth` adds `{"type": "remote", "url":
135
+ "https://www.pendingyou.com/mcp"}` instead, which you sign in to with `opencode mcp auth pendingyou`.
136
+
137
+ And a plugin, `~/.config/opencode/plugins/pendingyou.js`, one line that loads this version's from its private copy. It
138
+ does in OpenCode what the hooks do elsewhere, through the same hooks' script: each message you send carries the
139
+ reminder and any answer waiting (hidden in OpenCode's view, read by the model), a session's first message also says
140
+ what setup has left, a session that left you a question in chat is asked once to put it on a card (with a toast saying
141
+ why), and when you answer a card a session posted, the plugin starts a turn in that session: “Pending You: your person
142
+ answered “…” (req_…). Call get_request …” (never the answer itself). That works while OpenCode is open, idle or busy.
143
+
144
+ Then restart OpenCode (`/exit`, then `opencode --continue`): it finishes setting up the first time you write to it.
145
+
146
+ ## Pi
147
+
148
+ Pi 0.99.0 or later (pi.dev, the `pi` command; its MCP client came in 0.99.0). Like OpenCode, Pi runs the bridge as a
149
+ local server, with its own connection here ("Pi on Ann-MBP"). In `~/.pi/agent/mcp.json` (or `$PI_CODING_AGENT_DIR`'s,
150
+ edited in place, everything else kept):
151
+
152
+ ```json
153
+ "pendingyou": {
154
+ "command": "/Users/ann/.config/pendingyou/bin/pendingyou-mcp",
155
+ "args": ["--app", "pi"],
156
+ "exposure": "direct",
157
+ "description": "Ask your person through Pending You when a decision, fact or step is theirs, and hear their answers."
158
+ }
159
+ ```
160
+
161
+ `direct` puts Pending You's tools in front of Pi's model (Pi's default leaves an MCP server's tools to scripts). Pi asks
162
+ nobody before it runs a tool, so there's nothing to allow. `--oauth` adds Pending You's address instead, which you sign
163
+ in to with `pi mcp login pendingyou` (from Pi 1.0.1 as pi.dev's own client).
164
+
165
+ Pi has no hooks, so `init` gives it an extension: `~/.config/pendingyou/pi/pendingyou.js`, in the `extensions` of Pi's
166
+ `~/.pi/agent/settings.json`, by a path that never changes (upgrading rewrites the file, never your settings). It runs
167
+ the same hooks' script as a session starts, with each message, after Pending You's card tools and as a run ends (a run
168
+ that leaves you something only in chat goes on once), and when you answer a card a session posted it starts a turn in
169
+ that session, idle or busy: “Pending You: your person answered “…” (req_…). Call get_request …” (never the answer
170
+ itself). In Pi's terminal the first session finishes setting up by itself, once Pending You's tools are there.
171
+
172
+ Then run `/reload` in each Pi session that's open, or start `pi`. Pi's shell waits for a command to end, so in Pi
173
+ `npx pendingyou hold` answers at once instead of waiting.
99
174
 
100
175
  ## Nothing left only in chat
101
176
 
@@ -130,11 +205,34 @@ costs a request: this folder's open cards, given up after 1.5 seconds.
130
205
 
131
206
  Codex gets the same two (0.11.0). Its Stop check reads Codex's final message, and whether the turn posted, updated or
132
207
  replied on a card comes from Codex's own hook for Pending You's tools in that turn; it asks once a turn at most, for an
133
- action card titled “Confirm in Codex on <computer>: <step>” when it asked you to type something there.
208
+ action card titled “Confirm in Codex on <computer>: <step>” when it asked you to type something there. OpenCode's plugin
209
+ and Pi's extension (0.12.0) do the same in theirs.
134
210
 
135
211
  **On 0.2.0 with another origin?** Its hooks blocked every message (`There's no "--origin" command`). Run
136
212
  `npx pendingyou@latest init --origin <url>` again to repair them; see [CHANGELOG.md](CHANGELOG.md).
137
213
 
214
+ ## When Claude Code waits for your OK
215
+
216
+ Claude Code stops to ask before it runs something it isn't allowed to run by itself (`rm -rf dist/`, a push, a file
217
+ outside the folder), and waits, however long you're away. Since 0.13.0, when you don't answer within 10 seconds, a card
218
+ says so: “Claude Code is waiting for your OK: rm -rf dist/”, with the folder and the computer, and one step: answer it
219
+ in Claude Code there (with the session's link while Remote Control is on, where you can answer it from your phone).
220
+ It's asked in both places, so you get a push only once it has waited your hand-off time (3 minutes unless you changed
221
+ it). It goes away by itself once you answer in Claude Code: allowed, denied, or the session closed. One card per
222
+ session: a newer prompt changes it.
223
+
224
+ The hook (Claude Code's PermissionRequest) never answers for you: it prints nothing, so the prompt shows exactly as
225
+ before, and it's done in about a tenth of a second. A small process in the background posts, changes and withdraws the
226
+ card through Pending You, with Claude Code's own connection here. Nothing secret goes on the card: values after
227
+ anything named like a key, token or password, a URL's user and password and query values, and anything that looks like
228
+ a key are blanked (`curl -H "Authorization: …" https://api.example.com/items?token=…`), and a command that can't be
229
+ shown safely (several lines, inline code like `node -e`, one that takes a secret) shows only as “Bash: node …”. No card
230
+ for `claude -p` or the Agent SDK, where nobody sees a prompt.
231
+
232
+ It needs Claude Code 2.1.119 or later, signing in through this computer's sign-in (not `--oauth`), and isn't on
233
+ Windows yet. Don't want it? `npx pendingyou init --no-permission-cards` (later runs keep it off; `--permission-cards`
234
+ turns it back on). `npx pendingyou status` says which.
235
+
138
236
  ## How Claude Code hears your answer
139
237
 
140
238
  **Claude Code 2.1.287 or later: the wake mod.** There's nothing to run. In each session the mod learns the cards
@@ -220,7 +318,7 @@ more, then skipped. `--once` stops after the first.
220
318
 
221
319
  | Command | What it does |
222
320
  | --- | --- |
223
- | `init` | Set up every agent on this computer (`--app`, `--oauth`, `--no-login`, `--no-browser`, `--device`, `--browser`, `--yes`, `--name`) |
321
+ | `init` | Set up every agent on this computer (`--app`, `--oauth`, `--no-login`, `--no-browser`, `--device`, `--browser`, `--yes`, `--name`, `--no-permission-cards`) |
224
322
  | `hold <requestId>` | Wait for the answer, print it, exit (`--timeout 4h`); the wake mod answers it itself |
225
323
  | `status` | What's set up for each agent; exits 0 when each hears answers right away (`--app`) |
226
324
  | `login` / `logout` | Sign an agent in again, or out (`--app`, `--device`, `--browser`, `--force`, `--name`, `--all`) |
@@ -228,11 +326,11 @@ more, then skipped. `--once` stops after the first.
228
326
  | `pickup` / `handoff` | The session-start and next-message hooks (`--app`) |
229
327
  | `stopcheck` | The Stop hook: items for you left only in chat (`--app`) |
230
328
  | `uninstall` | Remove what `init` added, and sign out (`--app`) |
231
- | `mcp --app <app>` | Not yet: a stdio MCP server for an agent that can't run a headers helper |
329
+ | `mcp --app <app>` | The stdio bridge: a local MCP server for an agent that can't run a headers helper (OpenCode, Pi), which passes each message on to Pending You with that agent's own sign-in here (`--check`) |
232
330
 
233
331
  Every command takes `--origin` (default `https://www.pendingyou.com`, or `$PENDINGYOU_ORIGIN`). `--app` takes
234
- `claude-code` or `codex` (several with commas where a command sets up or reports several); with none, a command that
235
- acts for one agent acts for Claude Code. Options can come before or after the command.
332
+ `claude-code`, `codex`, `opencode` or `pi` (several with commas where a command sets up or reports several); with none,
333
+ a command that acts for one agent acts for Claude Code. Options can come before or after the command.
236
334
 
237
335
  ## What it can see, and where it keeps things
238
336
 
@@ -247,11 +345,18 @@ Pending You (Claude Code says which server it's for; Codex's is the one `init` w
247
345
  sign-in each agent's connection used, so signing in again keeps it.
248
346
 
249
347
  `state.json` beside them remembers which answers were already handed over (ids only) and how many session starts
250
- reminded an unfinished setup; `claude-code.json` and `codex.json` what `init` added; `codex-threads.json` which cards
251
- each Codex thread waits on (ids, names and titles: never an answer); and `cli/` the hooks' copy of the command line.
252
- `init`, `status` and the hooks read `~/.claude.json` (and a folder's `.mcp.json`, and `~/.claude/settings.json`'s
253
- enabled plugins) and `~/.codex/config.toml` only to see whether the agent has a `pendingyou` MCP server and where it
254
- points. Nothing is sent anywhere but Pending You.
348
+ reminded an unfinished setup, and when; `permission-cards/` each Claude Code session's prompts still waiting on your OK
349
+ and its card (ids, hashes and the card's redacted words: never what the tool was given), gone once nothing waits;
350
+ `claude-code.json`, `codex.json`, `opencode.json` and `pi.json` what `init`
351
+ added; `codex-threads.json`, `opencode-threads.json` and `pi-threads.json` which cards each Codex thread or OpenCode or
352
+ Pi session waits on (ids, names and titles: never an answer); `pi/` Pi's extension; and `cli/` the hooks' copy of the
353
+ command line. `init`, `status` and the hooks read `~/.claude.json` (and a folder's `.mcp.json`, and
354
+ `~/.claude/settings.json`'s enabled plugins), `~/.codex/config.toml`, OpenCode's config and Pi's `mcp.json` only to see
355
+ whether the agent has a `pendingyou` MCP server and where it points. For a permission prompt's card, the worker reads
356
+ the end of the Claude Code session's transcript only for the name the session gave Pending You, and `git remote
357
+ get-url origin` in its folder (sent without any user name or password in it). Nothing is sent anywhere but Pending
358
+ You. OpenCode's plugin reads its session's messages only when it goes idle, for the check that
359
+ nothing was left in chat, and hands the hooks' script just that turn's words and tool names.
255
360
 
256
361
  The wake mod runs inside Claude Code and reaches Pending You only through the session's own connection, with
257
362
  `get_request` on the session's own cards (and, in `init`'s copy, `whoami` to see whether setup is finished). It keeps
package/dist/api.js CHANGED
@@ -131,19 +131,21 @@ async function hookToken(io, origin, force, signal, app) {
131
131
  throw new Unavailable('The sign-in is being refreshed in the background.');
132
132
  }
133
133
  /**
134
- * The token `pendingyou mcp-headers` hands an app's MCP server (remote.ts). The app runs the helper each time it
135
- * connects and gives it 10 seconds, so, as for a hook, a refresh is never made in this process: within 5 minutes of
136
- * expiry it starts `pendingyou refresh` detached and waits for its token, up to `waitMs`, then uses the old one while it
137
- * still works. A token handed over has at least 10 seconds left; the app runs the helper again on a 401.
134
+ * The token `pendingyou mcp-headers` hands an app's MCP server (remote.ts), and the stdio bridge sends with each
135
+ * message (bridge.ts). The app runs the helper each time it connects and gives it 10 seconds, and the bridge may be
136
+ * stopped at any moment, so, as for a hook, a refresh is never made in this process: within 5 minutes of expiry it
137
+ * starts `pendingyou refresh` detached and waits for its token, up to `waitMs`, then uses the old one while it still
138
+ * works. A token handed over has at least 10 seconds left; the app runs the helper again on a 401. `force` (the bridge,
139
+ * after a 401) refreshes whatever the expiry says, and hands over only the new token.
138
140
  */
139
- export async function helperToken(io, origin, waitMs, app = DEFAULT_APP) {
141
+ export async function helperToken(io, origin, waitMs, app = DEFAULT_APP, force = false) {
140
142
  const stored = await readCredential(io, origin, app);
141
143
  if (!stored)
142
144
  throw new SignInNeeded(false);
143
- if (stored.expiresAt - io.now() > SOON_MS)
145
+ if (!force && stored.expiresAt - io.now() > SOON_MS)
144
146
  return stored.accessToken;
145
147
  if (!(await isLocked(credentialsPath(io))))
146
- io.background(refreshArgs(origin, app));
148
+ io.background(refreshArgs(origin, app, force));
147
149
  const until = io.now() + waitMs;
148
150
  while (io.now() < until) {
149
151
  await io.sleep(100);
@@ -153,7 +155,7 @@ export async function helperToken(io, origin, waitMs, app = DEFAULT_APP) {
153
155
  if (current.refreshToken !== stored.refreshToken)
154
156
  return current.accessToken;
155
157
  }
156
- if (stored.expiresAt - io.now() > USABLE_MS)
158
+ if (!force && stored.expiresAt - io.now() > USABLE_MS)
157
159
  return stored.accessToken;
158
160
  throw new Unavailable('Pending You couldn’t be reached to refresh the sign-in.');
159
161
  }
@@ -11,7 +11,7 @@
11
11
  // to ask it's kept, and init says how to switch. Kept, Claude Code signs in by itself and the hooks get a sign-in
12
12
  // that only hears; kept beside the plugin, no skill is saved either: the plugin carries it.
13
13
  // - one at another Pending You: replaced once the person says so (or `--yes`), or else Claude Code is left alone.
14
- import { addCommand, claudeState, helperServer, installClaude, mcpFromClaude, mcpFromFile, PLUGIN_ORIGINS, pluginFor, readManifest, removeCommand, sameUrl, uninstallClaude, } from "../claude.js";
14
+ import { addCommand, claudeState, helperServer, installClaude, mcpFromClaude, mcpFromFile, PLUGIN_ORIGINS, permissionBlocker, pluginFor, readManifest, removeCommand, sameUrl, uninstallClaude, } from "../claude.js";
15
15
  import { connectionMachine, readCredential } from "../credentials.js";
16
16
  import { differs, serverForFolder, siteOf } from "../environment.js";
17
17
  import { originArgs } from "../hooks.js";
@@ -185,6 +185,7 @@ export const claudeCode = {
185
185
  hookLine: ictx.hookLine,
186
186
  report: ictx.report,
187
187
  progress: ictx.progress,
188
+ ...(ctx.permissionCards === undefined ? {} : { permissionCards: ctx.permissionCards }),
188
189
  }),
189
190
  next: (ictx) => {
190
191
  if (mode === 'oauth') {
@@ -223,6 +224,23 @@ export const claudeCode = {
223
224
  return claudeStatus(io, ctx);
224
225
  },
225
226
  };
227
+ /**
228
+ * status's line for the permission-prompt hooks (0.13.0): on; off, as the person chose; or why Claude Code here can't
229
+ * have them. Never a reason status isn't Ready.
230
+ */
231
+ function permissionLine(io, claude, manifest, helper, flag) {
232
+ const label = 'Permission prompts:';
233
+ if (claude.permission)
234
+ return ` ok ${label} a card when Claude Code waits for your OK`;
235
+ if (manifest?.permissionCards === false)
236
+ return ` ${label} off, as you chose; for a card when Claude Code waits for your OK, run npx pendingyou init --permission-cards${flag}`;
237
+ const blocker = claude.claude
238
+ ? permissionBlocker(io, claude.claude, helper ? 'helper' : 'oauth')
239
+ : null;
240
+ return blocker
241
+ ? ` ${label} no card when Claude Code waits for your OK: ${blocker}`
242
+ : ` missing ${label} no card when Claude Code waits for your OK; run npx pendingyou@latest init${flag}`;
243
+ }
226
244
  /** Claude Code's section of `pendingyou status`: what's set up and what's missing, each with the command that fixes it. */
227
245
  async function claudeStatus(io, ctx) {
228
246
  const { origin, signIn } = ctx;
@@ -338,7 +356,7 @@ async function claudeStatus(io, ctx) {
338
356
  ? [
339
357
  ` ok Plugin: ${plugin} is enabled${pluginServer ? '' : mcp !== false && mcp !== 'unknown' ? '; Claude Code uses the server added by hand instead of its own' : ''}`,
340
358
  ]
341
- : []), ` ${mark(skillOk)} Skill: ${skillText}`, ` ${mark(claude.hooks && hooksRun)} Hooks: ${claude.hooks ? `session-start pickup and next-message hand-off; ${formText}` : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(claude.stopcheck)} Stop check: ${claude.stopcheck ? 'asks Claude Code to post what it leaves you in chat' : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(modOk)} Wake mod: ${modText}`);
359
+ : []), ` ${mark(skillOk)} Skill: ${skillText}`, ` ${mark(claude.hooks && hooksRun)} Hooks: ${claude.hooks ? `session-start pickup and next-message hand-off; ${formText}` : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(claude.stopcheck)} Stop check: ${claude.stopcheck ? 'asks Claude Code to post what it leaves you in chat' : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(modOk)} Wake mod: ${modText}`, permissionLine(io, claude, manifest, helper, flag));
342
360
  // How setup stands on Pending You (from servers that say): never a reason it isn't Ready.
343
361
  const setup = setupStatus(NAME, signIn.state === 'ok' ? setupOf(signIn.for) : null);
344
362
  if (setup)
@@ -0,0 +1,44 @@
1
+ // The Codex app for macOS (0.13.0; research 2026-10-04): "ChatGPT.app" (bundle id com.openai.codex; 26.928 carries codex
2
+ // 0.159.2) has its own codex command line inside it, and shares ~/.codex with Codex CLI: its MCP servers (the headers
3
+ // helper too), ~/.agents/skills, ~/.codex/hooks.json and the hooks' trust. Its hooks run with a PATH that ends in its own
4
+ // codex, and its private Codex server reads ~/.codex/queue_1.sqlite every 10 seconds, so `codex queue --thread` wakes a
5
+ // chat the app has loaded while it's open (an idle chat is let go after 3 hours, or behind 10 newer idle ones; then the
6
+ // message waits until the chat is opened again). Where `codex` isn't on PATH, init, status and the listener
7
+ // (codex-wake.ts) run the app's own instead: these paths, in /Applications or ~/Applications, under either name.
8
+ import { stat } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ /** The app's names: the ChatGPT desktop app that carries Codex, and Codex's own, should it ship as that. */
11
+ const APP_NAMES = ['ChatGPT.app', 'Codex.app'];
12
+ /**
13
+ * Where a Codex app keeps its own codex: `Contents/Resources/codex-cli/bin/codex`, and the CodexCLI.app inside it that
14
+ * the app's own code looks for (`codex-cli/CodexCLI.app/Contents/MacOS/codex`), in /Applications, then ~/Applications.
15
+ * `system` is /Applications unless PENDINGYOU_APPLICATIONS_DIR says otherwise (tests: they never look in the real one).
16
+ */
17
+ export function appCodexPaths(home, system = '/Applications') {
18
+ return [system, join(home, 'Applications')].flatMap((root) => APP_NAMES.flatMap((name) => {
19
+ const cli = join(root, name, 'Contents', 'Resources', 'codex-cli');
20
+ return [join(cli, 'bin', 'codex'), join(cli, 'CodexCLI.app', 'Contents', 'MacOS', 'codex')];
21
+ }));
22
+ }
23
+ /** `codex --version`'s version ("codex-cli 0.159.2"), or "installed" when it can't be read. */
24
+ const versionOf = (stdout) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? 'installed';
25
+ const executable = (path) => stat(path).then((info) => info.isFile() && (info.mode & 0o111) !== 0, () => false);
26
+ /**
27
+ * The codex to run: `codex` on PATH; else, on a Mac, the Codex app's own (the first of `paths` there that answers
28
+ * `--version`); null when there's neither.
29
+ */
30
+ export async function findCodex(io, paths = appCodexPaths(io.home, io.env.PENDINGYOU_APPLICATIONS_DIR || undefined)) {
31
+ const onPath = await io.run('codex', ['--version'], 20_000);
32
+ if (onPath.code === 0)
33
+ return { command: 'codex', version: versionOf(onPath.stdout), app: false };
34
+ if (io.platform !== 'darwin')
35
+ return null;
36
+ for (const path of paths) {
37
+ if (!(await executable(path)))
38
+ continue;
39
+ const result = await io.run(path, ['--version'], 20_000);
40
+ if (result.code === 0)
41
+ return { command: path, version: versionOf(result.stdout), app: true };
42
+ }
43
+ return null;
44
+ }