diff --git a/README.md b/README.md index cf83f24..5e11bb6 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ This installer: Preinstalled package sources are: ```json -["npm:@e9n/pi-channels", "npm:pi-memory-md", "npm:pi-teams"] +["npm:@e9n/pi-channels", "npm:pi-teams"] ``` If `npm` is available, it also installs these packages during install. diff --git a/package-lock.json b/package-lock.json index b928aa2..fff923f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -959,6 +959,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -975,6 +976,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -991,6 +993,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1007,6 +1010,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1023,6 +1027,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1039,6 +1044,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1055,6 +1061,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1071,6 +1078,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1087,6 +1095,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1103,6 +1112,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1119,6 +1129,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1135,6 +1146,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1151,6 +1163,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1167,6 +1180,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1183,6 +1197,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1199,6 +1214,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1215,6 +1231,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1231,6 +1248,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1247,6 +1265,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1263,6 +1282,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1279,6 +1299,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1295,6 +1316,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1311,6 +1333,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1327,6 +1350,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1343,6 +1367,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1359,6 +1384,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1391,30 +1417,11 @@ } } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1424,92 +1431,9 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", - "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", - "license": "BSD-3-Clause" - }, - "node_modules/@lit/reactive-element": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", - "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.5.0" - } - }, - "node_modules/@lmstudio/lms-isomorphic": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@lmstudio/lms-isomorphic/-/lms-isomorphic-0.4.6.tgz", - "integrity": "sha512-v0LIjXKnDe3Ff3XZO5eQjlVxTjleUHXaom14MV7QU9bvwaoo3l5p71+xJ3mmSaqZq370CQ6pTKCn1Bb7Jf+VwQ==", - "license": "Apache-2.0", - "dependencies": { - "ws": "^8.16.0" - } - }, - "node_modules/@lmstudio/sdk": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@lmstudio/sdk/-/sdk-1.5.0.tgz", - "integrity": "sha512-fdY12x4hb14PEjYijh7YeCqT1ZDY5Ok6VR4l4+E/dI+F6NW8oB+P83Sxed5vqE4XgTzbgyPuSR2ZbMNxxF+6jA==", - "license": "Apache-2.0", - "dependencies": { - "@lmstudio/lms-isomorphic": "^0.4.6", - "chalk": "^4.1.2", - "jsonschema": "^1.5.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.5" - } - }, - "node_modules/@lmstudio/sdk/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@lmstudio/sdk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lmstudio/sdk/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@mariozechner/clipboard": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", @@ -1702,48 +1626,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/@mariozechner/mini-lit": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@mariozechner/mini-lit/-/mini-lit-0.2.1.tgz", - "integrity": "sha512-u300euLgCsDDlb8o2Wbz+55eSJga5X2vB58s9XBuFIr2Bi3iI+GMR7t/NYo/O6Vr6obXShXgYjR3SRUJVgo+kQ==", - "dependencies": { - "@preact/signals-core": "^1.12.1", - "class-variance-authority": "^0.7.1", - "diff": "^8.0.2", - "highlight.js": "^11.11.1", - "html-parse-string": "^0.0.9", - "katex": "^0.16.22", - "lucide": "^0.544.0", - "marked": "^16.3.0", - "tailwind-merge": "^3.3.1", - "tailwind-variants": "^3.1.1", - "uhtml": "^5.0.9" - }, - "peerDependencies": { - "lit": "^3.3.1" - } - }, - "node_modules/@mariozechner/mini-lit/node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@mariozechner/mini-lit/node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/@mariozechner/pi-agent-core": { "resolved": "packages/agent", "link": true @@ -1770,256 +1652,6 @@ "zod-to-json-schema": "^3.24.1" } }, - "node_modules/@napi-rs/canvas": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.96.tgz", - "integrity": "sha512-6NNmNxvoJKeucVjxaaRUt3La2i5jShgiAbaY3G/72s1Vp3U06XPrAIxkAjBxpDcamEn/t+WJ4OOlGmvILo4/Ew==", - "license": "MIT", - "optional": true, - "workspaces": [ - "e2e/*" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.96", - "@napi-rs/canvas-darwin-arm64": "0.1.96", - "@napi-rs/canvas-darwin-x64": "0.1.96", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.96", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.96", - "@napi-rs/canvas-linux-arm64-musl": "0.1.96", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.96", - "@napi-rs/canvas-linux-x64-gnu": "0.1.96", - "@napi-rs/canvas-linux-x64-musl": "0.1.96", - "@napi-rs/canvas-win32-arm64-msvc": "0.1.96", - "@napi-rs/canvas-win32-x64-msvc": "0.1.96" - } - }, - "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.96.tgz", - "integrity": "sha512-ew1sPrN3dGdZ3L4FoohPfnjq0f9/Jk7o+wP7HkQZokcXgIUD6FIyICEWGhMYzv53j63wUcPvZeAwgewX58/egg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.96.tgz", - "integrity": "sha512-Q/wOXZ5PzTqpdmA5eUOcegCf4Go/zz3aZ5DlzSeDpOjFmfwMKh8EzLAoweQ+mJVagcHQyzoJhaTEnrO68TNyNg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.96.tgz", - "integrity": "sha512-UrXiQz28tQEvGM1qvyptewOAfmUrrd5+wvi6Rzjj2VprZI8iZ2KIvBD2lTTG1bVF95AbeDeG7PJA0D9sLKaOFA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.96.tgz", - "integrity": "sha512-I90ODxweD8aEP6XKU/NU+biso95MwCtQ2F46dUvhec1HesFi0tq/tAJkYic/1aBSiO/1kGKmSeD1B0duOHhEHQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.96.tgz", - "integrity": "sha512-Dx/0+RFV++w3PcRy+4xNXkghhXjA5d0Mw1bs95emn5Llinp1vihMaA6WJt3oYv2LAHc36+gnrhIBsPhUyI2SGw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.96.tgz", - "integrity": "sha512-UvOi7fii3IE2KDfEfhh8m+LpzSRvhGK7o1eho99M2M0HTik11k3GX+2qgVx9EtujN3/bhFFS1kSO3+vPMaJ0Mg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.96.tgz", - "integrity": "sha512-MBSukhGCQ5nRtf9NbFYWOU080yqkZU1PbuH4o1ROvB4CbPl12fchDR35tU83Wz8gWIM9JTn99lBn9DenPIv7Ig==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.96.tgz", - "integrity": "sha512-I/ccu2SstyKiV3HIeVzyBIWfrJo8cN7+MSQZPnabewWV6hfJ2nY7Df2WqOHmobBRUw84uGR6zfQHsUEio/m5Vg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.96.tgz", - "integrity": "sha512-H3uov7qnTl73GDT4h52lAqpJPsl1tIUyNPWJyhQ6gHakohNqqRq3uf80+NEpzcytKGEOENP1wX3yGwZxhjiWEQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-win32-arm64-msvc": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.96.tgz", - "integrity": "sha512-ATp6Y+djOjYtkfV/VRH7CZ8I1MEtkUQBmKUbuWw5zWEHHqfL0cEcInE4Cxgx7zkNAhEdBbnH8HMVrqNp+/gwxA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.96.tgz", - "integrity": "sha512-UYGdTltVd+Z8mcIuoqGmAXXUvwH5CLf2M6mIB5B0/JmX5J041jETjqtSYl7gN+aj3k1by/SG6sS0hAwCqyK7zw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2058,338 +1690,6 @@ "node": ">= 8" } }, - "node_modules/@parcel/watcher": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", - "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", - "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", - "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", - "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", - "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", - "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", - "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", - "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", - "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", - "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", - "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@preact/signals-core": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.13.0.tgz", - "integrity": "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2461,6 +1761,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2474,6 +1775,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2487,6 +1789,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2500,6 +1803,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2513,6 +1817,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2526,6 +1831,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2539,6 +1845,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2552,6 +1859,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2565,6 +1873,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2578,6 +1887,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2591,6 +1901,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2604,6 +1915,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2617,6 +1929,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2630,6 +1943,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2643,6 +1957,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2656,6 +1971,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2669,6 +1985,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2682,6 +1999,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2695,6 +2013,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2708,6 +2027,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2721,6 +2041,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2734,6 +2055,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2747,6 +2069,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2760,6 +2083,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2773,6 +2097,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3513,282 +2838,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tailwindcss/cli": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.1.tgz", - "integrity": "sha512-b7MGn51IA80oSG+7fuAgzfQ+7pZBgjzbqwmiv6NO7/+a1sev32cGqnwhscT7h0EcAvMa9r7gjRylqOH8Xhc4DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/watcher": "^2.5.1", - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", - "enhanced-resolve": "^5.19.0", - "mri": "^1.2.0", - "picocolors": "^1.1.1", - "tailwindcss": "4.2.1" - }, - "bin": { - "tailwindcss": "dist/index.mjs" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.31.1", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", - "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", - "tailwindcss": "4.2.1" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", @@ -3875,6 +2924,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/hosted-git-info": { @@ -3922,12 +2972,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -4179,15 +3223,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@webreflection/alien-signals": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@webreflection/alien-signals/-/alien-signals-0.3.2.tgz", - "integrity": "sha512-DmNjD8Kq5iM+Toirp3llS/izAiI3Dwav5nHRvKdR/YJBTgun3y4xK76rs9CFYD2bZwZJN/rP+HjEqKTteGK+Yw==", - "license": "MIT", - "dependencies": { - "alien-signals": "^2.0.6" - } - }, "node_modules/@xterm/headless": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.5.0.tgz", @@ -4270,12 +3305,6 @@ } } }, - "node_modules/alien-signals": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.8.tgz", - "integrity": "sha512-844G1VLkk0Pe2SJjY0J8vp8ADI73IM4KliNu2OGlYzWpO28NexEUvjHTcFjFX3VXoiUtwTbHxLNI9ImkcoBqzA==", - "license": "MIT" - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -4316,15 +3345,6 @@ "dev": true, "license": "MIT" }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -4578,18 +3598,6 @@ "dev": true, "license": "ISC" }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, "node_modules/cli-highlight": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", @@ -4671,15 +3679,6 @@ "node": ">=8" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4832,12 +3831,6 @@ "node": ">=12" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -4961,6 +3954,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -4975,15 +3969,6 @@ "node": ">=0.3.1" } }, - "node_modules/docx-preview": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.7.tgz", - "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", - "license": "Apache-2.0", - "dependencies": { - "jszip": ">=3.0.0" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5022,19 +4007,6 @@ "once": "^1.4.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -5091,6 +4063,7 @@ "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -5273,18 +4246,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -5494,6 +4455,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5619,7 +4581,7 @@ "version": "4.13.6", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -5732,21 +4694,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5816,12 +4763,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/html-parse-string": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/html-parse-string/-/html-parse-string-0.0.9.tgz", - "integrity": "sha512-wyGnsOolHbNrcb8N6bdJF4EHyzd3zVGCb9/mBxeNjAYBDOZqD7YkqLBz7kXtdgHwNnV8lN/BpSDpsI1zm8Sd8g==", - "license": "MIT" - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5893,16 +4834,11 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -5953,15 +4889,6 @@ "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", "license": "MIT" }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6016,12 +4943,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6033,7 +4954,10 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -6045,19 +4969,6 @@ "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -6086,57 +4997,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/jsonschema": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", - "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -6158,40 +5018,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/katex": { - "version": "0.16.35", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.35.tgz", - "integrity": "sha512-S0+riEvy1CK4VKse1ivMff8gmabe/prY7sKB3njjhyoLLsNFDQYtKNgXrbWUggGDCJBz7Fctl5i8fLCESHXzSg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/koffi": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.1.tgz", @@ -6203,20 +5029,14 @@ "url": "https://liberapay.com/Koromix" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, "license": "MPL-2.0", + "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -6248,11 +5068,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6268,11 +5090,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6288,11 +5112,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6308,11 +5134,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6328,11 +5156,13 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6348,11 +5178,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6368,11 +5200,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6388,11 +5222,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6408,11 +5244,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6428,11 +5266,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6448,11 +5288,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6461,37 +5303,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lit": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", - "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit/reactive-element": "^2.1.0", - "lit-element": "^4.2.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-element": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", - "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.5.0", - "@lit/reactive-element": "^2.1.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-html": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", - "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", - "license": "BSD-3-Clause", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -6514,16 +5325,11 @@ "node": "20 || >=22" } }, - "node_modules/lucide": { - "version": "0.544.0", - "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.544.0.tgz", - "integrity": "sha512-U5ORwr5z9Sx7bNTDFaW55RbjVdQEnAcT3vws9uz3vRT1G4XXJUDAhRZdxhFoIyHEvjmTkzzlEhjSLYM5n4mb5w==", - "license": "ISC" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -6656,16 +5462,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6687,6 +5483,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -6815,15 +5612,6 @@ ], "license": "MIT" }, - "node_modules/ollama": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.6.3.tgz", - "integrity": "sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==", - "license": "MIT", - "dependencies": { - "whatwg-fetch": "^3.6.20" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6948,12 +5736,6 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -7031,28 +5813,12 @@ "node": ">= 14.16" } }, - "node_modules/pdfjs-dist": { - "version": "5.4.394", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.394.tgz", - "integrity": "sha512-9ariAYGqUJzx+V/1W4jHyiyCep6IZALmDzoaTLZ6VNu8q9LWi1/ukhzHgE2Xsx96AZi0mbZuK4/ttIbqSbLypg==", - "license": "Apache-2.0", - "engines": { - "node": ">=20.16.0 || >=22.3.0" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^0.1.81" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "license": "MIT" }, - "node_modules/pi-memory-md": { - "resolved": "packages/pi-memory-md", - "link": true - }, "node_modules/pi-teams": { "resolved": "packages/pi-teams", "link": true @@ -7061,6 +5827,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -7080,6 +5847,7 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7132,12 +5900,6 @@ "node": ">=10" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -7333,7 +6095,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -7382,6 +6144,7 @@ "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -7476,19 +6239,6 @@ ], "license": "MIT" }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -7502,12 +6252,6 @@ "node": ">=10" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -7692,17 +6436,12 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -7776,15 +6515,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -7875,54 +6605,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tailwind-merge": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", - "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwind-variants": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz", - "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==", - "license": "MIT", - "engines": { - "node": ">=16.x", - "pnpm": ">=7.x" - }, - "peerDependencies": { - "tailwind-merge": ">=3.0.0", - "tailwindcss": "*" - }, - "peerDependenciesMeta": { - "tailwind-merge": { - "optional": true - } - } - }, - "node_modules/tailwindcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -7992,6 +6674,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -8008,6 +6691,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8025,6 +6709,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8174,7 +6859,7 @@ "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.27.0", @@ -8217,15 +6902,6 @@ "node": ">=14.17" } }, - "node_modules/uhtml": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/uhtml/-/uhtml-5.0.9.tgz", - "integrity": "sha512-qPyu3vGilaLe6zrjOCD/xezWEHLwdevxmbY3hzyhT25KBDF4F7YYW3YZcL3kylD/6dMoVISHjn8ggV3+9FY+5g==", - "license": "MIT", - "dependencies": { - "@webreflection/alien-signals": "^0.3.2" - } - }, "node_modules/uint8array-extras": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", @@ -8257,6 +6933,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/uuid": { @@ -8283,6 +6960,7 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0", @@ -8380,6 +7058,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8397,6 +7076,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8500,12 +7180,6 @@ "node": ">= 8" } }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT" - }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -8601,18 +7275,6 @@ } } }, - "node_modules/xlsx": { - "version": "0.20.3", - "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", - "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", - "license": "Apache-2.0", - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -8933,66 +7595,6 @@ "@sinclair/typebox": "*" } }, - "packages/pi-memory-md": { - "version": "0.1.1", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3" - }, - "devDependencies": { - "@mariozechner/pi-coding-agent": "latest", - "@types/node": "^20.0.0", - "husky": "^9.1.7", - "typescript": "^5.0.0" - } - }, - "packages/pi-memory-md/node_modules/@mariozechner/pi-coding-agent": { - "version": "0.56.2", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.56.2.tgz", - "integrity": "sha512-svK9zg5f+I4yko57MzdfBQBqZpFT1Hr8nZ3o7nYMTuIFcf2vABylA8lNI57Avjg38js1PToc6jXXFa/3JWqELg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.56.2", - "@mariozechner/pi-ai": "^0.56.2", - "@mariozechner/pi-tui": "^0.56.2", - "@silvia-odwyer/photon-node": "^0.3.4", - "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", - "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", - "glob": "^13.0.1", - "hosted-git-info": "^9.0.2", - "ignore": "^7.0.5", - "marked": "^15.0.12", - "minimatch": "^10.2.3", - "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", - "undici": "^7.19.1", - "yaml": "^2.8.2" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=20.6.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.2" - } - }, - "packages/pi-memory-md/node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, "packages/pi-runtime-daemon": { "name": "@local/pi-runtime-daemon", "version": "0.0.1", diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index a4d13d3..c3c0696 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -61,6 +61,15 @@ import { type ToolHtmlRenderer, } from "./export-html/index.js"; import { createToolHtmlRenderer } from "./export-html/tool-renderer.js"; +import { + RuntimeMemoryManager, + type RuntimeMemoryForgetInput, + type RuntimeMemoryRebuildResult, + type RuntimeMemoryRememberInput, + type RuntimeMemorySearchResult, + type RuntimeMemoryStatus, + type RuntimeMemoryRecord, +} from "./memory/runtime-memory.js"; import { type ContextUsage, type ExtensionCommandContextActions, @@ -337,6 +346,9 @@ export class AgentSession { // Base system prompt (without extension appends) - used to apply fresh appends each turn private _baseSystemPrompt = ""; + private _memoryManager: RuntimeMemoryManager; + private _memoryWriteQueue: Promise = Promise.resolve(); + private _memoryDisposePromise: Promise | null = null; constructor(config: AgentSessionConfig) { this.agent = config.agent; @@ -350,6 +362,10 @@ export class AgentSession { this._extensionRunnerRef = config.extensionRunnerRef; this._initialActiveToolNames = config.initialActiveToolNames; this._baseToolsOverride = config.baseToolsOverride; + this._memoryManager = new RuntimeMemoryManager({ + sessionManager: this.sessionManager, + settingsManager: this.settingsManager, + }); // Always subscribe to agent events for internal handling // (session persistence, extensions, auto-compaction, retry logic) @@ -499,6 +515,16 @@ export class AgentSession { this._resolveRetry(); } } + + if (event.message.role === "user" || event.message.role === "assistant") { + try { + this._memoryManager.recordMessage(event.message); + } catch (error) { + const message = + error instanceof Error ? error.message : String(error); + console.error(`[memory] episode write failed: ${message}`); + } + } } // Check auto-retry and auto-compaction after agent completes @@ -513,6 +539,10 @@ export class AgentSession { } await this._checkCompaction(msg); + + if (msg.stopReason !== "error") { + this._enqueueMemoryPromotion([...this.agent.state.messages]); + } } } @@ -667,6 +697,7 @@ export class AgentSession { dispose(): void { this._disconnectFromAgent(); this._eventListeners = []; + void this._disposeMemoryManager(); } // ========================================================================= @@ -804,6 +835,107 @@ export class AgentSession { return this._resourceLoader.getPrompts().prompts; } + async transformRuntimeContext( + messages: AgentMessage[], + signal?: AbortSignal, + ): Promise { + await this._awaitMemoryWrites(); + return this._memoryManager.injectContext(messages, { signal }); + } + + async getMemoryStatus(): Promise { + await this._awaitMemoryWrites(); + return this._memoryManager.getStatus(); + } + + async getCoreMemories(): Promise { + await this._awaitMemoryWrites(); + return this._memoryManager.listCoreMemories(); + } + + async searchMemory( + query: string, + limit?: number, + ): Promise { + await this._awaitMemoryWrites(); + return this._memoryManager.search(query, limit); + } + + async rememberMemory( + input: RuntimeMemoryRememberInput, + ): Promise { + await this._awaitMemoryWrites(); + return this._memoryManager.remember(input); + } + + async forgetMemory( + input: RuntimeMemoryForgetInput, + ): Promise<{ ok: true; forgotten: boolean }> { + await this._awaitMemoryWrites(); + return this._memoryManager.forget(input); + } + + async rebuildMemory(): Promise { + await this._awaitMemoryWrites(); + return this._memoryManager.rebuild(); + } + + private async _awaitMemoryWrites(): Promise { + try { + await this._memoryWriteQueue; + } catch { + // Memory writes are best-effort; failures should not block chat. + } + } + + private async _disposeMemoryManager(): Promise { + if (this._memoryDisposePromise) { + await this._memoryDisposePromise; + return; + } + + this._memoryDisposePromise = (async () => { + try { + await this._agentEventQueue; + } catch { + // Event processing failures should not block shutdown. + } + + try { + await this._memoryWriteQueue; + } catch { + // Memory writes are best-effort during shutdown too. + } + + this._memoryManager.dispose(); + })(); + + await this._memoryDisposePromise; + } + + private _enqueueMemoryPromotion(messages: AgentMessage[]): void { + this._memoryWriteQueue = this._memoryWriteQueue + .catch(() => undefined) + .then(async () => { + if (!this.model) { + return; + } + const apiKey = await this._modelRegistry.getApiKey(this.model); + if (!apiKey) { + return; + } + await this._memoryManager.promoteTurn({ + model: this.model, + apiKey, + messages, + }); + }) + .catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + console.error(`[memory] promotion failed: ${message}`); + }); + } + private _normalizePromptSnippet( text: string | undefined, ): string | undefined { diff --git a/packages/coding-agent/src/core/gateway/runtime.ts b/packages/coding-agent/src/core/gateway/runtime.ts index 91491da..8623047 100644 --- a/packages/coding-agent/src/core/gateway/runtime.ts +++ b/packages/coding-agent/src/core/gateway/runtime.ts @@ -297,6 +297,16 @@ export class GatewayRuntime { return managedSession; } + private async resolveMemorySession( + sessionKey: string | null | undefined, + ): Promise { + if (!sessionKey || sessionKey === this.primarySessionKey) { + return this.primarySession; + } + const managedSession = await this.ensureSession(sessionKey); + return managedSession.session; + } + private async processNext( managedSession: ManagedGatewaySession, ): Promise { @@ -624,6 +634,102 @@ export class GatewayRuntime { return; } + if (method === "GET" && path === "/memory/status") { + const sessionKey = url.searchParams.get("sessionKey"); + const memorySession = await this.resolveMemorySession(sessionKey); + const memory = await memorySession.getMemoryStatus(); + this.writeJson(response, 200, { memory }); + return; + } + + if (method === "GET" && path === "/memory/core") { + const sessionKey = url.searchParams.get("sessionKey"); + const memorySession = await this.resolveMemorySession(sessionKey); + const memories = await memorySession.getCoreMemories(); + this.writeJson(response, 200, { memories }); + return; + } + + if (method === "POST" && path === "/memory/search") { + const body = await this.readJsonBody(request); + const query = typeof body.query === "string" ? body.query : ""; + const limit = + typeof body.limit === "number" && Number.isFinite(body.limit) + ? Math.max(1, Math.floor(body.limit)) + : undefined; + const sessionKey = + typeof body.sessionKey === "string" ? body.sessionKey : undefined; + const memorySession = await this.resolveMemorySession(sessionKey); + const result = await memorySession.searchMemory(query, limit); + this.writeJson(response, 200, result); + return; + } + + if (method === "POST" && path === "/memory/remember") { + const body = await this.readJsonBody(request); + const content = typeof body.content === "string" ? body.content : ""; + if (!content.trim()) { + this.writeJson(response, 400, { error: "Missing memory content" }); + return; + } + const sessionKey = + typeof body.sessionKey === "string" ? body.sessionKey : undefined; + const memorySession = await this.resolveMemorySession(sessionKey); + const memory = await memorySession.rememberMemory({ + bucket: + body.bucket === "core" || body.bucket === "archival" + ? body.bucket + : undefined, + kind: + body.kind === "profile" || + body.kind === "preference" || + body.kind === "relationship" || + body.kind === "fact" || + body.kind === "secret" + ? body.kind + : undefined, + key: typeof body.key === "string" ? body.key : undefined, + content, + source: "manual", + }); + this.writeJson(response, 200, { ok: true, memory }); + return; + } + + if (method === "POST" && path === "/memory/forget") { + const body = await this.readJsonBody(request); + const id = + typeof body.id === "number" && Number.isFinite(body.id) + ? Math.floor(body.id) + : undefined; + const key = typeof body.key === "string" ? body.key : undefined; + if (id === undefined && !key) { + this.writeJson(response, 400, { + error: "Memory forget requires an id or key", + }); + return; + } + const sessionKey = + typeof body.sessionKey === "string" ? body.sessionKey : undefined; + const memorySession = await this.resolveMemorySession(sessionKey); + const result = await memorySession.forgetMemory({ + id, + key, + }); + this.writeJson(response, 200, result); + return; + } + + if (method === "POST" && path === "/memory/rebuild") { + const body = await this.readJsonBody(request); + const sessionKey = + typeof body.sessionKey === "string" ? body.sessionKey : undefined; + const memorySession = await this.resolveMemorySession(sessionKey); + const result = await memorySession.rebuildMemory(); + this.writeJson(response, 200, result); + return; + } + const sessionMatch = path.match( /^\/sessions\/([^/]+)(?:\/(events|messages|abort|reset|chat|history|model|reload))?$/, ); diff --git a/packages/coding-agent/src/core/memory/runtime-memory.ts b/packages/coding-agent/src/core/memory/runtime-memory.ts new file mode 100644 index 0000000..f249f4f --- /dev/null +++ b/packages/coding-agent/src/core/memory/runtime-memory.ts @@ -0,0 +1,1619 @@ +import { createHash } from "node:crypto"; +import { + existsSync, + mkdirSync, + readdirSync, + readFileSync, + statSync, +} from "node:fs"; +import { createRequire } from "node:module"; +import { homedir } from "node:os"; +import { basename, join, resolve } from "node:path"; +import type { AgentMessage } from "@mariozechner/pi-agent-core"; +import { + completeSimple, + type Model, + type TextContent, +} from "@mariozechner/pi-ai"; +import { parseFrontmatter } from "../../utils/frontmatter.js"; +import type { SettingsManager } from "../settings-manager.js"; +import type { ReadonlySessionManager } from "../session-manager.js"; + +const DEFAULT_STORAGE_DIR = join(homedir(), ".pi", "memory"); +const MAX_EPISODE_CHARS = 4_000; +const MAX_EPISODES = 5_000; +const DEFAULT_CORE_TOKEN_BUDGET = 700; +const DEFAULT_RECALL_RESULTS = 4; +const DEFAULT_WRITER_MAX_TOKENS = 600; +const CUSTOM_MEMORY_TYPE = "companion_memory"; +const require = createRequire(import.meta.url); + +const MEMORY_WRITER_SYSTEM_PROMPT = `You manage long-term conversational memory for a companion agent. + +Decide which facts from the latest exchange should be persisted for future chats. + +Rules: +- Save only information grounded in the user or assistant messages. +- Prefer durable facts, explicit remember requests, stable preferences, relationship context, and secrets/keys/codes the user will expect the companion to recall later. +- Use bucket "core" only for stable profile, preference, or relationship memory. +- Use bucket "archival" for facts and secrets that should be searchable later. +- Never invent details or infer beyond the exchange. +- If nothing should be saved, return {"memories":[]}. + +Return strict JSON with this shape: +{"memories":[{"bucket":"core"|"archival","kind":"profile"|"preference"|"relationship"|"fact"|"secret","key":"stable-memory-slot","content":"memory text"}]}`; + +export type RuntimeMemoryBucket = "core" | "archival"; +export type RuntimeMemoryKind = + | "profile" + | "preference" + | "relationship" + | "fact" + | "secret"; +export type RuntimeMemorySource = "auto" | "manual" | "legacy-import"; + +export interface CompanionMemorySettings { + enabled?: boolean; + storageDir?: string; + maxCoreTokens?: number; + maxRecallResults?: number; + writer?: { + enabled?: boolean; + maxTokens?: number; + }; +} + +export interface RuntimeMemoryIdentity { + key: string; + scope: "agent" | "companion" | "sandbox" | "unknown"; +} + +export interface RuntimeMemoryStatus { + enabled: boolean; + ready: boolean; + identity: RuntimeMemoryIdentity | null; + storagePath: string | null; + coreCount: number; + archivalCount: number; + episodeCount: number; + lastMemoryWriteAt: number | null; + lastEpisodeAt: number | null; + legacyImportComplete: boolean; +} + +export interface RuntimeMemoryRecord { + id: number; + bucket: RuntimeMemoryBucket; + kind: RuntimeMemoryKind; + key: string; + content: string; + source: RuntimeMemorySource; + createdAt: number; + updatedAt: number; + lastAccessedAt: number | null; +} + +export interface RuntimeMemoryRememberInput { + bucket?: RuntimeMemoryBucket; + kind?: RuntimeMemoryKind; + key?: string; + content: string; + source?: RuntimeMemorySource; +} + +export interface RuntimeMemoryForgetInput { + id?: number; + key?: string; +} + +export interface RuntimeMemorySearchResultItem { + id: number; + sourceType: "memory" | "episode"; + score: number; + kind?: RuntimeMemoryKind; + bucket?: RuntimeMemoryBucket; + key?: string; + content: string; + role?: "user" | "assistant"; + timestamp: number; + source?: RuntimeMemorySource; +} + +export interface RuntimeMemorySearchResult { + query: string; + results: RuntimeMemorySearchResultItem[]; +} + +export interface RuntimeMemoryRebuildResult { + ok: true; + memoryRows: number; + episodeRows: number; +} + +interface MemoryRow { + id: number; + bucket: RuntimeMemoryBucket; + kind: RuntimeMemoryKind; + memory_key: string; + content: string; + source: RuntimeMemorySource; + created_at: number; + updated_at: number; + last_accessed_at: number | null; + search_text: string; +} + +interface EpisodeRow { + id: number; + role: "user" | "assistant"; + text: string; + timestamp: number; + search_text: string; +} + +interface MemoryWriterResponse { + memories?: Array<{ + bucket?: unknown; + kind?: unknown; + key?: unknown; + content?: unknown; + }>; +} + +interface LegacyMemoryFile { + path: string; + body: string; +} + +interface SqliteStatementResult { + changes: number; + lastInsertRowid: number | bigint; +} + +interface SqliteStatement { + run(...args: unknown[]): SqliteStatementResult; + get(...args: unknown[]): unknown; + all(...args: unknown[]): unknown[]; +} + +interface SqliteDatabase { + exec(sql: string): void; + prepare(sql: string): SqliteStatement; + close(): void; +} + +type SqliteDatabaseConstructor = new (path: string) => SqliteDatabase; + +let cachedSqliteDatabaseConstructor: + | SqliteDatabaseConstructor + | null + | undefined; + +function loadSqliteDatabaseConstructor(): SqliteDatabaseConstructor | null { + if (cachedSqliteDatabaseConstructor !== undefined) { + return cachedSqliteDatabaseConstructor; + } + + try { + const sqliteModule = require("node:sqlite") as { + DatabaseSync?: SqliteDatabaseConstructor; + }; + cachedSqliteDatabaseConstructor = + typeof sqliteModule.DatabaseSync === "function" + ? sqliteModule.DatabaseSync + : null; + } catch { + cachedSqliteDatabaseConstructor = null; + } + + return cachedSqliteDatabaseConstructor; +} + +function asRecord(value: unknown): Record | null { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + return null; + } + return value as Record; +} + +function asString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 + ? value.trim() + : null; +} + +function expandHomePath(value: string): string { + if (!value.startsWith("~")) { + return value; + } + return join(homedir(), value.slice(1)); +} + +function getCompanionMemorySettings( + settingsManager: SettingsManager, +): Required & { + writer: { enabled: boolean; maxTokens: number }; +} { + const globalSettings = asRecord(settingsManager.getGlobalSettings()) ?? {}; + const projectSettings = asRecord(settingsManager.getProjectSettings()) ?? {}; + const globalMemory = asRecord(globalSettings.companionMemory) ?? {}; + const projectMemory = asRecord(projectSettings.companionMemory) ?? {}; + + const enabled = + typeof projectMemory.enabled === "boolean" + ? projectMemory.enabled + : typeof globalMemory.enabled === "boolean" + ? globalMemory.enabled + : true; + const storageDir = + asString(projectMemory.storageDir) ?? + asString(globalMemory.storageDir) ?? + DEFAULT_STORAGE_DIR; + const maxCoreTokens = + (typeof projectMemory.maxCoreTokens === "number" + ? projectMemory.maxCoreTokens + : typeof globalMemory.maxCoreTokens === "number" + ? globalMemory.maxCoreTokens + : DEFAULT_CORE_TOKEN_BUDGET) || DEFAULT_CORE_TOKEN_BUDGET; + const maxRecallResults = + (typeof projectMemory.maxRecallResults === "number" + ? projectMemory.maxRecallResults + : typeof globalMemory.maxRecallResults === "number" + ? globalMemory.maxRecallResults + : DEFAULT_RECALL_RESULTS) || DEFAULT_RECALL_RESULTS; + + const globalWriter = asRecord(globalMemory.writer) ?? {}; + const projectWriter = asRecord(projectMemory.writer) ?? {}; + const writerEnabled = + typeof projectWriter.enabled === "boolean" + ? projectWriter.enabled + : typeof globalWriter.enabled === "boolean" + ? globalWriter.enabled + : true; + const writerMaxTokens = + (typeof projectWriter.maxTokens === "number" + ? projectWriter.maxTokens + : typeof globalWriter.maxTokens === "number" + ? globalWriter.maxTokens + : DEFAULT_WRITER_MAX_TOKENS) || DEFAULT_WRITER_MAX_TOKENS; + + return { + enabled, + storageDir: expandHomePath(storageDir), + maxCoreTokens, + maxRecallResults, + writer: { + enabled: writerEnabled, + maxTokens: writerMaxTokens, + }, + }; +} + +function normalizeWhitespace(value: string): string { + return value.replace(/\s+/g, " ").trim(); +} + +function normalizeSearchText(value: string): string { + return normalizeWhitespace( + value + .toLowerCase() + .replace(/[`"'()[\]{}<>]/g, " ") + .replace(/[^a-z0-9._:/+-]+/g, " "), + ); +} + +function tokenize(value: string): string[] { + const seen = new Set(); + for (const token of normalizeSearchText(value).split(" ")) { + if (token.length < 2) { + continue; + } + seen.add(token); + } + return Array.from(seen); +} + +function estimateTextTokens(value: string): number { + return Math.max(1, Math.ceil(value.length / 4)); +} + +function buildDbFileName(identity: RuntimeMemoryIdentity): string { + const slug = identity.key.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 80); + const hash = createHash("sha256") + .update(identity.key) + .digest("hex") + .slice(0, 12); + return `${slug}-${hash}.sqlite`; +} + +function parseAgentIdFromSessionKey(value: string): string | null { + const match = value.match(/^agent:([^:]+):companion:[^:]+$/); + return match?.[1] ?? null; +} + +function parseAgentIdFromSanitizedSessionKey(value: string): string | null { + if (!value.startsWith("agent_")) { + return null; + } + const marker = "_companion_"; + const markerIndex = value.lastIndexOf(marker); + if (markerIndex <= "agent_".length) { + return null; + } + return value.slice("agent_".length, markerIndex); +} + +function resolveIdentity(params: { + sessionManager: ReadonlySessionManager; + settingsManager: SettingsManager; +}): RuntimeMemoryIdentity | null { + const settings = asRecord(params.settingsManager.getGlobalSettings()) ?? {}; + const sessionDirName = basename(params.sessionManager.getSessionDir()); + const sessionAgentId = parseAgentIdFromSanitizedSessionKey(sessionDirName); + if (sessionAgentId) { + return { key: `agent:${sessionAgentId}`, scope: "agent" }; + } + + const directSessionKey = asString(settings.sessionKey); + const directAgentId = directSessionKey + ? parseAgentIdFromSessionKey(directSessionKey) + : null; + if (directAgentId) { + return { key: `agent:${directAgentId}`, scope: "agent" }; + } + + const companion = asRecord(settings.companion); + const explicitCompanionId = asString(companion?.id); + if (explicitCompanionId) { + return { key: `companion:${explicitCompanionId}`, scope: "companion" }; + } + + const sandboxHandle = asString(settings.sandboxHandle); + if (sandboxHandle) { + return { key: `sandbox:${sandboxHandle}`, scope: "sandbox" }; + } + + return null; +} + +function extractTextFromMessage(message: AgentMessage): string { + if (message.role !== "user" && message.role !== "assistant") { + return ""; + } + + if (typeof message.content === "string") { + return normalizeWhitespace(message.content); + } + + if (!Array.isArray(message.content)) { + return ""; + } + + return normalizeWhitespace( + message.content + .filter((part): part is TextContent => part.type === "text") + .map((part) => part.text) + .join("\n"), + ); +} + +function createSearchText(memory: { + bucket: RuntimeMemoryBucket; + kind: RuntimeMemoryKind; + key: string; + content: string; +}): string { + return normalizeSearchText( + `${memory.bucket} ${memory.kind} ${memory.key} ${memory.content}`, + ); +} + +function createEpisodeSearchText( + role: "user" | "assistant", + text: string, +): string { + return normalizeSearchText(`${role} ${text}`); +} + +function clampText(value: string, maxChars: number): string { + if (value.length <= maxChars) { + return value; + } + return `${value.slice(0, maxChars - 3)}...`; +} + +function trimSnippet(value: string, maxChars = 220): string { + const trimmed = normalizeWhitespace(value); + if (trimmed.length <= maxChars) { + return trimmed; + } + return `${trimmed.slice(0, maxChars - 3)}...`; +} + +function isMemoryBucket(value: unknown): value is RuntimeMemoryBucket { + return value === "core" || value === "archival"; +} + +function isMemoryKind(value: unknown): value is RuntimeMemoryKind { + return ( + value === "profile" || + value === "preference" || + value === "relationship" || + value === "fact" || + value === "secret" + ); +} + +function defaultBucketForKind(kind: RuntimeMemoryKind): RuntimeMemoryBucket { + switch (kind) { + case "profile": + case "preference": + case "relationship": + return "core"; + case "fact": + case "secret": + return "archival"; + } +} + +function normalizeMemoryKey(value: string): string { + const normalized = normalizeSearchText(value).replace(/\s+/g, "-"); + return normalized.length > 0 ? normalized : "memory"; +} + +function unwrapJson(text: string): string { + const fencedMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/i); + if (fencedMatch?.[1]) { + return fencedMatch[1].trim(); + } + return text.trim(); +} + +function scoreCandidate( + searchText: string, + queryTokens: string[], + rawText: string, + timestamp: number, + boost = 0, +): number { + if (queryTokens.length === 0) { + return 1; + } + + let score = boost; + let matchedAll = true; + for (const token of queryTokens) { + if (searchText.includes(token)) { + score += token.length >= 5 ? 16 : 10; + } else { + matchedAll = false; + } + } + + const normalizedRaw = normalizeSearchText(rawText); + const phrase = normalizeSearchText(queryTokens.join(" ")); + if (phrase.length > 0 && normalizedRaw.includes(phrase)) { + score += 24; + } + if (matchedAll) { + score += 14; + } + + const ageDays = Math.max(0, (Date.now() - timestamp) / 86_400_000); + score += Math.max(0, 8 - ageDays / 14); + + return score; +} + +function listMarkdownFiles(rootDir: string): string[] { + if (!existsSync(rootDir)) { + return []; + } + + const results: string[] = []; + const stack = [rootDir]; + while (stack.length > 0) { + const current = stack.pop(); + if (!current) { + continue; + } + for (const entry of readdirSync(current, { withFileTypes: true })) { + const fullPath = join(current, entry.name); + if (entry.isDirectory()) { + stack.push(fullPath); + continue; + } + if (entry.isFile() && entry.name.endsWith(".md")) { + results.push(fullPath); + } + } + } + + return results.sort((left, right) => left.localeCompare(right)); +} + +function readLegacyMemoryFiles(baseDir: string): LegacyMemoryFile[] { + const files = listMarkdownFiles(join(baseDir, "core", "user")); + return files + .map((filePath) => { + try { + const raw = readFileSync(filePath, "utf8"); + const parsed = parseFrontmatter>(raw); + const body = normalizeWhitespace(parsed.body); + if (!body) { + return null; + } + return { path: filePath, body }; + } catch { + return null; + } + }) + .filter((file): file is LegacyMemoryFile => file !== null); +} + +function guessLegacyKind(filePath: string, body: string): RuntimeMemoryKind { + const lowerPath = filePath.toLowerCase(); + const lowerBody = body.toLowerCase(); + if (lowerPath.includes("prefer") || lowerBody.includes("preferences")) { + return "preference"; + } + if ( + lowerPath.includes("identity") || + lowerBody.includes("about your human") || + lowerBody.includes("user identity") + ) { + return "profile"; + } + return "relationship"; +} + +export class RuntimeMemoryManager { + private readonly sessionManager: ReadonlySessionManager; + private readonly settingsManager: SettingsManager; + private readonly settings: Required & { + writer: { enabled: boolean; maxTokens: number }; + }; + private readonly identity: RuntimeMemoryIdentity | null; + private readonly dbPath: string | null; + private readonly database: SqliteDatabase | null; + + constructor(params: { + sessionManager: ReadonlySessionManager; + settingsManager: SettingsManager; + }) { + this.sessionManager = params.sessionManager; + this.settingsManager = params.settingsManager; + this.settings = getCompanionMemorySettings(params.settingsManager); + this.identity = this.settings.enabled ? resolveIdentity(params) : null; + + if (!this.settings.enabled || !this.identity) { + this.dbPath = null; + this.database = null; + return; + } + + this.dbPath = join( + this.settings.storageDir, + buildDbFileName(this.identity), + ); + + const DatabaseSync = loadSqliteDatabaseConstructor(); + if (!DatabaseSync) { + this.database = null; + return; + } + + mkdirSync(this.settings.storageDir, { recursive: true }); + this.database = new DatabaseSync(this.dbPath); + this.database.exec("PRAGMA journal_mode = WAL;"); + this.database.exec("PRAGMA busy_timeout = 5000;"); + this.database.exec("PRAGMA synchronous = NORMAL;"); + this.database.exec(` + CREATE TABLE IF NOT EXISTS memories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + bucket TEXT NOT NULL, + kind TEXT NOT NULL, + memory_key TEXT NOT NULL, + content TEXT NOT NULL, + search_text TEXT NOT NULL, + source TEXT NOT NULL, + active INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + last_accessed_at INTEGER, + superseded_at INTEGER, + superseded_by_id INTEGER + ); + CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_active_key + ON memories(memory_key) + WHERE active = 1; + CREATE INDEX IF NOT EXISTS idx_memories_bucket_active + ON memories(bucket, active, updated_at DESC); + CREATE INDEX IF NOT EXISTS idx_memories_updated_at + ON memories(updated_at DESC); + + CREATE TABLE IF NOT EXISTS episodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT NOT NULL, + session_ref TEXT NOT NULL, + role TEXT NOT NULL, + text TEXT NOT NULL, + search_text TEXT NOT NULL, + timestamp INTEGER NOT NULL + ); + CREATE INDEX IF NOT EXISTS idx_episodes_timestamp + ON episodes(timestamp DESC); + + CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + `); + + this.runLegacyImport(); + } + + dispose(): void { + this.database?.close(); + } + + isEnabled(): boolean { + return this.database !== null && this.identity !== null; + } + + getStatus(): RuntimeMemoryStatus { + if (!this.identity) { + return { + enabled: this.settings.enabled, + ready: false, + identity: null, + storagePath: null, + coreCount: 0, + archivalCount: 0, + episodeCount: 0, + lastMemoryWriteAt: null, + lastEpisodeAt: null, + legacyImportComplete: false, + }; + } + + if (!this.database) { + return { + enabled: this.settings.enabled, + ready: false, + identity: this.identity, + storagePath: this.dbPath, + coreCount: 0, + archivalCount: 0, + episodeCount: 0, + lastMemoryWriteAt: null, + lastEpisodeAt: null, + legacyImportComplete: false, + }; + } + + const counts = this.database + .prepare( + `SELECT + SUM(CASE WHEN bucket = 'core' AND active = 1 THEN 1 ELSE 0 END) AS core_count, + SUM(CASE WHEN bucket = 'archival' AND active = 1 THEN 1 ELSE 0 END) AS archival_count + FROM memories`, + ) + .get() as { core_count?: number | null; archival_count?: number | null }; + const episodeCountRow = this.database + .prepare(`SELECT COUNT(*) AS count FROM episodes`) + .get() as { count: number }; + const lastMemoryWrite = this.database + .prepare(`SELECT MAX(updated_at) AS updated_at FROM memories`) + .get() as { updated_at?: number | null }; + const lastEpisodeWrite = this.database + .prepare(`SELECT MAX(timestamp) AS timestamp FROM episodes`) + .get() as { timestamp?: number | null }; + + return { + enabled: true, + ready: true, + identity: this.identity, + storagePath: this.dbPath, + coreCount: counts.core_count ?? 0, + archivalCount: counts.archival_count ?? 0, + episodeCount: episodeCountRow.count, + lastMemoryWriteAt: lastMemoryWrite.updated_at ?? null, + lastEpisodeAt: lastEpisodeWrite.timestamp ?? null, + legacyImportComplete: + this.getMetadata("legacy_import_complete") === "true", + }; + } + + listCoreMemories(): RuntimeMemoryRecord[] { + if (!this.database) { + return []; + } + + const rows = this.database + .prepare( + `SELECT + id, + bucket, + kind, + memory_key, + content, + source, + created_at, + updated_at, + last_accessed_at, + search_text + FROM memories + WHERE active = 1 AND bucket = 'core' + ORDER BY updated_at DESC, id DESC`, + ) + .all() as MemoryRow[]; + return rows.map((row) => this.mapMemoryRow(row)); + } + + search( + query: string, + limit = DEFAULT_RECALL_RESULTS, + ): RuntimeMemorySearchResult { + if (!this.database) { + return { query, results: [] }; + } + + const queryText = normalizeWhitespace(query); + const queryTokens = tokenize(queryText); + const memoryRows = this.findRelevantMemories( + queryTokens, + Math.max(limit * 4, 12), + ); + const episodeRows = this.findRelevantEpisodes( + queryTokens, + Math.max(limit * 4, 20), + ); + const results: RuntimeMemorySearchResultItem[] = []; + + for (const row of memoryRows) { + const boost = row.kind === "secret" ? 6 : row.bucket === "core" ? 3 : 0; + const score = scoreCandidate( + row.search_text, + queryTokens, + `${row.memory_key} ${row.content}`, + row.updated_at, + boost, + ); + if (score <= 0) { + continue; + } + results.push({ + id: row.id, + sourceType: "memory", + score, + kind: row.kind, + bucket: row.bucket, + key: row.memory_key, + content: row.content, + source: row.source, + timestamp: row.updated_at, + }); + } + + for (const row of episodeRows) { + const score = scoreCandidate( + row.search_text, + queryTokens, + row.text, + row.timestamp, + row.role === "assistant" ? 1 : 0, + ); + if (score <= 0) { + continue; + } + results.push({ + id: row.id, + sourceType: "episode", + score, + role: row.role, + content: row.text, + timestamp: row.timestamp, + }); + } + + results.sort((left, right) => { + if (right.score !== left.score) { + return right.score - left.score; + } + return right.timestamp - left.timestamp; + }); + + return { + query: queryText, + results: results.slice(0, limit), + }; + } + + remember(input: RuntimeMemoryRememberInput): RuntimeMemoryRecord | null { + if (!this.database) { + return null; + } + + const content = normalizeWhitespace(input.content); + if (!content) { + return null; + } + + const kind = input.kind ?? "fact"; + const bucket = input.bucket ?? defaultBucketForKind(kind); + const memoryKey = normalizeMemoryKey(input.key ?? content); + const now = Date.now(); + + const existing = this.database + .prepare( + `SELECT + id, + bucket, + kind, + memory_key, + content, + source, + created_at, + updated_at, + last_accessed_at, + search_text + FROM memories + WHERE memory_key = ? AND active = 1`, + ) + .get(memoryKey) as MemoryRow | undefined; + + if (existing) { + if ( + existing.content === content && + existing.bucket === bucket && + existing.kind === kind + ) { + this.database + .prepare( + `UPDATE memories + SET updated_at = ?, last_accessed_at = ? + WHERE id = ?`, + ) + .run(now, now, existing.id); + return this.getMemoryById(existing.id); + } + } + let newId = 0; + this.database.exec("BEGIN IMMEDIATE;"); + try { + if (existing) { + this.database + .prepare( + `UPDATE memories + SET active = 0, superseded_at = ? + WHERE id = ?`, + ) + .run(now, existing.id); + } + + const insertResult = this.database + .prepare( + `INSERT INTO memories ( + bucket, + kind, + memory_key, + content, + search_text, + source, + active, + created_at, + updated_at + ) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)`, + ) + .run( + bucket, + kind, + memoryKey, + content, + createSearchText({ + bucket, + kind, + key: memoryKey, + content, + }), + input.source ?? "manual", + now, + now, + ); + + newId = Number(insertResult.lastInsertRowid); + + if (existing) { + this.database + .prepare( + `UPDATE memories + SET superseded_by_id = ? + WHERE id = ?`, + ) + .run(newId, existing.id); + } + + this.database.exec("COMMIT;"); + } catch (error) { + this.database.exec("ROLLBACK;"); + throw error; + } + + return this.getMemoryById(newId); + } + + forget(input: RuntimeMemoryForgetInput): { ok: true; forgotten: boolean } { + if (!this.database) { + return { ok: true, forgotten: false }; + } + + if (typeof input.id === "number") { + const result = this.database + .prepare( + `UPDATE memories + SET active = 0, superseded_at = ? + WHERE id = ? AND active = 1`, + ) + .run(Date.now(), input.id); + return { ok: true, forgotten: result.changes > 0 }; + } + + if (input.key) { + const result = this.database + .prepare( + `UPDATE memories + SET active = 0, superseded_at = ? + WHERE memory_key = ? AND active = 1`, + ) + .run(Date.now(), normalizeMemoryKey(input.key)); + return { ok: true, forgotten: result.changes > 0 }; + } + + return { ok: true, forgotten: false }; + } + + rebuild(): RuntimeMemoryRebuildResult { + if (!this.database) { + return { ok: true, memoryRows: 0, episodeRows: 0 }; + } + + const memoryRows = this.database + .prepare( + `SELECT + id, + bucket, + kind, + memory_key, + content, + source, + created_at, + updated_at, + last_accessed_at, + search_text + FROM memories`, + ) + .all() as MemoryRow[]; + for (const row of memoryRows) { + this.database + .prepare(`UPDATE memories SET search_text = ? WHERE id = ?`) + .run( + createSearchText({ + bucket: row.bucket, + kind: row.kind, + key: row.memory_key, + content: row.content, + }), + row.id, + ); + } + + const episodeRows = this.database + .prepare(`SELECT id, role, text, timestamp, search_text FROM episodes`) + .all() as EpisodeRow[]; + for (const row of episodeRows) { + this.database + .prepare(`UPDATE episodes SET search_text = ? WHERE id = ?`) + .run(createEpisodeSearchText(row.role, row.text), row.id); + } + + return { + ok: true, + memoryRows: memoryRows.length, + episodeRows: episodeRows.length, + }; + } + + recordMessage(message: AgentMessage): void { + if (!this.database) { + return; + } + if (message.role !== "user" && message.role !== "assistant") { + return; + } + + const text = clampText(extractTextFromMessage(message), MAX_EPISODE_CHARS); + if (!text) { + return; + } + + this.database + .prepare( + `INSERT INTO episodes ( + session_id, + session_ref, + role, + text, + search_text, + timestamp + ) VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + this.sessionManager.getSessionId(), + basename(this.sessionManager.getSessionDir()), + message.role, + text, + createEpisodeSearchText(message.role, text), + message.timestamp, + ); + + this.trimEpisodes(); + } + + async injectContext( + messages: AgentMessage[], + options?: { signal?: AbortSignal }, + ): Promise { + if (!this.database) { + return messages; + } + + options?.signal?.throwIfAborted?.(); + const lastUserIndex = findLastUserMessageIndex(messages); + if (lastUserIndex === -1) { + return messages; + } + + const userMessage = messages[lastUserIndex]; + const userText = extractTextFromMessage(userMessage); + if (!userText) { + return messages; + } + + const core = this.selectCoreRecall(); + const search = this.search(userText, this.settings.maxRecallResults); + const memoryIds = search.results + .filter( + ( + item, + ): item is RuntimeMemorySearchResultItem & { sourceType: "memory" } => + item.sourceType === "memory", + ) + .map((item) => item.id); + this.touchMemories(memoryIds); + + const memoryBlock = renderMemoryBlock(core, search.results); + if (!memoryBlock) { + return messages; + } + + const injectedMessage: AgentMessage = { + role: "custom", + customType: CUSTOM_MEMORY_TYPE, + content: memoryBlock, + display: false, + details: { + identity: this.identity, + }, + timestamp: Date.now(), + }; + + return [ + ...messages.slice(0, lastUserIndex), + injectedMessage, + ...messages.slice(lastUserIndex), + ]; + } + + async promoteTurn(params: { + model: Model | undefined; + apiKey: string | undefined; + messages: AgentMessage[]; + signal?: AbortSignal; + }): Promise { + if (!this.database || !this.settings.writer.enabled || !params.model) { + return; + } + + const userText = findLastRoleText(params.messages, "user"); + const assistantText = findLastRoleText(params.messages, "assistant"); + if (!userText || !assistantText) { + return; + } + + const response = await completeSimple( + params.model, + { + systemPrompt: MEMORY_WRITER_SYSTEM_PROMPT, + messages: [ + { + role: "user" as const, + content: [ + { + type: "text" as const, + text: [ + `Latest user message:`, + `${userText}`, + ``, + `Latest assistant reply:`, + `${assistantText}`, + ].join("\n"), + }, + ], + timestamp: Date.now(), + }, + ], + }, + params.model.reasoning + ? { + apiKey: params.apiKey, + maxTokens: this.settings.writer.maxTokens, + signal: params.signal, + reasoning: "low", + } + : { + apiKey: params.apiKey, + maxTokens: this.settings.writer.maxTokens, + signal: params.signal, + }, + ); + + if (response.stopReason === "error") { + return; + } + + const text = unwrapJson( + response.content + .filter( + (part): part is { type: "text"; text: string } => + part.type === "text", + ) + .map((part) => part.text) + .join("\n"), + ); + + let parsed: MemoryWriterResponse | null = null; + try { + parsed = JSON.parse(text) as MemoryWriterResponse; + } catch { + parsed = null; + } + + const candidates = Array.isArray(parsed?.memories) ? parsed.memories : []; + const remembered = new Set(); + for (const candidate of candidates) { + if (!isMemoryKind(candidate.kind)) { + continue; + } + const kind = candidate.kind; + const bucket = isMemoryBucket(candidate.bucket) + ? candidate.bucket + : defaultBucketForKind(kind); + const content = asString(candidate.content); + if (!content) { + continue; + } + + const key = normalizeMemoryKey( + asString(candidate.key) ?? trimSnippet(content, 80), + ); + const dedupeKey = `${bucket}:${kind}:${key}`; + if (remembered.has(dedupeKey)) { + continue; + } + remembered.add(dedupeKey); + this.remember({ + bucket, + kind, + key, + content, + source: "auto", + }); + } + + if (remembered.size > 0) { + return; + } + + const fallback = inferFallbackMemory(userText); + if (fallback) { + this.remember({ + ...fallback, + source: "auto", + }); + } + } + + private runLegacyImport(): void { + if ( + !this.database || + this.getMetadata("legacy_import_complete") === "true" + ) { + return; + } + + const legacyDir = resolveLegacyProjectDir( + this.settingsManager, + this.sessionManager.getCwd(), + ); + if (!legacyDir) { + this.setMetadata("legacy_import_complete", "true"); + return; + } + + const stats = statSyncSafe(legacyDir); + if (!stats?.isDirectory()) { + this.setMetadata("legacy_import_complete", "true"); + return; + } + + const legacyFiles = readLegacyMemoryFiles(legacyDir); + for (const file of legacyFiles) { + const kind = guessLegacyKind(file.path, file.body); + this.remember({ + bucket: defaultBucketForKind(kind), + kind, + key: normalizeMemoryKey(`legacy:${basename(file.path, ".md")}`), + content: trimSnippet(file.body, 500), + source: "legacy-import", + }); + } + + this.setMetadata("legacy_import_complete", "true"); + } + + private findRelevantMemories( + queryTokens: string[], + limit: number, + ): MemoryRow[] { + if (!this.database) { + return []; + } + + let sql = ` + SELECT + id, + bucket, + kind, + memory_key, + content, + source, + created_at, + updated_at, + last_accessed_at, + search_text + FROM memories + WHERE active = 1`; + const values: string[] = []; + if (queryTokens.length > 0) { + sql += ` AND (${queryTokens.map(() => `instr(search_text, ?) > 0`).join(" OR ")})`; + values.push(...queryTokens); + } + sql += ` ORDER BY updated_at DESC, id DESC LIMIT ${limit}`; + + return this.database.prepare(sql).all(...values) as MemoryRow[]; + } + + private findRelevantEpisodes( + queryTokens: string[], + limit: number, + ): EpisodeRow[] { + if (!this.database) { + return []; + } + + let sql = ` + SELECT + id, + role, + text, + timestamp, + search_text + FROM episodes`; + const values: string[] = []; + if (queryTokens.length > 0) { + sql += ` WHERE ${queryTokens.map(() => `instr(search_text, ?) > 0`).join(" OR ")}`; + values.push(...queryTokens); + } + sql += ` ORDER BY timestamp DESC, id DESC LIMIT ${limit}`; + + return this.database.prepare(sql).all(...values) as EpisodeRow[]; + } + + private selectCoreRecall(): RuntimeMemoryRecord[] { + if (!this.database) { + return []; + } + + const rows = this.database + .prepare( + `SELECT + id, + bucket, + kind, + memory_key, + content, + source, + created_at, + updated_at, + last_accessed_at, + search_text + FROM memories + WHERE active = 1 AND bucket = 'core' AND kind != 'secret' + ORDER BY updated_at DESC, id DESC`, + ) + .all() as MemoryRow[]; + + const selected: RuntimeMemoryRecord[] = []; + let usedTokens = 0; + for (const row of rows) { + const memory = this.mapMemoryRow(row); + const nextTokens = + estimateTextTokens(memory.content) + estimateTextTokens(memory.key); + if ( + selected.length > 0 && + usedTokens + nextTokens > this.settings.maxCoreTokens + ) { + break; + } + selected.push(memory); + usedTokens += nextTokens; + } + return selected; + } + + private touchMemories(ids: number[]): void { + if (!this.database || ids.length === 0) { + return; + } + const unique = Array.from(new Set(ids)); + const placeholders = unique.map(() => "?").join(", "); + this.database + .prepare( + `UPDATE memories + SET last_accessed_at = ? + WHERE id IN (${placeholders})`, + ) + .run(Date.now(), ...unique); + } + + private trimEpisodes(): void { + if (!this.database) { + return; + } + + const countRow = this.database + .prepare(`SELECT COUNT(*) AS count FROM episodes`) + .get() as { count: number }; + if (countRow.count <= MAX_EPISODES) { + return; + } + + const overflow = countRow.count - MAX_EPISODES; + this.database + .prepare( + `DELETE FROM episodes + WHERE id IN ( + SELECT id FROM episodes + ORDER BY timestamp ASC, id ASC + LIMIT ? + )`, + ) + .run(overflow); + } + + private getMemoryById(id: number): RuntimeMemoryRecord | null { + if (!this.database) { + return null; + } + const row = this.database + .prepare( + `SELECT + id, + bucket, + kind, + memory_key, + content, + source, + created_at, + updated_at, + last_accessed_at, + search_text + FROM memories + WHERE id = ?`, + ) + .get(id) as MemoryRow | undefined; + return row ? this.mapMemoryRow(row) : null; + } + + private getMetadata(key: string): string | null { + if (!this.database) { + return null; + } + const row = this.database + .prepare(`SELECT value FROM metadata WHERE key = ?`) + .get(key) as { value?: string } | undefined; + return row?.value ?? null; + } + + private setMetadata(key: string, value: string): void { + if (!this.database) { + return; + } + this.database + .prepare( + `INSERT INTO metadata (key, value) + VALUES (?, ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value`, + ) + .run(key, value); + } + + private mapMemoryRow(row: MemoryRow): RuntimeMemoryRecord { + return { + id: row.id, + bucket: row.bucket, + kind: row.kind, + key: row.memory_key, + content: row.content, + source: row.source, + createdAt: row.created_at, + updatedAt: row.updated_at, + lastAccessedAt: row.last_accessed_at, + }; + } +} + +function statSyncSafe(path: string): ReturnType | null { + try { + return statSync(path); + } catch { + return null; + } +} + +function resolveLegacyProjectDir( + settingsManager: SettingsManager, + cwd: string, +): string | null { + const settings = asRecord(settingsManager.getGlobalSettings()) ?? {}; + const legacySettings = asRecord(settings["pi-memory-md"]) ?? {}; + const configuredRoot = + asString(legacySettings.localPath) ?? join(homedir(), ".pi", "memory-md"); + const legacyRoot = expandHomePath(configuredRoot); + const legacyProjectDir = join(legacyRoot, basename(cwd)); + if (existsSync(legacyProjectDir)) { + return legacyProjectDir; + } + + const hashedDir = join( + legacyRoot, + `${basename(cwd)}-${createHash("sha256").update(resolve(cwd)).digest("hex").slice(0, 12)}`, + ); + return existsSync(hashedDir) ? hashedDir : null; +} + +function renderMemoryBlock( + coreMemories: RuntimeMemoryRecord[], + searchResults: RuntimeMemorySearchResultItem[], +): string | null { + const lines: string[] = []; + const coreIds = new Set(coreMemories.map((memory) => memory.id)); + + if (coreMemories.length > 0) { + lines.push("Companion Memory"); + lines.push(""); + lines.push("Core memory:"); + for (const memory of coreMemories) { + lines.push(`- [${memory.kind}] ${memory.content}`); + } + } + + const memoryResults = searchResults.filter( + (item) => item.sourceType === "memory" && !coreIds.has(item.id), + ); + const episodeResults = searchResults.filter( + (item) => item.sourceType === "episode", + ); + + if (memoryResults.length > 0) { + if (lines.length === 0) { + lines.push("Companion Memory"); + lines.push(""); + } else { + lines.push(""); + } + lines.push("Relevant long-term memory:"); + for (const result of memoryResults) { + lines.push(`- [${result.kind}] ${trimSnippet(result.content)}`); + } + } + + if (episodeResults.length > 0) { + if (lines.length === 0) { + lines.push("Companion Memory"); + lines.push(""); + } else { + lines.push(""); + } + lines.push("Relevant past conversation snippets:"); + for (const result of episodeResults) { + const date = new Date(result.timestamp).toISOString().slice(0, 10); + lines.push(`- (${date}) ${trimSnippet(result.content)}`); + } + } + + if (lines.length === 0) { + return null; + } + + lines.push(""); + lines.push( + "Use this memory when it is relevant. If a memory might be outdated or ambiguous, verify it with the user.", + ); + return lines.join("\n"); +} + +function findLastUserMessageIndex(messages: AgentMessage[]): number { + for (let index = messages.length - 1; index >= 0; index--) { + if (messages[index]?.role === "user") { + return index; + } + } + return -1; +} + +function findLastRoleText( + messages: AgentMessage[], + role: "user" | "assistant", +): string { + for (let index = messages.length - 1; index >= 0; index--) { + const message = messages[index]; + if (message?.role === role) { + const text = extractTextFromMessage(message); + if (text) { + return text; + } + } + } + return ""; +} + +function inferFallbackMemory( + userText: string, +): Omit | null { + const normalized = userText.toLowerCase(); + if ( + /\b(password|passcode|pin|token|secret|api key|door code|key code|wifi password)\b/i.test( + userText, + ) + ) { + return { + bucket: "archival", + kind: "secret", + key: normalizeMemoryKey(trimSnippet(userText, 80)), + content: trimSnippet(userText, 300), + }; + } + + if (/\bremember\b/i.test(normalized)) { + return { + bucket: "archival", + kind: "fact", + key: normalizeMemoryKey(trimSnippet(userText, 80)), + content: trimSnippet(userText, 300), + }; + } + + return null; +} diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index cef1231..c3c5068 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -320,6 +320,7 @@ export async function createAgentSession( }; const extensionRunnerRef: { current?: ExtensionRunner } = {}; + const sessionRef: { current?: AgentSession } = {}; agent = new Agent({ initialState: { @@ -331,9 +332,15 @@ export async function createAgentSession( convertToLlm: convertToLlmWithBlockImages, sessionId: sessionManager.getSessionId(), transformContext: async (messages) => { + const currentSession = sessionRef.current; + let transformedMessages = messages; + if (currentSession) { + transformedMessages = + await currentSession.transformRuntimeContext(transformedMessages); + } const runner = extensionRunnerRef.current; - if (!runner) return messages; - return runner.emitContext(messages); + if (!runner) return transformedMessages; + return runner.emitContext(transformedMessages); }, steeringMode: settingsManager.getSteeringMode(), followUpMode: settingsManager.getFollowUpMode(), @@ -393,6 +400,7 @@ export async function createAgentSession( initialActiveToolNames, extensionRunnerRef, }); + sessionRef.current = session; const extensionsResult = resourceLoader.getExtensions(); return { diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index ca54c5e..ee31113 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -63,6 +63,17 @@ export interface GatewaySettings { webhook?: GatewayWebhookSettings; } +export interface CompanionMemorySettings { + enabled?: boolean; + storageDir?: string; + maxCoreTokens?: number; + maxRecallResults?: number; + writer?: { + enabled?: boolean; + maxTokens?: number; + }; +} + export type TransportSetting = Transport; /** @@ -125,6 +136,7 @@ export interface Settings { showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME markdown?: MarkdownSettings; gateway?: GatewaySettings; + companionMemory?: CompanionMemorySettings; } /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */ diff --git a/packages/pi-memory-md/LICENSE b/packages/pi-memory-md/LICENSE deleted file mode 100644 index c20c188..0000000 --- a/packages/pi-memory-md/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Vandee - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/pi-memory-md/README.md b/packages/pi-memory-md/README.md deleted file mode 100644 index d0fae66..0000000 --- a/packages/pi-memory-md/README.md +++ /dev/null @@ -1,199 +0,0 @@ -# pi-memory-md - -Letta-like memory management for [pi](https://github.com/badlogic/pi-mono) using GitHub-backed markdown files. - -## Features - -- **Persistent Memory**: Store context, preferences, and knowledge across sessions -- **Git-backed**: Version control with full history -- **Prompt append**: Memory index automatically appended to conversation at session start -- **On-demand access**: LLM reads full content via tools when needed -- **Multi-project**: Separate memory spaces per project - -## Quick Start - -```bash -# 1. Install -pi install npm:pi-memory-md -# Or for latest from GitHub: -pi install git:github.com/VandeeFeng/pi-memory-md - -# 2. Create a GitHub repository (private recommended) - -# 3. Configure pi -# Add to ~/.pi/agent/settings.json: -{ - "pi-memory-md": { - "enabled": true, - "repoUrl": "git@github.com:username/repo.git", - "localPath": "~/.pi/memory-md" - } -} - -# 4. Start a new pi session -# The extension will auto-initialize and sync on first run -``` - -**Commands available in pi:** - -- `:memory-init` - Initialize repository structure -- `:memory-status` - Show repository status - -## How It Works - -``` -Session Start - ↓ -1. Git pull (sync latest changes) - ↓ -2. Scan all .md files in memory directory - ↓ -3. Build index (descriptions + tags only - NOT full content) - ↓ -4. Append index to conversation via prompt append (not system prompt) - ↓ -5. LLM reads full file content via tools when needed -``` - -**Why index-only via prompt append?** Keeps token usage low while making full content accessible on-demand. The index is appended to the conversation, not injected into the system prompt. - -## Available Tools - -The LLM can use these tools to interact with memory: - -| Tool | Parameters | Description | -| --------------- | ------------------------------------- | ------------------------------------- | ---------- | -------------- | -| `memory_init` | `{force?: boolean}` | Initialize or reinitialize repository | -| `memory_sync` | `{action: "pull" | "push" | "status"}` | Git operations | -| `memory_read` | `{path: string}` | Read a memory file | -| `memory_write` | `{path, content, description, tags?}` | Create/update memory file | -| `memory_list` | `{directory?: string}` | List all memory files | -| `memory_search` | `{query, searchIn}` | Search by content/tags/description | - -## Memory File Format - -```markdown ---- -description: "User identity and background" -tags: ["user", "identity"] -created: "2026-02-14" -updated: "2026-02-14" ---- - -# Your Content Here - -Markdown content... -``` - -## Directory Structure - -``` -~/.pi/memory-md/ -└── project-name/ - ├── core/ - │ ├── user/ # Your preferences - │ │ ├── identity.md - │ │ └── prefer.md - │ └── project/ # Project context - │ └── tech-stack.md - └── reference/ # On-demand docs -``` - -## Configuration - -```json -{ - "pi-memory-md": { - "enabled": true, - "repoUrl": "git@github.com:username/repo.git", - "localPath": "~/.pi/memory-md", - "injection": "message-append", - "autoSync": { - "onSessionStart": true - } - } -} -``` - -| Setting | Default | Description | -| ------------------------- | ------------------ | -------------------------------------------------------------- | -| `enabled` | `true` | Enable extension | -| `repoUrl` | Required | GitHub repository URL | -| `localPath` | `~/.pi/memory-md` | Local clone path | -| `injection` | `"message-append"` | Memory injection mode: `"message-append"` or `"system-prompt"` | -| `autoSync.onSessionStart` | `true` | Git pull on session start | - -### Memory Injection Modes - -The extension supports two modes for injecting memory into the conversation: - -#### 1. Message Append (Default) - -```json -{ - "pi-memory-md": { - "injection": "message-append" - } -} -``` - -- Memory is sent as a custom message before the user's first message -- Not visible in the TUI (`display: false`) -- Persists in the session history -- Injected only once per session (on first agent turn) -- **Pros**: Lower token usage, memory persists naturally in conversation -- **Cons**: Only visible when the model scrolls back to earlier messages - -#### 2. System Prompt - -```json -{ - "pi-memory-md": { - "injection": "system-prompt" - } -} -``` - -- Memory is appended to the system prompt -- Rebuilt and injected on every agent turn -- Always visible to the model in the system context -- **Pros**: Memory always present in system context, no need to scroll back -- **Cons**: Higher token usage (repeated on every prompt) - -**Recommendation**: Use `message-append` (default) for optimal token efficiency. Switch to `system-prompt` if you notice the model not accessing memory consistently. - -## Usage Examples - -Simply talk to pi - the LLM will automatically use memory tools when appropriate: - -``` -You: Save my preference for 2-space indentation in TypeScript files to memory. - -Pi: [Uses memory_write tool to save your preference] -``` - -You can also explicitly request operations: - -``` -You: List all memory files for this project. -You: Search memory for "typescript" preferences. -You: Read core/user/identity.md -You: Sync my changes to the repository. -``` - -The LLM automatically: - -- Reads memory index at session start (appended to conversation) -- Writes new information when you ask to remember something -- Syncs changes when needed - -## Commands - -Use these directly in pi: - -- `:memory-status` - Show repository status -- `:memory-init` - Initialize repository structure - -## Reference - -- [Introducing Context Repositories: Git-based Memory for Coding Agents | Letta](https://www.letta.com/blog/context-repositories) diff --git a/packages/pi-memory-md/memory-md.ts b/packages/pi-memory-md/memory-md.ts deleted file mode 100644 index 6abc451..0000000 --- a/packages/pi-memory-md/memory-md.ts +++ /dev/null @@ -1,641 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import type { - ExtensionAPI, - ExtensionContext, -} from "@mariozechner/pi-coding-agent"; -import type { GrayMatterFile } from "gray-matter"; -import matter from "gray-matter"; -import { registerAllTools } from "./tools.js"; - -/** - * Type definitions for memory files, settings, and git operations. - */ - -export interface MemoryFrontmatter { - description: string; - limit?: number; - tags?: string[]; - created?: string; - updated?: string; -} - -export interface MemoryFile { - path: string; - frontmatter: MemoryFrontmatter; - content: string; -} - -export interface MemoryMdSettings { - enabled?: boolean; - repoUrl?: string; - localPath?: string; - autoSync?: { - onSessionStart?: boolean; - }; - injection?: "system-prompt" | "message-append"; - systemPrompt?: { - maxTokens?: number; - includeProjects?: string[]; - }; -} - -export interface GitResult { - stdout: string; - success: boolean; -} - -export interface SyncResult { - success: boolean; - message: string; - updated?: boolean; -} - -export type ParsedFrontmatter = GrayMatterFile["data"]; - -/** - * Helper functions for paths, dates, and settings. - */ - -const DEFAULT_LOCAL_PATH = path.join(os.homedir(), ".pi", "memory-md"); - -export function getCurrentDate(): string { - return new Date().toISOString().split("T")[0]; -} - -function expandPath(p: string): string { - if (p.startsWith("~")) { - return path.join(os.homedir(), p.slice(1)); - } - return p; -} - -export function getMemoryDir( - settings: MemoryMdSettings, - ctx: ExtensionContext, -): string { - const basePath = settings.localPath || DEFAULT_LOCAL_PATH; - return path.join(basePath, path.basename(ctx.cwd)); -} - -function getRepoName(settings: MemoryMdSettings): string { - if (!settings.repoUrl) return "memory-md"; - const match = settings.repoUrl.match(/\/([^/]+?)(\.git)?$/); - return match ? match[1] : "memory-md"; -} - -function loadSettings(): MemoryMdSettings { - const DEFAULT_SETTINGS: MemoryMdSettings = { - enabled: true, - repoUrl: "", - localPath: DEFAULT_LOCAL_PATH, - autoSync: { onSessionStart: true }, - injection: "message-append", - systemPrompt: { - maxTokens: 10000, - includeProjects: ["current"], - }, - }; - - const globalSettings = path.join( - os.homedir(), - ".pi", - "agent", - "settings.json", - ); - if (!fs.existsSync(globalSettings)) { - return DEFAULT_SETTINGS; - } - - try { - const content = fs.readFileSync(globalSettings, "utf-8"); - const parsed = JSON.parse(content); - const loadedSettings = { - ...DEFAULT_SETTINGS, - ...(parsed["pi-memory-md"] as MemoryMdSettings), - }; - - if (loadedSettings.localPath) { - loadedSettings.localPath = expandPath(loadedSettings.localPath); - } - - return loadedSettings; - } catch (error) { - console.warn("Failed to load memory settings:", error); - return DEFAULT_SETTINGS; - } -} - -/** - * Git sync operations (fetch, pull, push, status). - */ - -export async function gitExec( - pi: ExtensionAPI, - cwd: string, - ...args: string[] -): Promise { - try { - const result = await pi.exec("git", args, { cwd }); - return { - stdout: result.stdout || "", - success: true, - }; - } catch { - return { stdout: "", success: false }; - } -} - -export async function syncRepository( - pi: ExtensionAPI, - settings: MemoryMdSettings, - isRepoInitialized: { value: boolean }, -): Promise { - const localPath = settings.localPath; - const repoUrl = settings.repoUrl; - - if (!repoUrl || !localPath) { - return { - success: false, - message: "GitHub repo URL or local path not configured", - }; - } - - if (fs.existsSync(localPath)) { - const gitDir = path.join(localPath, ".git"); - if (!fs.existsSync(gitDir)) { - return { - success: false, - message: `Directory exists but is not a git repo: ${localPath}`, - }; - } - - const pullResult = await gitExec( - pi, - localPath, - "pull", - "--rebase", - "--autostash", - ); - if (!pullResult.success) { - return { - success: false, - message: "Pull failed - try manual git operations", - }; - } - - isRepoInitialized.value = true; - const updated = - pullResult.stdout.includes("Updating") || - pullResult.stdout.includes("Fast-forward"); - const repoName = getRepoName(settings); - return { - success: true, - message: updated - ? `Pulled latest changes from [${repoName}]` - : `[${repoName}] is already latest`, - updated, - }; - } - - fs.mkdirSync(localPath, { recursive: true }); - - const memoryDirName = path.basename(localPath); - const parentDir = path.dirname(localPath); - const cloneResult = await gitExec( - pi, - parentDir, - "clone", - repoUrl, - memoryDirName, - ); - - if (cloneResult.success) { - isRepoInitialized.value = true; - const repoName = getRepoName(settings); - return { - success: true, - message: `Cloned [${repoName}] successfully`, - updated: true, - }; - } - - return { success: false, message: "Clone failed - check repo URL and auth" }; -} - -/** - * Memory file read/write/list operations. - */ - -function validateFrontmatter(data: ParsedFrontmatter): { - valid: boolean; - error?: string; -} { - if (!data) { - return { - valid: false, - error: "No frontmatter found (requires --- delimiters)", - }; - } - - const frontmatter = data as MemoryFrontmatter; - - if (!frontmatter.description || typeof frontmatter.description !== "string") { - return { - valid: false, - error: "Frontmatter must have a 'description' field (string)", - }; - } - - if ( - frontmatter.limit !== undefined && - (typeof frontmatter.limit !== "number" || frontmatter.limit <= 0) - ) { - return { valid: false, error: "'limit' must be a positive number" }; - } - - if (frontmatter.tags !== undefined && !Array.isArray(frontmatter.tags)) { - return { valid: false, error: "'tags' must be an array of strings" }; - } - - return { valid: true }; -} - -export function readMemoryFile(filePath: string): MemoryFile | null { - try { - const content = fs.readFileSync(filePath, "utf-8"); - const parsed = matter(content); - const validation = validateFrontmatter(parsed.data); - - if (!validation.valid) { - throw new Error(validation.error); - } - - return { - path: filePath, - frontmatter: parsed.data as MemoryFrontmatter, - content: parsed.content, - }; - } catch (error) { - console.error( - `Failed to read memory file ${filePath}:`, - error instanceof Error ? error.message : error, - ); - return null; - } -} - -export function listMemoryFiles(memoryDir: string): string[] { - const files: string[] = []; - - function walkDir(dir: string) { - if (!fs.existsSync(dir)) return; - - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walkDir(fullPath); - } else if (entry.isFile() && entry.name.endsWith(".md")) { - files.push(fullPath); - } - } - } - - walkDir(memoryDir); - return files; -} - -export function writeMemoryFile( - filePath: string, - content: string, - frontmatter: MemoryFrontmatter, -): void { - const fileDir = path.dirname(filePath); - fs.mkdirSync(fileDir, { recursive: true }); - const frontmatterStr = matter.stringify(content, frontmatter); - fs.writeFileSync(filePath, frontmatterStr); -} - -/** - * Build memory context for agent prompt. - */ - -function ensureDirectoryStructure(memoryDir: string): void { - const dirs = [ - path.join(memoryDir, "core", "user"), - path.join(memoryDir, "core", "project"), - path.join(memoryDir, "reference"), - ]; - - for (const dir of dirs) { - fs.mkdirSync(dir, { recursive: true }); - } -} - -function createDefaultFiles(memoryDir: string): void { - const identityFile = path.join(memoryDir, "core", "user", "identity.md"); - if (!fs.existsSync(identityFile)) { - writeMemoryFile( - identityFile, - "# User Identity\n\nCustomize this file with your information.", - { - description: "User identity and background", - tags: ["user", "identity"], - created: getCurrentDate(), - }, - ); - } - - const preferFile = path.join(memoryDir, "core", "user", "prefer.md"); - if (!fs.existsSync(preferFile)) { - writeMemoryFile( - preferFile, - "# User Preferences\n\n## Communication Style\n- Be concise\n- Show code examples\n\n## Code Style\n- 2 space indentation\n- Prefer const over var\n- Functional programming preferred", - { - description: "User habits and code style preferences", - tags: ["user", "preferences"], - created: getCurrentDate(), - }, - ); - } -} - -function buildMemoryContext( - settings: MemoryMdSettings, - ctx: ExtensionContext, -): string { - const coreDir = path.join(getMemoryDir(settings, ctx), "core"); - - if (!fs.existsSync(coreDir)) { - return ""; - } - - const files = listMemoryFiles(coreDir); - if (files.length === 0) { - return ""; - } - - const memoryDir = getMemoryDir(settings, ctx); - const lines: string[] = [ - "# Project Memory", - "", - "Available memory files (use memory_read to view full content):", - "", - ]; - - for (const filePath of files) { - const memory = readMemoryFile(filePath); - if (memory) { - const relPath = path.relative(memoryDir, filePath); - const { description, tags } = memory.frontmatter; - const tagStr = tags?.join(", ") || "none"; - lines.push(`- ${relPath}`); - lines.push(` Description: ${description}`); - lines.push(` Tags: ${tagStr}`); - lines.push(""); - } - } - - return lines.join("\n"); -} - -/** - * Main extension initialization. - * - * Lifecycle: - * 1. session_start: Start async sync (non-blocking), build memory context - * 2. before_agent_start: Wait for sync, then inject memory on first agent turn - * 3. Register tools and commands for memory operations - * - * Memory injection modes: - * - message-append (default): Send as custom message with display: false, not visible in TUI but persists in session - * - system-prompt: Append to system prompt on each agent turn (rebuilds every prompt) - * - * Key optimization: - * - Sync runs asynchronously without blocking user input - * - Memory is injected after user sends first message (before_agent_start) - * - * Configuration: - * Set injection in settings to choose between "message-append" or "system-prompt" - * - * Commands: - * - /memory-status: Show repository status - * - /memory-init: Initialize memory repository - * - /memory-refresh: Manually refresh memory context - */ - -export default function memoryMdExtension(pi: ExtensionAPI) { - let settings: MemoryMdSettings = loadSettings(); - const repoInitialized = { value: false }; - let syncPromise: Promise | null = null; - let cachedMemoryContext: string | null = null; - let memoryInjected = false; - - pi.on("session_start", async (_event, ctx) => { - settings = loadSettings(); - - if (!settings.enabled) { - return; - } - - const memoryDir = getMemoryDir(settings, ctx); - const coreDir = path.join(memoryDir, "core"); - - if (!fs.existsSync(coreDir)) { - ctx.ui.notify( - "Memory-md not initialized. Use /memory-init to set up project memory.", - "info", - ); - return; - } - - if (settings.autoSync?.onSessionStart && settings.localPath) { - syncPromise = syncRepository(pi, settings, repoInitialized).then( - (syncResult) => { - if (settings.repoUrl) { - ctx.ui.notify( - syncResult.message, - syncResult.success ? "info" : "error", - ); - } - return syncResult; - }, - ); - } - - cachedMemoryContext = buildMemoryContext(settings, ctx); - memoryInjected = false; - }); - - pi.on("before_agent_start", async (event, ctx) => { - if (syncPromise) { - await syncPromise; - syncPromise = null; - } - - if (!cachedMemoryContext) { - return undefined; - } - - const mode = settings.injection || "message-append"; - const isFirstInjection = !memoryInjected; - - if (isFirstInjection) { - memoryInjected = true; - const fileCount = cachedMemoryContext - .split("\n") - .filter((l) => l.startsWith("-")).length; - ctx.ui.notify(`Memory injected: ${fileCount} files (${mode})`, "info"); - } - - if (mode === "message-append" && isFirstInjection) { - return { - message: { - customType: "pi-memory-md", - content: `# Project Memory\n\n${cachedMemoryContext}`, - display: false, - }, - }; - } - - if (mode === "system-prompt") { - return { - systemPrompt: `${event.systemPrompt}\n\n# Project Memory\n\n${cachedMemoryContext}`, - }; - } - - return undefined; - }); - - registerAllTools(pi, settings, repoInitialized); - - pi.registerCommand("memory-status", { - description: "Show memory repository status", - handler: async (_args, ctx) => { - const projectName = path.basename(ctx.cwd); - const memoryDir = getMemoryDir(settings, ctx); - const coreUserDir = path.join(memoryDir, "core", "user"); - - if (!fs.existsSync(coreUserDir)) { - ctx.ui.notify( - `Memory: ${projectName} | Not initialized | Use /memory-init to set up`, - "info", - ); - return; - } - - const result = await gitExec( - pi, - settings.localPath!, - "status", - "--porcelain", - ); - const isDirty = result.stdout.trim().length > 0; - - ctx.ui.notify( - `Memory: ${projectName} | Repo: ${isDirty ? "Uncommitted changes" : "Clean"} | Path: ${memoryDir}`, - isDirty ? "warning" : "info", - ); - }, - }); - - pi.registerCommand("memory-init", { - description: "Initialize memory repository", - handler: async (_args, ctx) => { - const memoryDir = getMemoryDir(settings, ctx); - const alreadyInitialized = fs.existsSync( - path.join(memoryDir, "core", "user"), - ); - - const result = await syncRepository(pi, settings, repoInitialized); - - if (!result.success) { - ctx.ui.notify(`Initialization failed: ${result.message}`, "error"); - return; - } - - ensureDirectoryStructure(memoryDir); - createDefaultFiles(memoryDir); - - if (alreadyInitialized) { - ctx.ui.notify(`Memory already exists: ${result.message}`, "info"); - } else { - ctx.ui.notify( - `Memory initialized: ${result.message}\n\nCreated:\n - core/user\n - core/project\n - reference`, - "info", - ); - } - }, - }); - - pi.registerCommand("memory-refresh", { - description: "Refresh memory context from files", - handler: async (_args, ctx) => { - const memoryContext = buildMemoryContext(settings, ctx); - - if (!memoryContext) { - ctx.ui.notify("No memory files found to refresh", "warning"); - return; - } - - cachedMemoryContext = memoryContext; - memoryInjected = false; - - const mode = settings.injection || "message-append"; - const fileCount = memoryContext - .split("\n") - .filter((l) => l.startsWith("-")).length; - - if (mode === "message-append") { - pi.sendMessage({ - customType: "pi-memory-md-refresh", - content: `# Project Memory (Refreshed)\n\n${memoryContext}`, - display: false, - }); - ctx.ui.notify( - `Memory refreshed: ${fileCount} files injected (${mode})`, - "info", - ); - } else { - ctx.ui.notify( - `Memory cache refreshed: ${fileCount} files (will be injected on next prompt)`, - "info", - ); - } - }, - }); - - pi.registerCommand("memory-check", { - description: "Check memory folder structure", - handler: async (_args, ctx) => { - const memoryDir = getMemoryDir(settings, ctx); - - if (!fs.existsSync(memoryDir)) { - ctx.ui.notify(`Memory directory not found: ${memoryDir}`, "error"); - return; - } - - const { execSync } = await import("node:child_process"); - let treeOutput = ""; - - try { - treeOutput = execSync(`tree -L 3 -I "node_modules" "${memoryDir}"`, { - encoding: "utf-8", - }); - } catch { - try { - treeOutput = execSync( - `find "${memoryDir}" -type d -not -path "*/node_modules/*"`, - { encoding: "utf-8" }, - ); - } catch { - treeOutput = "Unable to generate directory tree."; - } - } - - ctx.ui.notify(treeOutput.trim(), "info"); - }, - }); -} diff --git a/packages/pi-memory-md/package.json b/packages/pi-memory-md/package.json deleted file mode 100644 index 5ea57c5..0000000 --- a/packages/pi-memory-md/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "pi-memory-md", - "version": "0.1.1", - "description": "Letta-like memory management for pi using structured markdown files in a GitHub repository", - "type": "module", - "license": "MIT", - "author": "VandeePunk", - "repository": { - "type": "git", - "url": "git+https://github.com/VandeeFeng/pi-memory-md.git" - }, - "keywords": [ - "pi-package", - "pi-extension", - "pi-skill", - "memory", - "markdown", - "git", - "letta", - "persistent-memory", - "ai-memory", - "coding-agent" - ], - "dependencies": { - "gray-matter": "^4.0.3" - }, - "devDependencies": { - "@mariozechner/pi-coding-agent": "latest", - "@types/node": "^20.0.0", - "husky": "^9.1.7", - "typescript": "^5.0.0" - }, - "pi": { - "extensions": [ - "./memory-md.ts" - ], - "skills": [ - "./skills/memory-init/SKILL.md", - "./skills/memory-management/SKILL.md", - "./skills/memory-sync/SKILL.md", - "./skills/memory-search/SKILL.md" - ] - }, - "files": [ - "memory-md.ts", - "tools.ts", - "skills", - "README.md", - "CHANGELOG.md", - "LICENSE" - ], - "scripts": { - "prepare": "husky", - "check": "biome check --write --error-on-warnings . && tsgo --noEmit" - } -} diff --git a/packages/pi-memory-md/skills/memory-init/SKILL.md b/packages/pi-memory-md/skills/memory-init/SKILL.md deleted file mode 100644 index 23ff23c..0000000 --- a/packages/pi-memory-md/skills/memory-init/SKILL.md +++ /dev/null @@ -1,281 +0,0 @@ ---- -name: memory-init -description: Initial setup and bootstrap for pi-memory-md repository ---- - -# Memory Init - -Use this skill to set up pi-memory-md for the first time or reinitialize an existing installation. - -## Prerequisites - -1. **GitHub repository** - Create a new empty repository on GitHub -2. **Git access** - Configure SSH keys or personal access token -3. **Node.js & npm** - For installing the package - -## Step 1: Install Package - -```bash -pi install npm:pi-memory-md -``` - -## Step 2: Create GitHub Repository - -Create a new repository on GitHub: - -- Name it something like `memory-md` or `pi-memory` -- Make it private (recommended) -- Don't initialize with README (we'll do that) - -**Clone URL will be:** `git@github.com:username/repo-name.git` - -## Step 3: Configure Settings - -Add to your settings file (global: `~/.pi/agent/settings.json`, project: `.pi/settings.json`): - -```json -{ - "pi-memory-md": { - "enabled": true, - "repoUrl": "git@github.com:username/repo-name.git", - "localPath": "~/.pi/memory-md", - "autoSync": { - "onSessionStart": true - } - } -} -``` - -**Settings explained:** - -| Setting | Purpose | Default | -| ------------------------- | ----------------------------------- | ----------------- | -| `enabled` | Enable/disable extension | `true` | -| `repoUrl` | GitHub repository URL | Required | -| `localPath` | Local clone location (supports `~`) | `~/.pi/memory-md` | -| `autoSync.onSessionStart` | Auto-pull on session start | `true` | - -## Step 4: Initialize Repository - -Start pi and run: - -``` -memory_init() -``` - -**This does:** - -1. Clones the GitHub repository -2. Creates directory structure: - - `core/user/` - Your identity and preferences - - `core/project/` - Project-specific info -3. Creates default files: - - `core/user/identity.md` - User identity template - - `core/user/prefer.md` - User preferences template - -**Example output:** - -``` -Memory repository initialized: -Cloned repository successfully - -Created directory structure: - - core/user - - core/project - - reference -``` - -## Step 5: Import Preferences from AGENTS.md - -After initialization, extract relevant preferences from your `AGENTS.md` file to populate `prefer.md`: - -1. **Read AGENTS.md** (typically at `.pi/agent/AGENTS.md` or project root) - -2. **Extract relevant sections** such as: - - IMPORTANT Rules - - Code Quality Principles - - Coding Style Preferences - - Architecture Principles - - Development Workflow - - Technical Preferences - -3. **Present extracted content** to the user in a summarized format - -4. **Ask first confirmation**: Include these extracted preferences in `prefer.md`? - - ``` - Found these preferences in AGENTS.md: - - IMPORTANT Rules: [summary] - - Code Quality Principles: [summary] - - Coding Style: [summary] - - Include these in core/user/prefer.md? (yes/no) - ``` - -5. **Ask for additional content**: Is there anything else you want to add to your preferences? - - ``` - Any additional preferences you'd like to include? (e.g., communication style, specific tools, workflows) - ``` - -6. **Update prefer.md** with: - - Extracted content from AGENTS.md (if user confirmed) - - Any additional preferences provided by user - -## Step 6: Verify Setup - -Check status with command: - -``` -/memory-status -``` - -Should show: `Memory: project-name | Repo: Clean | Path: {localPath}/project-name` - -List files: - -``` -memory_list() -``` - -Should show: `core/user/identity.md`, `core/user/prefer.md` - -## Project Structure - -**Base path**: Configured via `settings["pi-memory-md"].localPath` (default: `~/.pi/memory-md`) - -Each project gets its own folder in the repository: - -``` -{localPath}/ -├── project-a/ -│ ├── core/ -│ │ ├── user/ -│ │ │ ├── identity.md -│ │ │ └── prefer.md -│ │ └── project/ -│ └── reference/ -├── project-b/ -│ └── ... -└── project-c/ - └── ... -``` - -Project name is derived from: - -- Git repository name (if in a git repo) -- Or current directory name - -## First-Time Setup Script - -Automate setup with this script: - -```bash -#!/bin/bash -# setup-memory-md.sh - -REPO_URL="git@github.com:username/memory-repo.git" -SETTINGS_FILE="$HOME/.pi/agent/settings.json" - -# Backup existing settings -cp "$SETTINGS_FILE" "$SETTINGS_FILE.bak" - -# Add pi-memory-md configuration -node -e " -const fs = require('fs'); -const path = require('path'); -const settingsPath = '$SETTINGS_FILE'; -const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); -settings['pi-memory-md'] = { - enabled: true, - repoUrl: '$REPO_URL', - localPath: path.join(require('os').homedir(), '.pi', 'memory-md'), - autoSync: { - onSessionStart: true, - onMessageCreate: false - } -}; -fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); -" - -echo "Settings configured. Now run: memory_init()" -``` - -## Reinitializing - -To reset everything: - -``` -memory_init(force=true) -``` - -**Warning:** This will re-clone the repository, potentially losing local uncommitted changes. - -## Troubleshooting - -### Clone Failed - -**Error:** `Clone failed: Permission denied` - -**Solution:** - -1. Verify SSH keys are configured: `ssh -T git@github.com` -2. Check repo URL is correct in settings -3. Ensure repo exists on GitHub - -### Settings Not Found - -**Error:** `GitHub repo URL not configured in settings["pi-memory-md"].repoUrl` - -**Solution:** - -1. Edit settings file (global or project) -2. Add `pi-memory-md` section (see Step 3) -3. Run `/reload` in pi - -### Directory Already Exists - -**Error:** `Directory exists but is not a git repo` - -**Solution:** - -1. Remove existing directory: `rm -rf {localPath}` (use your configured path) -2. Run `memory_init()` again - -### No Write Permission - -**Error:** `EACCES: permission denied` - -**Solution:** - -1. Check directory permissions: `ls -la {localPath}/..` (use your configured path) -2. Fix ownership: `sudo chown -R $USER:$USER {localPath}` (use your configured path) - -## Verification Checklist - -After setup, verify: - -- [ ] Package installed: `pi install npm:pi-memory-md` -- [ ] Settings configured in settings file -- [ ] GitHub repository exists and is accessible -- [ ] Repository cloned to configured `localPath` -- [ ] Directory structure created -- [ ] `/memory-status` shows correct info -- [ ] `memory_list()` returns files -- [ ] `prefer.md` populated (either from AGENTS.md or default template) - -## Next Steps - -After initialization: - -1. **Import preferences** - Agent will prompt to extract from AGENTS.md -2. Edit your identity: `memory_read(path="core/user/identity.md")` then `memory_write(...)` to update -3. Review preferences: `memory_read(path="core/user/prefer.md")` -4. Add project context: `memory_write(path="core/project/overview.md", ...)` -5. Learn more: See `memory-management` skill - -## Related Skills - -- `memory-management` - Creating and managing memory files -- `memory-sync` - Git synchronization -- `memory-search` - Finding information diff --git a/packages/pi-memory-md/skills/memory-management/SKILL.md b/packages/pi-memory-md/skills/memory-management/SKILL.md deleted file mode 100644 index 5c3539a..0000000 --- a/packages/pi-memory-md/skills/memory-management/SKILL.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -name: memory-management -description: Core memory operations for pi-memory-md - create, read, update, and delete memory files ---- - -# Memory Management - -Use this skill when working with pi-memory-md memory files. Memory is stored as markdown files with YAML frontmatter in a git repository. - -## Design Philosophy - -Inspired by Letta memory filesystem: - -- **File-based memory**: Each memory is a `.md` file with YAML frontmatter -- **Git-backed**: Full version control and cross-device sync -- **Auto-injection**: Files in `core/` are automatically injected to context -- **Organized by purpose**: Fixed structure for core info, flexible for everything else - -## Directory Structure - -**Base path**: Configured via `settings["pi-memory-md"].localPath` (default: `~/.pi/memory-md`) - -``` -{localPath}/ -└── {project-name}/ # Project memory root - ├── core/ # Auto-injected to context every session - │ ├── user/ # 【FIXED】User information - │ │ ├── identity.md # Who the user is - │ │ └── prefer.md # User habits and code style preferences - │ │ - │ └── project/ # 【FIXED】Project information (pre-created) - │ ├── overview.md # Project overview - │ ├── architecture.md # Architecture and design - │ ├── conventions.md # Code conventions - │ └── commands.md # Common commands - │ - ├── docs/ # 【AGENT-CREATED】Reference documentation - ├── archive/ # 【AGENT-CREATED】Historical information - ├── research/ # 【AGENT-CREATED】Research findings - └── notes/ # 【AGENT-CREATED】Standalone notes -``` - -**Important:** `core/project/` is a pre-defined folder under `core/`. Do NOT create another `project/` folder at the project root level. - -## Core Design: Fixed vs Flexible - -### 【FIXED】core/user/ and core/project/ - -These are **pre-defined** and **auto-injected** into every session: - -**core/user/** - User information (2 fixed files) - -- `identity.md` - Who the user is (name, role, background) -- `prefer.md` - User habits and code style preferences - -**core/project/** - Project information - -- `overview.md` - Project overview -- `architecture.md` - Architecture and design -- `conventions.md` - Code conventions -- `commands.md` - Common commands -- `changelog.md` - Development history - -**Why fixed?** - -- Always in context, no need to remember to load -- Core identity that defines every interaction -- Project context needed for all decisions - -**Rule:** ONLY `user/` and `project/` exist under `core/`. No other folders. - -## Decision Tree - -### Does this need to be in EVERY conversation? - -**Yes** → Place under `core/` - -- User-related → `core/user/` -- Project-related → `core/project/` - -**No** → Place at project root level (same level as `core/`) - -- Reference docs → `docs/` -- Historical → `archive/` -- Research → `research/` -- Notes → `notes/` -- Other? → Create appropriate folder - -**Important:** `core/project/` is a FIXED subdirectory under `core/`. Always use `core/project/` for project-specific memory files, NEVER create a `project/` folder at the root level. - -## YAML Frontmatter Schema - -Every memory file MUST have YAML frontmatter: - -```yaml ---- -description: "Human-readable description of this memory file" -tags: ["user", "identity"] -created: "2026-02-14" -updated: "2026-02-14" ---- -``` - -**Required fields:** - -- `description` (string) - Human-readable description - -**Optional fields:** - -- `tags` (array of strings) - For searching and categorization -- `created` (date) - File creation date (auto-added on create) -- `updated` (date) - Last modification date (auto-updated on update) - -## Examples - -### Example 1: User Identity (core/user/identity.md) - -```bash -memory_write( - path="core/user/identity.md", - description="User identity and background", - tags=["user", "identity"], - content="# User Identity\n\nName: Vandee\nRole: Developer..." -) -``` - -### Example 2: User Preferences (core/user/prefer.md) - -```bash -memory_write( - path="core/user/prefer.md", - description="User habits and code style preferences", - tags=["user", "preferences"], - content="# User Preferences\n\n## Communication Style\n- Be concise\n- Show code examples\n\n## Code Style\n- 2 space indentation\n- Prefer const over var\n- Functional programming" -) -``` - -### Example 3: Project Architecture (core/project/) - -```bash -memory_write( - path="core/project/architecture.md", - description="Project architecture and design", - tags=["project", "architecture"], - content="# Architecture\n\n..." -) -``` - -### Example 3: Reference Docs (root level) - -```bash -memory_write( - path="docs/api/rest-endpoints.md", - description="REST API reference documentation", - tags=["docs", "api"], - content="# REST Endpoints\n\n..." -) -``` - -### Example 4: Archived Decision (root level) - -```bash -memory_write( - path="archive/decisions/2024-01-15-auth-redesign.md", - description="Auth redesign decision from January 2024", - tags=["archive", "decision"], - content="# Auth Redesign\n\n..." -) -``` - -## Reading Memory Files - -Use the `memory_read` tool: - -```bash -memory_read(path="core/user/identity.md") -``` - -## Listing Memory Files - -Use the `memory_list` tool: - -```bash -# List all files -memory_list() - -# List files in specific directory -memory_list(directory="core/project") - -# List only core/ files -memory_list(directory="system") -``` - -## Updating Memory Files - -To update a file, use `memory_write` with the same path: - -```bash -memory_write( - path="core/user/identity.md", - description="Updated user identity", - content="New content..." -) -``` - -The extension preserves existing `created` date and updates `updated` automatically. - -## Folder Creation Guidelines - -### core/ directory - FIXED structure - -**Only two folders exist under `core/`:** - -- `user/` - User identity and preferences -- `project/` - Project-specific information - -**Do NOT create any other folders under `core/`.** - -### Root level (same level as core/) - COMPLETE freedom - -**Agent can create any folder structure at project root level (same level as `core/`):** - -- `docs/` - Reference documentation -- `archive/` - Historical information -- `research/` - Research findings -- `notes/` - Standalone notes -- `examples/` - Code examples -- `guides/` - How-to guides - -**Rule:** Organize root level in a way that makes sense for the project. - -**WARNING:** Do NOT create a `project/` folder at root level. Use `core/project/` instead. - -## Best Practices - -### DO: - -- Use `core/user/identity.md` for user identity -- Use `core/user/prefer.md` for user habits and code style -- Use `core/project/` for project-specific information -- Use root level for reference, historical, and research content -- Keep files focused on a single topic -- Organize root level folders by content type - -### DON'T: - -- Create folders under `core/` other than `user/` and `project/` -- Create other files under `core/user/` (only `identity.md` and `prefer.md`) -- Create a `project/` folder at root level (use `core/project/` instead) -- Put reference docs in `core/` (use root `docs/`) -- Create giant files (split into focused topics) -- Mix unrelated content in same file - -## Maintenance - -### Session Wrap-up - -After completing work, archive to root level: - -```bash -memory_write( - path="archive/sessions/2025-02-14-bug-fix.md", - description="Session summary: fixed database connection bug", - tags=["archive", "session"], - content="..." -) -``` - -### Regular Cleanup - -- Consolidate duplicate information -- Update descriptions to stay accurate -- Remove information that's no longer relevant -- Archive old content to appropriate root level folders - -## When to Use This Skill - -Use `memory-management` when: - -- User asks to remember something for future sessions -- Creating or updating project documentation -- Setting preferences or guidelines -- Storing reference material -- Building knowledge base about the project -- Organizing information by type or domain -- Creating reusable patterns and solutions -- Documenting troubleshooting steps - -## Related Skills - -- `memory-sync` - Git synchronization operations -- `memory-init` - Initial repository setup -- `memory-search` - Finding specific information -- `memory-check` - Validate folder structure before syncing - -## Before Syncing - -**IMPORTANT**: Before running `memory_sync(action="push")`, ALWAYS run `memory_check()` first to verify the folder structure is correct: - -```bash -# Check structure first -memory_check() - -# Then push if structure is correct -memory_sync(action="push") -``` - -This prevents accidentally pushing files in wrong locations (e.g., root `project/` instead of `core/project/`). diff --git a/packages/pi-memory-md/skills/memory-search/SKILL.md b/packages/pi-memory-md/skills/memory-search/SKILL.md deleted file mode 100644 index 05716a4..0000000 --- a/packages/pi-memory-md/skills/memory-search/SKILL.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -name: memory-search -description: Search and retrieve information from pi-memory-md memory files ---- - -# Memory Search - -Use this skill to find information stored in pi-memory-md memory files. - -## Search Types - -### Search by Content - -Search within markdown content: - -``` -memory_search(query="typescript", searchIn="content") -``` - -Returns matching files with content excerpts. - -### Search by Tags - -Find files with specific tags: - -``` -memory_search(query="user", searchIn="tags") -``` - -Best for finding files by category or topic. - -### Search by Description - -Find files by their frontmatter description: - -``` -memory_search(query="identity", searchIn="description") -``` - -Best for discovering files by purpose. - -## Common Search Patterns - -| Goal | Command | -| ---------------- | ------------------------------------------------------------- | -| User preferences | `memory_search(query="user", searchIn="tags")` | -| Project info | `memory_search(query="architecture", searchIn="description")` | -| Code style | `memory_search(query="typescript", searchIn="content")` | -| Reference docs | `memory_search(query="reference", searchIn="tags")` | - -## Search Tips - -- **Case insensitive**: `typescript` and `TYPESCRIPT` work the same -- **Partial matches**: `auth` matches "auth", "authentication", "author" -- **Be specific**: "JWT token validation" > "token" -- **Try different types**: If content search fails, try tags or description - -## When Results Are Empty - -1. Check query spelling -2. Try different `searchIn` type -3. List all files: `memory_list()` -4. Sync repository: `memory_sync(action="pull")` - -## Related Skills - -- `memory-management` - Read and write files -- `memory-sync` - Ensure latest data -- `memory-init` - Setup repository diff --git a/packages/pi-memory-md/skills/memory-sync/SKILL.md b/packages/pi-memory-md/skills/memory-sync/SKILL.md deleted file mode 100644 index b93137d..0000000 --- a/packages/pi-memory-md/skills/memory-sync/SKILL.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -name: memory-sync -description: Git synchronization operations for pi-memory-md repository ---- - -# Memory Sync - -Git synchronization for pi-memory-md repository. - -## Configuration - -Configure `pi-memory-md.repoUrl` in settings file (global: `~/.pi/agent/settings.json`, project: `.pi/settings.json`) - -## Sync Operations - -### Pull - -Fetch latest changes from GitHub: - -``` -memory_sync(action="pull") -``` - -Use before starting work or switching machines. - -### Push - -Upload local changes to GitHub: - -``` -memory_sync(action="push") -``` - -Auto-commits changes before pushing. - -**Before pushing, ALWAYS run memory_check first:** - -``` -memory_check() -``` - -This verifies that the folder structure is correct (e.g., files are in `core/project/` not in a root `project/` folder). - -### Status - -Check uncommitted changes: - -``` -memory_sync(action="status") -``` - -Shows modified/added/deleted files. - -## Typical Workflow - -| Action | Command | -| -------------- | ------------------------------ | -| Get updates | `memory_sync(action="pull")` | -| Check changes | `memory_sync(action="status")` | -| Upload changes | `memory_sync(action="push")` | - -## Troubleshooting - -| Error | Solution | -| ----------------- | --------------------------------------- | -| Non-fast-forward | Pull first, then push | -| Conflicts | Manual resolution via bash git commands | -| Not a git repo | Run `memory_init(force=true)` | -| Permission denied | Check SSH keys or repo URL | - -## Related Skills - -- `memory-management` - Read and write files -- `memory-init` - Setup repository diff --git a/packages/pi-memory-md/tools.ts b/packages/pi-memory-md/tools.ts deleted file mode 100644 index a4e3425..0000000 --- a/packages/pi-memory-md/tools.ts +++ /dev/null @@ -1,732 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent"; -import { keyHint } from "@mariozechner/pi-coding-agent"; -import { Text } from "@mariozechner/pi-tui"; -import { Type } from "@sinclair/typebox"; -import type { MemoryFrontmatter, MemoryMdSettings } from "./memory-md.js"; -import { - getCurrentDate, - getMemoryDir, - gitExec, - listMemoryFiles, - readMemoryFile, - syncRepository, - writeMemoryFile, -} from "./memory-md.js"; - -function renderWithExpandHint( - text: string, - theme: Theme, - lineCount: number, -): Text { - const remaining = lineCount - 1; - if (remaining > 0) { - text += - "\n" + - theme.fg("muted", `... (${remaining} more lines,`) + - " " + - keyHint("expandTools", "to expand") + - theme.fg("muted", ")"); - } - return new Text(text, 0, 0); -} - -export function registerMemorySync( - pi: ExtensionAPI, - settings: MemoryMdSettings, - isRepoInitialized: { value: boolean }, -): void { - pi.registerTool({ - name: "memory_sync", - label: "Memory Sync", - description: "Synchronize memory repository with git (pull/push/status)", - parameters: Type.Object({ - action: Type.Union( - [Type.Literal("pull"), Type.Literal("push"), Type.Literal("status")], - { - description: "Action to perform", - }, - ), - }), - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const { action } = params as { action: "pull" | "push" | "status" }; - const localPath = settings.localPath!; - const memoryDir = getMemoryDir(settings, ctx); - const coreUserDir = path.join(memoryDir, "core", "user"); - - if (action === "status") { - const initialized = - isRepoInitialized.value && fs.existsSync(coreUserDir); - if (!initialized) { - return { - content: [ - { - type: "text", - text: "Memory repository not initialized. Use memory_init to set up.", - }, - ], - details: { initialized: false }, - }; - } - - const result = await gitExec(pi, localPath, "status", "--porcelain"); - const dirty = result.stdout.trim().length > 0; - - return { - content: [ - { - type: "text", - text: dirty - ? `Changes detected:\n${result.stdout}` - : "No uncommitted changes", - }, - ], - details: { initialized: true, dirty }, - }; - } - - if (action === "pull") { - const result = await syncRepository(pi, settings, isRepoInitialized); - return { - content: [{ type: "text", text: result.message }], - details: { success: result.success }, - }; - } - - if (action === "push") { - const statusResult = await gitExec( - pi, - localPath, - "status", - "--porcelain", - ); - const hasChanges = statusResult.stdout.trim().length > 0; - - if (hasChanges) { - await gitExec(pi, localPath, "add", "."); - - const timestamp = new Date() - .toISOString() - .replace(/[:.]/g, "-") - .slice(0, 19); - const commitMessage = `Update memory - ${timestamp}`; - const commitResult = await gitExec( - pi, - localPath, - "commit", - "-m", - commitMessage, - ); - - if (!commitResult.success) { - return { - content: [ - { type: "text", text: "Commit failed - nothing pushed" }, - ], - details: { success: false }, - }; - } - } - - const result = await gitExec(pi, localPath, "push"); - if (result.success) { - return { - content: [ - { - type: "text", - text: hasChanges - ? `Committed and pushed changes to repository` - : `No changes to commit, repository up to date`, - }, - ], - details: { success: true, committed: hasChanges }, - }; - } - return { - content: [{ type: "text", text: "Push failed - check git status" }], - details: { success: false }, - }; - } - - return { - content: [{ type: "text", text: "Unknown action" }], - details: {}, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("memory_sync ")); - text += theme.fg("accent", args.action); - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const content = result.content[0]; - if (content?.type !== "text") { - return new Text(theme.fg("dim", "Empty result"), 0, 0); - } - - if (isPartial) { - return new Text(theme.fg("warning", "Syncing..."), 0, 0); - } - - if (!expanded) { - const lines = content.text.split("\n"); - const summary = lines[0]; - return renderWithExpandHint( - theme.fg("success", summary), - theme, - lines.length, - ); - } - - return new Text(theme.fg("toolOutput", content.text), 0, 0); - }, - }); -} - -export function registerMemoryRead( - pi: ExtensionAPI, - settings: MemoryMdSettings, -): void { - pi.registerTool({ - name: "memory_read", - label: "Memory Read", - description: "Read a memory file by path", - parameters: Type.Object({ - path: Type.String({ - description: - "Relative path to memory file (e.g., 'core/user/identity.md')", - }), - }) as any, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const { path: relPath } = params as { path: string }; - const memoryDir = getMemoryDir(settings, ctx); - const fullPath = path.join(memoryDir, relPath); - - const memory = readMemoryFile(fullPath); - if (!memory) { - return { - content: [ - { type: "text", text: `Failed to read memory file: ${relPath}` }, - ], - details: { error: true }, - }; - } - - return { - content: [ - { - type: "text", - text: `# ${memory.frontmatter.description}\n\nTags: ${memory.frontmatter.tags?.join(", ") || "none"}\n\n${memory.content}`, - }, - ], - details: { frontmatter: memory.frontmatter }, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("memory_read ")); - text += theme.fg("accent", args.path); - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const details = result.details as - | { error?: boolean; frontmatter?: MemoryFrontmatter } - | undefined; - const content = result.content[0]; - - if (isPartial) { - return new Text(theme.fg("warning", "Reading..."), 0, 0); - } - - if (details?.error) { - const text = content?.type === "text" ? content.text : "Error"; - return new Text(theme.fg("error", text), 0, 0); - } - - const desc = details?.frontmatter?.description || "Memory file"; - const tags = details?.frontmatter?.tags?.join(", ") || "none"; - const text = content?.type === "text" ? content.text : ""; - - if (!expanded) { - const lines = text.split("\n"); - const summary = `${theme.fg("success", desc)}\n${theme.fg("muted", `Tags: ${tags}`)}`; - return renderWithExpandHint(summary, theme, lines.length + 2); - } - - let resultText = theme.fg("success", desc); - resultText += `\n${theme.fg("muted", `Tags: ${tags}`)}`; - if (text) { - resultText += `\n${theme.fg("toolOutput", text)}`; - } - return new Text(resultText, 0, 0); - }, - }); -} - -export function registerMemoryWrite( - pi: ExtensionAPI, - settings: MemoryMdSettings, -): void { - pi.registerTool({ - name: "memory_write", - label: "Memory Write", - description: "Create or update a memory file with YAML frontmatter", - parameters: Type.Object({ - path: Type.String({ - description: - "Relative path to memory file (e.g., 'core/user/identity.md')", - }), - content: Type.String({ description: "Markdown content" }), - description: Type.String({ description: "Description for frontmatter" }), - tags: Type.Optional(Type.Array(Type.String())), - }) as any, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const { - path: relPath, - content, - description, - tags, - } = params as { - path: string; - content: string; - description: string; - tags?: string[]; - }; - - const memoryDir = getMemoryDir(settings, ctx); - const fullPath = path.join(memoryDir, relPath); - - const existing = readMemoryFile(fullPath); - const existingFrontmatter = existing?.frontmatter || { description }; - - const frontmatter: MemoryFrontmatter = { - ...existingFrontmatter, - description, - updated: getCurrentDate(), - ...(tags && { tags }), - }; - - writeMemoryFile(fullPath, content, frontmatter); - - return { - content: [{ type: "text", text: `Memory file written: ${relPath}` }], - details: { path: fullPath, frontmatter }, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("memory_write ")); - text += theme.fg("accent", args.path); - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const content = result.content[0]; - if (content?.type !== "text") { - return new Text(theme.fg("dim", "Empty result"), 0, 0); - } - - if (isPartial) { - return new Text(theme.fg("warning", "Writing..."), 0, 0); - } - - if (!expanded) { - const details = result.details as - | { frontmatter?: MemoryFrontmatter } - | undefined; - const lineCount = details?.frontmatter ? 3 : 1; - return renderWithExpandHint( - theme.fg("success", `Written: ${content.text}`), - theme, - lineCount, - ); - } - - const details = result.details as - | { path?: string; frontmatter?: MemoryFrontmatter } - | undefined; - let text = theme.fg("success", content.text); - if (details?.frontmatter) { - const fm = details.frontmatter; - text += `\n${theme.fg("muted", `Description: ${fm.description}`)}`; - if (fm.tags) { - text += `\n${theme.fg("muted", `Tags: ${fm.tags.join(", ")}`)}`; - } - } - return new Text(text, 0, 0); - }, - }); -} - -export function registerMemoryList( - pi: ExtensionAPI, - settings: MemoryMdSettings, -): void { - pi.registerTool({ - name: "memory_list", - label: "Memory List", - description: "List all memory files in the repository", - parameters: Type.Object({ - directory: Type.Optional( - Type.String({ description: "Filter by directory (e.g., 'core/user')" }), - ), - }) as any, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const { directory } = params as { directory?: string }; - const memoryDir = getMemoryDir(settings, ctx); - const searchDir = directory ? path.join(memoryDir, directory) : memoryDir; - const files = listMemoryFiles(searchDir); - const relPaths = files.map((f) => path.relative(memoryDir, f)); - - return { - content: [ - { - type: "text", - text: `Memory files (${relPaths.length}):\n\n${relPaths.map((p) => ` - ${p}`).join("\n")}`, - }, - ], - details: { files: relPaths, count: relPaths.length }, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("memory_list")); - if (args.directory) { - text += ` ${theme.fg("accent", args.directory)}`; - } - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const details = result.details as { count?: number } | undefined; - - if (isPartial) { - return new Text(theme.fg("warning", "Listing..."), 0, 0); - } - - if (!expanded) { - const count = details?.count ?? 0; - const content = result.content[0]; - const lines = content?.type === "text" ? content.text.split("\n") : []; - return renderWithExpandHint( - theme.fg("success", `${count} memory files`), - theme, - lines.length, - ); - } - - const content = result.content[0]; - const text = content?.type === "text" ? content.text : ""; - return new Text(theme.fg("toolOutput", text), 0, 0); - }, - }); -} - -export function registerMemorySearch( - pi: ExtensionAPI, - settings: MemoryMdSettings, -): void { - pi.registerTool({ - name: "memory_search", - label: "Memory Search", - description: "Search memory files by content or tags", - parameters: Type.Object({ - query: Type.String({ description: "Search query" }), - searchIn: Type.Union( - [ - Type.Literal("content"), - Type.Literal("tags"), - Type.Literal("description"), - ], - { - description: "Where to search", - }, - ), - }) as any, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const { query, searchIn } = params as { - query: string; - searchIn: "content" | "tags" | "description"; - }; - const memoryDir = getMemoryDir(settings, ctx); - const files = listMemoryFiles(memoryDir); - const results: Array<{ path: string; match: string }> = []; - - const queryLower = query.toLowerCase(); - - for (const filePath of files) { - const memory = readMemoryFile(filePath); - if (!memory) continue; - - const relPath = path.relative(memoryDir, filePath); - const { frontmatter, content } = memory; - - if (searchIn === "content") { - if (content.toLowerCase().includes(queryLower)) { - const lines = content.split("\n"); - const matchLine = lines.find((line) => - line.toLowerCase().includes(queryLower), - ); - results.push({ - path: relPath, - match: matchLine || content.substring(0, 100), - }); - } - } else if (searchIn === "tags") { - if ( - frontmatter.tags?.some((tag) => - tag.toLowerCase().includes(queryLower), - ) - ) { - results.push({ - path: relPath, - match: `Tags: ${frontmatter.tags?.join(", ")}`, - }); - } - } else if (searchIn === "description") { - if (frontmatter.description.toLowerCase().includes(queryLower)) { - results.push({ path: relPath, match: frontmatter.description }); - } - } - } - - return { - content: [ - { - type: "text", - text: `Found ${results.length} result(s):\n\n${results.map((r) => ` ${r.path}\n ${r.match}`).join("\n\n")}`, - }, - ], - details: { results, count: results.length }, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("memory_search ")); - text += theme.fg("accent", `"${args.query}"`); - text += ` ${theme.fg("muted", args.searchIn)}`; - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const details = result.details as { count?: number } | undefined; - - if (isPartial) { - return new Text(theme.fg("warning", "Searching..."), 0, 0); - } - - if (!expanded) { - const count = details?.count ?? 0; - const content = result.content[0]; - const lines = content?.type === "text" ? content.text.split("\n") : []; - return renderWithExpandHint( - theme.fg("success", `${count} result(s)`), - theme, - lines.length, - ); - } - - const content = result.content[0]; - const text = content?.type === "text" ? content.text : ""; - return new Text(theme.fg("toolOutput", text), 0, 0); - }, - }); -} - -export function registerMemoryInit( - pi: ExtensionAPI, - settings: MemoryMdSettings, - isRepoInitialized: { value: boolean }, -): void { - pi.registerTool({ - name: "memory_init", - label: "Memory Init", - description: - "Initialize memory repository (clone or create initial structure)", - parameters: Type.Object({ - force: Type.Optional( - Type.Boolean({ description: "Reinitialize even if already set up" }), - ), - }) as any, - - async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { - const { force = false } = params as { force?: boolean }; - - if (isRepoInitialized.value && !force) { - return { - content: [ - { - type: "text", - text: "Memory repository already initialized. Use force: true to reinitialize.", - }, - ], - details: { initialized: true }, - }; - } - - const result = await syncRepository(pi, settings, isRepoInitialized); - - return { - content: [ - { - type: "text", - text: result.success - ? `Memory repository initialized:\n${result.message}\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}` - : `Initialization failed: ${result.message}`, - }, - ], - details: { success: result.success }, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("memory_init")); - if (args.force) { - text += ` ${theme.fg("warning", "--force")}`; - } - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const details = result.details as - | { initialized?: boolean; success?: boolean } - | undefined; - const content = result.content[0]; - - if (isPartial) { - return new Text(theme.fg("warning", "Initializing..."), 0, 0); - } - - if (details?.initialized) { - return new Text(theme.fg("muted", "Already initialized"), 0, 0); - } - - if (!expanded) { - const success = details?.success; - const contentText = content?.type === "text" ? content.text : ""; - const lines = contentText.split("\n"); - const summary = success - ? theme.fg("success", "Initialized") - : theme.fg("error", "Initialization failed"); - return renderWithExpandHint(summary, theme, lines.length); - } - - const text = content?.type === "text" ? content.text : ""; - return new Text(theme.fg("toolOutput", text), 0, 0); - }, - }); -} - -export function registerMemoryCheck( - pi: ExtensionAPI, - settings: MemoryMdSettings, -): void { - pi.registerTool({ - name: "memory_check", - label: "Memory Check", - description: "Check current project memory folder structure", - parameters: Type.Object({}) as any, - - async execute(_toolCallId, _params, _signal, _onUpdate, ctx) { - const memoryDir = getMemoryDir(settings, ctx); - - if (!fs.existsSync(memoryDir)) { - return { - content: [ - { - type: "text", - text: `Memory directory not found: ${memoryDir}\n\nProject memory may not be initialized yet.`, - }, - ], - details: { exists: false }, - }; - } - - const { execSync } = await import("node:child_process"); - let treeOutput = ""; - - try { - treeOutput = execSync(`tree -L 3 -I "node_modules" "${memoryDir}"`, { - encoding: "utf-8", - }); - } catch { - try { - treeOutput = execSync( - `find "${memoryDir}" -type d -not -path "*/node_modules/*" | head -20`, - { - encoding: "utf-8", - }, - ); - } catch { - treeOutput = - "Unable to generate directory tree. Please check permissions."; - } - } - - const files = listMemoryFiles(memoryDir); - const relPaths = files.map((f) => path.relative(memoryDir, f)); - - return { - content: [ - { - type: "text", - text: `Memory directory structure for project: ${path.basename(ctx.cwd)}\n\nPath: ${memoryDir}\n\n${treeOutput}\n\nMemory files (${relPaths.length}):\n${relPaths.map((p) => ` ${p}`).join("\n")}`, - }, - ], - details: { path: memoryDir, fileCount: relPaths.length }, - }; - }, - - renderCall(_args, theme) { - return new Text(theme.fg("toolTitle", theme.bold("memory_check")), 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - const details = result.details as - | { exists?: boolean; path?: string; fileCount?: number } - | undefined; - const content = result.content[0]; - - if (isPartial) { - return new Text(theme.fg("warning", "Checking..."), 0, 0); - } - - if (!expanded) { - const exists = details?.exists ?? true; - const fileCount = details?.fileCount ?? 0; - const contentText = content?.type === "text" ? content.text : ""; - const lines = contentText.split("\n"); - const summary = exists - ? theme.fg("success", `Structure: ${fileCount} files`) - : theme.fg("error", "Not initialized"); - return renderWithExpandHint(summary, theme, lines.length); - } - - const text = content?.type === "text" ? content.text : ""; - return new Text(theme.fg("toolOutput", text), 0, 0); - }, - }); -} - -export function registerAllTools( - pi: ExtensionAPI, - settings: MemoryMdSettings, - isRepoInitialized: { value: boolean }, -): void { - registerMemorySync(pi, settings, isRepoInitialized); - registerMemoryRead(pi, settings); - registerMemoryWrite(pi, settings); - registerMemoryList(pi, settings); - registerMemorySearch(pi, settings); - registerMemoryInit(pi, settings, isRepoInitialized); - registerMemoryCheck(pi, settings); -} diff --git a/public-install.sh b/public-install.sh index b19e8b8..f04391a 100755 --- a/public-install.sh +++ b/public-install.sh @@ -23,7 +23,6 @@ SERVICE_STDERR_LOG="" DEFAULT_PACKAGES=( "npm:@e9n/pi-channels" - "npm:pi-memory-md" "npm:pi-teams" )