klypix-mcp 1.90.1 → 1.92.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/README.md +67 -12
- package/bin/klypix-append.mjs +42 -11
- package/bin/klypix-install.mjs +9 -4
- package/bin/klypix-mcp.mjs +3 -1
- package/bin/klypix-read.mjs +13 -4
- package/bin/klypix-sessions.mjs +109 -0
- package/bin/klypix-worker.mjs +177 -6
- package/bin/klypix-write.mjs +27 -2
- package/package.json +3 -2
- package/src/agent-presence.mjs +429 -2
- package/src/agent-rules.mjs +25 -2
- package/src/app-lease.mjs +188 -0
- package/src/app-tools.mjs +361 -0
- package/src/brain-doctor.mjs +6 -0
- package/src/finding-routing.mjs +6 -1
- package/src/global-brain-hook.mjs +2 -1
- package/src/klypix-core.mjs +270 -53
- package/src/klypix-format.mjs +568 -43
- package/src/mcp-presence.mjs +14 -1
- package/src/mcp-supervisor.mjs +3 -1
package/README.md
CHANGED
|
@@ -119,12 +119,16 @@ npx klypix-mcp conformance
|
|
|
119
119
|
|
|
120
120
|
It runs in a temporary fixture and touches nothing else. It checks tool discovery, task memory,
|
|
121
121
|
truthful peer reporting, overlap surfacing, proactive logging, and in-band delivery of a peer note.
|
|
122
|
-
It verifies 15 required coordination behaviours — not the
|
|
122
|
+
It verifies 15 required coordination behaviours — not the 25 tools, and not the retrieval engine.
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
126
|
## Quick start
|
|
127
127
|
|
|
128
|
+
**You need Node.js 20 or newer** (`node -v` to check). Every way of connecting runs the server with
|
|
129
|
+
`node` — `npx`, the installed bundle, and the entry KLYPIX's Settings buttons write — and none of them
|
|
130
|
+
checks for Node first.
|
|
131
|
+
|
|
128
132
|
Run this **inside your project**:
|
|
129
133
|
|
|
130
134
|
```bash
|
|
@@ -142,7 +146,7 @@ git repo, and then **proves the result** before it exits:
|
|
|
142
146
|
editors Claude Code · Cursor · Codex · Gemini CLI · Antigravity · VS Code
|
|
143
147
|
wired 9 file(s) · 9 updated (skipped 5 for tools you don't have)
|
|
144
148
|
git .klypix merge driver registered
|
|
145
|
-
verified ✓
|
|
149
|
+
verified ✓ 25 tools reachable via .mcp.json (892ms)
|
|
146
150
|
```
|
|
147
151
|
|
|
148
152
|
That last line is the point. MCP config fails **silently** — a wrong entry means the server never
|
|
@@ -315,15 +319,17 @@ behaviour is unverified.
|
|
|
315
319
|
| **Gemini CLI / Antigravity** | MCP config + always-on rules file | `link` | Model must call `brain_sync` | Model must call `brain_note` | For the MCP connection |
|
|
316
320
|
| **Windsurf** | Rules file only | `link` | Reaches the tools through Windsurf's own global MCP config | Model must call `brain_note` | Via its own MCP config |
|
|
317
321
|
| **Aider** | Rules file only (no MCP) | `link` | CLI path: `npx -y -p klypix-mcp klypix-read` | CLI path: `npx -y -p klypix-mcp klypix-append` | — |
|
|
318
|
-
| **Claude Desktop** | One-time manual
|
|
322
|
+
| **Claude Desktop** | One-time config: KLYPIX's Settings button, or a manual edit | KLYPIX app or you | Model must call `brain_sync` | Model must call `brain_note` | For the MCP connection |
|
|
319
323
|
|
|
320
324
|
`install` and `link` are different things and are not interchangeable: `install` sets up the
|
|
321
325
|
machine engine and hooks, then wires supported hosts detected for this project (see *Quick start*).
|
|
322
326
|
`link` is the explicit per-project repair/projection path for all 14 managed files, regardless of
|
|
323
327
|
which hosts are installed.
|
|
324
328
|
|
|
325
|
-
**Claude Desktop** —
|
|
326
|
-
for you
|
|
329
|
+
**Claude Desktop** — in the KLYPIX desktop app, Settings → Project → **Connect Claude Desktop**
|
|
330
|
+
writes this entry for you; or add it to `claude_desktop_config.json` by hand. Either way it needs
|
|
331
|
+
Node.js on the PC, and Claude Desktop must be quit completely (tray icon → Quit) and reopened before
|
|
332
|
+
it lists the tools:
|
|
327
333
|
|
|
328
334
|
```json
|
|
329
335
|
{
|
|
@@ -451,8 +457,9 @@ KLYPIX has identified before that is **not on the lane now** (closed, or quiet)
|
|
|
451
457
|
directed note the moment it next acts — a directed note is kept 7 days, and the sender is told it
|
|
452
458
|
is *queued*, not delivered. That is what stops the human from being the courier between two
|
|
453
459
|
agents: the standing rule every host receives is that a message it would otherwise ask the person
|
|
454
|
-
to relay or paste is sent this way instead. Nothing here starts or wakes a session — the
|
|
455
|
-
waits until a person opens it
|
|
460
|
+
to relay or paste is sent this way instead. Nothing here starts or wakes a session on its own — the
|
|
461
|
+
note waits until a person opens it, and the person decides when that is (see *Reopen on your OK*
|
|
462
|
+
below). A supported
|
|
456
463
|
KLYPIX action offers the note in model-visible context; the next independent supported action
|
|
457
464
|
replays it and records an acknowledgement. That acknowledgement proves only that a later action followed the
|
|
458
465
|
offer — never that a person read it or that an agent acted on it. The note keeps replaying until the
|
|
@@ -463,6 +470,30 @@ instead of silently looking delivered. The send-time audience is fixed, unresolv
|
|
|
463
470
|
fail closed, the core lane is machine-local, a note to every session expires after 24 hours and a
|
|
464
471
|
directed note after 7 days, and notes are never written into the brain.
|
|
465
472
|
|
|
473
|
+
### Reopen on your OK
|
|
474
|
+
|
|
475
|
+
A note to a session that has closed would otherwise wait until someone happens to open that
|
|
476
|
+
session again. When the person wants it handled sooner, the sending agent calls `brain_reopen`, and
|
|
477
|
+
**KLYPIX asks the person** — an in-chat *Reopen / Not now* prompt in apps that support MCP
|
|
478
|
+
elicitation (Claude Code, Codex), otherwise a small native dialog. Only on *Reopen* does KLYPIX
|
|
479
|
+
open that Claude Code or Codex session in a **new, visible terminal**, in the folder it worked in,
|
|
480
|
+
with the host's own resume command (`claude --resume <id>`, `codex resume <id>`); the session
|
|
481
|
+
receives the waiting note at its first action and the sender sees the receipt as usual.
|
|
482
|
+
|
|
483
|
+
- The person decides, never a model: the answer comes from the app's prompt or KLYPIX's own
|
|
484
|
+
dialog, which no agent can click. With neither available, nothing opens and the person is given
|
|
485
|
+
the command to run.
|
|
486
|
+
- Only a session with a note **addressed to it** is reopened — KLYPIX reopens a conversation so a
|
|
487
|
+
note can reach it; it does not start, route or supervise agents.
|
|
488
|
+
- The first prompt the reopened session gets is fixed text that tells it to collect the note; the
|
|
489
|
+
note itself arrives through the labelled message channel as information from another session,
|
|
490
|
+
never typed in as if the person had said it. The sender's session identity never leaks into it.
|
|
491
|
+
- *Not now* is remembered: the same session is not offered again for a while, and a double click
|
|
492
|
+
cannot open two windows.
|
|
493
|
+
- From a terminal: `npx klypix-mcp sessions` lists this project's closed sessions and the notes
|
|
494
|
+
waiting for them; `npx klypix-mcp sessions reopen <id>` reopens one (`--quiet` lets it wait for
|
|
495
|
+
you instead of starting on the note).
|
|
496
|
+
|
|
466
497
|
Durable handoffs go in the brain itself — decisions, findings, open questions and skills captured
|
|
467
498
|
as cards, each stamped with the agent that wrote it.
|
|
468
499
|
|
|
@@ -503,6 +534,27 @@ The app is a separate, proprietary Windows product. The format, this server and
|
|
|
503
534
|
Apache-2.0 and work with no app installed. The app's interface is available in English and Arabic
|
|
504
535
|
(some newer panels are still English-only).
|
|
505
536
|
|
|
537
|
+
### KLYPIX canvases and your AI tool
|
|
538
|
+
|
|
539
|
+
Beyond project brains, the same connection reads and writes the KLYPIX canvases (spaces) saved on
|
|
540
|
+
your PC. **Your AI tool reads what KLYPIX has already read:**
|
|
541
|
+
|
|
542
|
+
- `read_canvas` prints every card with its id; `read_card_contents` returns what is inside a card
|
|
543
|
+
from what KLYPIX saved — the transcript on a video card, the text card **Read contents** made for a
|
|
544
|
+
reel or web page, an OCR card for a photo, a folder's file list. For a reel or a video, choose Read
|
|
545
|
+
contents in KLYPIX first (select the card, press Enter), let the canvas save, then ask your AI tool.
|
|
546
|
+
This version does not start new readings itself.
|
|
547
|
+
- What a person set up in KLYPIX is respected: cards inside a box **locked from AI tools** are left
|
|
548
|
+
out of every read; frozen cards are marked read-only; collapsed boxes, comments and tags are shown.
|
|
549
|
+
- `klypix_status` tells your AI tool what KLYPIX can do on this PC right now, and which step the person
|
|
550
|
+
has to take.
|
|
551
|
+
- **A canvas that is open in KLYPIX is not written.** KLYPIX builds that write the open-canvas lease
|
|
552
|
+
(`%APPDATA%\klypix\agent-bridge\endpoint.json`, no secret in it) make `add_to_canvas` refuse
|
|
553
|
+
that canvas and say why; project brains are the exception, because KLYPIX merges them. With an older
|
|
554
|
+
KLYPIX, `add_to_canvas` writes and its reply tells the person to close the canvas's tab and open it
|
|
555
|
+
again.
|
|
556
|
+
- Text that comes back from cards, pages, reels and files is fenced as data, never instructions.
|
|
557
|
+
|
|
506
558
|
## Measure it yourself
|
|
507
559
|
|
|
508
560
|
Claims about a shared brain — "nothing is lost", "it stays fast" — are unfalsifiable until a
|
|
@@ -637,7 +689,7 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
637
689
|
|
|
638
690
|
---
|
|
639
691
|
|
|
640
|
-
## The
|
|
692
|
+
## The 25 verbs
|
|
641
693
|
|
|
642
694
|
| Tool | What it does |
|
|
643
695
|
|---|---|
|
|
@@ -651,20 +703,23 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
651
703
|
| `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, tool count, projection drift |
|
|
652
704
|
| `brain_message` | Session-to-session coordination notes — to a live session, or queued for one that is not running until it next starts — with a fixed send-time audience and per-recipient pending / offer / acknowledgement / consumption / failure receipts (a directed note is kept 7 days, a broadcast 24h; never written into the brain) |
|
|
653
705
|
| `brain_message_receipt` | Explicitly record model-side consumption using the exact message id and per-recipient offer token; acknowledgement alone never consumes a note |
|
|
706
|
+
| `brain_reopen` | Reopen a closed Claude Code or Codex session so a note waiting for it gets there now — only after the person answers *Reopen* in an in-chat prompt or KLYPIX's own dialog; opens a new, visible terminal in that session's folder with the host's resume command |
|
|
654
707
|
| `brain_sync` | Context Gateway: task capsule, active-task peers, exact-file overlap, one-time alerts, timing, and optional result-manifest reconciliation |
|
|
655
708
|
| `brain_connect` | Find and draw related-but-unlinked cards |
|
|
656
709
|
| `project_map_context` | Read-only, bounded code-graph evidence beside correction-aware brain context, with exact-path review proposals; external artifacts (e.g. Graphify) are supported but never installed or run locally |
|
|
657
710
|
| `project_map_scan` | KLYPIX's own zero-install scanner: gitignore-aware file inventory + file-level import edges (relative, tsconfig-alias, and monorepo-workspace imports resolved) written to `klypix-map/graph.json` — which then serves `project_map_context` automatically |
|
|
658
711
|
| `project_map_drift` | Read-only drift report: brain cards whose referenced files are gone or moved (with rename candidates), plus a headline when the checkout itself is behind its origin default branch |
|
|
659
712
|
| `canvas_view` | Returns the board as a structured render spec plus a text summary, and declares an MCP Apps (SEP-1865) UI resource |
|
|
660
|
-
| `read_canvas` | A canvas as markdown
|
|
661
|
-
| `
|
|
713
|
+
| `read_canvas` | A canvas as markdown: every card with its id, the connection graph, `[[links]]`, `#tags` and tag pills, status, comments, reactions, frozen and collapsed boxes, and the readings KLYPIX already saved on link, video and photo cards; photo cards' images attached, each labelled with its card. Cards inside a box a person locked from AI tools are left out, and counted. Titles as KLYPIX shows them work as names |
|
|
714
|
+
| `read_card_contents` | What is inside up to 5 cards — a reel, YouTube video, web page, video or audio file, photo, document or folder — from what KLYPIX has already read: transcripts it saved on the card, its Read contents and OCR result cards, folder listings. Fenced as data, marked full or partial, with where it was made (this PC or cloud AI). A card KLYPIX has not read yet comes back with the one step the person takes in KLYPIX; this version starts no new readings |
|
|
715
|
+
| `klypix_status` | What KLYPIX can do on this PC right now: whether the app is running and which canvases it has open (from the lease file the app writes), where canvases are read from, and what each feature still needs from the person |
|
|
716
|
+
| `search_canvases` | Search across canvases by name, content, tags and tag pills, and the readings KLYPIX saved on cards; returns card ids and dates. Never searches inside a box a person locked from AI tools |
|
|
662
717
|
| `search_all_brains` | Cross-project memory search across every registered brain on this machine |
|
|
663
718
|
| `create_canvas` | New `.klypix` from cards + connections |
|
|
664
|
-
| `add_to_canvas` | Append cards/connections (positions preserved) |
|
|
719
|
+
| `add_to_canvas` | Append cards/connections (positions preserved), bordered and readable on KLYPIX's dark and Paper themes; a card's `group` puts it in that titled box. Refuses a canvas open in KLYPIX (`OPEN_IN_APP`), a box locked from AI tools (`SCOPE_LOCKED`) or a frozen box (`FROZEN`), and writes nothing; project brains are the exception to the first. Returns the new card ids |
|
|
665
720
|
| `list_canvases` | List every `.klypix` in the vault |
|
|
666
721
|
|
|
667
|
-
Exactly
|
|
722
|
+
Exactly 25 as of klypix-mcp 1.92.0, machine-verifiable with `npx klypix-mcp doctor`.
|
|
668
723
|
|
|
669
724
|
> **`canvas_view`:** no MCP Apps host has been observed rendering the UI resource yet — there is no
|
|
670
725
|
> screenshot and no host-level test. Hosts without the extension get clean text, which is the path
|
package/bin/klypix-append.mjs
CHANGED
|
@@ -11,15 +11,22 @@
|
|
|
11
11
|
// echo '<addition>' | node scripts/append-klypix.mjs <file.klypix>
|
|
12
12
|
//
|
|
13
13
|
// addition:
|
|
14
|
-
// { "cards": [{ "text": "...", "heading"?, "color"? }],
|
|
14
|
+
// { "cards": [{ "text": "...", "heading"?, "color"?, "group"?, "border"? }],
|
|
15
15
|
// "connections": [{ "from": <idx|title>, "to": <idx|title>, "relationship"? }] }
|
|
16
16
|
// from/to may reference a NEW card (by index in this addition, or its title)
|
|
17
17
|
// or an EXISTING card already on the canvas (by its title). New cards land in
|
|
18
18
|
// a column just to the right of the current content, stacked on top.
|
|
19
|
+
//
|
|
20
|
+
// Like add_to_canvas it honours the KLYPIX lease (src/app-lease.mjs): a canvas
|
|
21
|
+
// that is open in KLYPIX is refused and left byte-identical (project brains are
|
|
22
|
+
// the exception — KLYPIX merges them), and on Windows with no lease file it
|
|
23
|
+
// writes and prints the step that keeps the cards if the canvas IS open.
|
|
19
24
|
|
|
20
25
|
import fs from 'fs';
|
|
21
|
-
import
|
|
26
|
+
import path from 'path';
|
|
27
|
+
import { appendToKlypix, atomicWrite, readManifestCheap } from '../src/klypix-format.mjs';
|
|
22
28
|
import { brainCaptureLockPath, withAdvisoryWriteLock } from '../src/brain-write-lock.mjs';
|
|
29
|
+
import { canvasWriteLockPath, leaseVerdict, tellUser, LEASE_SINCE_APP_VERSION } from '../src/app-lease.mjs';
|
|
23
30
|
|
|
24
31
|
const args = process.argv.slice(2);
|
|
25
32
|
const file = args.find(a => !a.startsWith('--'));
|
|
@@ -33,22 +40,46 @@ try {
|
|
|
33
40
|
addition = JSON.parse(raw);
|
|
34
41
|
} catch (e) { console.error('Addition is not valid JSON:', e.message); process.exit(2); }
|
|
35
42
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
43
|
+
// A project brain: by file name, or by the manifest's explicit kind (a renamed brain).
|
|
44
|
+
const manifest = readManifestCheap(file);
|
|
45
|
+
const isBrain = /^brain\.(klypix|any)$/i.test(path.basename(file)) || manifest?.kind === 'brain';
|
|
46
|
+
const title = (manifest && typeof manifest.title === 'string' && manifest.title.trim()) || path.basename(file).replace(/\.(klypix|any)$/i, '');
|
|
47
|
+
|
|
48
|
+
// Read-modify-write under the SAME cross-process lock as the MCP engine:
|
|
49
|
+
// brains share the hooks' and desktop app's folder lock; an ordinary canvas
|
|
50
|
+
// locks in the profile, so no `.claude` folder appears beside it. Racing them
|
|
51
|
+
// unlocked is silent last-writer-wins loss. The callback only RETURNS — exiting
|
|
52
|
+
// inside it would skip the lock's release.
|
|
53
|
+
const outcome = await withAdvisoryWriteLock(isBrain ? brainCaptureLockPath(file) : canvasWriteLockPath(file), async (locked) => {
|
|
54
|
+
if (!locked) return { status: 'busy' };
|
|
55
|
+
const verdict = isBrain ? { action: 'write' } : leaseVerdict(file);
|
|
56
|
+
if (verdict.action === 'refuse') return { status: 'open' };
|
|
40
57
|
let buf;
|
|
41
58
|
try {
|
|
42
59
|
buf = await appendToKlypix(fs.readFileSync(file), addition);
|
|
43
|
-
} catch (e) {
|
|
44
|
-
await atomicWrite(file, buf);
|
|
45
|
-
return
|
|
60
|
+
} catch (e) { return { status: 'failed', message: e.message, code: e.code }; }
|
|
61
|
+
await atomicWrite(file, buf, { isBrain, restorePoint: !isBrain, reason: 'klypix-append' });
|
|
62
|
+
return { status: 'written', warn: verdict.action === 'warn' };
|
|
46
63
|
}, { tries: 100, waitMs: 60 });
|
|
47
|
-
|
|
64
|
+
|
|
65
|
+
if (outcome.status === 'busy') {
|
|
48
66
|
console.error('append-klypix refused (file unchanged): the write lock is held by another writer — retry in a moment.');
|
|
49
67
|
process.exit(1);
|
|
50
68
|
}
|
|
69
|
+
if (outcome.status === 'open') {
|
|
70
|
+
console.error(`append-klypix refused (file unchanged): '${title}' is open in KLYPIX right now (code OPEN_IN_APP).`);
|
|
71
|
+
console.error(`Tell the user: ${tellUser('OPEN_IN_APP', { canvas: title })}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
if (outcome.status === 'failed') {
|
|
75
|
+
console.error(outcome.message);
|
|
76
|
+
if (outcome.code === 'SCOPE_LOCKED' || outcome.code === 'FROZEN') console.error(`Tell the user: ${tellUser(outcome.code)}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
51
79
|
const cardCount = Array.isArray(addition.cards) ? addition.cards.length : 0;
|
|
52
80
|
const connCount = Array.isArray(addition.connections) ? addition.connections.length : 0;
|
|
53
81
|
console.log(`Appended ${cardCount} card(s), ${connCount} connection(s) to ${file}.`);
|
|
54
|
-
console.log(
|
|
82
|
+
console.log(isBrain
|
|
83
|
+
? `It appears in the brain if it is open in KLYPIX (inside OneDrive or Dropbox, when you next open it). Verify: node scripts/read-klypix.mjs "${file}"`
|
|
84
|
+
: `They appear when the canvas is next opened in KLYPIX. Verify: node scripts/read-klypix.mjs "${file}"`);
|
|
85
|
+
if (outcome.warn) console.log(`Tell the user: ${tellUser('MAY_BE_OPEN', { canvas: title, version: LEASE_SINCE_APP_VERSION })}`);
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -118,8 +118,10 @@ function wireCodex() {
|
|
|
118
118
|
// Pre-1.35 installed a global `--vault "."` entry. A global Codex process
|
|
119
119
|
// resolves that dot from the app install directory, not the user's project,
|
|
120
120
|
// so it can silently bind the wrong brain and override the correct project
|
|
121
|
-
// table. Remove only KLYPIX-owned global tables
|
|
122
|
-
|
|
121
|
+
// table. Remove only KLYPIX-owned global tables whose --vault is RELATIVE;
|
|
122
|
+
// preserve every other server — including the absolute-vault table KLYPIX's
|
|
123
|
+
// own Settings → Codex button writes (removing it greyed that button).
|
|
124
|
+
const globalMcp = disconnectCodexMcpServer({ configPath: CODEX_CONFIG, onlyRelativeVault: true });
|
|
123
125
|
const instructions = mergeCodexGlobalInstructions(HOME);
|
|
124
126
|
const hookScript = path.join(BRAIN_DIR, 'codex-brain-hook.mjs');
|
|
125
127
|
const presence = CODEX_HOOKS && exists(hookScript)
|
|
@@ -285,7 +287,10 @@ const flatten = (code) => code
|
|
|
285
287
|
// commitsInRange / makeContainmentProbe from it. It was already STAGED in
|
|
286
288
|
// the flat bundle (mcp-presence needs it) but never flattened, because
|
|
287
289
|
// nothing in bin/ had imported it directly before.
|
|
288
|
-
|
|
290
|
+
// app-lease / app-tools (P0 agent parity): the worker lazily imports the
|
|
291
|
+
// app tools (klypix_status, read_card_contents); klypix-core and the app
|
|
292
|
+
// tools import the lease reader.
|
|
293
|
+
.replace(/\.\.\/src\/(bench|brain-doctor|agent-presence|agent-rules|capture-gap|enrichment|finding-routing|mcp-presence|mcp-supervisor|mcp-auto-update|presence-relay|repo-state|semantic-memory|runtime-inspector|project-graph|git-capture-install|app-lease|app-tools)\.mjs/g, './$1.mjs')
|
|
289
294
|
.replace(/klypix-worker\.mjs/g, 'klypix-mcp-worker.mjs')
|
|
290
295
|
.replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
|
|
291
296
|
|
|
@@ -429,7 +434,7 @@ try {
|
|
|
429
434
|
// a newer klypix-format cannot. merge-brains and the driver ship here so
|
|
430
435
|
// brain-history's restore merge, the KLYPIX core and the git driver all
|
|
431
436
|
// find one engine in this directory.
|
|
432
|
-
for (const f of ['global-brain-hook.mjs', 'klypix-format.mjs', 'brain-graveyard.mjs', 'merge-brains.mjs', 'klypix-merge-driver.mjs', 'capture-gap.mjs', 'brain-semantic.mjs', 'semantic-memory.mjs', 'enrichment.mjs', 'provenance.mjs', 'brain-note.mjs', 'brain-evidence.mjs', 'brain-git-hook.mjs', 'git-capture-install.mjs', 'brain-history.mjs', 'klypix-core.mjs', 'brain-write-lock.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'editor-detect.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'repo-state.mjs', 'result-reconcile.mjs', 'finding-routing.mjs', 'presence-relay.mjs', 'mcp-supervisor.mjs', 'mcp-auto-update.mjs', 'runtime-inspector.mjs', 'project-graph.mjs', 'bench.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
|
|
437
|
+
for (const f of ['global-brain-hook.mjs', 'klypix-format.mjs', 'brain-graveyard.mjs', 'merge-brains.mjs', 'klypix-merge-driver.mjs', 'capture-gap.mjs', 'brain-semantic.mjs', 'semantic-memory.mjs', 'enrichment.mjs', 'provenance.mjs', 'brain-note.mjs', 'brain-evidence.mjs', 'brain-git-hook.mjs', 'git-capture-install.mjs', 'brain-history.mjs', 'app-lease.mjs', 'klypix-core.mjs', 'app-tools.mjs', 'brain-write-lock.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'editor-detect.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'repo-state.mjs', 'result-reconcile.mjs', 'finding-routing.mjs', 'presence-relay.mjs', 'mcp-supervisor.mjs', 'mcp-auto-update.mjs', 'runtime-inspector.mjs', 'project-graph.mjs', 'bench.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
|
|
433
438
|
const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
|
|
434
439
|
}
|
|
435
440
|
for (const [src, dst] of [
|
package/bin/klypix-mcp.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const PKG_VERSION = (() => {
|
|
|
19
19
|
}
|
|
20
20
|
})();
|
|
21
21
|
|
|
22
|
-
const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench']);
|
|
22
|
+
const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench', 'sessions']);
|
|
23
23
|
|
|
24
24
|
const USAGE = [
|
|
25
25
|
`klypix-mcp ${PKG_VERSION} — shared project brain + MCP coordination server.`,
|
|
@@ -29,6 +29,8 @@ const USAGE = [
|
|
|
29
29
|
' [--no-project] [--json] brain, config for the editors you actually have, merge driver, verified',
|
|
30
30
|
' link [dir] [--check] project this project\'s 14 managed agent config files (--check audits, writes nothing, exits 1 on drift)',
|
|
31
31
|
' doctor [--npm] [--all] [--json] read-only self-check; exits 1 on drift',
|
|
32
|
+
' sessions [--json] closed agent sessions of this project and the notes waiting for them',
|
|
33
|
+
' sessions reopen <id> [--quiet] reopen one (Claude Code / Codex) in a new terminal so its waiting notes reach it',
|
|
32
34
|
' runtime [--json] [--watch seconds] passive MCP process/RAM attribution; never terminates a process',
|
|
33
35
|
' conformance [--json] launch two real MCP clients against this build',
|
|
34
36
|
' bench [--quick] [--json] [--out F] reproducible benchmark: concurrent-write safety, latency, soak, crash',
|
package/bin/klypix-read.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import fs from 'fs';
|
|
17
17
|
import path from 'path';
|
|
18
|
-
import { parseKlypix, structToMarkdown } from '../src/klypix-format.mjs';
|
|
18
|
+
import { parseKlypix, structToMarkdown, scopeLockedView } from '../src/klypix-format.mjs';
|
|
19
19
|
|
|
20
20
|
const args = process.argv.slice(2);
|
|
21
21
|
const file = args.find(a => !a.startsWith('--'));
|
|
@@ -31,20 +31,29 @@ try {
|
|
|
31
31
|
console.error(e.message);
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
|
-
const {
|
|
34
|
+
const { zip, assetPaths } = parsed;
|
|
35
|
+
// This is the read path agents without MCP use (Aider), so it shows them what
|
|
36
|
+
// read_canvas shows: cards inside a box a person locked from AI tools in KLYPIX
|
|
37
|
+
// are left out (and counted), and each card's id, saved reading and state ride
|
|
38
|
+
// along.
|
|
39
|
+
const view = scopeLockedView(parsed);
|
|
40
|
+
const struct = view.struct;
|
|
35
41
|
// Fall back to the filename for the title when the file didn't store one.
|
|
36
42
|
if (!struct.title || struct.title === 'Untitled') {
|
|
37
43
|
struct.title = path.basename(file).replace(/\.(klypix|any)$/i, '');
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
// Optionally extract binary assets so the agent can open images with vision
|
|
46
|
+
// Optionally extract binary assets so the agent can open images with vision —
|
|
47
|
+
// never an asset only a hidden (scope-locked) card uses.
|
|
41
48
|
if (assetsDir && assetPaths.length) {
|
|
42
49
|
fs.mkdirSync(assetsDir, { recursive: true });
|
|
50
|
+
const visibleAssets = new Set(struct.assets);
|
|
43
51
|
for (const p of assetPaths) {
|
|
52
|
+
if (!visibleAssets.has(path.basename(p))) continue;
|
|
44
53
|
const bytes = await zip.file(p).async('nodebuffer');
|
|
45
54
|
fs.writeFileSync(path.join(assetsDir, path.basename(p)), bytes);
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
if (asJson) { console.log(JSON.stringify(struct, null, 2)); process.exit(0); }
|
|
50
|
-
console.log(structToMarkdown(struct, { assetsDir }));
|
|
59
|
+
console.log(structToMarkdown(struct, { assetsDir, parsed, lockedBoxes: view.boxes }));
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// klypix-sessions — `npx klypix-mcp sessions`. The human side of "reopen on your
|
|
3
|
+
// OK": the recent agent sessions of THIS project that are not running, which of
|
|
4
|
+
// them have notes waiting, and a one-word way to bring one back so its notes
|
|
5
|
+
// reach it.
|
|
6
|
+
//
|
|
7
|
+
// npx klypix-mcp sessions # list (this project)
|
|
8
|
+
// npx klypix-mcp sessions --json # the same, machine-readable
|
|
9
|
+
// npx klypix-mcp sessions reopen <id> # reopen it in a new terminal; it starts on the note
|
|
10
|
+
// npx klypix-mcp sessions reopen <id> --quiet # reopen it, but let it wait for you
|
|
11
|
+
// npx klypix-mcp sessions ... --project <dir> | --dry-run
|
|
12
|
+
//
|
|
13
|
+
// Typing `reopen` IS the human's yes, so there is no second prompt here. Only a
|
|
14
|
+
// Claude Code or Codex session that has a note waiting can be reopened — KLYPIX
|
|
15
|
+
// reopens a conversation so a note can reach it; it does not start agents.
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import {
|
|
19
|
+
buildReopenLaunch,
|
|
20
|
+
findProjectBrain,
|
|
21
|
+
launchReopen,
|
|
22
|
+
listKnownSessions,
|
|
23
|
+
recordSessionReopen,
|
|
24
|
+
reopenCandidate,
|
|
25
|
+
REOPEN_NUDGE,
|
|
26
|
+
} from '../src/agent-presence.mjs';
|
|
27
|
+
|
|
28
|
+
const isDir = (p) => { try { return fs.statSync(p).isDirectory(); } catch { return false; } };
|
|
29
|
+
const raw = process.argv.slice(2);
|
|
30
|
+
const argv = (raw[0] === 'sessions' && !isDir(path.resolve(raw[0]))) ? raw.slice(1) : raw;
|
|
31
|
+
const has = (flag) => argv.includes(flag);
|
|
32
|
+
const val = (flag) => { const i = argv.indexOf(flag); return i >= 0 ? argv[i + 1] : undefined; };
|
|
33
|
+
const projectDir = path.resolve(val('--project') || process.cwd());
|
|
34
|
+
const brainPath = findProjectBrain(projectDir);
|
|
35
|
+
const word = (client) => {
|
|
36
|
+
const key = String(client || '').toLowerCase();
|
|
37
|
+
if (key === 'claude-code' || key === 'claude') return 'Claude Code';
|
|
38
|
+
if (key === 'codex') return 'Codex';
|
|
39
|
+
return key ? key.replace(/(^|[-_ ])([a-z])/g, (_m, p, c) => `${p}${c.toUpperCase()}`) : 'Unknown';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!brainPath) {
|
|
43
|
+
console.error(`No project brain (brain.klypix) at or above ${projectDir} — run this in the project folder, or pass --project <dir>.`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (argv[0] === 'reopen') {
|
|
48
|
+
const target = argv[1] && !argv[1].startsWith('--') ? argv[1] : '';
|
|
49
|
+
if (!target) {
|
|
50
|
+
console.error('Usage: npx klypix-mcp sessions reopen <session id or 8+ character prefix> [--quiet] [--dry-run]');
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
const candidate = reopenCandidate({ brainPath, target, humanInitiated: true });
|
|
54
|
+
if (!candidate.ok) {
|
|
55
|
+
const who = candidate.entry ? `${word(candidate.entry.client)} ${String(candidate.entry.id).slice(0, 8)}` : `"${target}"`;
|
|
56
|
+
const why = {
|
|
57
|
+
'unknown-session': `no session KLYPIX remembers in this project matches ${who}`,
|
|
58
|
+
'ambiguous-session': `${who} matches more than one session — use more characters of its id`,
|
|
59
|
+
live: `${who} is running${candidate.statusLabel ? ` (${candidate.statusLabel})` : ''}; it gets its notes at its next action`,
|
|
60
|
+
'unsupported-client': `${who} cannot be reopened by KLYPIX (only Claude Code and Codex have a verified resume-by-id)`,
|
|
61
|
+
'unsafe-id': `${who} has an id that cannot be passed to a terminal safely`,
|
|
62
|
+
'no-waiting-note': `no note is waiting for ${who} — KLYPIX reopens a session only so a note can reach it`,
|
|
63
|
+
'just-reopened': `${who} was reopened moments ago — look for its terminal window`,
|
|
64
|
+
}[candidate.reason] || `${who} cannot be reopened (${candidate.reason})`;
|
|
65
|
+
console.error(`Not reopened: ${why}.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const plan = buildReopenLaunch({ hostKey: candidate.hostKey, sessionId: candidate.entry.id, cwd: candidate.cwd, prompt: has('--quiet') ? '' : REOPEN_NUDGE });
|
|
69
|
+
const launch = launchReopen(plan, { env: has('--dry-run') ? { ...process.env, KLYPIX_REOPEN_LAUNCH: 'dry-run' } : process.env });
|
|
70
|
+
const label = `${candidate.hostLabel} ${candidate.entry.id.slice(0, 8)}`;
|
|
71
|
+
if (launch.dryRun) {
|
|
72
|
+
console.log(`Dry run: would reopen ${label} via ${plan.method} in ${candidate.cwd} with ${candidate.command}${has('--quiet') ? '' : ' (and let it act on the note)'}.`);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
if (!launch.launched) {
|
|
76
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'manual', via: 'cli', method: plan.method || null });
|
|
77
|
+
console.error(`Could not open a terminal here (${plan.reason || launch.reason || 'unknown'}). Run it yourself:\n cd ${JSON.stringify(candidate.cwd)}\n ${candidate.command}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'reopened', via: 'cli', method: plan.method });
|
|
81
|
+
const notes = candidate.waitingNotes.length;
|
|
82
|
+
console.log(`🔓 Reopened ${label} in a new terminal (${candidate.cwd}) with ${candidate.command}${has('--quiet') ? ' — it waits for you' : ' — it starts on the note'}. ${notes === 1 ? 'Its waiting note is' : `Its ${notes} waiting notes are`} delivered at its first action.`);
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const rows = listKnownSessions({ brainPath, now }).filter((row) => !row.live);
|
|
88
|
+
if (has('--json')) {
|
|
89
|
+
console.log(JSON.stringify({ project: path.dirname(brainPath), brain: brainPath, sessions: rows.map((row) => ({
|
|
90
|
+
id: row.id, client: row.client, intent: row.intent, status: row.status, lastSeen: row.lastSeen,
|
|
91
|
+
endedAt: row.endedAt || null, waitingNotes: row.waitingDirectedNotes, waitingBroadcasts: row.waitingNotes - row.waitingDirectedNotes,
|
|
92
|
+
cwd: row.cwd || null, reopenable: Boolean(row.resumeCommand) && row.waitingDirectedNotes > 0, resumeCommand: row.resumeCommand || null,
|
|
93
|
+
})) }, null, 2));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
if (!rows.length) {
|
|
97
|
+
console.log('No recent sessions of this project are closed. (Live sessions: npx klypix-mcp doctor.)');
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
const waiting = rows.filter((row) => row.waitingDirectedNotes > 0);
|
|
101
|
+
console.log(`Recent sessions of ${path.basename(path.dirname(brainPath))} that are not running (${rows.length}${waiting.length ? `, ${waiting.length} with notes waiting` : ''}):`);
|
|
102
|
+
for (const row of rows.slice(0, 20)) {
|
|
103
|
+
const n = row.waitingDirectedNotes;
|
|
104
|
+
const note = n ? ` · 📬 ${n} note${n === 1 ? '' : 's'} waiting` : '';
|
|
105
|
+
console.log(` ${row.id.slice(0, 8)} ${word(row.client).padEnd(11)} ${String(row.status).padEnd(18)}${note}${row.intent ? ` “${row.intent.slice(0, 70)}”` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
const first = waiting.find((row) => row.resumeCommand);
|
|
108
|
+
if (first) console.log(`\nReopen one so its notes reach it: npx klypix-mcp sessions reopen ${first.id.slice(0, 8)}${waiting.length > 1 ? ' (any id above with notes waiting)' : ''}`);
|
|
109
|
+
process.exit(0);
|