pendingyou 0.12.0 → 0.14.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 +142 -5
- package/README.md +112 -28
- package/dist/api.js +0 -50
- package/dist/apps/claude-code.js +16 -36
- package/dist/apps/codex-app.js +26 -0
- package/dist/apps/codex-wake.js +299 -120
- package/dist/apps/codex.js +51 -83
- package/dist/apps/ids.js +0 -11
- package/dist/apps/opencode-plugin.js +39 -65
- package/dist/apps/opencode.js +0 -70
- package/dist/apps/pi-extension.js +63 -73
- package/dist/apps/pi.js +0 -62
- package/dist/apps/registry.js +0 -2
- package/dist/args.js +45 -39
- package/dist/bridge.js +71 -83
- package/dist/claude.js +63 -87
- 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 +1 -12
- package/dist/format.js +30 -13
- package/dist/handed.js +71 -0
- package/dist/hold.js +0 -24
- package/dist/hooks.js +8 -29
- package/dist/index.js +0 -2
- package/dist/init.js +2 -33
- package/dist/install.js +0 -32
- package/dist/io.js +2 -12
- package/dist/json.js +0 -29
- package/dist/jsonc.js +0 -33
- package/dist/main.js +15 -24
- package/dist/mod.js +0 -23
- package/dist/oauth.js +0 -45
- package/dist/permission.js +631 -0
- package/dist/pickup.js +109 -63
- package/dist/qr.js +0 -28
- package/dist/redact.js +292 -0
- package/dist/remote.js +0 -90
- package/dist/setup.js +8 -27
- package/dist/shim.js +8 -24
- package/dist/signin.js +0 -42
- package/dist/state.js +19 -10
- package/dist/stopcheck.js +50 -84
- 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 +109 -62
- package/mod/hooks/wake.js +119 -104
- package/package.json +2 -1
- package/mod/tests/wake.test.ts +0 -247
package/dist/apps/claude-code.js
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
import { addCommand, claudeState, helperServer, installClaude, mcpFromClaude, mcpFromFile, PLUGIN_ORIGINS, pluginFor, readManifest, removeCommand, sameUrl, uninstallClaude, } from "../claude.js";
|
|
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";
|
|
17
4
|
import { originArgs } from "../hooks.js";
|
|
@@ -21,12 +8,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";
|
|
23
10
|
const NAME = APP_NAMES['claude-code'];
|
|
24
|
-
/** The sentence's lead-in, and what restarting means, in init's last lines. */
|
|
25
11
|
const RESTART = 'restart Claude Code here (/exit, then claude --continue), or start it in the folder you work in';
|
|
26
|
-
/**
|
|
27
|
-
* Whether to move a server that signs in by itself to this computer's sign-in: without a word where there's no
|
|
28
|
-
* browser, `--yes`, or Yes (the default) at a terminal; kept, with nobody to ask.
|
|
29
|
-
*/
|
|
30
12
|
async function switches(io, ctx, what) {
|
|
31
13
|
if (ctx.headless || ctx.yes)
|
|
32
14
|
return true;
|
|
@@ -36,7 +18,6 @@ async function switches(io, ctx, what) {
|
|
|
36
18
|
const answer = await io.ask('Switch Claude Code to this computer’s sign-in? [Y/n] ');
|
|
37
19
|
return !/^\s*no?\s*$/i.test(answer);
|
|
38
20
|
}
|
|
39
|
-
/** The plan for a server at another Pending You: replaced once the person says so, or Claude Code left alone. */
|
|
40
21
|
async function replaces(io, ctx, server, url) {
|
|
41
22
|
ctx.progress(`Claude Code’s pendingyou MCP server points at ${server.url}, not ${url}.\n`);
|
|
42
23
|
if (ctx.yes)
|
|
@@ -92,7 +73,6 @@ export const claudeCode = {
|
|
|
92
73
|
};
|
|
93
74
|
}
|
|
94
75
|
else if (server?.url && !sameUrl(server.url, url)) {
|
|
95
|
-
// Another Pending You (staging, when production was asked for): asked before anyone signs in.
|
|
96
76
|
if (!(await replaces(io, ctx, server, url))) {
|
|
97
77
|
const commands = ` ${removeCommand(server.scope)}\n ${add}`;
|
|
98
78
|
return {
|
|
@@ -123,7 +103,6 @@ export const claudeCode = {
|
|
|
123
103
|
else if (ours)
|
|
124
104
|
plan = { action: 'switch', server };
|
|
125
105
|
else if (server.helper) {
|
|
126
|
-
// Someone else's headersHelper: theirs to change. The hooks hear through a sign-in of their own.
|
|
127
106
|
mode = 'oauth';
|
|
128
107
|
plan = helperMode
|
|
129
108
|
? {
|
|
@@ -152,7 +131,6 @@ export const claudeCode = {
|
|
|
152
131
|
}
|
|
153
132
|
}
|
|
154
133
|
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
134
|
if (helperMode &&
|
|
157
135
|
(await switches(io, ctx, 'The Pending You plugin signs Claude Code in to Pending You by itself here.')))
|
|
158
136
|
plan = { action: 'add', plugin: true };
|
|
@@ -185,6 +163,7 @@ export const claudeCode = {
|
|
|
185
163
|
hookLine: ictx.hookLine,
|
|
186
164
|
report: ictx.report,
|
|
187
165
|
progress: ictx.progress,
|
|
166
|
+
...(ctx.permissionCards === undefined ? {} : { permissionCards: ctx.permissionCards }),
|
|
188
167
|
}),
|
|
189
168
|
next: (ictx) => {
|
|
190
169
|
if (mode === 'oauth') {
|
|
@@ -223,7 +202,19 @@ export const claudeCode = {
|
|
|
223
202
|
return claudeStatus(io, ctx);
|
|
224
203
|
},
|
|
225
204
|
};
|
|
226
|
-
|
|
205
|
+
function permissionLine(io, claude, manifest, helper, flag) {
|
|
206
|
+
const label = 'Permission prompts:';
|
|
207
|
+
if (claude.permission)
|
|
208
|
+
return ` ok ${label} a card when Claude Code waits for your OK`;
|
|
209
|
+
if (manifest?.permissionCards === false)
|
|
210
|
+
return ` ${label} off, as you chose; for a card when Claude Code waits for your OK, run npx pendingyou init --permission-cards${flag}`;
|
|
211
|
+
const blocker = claude.claude
|
|
212
|
+
? permissionBlocker(io, claude.claude, helper ? 'helper' : 'oauth')
|
|
213
|
+
: null;
|
|
214
|
+
return blocker
|
|
215
|
+
? ` ${label} no card when Claude Code waits for your OK: ${blocker}`
|
|
216
|
+
: ` missing ${label} no card when Claude Code waits for your OK; run npx pendingyou@latest init${flag}`;
|
|
217
|
+
}
|
|
227
218
|
async function claudeStatus(io, ctx) {
|
|
228
219
|
const { origin, signIn } = ctx;
|
|
229
220
|
const [claude, here, manifest] = await Promise.all([
|
|
@@ -234,8 +225,6 @@ async function claudeStatus(io, ctx) {
|
|
|
234
225
|
const credential = await readCredential(io, origin);
|
|
235
226
|
const flag = originArgs(origin);
|
|
236
227
|
const mcp = claude.mcp;
|
|
237
|
-
// Through the helper (this computer's own sign-in): Claude Code's server runs ours, init set it up so, or nothing is
|
|
238
|
-
// set up yet and init would (everywhere but Windows).
|
|
239
228
|
const helped = usesHelper(mcp, origin);
|
|
240
229
|
const helper = io.platform !== 'win32' &&
|
|
241
230
|
(helped ||
|
|
@@ -249,7 +238,6 @@ async function claudeStatus(io, ctx) {
|
|
|
249
238
|
const url = `${origin}/mcp`;
|
|
250
239
|
const plugin = claude.plugins.find((id) => siteOf(PLUGIN_ORIGINS[id] ?? '') === siteOf(origin));
|
|
251
240
|
const sameServer = mcp !== false && mcp !== 'unknown' && (!mcp.url || mcp.url.replace(/\/+$/, '') === url);
|
|
252
|
-
// The plugin's server is Claude Code's when nobody added one by hand.
|
|
253
241
|
const pluginServer = mcp === false && Boolean(plugin);
|
|
254
242
|
const mcpOk = helper ? sameServer && helped : sameServer || pluginServer;
|
|
255
243
|
const mcpText = mcp === 'unknown'
|
|
@@ -269,7 +257,6 @@ async function claudeStatus(io, ctx) {
|
|
|
269
257
|
: `pendingyou (${mcp.url}) signs in through a browser${why ? ', which can’t finish on this computer' : ''}; run npx pendingyou init${flag}`;
|
|
270
258
|
const form = claude.form;
|
|
271
259
|
const hooksRun = form?.runs ?? true;
|
|
272
|
-
// The wake mod (0.10.0): Claude Code 2.1.287 or later loads it from the plugin or from CLAUDE_CODE_PLUGIN_DIRS.
|
|
273
260
|
const mod = claude.mod;
|
|
274
261
|
const modOk = mod.loads && (mod.plugin || mod.present);
|
|
275
262
|
const modText = !claude.claude
|
|
@@ -293,9 +280,7 @@ async function claudeStatus(io, ctx) {
|
|
|
293
280
|
: form.form === 'npx'
|
|
294
281
|
? `they run through npx${via}, which is slower; run npx pendingyou@latest init${flag} to run them directly`
|
|
295
282
|
: 'they run pendingyou from PATH or a file of your own';
|
|
296
|
-
// login makes a connection only once Claude Code's server signs in through the helper; init does both.
|
|
297
283
|
const fix = `npx pendingyou ${helped ? 'login' : 'init'}${flag}`;
|
|
298
|
-
// The connection's name as Pending You has it, so status never drifts from it; else this computer's own record.
|
|
299
284
|
const title = signIn.state === 'ok' && signIn.for
|
|
300
285
|
? connectionTitle(signIn.for)
|
|
301
286
|
: `${NAME} on ${(await connectionMachine(io, origin)) ?? (machineOf(io.host) || 'this computer')}`;
|
|
@@ -320,7 +305,6 @@ async function claudeStatus(io, ctx) {
|
|
|
320
305
|
: signIn.state === 'none'
|
|
321
306
|
? `not signed in; run npx pendingyou login${flag}`
|
|
322
307
|
: 'Pending You couldn’t be reached';
|
|
323
|
-
// The helper as Claude Code runs it at its barest: found Node, and runs pendingyou's copy.
|
|
324
308
|
const check = helper ? await checkHelper(io, origin) : null;
|
|
325
309
|
const skillOk = claude.skill || Boolean(plugin);
|
|
326
310
|
const skillText = claude.skill
|
|
@@ -338,18 +322,14 @@ async function claudeStatus(io, ctx) {
|
|
|
338
322
|
? [
|
|
339
323
|
` ok Plugin: ${plugin} is enabled${pluginServer ? '' : mcp !== false && mcp !== 'unknown' ? '; Claude Code uses the server added by hand instead of its own' : ''}`,
|
|
340
324
|
]
|
|
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}
|
|
342
|
-
// How setup stands on Pending You (from servers that say): never a reason it isn't Ready.
|
|
325
|
+
: []), ` ${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));
|
|
343
326
|
const setup = setupStatus(NAME, signIn.state === 'ok' ? setupOf(signIn.for) : null);
|
|
344
327
|
if (setup)
|
|
345
328
|
lines.push(` ${setup === 'finished' ? 'ok ' : ' '} Setup: ${setup}`);
|
|
346
|
-
// A folder's own server (local or project scope) wins over the user one in Claude Code: say when it's elsewhere. (The
|
|
347
|
-
// plugin's server counts only when no server was added by hand, and is production's.)
|
|
348
329
|
if (here && (here.scope === 'local' || here.scope === 'project') && differs(here, origin))
|
|
349
330
|
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`);
|
|
350
331
|
const user = mcp !== false && mcp !== 'unknown' && mcp.url ? { url: mcp.url, scope: 'user' } : null;
|
|
351
332
|
const other = differs(here, origin) ? here : differs(user, origin) ? user : null;
|
|
352
|
-
// Through the helper, Claude Code reaches Pending You only with the helper, the connection and the server together.
|
|
353
333
|
const reaches = !helper || (signOk && mcpOk && check?.ok === true);
|
|
354
334
|
const ready = reaches && signIn.state === 'ok' && claude.hooks && hooksRun && !other;
|
|
355
335
|
if (other)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const APP_NAMES = ['ChatGPT.app', 'Codex.app'];
|
|
4
|
+
export function appCodexPaths(home, system = '/Applications') {
|
|
5
|
+
return [system, join(home, 'Applications')].flatMap((root) => APP_NAMES.flatMap((name) => {
|
|
6
|
+
const cli = join(root, name, 'Contents', 'Resources', 'codex-cli');
|
|
7
|
+
return [join(cli, 'bin', 'codex'), join(cli, 'CodexCLI.app', 'Contents', 'MacOS', 'codex')];
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
const versionOf = (stdout) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? 'installed';
|
|
11
|
+
const executable = (path) => stat(path).then((info) => info.isFile() && (info.mode & 0o111) !== 0, () => false);
|
|
12
|
+
export async function findCodex(io, paths = appCodexPaths(io.home, io.env.PENDINGYOU_APPLICATIONS_DIR || undefined)) {
|
|
13
|
+
const onPath = await io.run('codex', ['--version'], 20_000);
|
|
14
|
+
if (onPath.code === 0)
|
|
15
|
+
return { command: 'codex', version: versionOf(onPath.stdout), app: false };
|
|
16
|
+
if (io.platform !== 'darwin')
|
|
17
|
+
return null;
|
|
18
|
+
for (const path of paths) {
|
|
19
|
+
if (!(await executable(path)))
|
|
20
|
+
continue;
|
|
21
|
+
const result = await io.run(path, ['--version'], 20_000);
|
|
22
|
+
if (result.code === 0)
|
|
23
|
+
return { command: path, version: versionOf(result.stdout), app: true };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|