@tpsdev-ai/flair-mcp 0.48.0 → 0.50.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 CHANGED
@@ -41,12 +41,13 @@ Once configured, Claude Code (or any MCP client) gets these tools:
41
41
  | Tool | Description |
42
42
  |------|-------------|
43
43
  | `memory_search` | Semantic search across memories. Understands "what happened today". |
44
- | `memory_store` | Save a memory with type (lesson/decision/fact) and durability. |
44
+ | `memory_store` | Save a memory with type (lesson/decision/fact) and durability. Optional `usedMemoryIds` cites memories that informed the write. |
45
45
  | `memory_get` | Retrieve a specific memory by ID. |
46
46
  | `memory_delete` | Delete a memory. |
47
47
  | `bootstrap` | Cold-start context — soul + recent memories in one call. |
48
48
  | `soul_set` | Set personality or project context (included in every bootstrap). |
49
49
  | `soul_get` | Get a personality or project context entry. |
50
+ | `record_usage` | Report that recalled memories were actually used (drives `usageCount`). |
50
51
 
51
52
  ## Environment Variables
52
53
 
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  * - soul_get — get a personality/context entry
15
15
  * - flair_workspace_set — write own WorkspaceState (Office Space coordination)
16
16
  * - flair_orgevent — publish an OrgEvent attributed to self (no forging)
