pendingyou 0.3.0 → 0.4.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 +12 -0
- package/README.md +6 -2
- package/dist/format.js +1 -1
- package/dist/pickup.js +47 -10
- package/dist/stopcheck.js +5 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- The next-message hook's reminder now names this folder's open cards still waiting on you, so a question you answer
|
|
6
|
+
in chat gets its card cancelled: `Pending You: anything you need from your person also goes on a card. Your open
|
|
7
|
+
cards here: "Which DNS host?" (req_…); … +2 more. If this message answers or settles one, cancel_request it with
|
|
8
|
+
answeredHere (or update_request if it changed).` At most 3 cards, newest first, titles cut to 50 characters; the
|
|
9
|
+
plain reminder when none are open. One small request per message while signed in (answers too while a hold is
|
|
10
|
+
outstanding), given up after 1.5 seconds; on any error, or against a server that doesn't list open cards yet, the
|
|
11
|
+
line is the plain reminder.
|
|
12
|
+
- The Stop check no longer asks Claude Code to go on when its message says a card is coming ("you'll get a card to
|
|
13
|
+
publish it once it's merged", "I'll post a card", "I'll send you a card").
|
|
14
|
+
|
|
3
15
|
## 0.3.0
|
|
4
16
|
|
|
5
17
|
- New Stop hook, `pendingyou stopcheck`: when Claude Code ends a turn with a question for you, a step only you can do
|
package/README.md
CHANGED
|
@@ -28,9 +28,13 @@ Deep into a long conversation, Claude Code tends to end a turn with a question f
|
|
|
28
28
|
"Your part:" list, and not put it in Pending You. Two small things catch that while you're signed in:
|
|
29
29
|
|
|
30
30
|
- Each message you send carries one line for Claude Code (about 18 tokens): *Pending You: anything you need from your
|
|
31
|
-
person also goes on a card.*
|
|
31
|
+
person also goes on a card.* While cards Claude Code posted from this folder are still waiting on you, the same line
|
|
32
|
+
names them (at most 3, newest first, about 20 tokens each) and asks it to `cancel_request` one with `answeredHere`
|
|
33
|
+
when your message answers it, so a question you answer in chat doesn't stay in your queue. That costs one small
|
|
34
|
+
request per message, given up after 1.5 seconds.
|
|
32
35
|
- When Claude Code finishes a turn, `pendingyou stopcheck` looks at its last message. If it has something for you and
|
|
33
|
-
nothing was posted, updated or replied to in Pending You that turn (and the message doesn't say it's on a card
|
|
36
|
+
nothing was posted, updated or replied to in Pending You that turn (and the message doesn't say it's on a card, or
|
|
37
|
+
that one is coming),
|
|
34
38
|
Claude Code is asked to go on once (about 31 tokens): post them as cards, or say they don't need one. It never asks
|
|
35
39
|
twice in a row.
|
|
36
40
|
|
package/dist/format.js
CHANGED
|
@@ -5,7 +5,7 @@ const MAX_ANSWER = 600;
|
|
|
5
5
|
const MAX_MESSAGE = 400;
|
|
6
6
|
const MAX_TITLE = 140;
|
|
7
7
|
/** Cuts text to `max` characters on one line, and says so. */
|
|
8
|
-
function clip(text, max) {
|
|
8
|
+
export function clip(text, max) {
|
|
9
9
|
const flat = text.replace(/\s+/g, ' ').trim();
|
|
10
10
|
const chars = [...flat];
|
|
11
11
|
if (chars.length <= max)
|
package/dist/pickup.js
CHANGED
|
@@ -3,19 +3,47 @@
|
|
|
3
3
|
// - `pendingyou pickup` (SessionStart): a new or resumed session picks up answers that arrived while nothing was
|
|
4
4
|
// running, for questions asked from this folder.
|
|
5
5
|
// - `pendingyou handoff` (UserPromptSubmit): your next message brings a waiting answer with it ("You answered in
|
|
6
|
-
// Pending You: …").
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Pending You: …"). While signed in, it also adds one short line (REMINDER) to every message, so an assistant
|
|
7
|
+
// deep in a long conversation still puts what it needs from the person on a card, and that line names this folder's
|
|
8
|
+
// cards still waiting on the person (openLine), so one they answer in chat gets cancelled with answeredHere. That's
|
|
9
|
+
// one small request per message (the open cards, and answers too while a hold from this computer is outstanding),
|
|
10
|
+
// time-boxed short; on any error, or from a server too old to list open cards, the line is the plain reminder.
|
|
9
11
|
//
|
|
10
12
|
// Whatever they print, Claude Code adds to the conversation. A hook must never get in the way: each is time-boxed,
|
|
11
13
|
// prints nothing when there's nothing new, never repeats an answer it handed over, and always exits 0.
|
|
12
14
|
import { getJson, SignInNeeded } from "./api.js";
|
|
13
15
|
import { readCredential } from "./credentials.js";
|
|
14
|
-
import { describe, isClosed } from "./format.js";
|
|
16
|
+
import { clip, describe, isClosed } from "./format.js";
|
|
15
17
|
import { markHanded, momentOf, waitingOn, wasHanded } from "./state.js";
|
|
16
|
-
const TIME_BOX_MS = { pickup: 5000, handoff: 2500 };
|
|
18
|
+
const TIME_BOX_MS = { pickup: 5000, handoff: 2500, open: 1500 };
|
|
17
19
|
/** What every message carries while signed in (about 18 tokens). */
|
|
18
20
|
export const REMINDER = 'Pending You: anything you need from your person also goes on a card.';
|
|
21
|
+
/** The most open cards the line names, and how much of each title. */
|
|
22
|
+
export const OPEN_SHOWN = 3;
|
|
23
|
+
export const OPEN_TITLE = 50;
|
|
24
|
+
/**
|
|
25
|
+
* The reminder, naming this folder's open cards still waiting on the person when there are any (about 45 tokens plus
|
|
26
|
+
* 20 a card): `… Your open cards here: "Which DNS host?" (req_…); … +2 more. If this message answers …`.
|
|
27
|
+
*/
|
|
28
|
+
export function openLine(open) {
|
|
29
|
+
if (typeof open !== 'object' || open === null)
|
|
30
|
+
return REMINDER;
|
|
31
|
+
const { requests, total } = open;
|
|
32
|
+
if (!Array.isArray(requests))
|
|
33
|
+
return REMINDER;
|
|
34
|
+
const cards = requests
|
|
35
|
+
.filter((card) => typeof card?.requestId === 'string' &&
|
|
36
|
+
/^req_[A-Za-z0-9-]{1,40}$/.test(card.requestId) &&
|
|
37
|
+
typeof card.title === 'string')
|
|
38
|
+
.slice(0, OPEN_SHOWN);
|
|
39
|
+
if (cards.length === 0)
|
|
40
|
+
return REMINDER;
|
|
41
|
+
const more = typeof total === 'number' && total > cards.length ? total - cards.length : 0;
|
|
42
|
+
const named = cards
|
|
43
|
+
.map((card) => `"${clip(card.title.replaceAll('"', "'"), OPEN_TITLE).text}" (${card.requestId})`)
|
|
44
|
+
.join('; ');
|
|
45
|
+
return `${REMINDER} Your open cards here: ${named}${more ? ` +${more} more` : ''}. If this message answers or settles one, cancel_request it with answeredHere (or update_request if it changed).`;
|
|
46
|
+
}
|
|
19
47
|
/** The folder Claude Code says the session is in (the hook's stdin), or where this runs. */
|
|
20
48
|
async function sessionFolder(io) {
|
|
21
49
|
const text = await io.readStdin(500).catch(() => '');
|
|
@@ -38,19 +66,28 @@ export function folderForms(cwd, home) {
|
|
|
38
66
|
export async function pickup(io, options) {
|
|
39
67
|
const { origin, mode } = options;
|
|
40
68
|
let remind = false;
|
|
69
|
+
let line = REMINDER;
|
|
41
70
|
try {
|
|
42
71
|
const cwd = await sessionFolder(io);
|
|
72
|
+
// The hand-off hears answers only while a hold from this computer is outstanding; it always asks for open cards.
|
|
73
|
+
let answers = true;
|
|
43
74
|
if (mode === 'handoff') {
|
|
44
75
|
remind = (await readCredential(io, origin)) !== null;
|
|
45
|
-
if (
|
|
76
|
+
if (!remind)
|
|
46
77
|
return 0;
|
|
78
|
+
answers = (await waitingOn(io, origin)).length > 0;
|
|
47
79
|
}
|
|
48
80
|
const query = new URLSearchParams([
|
|
49
81
|
...folderForms(cwd, io.home).map((form) => ['cwd', form]),
|
|
50
|
-
['limit', '5'],
|
|
82
|
+
['limit', answers ? '5' : '0'],
|
|
83
|
+
...(mode === 'handoff' ? [['open', String(OPEN_SHOWN)]] : []),
|
|
51
84
|
]);
|
|
52
|
-
const { status, body } = await getJson(io, origin, `/mcp/cli/answers?${query}`, TIME_BOX_MS[mode], io.signal);
|
|
53
|
-
if (status !== 200
|
|
85
|
+
const { status, body } = await getJson(io, origin, `/mcp/cli/answers?${query}`, answers ? TIME_BOX_MS[mode] : TIME_BOX_MS.open, io.signal);
|
|
86
|
+
if (status !== 200)
|
|
87
|
+
return 0;
|
|
88
|
+
if (mode === 'handoff')
|
|
89
|
+
line = openLine(body.open);
|
|
90
|
+
if (!answers || !Array.isArray(body.requests))
|
|
54
91
|
return 0;
|
|
55
92
|
const fresh = [];
|
|
56
93
|
for (const heard of body.requests)
|
|
@@ -83,6 +120,6 @@ export async function pickup(io, options) {
|
|
|
83
120
|
}
|
|
84
121
|
finally {
|
|
85
122
|
if (remind)
|
|
86
|
-
io.out(`${
|
|
123
|
+
io.out(`${line}\n`);
|
|
87
124
|
}
|
|
88
125
|
}
|
package/dist/stopcheck.js
CHANGED
|
@@ -14,8 +14,11 @@ const CHUNK = 64 * 1024;
|
|
|
14
14
|
export const STOP_REASON = 'Items for your person are in chat but not in Pending You: post them as cards (ask in both places), or say they don’t need one.';
|
|
15
15
|
/** Tools that put something in front of the person: Pending You's, under any MCP server name. */
|
|
16
16
|
const POSTING_TOOL = /^mcp__.+__(post_request|update_request|reply_in_thread)$/;
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Text that says the items are already in Pending You, or will be: "you'll get a card to publish it once it's merged"
|
|
19
|
+
* announces a card to come, not a step left in chat.
|
|
20
|
+
*/
|
|
21
|
+
const SAYS_POSTED = /\b(posted|put|added|raised|filed)\b[^.\n]{0,60}\b(cards?|pending ?you)\b|\bon (a|the|its|their|your|that|this) cards?\b|\b(in|to|through|via|on) pending ?you\b|\bpending ?you cards?\b|\b(i|we|you)(?:(?:'|’)ll| will)\s+(?:\w+\s+)?(post|send|put|add|raise|file|get|see|find)\b[^.\n]{0,40}\bcards?\b/i;
|
|
19
22
|
/** Steps only the person can take, said to them. */
|
|
20
23
|
const STEPS_FOR_THEM = /\b(you(?:'|’)ll need to|you will need to|you(?:'|’)ll have to|you will have to|your (?:next )?steps?\b|can you|could you|would you mind|please (?:run|confirm|approve|check|add|set|sign|create|open|paste|reply|send|choose|pick|decide|let me know)|in your terminal|only you can|waiting on you|waiting for you|needs? you to|over to you|parked for you|when you(?:'|’)re ready|up to you|your call\b|is yours\b|say the word|let me know (?:which|whether)|tell me (?:which|whether)|needs? your (?:ok|okay|approval|decision|sign-?off|go-ahead))/i;
|
|
21
24
|
/** A heading over a list of things for the person. */
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The command line's version, as published (package.json). A test keeps them the same.
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.4.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Pending You for Claude Code: hear your answers from Pending You in the background, and set Claude Code up in one command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://www.pendingyou.com",
|