@tpsdev-ai/flair-client 0.7.0 → 0.8.1

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/dist/client.d.ts CHANGED
@@ -16,6 +16,7 @@ export declare class FlairClient {
16
16
  private privateKey;
17
17
  private keyResolved;
18
18
  private keyPath;
19
+ private rawPrivateKey;
19
20
  private timeoutMs;
20
21
  private basicAuth;
21
22
  constructor(config: FlairClientConfig);
@@ -61,11 +62,26 @@ declare class MemoryApi {
61
62
  }): Promise<SearchResult[]>;
62
63
  /** Get a memory by ID. */
63
64
  get(id: string): Promise<Memory | null>;
64
- /** List recent memories. */
65
+ /**
66
+ * List recent memories. All filters combine with AND.
67
+ *
68
+ * Uses Harper's `POST /Memory/search_by_conditions` endpoint with an
69
+ * explicit conditions array. The Memory.search() override injects the
70
+ * agentId scoping condition.
71
+ *
72
+ * Note: `order` is applied client-side after retrieval. Harper's
73
+ * search_by_conditions does not accept a sort/order field in the body.
74
+ */
65
75
  list(opts?: {
76
+ tags?: string[];
66
77
  limit?: number;
67
78
  type?: MemoryType;
68
79
  durability?: Durability;
80
+ /** Filter by subject (entity the memory is about). Indexed; efficient. */
81
+ subject?: string;
82
+ /** Chronological ordering applied client-side after retrieval.
83
+ * Server-side sort is not available via search_by_conditions. */
84
+ order?: "createdAt-asc" | "createdAt-desc";
69
85
  }): Promise<Memory[]>;
70
86
  /** Delete a memory. */
71
87
  delete(id: string): Promise<void>;
package/dist/client.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * const results = await flair.memory.search('that thing')
8
8
  * const ctx = await flair.bootstrap({ maxTokens: 4000 })
9
9
  */
10
+ import { createPrivateKey } from "node:crypto";
10
11
  import { loadPrivateKey, resolveKeyPath, signRequest } from "./auth.js";
11
12
  const DEFAULT_URL = "http://localhost:19926";
12
13
  const DEFAULT_TIMEOUT = 30_000;
