@webinson/peer 0.4.0 → 0.4.1

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 CHANGED
@@ -16,7 +16,8 @@ In a Claude Code thread:
16
16
 
17
17
  - `/peer` shares this thread in your workspace.
18
18
  - `/peer <message>` shares it and passes the message on to the agent as your prompt.
19
- - `/peer stop` ends sharing. Ending the session ends it too.
19
+ - `/peer stop` ends sharing. Quitting or restarting Claude Code only pauses it: the thread shows as stopped and
20
+ is shared again when the session is resumed. `/clear` and signing out of Claude Code end it.
20
21
  - A shared thread's title starts with `● Peer · `, so it stands out in Claude Code's session list; the mark comes
21
22
  back if Claude Code renames the thread, and `/peer stop` takes it off.
22
23
  - `/peer-list` lists the shared threads by repository, then by person, with each thread's branch and
@@ -42,7 +43,8 @@ branch name, say) and is data, not instructions. An agent that reads it must not
42
43
  - **active**: the thread reported in the last 15 minutes, working or waiting for its person.
43
44
  - **silent**: nothing for 15 minutes. The session may have crashed or the laptop closed. An agent
44
45
  reports only around its turns, so an open thread nobody uses goes silent too.
45
- - **stopped**: the session ended, `/peer stop` was typed, or the thread was silent for 2 hours.
46
+ - **stopped**: the session ended or closed (until it is resumed), `/peer stop` was typed, or the thread was silent
47
+ for 2 hours.
46
48
  Stopped threads stay in the list for 8 hours.
47
49
 
48
50
  ## How it works
package/bin/peer.mjs CHANGED
@@ -278,14 +278,29 @@ async function hook(event) {
278
278
  if (sessionTitle) answer({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", sessionTitle } });
279
279
  return;
280
280
  }
281
+ if (event === "start") forgetOldThreads();
281
282
  if (!shared) return;
282
283
  if (event === "stop") await reportQuietly(session, "idle", input.cwd);
284
+ // A thread comes back with its session: a restart, a quit or /exit leaves it shared, and resuming it
285
+ // reports it again. /clear and signing out of Claude Code end it for good.
286
+ if (event === "start" && input.source === "resume") await reportQuietly(session, "idle", input.cwd);
283
287
  if (event === "end") {
284
288
  await reportQuietly(session, "stopped", input.cwd);
285
- fs.rmSync(marker, { force: true });
289
+ if (input.reason === "clear" || input.reason === "logout") fs.rmSync(marker, { force: true });
286
290
  }
287
291
  }
288
292
 
293
+ // Threads not resumed for a month are forgotten, so a session resumed much later starts unshared.
294
+ const FORGET_AFTER_MS = 30 * 24 * 60 * 60 * 1000;
295
+ function forgetOldThreads() {
296
+ try {
297
+ for (const name of fs.readdirSync(SESSIONS)) {
298
+ const file = path.join(SESSIONS, name);
299
+ if (Date.now() - fs.statSync(file).mtimeMs > FORGET_AFTER_MS) fs.rmSync(file, { force: true });
300
+ }
301
+ } catch {}
302
+ }
303
+
289
304
  // A turn never waits on or fails because of the hub: a report that does not go through is dropped.
290
305
  async function reportQuietly(session, state, cwd) {
291
306
  const saved = config();
@@ -346,7 +361,7 @@ function install() {
346
361
  }
347
362
  const settings = withoutOurHooks(readJson(SETTINGS) ?? {});
348
363
  settings.hooks ??= {};
349
- const events = [["UserPromptSubmit", "prompt"], ["Stop", "stop"], ["SessionEnd", "end"]];
364
+ const events = [["UserPromptSubmit", "prompt"], ["Stop", "stop"], ["SessionEnd", "end"], ["SessionStart", "start"]];
350
365
  // Team memory hooks only once this computer is connected to a team server (peer team connect).
351
366
  if (fs.existsSync(TEAM_FILE)) events.push(["UserPromptSubmit", "team-prompt"], ["SessionStart", "team-start"]);
352
367
  for (const [event, name] of events) {
@@ -466,8 +481,10 @@ async function doctor() {
466
481
  for (const [event, groups] of Object.entries(settings?.hooks ?? {})) {
467
482
  for (const group of groups) for (const h of group.hooks ?? []) if (String(h.command ?? "").includes(HOOK_TAG)) ours.push({ event, ...h });
468
483
  }
469
- const events = ["UserPromptSubmit", "Stop", "SessionEnd"];
470
- const missing = events.filter((e) => !ours.some((h) => h.event === e));
484
+ const events = { prompt: "UserPromptSubmit", stop: "Stop", end: "SessionEnd", start: "SessionStart" };
485
+ const missing = Object.entries(events)
486
+ .filter(([name, event]) => !ours.some((h) => h.event === event && String(h.command).includes(`hook ${name} `)))
487
+ .map(([, event]) => event);
471
488
  if (!settings) bad(`no ${SETTINGS}: run \`peer install\``);
472
489
  else if (missing.length) bad(`${SETTINGS} lacks Peer's ${missing.join(", ")} hook: run \`peer install\``);
473
490
  else ok(`hooks in ${SETTINGS}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webinson/peer",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Share chosen Claude Code threads with your team: who has agents running and on what.",
5
5
  "license": "MIT",
6
6
  "type": "module",