pendingyou 0.11.0 → 0.12.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,72 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 (2026-10-04)
4
+
5
+ - The stdio bridge, `pendingyou mcp --app <app>`: a local MCP server for an agent that can't give its MCP client a
6
+ header from a command (Claude Code's headersHelper, Codex's http_headers_helper), so its `pendingyou` server signs in
7
+ through this computer's own sign-in all the same, with no second sign-in. The agent runs it by a path that never
8
+ changes, `~/.config/pendingyou/bin/pendingyou-mcp --app <app>` (`--origin` off production), a small script that finds
9
+ Node as the headers helper does and runs this version's private copy, so the agent's config never changes on an
10
+ upgrade. It passes each JSON-RPC line from stdin on to `<origin>/mcp` with the agent's own connection, and each
11
+ 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
12
+ plain lines on stderr. It keeps the server's session id, sends the protocol version the handshake agreed (and a
13
+ 2026-07-28 request's own headers), starts a session again when the server forgot it, refreshes the sign-in once after
14
+ a 401 (in the background, as the helper does) and then answers with an error saying to run `npx -y
15
+ pendingyou@latest init`; Pending You unreachable, a problem on its side, a redirect (never followed: the token goes
16
+ nowhere else) or a cut-off answer each get a plain JSON-RPC error. It waits for what's under way when stdin closes,
17
+ and stops at once on SIGTERM. `--check` says which Node runs it. A module asks for it with `bridged` (docs/apps.md
18
+ has the contract), and init writes the launcher only then.
19
+ - OpenCode (1.17.0 or later; read from 1.18.34's source): `init` finds `opencode` beside Claude Code and Codex and sets
20
+ it up with the same one approval, as its own connection ("OpenCode on Ann-MBP", a device sign-in with
21
+ `assistant=opencode` and a client of its own). Its `pendingyou` MCP server runs the bridge, in OpenCode's global
22
+ config (`~/.config/opencode/opencode.json` or `.jsonc`), edited in place by a small JSON-with-comments editor that
23
+ 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
24
+ hand, and a new file gets OpenCode's `$schema`. A remote server at this Pending You that signs in by itself is moved
25
+ as Codex's is (asked at a terminal, kept with nobody to ask), keeping its `enabled` and `timeout`; one at another
26
+ Pending You is replaced only once the person says so. `--oauth` writes a remote server instead (`opencode mcp auth
27
+ pendingyou`). A `permission` that would make OpenCode ask (`"ask"`, or a catch-all rule) gets `"pendingyou_*":
28
+ "allow"`; a rule of the person's that names Pending You is kept. The skill goes in
29
+ `~/.config/opencode/skills/pendingyou/SKILL.md`, unless OpenCode already reads the one Claude Code's or Codex's init
30
+ saved (a second would be a duplicate).
31
+ - OpenCode's plugin, `~/.config/opencode/plugins/pendingyou.js`: one line re-exporting it from this version's private
32
+ copy, which OpenCode loads at startup. It runs the hooks' script for what it decides: each message carries the
33
+ hand-off (as a part the model reads and the TUI doesn't show), and a session's first message what a session start
34
+ says (the setup line while setup isn't finished: `handoff` takes `source` for that); the cards a session posts are
35
+ remembered (`posted`); a session that goes idle with something for the person left only in chat is asked once to put
36
+ it on a card, never for the turn its own prompt started, one the person stopped or a subagent's, with a toast saying
37
+ why; and when a card is answered, a listener per session (`listen --app opencode`, the plugin's child) hands the
38
+ plugin the wake mod's words, and the plugin starts the turn in that session (`client.session.promptAsync`), idle or
39
+ busy. The answer itself never leaves Pending You but through `get_request`. Its own prompts carry
40
+ `metadata.pendingyou`. `init` ends: restart OpenCode (`/exit`, then `opencode --continue`); it finishes setting up the
41
+ first time you write to it. `status` checks the bridge as OpenCode runs it and with a handshake; `uninstall` gives the
42
+ config back as it was. Not on Windows yet.
43
+ - 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`
44
+ finds `pi` and sets it up with the others, on the same one approval, as its own connection ("Pi on Ann-MBP"). Its
45
+ `pendingyou` server runs the bridge, in `~/.pi/agent/mcp.json` (or `$PI_CODING_AGENT_DIR`'s), with `"exposure":
46
+ "direct"`, without which Pi's model reaches an MCP server's tools only from scripts. Only that member is written,
47
+ every other byte of the file as it was (a small JSON editor, `json.ts`, that checks each edit by reading it back); a
48
+ 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
49
+ at another Pending You is replaced only once the person says so; one Pi signs in to by itself (`pi mcp login`) is
50
+ moved on Yes (the default) at a terminal, or `--yes`; one with a command or an Authorization of its own is kept.
51
+ `--oauth` writes Pending You's address instead, signed in with `pi mcp login pendingyou`, as pi.dev's own client
52
+ (`oauth.clientRegistration: "cimd"`) from Pi 1.0.1. The skill goes in `~/.pi/agent/skills/pendingyou/SKILL.md`,
53
+ unless the same stub is already in `~/.agents/skills` (Codex's), which Pi reads too.
54
+ - Pi's extension, in place of hooks (Pi has none): `~/.config/pendingyou/pi/pendingyou.js`, listed in Pi's
55
+ `settings.json` `extensions` by a path that never changes (upgrading rewrites the file). It runs the hooks' script for
56
+ Pi: as a session starts (`pickup`, for Pi's next turn), with each message (`handoff`), after Pending You's card tools
57
+ (`posted --app pi`) and as a run settles (`stopcheck`: a run that left the person something only in chat goes on
58
+ once). When a card is answered, written back on or its fallback comes due, the listener the extension keeps per
59
+ session (`listen --app pi`, the same as OpenCode's) hands it the wake mod's words, and the extension starts the turn
60
+ (`pi.sendMessage`, at once when Pi is idle, after the run when it's busy), never with the answer in it. In Pi's
61
+ terminal the first session finishes setting up by itself: the session start's setup line starts the turn, once Pending
62
+ You's tools are there (Pi waits for MCP servers only before a typed prompt), and Pi's line waits 10 minutes after the
63
+ last, so two sessions reloaded together don't both set up. `init` ends: `/reload` in each Pi session that's open, or
64
+ start `pi`. `status` checks the bridge as Pi runs it and with a handshake; `uninstall` gives both files back as they
65
+ were (or takes away the ones `init` made). Not on Windows yet.
66
+ - `hold` inside Pi (which marks its processes `AI_AGENT=pi`, `PI_CODING_AGENT=true`) answers at once: Pi's shell waits
67
+ for a command to end, so a hold would stop the session for hours. The extension wakes the session anyway; without
68
+ it, `hold` says to check with `get_request` instead.
69
+
3
70
  ## 0.11.0
4
71
 
5
72
  - 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,32 @@
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
27
  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`.
28
+ - and wakes it when you answer: Claude Code 2.1.287 or later through the wake mod, Codex through `codex queue`, OpenCode
29
+ through its plugin and Pi through its extension (which also do the hooks' work there).
27
30
 
28
31
  Then it says what to do next, and the one sentence to say if it's needed:
29
32
 
@@ -42,7 +45,8 @@ stands. `npx pendingyou uninstall` removes exactly what `init` added.
42
45
 
43
46
  Your other settings are kept as they are: `init` merges into Claude Code's `~/.claude/settings.json`, Codex's
44
47
  `~/.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.
48
+ `~/.codex/hooks.json`, OpenCode's config and Pi's `~/.pi/agent/mcp.json` and `settings.json` (Pending You's own entry
49
+ in each, every other byte as it was), and running it again changes nothing it doesn't need to.
46
50
 
47
51
  **A pendingyou server that's there already** is decided on before anyone signs in. One at another Pending You
48
52
  (staging, say) is named and replaced only once you say so (`--yes` replaces it without asking); keep it and that agent
@@ -97,6 +101,65 @@ it with `codex queue`: “Pending You: your person answered “…” (req_…).
97
101
  answer itself never goes in a command's arguments, where other programs could see it). Codex starts a turn at
98
102
  once when the thread is open and idle, after the current one when it's working, or when you next open it.
99
103
 
104
+ ## OpenCode
105
+
106
+ 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:
107
+ the stdio bridge, `pendingyou mcp`, which passes each message on to Pending You with OpenCode's own connection here
108
+ ("OpenCode on Ann-MBP"), so there's still one sign-in and nothing to Authenticate. In OpenCode's global config
109
+ (`~/.config/opencode/opencode.json` or `.jsonc`, edited in place with your comments and everything else kept):
110
+
111
+ ```json
112
+ "mcp": {
113
+ "pendingyou": {
114
+ "type": "local",
115
+ "command": ["/Users/ann/.config/pendingyou/bin/pendingyou-mcp", "--app", "opencode"]
116
+ }
117
+ }
118
+ ```
119
+
120
+ That path never changes: upgrading rewrites the small script there, never OpenCode's config. If your config's
121
+ `permission` makes OpenCode ask (`"ask"`), `init` adds `"pendingyou_*": "allow"` so a question you're away for doesn't
122
+ wait on a prompt; a rule of your own for Pending You's tools is kept. `--oauth` adds `{"type": "remote", "url":
123
+ "https://www.pendingyou.com/mcp"}` instead, which you sign in to with `opencode mcp auth pendingyou`.
124
+
125
+ And a plugin, `~/.config/opencode/plugins/pendingyou.js`, one line that loads this version's from its private copy. It
126
+ does in OpenCode what the hooks do elsewhere, through the same hooks' script: each message you send carries the
127
+ reminder and any answer waiting (hidden in OpenCode's view, read by the model), a session's first message also says
128
+ 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
129
+ why), and when you answer a card a session posted, the plugin starts a turn in that session: “Pending You: your person
130
+ answered “…” (req_…). Call get_request …” (never the answer itself). That works while OpenCode is open, idle or busy.
131
+
132
+ Then restart OpenCode (`/exit`, then `opencode --continue`): it finishes setting up the first time you write to it.
133
+
134
+ ## Pi
135
+
136
+ 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
137
+ local server, with its own connection here ("Pi on Ann-MBP"). In `~/.pi/agent/mcp.json` (or `$PI_CODING_AGENT_DIR`'s,
138
+ edited in place, everything else kept):
139
+
140
+ ```json
141
+ "pendingyou": {
142
+ "command": "/Users/ann/.config/pendingyou/bin/pendingyou-mcp",
143
+ "args": ["--app", "pi"],
144
+ "exposure": "direct",
145
+ "description": "Ask your person through Pending You when a decision, fact or step is theirs, and hear their answers."
146
+ }
147
+ ```
148
+
149
+ `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
150
+ nobody before it runs a tool, so there's nothing to allow. `--oauth` adds Pending You's address instead, which you sign
151
+ in to with `pi mcp login pendingyou` (from Pi 1.0.1 as pi.dev's own client).
152
+
153
+ Pi has no hooks, so `init` gives it an extension: `~/.config/pendingyou/pi/pendingyou.js`, in the `extensions` of Pi's
154
+ `~/.pi/agent/settings.json`, by a path that never changes (upgrading rewrites the file, never your settings). It runs
155
+ the same hooks' script as a session starts, with each message, after Pending You's card tools and as a run ends (a run
156
+ that leaves you something only in chat goes on once), and when you answer a card a session posted it starts a turn in
157
+ that session, idle or busy: “Pending You: your person answered “…” (req_…). Call get_request …” (never the answer
158
+ itself). In Pi's terminal the first session finishes setting up by itself, once Pending You's tools are there.
159
+
160
+ 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
161
+ `npx pendingyou hold` answers at once instead of waiting.
162
+
100
163
  ## Nothing left only in chat
101
164
 
102
165
  Deep into a long conversation, Claude Code tends to end a turn with a question for you, a step only you can do, or a
@@ -130,7 +193,8 @@ costs a request: this folder's open cards, given up after 1.5 seconds.
130
193
 
131
194
  Codex gets the same two (0.11.0). Its Stop check reads Codex's final message, and whether the turn posted, updated or
132
195
  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.
196
+ action card titled “Confirm in Codex on <computer>: <step>” when it asked you to type something there. OpenCode's plugin
197
+ and Pi's extension (0.12.0) do the same in theirs.
134
198
 
135
199
  **On 0.2.0 with another origin?** Its hooks blocked every message (`There's no "--origin" command`). Run
136
200
  `npx pendingyou@latest init --origin <url>` again to repair them; see [CHANGELOG.md](CHANGELOG.md).
@@ -228,11 +292,11 @@ more, then skipped. `--once` stops after the first.
228
292
  | `pickup` / `handoff` | The session-start and next-message hooks (`--app`) |
229
293
  | `stopcheck` | The Stop hook: items for you left only in chat (`--app`) |
230
294
  | `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 |
295
+ | `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
296
 
233
297
  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.
298
+ `claude-code`, `codex`, `opencode` or `pi` (several with commas where a command sets up or reports several); with none,
299
+ a command that acts for one agent acts for Claude Code. Options can come before or after the command.
236
300
 
237
301
  ## What it can see, and where it keeps things
238
302
 
@@ -247,11 +311,13 @@ Pending You (Claude Code says which server it's for; Codex's is the one `init` w
247
311
  sign-in each agent's connection used, so signing in again keeps it.
248
312
 
249
313
  `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.
314
+ reminded an unfinished setup, and when; `claude-code.json`, `codex.json`, `opencode.json` and `pi.json` what `init`
315
+ added; `codex-threads.json`, `opencode-threads.json` and `pi-threads.json` which cards each Codex thread or OpenCode or
316
+ Pi session waits on (ids, names and titles: never an answer); `pi/` Pi's extension; and `cli/` the hooks' copy of the
317
+ command line. `init`, `status` and the hooks read `~/.claude.json` (and a folder's `.mcp.json`, and
318
+ `~/.claude/settings.json`'s enabled plugins), `~/.codex/config.toml`, OpenCode's config and Pi's `mcp.json` only to see
319
+ whether the agent has a `pendingyou` MCP server and where it points. Nothing is sent anywhere but Pending You. OpenCode's plugin reads its session's messages only when it goes idle, for the check that
320
+ nothing was left in chat, and hands the hooks' script just that turn's words and tool names.
255
321
 
256
322
  The wake mod runs inside Claude Code and reaches Pending You only through the session's own connection, with
257
323
  `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
  }