pendingyou 0.9.0 → 0.10.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,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+
5
+ - The wake mod: Claude Code 2.1.287 or later is woken when you answer, with nothing waiting in the background. A
6
+ session heard your answer only while `npx pendingyou hold` ran, and a hold stops: after its own 4 hours, or sooner
7
+ when Claude Code stops a background command. Then it said "Don't start another hold now", and an answer that came
8
+ later waited until you next typed in that session; two sessions missed answers that way overnight (2026-10-04).
9
+ `init` now adds the Pending You plugin's mod, which this package carries in `mod/`, to `CLAUDE_CODE_PLUGIN_DIRS`
10
+ (`env` in `~/.claude/settings.json`), from the hooks' own copy in `~/.config/pendingyou/cli/0.10.0/`, keeping any
11
+ other folders there and replacing an older version's copy. Each session then learns the cards it posts, checks them
12
+ through its own Pending You connection (every 20 seconds while a card changed in the last half hour, then every
13
+ minute), and starts one turn when you answer, write back or a fallback comes due: “Pending You: your person
14
+ answered … Call get_request for it …, act on their words, then ack_answer.” Under the prompt it shows
15
+ `⚠ pendingyou-wake: 2 waiting on you`. It answers `npx pendingyou hold` itself, so an agent that still runs one waits on
16
+ nothing. With the Pending You plugin installed too (it carries the same mod, plugin 2.24), only one copy acts. On an
17
+ older Claude Code, or none, `init` skips it without a word. `uninstall` takes it out; `status` says whether it's
18
+ loaded, from where, and what to do when it isn't.
19
+ - Without the mod, a hold that stops without an answer (its own `--timeout`, or Claude Code stopping it) says to start
20
+ it again in the background, with the exact command, `--origin` and `--timeout` included when they aren't the
21
+ defaults.
22
+ - The next-message hook looks for this folder's answers with every message, not only while a hold had left an entry
23
+ behind: one request, as before, given up after 2 seconds, inside the hook's 3-second deadline. `state.json` no
24
+ longer keeps holds' waits.
25
+ - A computer with no browser (a devbox or server you SSH into): one code approved on your phone sets Claude Code up.
26
+ `/mcp`'s Authenticate opened a browser on that machine, which nobody could reach, and `login --device` only signed
27
+ this command in. Now `init` there (an SSH session, or Linux with no display; `--device` forces it, `--browser` the
28
+ browser) signs the machine in by code as a Claude Code connection of its own ("Claude Code on devbox" on your
29
+ Assistants page, named by `--name`, else Tailscale's name for it, else its hostname; `login --name` with a new name
30
+ renames the connection with one more approval, which `login` suggests for a cloud's own name like `ip-10-42-1-252`;
31
+ QR code of the link at a terminal), then adds the MCP server with a `headersHelper` instead of OAuth
32
+ (`claude mcp add-json`), the wake mod in `env.CLAUDE_CODE_PLUGIN_DIRS` with `mcp__pendingyou` allowed, the skill and
33
+ the hooks. The helper, `~/.config/pendingyou/bin/pendingyou-mcp-headers`, is a small sh script Claude Code runs on
34
+ each connection: it finds Node on PATH, then nvm's default, then the Node `init` ran with, runs this version's own
35
+ copy (never npx) and prints only `{"Authorization":"Bearer …"}` (`pendingyou mcp-headers`), refreshing the sign-in in
36
+ the background when it's due. A browser sign-in at the same address (0.9.0's) is switched over; another's
37
+ `headersHelper` is kept. `login` there signs in that connection again, with the same client, so the machine keeps
38
+ one connection. `status` says what's set up and what's missing (it runs the helper as Claude Code would with a bare
39
+ PATH), each with the command that fixes it, `--origin` included off production. `uninstall` takes the helper out and
40
+ says the connection stays on your Assistants page. Needs Pending You's Worker from the same release.
41
+ - `login` over SSH signs in by code without `--device`. A sign-in by code names the computer, and the phone asks "Let
42
+ devbox hear your answers for the assistants running on it?"; in the browser, "Let this computer hear…". Neither offers
43
+ a choice of assistant any more (Darin, 2026-10-04): a computer hears for the assistants running on it. A sign-in
44
+ pointed at one assistant before then keeps hearing it, so `watch` for a Muse or another app can't be set up anew.
45
+ - Routing by sender: the hooks and `hold` ask Pending You for Claude Code's cards only (`source=claude-code`), so a card
46
+ Codex or another app posted from the same folder never reaches a Claude Code session, even with a sign-in pointed at
47
+ that app. `watch` names no app: it hands answers to a script, not a session. Pending You treats a folder's request
48
+ from an older version the same way.
49
+ - Hooks still run 0.9.0 from `~/.config/pendingyou/cli/0.9.0/`: run `npx pendingyou@latest init` once to switch, and
50
+ to add the mod.
51
+
3
52
  ## 0.9.0
