pendingyou 0.21.0 → 0.22.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 +13 -0
- package/README.md +7 -5
- package/dist/apps/codex-app.js +1 -1
- package/dist/apps/codex.js +39 -13
- package/dist/init.js +19 -8
- package/dist/trust.js +37 -0
- package/dist/version.js +1 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/README.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0 (2026-10-06)
|
|
4
|
+
|
|
5
|
+
- Trusting Codex's hooks is one command now. When `init` or `status` finds Pending You's hooks in Codex but not trusted
|
|
6
|
+
yet, it prints the command that opens a Codex here (`codex` on PATH, else the Codex command line the ChatGPT app
|
|
7
|
+
brings, by its full path, quoted) and says to type `/hooks` there, look over Pending You's hooks and trust them. At a
|
|
8
|
+
terminal it then waits, looking again every 3 seconds for up to 10 minutes, and says "Ready: Codex hears answers
|
|
9
|
+
right away" once they're trusted (Ctrl-C stops the wait; `npx pendingyou status` checks later). Run by an agent, it
|
|
10
|
+
prints the same steps without waiting. It never trusts them for you.
|
|
11
|
+
- The instructions no longer send you to a Hooks page in the Codex app's Settings, which people couldn't find, nor to
|
|
12
|
+
other ways of trusting the hooks that haven't been seen working: init's last lines and `status` lead with `/hooks`.
|
|
13
|
+
- Looking for the Codex inside the ChatGPT or Codex app, the command line tries the `CodexCLI.app` it carries first,
|
|
14
|
+
then its `bin` folder.
|
|
15
|
+
|
|
3
16
|
## 0.21.0 (2026-10-06)
|
|
4
17
|
|
|
5
18
|
- Answer from Herdr. The Pending You plugin for Herdr (0.2) can now answer your cards from its popup, once you sign
|
package/README.md
CHANGED
|
@@ -41,8 +41,8 @@ Finish setting up Pending You: it’s connected here already.
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
The first session finishes setting up on its own: it checks in with Pending You, reports what's set up and sends you a
|
|
44
|
-
test card. Claude Code 2.1.287 or later starts that turn by itself; otherwise (and in Codex, after you trust its hooks
|
|
45
|
-
|
|
44
|
+
test card. Claude Code 2.1.287 or later starts that turn by itself; otherwise (and in Codex, after you trust its hooks in
|
|
45
|
+
`/hooks`) the session start adds one line with
|
|
46
46
|
the three calls that are left, and you say the sentence.
|
|
47
47
|
`npx pendingyou status` says what's set up for each agent, what's missing with the command that fixes it, and how setup
|
|
48
48
|
stands. `npx pendingyou uninstall` removes exactly what `init` added.
|
|
@@ -99,9 +99,11 @@ default_tools_approval_mode = "approve"
|
|
|
99
99
|
and hooks in `~/.codex/hooks.json`: a session start, each message, Pending You's card tools (so the Stop check knows a
|
|
100
100
|
turn posted, and so the thread is woken), and the Stop check; and (0.15.0) Codex's permission prompt and every tool
|
|
101
101
|
call that ran (a card when Codex waits for your OK, [below](#when-claude-code-waits-for-your-ok)) and the session's end
|
|
102
|
-
([below](#which-sessions-are-open)). **Codex runs a hook only once you trust it
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
([below](#which-sessions-are-open)). **Codex runs a hook only once you trust it**, in Codex's `/hooks`. When `init` or
|
|
103
|
+
`status` finds Pending You's hooks not trusted yet, it prints the one command that opens a Codex here (`codex`, or
|
|
104
|
+
the one the ChatGPT app brings, by its full path) and says to type `/hooks` there and trust them; at a terminal it
|
|
105
|
+
then waits (every 3 seconds, for up to 10 minutes; Ctrl-C stops it) until Codex trusts them, and says whether Codex is
|
|
106
|
+
ready. It never trusts them for you. `status` says which are trusted. Upgrading from 0.14.0 adds three hooks, which Codex
|
|
105
107
|
skips until you trust them once; `init` says so, and the ones you trusted stay trusted. A `pendingyou` server or
|
|
106
108
|
hooks Codex's `/import` copied from Claude Code (without its sign-in) are replaced.
|
|
107
109
|
|
package/dist/apps/codex-app.js
CHANGED
|
@@ -4,7 +4,7 @@ const APP_NAMES = ['ChatGPT.app', 'Codex.app'];
|
|
|
4
4
|
export function appCodexPaths(home, system = '/Applications') {
|
|
5
5
|
return [system, join(home, 'Applications')].flatMap((root) => APP_NAMES.flatMap((name) => {
|
|
6
6
|
const cli = join(root, name, 'Contents', 'Resources', 'codex-cli');
|
|
7
|
-
return [join(cli, '
|
|
7
|
+
return [join(cli, 'CodexCLI.app', 'Contents', 'MacOS', 'codex'), join(cli, 'bin', 'codex')];
|
|
8
8
|
}));
|
|
9
9
|
}
|
|
10
10
|
const versionOf = (stdout) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? 'installed';
|
package/dist/apps/codex.js
CHANGED
|
@@ -19,8 +19,22 @@ export const CODEX_MIN = '0.152.0';
|
|
|
19
19
|
export const CODEX_WAKE = '0.157.0';
|
|
20
20
|
export const CODEX_APP_WAKE = '0.149.0';
|
|
21
21
|
const FROM_APP = 'the Codex app’s';
|
|
22
|
-
const
|
|
23
|
-
|
|
22
|
+
const TRUST = 'type /hooks in Codex and trust';
|
|
23
|
+
export function trustSteps(found) {
|
|
24
|
+
const why = 'Codex runs Pending You’s hooks only once you trust them.';
|
|
25
|
+
if (!found)
|
|
26
|
+
return [
|
|
27
|
+
`${why} Open Codex in a terminal (codex, or the one that comes inside the ChatGPT app), type /hooks, look over Pending You’s hooks, and trust them.`,
|
|
28
|
+
];
|
|
29
|
+
const command = found.app ? `'${found.command.replaceAll("'", `'\\''`)}'` : found.command;
|
|
30
|
+
return [
|
|
31
|
+
`${why} Open Codex in a terminal:`,
|
|
32
|
+
'',
|
|
33
|
+
` ${command}`,
|
|
34
|
+
'',
|
|
35
|
+
'Type /hooks, look over Pending You’s hooks, and trust them.',
|
|
36
|
+
];
|
|
37
|
+
}
|
|
24
38
|
const DONT_SAVE = 'In the Codex app, don’t press Save on pendingyou in Settings › MCP: that writes it again without this computer’s sign-in (if you did, run init again to put it back).';
|
|
25
39
|
const CARD_TOOLS = '^mcp__pendingyou__(post_request|update_request|reply_in_thread|cancel_request)$';
|
|
26
40
|
export const CODEX_HOOKS = [
|
|
@@ -161,7 +175,7 @@ async function readHooksFile(io) {
|
|
|
161
175
|
};
|
|
162
176
|
return { path, json: parsed };
|
|
163
177
|
}
|
|
164
|
-
async function
|
|
178
|
+
async function readHookTrust(io) {
|
|
165
179
|
const file = await readHooksFile(io);
|
|
166
180
|
const config = (await readText(await configPath(io)).catch(() => null)) ?? '';
|
|
167
181
|
const hooks = 'json' in file && file.json.hooks && typeof file.json.hooks === 'object'
|
|
@@ -193,6 +207,7 @@ async function hookTrust(io) {
|
|
|
193
207
|
return { event: spec.event, sub: spec.sub, trust: 'missing' };
|
|
194
208
|
});
|
|
195
209
|
}
|
|
210
|
+
const untrusted = async (io) => (await readHookTrust(io)).filter((hook) => hook.trust === 'untrusted' || hook.trust === 'changed');
|
|
196
211
|
async function signedInByItself(io, program) {
|
|
197
212
|
const result = await io.run(program, ['mcp', 'list', '--json'], 30_000);
|
|
198
213
|
if (result.code !== 0)
|
|
@@ -433,7 +448,7 @@ export const codex = {
|
|
|
433
448
|
report({
|
|
434
449
|
ok: true,
|
|
435
450
|
text: merged.changed
|
|
436
|
-
? `Installed or updated Codex’s session-start pickup, next-message hand-off, card and Stop check hooks${extra ? `, ${cards ? 'its permission-prompt hooks (a card when Codex waits for your OK; --no-permission-cards leaves them out) ' : ''}and its session-end hook (Pending You hears when a thread is open)` : ''} in ${file.path}. Codex
|
|
451
|
+
? `Installed or updated Codex’s session-start pickup, next-message hand-off, card and Stop check hooks${extra ? `, ${cards ? 'its permission-prompt hooks (a card when Codex waits for your OK; --no-permission-cards leaves them out) ' : ''}and its session-end hook (Pending You hears when a thread is open)` : ''} in ${file.path}. Codex runs them once you trust them (how: below).`
|
|
437
452
|
: removed.changed
|
|
438
453
|
? `Took out Codex’s permission-prompt hooks, as you asked: no card when Codex waits for your OK. To turn them on: npx pendingyou init --app codex --permission-cards${originArgs(ictx.origin)}`
|
|
439
454
|
: 'Codex’s hooks were already installed.',
|
|
@@ -469,14 +484,17 @@ export const codex = {
|
|
|
469
484
|
});
|
|
470
485
|
return steps;
|
|
471
486
|
},
|
|
472
|
-
next: () => {
|
|
487
|
+
next: async () => {
|
|
473
488
|
const login = mode === 'oauth' && action !== 'keep';
|
|
474
489
|
const first = login ? `run ${shWord(program)} mcp login pendingyou, then ` : '';
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
490
|
+
const waiting = (await untrusted(io)).length > 0;
|
|
491
|
+
const start = waiting
|
|
492
|
+
? 'open Codex as above, type /hooks and trust Pending You’s hooks'
|
|
493
|
+
: detected.command
|
|
494
|
+
? 'open the Codex app and start a chat in the folder you work in'
|
|
495
|
+
: 'start codex here, or in the folder you work in';
|
|
496
|
+
const again = newHooks && waiting
|
|
497
|
+
? `Codex has new Pending You hooks (${newHooks}), which it skips until you trust them once.`
|
|
480
498
|
: null;
|
|
481
499
|
return {
|
|
482
500
|
lines: [
|
|
@@ -542,7 +560,7 @@ export const codex = {
|
|
|
542
560
|
codex.detect(io),
|
|
543
561
|
codexServer(io),
|
|
544
562
|
readCodexManifest(io),
|
|
545
|
-
|
|
563
|
+
readHookTrust(io),
|
|
546
564
|
readText(codexSkillPath(io)).catch(() => null),
|
|
547
565
|
]);
|
|
548
566
|
const credential = await readCredential(io, origin, 'codex');
|
|
@@ -593,7 +611,7 @@ export const codex = {
|
|
|
593
611
|
const hooksOk = core.every((hook) => hook.trust === 'trusted');
|
|
594
612
|
const hooksText = core.some((hook) => hook.trust === 'missing')
|
|
595
613
|
? `not installed; run npx pendingyou init --app codex${flag}`
|
|
596
|
-
: `${core.map((hook) => `${hook.event} ${words[hook.trust]}`).join(', ')}${hooksOk ? '' : `. Codex skips a hook you haven’t trusted: ${
|
|
614
|
+
: `${core.map((hook) => `${hook.event} ${words[hook.trust]}`).join(', ')}${hooksOk ? '' : `. Codex skips a hook you haven’t trusted: ${TRUST} them`}`;
|
|
597
615
|
const extraLine = (label, what, subs, off) => {
|
|
598
616
|
const hooks = trust.filter((hook) => subs.includes(hook.sub));
|
|
599
617
|
if (off)
|
|
@@ -603,7 +621,7 @@ export const codex = {
|
|
|
603
621
|
const trusted = hooks.every((hook) => hook.trust === 'trusted');
|
|
604
622
|
return trusted
|
|
605
623
|
? ` ok ${label} ${what} (${hooks.map((hook) => `${hook.event} trusted`).join(', ')})`
|
|
606
|
-
: ` missing ${label} ${what}, once you trust ${hooks.length === 1 ? 'its hook' : 'its hooks'} (${hooks.map((hook) => `${hook.event} ${words[hook.trust]}`).join(', ')}): ${
|
|
624
|
+
: ` missing ${label} ${what}, once you trust ${hooks.length === 1 ? 'its hook' : 'its hooks'} (${hooks.map((hook) => `${hook.event} ${words[hook.trust]}`).join(', ')}): ${TRUST} ${hooks.length === 1 ? 'it' : 'them'}`;
|
|
607
625
|
};
|
|
608
626
|
const ownConnection = credential?.kind === 'connection' && helper;
|
|
609
627
|
const notOwn = 'off: it needs Codex signed in through this computer’s own connection';
|
|
@@ -650,4 +668,12 @@ export const codex = {
|
|
|
650
668
|
: 'Not ready: Codex here can’t reach Pending You until the lines marked missing are fixed.');
|
|
651
669
|
return { lines, ready };
|
|
652
670
|
},
|
|
671
|
+
async untrustedHooks(io) {
|
|
672
|
+
if ((await untrusted(io)).length === 0)
|
|
673
|
+
return null;
|
|
674
|
+
return {
|
|
675
|
+
lines: trustSteps(await findCodex(io)),
|
|
676
|
+
trusted: async () => (await untrusted(io)).length === 0,
|
|
677
|
+
};
|
|
678
|
+
},
|
|
653
679
|
};
|
package/dist/init.js
CHANGED
|
@@ -10,6 +10,7 @@ import { headlessReason, helperCommand, removeHelper, writeHelper } from "./remo
|
|
|
10
10
|
import { FINISH_SAY } from "./setup.js";
|
|
11
11
|
import { removeShim, shimPath, writeShim } from "./shim.js";
|
|
12
12
|
import { checkSignIn, logout, signInApps, titleFrom, titleHere } from "./signin.js";
|
|
13
|
+
import { trustHooks } from "./trust.js";
|
|
13
14
|
import { VERSION } from "./version.js";
|
|
14
15
|
const line = (step) => ` ${step.ok ? 'ok ' : 'check '} ${step.text}\n`;
|
|
15
16
|
async function findApps(io, wanted) {
|
|
@@ -175,7 +176,7 @@ export async function init(io, options) {
|
|
|
175
176
|
const helped = setUp.some((each) => each.signIn === 'connection' && !each.bridged);
|
|
176
177
|
const bridged = setUp.some((each) => each.bridged);
|
|
177
178
|
const shared = await installShared(io, helped, bridged, report);
|
|
178
|
-
const
|
|
179
|
+
const installed = [];
|
|
179
180
|
for (const each of setUp) {
|
|
180
181
|
if (setUp.length > 1)
|
|
181
182
|
io.out(`${each.app.name}:\n`);
|
|
@@ -191,14 +192,19 @@ export async function init(io, options) {
|
|
|
191
192
|
bridge: shared.bridge && each.bridged ? bridgeCommand(io, options.origin, each.app.id) : null,
|
|
192
193
|
signedIn: how === 'signed-in' || how === 'already',
|
|
193
194
|
};
|
|
194
|
-
|
|
195
|
-
const next = each.next(ictx, done);
|
|
196
|
-
if (next)
|
|
197
|
-
nexts.push(next);
|
|
195
|
+
installed.push({ each, ictx, done: await each.install(ictx) });
|
|
198
196
|
}
|
|
199
197
|
const others = await Promise.all(APPS.filter((app) => !setUp.some((each) => each.app.id === app.id)).map((app) => app.installed(io)));
|
|
200
198
|
if (!io.env.PENDINGYOU_SELF && !others.some(Boolean))
|
|
201
199
|
await pruneCli(io, shared.copy?.version ?? null);
|
|
200
|
+
for (const { each } of installed)
|
|
201
|
+
await trustHooks(io, each.app, options.origin);
|
|
202
|
+
const nexts = [];
|
|
203
|
+
for (const { each, ictx, done } of installed) {
|
|
204
|
+
const next = await each.next(ictx, done);
|
|
205
|
+
if (next)
|
|
206
|
+
nexts.push(next);
|
|
207
|
+
}
|
|
202
208
|
const last = closing(nexts);
|
|
203
209
|
if (last.length)
|
|
204
210
|
io.out(`${last.join('\n')}\n`);
|
|
@@ -223,16 +229,21 @@ async function coveredApps(io, wanted) {
|
|
|
223
229
|
}
|
|
224
230
|
export async function status(io, options) {
|
|
225
231
|
const apps = await coveredApps(io, options.apps);
|
|
226
|
-
|
|
232
|
+
const ready = [];
|
|
227
233
|
const sections = [];
|
|
228
234
|
for (const app of apps) {
|
|
229
235
|
const signIn = await checkSignIn(io, options.origin, app.id);
|
|
230
236
|
const section = await app.status(io, { origin: options.origin, signIn });
|
|
231
|
-
ready
|
|
237
|
+
ready.push(section.ready);
|
|
232
238
|
sections.push(section.lines.join('\n'));
|
|
233
239
|
}
|
|
234
240
|
io.out(`${sections.join('\n\n')}\n`);
|
|
235
|
-
|
|
241
|
+
for (const [at, app] of apps.entries()) {
|
|
242
|
+
const after = await trustHooks(io, app, options.origin);
|
|
243
|
+
if (after)
|
|
244
|
+
ready[at] = after.ready;
|
|
245
|
+
}
|
|
246
|
+
return ready.every(Boolean) ? 0 : 1;
|
|
236
247
|
}
|
|
237
248
|
export async function uninstall(io, options) {
|
|
238
249
|
const installed = [];
|
package/dist/trust.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { originArgs } from "./hooks.js";
|
|
2
|
+
import { checkSignIn } from "./signin.js";
|
|
3
|
+
export const TRUST_EVERY_MS = 3000;
|
|
4
|
+
export const TRUST_FOR_MS = 10 * 60_000;
|
|
5
|
+
export async function waitForTrust(io, trusted) {
|
|
6
|
+
const until = io.now() + TRUST_FOR_MS;
|
|
7
|
+
while (io.now() < until) {
|
|
8
|
+
if (io.signal.aborted)
|
|
9
|
+
return 'stopped';
|
|
10
|
+
await io.sleep(TRUST_EVERY_MS, io.signal);
|
|
11
|
+
if (io.signal.aborted)
|
|
12
|
+
return 'stopped';
|
|
13
|
+
if (await trusted())
|
|
14
|
+
return 'trusted';
|
|
15
|
+
}
|
|
16
|
+
return 'timed-out';
|
|
17
|
+
}
|
|
18
|
+
export async function trustHooks(io, app, origin) {
|
|
19
|
+
const pending = await app.untrustedHooks?.(io);
|
|
20
|
+
if (!pending)
|
|
21
|
+
return null;
|
|
22
|
+
const later = `npx pendingyou status${originArgs(origin)}`;
|
|
23
|
+
io.out(`\n${pending.lines.join('\n')}\n`);
|
|
24
|
+
if (!io.interactive) {
|
|
25
|
+
io.out(`Once you have, run ${later} to check.\n`);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
io.out(`Waiting for you to trust the hooks… (Ctrl-C to stop; run ${later} later)\n`);
|
|
29
|
+
const outcome = await waitForTrust(io, pending.trusted);
|
|
30
|
+
if (outcome !== 'trusted') {
|
|
31
|
+
io.out(`${outcome === 'stopped' ? 'Stopped waiting.' : 'Stopped waiting after 10 minutes.'} Once you’ve trusted them, run ${later}.\n`);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const section = await app.status(io, { origin, signIn: await checkSignIn(io, origin, app.id) });
|
|
35
|
+
io.out(`${section.lines.at(-1)}\n`);
|
|
36
|
+
return section;
|
|
37
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.22.0';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou-wake",
|
|
3
3
|
"displayName": "Pending You",
|
|
4
|
-
"version": "2.34.
|
|
4
|
+
"version": "2.34.1",
|
|
5
5
|
"description": "Wakes Claude Code when your person answers a Pending You card it posted, even while it sits idle. Installed by `npx pendingyou init`.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "RecordPlane",
|
package/mod/README.md
CHANGED
|
@@ -4,4 +4,4 @@ The pendingyou command line’s copy of the Pending You plugin’s wake mod, for
|
|
|
4
4
|
|
|
5
5
|
In each session it learns the cards the session posts to Pending You, checks them through the session’s own Pending You connection, and starts a turn when your person answers, writes back or a fallback comes due, or hands the session a question from another of their assistants (CLI 0.14.0), even while it sits idle. It answers `npx pendingyou hold` itself, so nothing waits in the background, and shows “2 waiting on you” under the prompt, after the plugin’s name. With the Pending You plugin installed too, only one copy acts.
|
|
6
6
|
|
|
7
|
-
Generated from recordplane/pendingyou (packages/claude-plugin/src/wake) with Pending You’s guide 2.34.
|
|
7
|
+
Generated from recordplane/pendingyou (packages/claude-plugin/src/wake) with Pending You’s guide 2.34.1; send changes there.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Pending You for your coding agents: one command sets up Claude Code, Codex, OpenCode and Pi to ask you through Pending You and hear your answers, even while they sit idle.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://www.pendingyou.com",
|