pendingyou 0.13.0 → 0.15.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 +134 -11
- package/README.md +114 -34
- package/dist/api.js +27 -50
- package/dist/apps/claude-code.js +14 -49
- package/dist/apps/codex-app.js +0 -18
- package/dist/apps/codex-wake.js +290 -122
- package/dist/apps/codex.js +102 -100
- package/dist/apps/ids.js +0 -11
- package/dist/apps/opencode-plugin.js +91 -67
- package/dist/apps/opencode.js +13 -80
- package/dist/apps/pi-extension.js +100 -75
- package/dist/apps/pi.js +13 -72
- package/dist/apps/registry.js +0 -2
- package/dist/apps/switch.js +15 -0
- package/dist/args.js +72 -41
- package/dist/bridge.js +0 -94
- package/dist/claude.js +6 -96
- package/dist/cli.js +0 -2
- package/dist/confirm.js +0 -69
- package/dist/credentials.js +0 -27
- package/dist/environment.js +0 -28
- package/dist/errors.js +0 -1
- package/dist/files.js +0 -16
- package/dist/format.js +59 -13
- package/dist/handed.js +87 -0
- package/dist/hold.js +0 -24
- package/dist/hooks.js +2 -27
- package/dist/index.js +0 -2
- package/dist/init.js +0 -32
- package/dist/install.js +0 -32
- package/dist/io.js +1 -10
- package/dist/json.js +0 -29
- package/dist/jsonc.js +0 -33
- package/dist/main.js +29 -31
- package/dist/mod.js +0 -23
- package/dist/oauth.js +0 -45
- package/dist/permission.js +118 -144
- package/dist/pickup.js +113 -63
- package/dist/presence.js +330 -0
- package/dist/qr.js +0 -28
- package/dist/redact.js +0 -77
- package/dist/remote.js +0 -90
- package/dist/setup.js +0 -30
- package/dist/shim.js +0 -23
- package/dist/signin.js +0 -42
- package/dist/state.js +19 -10
- package/dist/stopcheck.js +42 -86
- package/dist/toml.js +0 -34
- package/dist/version.js +1 -2
- package/dist/watch.js +3 -20
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/README.md +2 -2
- package/mod/hooks/register.js +142 -61
- package/mod/hooks/wake.js +179 -108
- package/package.json +2 -1
- package/mod/tests/wake.test.ts +0 -247
package/dist/apps/claude-code.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
// Claude Code (0.11.0's module of what claude.ts carries out): what `pendingyou init` decides before anyone signs in,
|
|
2
|
-
// what it does after, `status`'s section and the lines init ends with.
|
|
3
|
-
//
|
|
4
|
-
// Its MCP server signs in through this computer's own connection ("Claude Code on devbox"), by the headers helper, on
|
|
5
|
-
// every computer but Windows (where Claude Code runs a headersHelper through cmd) or with `--oauth`; the hooks use the
|
|
6
|
-
// same connection, so one approval covers both. A pendingyou server that's there decides the rest, before signing in:
|
|
7
|
-
// - ours, through the helper, at this Pending You: kept.
|
|
8
|
-
// - one at this Pending You that signs in by itself (Claude Code's own /mcp → Authenticate), or no server beside an
|
|
9
|
-
// enabled Pending You plugin for it (whose server is OAuth): with no browser here it's switched without a word (as
|
|
10
|
-
// 0.10.0 did: that sign-in can't finish here); at a terminal init asks, Yes by default; `--yes` switches; with nobody
|
|
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
|
-
// that only hears; kept beside the plugin, no skill is saved either: the plugin carries it.
|
|
13
|
-
// - one at another Pending You: replaced once the person says so (or `--yes`), or else Claude Code is left alone.
|
|
14
1
|
import { addCommand, claudeState, helperServer, installClaude, mcpFromClaude, mcpFromFile, PLUGIN_ORIGINS, permissionBlocker, pluginFor, readManifest, removeCommand, sameUrl, uninstallClaude, } from "../claude.js";
|
|
15
2
|
import { connectionMachine, readCredential } from "../credentials.js";
|
|
16
3
|
import { differs, serverForFolder, siteOf } from "../environment.js";
|
|
@@ -20,23 +7,12 @@ import { checkHelper, foundBy, headlessReason, helperCommand, helperPath, isOurH
|
|
|
20
7
|
import { connectionTitle, FINISH_SAY, setupOf, setupStatus } from "../setup.js";
|
|
21
8
|
import { shimPath } from "../shim.js";
|
|
22
9
|
import { APP_NAMES } from "./ids.js";
|
|
10
|
+
import { switches } from "./switch.js";
|
|
23
11
|
const NAME = APP_NAMES['claude-code'];
|
|
24
|
-
/** The sentence's lead-in, and what restarting means, in init's last lines. */
|
|
25
12
|
const RESTART = 'restart Claude Code here (/exit, then claude --continue), or start it in the folder you work in';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* browser, `--yes`, or Yes (the default) at a terminal; kept, with nobody to ask.
|
|
29
|
-
*/
|
|
30
|
-
async function switches(io, ctx, what) {
|
|
31
|
-
if (ctx.headless || ctx.yes)
|
|
32
|
-
return true;
|
|
33
|
-
if (!io.interactive)
|
|
34
|
-
return false;
|
|
35
|
-
io.out(`${what} init can move Claude Code to this computer’s own sign-in, which the hooks use too: one sign-in for both, and nothing to Authenticate. The connection it has now stays on your Assistants page until you remove it, and answers to cards it asked through that one won’t reach Claude Code by themselves.\n`);
|
|
36
|
-
const answer = await io.ask('Switch Claude Code to this computer’s sign-in? [Y/n] ');
|
|
37
|
-
return !/^\s*no?\s*$/i.test(answer);
|
|
13
|
+
async function switchesFor(io, ctx, what) {
|
|
14
|
+
return switches(io, ctx, { name: 'Claude Code', what, uses: 'the hooks use', headless: true });
|
|
38
15
|
}
|
|
39
|
-
/** The plan for a server at another Pending You: replaced once the person says so, or Claude Code left alone. */
|
|
40
16
|
async function replaces(io, ctx, server, url) {
|
|
41
17
|
ctx.progress(`Claude Code’s pendingyou MCP server points at ${server.url}, not ${url}.\n`);
|
|
42
18
|
if (ctx.yes)
|
|
@@ -92,7 +68,6 @@ export const claudeCode = {
|
|
|
92
68
|
};
|
|
93
69
|
}
|
|
94
70
|
else if (server?.url && !sameUrl(server.url, url)) {
|
|
95
|
-
// Another Pending You (staging, when production was asked for): asked before anyone signs in.
|
|
96
71
|
if (!(await replaces(io, ctx, server, url))) {
|
|
97
72
|
const commands = ` ${removeCommand(server.scope)}\n ${add}`;
|
|
98
73
|
return {
|
|
@@ -123,7 +98,6 @@ export const claudeCode = {
|
|
|
123
98
|
else if (ours)
|
|
124
99
|
plan = { action: 'switch', server };
|
|
125
100
|
else if (server.helper) {
|
|
126
|
-
// Someone else's headersHelper: theirs to change. The hooks hear through a sign-in of their own.
|
|
127
101
|
mode = 'oauth';
|
|
128
102
|
plan = helperMode
|
|
129
103
|
? {
|
|
@@ -138,7 +112,7 @@ export const claudeCode = {
|
|
|
138
112
|
};
|
|
139
113
|
}
|
|
140
114
|
else if (helperMode &&
|
|
141
|
-
(await
|
|
115
|
+
(await switchesFor(io, ctx, 'Claude Code signs in to Pending You by itself here (/mcp → Authenticate).')))
|
|
142
116
|
plan = { action: 'switch', server };
|
|
143
117
|
else {
|
|
144
118
|
mode = 'oauth';
|
|
@@ -152,9 +126,8 @@ export const claudeCode = {
|
|
|
152
126
|
}
|
|
153
127
|
}
|
|
154
128
|
else if (plugin) {
|
|
155
|
-
// No server added by hand, so the Pending You plugin's is Claude Code's: one added by hand would win over it.
|
|
156
129
|
if (helperMode &&
|
|
157
|
-
(await
|
|
130
|
+
(await switchesFor(io, ctx, 'The Pending You plugin signs Claude Code in to Pending You by itself here.')))
|
|
158
131
|
plan = { action: 'add', plugin: true };
|
|
159
132
|
else {
|
|
160
133
|
mode = 'oauth';
|
|
@@ -224,10 +197,6 @@ export const claudeCode = {
|
|
|
224
197
|
return claudeStatus(io, ctx);
|
|
225
198
|
},
|
|
226
199
|
};
|
|
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
200
|
function permissionLine(io, claude, manifest, helper, flag) {
|
|
232
201
|
const label = 'Permission prompts:';
|
|
233
202
|
if (claude.permission)
|
|
@@ -241,7 +210,14 @@ function permissionLine(io, claude, manifest, helper, flag) {
|
|
|
241
210
|
? ` ${label} no card when Claude Code waits for your OK: ${blocker}`
|
|
242
211
|
: ` missing ${label} no card when Claude Code waits for your OK; run npx pendingyou@latest init${flag}`;
|
|
243
212
|
}
|
|
244
|
-
|
|
213
|
+
function presenceLine(claude, connected, mod, flag) {
|
|
214
|
+
const label = 'Presence:';
|
|
215
|
+
if (!connected)
|
|
216
|
+
return ` ${label} off: it needs Claude Code signed in through this computer’s own connection`;
|
|
217
|
+
if (!claude.presence || !claude.hooks)
|
|
218
|
+
return ` missing ${label} Pending You isn’t told when a session closes; run npx pendingyou@latest init${flag}`;
|
|
219
|
+
return ` ok ${label} tells Pending You when this session is open (${mod ? 'as it starts, then every 5 minutes from the wake mod' : 'as it starts and with your messages'}; closed as it ends)`;
|
|
220
|
+
}
|
|
245
221
|
async function claudeStatus(io, ctx) {
|
|
246
222
|
const { origin, signIn } = ctx;
|
|
247
223
|
const [claude, here, manifest] = await Promise.all([
|
|
@@ -252,8 +228,6 @@ async function claudeStatus(io, ctx) {
|
|
|
252
228
|
const credential = await readCredential(io, origin);
|
|
253
229
|
const flag = originArgs(origin);
|
|
254
230
|
const mcp = claude.mcp;
|
|
255
|
-
// Through the helper (this computer's own sign-in): Claude Code's server runs ours, init set it up so, or nothing is
|
|
256
|
-
// set up yet and init would (everywhere but Windows).
|
|
257
231
|
const helped = usesHelper(mcp, origin);
|
|
258
232
|
const helper = io.platform !== 'win32' &&
|
|
259
233
|
(helped ||
|
|
@@ -267,7 +241,6 @@ async function claudeStatus(io, ctx) {
|
|
|
267
241
|
const url = `${origin}/mcp`;
|
|
268
242
|
const plugin = claude.plugins.find((id) => siteOf(PLUGIN_ORIGINS[id] ?? '') === siteOf(origin));
|
|
269
243
|
const sameServer = mcp !== false && mcp !== 'unknown' && (!mcp.url || mcp.url.replace(/\/+$/, '') === url);
|
|
270
|
-
// The plugin's server is Claude Code's when nobody added one by hand.
|
|
271
244
|
const pluginServer = mcp === false && Boolean(plugin);
|
|
272
245
|
const mcpOk = helper ? sameServer && helped : sameServer || pluginServer;
|
|
273
246
|
const mcpText = mcp === 'unknown'
|
|
@@ -287,7 +260,6 @@ async function claudeStatus(io, ctx) {
|
|
|
287
260
|
: `pendingyou (${mcp.url}) signs in through a browser${why ? ', which can’t finish on this computer' : ''}; run npx pendingyou init${flag}`;
|
|
288
261
|
const form = claude.form;
|
|
289
262
|
const hooksRun = form?.runs ?? true;
|
|
290
|
-
// The wake mod (0.10.0): Claude Code 2.1.287 or later loads it from the plugin or from CLAUDE_CODE_PLUGIN_DIRS.
|
|
291
263
|
const mod = claude.mod;
|
|
292
264
|
const modOk = mod.loads && (mod.plugin || mod.present);
|
|
293
265
|
const modText = !claude.claude
|
|
@@ -311,9 +283,7 @@ async function claudeStatus(io, ctx) {
|
|
|
311
283
|
: form.form === 'npx'
|
|
312
284
|
? `they run through npx${via}, which is slower; run npx pendingyou@latest init${flag} to run them directly`
|
|
313
285
|
: 'they run pendingyou from PATH or a file of your own';
|
|
314
|
-
// login makes a connection only once Claude Code's server signs in through the helper; init does both.
|
|
315
286
|
const fix = `npx pendingyou ${helped ? 'login' : 'init'}${flag}`;
|
|
316
|
-
// The connection's name as Pending You has it, so status never drifts from it; else this computer's own record.
|
|
317
287
|
const title = signIn.state === 'ok' && signIn.for
|
|
318
288
|
? connectionTitle(signIn.for)
|
|
319
289
|
: `${NAME} on ${(await connectionMachine(io, origin)) ?? (machineOf(io.host) || 'this computer')}`;
|
|
@@ -338,7 +308,6 @@ async function claudeStatus(io, ctx) {
|
|
|
338
308
|
: signIn.state === 'none'
|
|
339
309
|
? `not signed in; run npx pendingyou login${flag}`
|
|
340
310
|
: 'Pending You couldn’t be reached';
|
|
341
|
-
// The helper as Claude Code runs it at its barest: found Node, and runs pendingyou's copy.
|
|
342
311
|
const check = helper ? await checkHelper(io, origin) : null;
|
|
343
312
|
const skillOk = claude.skill || Boolean(plugin);
|
|
344
313
|
const skillText = claude.skill
|
|
@@ -356,18 +325,14 @@ async function claudeStatus(io, ctx) {
|
|
|
356
325
|
? [
|
|
357
326
|
` ok Plugin: ${plugin} is enabled${pluginServer ? '' : mcp !== false && mcp !== 'unknown' ? '; Claude Code uses the server added by hand instead of its own' : ''}`,
|
|
358
327
|
]
|
|
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));
|
|
360
|
-
// How setup stands on Pending You (from servers that say): never a reason it isn't Ready.
|
|
328
|
+
: []), ` ${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), presenceLine(claude, helper && connected, modOk, flag));
|
|
361
329
|
const setup = setupStatus(NAME, signIn.state === 'ok' ? setupOf(signIn.for) : null);
|
|
362
330
|
if (setup)
|
|
363
331
|
lines.push(` ${setup === 'finished' ? 'ok ' : ' '} Setup: ${setup}`);
|
|
364
|
-
// A folder's own server (local or project scope) wins over the user one in Claude Code: say when it's elsewhere. (The
|
|
365
|
-
// plugin's server counts only when no server was added by hand, and is production's.)
|
|
366
332
|
if (here && (here.scope === 'local' || here.scope === 'project') && differs(here, origin))
|
|
367
333
|
lines.push(` ${mark(false)} This folder: its ${here.scope}-scope pendingyou MCP server (${here.url}) is another Pending You than ${origin}; Claude Code uses it here instead`);
|
|
368
334
|
const user = mcp !== false && mcp !== 'unknown' && mcp.url ? { url: mcp.url, scope: 'user' } : null;
|
|
369
335
|
const other = differs(here, origin) ? here : differs(user, origin) ? user : null;
|
|
370
|
-
// Through the helper, Claude Code reaches Pending You only with the helper, the connection and the server together.
|
|
371
336
|
const reaches = !helper || (signOk && mcpOk && check?.ok === true);
|
|
372
337
|
const ready = reaches && signIn.state === 'ok' && claude.hooks && hooksRun && !other;
|
|
373
338
|
if (other)
|
package/dist/apps/codex-app.js
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
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
1
|
import { stat } from 'node:fs/promises';
|
|
9
2
|
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
3
|
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
4
|
export function appCodexPaths(home, system = '/Applications') {
|
|
18
5
|
return [system, join(home, 'Applications')].flatMap((root) => APP_NAMES.flatMap((name) => {
|
|
19
6
|
const cli = join(root, name, 'Contents', 'Resources', 'codex-cli');
|
|
20
7
|
return [join(cli, 'bin', 'codex'), join(cli, 'CodexCLI.app', 'Contents', 'MacOS', 'codex')];
|
|
21
8
|
}));
|
|
22
9
|
}
|
|
23
|
-
/** `codex --version`'s version ("codex-cli 0.159.2"), or "installed" when it can't be read. */
|
|
24
10
|
const versionOf = (stdout) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? 'installed';
|
|
25
11
|
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
12
|
export async function findCodex(io, paths = appCodexPaths(io.home, io.env.PENDINGYOU_APPLICATIONS_DIR || undefined)) {
|
|
31
13
|
const onPath = await io.run('codex', ['--version'], 20_000);
|
|
32
14
|
if (onPath.code === 0)
|