@webinson/peer 0.4.27 → 0.4.29
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/README.md +15 -6
- package/bin/codex-thread-mark.mjs +16 -7
- package/bin/peer.mjs +3 -3
- package/mod/codex/peer-thread-mark/.codex-plugin/plugin.json +2 -1
- package/mod/codex/peer-thread-mark/skills/peer/SKILL.md +16 -0
- package/mod/codex/peer-thread-mark/skills/peer-all/SKILL.md +8 -0
- package/mod/codex/peer-thread-mark/skills/peer-list/SKILL.md +8 -0
- package/mod/codex/peer-thread-mark/skills/peer-thread/SKILL.md +8 -0
- package/mod/peer-thread-mark/.claude-plugin/plugin.json +1 -1
- package/mod/peer-thread-mark/hooks/register.tsx +2 -2
- package/mod/peer-thread-mark/tests/collision-cases.mjs +6 -6
- package/mod/peer-thread-mark/tests/collision.test.tsx +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,8 +107,17 @@ peer login --codex you@company.com
|
|
|
107
107
|
If you already signed in to Peer, use `peer install --codex` in place of the login
|
|
108
108
|
command. Review and trust the Peer Thread Mark hooks in Codex's `/hooks` menu.
|
|
109
109
|
|
|
110
|
-
In
|
|
111
|
-
|
|
110
|
+
In the desktop Codex composer, type `/` and select one of the skills installed with Peer Thread Mark.
|
|
111
|
+
|
|
112
|
+
- `/peer` shares this chat. Append a task to share the chat and continue with that task.
|
|
113
|
+
Append `elephant` or `jellyfish` to select Peer's memory mode, or `stop` to stop sharing.
|
|
114
|
+
- `/peer-list` shows the threads shared in your Peer workspace.
|
|
115
|
+
- `/peer-thread` shows what the current chat saved to your personal OpenViking memory.
|
|
116
|
+
- `/peer-all [words] [here] [--newest | --oldest] [page N]` browses or searches that memory.
|
|
117
|
+
|
|
118
|
+
You can also send `peer: start [task | elephant | jellyfish]`, `peer: stop`, `peer: list`, `peer: thread`, or
|
|
119
|
+
`peer: all [words] [here] [--newest | --oldest] [page N]` as plain text. The hook handles sharing and
|
|
120
|
+
displays lists and memories locally.
|
|
112
121
|
The plugin uses the [Codex hook API](https://learn.chatgpt.com/docs/hooks) and lives under
|
|
113
122
|
[`mod/codex`](mod/codex) in its own marketplace.
|
|
114
123
|
|
|
@@ -146,16 +155,16 @@ follow their own settings. `peer uninstall --codex` removes the plugin from Code
|
|
|
146
155
|
## Same file: who else was on an edited file
|
|
147
156
|
|
|
148
157
|
After each edit in a shared thread, Peer sends the edited paths to the Hub and learns who else is on them: an agent
|
|
149
|
-
that claimed the file in the last 3 minutes, or
|
|
150
|
-
those while
|
|
158
|
+
that claimed the file in the last 3 minutes, or another person's session that changed the file in its work (the Peer
|
|
159
|
+
app keeps reporting those files while the session lasts, committed or not). In Claude Code the thread mark runs `peer collide` after each `Edit`, `Write`, `MultiEdit` or
|
|
151
160
|
`NotebookEdit`; in Codex the Peer Thread Mark plugin checks after each `apply_patch`.
|
|
152
161
|
|
|
153
162
|
The first time a set of people meets on a file, Claude Code draws a line under the edit (and under a folded group of
|
|
154
163
|
tool calls) with a **Same file** badge:
|
|
155
164
|
|
|
156
165
|
- `bob@… was on it 2 min ago`: another person's agent claimed the file;
|
|
157
|
-
- `bob@…
|
|
158
|
-
- `Your other thread was on it just now`, dimmed: another session of yours.
|
|
166
|
+
- `bob@… changed it`: a session of another person's changed the file in its work (it may be committed already);
|
|
167
|
+
- `Your other thread was on it just now`, dimmed: another session of yours. Files you changed yourself are not shown.
|
|
159
168
|
|
|
160
169
|
For another person, the agent also gets one sentence with the tool result, in Claude Code and in Codex, fenced as data
|
|
161
170
|
and built by the CLI from the Hub's fields only:
|
|
@@ -9,6 +9,8 @@ const local = (reason) => ({ decision: "block", reason });
|
|
|
9
9
|
const MARKETPLACE = "webinson-peer-codex";
|
|
10
10
|
const PLUGIN = `peer-thread-mark@${MARKETPLACE}`;
|
|
11
11
|
const setupFile = (peer) => path.join(peer.home, "codex-cli.json");
|
|
12
|
+
const SKILL_MENTION = /^\$peer-thread-mark:(peer(?:-all|-list|-thread)?)(?=\s|$)/;
|
|
13
|
+
const SKILL_LINK = /^\[(\$peer-thread-mark:[\w-]+)(?:\?[^\]\r\n]*)?\]\((?:\\.|[^\\)\r\n])+\)(?=\s|$)/;
|
|
12
14
|
|
|
13
15
|
export function installCodex(peer, { client = false } = {}) {
|
|
14
16
|
if (peer.cli.includes(`${path.sep}_npx${path.sep}`)) {
|
|
@@ -264,20 +266,24 @@ async function collideHook(input, session, peer) {
|
|
|
264
266
|
|
|
265
267
|
async function promptHook(prompt, id, cwd, peer) {
|
|
266
268
|
const session = `codex-${id}`;
|
|
269
|
+
// The desktop composer can send the namespaced skill as a Markdown link.
|
|
270
|
+
const link = SKILL_LINK.exec(prompt);
|
|
271
|
+
const skill = SKILL_MENTION.exec(link?.[1] ?? prompt);
|
|
272
|
+
const command = skill ? `/${skill[1]}${prompt.slice((link ?? skill)[0].length)}` : prompt;
|
|
267
273
|
// The text form also works in clients that handle slash commands before submitting a turn.
|
|
268
|
-
const all = /^(?:\/peer-all|peer:\s+all)(?:\s+([\s\S]*))?$/.exec(
|
|
274
|
+
const all = /^(?:\/peer-all|peer:\s+all)(?:\s+([\s\S]*))?$/.exec(command);
|
|
269
275
|
if (all) {
|
|
270
276
|
const query = peer.memoryQuery((all[1] ?? "").split(/\s+/).filter(Boolean), cwd || process.cwd());
|
|
271
277
|
return local(await peer.memoryText(query).catch((error) => `Peer: your memories did not load (${error.message})`));
|
|
272
278
|
}
|
|
273
|
-
if (/^(?:\/peer-list|peer:\s+list)$/.test(
|
|
279
|
+
if (/^(?:\/peer-list|peer:\s+list)$/.test(command)) {
|
|
274
280
|
try {
|
|
275
281
|
return local(await peer.listText(peer.signedIn()));
|
|
276
282
|
} catch (error) {
|
|
277
283
|
return local(`Peer: ${error.message}`);
|
|
278
284
|
}
|
|
279
285
|
}
|
|
280
|
-
if (/^(?:\/peer-thread|peer:\s+thread)$/.test(
|
|
286
|
+
if (/^(?:\/peer-thread|peer:\s+thread)$/.test(command)) {
|
|
281
287
|
try {
|
|
282
288
|
return local(peer.threadMemoryText(await peer.threadMemories(id, true)));
|
|
283
289
|
} catch (error) {
|
|
@@ -285,15 +291,18 @@ async function promptHook(prompt, id, cwd, peer) {
|
|
|
285
291
|
}
|
|
286
292
|
}
|
|
287
293
|
|
|
288
|
-
const share = /^(?:\/peer|peer:\s+start)(?:\s+([\s\S]*))?$/.exec(
|
|
294
|
+
const share = /^(?:\/peer|peer:\s+start)(?:\s+([\s\S]*))?$/.exec(command);
|
|
289
295
|
const message = share?.[1]?.trim() ?? "";
|
|
290
|
-
const stop = (share && message === "stop") || /^peer:\s+stop$/.test(
|
|
296
|
+
const stop = (share && message === "stop") || /^peer:\s+stop$/.test(command);
|
|
291
297
|
if (share || stop) {
|
|
292
298
|
try {
|
|
293
299
|
if (stop) return local(await setMode(peer, id, cwd, "inactive"));
|
|
294
300
|
const mode = modeOf(peer, session);
|
|
295
|
-
const
|
|
296
|
-
|
|
301
|
+
const selectedMode = /^(elephant|jellyfish)$/i.exec(message)?.[1].toLowerCase();
|
|
302
|
+
const hasWork = Boolean(message) && !selectedMode;
|
|
303
|
+
const said = await setMode(peer, id, cwd, selectedMode ?? (mode === "inactive" ? "elephant" : mode), { working: hasWork });
|
|
304
|
+
if (selectedMode) return local(`${said} Peer memory mode: ${selectedMode}.`);
|
|
305
|
+
if (!hasWork) return local(said);
|
|
297
306
|
return {
|
|
298
307
|
hookSpecificOutput: {
|
|
299
308
|
hookEventName: "UserPromptSubmit",
|
package/bin/peer.mjs
CHANGED
|
@@ -1386,8 +1386,8 @@ function minutesAgo(at, now = Date.now()) {
|
|
|
1386
1386
|
return mins === 1 ? "1 minute ago" : `${mins} minutes ago`;
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
|
-
// The people on a path, one entry each: a claim wins over
|
|
1390
|
-
//
|
|
1389
|
+
// The people on a path, one entry each: a claim wins over a file their session only reported changing in its work,
|
|
1390
|
+
// with the claim's own time. Your own changed files are no news to you and are left out.
|
|
1391
1391
|
function peopleOn(holders, me) {
|
|
1392
1392
|
const people = new Map();
|
|
1393
1393
|
for (const holder of holders) {
|
|
@@ -1415,7 +1415,7 @@ function collisionSentence(results, now = Date.now()) {
|
|
|
1415
1415
|
for (const person of result.people.filter((p) => !p.own).slice(0, 3)) {
|
|
1416
1416
|
lines.push(
|
|
1417
1417
|
person.kind === "changed"
|
|
1418
|
-
? `Another person, ${person.email},
|
|
1418
|
+
? `Another person, ${person.email}, changed ${result.path} in their work.`
|
|
1419
1419
|
: `Another person, ${person.email}, had an agent on ${result.path} ${minutesAgo(person.at, now)}.`,
|
|
1420
1420
|
);
|
|
1421
1421
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peer-thread-mark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Share selected Codex threads in Peer and browse their memories.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"skills": "./skills/",
|
|
6
7
|
"hooks": "./hooks/hooks.json",
|
|
7
8
|
"interface": {
|
|
8
9
|
"displayName": "Peer Thread Mark",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: peer
|
|
3
|
+
description: Share the current Codex chat on Peer, optionally with a task or a memory mode, or stop sharing it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Peer
|
|
7
|
+
|
|
8
|
+
The Peer Thread Mark hook handles sharing before this skill reaches the model. If the user invokes it and the hook has supplied a confirmation, continue with the task they appended.
|
|
9
|
+
|
|
10
|
+
If the hook did not confirm the action, direct the user to send one of these messages in the chat they want to share or stop sharing.
|
|
11
|
+
|
|
12
|
+
- `peer: start` shares the chat. Append a task to share it and continue with that task.
|
|
13
|
+
- `peer: start elephant` or `peer: start jellyfish` shares the chat and selects Peer's memory mode.
|
|
14
|
+
- `peer: stop` stops sharing.
|
|
15
|
+
|
|
16
|
+
If the plain-text command also reaches the model, direct the user to `peer doctor` to check the hook.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: peer-all
|
|
3
|
+
description: Browse or search your personal OpenViking memories with Peer, optionally scoped to the current repository.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Peer all
|
|
7
|
+
|
|
8
|
+
The Peer Thread Mark hook searches and displays these memories locally. If the request reaches the model, ask the user to send `peer: all` with their search terms and filters as a separate message. The hook accepts `here`, `--newest`, `--oldest`, and `page N`. If the plain-text command also reaches the model, direct them to `peer doctor`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: peer-list
|
|
3
|
+
description: List the people in your Peer workspace and the agent chats they share.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Peer list
|
|
7
|
+
|
|
8
|
+
The Peer Thread Mark hook shows this list directly in the chat. If the request reaches the model, ask the user to send `peer: list` by itself so the hook can answer. If that also reaches the model, direct them to `peer doctor`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: peer-thread
|
|
3
|
+
description: Show what the current Codex chat has saved to your personal OpenViking memory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Peer thread
|
|
7
|
+
|
|
8
|
+
The Peer Thread Mark hook reads the current chat's memories and displays them locally. If the request reaches the model, ask the user to send `peer: thread` by itself. If that also reaches the model, direct them to `peer doctor`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peer-thread-mark",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.28",
|
|
4
4
|
"description": "Marks a Claude Code thread shared with Peer (/peer) with a band above the prompt, with /peer stop and /peer start; panes for /peer-list, /peer-thread and /peer-all (search your memories)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"types": "./types/index.d.ts"
|
|
@@ -538,8 +538,8 @@ function collisionLine(ui: { Box: (p: never) => RenderNode; Text: (p: never) =>
|
|
|
538
538
|
const [newest] = others
|
|
539
539
|
const who = `${plain(newest!.email.split('@')[0])}@…`
|
|
540
540
|
const rest = others.length > 1 ? ` and ${others.length - 1} others` : ''
|
|
541
|
-
//
|
|
542
|
-
const what = newest!.kind === 'changed' ? '
|
|
541
|
+
// A file a session reported changing carries no age: the report is renewed every few seconds.
|
|
542
|
+
const what = newest!.kind === 'changed' ? 'changed it' : `was on it ${whenAgo(newest!.at, now)}`
|
|
543
543
|
const badge =
|
|
544
544
|
surface === 'terminal' ? (
|
|
545
545
|
<Text backgroundColor={COLLISION_COLOR} color="#ffffff" bold size="caption">{' Same file '}</Text>
|
|
@@ -25,7 +25,7 @@ export default {
|
|
|
25
25
|
"line": "bob@… was on it 2 min ago"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
"name": "another person
|
|
28
|
+
"name": "another person changed the file in their work",
|
|
29
29
|
"holders": [
|
|
30
30
|
{
|
|
31
31
|
"email": "carol@acme.test",
|
|
@@ -41,9 +41,9 @@ export default {
|
|
|
41
41
|
}
|
|
42
42
|
],
|
|
43
43
|
"sentence": [
|
|
44
|
-
"Another person, carol@acme.test,
|
|
44
|
+
"Another person, carol@acme.test, changed src/cart.ts in their work."
|
|
45
45
|
],
|
|
46
|
-
"line": "carol@…
|
|
46
|
+
"line": "carol@… changed it"
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
"name": "one person in several sessions",
|
|
@@ -117,7 +117,7 @@ export default {
|
|
|
117
117
|
"line": "Your other thread was on it just now"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
|
-
"name": "
|
|
120
|
+
"name": "a file you changed yourself",
|
|
121
121
|
"holders": [
|
|
122
122
|
{
|
|
123
123
|
"email": "ana@acme.test",
|
|
@@ -156,10 +156,10 @@ export default {
|
|
|
156
156
|
}
|
|
157
157
|
],
|
|
158
158
|
"sentence": [
|
|
159
|
-
"Another person, carol@acme.test,
|
|
159
|
+
"Another person, carol@acme.test, changed src/cart.ts in their work.",
|
|
160
160
|
"Another person, bob@acme.test, had an agent on src/cart.ts 2 minutes ago."
|
|
161
161
|
],
|
|
162
|
-
"line": "carol@…
|
|
162
|
+
"line": "carol@… changed it and 1 others"
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
"name": "a Hub before kinds",
|
|
@@ -162,7 +162,7 @@ test('after a timeout the turn makes no more calls, and an old CLI draws nothing
|
|
|
162
162
|
|
|
163
163
|
import CASES from './collision-cases.mjs'
|
|
164
164
|
|
|
165
|
-
test('the line under an edit follows the shared cases:
|
|
165
|
+
test('the line under an edit follows the shared cases: a changed file carries no age, your own are not drawn', async ($, on) => {
|
|
166
166
|
const state = setUp(on)
|
|
167
167
|
await $.classic.UserPromptSubmit({ prompt: 'go' } as never)
|
|
168
168
|
let n = 0
|