pathnote 0.2.0 → 0.2.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 +2 -1
- package/bin/pathnote.js +6 -4
- package/package.json +1 -1
- package/src/hub.js +24 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Quit Claude Desktop first (⌘Q). It rewrites its config file while running, so
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Open Claude Desktop again
|
|
34
|
+
Open Claude Desktop again and ask Claude something like the line below. The map opens in your browser as soon as Claude starts looking (http://127.0.0.1:4747).
|
|
35
35
|
|
|
36
36
|
> Go through the meeting notes and customer interviews and write next quarter's priorities as a new note.
|
|
37
37
|
|
|
@@ -48,6 +48,7 @@ Use the same command: `npx -y pathnote <folder> [<folder> ...]`.
|
|
|
48
48
|
## The viewer
|
|
49
49
|
|
|
50
50
|
- http://127.0.0.1:4747 (the next free port if 4747 is taken; the address is printed to the client's MCP log).
|
|
51
|
+
- It opens in your browser by itself the first time the AI uses Pathnote, unless a viewer page is already open. Add `--no-open` (or set `PATHNOTE_NO_OPEN=1`) to turn that off.
|
|
51
52
|
- Drag to move, scroll to zoom. File names appear as you zoom in.
|
|
52
53
|
- Several AI sessions on the same folders all report to one viewer.
|
|
53
54
|
- To keep the viewer open while clients come and go: `npx pathnote <folders> --viewer`.
|
package/bin/pathnote.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// pathnote <folder> [<folder> ...] [--port 4747] [--viewer]
|
|
2
|
+
// pathnote <folder> [<folder> ...] [--port 4747] [--viewer] [--no-open]
|
|
3
3
|
// pathnote --sample <new folder> copies the sample notes there, to try Pathnote safely
|
|
4
4
|
// Started by an MCP client: MCP over stdin/stdout, plus the viewer at http://127.0.0.1:4747
|
|
5
5
|
// --viewer: only the viewer, for keeping it open while MCP clients come and go
|
|
6
|
+
// --no-open (or PATHNOTE_NO_OPEN=1): don't open the viewer in the browser when the AI starts working
|
|
6
7
|
// stdout belongs to MCP, so every message for people goes to stderr.
|
|
7
8
|
import fs from 'node:fs';
|
|
8
9
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
10
|
import { Workspace } from '../src/workspace.js';
|
|
10
11
|
import { History } from '../src/history.js';
|
|
11
|
-
import { Hub } from '../src/hub.js';
|
|
12
|
+
import { Hub, openBrowser } from '../src/hub.js';
|
|
12
13
|
import { createServer } from '../src/mcp.js';
|
|
13
14
|
|
|
14
15
|
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
|
|
@@ -26,7 +27,7 @@ if (flag('--sample')) {
|
|
|
26
27
|
process.exit(0);
|
|
27
28
|
}
|
|
28
29
|
if (!folder || flag('--help')) {
|
|
29
|
-
console.error(`Pathnote ${pkg.version}\n\nUsage: pathnote <folder> [<folder> ...] [--port 4747] [--viewer]\n pathnote --sample <new folder>`);
|
|
30
|
+
console.error(`Pathnote ${pkg.version}\n\nUsage: pathnote <folder> [<folder> ...] [--port 4747] [--viewer] [--no-open]\n pathnote --sample <new folder>`);
|
|
30
31
|
process.exit(folder ? 0 : 1);
|
|
31
32
|
}
|
|
32
33
|
for (const f of folders) {
|
|
@@ -38,7 +39,8 @@ for (const f of folders) {
|
|
|
38
39
|
|
|
39
40
|
const ws = new Workspace(folders);
|
|
40
41
|
const history = new History(ws);
|
|
41
|
-
const
|
|
42
|
+
const noOpen = flag('--no-open') || process.env.PATHNOTE_NO_OPEN === '1';
|
|
43
|
+
const hub = new Hub(ws, history, Number(opt('--port')) || 4747, { open: noOpen ? null : openBrowser });
|
|
42
44
|
const url = await hub.start();
|
|
43
45
|
console.error(`Pathnote ${pkg.version}: ${ws.roots.join(', ')}\nViewer: ${url}`);
|
|
44
46
|
ws.warm().catch(() => {});
|
package/package.json
CHANGED
package/src/hub.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Claude Desktop may start several Pathnote processes; the first one for a set of folders owns the
|
|
3
3
|
// viewer port and the others forward their events to it.
|
|
4
4
|
import http from 'node:http';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
5
6
|
import fs from 'node:fs/promises';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,8 +11,11 @@ const PAGE = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'view
|
|
|
10
11
|
const KEEP = 500;
|
|
11
12
|
|
|
12
13
|
export class Hub {
|
|
13
|
-
|
|
14
|
+
// open: called with the viewer URL the first time the AI does something while no viewer page is
|
|
15
|
+
// connected, so the map shows up without the user looking for it. null turns this off.
|
|
16
|
+
constructor(ws, history, port, { open = null } = {}) {
|
|
14
17
|
this.ws = ws;
|
|
18
|
+
this.open = open;
|
|
15
19
|
this.history = history;
|
|
16
20
|
this.port = port;
|
|
17
21
|
this.events = [];
|
|
@@ -81,6 +85,12 @@ export class Hub {
|
|
|
81
85
|
this.events.push(e);
|
|
82
86
|
if (this.events.length > KEEP) this.events.shift();
|
|
83
87
|
for (const res of this.clients) res.write(`data: ${JSON.stringify(e)}\n\n`);
|
|
88
|
+
// Once per process: a page left open from an earlier run reconnects by itself, so it counts
|
|
89
|
+
if (this.open && this.clients.size === 0) {
|
|
90
|
+
const open = this.open;
|
|
91
|
+
this.open = null;
|
|
92
|
+
open(`http://127.0.0.1:${this.port}`);
|
|
93
|
+
}
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
// Only pages served from this port may call the API, so other websites can't trigger undo.
|
|
@@ -102,6 +112,7 @@ export class Hub {
|
|
|
102
112
|
if (req.method === 'GET' && url.pathname === '/api/graph') return send(res, 200, await this.ws.graph());
|
|
103
113
|
if (req.method === 'GET' && url.pathname === '/api/events') {
|
|
104
114
|
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-store', Connection: 'keep-alive' });
|
|
115
|
+
res.flushHeaders(); // the page counts as connected before the first event
|
|
105
116
|
for (const e of this.events) res.write(`data: ${JSON.stringify(e)}\n\n`);
|
|
106
117
|
this.clients.add(res);
|
|
107
118
|
req.on('close', () => this.clients.delete(res));
|
|
@@ -125,6 +136,18 @@ export class Hub {
|
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
138
|
|
|
139
|
+
// Opens a URL in the default browser. Errors are ignored: the URL is also in the client's MCP log.
|
|
140
|
+
export function openBrowser(url) {
|
|
141
|
+
const [cmd, args] = process.platform === 'darwin' ? ['open', [url]]
|
|
142
|
+
: process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]
|
|
143
|
+
: ['xdg-open', [url]];
|
|
144
|
+
try {
|
|
145
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
146
|
+
child.on('error', () => {});
|
|
147
|
+
child.unref();
|
|
148
|
+
} catch {}
|
|
149
|
+
}
|
|
150
|
+
|
|
128
151
|
function send(res, status, data) {
|
|
129
152
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
130
153
|
res.end(JSON.stringify(data));
|