persnally 2.0.0 → 2.0.2
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 +23 -8
- package/build/src/config.js +3 -1
- package/build/src/connect.js +21 -4
- package/build/src/lifecycle.js +11 -5
- package/build/src/mcp/index.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Persnally
|
|
2
2
|
|
|
3
|
-
[](https://github.com/sidpan2011/persnally/actions/workflows/ci.yml)
|
|
4
|
-
|
|
5
3
|
**So every AI finally knows you.**
|
|
6
4
|
|
|
7
5
|
Persnally is a local-first personal context engine. It learns who you are from your AI activity — your Claude and ChatGPT history, your code — and serves that context to every AI tool you use, so they stop treating you like a stranger.
|
|
@@ -20,15 +18,24 @@ The fix isn't a better model. It's a layer underneath all of them that holds *yo
|
|
|
20
18
|
|
|
21
19
|
```bash
|
|
22
20
|
npm install -g persnally
|
|
21
|
+
persnally setup
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
One command builds your mirror: it finds Claude/ChatGPT exports in `~/Downloads`, reads your local Claude Code sessions and git repos, synthesizes an evidence-linked profile, connects your AI clients (Claude Desktop, Claude Code, Cursor), and opens the dashboard.
|
|
25
|
+
|
|
26
|
+
For the richest result, export your data first ([claude.ai](https://claude.com) / [chatgpt.com](https://chatgpt.com) → Settings → Data export) and drop it in `~/Downloads` — then read a description of yourself that's sharper than your own bio, every sentence traceable to the conversations it came from.
|
|
27
|
+
|
|
28
|
+
Prefer each step explicit?
|
|
29
|
+
|
|
30
|
+
```bash
|
|
23
31
|
persnallyd start # the local daemon
|
|
24
32
|
persnallyd import claude ~/Downloads/<your-claude-export>
|
|
33
|
+
persnallyd import claude-code # your local Claude Code sessions
|
|
25
34
|
persnallyd import git ~/Projects # offline, no API needed
|
|
26
35
|
persnallyd profile # synthesize who you are
|
|
27
36
|
open http://127.0.0.1:4983 # see it, with evidence for every claim
|
|
28
37
|
```
|
|
29
38
|
|
|
30
|
-
Export your data ([claude.ai](https://claude.com) / [chatgpt.com](https://chatgpt.com) → Settings → Data export), point Persnally at it, and read a description of yourself that's sharper than your own bio — every sentence traceable to the conversations it came from.
|
|
31
|
-
|
|
32
39
|
## How it works
|
|
33
40
|
|
|
34
41
|
```
|
|
@@ -50,7 +57,11 @@ Export your data ([claude.ai](https://claude.com) / [chatgpt.com](https://chatgp
|
|
|
50
57
|
|
|
51
58
|
## Make your AI tools use it
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
```bash
|
|
61
|
+
persnallyd connect --all # writes the MCP config for Claude Desktop, Claude Code, Cursor
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or add the MCP server to any client manually. It exposes four tools backed by the daemon:
|
|
54
65
|
|
|
55
66
|
| Tool | What it does |
|
|
56
67
|
|------|-------------|
|
|
@@ -74,10 +85,14 @@ Add the MCP server to any client (Claude Desktop, Cursor, Claude Code). It expos
|
|
|
74
85
|
## CLI
|
|
75
86
|
|
|
76
87
|
```
|
|
77
|
-
|
|
88
|
+
persnally setup # one command: import, synthesize, connect
|
|
89
|
+
persnallyd start | stop | status # daemon lifecycle
|
|
78
90
|
persnallyd autostart [--remove] # run at login (macOS)
|
|
79
|
-
persnallyd
|
|
91
|
+
persnallyd connect [client|--all] # add to claude-code | claude-desktop | cursor
|
|
92
|
+
persnallyd import claude|claude-code|chatgpt|git <path>
|
|
93
|
+
persnallyd scope <client> <categories> # limit what a client can read
|
|
80
94
|
persnallyd profile # synthesize the profile
|
|
95
|
+
persnallyd consolidate # reflect now: refresh decay, add behavior patterns
|
|
81
96
|
persnallyd show [topics|events|profile]
|
|
82
97
|
persnallyd forget <topic> | --all | --batch <id>
|
|
83
98
|
persnallyd config set-key <sk-ant-…> # key for the background daemon
|
|
@@ -85,7 +100,7 @@ persnallyd config set-key <sk-ant-…> # key for the background daemon
|
|
|
85
100
|
|
|
86
101
|
## Status
|
|
87
102
|
|
|
88
|
-
Early and moving fast — see [ROADMAP.md](./ROADMAP.md). Today: import from Claude
|
|
103
|
+
Early and moving fast — see [ROADMAP.md](./ROADMAP.md). Today: import from Claude, ChatGPT, Claude Code, and git; a decay-weighted interest graph; an evidence-linked profile; a local dashboard; per-client permission scoping; nightly consolidation; and the MCP layer that serves it all. Next: cross-tool context everywhere, then a behavior model that can answer *what would I do here?*
|
|
89
104
|
|
|
90
105
|
## License
|
|
91
106
|
|
package/build/src/config.js
CHANGED
|
@@ -22,7 +22,9 @@ export function saveConfig(updates) {
|
|
|
22
22
|
const file = configFile();
|
|
23
23
|
mkdirSync(dirname(file), { recursive: true });
|
|
24
24
|
const merged = { ...loadConfig(), ...updates };
|
|
25
|
-
|
|
25
|
+
// mode on create closes the world-readable window before chmod; chmod also
|
|
26
|
+
// corrects perms on a pre-existing file. The config holds the API key.
|
|
27
|
+
writeFileSync(file, JSON.stringify(merged, null, 2) + "\n", { mode: 0o600 });
|
|
26
28
|
chmodSync(file, 0o600);
|
|
27
29
|
}
|
|
28
30
|
/** Env wins over config; sets process.env so the Anthropic SDK picks it up. */
|
package/build/src/connect.js
CHANGED
|
@@ -37,9 +37,17 @@ export function connectClient(client) {
|
|
|
37
37
|
const { file, installed } = configPathFor(client);
|
|
38
38
|
if (!installed)
|
|
39
39
|
return null;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
let cfg = {};
|
|
41
|
+
if (existsSync(file)) {
|
|
42
|
+
try {
|
|
43
|
+
cfg = JSON.parse(readFileSync(file, "utf-8"));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Never overwrite a config we couldn't parse — that would wipe the user's
|
|
47
|
+
// other MCP servers. Surface it instead.
|
|
48
|
+
throw new Error(`${file} is not valid JSON — fix it, then run \`persnallyd connect ${client}\` again`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
43
51
|
const servers = (cfg.mcpServers ??= {});
|
|
44
52
|
servers.persnally = { command: "node", args: [mcpServerPath()] };
|
|
45
53
|
mkdirSync(dirname(file), { recursive: true });
|
|
@@ -47,5 +55,14 @@ export function connectClient(client) {
|
|
|
47
55
|
return file;
|
|
48
56
|
}
|
|
49
57
|
export function connectAll() {
|
|
50
|
-
|
|
58
|
+
// One client with a malformed existing config must not abort onboarding for
|
|
59
|
+
// the others; connectClient throws rather than clobber a file it can't parse.
|
|
60
|
+
return CLIENTS.map((client) => {
|
|
61
|
+
try {
|
|
62
|
+
return { client, file: connectClient(client) };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return { client, file: null };
|
|
66
|
+
}
|
|
67
|
+
});
|
|
51
68
|
}
|
package/build/src/lifecycle.js
CHANGED
|
@@ -79,6 +79,9 @@ export function autostartInstalled() {
|
|
|
79
79
|
export function installAutostart(cliPath, port) {
|
|
80
80
|
if (process.platform !== "darwin")
|
|
81
81
|
throw new Error("autostart is macOS-only for now (launchd)");
|
|
82
|
+
// Paths carry the username/home dir; escape so an "&" or "<" in a path can't
|
|
83
|
+
// produce a malformed plist that silently breaks autostart.
|
|
84
|
+
const x = xmlEscape;
|
|
82
85
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
83
86
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
84
87
|
<plist version="1.0">
|
|
@@ -86,16 +89,16 @@ export function installAutostart(cliPath, port) {
|
|
|
86
89
|
<key>Label</key><string>${PLIST_LABEL}</string>
|
|
87
90
|
<key>ProgramArguments</key>
|
|
88
91
|
<array>
|
|
89
|
-
<string>${process.execPath}</string>
|
|
90
|
-
<string>${cliPath}</string>
|
|
92
|
+
<string>${x(process.execPath)}</string>
|
|
93
|
+
<string>${x(cliPath)}</string>
|
|
91
94
|
<string>serve</string>
|
|
92
95
|
<string>--port</string>
|
|
93
|
-
<string>${port}</string>
|
|
96
|
+
<string>${x(String(port))}</string>
|
|
94
97
|
</array>
|
|
95
98
|
<key>RunAtLoad</key><true/>
|
|
96
99
|
<key>KeepAlive</key><true/>
|
|
97
|
-
<key>StandardOutPath</key><string>${LOG_FILE}</string>
|
|
98
|
-
<key>StandardErrorPath</key><string>${LOG_FILE}</string>
|
|
100
|
+
<key>StandardOutPath</key><string>${x(LOG_FILE)}</string>
|
|
101
|
+
<key>StandardErrorPath</key><string>${x(LOG_FILE)}</string>
|
|
99
102
|
</dict>
|
|
100
103
|
</plist>
|
|
101
104
|
`;
|
|
@@ -117,3 +120,6 @@ export function removeAutostart() {
|
|
|
117
120
|
function sleep(ms) {
|
|
118
121
|
return new Promise((r) => setTimeout(r, ms));
|
|
119
122
|
}
|
|
123
|
+
function xmlEscape(s) {
|
|
124
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
125
|
+
}
|
package/build/src/mcp/index.js
CHANGED
|
@@ -7,13 +7,16 @@
|
|
|
7
7
|
* NLP engine: it fills persnally_track's structured schema from conversation
|
|
8
8
|
* context, so signal extraction costs zero extra inference.
|
|
9
9
|
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
10
11
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
import { DAEMON_HINT, DaemonUnreachable, daemonDelete, daemonGet, daemonPost } from "./daemon-client.js";
|
|
14
15
|
import { migrateV1Graph } from "./migrate-v1.js";
|
|
15
16
|
import { getClient, logEvent, setClient } from "./telemetry.js";
|
|
16
|
-
|
|
17
|
+
// Handshake version tracks package.json — same rule as the daemon's VERSION.
|
|
18
|
+
const pkg = JSON.parse(readFileSync(new URL("../../../package.json", import.meta.url), "utf-8"));
|
|
19
|
+
const server = new McpServer({ name: "persnally", version: pkg.version });
|
|
17
20
|
function text(s) {
|
|
18
21
|
return { content: [{ type: "text", text: s }] };
|
|
19
22
|
}
|