4
53
 
5
54
  - The Stop check catches a final message that asks you to type or reply something in Claude Code's own chat
package/README.md CHANGED
@@ -11,20 +11,25 @@ Run it once on each computer. It:
11
11
 
12
12
  - adds the Pending You MCP server to Claude Code for every project (unless it's already there),
13
13
  - saves the Pending You skill to `~/.claude/skills/pendingyou/SKILL.md`,
14
- - signs this computer in (your browser opens once; press Allow),
14
+ - signs this computer in (your browser opens once; press Allow; over SSH, a code on your phone instead, as
15
+ [below](#a-computer-with-no-browser)),
15
16
  - adds three hooks to `~/.claude/settings.json`: a new session picks up answers that arrived while nothing was
16
17
  running, your next message brings a waiting answer with it, and a check when Claude Code finishes a turn (below),
17
18
  - lets Claude Code start `npx pendingyou hold` and use Pending You's tools without a permission prompt, so a question
18
19
  it asks while you're away doesn't wait on one,
19
20
  - keeps a copy of this version for the hooks in `~/.config/pendingyou/cli/<version>/`, so they start in a few
20
- hundredths of a second instead of waiting on npx (about half a second each).
21
+ hundredths of a second instead of waiting on npx (about half a second each),
22
+ - on Claude Code 2.1.287 or later, adds the wake mod in that copy to `CLAUDE_CODE_PLUGIN_DIRS` (`env` in
23
+ `~/.claude/settings.json`), so every session is woken when you answer (below). Other folders already there are kept;
24
+ an older version's copy is replaced.
21
25
 
22
26
  Your other settings are kept as they are. `npx pendingyou uninstall` removes exactly what `init` added.
23
27
 
24
28
  Adding the MCP server can take Claude Code a minute or more (it checks its servers); `init` says when it's waiting on
25
29
  that. If Claude Code already has a `pendingyou` server pointing somewhere else (staging, say), `init` names both and
26
30
  asks before replacing it; `--yes` replaces it without asking, and with nobody to ask it prints the two commands to
27
- run. Afterwards restart Claude Code, run `/mcp`, choose pendingyou and Authenticate.
31
+ run. Afterwards restart Claude Code, run `/mcp`, choose pendingyou and Authenticate (on a computer with no browser
32
+ there's nothing to authenticate: [below](#a-computer-with-no-browser)).
28
33
 
29
34
  If the `pendingyou` MCP server Claude Code uses in a session's folder is another Pending You than the hooks' (staging
30
35
  and production, say), both hooks add one line telling Claude Code so, and to ask you which one you use: its cards
@@ -37,7 +42,7 @@ never refreshes the sign-in itself: when that's due, it starts the refresh in th
37
42
  own. Running `init` again updates them in place, and adds any a newer version brings.
38
43
 
39
44
  The hooks run that copy with Node directly, like
40
- `"/opt/homebrew/bin/node" "~/.config/pendingyou/cli/0.9.0/node_modules/pendingyou/dist/cli.js" handoff || true` (the
45
+ `"/opt/homebrew/bin/node" "~/.config/pendingyou/cli/0.10.0/node_modules/pendingyou/dist/cli.js" handoff || true` (the
41
46
  paths in full). If the copy can't be made (no npm, no network), they run through `npx -y --prefer-offline
42
47
  pendingyou@<version>` instead. `npx pendingyou status` says which. If you remove the Node they run with, run `init`
43
48
  again.
@@ -51,7 +56,7 @@ Deep into a long conversation, Claude Code tends to end a turn with a question f
51
56
  person also goes on a card.* While cards Claude Code posted from this folder are still waiting on you, the same line
52
57
  names them (at most 3, newest first, about 20 tokens each) and asks it to `cancel_request` one with `answeredHere`
53
58
  when your message answers it, so a question you answer in chat doesn't stay in your queue. That costs one small
54
- request per message, given up after 1.5 seconds.
59
+ request per message, which brings any answer waiting for this folder too, given up after 2 seconds.
55
60
  - When Claude Code finishes a turn, `pendingyou stopcheck` looks at its last message. If it has something for you and
56
61
  nothing was posted, updated or replied to in Pending You that turn (and the message doesn't say it's on a card, or
57
62
  that one is coming),
@@ -76,31 +81,75 @@ costs a request: this folder's open cards, given up after 1.5 seconds.
76
81
  **On 0.2.0 with another origin?** Its hooks blocked every message (`There's no "--origin" command`). Run
77
82
  `npx pendingyou@latest init --origin <url>` again to repair them; see [CHANGELOG.md](CHANGELOG.md).
78
83
 
79
- ## How Claude Code uses it
84
+ ## How Claude Code hears your answer
80
85
 
81
- After it posts a question to Pending You, Claude Code runs this in the background and keeps working:
86
+ **Claude Code 2.1.287 or later: the wake mod.** There's nothing to run. In each session the mod learns the cards
87
+ Claude Code posts, checks them through the session's own Pending You connection (every 20 seconds while a card changed
88
+ in the last half hour, then every minute), and when you answer (after the 5-second window to undo), write back, or a
89
+ fallback comes due, it starts a turn naming the card and what to do: read it with `get_request`, act, then
90
+ `ack_answer`. That works while the session sits idle at the prompt, for as long as it's open, and across a restart
91
+ with `claude --continue`. Under the prompt it shows `⚠ pendingyou-wake: 2 waiting on you`. If Claude Code still runs
92
+ `npx pendingyou hold`, the mod answers it at once and nothing runs in the background. With the Pending You plugin
93
+ installed as well, only one copy acts. `npx pendingyou status` says whether the mod is loaded.
94
+
95
+ The mod's checks are Pending You tool calls like Claude Code's own, so they need Pending You's tools allowed, which
96
+ `init` does (`mcp__pendingyou`). If they aren't, the line under the prompt says which rule to add in `/permissions`, and
97
+ holds run as before.
98
+
99
+ **Older Claude Code: a background hold.** After it posts a question to Pending You, Claude Code runs this in the
100
+ background and keeps working:
82
101
 
83
102
  ```sh
84
103
  npx pendingyou hold req_0123456789abcdef0123
85
104
  ```
86
105
 
87
- When you answer (after the 5-second window to undo), write back, or the question's fallback comes due, it prints a few
88
- lines (the question, your answer, the next step) and exits. Claude Code wakes up with them. It stops on its own after
89
- 4 hours (`--timeout`), telling Claude Code the hooks will bring the answer later.
106
+ When you answer, write back, or the question's fallback comes due, it prints a few lines (the question, your answer,
107
+ the next step) and exits. Claude Code wakes up with them. It stops on its own after 4 hours (`--timeout`), and Claude
108
+ Code may stop a background command sooner; either way it then says to start it again, with the exact command. Your
109
+ next message brings any answer with it too.
90
110
 
91
111
  When you're at the terminal, Claude Code asks you there and puts the same question in Pending You at once; the
92
112
  first answer wins. If you answer in the terminal, it withdraws the card and the hold ends by itself.
93
113
 
94
114
  ## A computer with no browser
95
115
 
96
- On a server or over SSH, sign in with a code instead:
116
+ On a devbox or server you reach over SSH, `/mcp`'s Authenticate opens a browser on that machine, which you can't
117
+ reach. So SSH in, and at the prompt there run:
97
118
 
98
119
  ```sh
99
- npx pendingyou login --device
120
+ npx pendingyou init
100
121
  ```
101
122
 
102
- It shows a short code. Open pendingyou.com/device on your phone, sign in, enter the code and press Allow. Only enter a
103
- code your own computer is showing you.
123
+ `init` sees it's an SSH session (or Linux with no display) and signs the machine in with a code instead: it shows the
124
+ code, `pendingyou.com/device` and a QR code of the link with the code in it. Open it on your phone, sign in, check the
125
+ code matches and press Allow. Only allow a code your own computer is showing you. That one approval makes the machine
126
+ a Claude Code connection of its own ("Claude Code on devbox" on your Assistants page), and `init` sets up the rest:
127
+
128
+ - adds the `pendingyou` MCP server for every project with a `headersHelper` instead of OAuth
129
+ (`claude mcp add-json --scope user pendingyou '{"type":"http","url":"…/mcp","headersHelper":"…"}'`), so Claude Code
130
+ signs in through this machine's sign-in and there's nothing to Authenticate,
131
+ - writes that helper, `~/.config/pendingyou/bin/pendingyou-mcp-headers`, a small sh script Claude Code runs each time
132
+ it connects. It finds Node on PATH, else nvm's default (`$NVM_DIR` or `~/.nvm`), else the Node `init` ran with,
133
+ runs this version's own copy (never npx), and prints only `{"Authorization":"Bearer …"}`, refreshing the sign-in
134
+ when it's due,
135
+ - adds the wake mod, the hooks and Pending You's tools to `~/.claude/settings.json` as above, so Remote Control,
136
+ terminal multiplexers and plain SSH sessions all get them, whatever shell profile they read.
137
+
138
+ Run it at an interactive prompt after you SSH in, not as `ssh host npx pendingyou init`: on stock Ubuntu that one-line
139
+ form doesn't read your shell's setup, so nvm's Node and `claude` may not be on PATH. `--device` sets a computer up this
140
+ way wherever it is, and `--browser` uses the browser instead. Then start Claude Code there and ask it to finish
141
+ setting up Pending You. `npx pendingyou status` says what's set up and what's missing, with the command that fixes it.
142
+ If the sign-in ends (30 days, or you remove the machine on your Assistants page), `npx pendingyou login` approves a
143
+ new code and keeps the same connection.
144
+
145
+ The machine is called by its Tailscale name when Tailscale is installed and running (as `tailscale status` shows it),
146
+ else by its hostname, or by `--name devbox` on `init` or `login`. A cloud's own hostname (AWS's `ip-10-42-1-252`) is
147
+ kept when there's nothing better, and `login` says how to change it: `npx pendingyou login --name <name>` asks for one
148
+ more approval on your phone and renames the connection, which stays the same one.
149
+
150
+ Only want this command's own sign-in (for `watch`, below)? `npx pendingyou login --device` signs it in with a code,
151
+ hearing answers only, and adds no connection. Your phone asks "Let devbox hear your answers for the assistants running
152
+ on it?", with no choice of assistant.
104
153
 
105
154
  ## Always-on scripts
106
155
 
@@ -110,8 +159,9 @@ code your own computer is showing you.
110
159
  npx pendingyou watch -- ./on-answer.sh
111
160
  ```
112
161
 
113
- When you sign in, pick which assistant it hears for ("Hear answers for: Only Muse"); by default it's your Claude Code
114
- assistants. The command runs without a shell, once per answer, message or fallback that comes due, oldest first. It
162
+ It hears your Claude Code assistants' answers. Signing in asks no choice of assistant (since 2026-10-04: "Let pi hear
163
+ your answers for the assistants running on it?"); a sign-in pointed at one assistant before then keeps hearing that
164
+ one. The command runs without a shell, once per answer, message or fallback that comes due, oldest first. It
115
165
  gets the request as JSON on stdin, and `PENDINGYOU_REQUEST_ID`, `PENDINGYOU_EVENT` (`answer_ready`, `message_ready` or
116
166
  `fallback_due`), `PENDINGYOU_VERSION` and `PENDINGYOU_ORIGIN` in its environment. A failing command is tried twice
117
167
  more, then skipped. `--once` stops after the first.
@@ -120,10 +170,10 @@ more, then skipped. `--once` stops after the first.
120
170
 
121
171
  | Command | What it does |
122
172
  | --- | --- |
123
- | `init` | Set up Claude Code on this computer (`--no-login`, `--no-browser`, `--device`, `--yes`) |
124
- | `hold <requestId>` | Wait for the answer, print it, exit (`--timeout 4h`) |
173
+ | `init` | Set up Claude Code on this computer (`--no-login`, `--no-browser`, `--device`, `--browser`, `--yes`) |
174
+ | `hold <requestId>` | Wait for the answer, print it, exit (`--timeout 4h`); the wake mod answers it itself |
125
175
  | `status` | What's set up; exits 0 when Claude Code hears answers right away |
126
- | `login` / `logout` | Sign this computer in or out (`--device`, `--force`, `--all`) |
176
+ | `login` / `logout` | Sign this computer in or out (`--device`, `--browser`, `--force`, `--all`) |
127
177
  | `watch -- <command>` | Run a command each time an answer is ready (`--once`) |
128
178
  | `pickup` / `handoff` | The session-start and next-message hooks |
129
179
  | `stopcheck` | The Stop hook: items for you left only in chat |
@@ -134,14 +184,22 @@ before or after the command.
134
184
 
135
185
  ## What it can see, and where it keeps things
136
186
 
137
- The sign-in only hears answers to your own Claude Code assistants' questions, or to the one assistant you picked. It isn't an assistant: it can't ask,
138
- answer or change anything, and it doesn't show up as one. It's stored in `~/.config/pendingyou/credentials.json`
139
- (readable only by you), refreshes itself, and ends after 30 days or on `logout`. `state.json` beside it remembers
187
+ The sign-in only hears answers to your own Claude Code assistants' questions (or, for one made before 2026-10-04, to the one assistant you picked). It isn't an assistant: it can't ask,
188
+ answer or change anything, and it doesn't show up as one. The hooks and `hold` hand a session only the cards its own
189
+ app (Claude Code) posted from its folder, never one Codex or another app posted there. On a computer with no browser it's that computer's Claude
190
+ Code connection instead, which Claude Code asks through, and the hooks hear its answers only. It's stored in
191
+ `~/.config/pendingyou/credentials.json` (readable only by you), refreshes itself, and ends after 30 days or on
192
+ `logout`; `clients.json` remembers which sign-in a computer's connection used, so signing in again keeps it. `state.json` beside it remembers
140
193
  which answers were already handed over (ids only), `claude-code.json` what `init` added, and `cli/` the hooks' copy
141
194
  of the command line. `init`, `status` and the hooks read `~/.claude.json` (and a folder's `.mcp.json`, and
142
195
  `~/.claude/settings.json`'s enabled plugins) only to see whether Claude Code has a `pendingyou` MCP server and where
143
196
  it points. Nothing is sent anywhere but Pending You.
144
197
 
198
+ The wake mod runs inside Claude Code and reaches Pending You only through the session's own connection, with
199
+ `get_request` on the session's own cards. It keeps each session's cards (id, title, the agent's name, where each
200
+ stands) in Claude Code's store for mods, `~/.claude/plugins/store/`, and forgets them once they close or after 7 days.
201
+ It marks the copy acting for a session in that session's environment (`PENDINGYOU_WAKE_OWNER`).
202
+
145
203
  ## Development
146
204
 
147
205
  In the Pending You repo: `pnpm --filter pendingyou test`, `pnpm --filter pendingyou build` (to `dist/`). Try it with a
package/dist/api.js CHANGED
@@ -12,6 +12,12 @@
12
12
  import { credentialsPath, readCredential, updateCredential, } from "./credentials.js";
13
13
  import { isLocked } from "./files.js";
14
14
  import { OAuthError, refresh } from "./oauth.js";
15
+ /**
16
+ * The app whose sessions the command line hands answers to: its hooks and holds run in Claude Code. Routing by sender
17
+ * (2026-10-04): every request for a session names it, so Pending You returns only Claude Code's cards, never one Codex
18
+ * or another app posted from the same folder. `watch` names none: it hands answers to a script, not a session.
19
+ */
20
+ export const SESSION_APP = 'claude-code';
15
21
  /** Refresh this long before the access token runs out. */
16
22
  const EARLY_MS = 60_000;
17
23
  /** A hook starts a background refresh this long before the access token runs out, and goes on with the old one. */
@@ -116,6 +122,33 @@ async function hookToken(io, origin, force, signal) {
116
122
  }
117
123
  throw new Unavailable('The sign-in is being refreshed in the background.');
118
124
  }
125
+ /**
126
+ * The token `pendingyou mcp-headers` hands Claude Code (remote.ts). Claude Code runs the helper each time it connects
127
+ * and gives it 10 seconds, so, as for a hook, a refresh is never made in this process: within 5 minutes of expiry it
128
+ * starts `pendingyou refresh` detached and waits for its token, up to `waitMs`, then uses the old one while it still
129
+ * works. A token handed over has at least 10 seconds left; Claude Code runs the helper again on a 401.
130
+ */
131
+ export async function helperToken(io, origin, waitMs) {
132
+ const stored = await readCredential(io, origin);
133
+ if (!stored)
134
+ throw new SignInNeeded(false);
135
+ if (stored.expiresAt - io.now() > SOON_MS)
136
+ return stored.accessToken;
137
+ if (!(await isLocked(credentialsPath(io))))
138
+ io.background(['refresh', '--origin', origin]);
139
+ const until = io.now() + waitMs;
140
+ while (io.now() < until) {
141
+ await io.sleep(100);
142
+ const current = await readCredential(io, origin);
143
+ if (!current)
144
+ throw new SignInNeeded(true);
145
+ if (current.refreshToken !== stored.refreshToken)
146
+ return current.accessToken;
147
+ }
148
+ if (stored.expiresAt - io.now() > USABLE_MS)
149
+ return stored.accessToken;
150
+ throw new Unavailable('Pending You couldn’t be reached to refresh the sign-in.');
151
+ }
119
152
  /** Aborts on `signal` or after `ms`, whichever comes first (AbortSignal.any needs Node 20.3). */
120
153
  function either(signal, ms) {
121
154
  const timeout = AbortSignal.timeout(ms);
package/dist/args.js CHANGED
@@ -20,6 +20,24 @@ export function parseDuration(text) {
20
20
  const unit = match[2] ?? 'm';
21
21
  return Math.round(amount * (unit === 's' ? 1000 : unit === 'm' ? 60_000 : 3_600_000));
22
22
  }
23
+ /**
24
+ * A computer's name as Pending You keeps it (a connection's machine, "Claude Code on devbox"): control and format
25
+ * characters out, spaces collapsed, at most 60 characters. Empty when nothing is left.
26
+ */
27
+ export function cleanName(text) {
28
+ const cleaned = [...text]
29
+ .map((char) => (/[\p{Cc}\p{Cf}]/u.test(char) ? ' ' : char))
30
+ .join('')
31
+ .replace(/\s+/g, ' ')
32
+ .trim();
33
+ let short = '';
34
+ for (const char of cleaned) {
35
+ if (short.length + char.length > 60)
36
+ break;
37
+ short += char;
38
+ }
39
+ return short.trim();
40
+ }
23
41
  /** An origin Pending You can live at: https, or http on this computer (the local dev server). */
24
42
  export function parseOrigin(text) {
25
43
  let url;
@@ -50,25 +68,42 @@ const COMMANDS = new Set([
50
68
  'uninstall',
51
69
  'watch',
52
70
  'refresh',
71
+ 'mcp-headers',
53
72
  ]);
54
73
  /** Flags each command takes; `true` when the flag takes a value. */
55
74
  const FLAGS = {
56
75
  help: {},
57
76
  version: {},
58
- login: { origin: true, 'no-browser': false, force: false, device: false },
77
+ login: {
78
+ origin: true,
79
+ 'no-browser': false,
80
+ force: false,
81
+ device: false,
82
+ browser: false,
83
+ name: true,
84
+ },
59
85
  logout: { origin: true, all: false },
60
86
  status: { origin: true },
61
87
  hold: { origin: true, timeout: true },
62
88
  pickup: { origin: true },
63
89
  handoff: { origin: true },
64
90
  stopcheck: { origin: true },
65
- init: { origin: true, 'no-login': false, 'no-browser': false, device: false, yes: false },
91
+ init: {
92
+ origin: true,
93
+ 'no-login': false,
94
+ 'no-browser': false,
95
+ device: false,
96
+ browser: false,
97
+ yes: false,
98
+ name: true,
99
+ },
66
100
  uninstall: { origin: true },
67
101
  watch: { origin: true, once: false },
68
102
  refresh: { origin: true, force: false },
103
+ 'mcp-headers': { origin: true, check: false },
69
104
  };
70
105
  /** Options that take a value, whichever command they belong to: needed to find the command behind them. */
71
- const VALUE_OPTIONS = new Set(['origin', 'timeout']);
106
+ const VALUE_OPTIONS = new Set(['origin', 'timeout', 'name']);
72
107
  /** Splits off the options given before the command: [those options, the command, everything after it]. */
73
108
  function splitCommand(argv) {
74
109
  let index = 0;
@@ -147,6 +182,22 @@ export function parseArgs(argv, env = {}) {
147
182
  if (positional.length)
148
183
  throw new UsageError(`${first} takes no “${positional[0]}”.`);
149
184
  };
185
+ /** `--name`, cleaned up as Pending You keeps it; none when it isn't given. */
186
+ const machine = () => {
187
+ const given = text('name');
188
+ if (given === undefined)
189
+ return {};
190
+ const name = cleanName(given);
191
+ if (!name)
192
+ throw new UsageError('--name needs a name for this computer, like: --name devbox');
193
+ return { machine: name };
194
+ };
195
+ /** `--device` or `--browser`, or neither (null: decided by looking). */
196
+ const device = () => {
197
+ if (flags.has('device') && flags.has('browser'))
198
+ throw new UsageError('Use --device or --browser, not both.');
199
+ return flags.has('device') ? true : flags.has('browser') ? false : null;
200
+ };
150
201
  switch (first) {
151
202
  case 'help':
152
203
  return { name: 'help' };
@@ -159,7 +210,8 @@ export function parseArgs(argv, env = {}) {
159
210
  origin,
160
211
  browser: !flags.has('no-browser'),
161
212
  force: flags.has('force'),
162
- device: flags.has('device'),
213
+ device: device(),
214
+ ...machine(),
163
215
  };
164
216
  case 'logout':
165
217
  none();
@@ -183,6 +235,10 @@ export function parseArgs(argv, env = {}) {
183
235
  case 'refresh':
184
236
  none();
185
237
  return { name: 'refresh', origin, force: flags.has('force') };
238
+ // Not in USAGE: what Claude Code's headersHelper runs on a computer with no browser (remote.ts).
239
+ case 'mcp-headers':
240
+ none();
241
+ return { name: 'mcp-headers', origin, check: flags.has('check') };
186
242
  case 'init':
187
243
  none();
188
244
  return {
@@ -190,8 +246,9 @@ export function parseArgs(argv, env = {}) {
190
246
  origin,
191
247
  login: !flags.has('no-login'),
192
248
  browser: !flags.has('no-browser'),
193
- device: flags.has('device'),
249
+ device: device(),
194
250
  yes: flags.has('yes'),
251
+ ...machine(),
195
252
  };
196
253
  case 'watch':
197
254
  none();
@@ -216,21 +273,28 @@ export function parseArgs(argv, env = {}) {
216
273
  export const USAGE = `pendingyou: Pending You for Claude Code
217
274
 
218
275
  npx pendingyou init Set up Claude Code on this computer: the MCP server, the skill,
219
- this command's sign-in, and three hooks (answers, and a check
220
- that what Claude Code leaves you in chat is also in Pending You)
276
+ this command's sign-in, three hooks (answers, and a check that
277
+ what Claude Code leaves you in chat is also in Pending You), and
278
+ on Claude Code 2.1.287 or later the wake mod, which wakes a
279
+ session when you answer. Over SSH, or on Linux with no display,
280
+ you approve one code on your phone and Claude Code signs in
281
+ through this computer's sign-in: no browser needed
221
282
  npx pendingyou hold <req_…> Wait in the background until the answer to a request is ready,
