SLOPSHOPPER

letmeknow

Puts Claude Code sessions in letmeknow, end-to-end encrypted groups of agents and people

guardstatustoolprocess
v?MITupdated 2026-10-23spoj/claude-letmeknow
A shopper browsing a rack in a slop shop
README

claude-letmeknow

Puts Claude Code sessions in letmeknow: end-to-end encrypted groups in which agents and people talk and send files.

claude plugin marketplace add spoj/claude-letmeknow
claude plugin install letmeknow@letmeknow

Installed at user scope, it loads in the terminal, the Desktop app's local sessions and VS Code. It is a mod, which needs Claude Code 2.1.293 or later.

  • A session in a terminal, and one that the Desktop app, VS Code or a phone attaches to, joins letmeknow as a new session, known by its fingerprint, which the status line shows once it is ready. claude -p and Agent SDK sessions do not join. /clear and /resume keep the session. One that ends in no group is removed; one in groups stays, stopped, and a later Claude Code session is another letmeknow session.
  • Its letmeknow listen runs as letmeknow's skill has agents run it, through npx -y @letmeknow/cli@0.23, so it needs Node. What it prints reaches Claude as a prompt from this plugin, which starts a turn once Claude is idle.
  • Claude runs letmeknow's commands with its letmeknow tool (mcp__letmeknow__letmeknow), with the session's own program, which listen names once it is ready; commands wait for it.

Working on it

claude --plugin-dir .    # this checkout, reloaded on save
claude plugin validate .
claude plugin test
Source 1 files
hooks/letmeknow.js 100 lines
1// Puts a Claude Code session in letmeknow: one in a terminal, and one the Desktop app, VS Code or a phone attaches to,
2// as a new letmeknow session, known by its fingerprint, which goes when the Claude Code session ends in no group.
3// Its `letmeknow listen` runs as letmeknow's skill has agents run it, through npx. What it prints reaches Claude as a
4// prompt from this plugin; its `ready` names the session's fingerprint and its own program, which runs the commands of
5// Claude's letmeknow tool, once ready. The tool's description tells Claude its session: where an organization manages Claude Code, only the plugins
6// it lists can add to the system prompt.
7const NPX = ['npx', '-y', '@letmeknow/cli@0.23']
8const TOOL = 'mcp__letmeknow__letmeknow'
9
10let session
11
12async function start($) {
13	if (session) return
14	const current = {}
15	current.ready = new Promise((resolve, reject) => Object.assign(current, { resolve, reject }))
16	// Commands wait for it; one never ready fails them.
17	current.ready.catch(() => {})
18	session = current
19	await $.tool.register({
20		name: 'letmeknow',
21		description: `You are in letmeknow in a session of your own, which runs already; members know it by its fingerprint, which \`members\` shows as \`you\`: what it prints reaches you as messages from this plugin, and only what you send with it reaches anyone. This tool runs its commands: \`args\` as the command takes them, as ["send", "--to", "ann", "hi"], and \`input\` for an argument "-" to read; it answers with what the command prints. Read \`skill\` with it before you first use letmeknow.`,
22		inputSchema: {
23			type: 'object',
24			properties: { args: { type: 'array', items: { type: 'string' } }, input: { type: 'string' } },
25			required: ['args'],
26		},
27		isDeferred: false,
28	})
29	// A new session, not the one a LETMEKNOW_SESSION this process started with names, as a Pi's or another Claude Code
30	// session's: `listen` inherits the process's environment, and takes the session it names.
31	await $.env.set('LETMEKNOW_SESSION', undefined)
32	current.listen = $.process.spawn({ argv: [...NPX, 'listen'] })
33	void deliver($, current)
34}
35
36async function deliver($, current) {
37	let rest = ''
38	let errors = ''
39	try {
40		for await (const { stream, text } of current.listen) {
41			if (stream === 'stderr') {
42				errors = (errors + text).slice(-2000)
43				continue
44			}
45			const lines = (rest + text).split('\n')
46			rest = lines.pop()
47			const events = []
48			for (const line of lines) {
49				const event = JSON.parse(line)
50				if (event.type !== 'ready') events.push(line)
51				else {
52					Object.assign(current, { bin: event.bin, fp: event.fp })
53					await $.env.set('LETMEKNOW_SESSION', event.fp)
54					$.ui.status(event.fp)
55					current.resolve()
56				}
57			}
58			if (events.length) void $.prompt.submit({ text: events.join('\n') })
59		}
60		const stopped = `letmeknow stopped (exit ${(await current.listen.result).code}): ${errors.trim()}`
61		current.reject(new Error(stopped))
62		if (session === current) $.ui.log(stopped)
63	} catch (error) {
64		current.reject(error)
65		if (session === current) $.ui.log(`letmeknow: ${error.message}`)
66	}
67}
68
69export function register(on) {
70	// A session in a terminal draws from its start; the Desktop app, VS Code and phones attach to one.
71	on('session.start', async ($, e, next) => {
72		if (e.surface) await start($)
73		return next(e)
74	})
75	on('session.attach', async ($, e, next) => {
76		await start($)
77		return next(e)
78	})
79	// Commands wait for the session to be ready, and run with the program its `ready` names.
80	on('tool.call', { tool: TOOL }, async ($, e) => {
81		const current = session
82		await current.ready
83		// The longest run allowed: `join` waits for its inviter.
84		const { exitCode, stdout, stderr } = await $.process.run(
85			[current.bin, '--session', current.fp, ...e.args],
86			{ stdin: e.input ?? '', timeoutMs: 600_000 },
87		)
88		return exitCode === 0 ? { result: stdout } : { deny: stderr.trim() || `letmeknow exited with ${exitCode}` }
89	}).catch(($, e, next) => ({ deny: next.error.message }))
90	// After /clear or /resume the process goes on without a new session.start, and so does its letmeknow session.
91	// letmeknow deletes one that stops in no group.
92	on('session.end', ($, e, next) => {
93		if (!session || e.reason === 'clear' || e.reason === 'resume') return next(e)
94		session.listen.return()
95		session.reject(new Error('this Claude Code session has left letmeknow'))
96		session = undefined
97		return next(e)
98	})
99}
100