@tpsdev-ai/pi-flair 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @tpsdev-ai/pi-flair
2
+
3
+ Pi extension for Flair memory access — persistent memory from within pi sessions.
4
+
5
+ ## Design Decision
6
+
7
+ **Implementation Path: Native pi Extension (Option B)**
8
+
9
+ - **MCP clients are NOT first-class in pi** — pi's core has no MCP client support. MCP appears only as anthropic-specific beta features in the SDK (`BetaMCPToolUseBlock`, etc.), not as a generic extension mechanism.
10
+ - **Option A (wrap flair-mcp)** would require:
11
+ - Waiting for pi to support MCP servers natively
12
+ - Deprecating flair-mcp's stdio transport in favor of HTTP-only
13
+ - Splitting maintenance between MCP and pi extensions
14
+ - **Option B (native extension)** wins because:
15
+ - Direct HTTP calls via `@tpsdev-ai/flair-client` (zero extra dependencies)
16
+ - Full control over tool registration and session lifecycle hooks
17
+ - Parity with flair-mcp features (search, store, bootstrap)
18
+ - Works today — no pi roadmap dependency
19
+
20
+ Reference: [pi extensions docs](https://pi.dev/docs/extensions.md)
21
+
22
+ ## Quick Start
23
+
24
+ ### Prerequisites
25
+
26
+ ```bash
27
+ npm install -g @tpsdev-ai/flair
28
+ flair init
29
+ flair agent add my-agent
30
+ ```
31
+
32
+ ### Install
33
+
34
+ ```bash
35
+ pi install npm:@tpsdev-ai/pi-flair
36
+ ```
37
+
38
+ Or project-local:
39
+
40
+ ```bash
41
+ pi install -l npm:@tpsdev-ai/pi-flair
42
+ ```
43
+
44
+ ### Configure
45
+
46
+ Add to `~/.pi/agent/settings.json` or `.pi/settings.json`:
47
+
48
+ ```json
49
+ {
50
+ "extensions": ["npm:@tpsdev-ai/pi-flair"],
51
+ "packages": ["npm:@tpsdev-ai/pi-flair"]
52
+ }
53
+ ```
54
+
55
+ Or use environment variables:
56
+
57
+ ```bash
58
+ export FLAIR_AGENT_ID=my-agent
59
+ export FLAIR_URL=http://127.0.0.1:9926
60
+ pi
61
+ ```
62
+
63
+ ## Tools
64
+
65
+ | Tool | Description |
66
+ |------|-------------|
67
+ | `memory_search` | Search memories by meaning. Understands temporal queries. |
68
+ | `memory_store` | Save memories with type + durability (permanent/persistent/standard/ephemeral). |
69
+ | `bootstrap` | Load session context: soul + memories + predicted context. |
70
+
71
+ ## Configuration Options
72
+
73
+ | Variable | Default | Description |
74
+ |----------|---------|-------------|
75
+ | `FLAIR_AGENT_ID` | *(required)* | Agent identity for memory scoping |
76
+ | `FLAIR_URL` | `http://127.0.0.1:9926` | Flair server URL |
77
+ | `FLAIR_KEY_PATH` | auto-resolved | Path to Ed25519 private key |
78
+ | `FLAIR_MAX_RECALL_RESULTS` | `5` | Max results for memory_search |
79
+ | `FLAIR_MAX_BOOTSTRAP_TOKENS` | `4000` | Max tokens in bootstrap output |
80
+ | `FLAIR_AUTO_RECALL` | `false` | Auto-load bootstrap on session start (opt-in) |
81
+ | `FLAIR_AUTO_CAPTURE` | `false` | Auto-save session context to memory |
82
+
83
+ ## Security Notes
84
+
85
+ ### Auto-Capture Warning
86
+
87
+ When `FLAIR_AUTO_CAPTURE=true`, all assistant responses are persisted to Flair memory with **ephemeral durability**. **This includes any secrets, credentials, or tokens your LLM may output.**
88
+
89
+ **Do not enable `FLAIR_AUTO_CAPTURE=true` if your sessions may output:**
90
+
91
+ - API keys (`sk-`, `ghp_`, `pat_`, etc.)
92
+ - Bearer tokens (`Bearer ` prefix)
93
+ - Private keys (`-----BEGIN PRIVATE KEY-----`, `-----BEGIN RSA PRIVATE KEY-----`)
94
+ - AWS/GCP/Azure credentials
95
+ - Any other sensitive data
96
+
97
+ Auto-capture is best-effort and uses `dedup: false` to ensure all content is captured. For production use, disable auto-capture and store only non-sensitive summaries manually via `memory_store`.
98
+
99
+ ## How It Works
100
+
101
+ ```
102
+ pi (extension) ↔ HTTP ↔ Flair (Harper)
103
+ ```
104
+
105
+ The extension calls Flair's HTTP API directly via `@tpsdev-ai/flair-client`. All memory is stored locally in `~/.flair/` with Ed25519 authentication.
106
+
107
+ ## Examples
108
+
109
+ ### Semantic Search
110
+
111
+ ```ts
112
+ // In a pi session:
113
+ memory_search(query: "what did I decide about auth flow?", limit: 5)
114
+ ```
115
+
116
+ ### Store Memory
117
+
118
+ ```ts
119
+ memory_store(
120
+ content: "PR reviews must include security assessment",
121
+ durability: "persistent"
122
+ )
123
+ ```
124
+
125
+ ### Bootstrap
126
+
127
+ ```ts
128
+ bootstrap(maxTokens: 4000)
129
+ ```
130
+
131
+ ## Testing
132
+
133
+ ```bash
134
+ cd packages/pi-flair
135
+ npm run build
136
+ # Run tests (TBD)
137
+ ```
138
+
139
+ ## License
140
+
141
+ [Apache 2.0](../../LICENSE)
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pi Extension for Flair Memory Access
3
+ *
4
+ * Adds Flair memory tools to pi sessions:
5
+ * - memory_search(agentId, query, limit) — semantic search
6
+ * - memory_store(agentId, content, durability) — save memories
7
+ * - bootstrap(agentId, maxTokens) — cold-start context
8
+ *
9
+ * Configuration:
10
+ * - flair_url (default: http://127.0.0.1:9926)
11
+ * - agentId (required, via FLAIR_AGENT_ID env var)
12
+ * - max_recall_results (default: 5)
13
+ * - max_bootstrap_tokens (default: 4000)
14
+ * - auto_capture (default: false) — auto-save session context to memory
15
+ * - auto_recall (default: false) — auto-load bootstrap on session start
16
+ *
17
+ * Usage:
18
+ * 1. Install: pi install npm:@tpsdev-ai/pi-flair
19
+ * 2. Configure in ~/.pi/agent/settings.json or .pi/settings.json:
20
+ * {
21
+ * "extensions": ["npm:@tpsdev-ai/pi-flair"],
22
+ * "flair_url": "http://127.0.0.1:9926",
23
+ * "agentId": "my-project"
24
+ * }
25
+ * 3. Or use environment variables:
26
+ * export FLAIR_AGENT_ID=my-agent
27
+ * export FLAIR_URL=http://127.0.0.1:9926
28
+ * 4. Restart pi
29
+ */
30
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
31
+ import { type FlairClientConfig } from "@tpsdev-ai/flair-client";
32
+ interface PluginConfig extends FlairClientConfig {
33
+ max_recall_results?: number;
34
+ max_bootstrap_tokens?: number;
35
+ auto_capture?: boolean;
36
+ auto_recall?: boolean;
37
+ }
38
+ /**
39
+ * Classify an error into a user-friendly message.
40
+ * @param err - The error to classify
41
+ * @param flairUrl - The Flair server URL for connection error messages
42
+ */
43
+ export declare function classifyError(err: unknown, flairUrl: string): string;
44
+ export default function (pi: ExtensionAPI): void;
45
+ export declare function manualBootstrap(config: PluginConfig, maxTokens?: number): Promise<string>;
46
+ export {};
47
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAEpF,OAAO,EAA2B,KAAK,iBAAiB,EAAwB,MAAM,yBAAyB,CAAC;AAIhH,UAAU,YAAa,SAAQ,iBAAiB;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAkFD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoBpE;AAQD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAyMxC;AAID,wBAAsB,eAAe,CACnC,MAAM,EAAE,YAAY,EACpB,SAAS,SAAO,GACf,OAAO,CAAC,MAAM,CAAC,CAIjB"}
package/dist/index.js ADDED
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Pi Extension for Flair Memory Access
3
+ *
4
+ * Adds Flair memory tools to pi sessions:
5
+ * - memory_search(agentId, query, limit) — semantic search
6
+ * - memory_store(agentId, content, durability) — save memories
7
+ * - bootstrap(agentId, maxTokens) — cold-start context
8
+ *
9
+ * Configuration:
10
+ * - flair_url (default: http://127.0.0.1:9926)
11
+ * - agentId (required, via FLAIR_AGENT_ID env var)
12
+ * - max_recall_results (default: 5)
13
+ * - max_bootstrap_tokens (default: 4000)
14
+ * - auto_capture (default: false) — auto-save session context to memory
15
+ * - auto_recall (default: false) — auto-load bootstrap on session start
16
+ *
17
+ * Usage:
18
+ * 1. Install: pi install npm:@tpsdev-ai/pi-flair
19
+ * 2. Configure in ~/.pi/agent/settings.json or .pi/settings.json:
20
+ * {
21
+ * "extensions": ["npm:@tpsdev-ai/pi-flair"],
22
+ * "flair_url": "http://127.0.0.1:9926",
23
+ * "agentId": "my-project"
24
+ * }
25
+ * 3. Or use environment variables:
26
+ * export FLAIR_AGENT_ID=my-agent
27
+ * export FLAIR_URL=http://127.0.0.1:9926
28
+ * 4. Restart pi
29
+ */
30
+ import { Type } from "@sinclair/typebox";
31
+ import { FlairClient, FlairError } from "@tpsdev-ai/flair-client";
32
+ // ─── Secret Filtering for Auto-Capture ───────────────────────────────────────
33
+ // Secret patterns to filter from auto-capture
34
+ const SECRET_PATTERNS = [
35
+ /sk-[a-zA-Z0-9]+/u, // OpenAI keys
36
+ /ghp_[a-zA-Z0-9_.-]+/u, // GitHub PATs (includes dots for JWT-like tokens)
37
+ /pat_[a-zA-Z0-9_.-]+/u, // Generic PATs (includes dots for JWT-like tokens)
38
+ /Bearer [a-zA-Z0-9_.-]+/u, // Bearer tokens (includes dots for JWTs)
39
+ /-----BEGIN PRIVATE KEY-----/u, // Private keys
40
+ /-----BEGIN RSA PRIVATE KEY-----/u, // RSA keys
41
+ /-----BEGIN EC PRIVATE KEY-----/u, // EC keys
42
+ ];
43
+ function containsSecrets(text) {
44
+ return SECRET_PATTERNS.some((pattern) => pattern.test(text));
45
+ }
46
+ // ─── Config Resolution ───────────────────────────────────────────────────────
47
+ function getConfig(pi) {
48
+ // Try to load from settings
49
+ // Note: pi doesn't expose settings.json directly in extension API
50
+ // We rely on environment variables and defaults
51
+ const flConfig = {
52
+ url: process.env.FLAIR_URL || "http://127.0.0.1:9926",
53
+ agentId: process.env.FLAIR_AGENT_ID,
54
+ keyPath: process.env.FLAIR_KEY_PATH,
55
+ };
56
+ return {
57
+ ...flConfig,
58
+ max_recall_results: parseInt(process.env.FLAIR_MAX_RECALL_RESULTS || "5", 10),
59
+ max_bootstrap_tokens: parseInt(process.env.FLAIR_MAX_BOOTSTRAP_TOKENS || "4000", 10),
60
+ auto_capture: process.env.FLAIR_AUTO_CAPTURE === "true",
61
+ auto_recall: process.env.FLAIR_AUTO_RECALL === "true", // default false (user must explicitly opt-in)
62
+ };
63
+ }
64
+ function getAgentId(config, ctx) {
65
+ if (config.agentId)
66
+ return config.agentId;
67
+ // Try to infer from working directory
68
+ const cwd = ctx.cwd;
69
+ const lastSlash = cwd.lastIndexOf("/");
70
+ let agentId;
71
+ if (lastSlash >= 0) {
72
+ agentId = cwd.slice(lastSlash + 1);
73
+ }
74
+ else {
75
+ agentId = cwd;
76
+ }
77
+ console.warn(`Flair: agentId not configured, falling back to cwd segment "${agentId}". Set FLAIR_AGENT_ID to silence this warning.`);
78
+ return agentId;
79
+ }
80
+ function createFlairClient(config) {
81
+ if (!config.agentId) {
82
+ throw new Error("FLAIR_AGENT_ID is required");
83
+ }
84
+ return new FlairClient({
85
+ agentId: config.agentId,
86
+ url: config.url || "http://127.0.0.1:9926",
87
+ keyPath: config.keyPath,
88
+ });
89
+ }
90
+ // ─── Error Classification ─────────────────────────────────────────────────────
91
+ /**
92
+ * Classify an error into a user-friendly message.
93
+ * @param err - The error to classify
94
+ * @param flairUrl - The Flair server URL for connection error messages
95
+ */
96
+ export function classifyError(err, flairUrl) {
97
+ if (err instanceof FlairError) {
98
+ const { status, body } = err;
99
+ if (status === 400)
100
+ return `validation_error: ${body}`;
101
+ if (status === 401 || status === 403)
102
+ return `auth_error: ${body}`;
103
+ if (status === 413)
104
+ return `payload_too_large: ${body}`;
105
+ if (status === 429)
106
+ return "rate_limited — retry after a moment";
107
+ if (status >= 500)
108
+ return `server_error (retriable): ${body}`;
109
+ return `http_error (${status}): ${body}`;
110
+ }
111
+ if (err instanceof Error) {
112
+ if (err.name.includes("Abort") || err.name.includes("Timeout")) {
113
+ return "timeout — the server took too long. Try shorter content or retry.";
114
+ }
115
+ if (err instanceof TypeError && err.message.includes("fetch")) {
116
+ return `connection_error (retriable): could not reach Flair at ${flairUrl}. Is it running?`;
117
+ }
118
+ return `unexpected_error: ${err.message}`;
119
+ }
120
+ return `unexpected_error: ${String(err)}`;
121
+ }
122
+ function errorResult(err, flairUrl) {
123
+ return { content: [{ type: "text", text: classifyError(err, flairUrl) }], details: {}, isError: true };
124
+ }
125
+ // ─── Extension Entry Point ────────────────────────────────────────────────────
126
+ export default function (pi) {
127
+ const config = getConfig(pi);
128
+ const flair = createFlairClient(config);
129
+ // ─── Tools ───────────────────────────────────────────────────────────────────
130
+ // memory_search tool
131
+ pi.registerTool({
132
+ name: "memory_search",
133
+ label: "Search Flair Memories",
134
+ description: "Search memories by meaning. Understands temporal queries like 'what happened today'.",
135
+ promptSnippet: "Search Flair memories for relevant context.",
136
+ promptGuidelines: [
137
+ "Use memory_search when you need to recall past conversations, decisions, or lessons from this project.",
138
+ "Use memory_search when the user asks about 'today', 'recently', or 'previously' in this session.",
139
+ ],
140
+ parameters: Type.Object({
141
+ query: Type.String({ description: "Search query — natural language, semantic matching" }),
142
+ limit: Type.Optional(Type.Number({ description: "Max results (default 5)" })),
143
+ }),
144
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
145
+ try {
146
+ const query = params.query;
147
+ const limit = params.limit ?? config.max_recall_results;
148
+ const results = await flair.memory.search(query, { limit });
149
+ if (results.length === 0) {
150
+ return { content: [{ type: "text", text: "No relevant memories found." }], details: {} };
151
+ }
152
+ const text = results
153
+ .map((r, i) => {
154
+ const date = r.createdAt ? r.createdAt.slice(0, 10) : "";
155
+ const idStr = r.id ? `id:${r.id}` : "";
156
+ const meta = [date, r.type, idStr].filter(Boolean).join(", ");
157
+ return `${i + 1}. ${r.content}${meta ? ` (${meta})` : ""}`;
158
+ })
159
+ .join("\n");
160
+ return { content: [{ type: "text", text }], details: {} };
161
+ }
162
+ catch (err) {
163
+ return errorResult(err, flair.url);
164
+ }
165
+ },
166
+ });
167
+ // memory_store tool
168
+ pi.registerTool({
169
+ name: "memory_store",
170
+ label: "Store Memory in Flair",
171
+ description: "Save information to persistent memory. Use for lessons, decisions, preferences, facts.",
172
+ promptSnippet: "Store memories for future recall.",
173
+ promptGuidelines: [
174
+ "Use memory_store to record important decisions, lessons learned, or preferences.",
175
+ "Use memory_store before finishing a session to capture key insights.",
176
+ ],
177
+ parameters: Type.Object({
178
+ content: Type.String({ description: "What to remember" }),
179
+ durability: Type.Optional(Type.String({
180
+ enum: ["permanent", "persistent", "standard", "ephemeral"],
181
+ description: "permanent — inviolable facts, identity, explicit never-forget (e.g., 'my name is Nathan')\n" +
182
+ "persistent — key decisions and lessons to recall weeks later (e.g., 'PR review process')\n" +
183
+ "standard — default working memory, recent context (e.g., 'discussed auth flow today')\n" +
184
+ "ephemeral — scratch state, auto-expires 72h (e.g., 'currently debugging issue #42')",
185
+ })),
186
+ tags: Type.Optional(Type.Array(Type.String(), { description: "Array of tag strings" })),
187
+ }),
188
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
189
+ try {
190
+ const content = params.content;
191
+ const durability = params.durability || "standard";
192
+ const result = await flair.memory.write(content, {
193
+ type: "session",
194
+ durability: durability,
195
+ tags: params.tags,
196
+ dedup: true,
197
+ dedupThreshold: 0.95,
198
+ });
199
+ // Check if dedup returned an existing memory
200
+ const agentId = getAgentId(config, ctx);
201
+ const generatedPrefix = `${agentId}-`;
202
+ const wasDeduped = result.id && !result.id.startsWith(generatedPrefix);
203
+ if (wasDeduped) {
204
+ return { content: [{ type: "text", text: `Similar memory already exists (id: ${result.id}): ${result.content?.slice(0, 200)}` }], details: {} };
205
+ }
206
+ const preview = content.length > 120 ? content.slice(0, 120) + "..." : content;
207
+ const tagStr = params.tags?.length ? params.tags.join(", ") : "none";
208
+ const text = [
209
+ `Memory stored (id: ${result.id})`,
210
+ `Preview: ${preview}`,
211
+ `Size: ${content.length} chars`,
212
+ `Tags: ${tagStr}`,
213
+ `Durability: ${durability}`,
214
+ ].join("\n");
215
+ return { content: [{ type: "text", text }], details: {} };
216
+ }
217
+ catch (err) {
218
+ return errorResult(err, flair.url);
219
+ }
220
+ },
221
+ });
222
+ // bootstrap tool
223
+ pi.registerTool({
224
+ name: "bootstrap",
225
+ label: "Bootstrap Flair Context",
226
+ description: "Get session context: soul + memories + predicted context. Run at session start.",
227
+ promptSnippet: "Load session context from Flair.",
228
+ promptGuidelines: [
229
+ "Call bootstrap at the start of a new session to load relevant past memories.",
230
+ "Only call bootstrap once per session — it's expensive.",
231
+ ],
232
+ parameters: Type.Object({
233
+ maxTokens: Type.Optional(Type.Number({ description: "Max tokens in output (default 4000)" })),
234
+ }),
235
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
236
+ try {
237
+ const maxTokens = params.maxTokens ?? config.max_bootstrap_tokens;
238
+ const result = await flair.bootstrap({ maxTokens });
239
+ if (!result.context) {
240
+ return { content: [{ type: "text", text: "No context available." }], details: {} };
241
+ }
242
+ return { content: [{ type: "text", text: result.context }], details: {} };
243
+ }
244
+ catch (err) {
245
+ return errorResult(err, flair.url);
246
+ }
247
+ },
248
+ });
249
+ // ─── Auto-Recall on Session Start ────────────────────────────────────────────
250
+ if (config.auto_recall !== false) {
251
+ pi.on("session_start", async (_event, ctx) => {
252
+ // In auto_recall mode, inject bootstrap context via before_agent_start
253
+ // This gives the LLM relevant memories at session start
254
+ ctx.ui.notify("Flair: loading session context...", "info");
255
+ try {
256
+ const maxTokens = config.max_bootstrap_tokens ?? 4000;
257
+ const result = await flair.bootstrap({ maxTokens });
258
+ if (result.context) {
259
+ // Inject a system message with the context
260
+ pi.appendEntry("flair-bootstrap", {
261
+ context: result.context,
262
+ timestamp: Date.now(),
263
+ });
264
+ ctx.ui.notify("Flair: context loaded", "info");
265
+ }
266
+ else {
267
+ ctx.ui.notify("Flair: no context available", "info");
268
+ }
269
+ }
270
+ catch (err) {
271
+ ctx.ui.notify(`Flair bootstrap failed: ${classifyError(err, flair.url)}`, "error");
272
+ }
273
+ });
274
+ }
275
+ // ─── Auto-Capture on Turn End (optional) ────────────────────────────────────
276
+ if (config.auto_capture === true) {
277
+ pi.on("turn_end", async (_event, ctx) => {
278
+ // Extract key insights from the turn and store them
279
+ // This is a simplified version — real implementation would parse the conversation
280
+ const branch = ctx.sessionManager.getBranch();
281
+ if (branch.length >= 2) {
282
+ const lastEntry = branch[branch.length - 2];
283
+ if (lastEntry.type === "message" && lastEntry.message.role === "assistant" && lastEntry.message.content) {
284
+ // Convert to string for storage
285
+ const content = JSON.stringify(lastEntry.message.content);
286
+ // Filter out content containing secrets
287
+ if (containsSecrets(content)) {
288
+ console.warn("Auto-capture skipped: potential secrets detected");
289
+ return;
290
+ }
291
+ if (content.length > 100) {
292
+ try {
293
+ await flair.memory.write(content.slice(0, 4000), {
294
+ type: "session",
295
+ durability: "ephemeral",
296
+ dedup: false,
297
+ });
298
+ }
299
+ catch (err) {
300
+ // Silently fail — auto-capture is best-effort
301
+ console.warn("Auto-capture failed:", classifyError(err, flair.url));
302
+ }
303
+ }
304
+ }
305
+ }
306
+ });
307
+ }
308
+ }
309
+ // ─── Helper for manual bootstrap injection ────────────────────────────────────
310
+ export async function manualBootstrap(config, maxTokens = 4000) {
311
+ const flair = createFlairClient(config);
312
+ const result = await flair.bootstrap({ maxTokens });
313
+ return result.context || "No context available.";
314
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tpsdev-ai/pi-flair",
3
+ "version": "0.8.0",
4
+ "description": "Flair memory extension for pi — persistent memory access from within pi sessions",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist/",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "dependencies": {
24
+ "@tpsdev-ai/flair-client": "0.8.0",
25
+ "@sinclair/typebox": "0.34.48"
26
+ },
27
+ "license": "Apache-2.0",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/tpsdev-ai/flair.git",
31
+ "directory": "packages/pi-flair"
32
+ },
33
+ "homepage": "https://tps.dev/#flair",
34
+ "keywords": [
35
+ "pi",
36
+ "pi-extension",
37
+ "flair",
38
+ "memory",
39
+ "semantic-search",
40
+ "ai",
41
+ "agent"
42
+ ],
43
+ "devDependencies": {
44
+ "@mariozechner/pi-coding-agent": "0.73.0",
45
+ "@types/node": "^24.0.0",
46
+ "typescript": "^5.0.0"
47
+ }
48
+ }