@rohirik/pi-ltm 2.1.0 → 2.8.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 +2 -2
- package/dist/index.js +1 -1
- package/package.json +22 -9
- package/src/__tests__/plugin.test.ts +3 -3
- package/src/hooks.ts +1 -1
- package/src/index.ts +3 -3
- package/src/tools.ts +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ package = "@rohirik/pi-ltm"
|
|
|
26
26
|
All three tools read from and write to the same database:
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
~/.claude/plugins/data/
|
|
29
|
+
~/.claude/plugins/data/OpenLtm-openltm/openltm.db
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
A memory learned in Pi appears in the next Claude Code session, and vice versa.
|
|
@@ -34,5 +34,5 @@ A memory learned in Pi appears in the next Claude Code session, and vice versa.
|
|
|
34
34
|
## Custom DB path
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
export LTM_DB_PATH="/custom/path/
|
|
37
|
+
export LTM_DB_PATH="/custom/path/openltm.db"
|
|
38
38
|
```
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{spawn as w,execSync as b}from"node:child_process";import{createRequire as y}from"node:module";import{resolve as x,dirname as T,join as p}from"node:path";import{homedir as v}from"node:os";import{existsSync as u,readdirSync as k}from"node:fs";var m="LTM_BRIDGE_DEPTH";function P(){let c=[process.env.BUN_INSTALL?p(process.env.BUN_INSTALL,"bin","bun"):null,p(v(),".bun","bin","bun"),"/opt/homebrew/bin/bun","/usr/local/bin/bun","/usr/bin/bun"].filter(t=>t!==null&&u(t));if(c.length>0)return c[0];for(let t of["which bun","command -v bun"])try{let e=b(t,{encoding:"utf-8",stdio:"pipe"}).trim();if(e&&u(e)&&!/\bpi\b/.test(e))return e}catch{}return null}function E(){let c=p(v(),".claude","plugins","cache","ltm","ltm");if(u(c))try{let t=k(c).filter(e=>/^\d/.test(e)).sort().reverse();for(let e of t){let n=p(c,e,"src","mcp-server.ts");if(u(n))return n}}catch{}try{let e=y(import.meta.url).resolve("@rohirik/
|
|
1
|
+
import{spawn as w,execSync as b}from"node:child_process";import{createRequire as y}from"node:module";import{resolve as x,dirname as T,join as p}from"node:path";import{homedir as v}from"node:os";import{existsSync as u,readdirSync as k}from"node:fs";var m="LTM_BRIDGE_DEPTH";function P(){let c=[process.env.BUN_INSTALL?p(process.env.BUN_INSTALL,"bin","bun"):null,p(v(),".bun","bin","bun"),"/opt/homebrew/bin/bun","/usr/local/bin/bun","/usr/bin/bun"].filter(t=>t!==null&&u(t));if(c.length>0)return c[0];for(let t of["which bun","command -v bun"])try{let e=b(t,{encoding:"utf-8",stdio:"pipe"}).trim();if(e&&u(e)&&!/\bpi\b/.test(e))return e}catch{}return null}function E(){let c=p(v(),".claude","plugins","cache","ltm","ltm");if(u(c))try{let t=k(c).filter(e=>/^\d/.test(e)).sort().reverse();for(let e of t){let n=p(c,e,"src","mcp-server.ts");if(u(n))return n}}catch{}try{let e=y(import.meta.url).resolve("@rohirik/openltm-core/package.json"),n=x(T(e),"src","mcp-server.ts");if(u(n))return n}catch{}return null}var f=class{constructor(t,e){this.runtime=t;this.script=e}child=null;pending=new Map;buffer="";nextId=1;exited=!1;start(){if(this.child)return;let t=parseInt(process.env[m]??"0",10),e={...process.env,[m]:String(t+1)};this.child=w(this.runtime,[this.script],{stdio:["pipe","pipe","pipe"],env:e}),this.child.stdout?.on("data",n=>{this.buffer+=n.toString("utf-8");let r=this.buffer.split(`
|
|
2
2
|
`);this.buffer=r.pop()??"";for(let o of r){let l=o.trim();if(l)try{let i=JSON.parse(l);if(i.id!==void 0){let s=this.pending.get(i.id);s&&(this.pending.delete(i.id),i.error?s.reject(new Error(i.error.message)):s.resolve(i.result))}}catch{}}}),this.child.on("exit",()=>{this.exited=!0;for(let n of this.pending.values())n.reject(new Error("LTM MCP server exited"));this.pending.clear()}),this.child.on("error",()=>{this.exited=!0;for(let n of this.pending.values())n.reject(new Error("LTM MCP server error"));this.pending.clear()})}send(t,e,n){let r=JSON.stringify({jsonrpc:"2.0",id:n,method:t,params:e})+`
|
|
3
3
|
`;this.child?.stdin?.write(r)}request(t,e,n=3e4){let r=this.nextId++;return new Promise((o,l)=>{let i=setTimeout(()=>{this.pending.delete(r),l(new Error(`LTM MCP timeout: ${t}`))},n);this.pending.set(r,{resolve:s=>{clearTimeout(i),o(s)},reject:s=>{clearTimeout(i),l(s)}}),this.send(t,e,r)})}async initialize(){await this.request("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"pi-ltm-bridge",version:"1.0.0"}}),this.send("notifications/initialized")}async listTools(){return(await this.request("tools/list"))?.tools??[]}async callTool(t,e){let n=await this.request("tools/call",{name:t,arguments:e},6e4),r=(n?.content??[]).filter(o=>o?.type==="text"&&o.text).map(o=>o.text).join(`
|
|
4
4
|
`);if(n?.isError)throw new Error(r||`${t} returned an error`);return r}};function S(c){let t=c;if(parseInt(process.env[m]??"0",10)>0)return;let e=P(),n=E();if(!e||!n)return;let r=new f(e,n);r.start();let o=(async()=>{await r.initialize();let l=await r.listTools();for(let i of l){let s=i.name;t.registerTool({name:s,label:s,description:i.description??"",parameters:i.inputSchema??{type:"object",properties:{}},async execute(g,a){return{content:[{type:"text",text:await r.callTool(s,a??{})}],details:{}}}})}})().catch(()=>{});t.on("before_agent_start",async l=>{await o;let i=l;try{let g=String(i?.cwd??process.cwd()).replace(/\/$/,"").split("/").pop()??"",a=await r.callTool("ltm_recall",{project:g,limit:8,sort_by:"relevance"});if(!a||a==="[]")return;let d=`## Prior Knowledge (LTM)
|
package/package.json
CHANGED
|
@@ -1,39 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rohirik/pi-ltm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "LTM (Long-Term Memory) native extension for Pi — share memories with Claude Code and OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/index.js"
|
|
9
9
|
},
|
|
10
|
-
"files": [
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
11
14
|
"pi": {
|
|
12
|
-
"extensions": [
|
|
15
|
+
"extensions": [
|
|
16
|
+
"./dist/index.js"
|
|
17
|
+
]
|
|
13
18
|
},
|
|
14
19
|
"scripts": {
|
|
15
|
-
"build": "bunx esbuild src/index.ts --bundle --platform=node --format=esm --target=esnext --outfile=dist/index.js --external:bun:sqlite --external:bun:test --external:@rohirik/
|
|
20
|
+
"build": "bunx esbuild src/index.ts --bundle --platform=node --format=esm --target=esnext --outfile=dist/index.js --external:bun:sqlite --external:bun:test --external:@rohirik/openltm-core --minify",
|
|
16
21
|
"prepublishOnly": "bun run build",
|
|
17
22
|
"typecheck": "bunx tsc --noEmit",
|
|
18
23
|
"test": "bun test"
|
|
19
24
|
},
|
|
20
25
|
"repository": {
|
|
21
26
|
"type": "git",
|
|
22
|
-
"url": "https://github.com/RohiRIK/
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
27
|
+
"url": "https://github.com/RohiRIK/OpenLtm"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"pi",
|
|
31
|
+
"pi-coding-agent",
|
|
32
|
+
"ltm",
|
|
33
|
+
"long-term-memory",
|
|
34
|
+
"extension"
|
|
35
|
+
],
|
|
25
36
|
"license": "MIT",
|
|
26
37
|
"publishConfig": {
|
|
27
38
|
"access": "public"
|
|
28
39
|
},
|
|
29
40
|
"dependencies": {
|
|
30
|
-
"@rohirik/
|
|
41
|
+
"@rohirik/openltm-core": "^2.8.0"
|
|
31
42
|
},
|
|
32
43
|
"peerDependencies": {
|
|
33
44
|
"bun-types": ">=1.0.0"
|
|
34
45
|
},
|
|
35
46
|
"peerDependenciesMeta": {
|
|
36
|
-
"bun-types": {
|
|
47
|
+
"bun-types": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
37
50
|
},
|
|
38
51
|
"devDependencies": {
|
|
39
52
|
"@types/bun": "latest"
|
|
@@ -4,7 +4,7 @@ import { join } from "path";
|
|
|
4
4
|
import { Database } from "bun:sqlite";
|
|
5
5
|
|
|
6
6
|
const dbPath = `/tmp/test-pi-ltm-${process.pid}-${Date.now()}.db`;
|
|
7
|
-
const SCHEMA_PATH = join(import.meta.dir, "..", "..", "..", "
|
|
7
|
+
const SCHEMA_PATH = join(import.meta.dir, "..", "..", "..", "openltm-core", "src", "schema.sql");
|
|
8
8
|
|
|
9
9
|
function createMockPi() {
|
|
10
10
|
const tools: Array<{
|
|
@@ -27,7 +27,7 @@ function createMockPi() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
beforeAll(async () => {
|
|
30
|
-
const { runPendingMigrations, _setDbForTesting } = await import("@rohirik/
|
|
30
|
+
const { runPendingMigrations, _setDbForTesting } = await import("@rohirik/openltm-core");
|
|
31
31
|
const db = new Database(dbPath, { create: true });
|
|
32
32
|
db.exec("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;");
|
|
33
33
|
db.exec(readFileSync(SCHEMA_PATH, "utf-8"));
|
|
@@ -80,7 +80,7 @@ describe("Pi LTM extension — registerHooks()", () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
it("before_agent_start returns systemPrompt with Prior Knowledge when memories exist", async () => {
|
|
83
|
-
const { learn } = await import("@rohirik/
|
|
83
|
+
const { learn } = await import("@rohirik/openltm-core");
|
|
84
84
|
learn({
|
|
85
85
|
content: "Pi test memory — use strict types",
|
|
86
86
|
category: "pattern",
|
package/src/hooks.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Pi extension entry point.
|
|
3
3
|
*
|
|
4
4
|
* Pi loads extensions with Node.js, not Bun — so bun:sqlite is unavailable.
|
|
5
|
-
* We bridge LTM tools by spawning the
|
|
5
|
+
* We bridge LTM tools by spawning the openltm-core MCP server as a Bun child
|
|
6
6
|
* process and proxying calls via newline-delimited JSON-RPC (MCP stdio transport).
|
|
7
7
|
*
|
|
8
8
|
* Pattern adapted from context-mode's Pi adapter (MIT).
|
|
@@ -59,10 +59,10 @@ function findMcpServer(): string | null {
|
|
|
59
59
|
// continue to next strategy
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
// 2.
|
|
62
|
+
// 2. openltm-core package (if mcp-server is added in a future version)
|
|
63
63
|
try {
|
|
64
64
|
const req = createRequire(import.meta.url);
|
|
65
|
-
const pkgJson = req.resolve("@rohirik/
|
|
65
|
+
const pkgJson = req.resolve("@rohirik/openltm-core/package.json");
|
|
66
66
|
const script = resolve(dirname(pkgJson), "src", "mcp-server.ts");
|
|
67
67
|
if (existsSync(script)) return script;
|
|
68
68
|
} catch {
|
package/src/tools.ts
CHANGED