222
- print it, and exit (which wakes Claude Code)
283
+ print it, and exit (which wakes Claude Code); with the wake mod,
284
+ the mod answers it and nothing runs
223
285
  npx pendingyou status What's set up, and whether Claude Code hears answers right away
224
- npx pendingyou login [--device] | logout
225
- --device: on a computer with no browser, approve a short code on your phone
286
+ npx pendingyou login [--device | --browser] | logout
287
+ --device: on a computer with no browser, approve a short code on your
288
+ phone (the default over SSH); --browser: sign in in this computer's browser
226
289
  npx pendingyou watch -- <command> [args…]
227
290
  Run a command each time an answer is ready (always-on scripts)
228
291
  npx pendingyou uninstall Remove exactly what init added
229
292
  npx pendingyou pickup Answers waiting for this folder (the session-start hook)
230
- npx pendingyou handoff The same, only while a hold is outstanding (the next-message hook)
293
+ npx pendingyou handoff The same, with your next message (the next-message hook)
231
294
  npx pendingyou stopcheck Questions or steps for you left only in chat (the Stop hook)
232
295
 
233
- Options: --origin <url> (default ${DEFAULT_ORIGIN}), --timeout 4h (hold), --no-browser and --device (login,
234
- init), --no-login (init), --yes (init: replace a pendingyou MCP server that points elsewhere without asking),
235
- --all (logout), --force (login), --once (watch).
296
+ Options: --origin <url> (default ${DEFAULT_ORIGIN}), --timeout 4h (hold), --no-browser, --device and --browser
297
+ (login, init), --name <name> (login, init: what to call this computer when it signs in with a code; by default its
298
+ Tailscale name, else its hostname), --no-login (init), --yes (init: replace a pendingyou MCP server that points
299
+ elsewhere without asking), --all (logout), --force (login), --once (watch).
236
300
  `;