17
+ * - record_usage — report that recalled memories were actually used (flair#1147)
17
18
  *
18
19
  * Auto-presence (flair#598): every tool call above triggers a fire-and-forget,
19
20
  * rate-limited `POST /Presence` heartbeat for the calling agent (see
@@ -38,4 +39,5 @@
38
39
  * — the silent `npx -y @tpsdev-ai/flair-mcp` failure. The shim checks the Node
39
40
  * version FIRST, then dynamically imports this module and calls runMcp().
40
41
  */
42
+ export declare function classifyError(err: unknown, flairUrl: string): string;
41
43
  export declare function runMcp(): Promise<void>;
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * - soul_get — get a personality/context entry
15
15
  * - flair_workspace_set — write own WorkspaceState (Office Space coordination)
16
16
  * - flair_orgevent — publish an OrgEvent attributed to self (no forging)
17
+ * - record_usage — report that recalled memories were actually used (flair#1147)
17
18
  *
18
19
  * Auto-presence (flair#598): every tool call above triggers a fire-and-forget,
19
20
  * rate-limited `POST /Presence` heartbeat for the calling agent (see
@@ -40,25 +41,27 @@
40
41
  */
41
42
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
42
43
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
43
- import { FlairClient, FlairError } from "@tpsdev-ai/flair-client";
44
+ import { FlairClient, FlairError, formatKeyLookup, inspectKeyLookup } from "@tpsdev-ai/flair-client";
44
45
  import { z } from "zod";
45
46
  import { deriveActivity, postPresenceSafe, resolveHeartbeatIntervalMs, resolvePresenceTimeoutMs, shouldSendHeartbeat, } from "./presence.js";
46
47
  import { readEnvOrUnset, stripInterpolationLiteralsFromEnv } from "./env-guard.js";
48
+ import { buildRecordUsageBody, citationIds, withCiteNudge } from "./usage.js";
49
+ import { serverInfo } from "./version.js";
47
50
  // ─── Error helpers ──────────────────────────────────────────────────────────
48
- function classifyError(err, flairUrl) {
51
+ export function classifyError(err, flairUrl) {
49
52
  if (err instanceof FlairError) {
50
53
  const { status, body } = err;
51
54
  if (status === 400)
52
55
  return `validation_error: ${body}`;
53
56
  if (status === 401 || status === 403) {
54
- // Auth failure on a previously-working session usually means the daemon
55
- // restarted (config reload, Harper alter_user, port change). Tell the
56
- // operator how to recover instead of just surfacing the raw 401 body.
57
- return `auth_error: ${body}\n` +
58
- `(Hint: this often follows a Flair daemon restart. Try:\n` +
59
- ` 1. Restart your MCP host (Claude Code, Cursor, etc) to spawn a fresh flair-mcp.\n` +
60
- ` 2. Check daemon: 'flair status' or 'curl ${flairUrl}/Health'.\n` +
61
- ` 3. Verify your agent key still matches the registered Agent record.)`;
57
+ // flair#1271: name the agent, the paths that were looked in, and the
58
+ // remedy. A cached-miss / wrong-HOME 401 is not a daemon-restart hint.
59
+ const lookup = err.keyLookup ?? {
60
+ ...inspectKeyLookup(readEnvOrUnset("FLAIR_AGENT_ID") ?? "", readEnvOrUnset("FLAIR_KEY_PATH")),
61
+ signed: false,
62
+ authMethod: "none",
63
+ };
64
+ return `auth_error: ${body}\n${formatKeyLookup(lookup)}`;
62
65
  }
63
66
  if (status === 413)
64
67
  return `payload_too_large: ${body}`;
@@ -212,10 +215,7 @@ export async function runMcp() {
212
215
  postPresenceSafe(presenceFlair, activity, lastKnownTask, resolvePresenceTimeoutMs()).catch(() => { });
213
216
  }
214
217
  // ─── MCP Server ──────────────────────────────────────────────────────────────
215
- const server = new McpServer({
216
- name: "flair",
217
- version: "0.1.0",
218
- });
218
+ const server = new McpServer(serverInfo());
219
219
  // ─── Tools ───────────────────────────────────────────────────────────────────
220
220
  server.tool("memory_search", "Search memories by meaning. Understands temporal queries like 'what happened today'.", {
221
221
  query: z.string().describe("Search query — natural language, semantic matching"),
@@ -235,7 +235,7 @@ export async function runMcp() {
235
235
  return `${i + 1}. ${r.content}${meta ? ` (${meta})` : ""}`;
236
236
  })
237
237
  .join("\n");
238
- return { content: [{ type: "text", text }] };
238
+ return { content: [{ type: "text", text: withCiteNudge(text) }] };
239
239
  }
240
240
  catch (err) {
241
241
  return errorResult(err, flair.url);
@@ -254,7 +254,9 @@ export async function runMcp() {
254
254
  "permanent/persistent -> shared, standard/ephemeral -> private). " +
255
255
  "private -- never visible to another agent, even one with a memory grant. " +
256
256
  "shared -- visible to the owner and any agent holding a read/search grant."),
257
- }, async ({ content, type, durability, tags, visibility }) => {
257
+ usedMemoryIds: z.array(z.string()).optional().describe("IDs of memories that informed this write (citation-on-write). Credited via the same " +
258
+ "deduped usage ledger as record_usage. Optional."),
259
+ }, async ({ content, type, durability, tags, visibility, usedMemoryIds }) => {
258
260
  heartbeat(); // auto-presence (flair#598) — fire-and-forget, rate-limited
259
261
  try {
260
262
  const result = await flair.memory.write(content, {
@@ -264,6 +266,7 @@ export async function runMcp() {
264
266
  visibility: visibility,
265
267
  dedup: true,
266
268
  dedupThreshold: 0.95,
269
+ usedMemoryIds: citationIds(usedMemoryIds),
267
270
  });
268
271
  // The server's conservative dedup gate NEVER suppresses a write
269
272
  // (memory-integrity fix, flair#526) — `result.deduplicated` is a
@@ -307,10 +310,15 @@ export async function runMcp() {
307
310
  content: z.string().describe("New content"),
308
311
  preserveHistory: z.coerce.boolean().optional().default(false)
309
312
  .describe("Write a new supersedes-linked version instead of overwriting in place (default false)"),
310
- }, async ({ id, content, preserveHistory }) => {
313
+ usedMemoryIds: z.array(z.string()).optional().describe("IDs of memories that informed this update (citation-on-write). Credited via the same " +
314
+ "deduped usage ledger as record_usage. Optional."),
315
+ }, async ({ id, content, preserveHistory, usedMemoryIds }) => {
311
316
  heartbeat(); // auto-presence (flair#598) — fire-and-forget, rate-limited
312
317
  try {
313
- const result = await flair.memory.update(id, content, { preserveHistory });
318
+ const result = await flair.memory.update(id, content, {
319
+ preserveHistory,
320
+ usedMemoryIds: citationIds(usedMemoryIds),
321
+ });
314
322
  const text = preserveHistory
315
323
  ? `Memory updated: new version stored (id: ${result.id}), supersedes ${id}.`
316
324
  : `Memory updated (id: ${id}).`;
@@ -406,7 +414,7 @@ export async function runMcp() {
406
414
  if (!result.context) {
407
415
  return { content: [{ type: "text", text: "No context available." }] };
408
416
  }
409
- return { content: [{ type: "text", text: result.context }] };
417
+ return { content: [{ type: "text", text: withCiteNudge(result.context) }] };
410
418
  }
411
419
  catch (err) {
412
420
  return errorResult(err, flair.url);
@@ -506,6 +514,38 @@ export async function runMcp() {
506
514
  return errorResult(err, flair.url);
507
515
  }
508
516
  });
517
+ // ─── Usage feedback (flair#1147) ─────────────────────────────────────────────
518
+ //
519
+ // POST /RecordUsage already existed; native /mcp already wrapped it. The
520
+ // stdio package did not, so a Claude Code / Cursor client could not close
521
+ // the usageCount loop. Identity is taken from the signed request — the body
522
+ // carries only memory id(s) + optional attribution, never agentId.
523
+ server.tool("record_usage", "Report that one or more memories were actually USED — cited or relied on to ground an answer or decision. " +
524
+ "Distinct from search (surfacing a memory is not usage). Dedup'd (you can only count once per memory) and rate-limited.", {
525
+ memoryId: z.string().optional().describe("A single memory id that was used"),
526
+ memoryIds: z.array(z.string()).optional().describe("IDs of the memories that were used (max 20 per call)"),
527
+ attribution: z.string().optional().describe("Optional one-line note on how it was used (opaque — stored for audit only)"),
528
+ }, async ({ memoryId, memoryIds, attribution }) => {
529
+ heartbeat(); // auto-presence (flair#598) — fire-and-forget, rate-limited
530
+ try {
531
+ const body = buildRecordUsageBody({ memoryId, memoryIds, attribution });
532
+ if (!body) {
533
+ return {
534
+ content: [{ type: "text", text: "record_usage requires memoryId or memoryIds." }],
535
+ isError: true,
536
+ };
537
+ }
538
+ const result = await flair.request("POST", "/RecordUsage", body);
539
+ const text = result?.recorded === true ? "Usage recorded." : "Usage request accepted.";
540
+ return {
541
+ content: [{ type: "text", text }],
542
+ structuredContent: { recorded: result?.recorded === true },
543
+ };
544
+ }
545
+ catch (err) {
546
+ return errorResult(err, flair.url);
547
+ }
548
+ });
509
549
  // ─── Start ───────────────────────────────────────────────────────────────────
510
550
  const transport = new StdioServerTransport();
511
551
  await server.connect(transport);
@@ -68,7 +68,7 @@
68
68
  * "hooks": {
69
69
  * "SessionStart": [
70
70
  * { "hooks": [ { "type": "command",
71
- * "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'" } ] }
71
+ * "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y -p @tpsdev-ai/flair-mcp@<version> flair-session-start 2>/dev/null) && printf %s \"$out\" || true'" } ] }
72
72
  * ]
73
73
  * }
74
74
  * }
@@ -68,7 +68,7 @@
68
68
  * "hooks": {
69
69
  * "SessionStart": [
70
70
  * { "hooks": [ { "type": "command",
71
- * "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'" } ] }
71
+ * "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y -p @tpsdev-ai/flair-mcp@<version> flair-session-start 2>/dev/null) && printf %s \"$out\" || true'" } ] }
72
72
  * ]
73
73
  * }
74
74
  * }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * flair#1147 — the usage-feedback loop on the stdio MCP surface.
3
+ *
4
+ * Native `/mcp` already exposes `record_usage` and `memory_store.usedMemoryIds`.
5
+ * The stdio package (`@tpsdev-ai/flair-mcp`) did not, so a Claude Code / Cursor
6
+ * client had no way to reach POST /RecordUsage. These helpers are the thin
7
+ * client-side half: body construction for that endpoint, citation passthrough
8
+ * on write, and the one-line "cite what you use" nudge on recalled ids.
9
+ *
10
+ * Identity is NEVER in the body — RecordUsage attributes from the signed
11
+ * request, same no-forge contract as flair_workspace_set / flair_orgevent.
12
+ */
13
+ /** One-line instruction on recalled ids (issue ask #3). Search/bootstrap hits are not usage. */
14
+ export declare const CITE_USAGE_NUDGE = "Cite memories you actually use via record_usage (or memory_store.usedMemoryIds). A search or bootstrap hit is not usage.";
15
+ export declare function withCiteNudge(text: string): string;
16
+ /**
17
+ * Build the POST /RecordUsage body from the MCP tool args.
18
+ * Accepts singular `memoryId` and/or `memoryIds`. Returns null when there is
19
+ * nothing to send (the tool should fail locally rather than POST an empty list).
20
+ * Never includes agentId — the server attributes from the signature.
21
+ *
22
+ * MERGE vs PREFER (deliberate, named — Sherlock/Kern #1404):
23
+ * This helper MERGES `memoryId` + `memoryIds`, then dedupes. Native `/mcp`
24
+ * `recordUsage` (resources/mcp-tools.ts) PREFERS `memoryIds` and drops
25
+ * `memoryId` when both are supplied. The HTTP endpoint does the same:
26
+ * `RecordUsage.post()` is `data?.memoryIds ?? [data?.memoryId]` — PREFER,
27
+ * not union. If `memoryIds` is present (even `[]`, which is truthy),
28
+ * `memoryId` is never read.
29
+ *
30
+ * The stdio merge is load-bearing because it flattens first: we send only
31
+ * a single `memoryIds` array, so the server's prefer is never exercised
32
+ * on two fields. A future path that POSTs both fields through to
33
+ * `/RecordUsage` without flattening would silently drop `memoryId`
34
+ * (delivered-but-uncounted; empty `memoryIds: []` alongside a real
35
+ * `memoryId` would 400 rather than fall through).
36
+ *
37
+ * Native prefer is a pre-existing delivered-but-uncounted bug on a
38
+ * different surface, tracked in flair#1410 — not a regression from this
39
+ * PR, and not a blocker for #1147. Do not "align" this helper to prefer.
40
+ */
41
+ export declare function buildRecordUsageBody(args: {
42
+ memoryId?: string;
43
+ memoryIds?: string[];
44
+ attribution?: string;
45
+ }): {
46
+ memoryIds: string[];
47
+ attribution?: string;
48
+ } | null;
49
+ /**
50
+ * Citation-on-write passthrough. Only returns a list when the caller actually
51
+ * supplied a non-empty array of non-empty strings — omitted/empty is undefined
52
+ * so the write body stays byte-identical to a pre-#1147 write.
53
+ */
54
+ export declare function citationIds(usedMemoryIds?: string[]): string[] | undefined;
package/dist/usage.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * flair#1147 — the usage-feedback loop on the stdio MCP surface.
3
+ *
4
+ * Native `/mcp` already exposes `record_usage` and `memory_store.usedMemoryIds`.
5
+ * The stdio package (`@tpsdev-ai/flair-mcp`) did not, so a Claude Code / Cursor
6
+ * client had no way to reach POST /RecordUsage. These helpers are the thin
7
+ * client-side half: body construction for that endpoint, citation passthrough
8
+ * on write, and the one-line "cite what you use" nudge on recalled ids.
9
+ *
10
+ * Identity is NEVER in the body — RecordUsage attributes from the signed
11
+ * request, same no-forge contract as flair_workspace_set / flair_orgevent.
12
+ */
13
+ /** One-line instruction on recalled ids (issue ask #3). Search/bootstrap hits are not usage. */
14
+ export const CITE_USAGE_NUDGE = "Cite memories you actually use via record_usage (or memory_store.usedMemoryIds). A search or bootstrap hit is not usage.";
15
+ export function withCiteNudge(text) {
16
+ if (!text)
17
+ return text;
18
+ return `${text}\n\n${CITE_USAGE_NUDGE}`;
19
+ }
20
+ /**
21
+ * Build the POST /RecordUsage body from the MCP tool args.
22
+ * Accepts singular `memoryId` and/or `memoryIds`. Returns null when there is
23
+ * nothing to send (the tool should fail locally rather than POST an empty list).
24
+ * Never includes agentId — the server attributes from the signature.
25
+ *
26
+ * MERGE vs PREFER (deliberate, named — Sherlock/Kern #1404):
27
+ * This helper MERGES `memoryId` + `memoryIds`, then dedupes. Native `/mcp`
28
+ * `recordUsage` (resources/mcp-tools.ts) PREFERS `memoryIds` and drops
29
+ * `memoryId` when both are supplied. The HTTP endpoint does the same:
30
+ * `RecordUsage.post()` is `data?.memoryIds ?? [data?.memoryId]` — PREFER,
31
+ * not union. If `memoryIds` is present (even `[]`, which is truthy),
32
+ * `memoryId` is never read.
33
+ *
34
+ * The stdio merge is load-bearing because it flattens first: we send only
35
+ * a single `memoryIds` array, so the server's prefer is never exercised
36
+ * on two fields. A future path that POSTs both fields through to
37
+ * `/RecordUsage` without flattening would silently drop `memoryId`
38
+ * (delivered-but-uncounted; empty `memoryIds: []` alongside a real
39
+ * `memoryId` would 400 rather than fall through).
40
+ *
41
+ * Native prefer is a pre-existing delivered-but-uncounted bug on a
42
+ * different surface, tracked in flair#1410 — not a regression from this
43
+ * PR, and not a blocker for #1147. Do not "align" this helper to prefer.
44
+ */
45
+ export function buildRecordUsageBody(args) {
46
+ const ids = [];
47
+ if (Array.isArray(args.memoryIds)) {
48
+ for (const id of args.memoryIds) {
49
+ if (typeof id === "string" && id.length > 0)
50
+ ids.push(id);
51
+ }
52
+ }
53
+ if (typeof args.memoryId === "string" && args.memoryId.length > 0) {
54
+ ids.push(args.memoryId);
55
+ }
56
+ const memoryIds = [...new Set(ids)];
57
+ if (memoryIds.length === 0)
58
+ return null;
59
+ const body = { memoryIds };
60
+ if (typeof args.attribution === "string" && args.attribution.length > 0) {
61
+ body.attribution = args.attribution;
62
+ }
63
+ return body;
64
+ }
65
+ /**
66
+ * Citation-on-write passthrough. Only returns a list when the caller actually
67
+ * supplied a non-empty array of non-empty strings — omitted/empty is undefined
68
+ * so the write body stays byte-identical to a pre-#1147 write.
69
+ */
70
+ export function citationIds(usedMemoryIds) {
71
+ if (!Array.isArray(usedMemoryIds) || usedMemoryIds.length === 0)
72
+ return undefined;
73
+ if (!usedMemoryIds.every((id) => typeof id === "string" && id.length > 0))
74
+ return undefined;
75
+ return usedMemoryIds;
76
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * version.ts — the version advertised in MCP `initialize` `serverInfo`.
3
+ *
4
+ * `process.env.npm_package_version` is only populated inside `npm run`, so
5
+ * reading this package's own package.json relative to THIS running module is
6
+ * the only way to report the version of the code that's actually executing
7
+ * (same reason as resources/version.ts). Walk-by-name rather than a fixed
8
+ * number of `..` hops, matching src/lib/mcp-spec.ts: this file compiles to
9
+ * dist/version.js, tests import the .ts next to src/, and a hardcoded depth
10
+ * is one move away from silently advertising the wrong package — or "dev".
11
+ */
12
+ /** The published package whose version `initialize` must report. */
13
+ export declare const FLAIR_MCP_PACKAGE = "@tpsdev-ai/flair-mcp";
14
+ /** What we advertise when package.json cannot be read. */
15
+ export declare const UNKNOWN_VERSION = "dev";
16
+ /**
17
+ * Walk up from `startDir` looking for `@tpsdev-ai/flair-mcp`'s own package.json.
18
+ * Exported for tests, which need to exercise the not-found path without
19
+ * corrupting a real install.
20
+ */
21
+ export declare function resolvePackageVersionFrom(startDir: string): string;
22
+ /** This package's version — what `initialize` puts in `serverInfo.version`. */
23
+ export declare function resolvePackageVersion(): string;
24
+ /** The `serverInfo` object passed to `McpServer` (and thus returned on initialize). */
25
+ export declare function serverInfo(): {
26
+ name: "flair";
27
+ version: string;
28
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * version.ts — the version advertised in MCP `initialize` `serverInfo`.
3
+ *
4
+ * `process.env.npm_package_version` is only populated inside `npm run`, so
5
+ * reading this package's own package.json relative to THIS running module is
6
+ * the only way to report the version of the code that's actually executing
7
+ * (same reason as resources/version.ts). Walk-by-name rather than a fixed
8
+ * number of `..` hops, matching src/lib/mcp-spec.ts: this file compiles to
9
+ * dist/version.js, tests import the .ts next to src/, and a hardcoded depth
10
+ * is one move away from silently advertising the wrong package — or "dev".
11
+ */
12
+ import { readFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ /** The published package whose version `initialize` must report. */
16
+ export const FLAIR_MCP_PACKAGE = "@tpsdev-ai/flair-mcp";
17
+ /** What we advertise when package.json cannot be read. */
18
+ export const UNKNOWN_VERSION = "dev";
19
+ /**
20
+ * Walk up from `startDir` looking for `@tpsdev-ai/flair-mcp`'s own package.json.
21
+ * Exported for tests, which need to exercise the not-found path without
22
+ * corrupting a real install.
23
+ */
24
+ export function resolvePackageVersionFrom(startDir) {
25
+ let dir = startDir;
26
+ // 8 levels is far more than any real layout needs (dist/ → package root is 1)
27
+ // while still terminating on a pathological symlink loop.
28
+ for (let i = 0; i < 8; i++) {
29
+ try {
30
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
31
+ if (pkg?.name === FLAIR_MCP_PACKAGE && typeof pkg.version === "string" && pkg.version) {
32
+ return pkg.version;
33
+ }
34
+ }
35
+ catch {
36
+ // No package.json here, or unreadable/malformed — keep walking.
37
+ }
38
+ const parent = dirname(dir);
39
+ if (parent === dir)
40
+ break;
41
+ dir = parent;
42
+ }
43
+ return process.env.npm_package_version ?? UNKNOWN_VERSION;
44
+ }
45
+ /** This package's version — what `initialize` puts in `serverInfo.version`. */
46
+ export function resolvePackageVersion() {
47
+ return resolvePackageVersionFrom(dirname(fileURLToPath(import.meta.url)));
48
+ }
49
+ /** The `serverInfo` object passed to `McpServer` (and thus returned on initialize). */
50
+ export function serverInfo() {
51
+ return { name: "flair", version: resolvePackageVersion() };
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair-mcp",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "MCP server for Flair — persistent memory for Claude Code, Cursor, and any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "1.27.1",
31
- "@tpsdev-ai/flair-client": "0.48.0",
31
+ "@tpsdev-ai/flair-client": "0.50.0",
32
32
  "zod": "4.3.6"
33
33
  },
34
34
  "license": "Apache-2.0",