@@ -18,12 +19,16 @@ export class FlairClient {
18
19
  privateKey = null;
19
20
  keyResolved = false;
20
21
  keyPath;
22
+ rawPrivateKey;
21
23
  timeoutMs;
22
24
  basicAuth = null;
23
25
  constructor(config) {
24
26
  this.url = (config.url ?? process.env.FLAIR_URL ?? DEFAULT_URL).replace(/\/$/, "");
25
27
  this.agentId = config.agentId || process.env.FLAIR_AGENT_ID || "";
26
28
  this.keyPath = config.keyPath;
29
+ if (config.privateKey !== undefined) {
30
+ this.rawPrivateKey = config.privateKey;
31
+ }
27
32
  this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT;
28
33
  // Basic auth fallback for standalone deployments without Ed25519 keys
29
34
  const adminUser = config.adminUser ?? process.env.FLAIR_ADMIN_USER;
@@ -38,6 +43,16 @@ export class FlairClient {
38
43
  if (this.keyResolved)
39
44
  return this.privateKey;
40
45
  this.keyResolved = true;
46
+ // In-memory key takes priority over file-based resolution.
47
+ if (this.rawPrivateKey) {
48
+ if (typeof this.rawPrivateKey === "string") {
49
+ this.privateKey = createPrivateKey(this.rawPrivateKey);
50
+ }
51
+ else {
52
+ this.privateKey = this.rawPrivateKey;
53
+ }
54
+ return this.privateKey;
55
+ }
41
56
  const path = resolveKeyPath(this.agentId, this.keyPath);
42
57
  if (path) {
43
58
  // Key file exists — failure to parse is a hard error.
@@ -101,10 +116,11 @@ class MemoryApi {
101
116
  // dedup checks inflate scores above the threshold.
102
117
  const existing = await this.search(content, { limit: 1, minScore: threshold, scoring: "raw" });
103
118
  if (existing.length > 0) {
104
- // Return the existing memory instead of creating a duplicate
119
+ // Return the existing memory instead of creating a duplicate.
120
+ // Flag deduped so callers know this write was suppressed.
105
121
  const match = await this.get(existing[0].id);
106
122
  if (match)
107
- return match;
123
+ return { ...match, deduped: true };
108
124
  }
109
125
  }
110
126
  const id = opts.id ?? `${this.client.agentId}-${crypto.randomUUID()}`;
@@ -149,17 +165,49 @@ class MemoryApi {
149
165
  throw e;
150
166
  }
151
167
  }
152
- /** List recent memories. */
168
+ /**
169
+ * List recent memories. All filters combine with AND.
170
+ *
171
+ * Uses Harper's `POST /Memory/search_by_conditions` endpoint with an
172
+ * explicit conditions array. The Memory.search() override injects the
173
+ * agentId scoping condition.
174
+ *
175
+ * Note: `order` is applied client-side after retrieval. Harper's
176
+ * search_by_conditions does not accept a sort/order field in the body.
177
+ */
153
178
  async list(opts = {}) {
154
- const params = new URLSearchParams();
155
- params.set("agentId", this.client.agentId);
179
+ // Build conditions array — agentId is always scoped
180
+ const conditions = [
181
+ { search_attribute: "agentId", search_type: "equals", search_value: this.client.agentId },
182
+ ];
183
+ if (opts.subject) {
184
+ conditions.push({ search_attribute: "subject", search_type: "equals", search_value: opts.subject });
185
+ }
186
+ for (const tag of opts.tags ?? []) {
187
+ conditions.push({ search_attribute: "tags", search_type: "contains", search_value: tag });
188
+ }
189
+ if (opts.type) {
190
+ conditions.push({ search_attribute: "type", search_type: "equals", search_value: opts.type });
191
+ }
192
+ if (opts.durability) {
193
+ conditions.push({ search_attribute: "durability", search_type: "equals", search_value: opts.durability });
194
+ }
195
+ const body = {
196
+ operator: "and",
197
+ conditions,
198
+ get_attributes: ["*"],
199
+ };
156
200
  if (opts.limit)
157
- params.set("limit", String(opts.limit));
158
- if (opts.type)
159
- params.set("type", opts.type);
160
- if (opts.durability)
161
- params.set("durability", opts.durability);
162
- return this.client.request("GET", `/Memory?${params}`);
201
+ body.limit = opts.limit;
202
+ const result = await this.client.request("POST", "/Memory/search_by_conditions", body);
203
+ // search_by_conditions returns either an array or { results: [...] }
204
+ const memories = Array.isArray(result) ? result : (result?.results ?? []);
205
+ // Client-side sort (Harper's search_by_conditions does not accept sort in body)
206
+ if (opts.order) {
207
+ const dir = opts.order === "createdAt-desc" ? -1 : 1;
208
+ memories.sort((a, b) => dir * (a.createdAt > b.createdAt ? 1 : a.createdAt < b.createdAt ? -1 : 0));
209
+ }
210
+ return memories;
163
211
  }
164
212
  /** Delete a memory. */
165
213
  async delete(id) {
package/dist/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { KeyObject } from "node:crypto";
2
+ export type { KeyObject };
1
3
  /** Memory durability levels. */
2
4
  export type Durability = "permanent" | "persistent" | "standard" | "ephemeral";
3
5
  /** Memory type classification. */
@@ -13,6 +15,9 @@ export interface Memory {
13
15
  subject?: string;
14
16
  createdAt: string;
15
17
  updatedAt?: string;
18
+ /** Set to true when write() returned an existing near-duplicate instead of
19
+ * creating a new entry. Omitted/undefined for new writes. */
20
+ deduped?: boolean;
16
21
  }
17
22
  /** A soul entry (persistent personality/values). */
18
23
  export interface SoulEntry {
@@ -47,6 +52,9 @@ export interface FlairClientConfig {
47
52
  agentId?: string;
48
53
  /** Path to Ed25519 private key file. Auto-resolved if omitted. */
49
54
  keyPath?: string;
55
+ /** In-memory Ed25519 private key (PEM string or pre-loaded KeyObject).
56
+ * Bypasses keyPath/file resolution. Wins over keyPath when both are supplied. */
57
+ privateKey?: string | KeyObject;
50
58
  /** Request timeout in ms. Default: 10000 */
51
59
  timeoutMs?: number;
52
60
  /** Admin username for Basic auth fallback (standalone deployments). Falls back to FLAIR_ADMIN_USER env var. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair-client",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Lightweight client for Flair — identity, memory, and soul for AI agents. Zero heavy dependencies.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "license": "Apache-2.0",
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "https://github.com/tpsdev-ai/flair.git",
32
+ "url": "git+https://github.com/tpsdev-ai/flair.git",
33
33
  "directory": "packages/flair-client"
34
34
  },
35
35
  "homepage": "https://tps.dev/#flair",