@tpsdev-ai/flair-client 0.1.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,118 @@
1
+ # @tpsdev-ai/flair-client
2
+
3
+ Lightweight client for [Flair](https://tps.dev/#flair) — identity, memory, and soul for AI agents.
4
+
5
+ Zero heavy dependencies. Just Ed25519 auth + HTTP. Works with any Flair instance, local or remote.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @tpsdev-ai/flair-client
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { FlairClient } from '@tpsdev-ai/flair-client'
17
+
18
+ const flair = new FlairClient({
19
+ url: 'http://localhost:9926', // or remote: https://flair.example.com
20
+ agentId: 'my-agent',
21
+ // keyPath auto-resolved from ~/.flair/keys/my-agent.key
22
+ })
23
+
24
+ // Write a memory
25
+ await flair.memory.write('Harper v5 sandbox blocks bare imports')
26
+
27
+ // Search by meaning
28
+ const results = await flair.memory.search('native module loading issues')
29
+ // → [{ content: 'Harper v5 sandbox blocks bare imports', score: 0.72, ... }]
30
+
31
+ // Cold-start bootstrap (soul + recent memories)
32
+ const ctx = await flair.bootstrap({ maxTokens: 4000 })
33
+ console.log(ctx.context) // formatted context block
34
+ ```
35
+
36
+ ## Memory API
37
+
38
+ ```ts
39
+ // Write with options
40
+ await flair.memory.write('deploy procedure changed', {
41
+ type: 'decision',
42
+ durability: 'persistent',
43
+ tags: ['ops', 'deploy'],
44
+ })
45
+
46
+ // Get by ID
47
+ const mem = await flair.memory.get('my-agent-1234567890')
48
+
49
+ // List recent
50
+ const recent = await flair.memory.list({ limit: 10 })
51
+
52
+ // Delete
53
+ await flair.memory.delete('my-agent-1234567890')
54
+ ```
55
+
56
+ ## Soul API
57
+
58
+ ```ts
59
+ // Set personality/values
60
+ await flair.soul.set('role', 'Security reviewer, meticulous and skeptical')
61
+ await flair.soul.set('tone', 'Direct, technical, no fluff')
62
+
63
+ // Read
64
+ const role = await flair.soul.get('role')
65
+
66
+ // List all
67
+ const entries = await flair.soul.list()
68
+ ```
69
+
70
+ ## Auth
71
+
72
+ Flair uses Ed25519 signatures. The client auto-discovers your key from:
73
+
74
+ 1. `keyPath` option (explicit)
75
+ 2. `FLAIR_KEY_DIR` env + `{agentId}.key`
76
+ 3. `~/.flair/keys/{agentId}.key`
77
+ 4. `~/.tps/secrets/flair/{agentId}-priv.key`
78
+
79
+ Generate a key with the Flair CLI:
80
+
81
+ ```bash
82
+ npm install -g @tpsdev-ai/flair
83
+ flair init && flair agent add my-agent
84
+ ```
85
+
86
+ ## Use with Claude Code
87
+
88
+ Add to your `CLAUDE.md`:
89
+
90
+ ```markdown
91
+ ## Memory (Flair)
92
+ You have persistent memory. Use it.
93
+ - Bootstrap on start: `npx @tpsdev-ai/flair-client bootstrap --agent $AGENT_ID`
94
+ - Store lessons: `npx @tpsdev-ai/flair-client write --agent $AGENT_ID "what you learned"`
95
+ - Search: `npx @tpsdev-ai/flair-client search --agent $AGENT_ID "your query"`
96
+ ```
97
+
98
+ Or use the Flair CLI directly:
99
+
100
+ ```markdown
101
+ ## Memory (Flair)
102
+ - `flair memory search --agent my-agent -q "your query"`
103
+ - `flair memory add --agent my-agent --content "what to remember"`
104
+ - `flair memory list --agent my-agent --limit 20`
105
+ ```
106
+
107
+ ## Configuration
108
+
109
+ | Option | Env | Default | Description |
110
+ |--------|-----|---------|-------------|
111
+ | `url` | `FLAIR_URL` | `http://localhost:9926` | Flair server URL |
112
+ | `agentId` | `FLAIR_AGENT_ID` | — | Agent identifier |
113
+ | `keyPath` | `FLAIR_KEY_DIR` | auto-resolved | Private key path |
114
+ | `timeoutMs` | — | `10000` | Request timeout |
115
+
116
+ ## License
117
+
118
+ [Apache 2.0](../../LICENSE)
package/dist/auth.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Ed25519 request signing for Flair.
3
+ *
4
+ * Signs requests with: agentId:timestamp:nonce:METHOD:/path
5
+ * Produces: TPS-Ed25519 agentId:timestamp:nonce:base64(signature)
6
+ */
7
+ import { type KeyObject } from "node:crypto";
8
+ /** Resolve an Ed25519 private key from a file (base64 PKCS8 DER or raw 32-byte seed). */
9
+ export declare function loadPrivateKey(path: string): KeyObject;
10
+ /** Find the agent's private key file from standard locations. */
11
+ export declare function resolveKeyPath(agentId: string, keyPath?: string): string | null;
12
+ /** Build an Authorization header for a Flair request. */
13
+ export declare function signRequest(agentId: string, privateKey: KeyObject, method: string, path: string): string;
package/dist/auth.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Ed25519 request signing for Flair.
3
+ *
4
+ * Signs requests with: agentId:timestamp:nonce:METHOD:/path
5
+ * Produces: TPS-Ed25519 agentId:timestamp:nonce:base64(signature)
6
+ */
7
+ import { randomUUID, sign as ed25519Sign, createPrivateKey } from "node:crypto";
8
+ import { readFileSync, existsSync } from "node:fs";
9
+ import { resolve } from "node:path";
10
+ import { homedir } from "node:os";
11
+ const PKCS8_ED25519_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
12
+ /** Resolve an Ed25519 private key from a file (base64 PKCS8 DER or raw 32-byte seed). */
13
+ export function loadPrivateKey(path) {
14
+ const raw = readFileSync(path);
15
+ // Try as base64-encoded PKCS8 DER first
16
+ const decoded = raw.length === 32 ? raw : Buffer.from(raw.toString("utf-8").trim(), "base64");
17
+ const der = decoded.length === 32
18
+ ? Buffer.concat([PKCS8_ED25519_PREFIX, decoded])
19
+ : decoded;
20
+ return createPrivateKey({ key: der, format: "der", type: "pkcs8" });
21
+ }
22
+ /** Find the agent's private key file from standard locations. */
23
+ export function resolveKeyPath(agentId, keyPath) {
24
+ if (keyPath) {
25
+ const resolved = resolve(keyPath.replace(/^~/, homedir()));
26
+ return existsSync(resolved) ? resolved : null;
27
+ }
28
+ const candidates = [
29
+ process.env.FLAIR_KEY_DIR ? resolve(process.env.FLAIR_KEY_DIR, `${agentId}.key`) : null,
30
+ resolve(homedir(), ".flair", "keys", `${agentId}.key`),
31
+ resolve(homedir(), ".tps", "secrets", "flair", `${agentId}-priv.key`),
32
+ ].filter(Boolean);
33
+ return candidates.find(existsSync) ?? null;
34
+ }
35
+ /** Build an Authorization header for a Flair request. */
36
+ export function signRequest(agentId, privateKey, method, path) {
37
+ const ts = Date.now().toString();
38
+ const nonce = randomUUID();
39
+ const payload = `${agentId}:${ts}:${nonce}:${method}:${path}`;
40
+ const sig = ed25519Sign(null, Buffer.from(payload), privateKey);
41
+ return `TPS-Ed25519 ${agentId}:${ts}:${nonce}:${sig.toString("base64")}`;
42
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Flair client — lightweight, zero-dep HTTP client for Flair.
3
+ *
4
+ * Usage:
5
+ * const flair = new FlairClient({ agentId: 'my-agent' })
6
+ * await flair.memory.write('learned something')
7
+ * const results = await flair.memory.search('that thing')
8
+ * const ctx = await flair.bootstrap({ maxTokens: 4000 })
9
+ */
10
+ import type { FlairClientConfig, Memory, MemoryType, Durability, SoulEntry, SearchResult, BootstrapResult } from "./types.js";
11
+ export declare class FlairClient {
12
+ readonly url: string;
13
+ readonly agentId: string;
14
+ readonly memory: MemoryApi;
15
+ readonly soul: SoulApi;
16
+ private privateKey;
17
+ private keyResolved;
18
+ private keyPath;
19
+ private timeoutMs;
20
+ constructor(config: FlairClientConfig);
21
+ private resolveKey;
22
+ /** Make an authenticated request to Flair. */
23
+ request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
24
+ /** Cold-start bootstrap — get soul + recent memories as a formatted context block. */
25
+ bootstrap(opts?: {
26
+ maxTokens?: number;
27
+ }): Promise<BootstrapResult>;
28
+ /** Health check. */
29
+ health(): Promise<{
30
+ status: string;
31
+ }>;
32
+ }
33
+ declare class MemoryApi {
34
+ private client;
35
+ constructor(client: FlairClient);
36
+ /** Write a memory. */
37
+ write(content: string, opts?: {
38
+ id?: string;
39
+ type?: MemoryType;
40
+ durability?: Durability;
41
+ tags?: string[];
42
+ subject?: string;
43
+ }): Promise<Memory>;
44
+ /** Search memories by meaning. */
45
+ search(query: string, opts?: {
46
+ limit?: number;
47
+ }): Promise<SearchResult[]>;
48
+ /** Get a memory by ID. */
49
+ get(id: string): Promise<Memory | null>;
50
+ /** List recent memories. */
51
+ list(opts?: {
52
+ limit?: number;
53
+ type?: MemoryType;
54
+ durability?: Durability;
55
+ }): Promise<Memory[]>;
56
+ /** Delete a memory. */
57
+ delete(id: string): Promise<void>;
58
+ }
59
+ declare class SoulApi {
60
+ private client;
61
+ constructor(client: FlairClient);
62
+ /** Set a soul entry (key-value personality/values). */
63
+ set(key: string, value: string): Promise<SoulEntry>;
64
+ /** Get a soul entry. */
65
+ get(key: string): Promise<SoulEntry | null>;
66
+ /** List all soul entries. */
67
+ list(): Promise<SoulEntry[]>;
68
+ }
69
+ export declare class FlairError extends Error {
70
+ readonly method: string;
71
+ readonly path: string;
72
+ readonly status: number;
73
+ readonly body: string;
74
+ constructor(method: string, path: string, status: number, body: string);
75
+ }
76
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Flair client — lightweight, zero-dep HTTP client for Flair.
3
+ *
4
+ * Usage:
5
+ * const flair = new FlairClient({ agentId: 'my-agent' })
6
+ * await flair.memory.write('learned something')
7
+ * const results = await flair.memory.search('that thing')
8
+ * const ctx = await flair.bootstrap({ maxTokens: 4000 })
9
+ */
10
+ import { loadPrivateKey, resolveKeyPath, signRequest } from "./auth.js";
11
+ const DEFAULT_URL = "http://localhost:9926";
12
+ const DEFAULT_TIMEOUT = 10_000;
13
+ export class FlairClient {
14
+ url;
15
+ agentId;
16
+ memory;
17
+ soul;
18
+ privateKey = null;
19
+ keyResolved = false;
20
+ keyPath;
21
+ timeoutMs;
22
+ constructor(config) {
23
+ this.url = (config.url ?? DEFAULT_URL).replace(/\/$/, "");
24
+ this.agentId = config.agentId;
25
+ this.keyPath = config.keyPath;
26
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT;
27
+ this.memory = new MemoryApi(this);
28
+ this.soul = new SoulApi(this);
29
+ }
30
+ resolveKey() {
31
+ if (this.keyResolved)
32
+ return this.privateKey;
33
+ this.keyResolved = true;
34
+ const path = resolveKeyPath(this.agentId, this.keyPath);
35
+ if (path) {
36
+ // Key file exists — failure to parse is a hard error.
37
+ // Silent fallback to unauthenticated would be a security risk.
38
+ this.privateKey = loadPrivateKey(path);
39
+ }
40
+ return this.privateKey;
41
+ }
42
+ /** Make an authenticated request to Flair. */
43
+ async request(method, path, body) {
44
+ const headers = { "Content-Type": "application/json" };
45
+ const key = this.resolveKey();
46
+ if (key) {
47
+ headers["Authorization"] = signRequest(this.agentId, key, method, path);
48
+ }
49
+ const res = await fetch(`${this.url}${path}`, {
50
+ method,
51
+ headers,
52
+ body: body !== undefined ? JSON.stringify(body) : undefined,
53
+ signal: AbortSignal.timeout(this.timeoutMs),
54
+ });
55
+ if (!res.ok) {
56
+ const text = await res.text().catch(() => "");
57
+ throw new FlairError(method, path, res.status, text.slice(0, 500));
58
+ }
59
+ const text = await res.text();
60
+ return text ? JSON.parse(text) : {};
61
+ }
62
+ /** Cold-start bootstrap — get soul + recent memories as a formatted context block. */
63
+ async bootstrap(opts = {}) {
64
+ return this.request("POST", "/BootstrapMemories", {
65
+ agentId: this.agentId,
66
+ maxTokens: opts.maxTokens ?? 4000,
67
+ });
68
+ }
69
+ /** Health check. */
70
+ async health() {
71
+ return this.request("GET", "/Health");
72
+ }
73
+ }
74
+ // ─── Memory API ─────────────────────────────────────────────────────────────
75
+ class MemoryApi {
76
+ client;
77
+ constructor(client) {
78
+ this.client = client;
79
+ }
80
+ /** Write a memory. */
81
+ async write(content, opts = {}) {
82
+ const id = opts.id ?? `${this.client.agentId}-${Date.now()}`;
83
+ return this.client.request("PUT", `/Memory/${id}`, {
84
+ id,
85
+ agentId: this.client.agentId,
86
+ content,
87
+ type: opts.type ?? "session",
88
+ durability: opts.durability ?? "standard",
89
+ tags: opts.tags ?? [],
90
+ subject: opts.subject,
91
+ createdAt: new Date().toISOString(),
92
+ });
93
+ }
94
+ /** Search memories by meaning. */
95
+ async search(query, opts = {}) {
96
+ const result = await this.client.request("POST", "/SemanticSearch", { agentId: this.client.agentId, q: query, limit: opts.limit ?? 5 });
97
+ return (result.results ?? []).map((r) => ({
98
+ id: r.id ?? r.memory?.id ?? "",
99
+ content: r.content ?? r.memory?.content ?? "",
100
+ score: r.score ?? r.similarity ?? 0,
101
+ type: r.type ?? r.memory?.type,
102
+ durability: r.durability ?? r.memory?.durability,
103
+ tags: r.tags ?? r.memory?.tags,
104
+ createdAt: r.createdAt ?? r.memory?.createdAt,
105
+ }));
106
+ }
107
+ /** Get a memory by ID. */
108
+ async get(id) {
109
+ try {
110
+ return await this.client.request("GET", `/Memory/${id}`);
111
+ }
112
+ catch (e) {
113
+ if (e instanceof FlairError && e.status === 404)
114
+ return null;
115
+ throw e;
116
+ }
117
+ }
118
+ /** List recent memories. */
119
+ async list(opts = {}) {
120
+ const params = new URLSearchParams();
121
+ params.set("agentId", this.client.agentId);
122
+ if (opts.limit)
123
+ params.set("limit", String(opts.limit));
124
+ if (opts.type)
125
+ params.set("type", opts.type);
126
+ if (opts.durability)
127
+ params.set("durability", opts.durability);
128
+ return this.client.request("GET", `/Memory?${params}`);
129
+ }
130
+ /** Delete a memory. */
131
+ async delete(id) {
132
+ await this.client.request("DELETE", `/Memory/${id}`);
133
+ }
134
+ }
135
+ // ─── Soul API ───────────────────────────────────────────────────────────────
136
+ class SoulApi {
137
+ client;
138
+ constructor(client) {
139
+ this.client = client;
140
+ }
141
+ /** Set a soul entry (key-value personality/values). */
142
+ async set(key, value) {
143
+ const id = `${this.client.agentId}:${key}`;
144
+ return this.client.request("PUT", `/Soul/${encodeURIComponent(id)}`, {
145
+ id,
146
+ agentId: this.client.agentId,
147
+ key,
148
+ value,
149
+ createdAt: new Date().toISOString(),
150
+ });
151
+ }
152
+ /** Get a soul entry. */
153
+ async get(key) {
154
+ const id = `${this.client.agentId}:${key}`;
155
+ try {
156
+ return await this.client.request("GET", `/Soul/${encodeURIComponent(id)}`);
157
+ }
158
+ catch (e) {
159
+ if (e instanceof FlairError && e.status === 404)
160
+ return null;
161
+ throw e;
162
+ }
163
+ }
164
+ /** List all soul entries. */
165
+ async list() {
166
+ const params = new URLSearchParams({ agentId: this.client.agentId });
167
+ return this.client.request("GET", `/Soul?${params}`);
168
+ }
169
+ }
170
+ // ─── Error ──────────────────────────────────────────────────────────────────
171
+ export class FlairError extends Error {
172
+ method;
173
+ path;
174
+ status;
175
+ body;
176
+ constructor(method, path, status, body) {
177
+ super(`Flair ${method} ${path} → ${status}: ${body}`);
178
+ this.method = method;
179
+ this.path = path;
180
+ this.status = status;
181
+ this.body = body;
182
+ this.name = "FlairError";
183
+ }
184
+ }
@@ -0,0 +1,3 @@
1
+ export { FlairClient, FlairError } from "./client.js";
2
+ export { loadPrivateKey, resolveKeyPath, signRequest } from "./auth.js";
3
+ export type { FlairClientConfig, Memory, MemoryType, Durability, SoulEntry, SearchResult, BootstrapResult, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { FlairClient, FlairError } from "./client.js";
2
+ export { loadPrivateKey, resolveKeyPath, signRequest } from "./auth.js";
@@ -0,0 +1,52 @@
1
+ /** Memory durability levels. */
2
+ export type Durability = "permanent" | "persistent" | "standard" | "ephemeral";
3
+ /** Memory type classification. */
4
+ export type MemoryType = "session" | "lesson" | "decision" | "preference" | "fact" | "goal";
5
+ /** A memory record. */
6
+ export interface Memory {
7
+ id: string;
8
+ agentId: string;
9
+ content: string;
10
+ type: MemoryType;
11
+ durability: Durability;
12
+ tags: string[];
13
+ subject?: string;
14
+ createdAt: string;
15
+ updatedAt?: string;
16
+ }
17
+ /** A soul entry (persistent personality/values). */
18
+ export interface SoulEntry {
19
+ id: string;
20
+ agentId: string;
21
+ key: string;
22
+ value: string;
23
+ createdAt: string;
24
+ }
25
+ /** Semantic search result. */
26
+ export interface SearchResult {
27
+ id: string;
28
+ content: string;
29
+ score: number;
30
+ type?: MemoryType;
31
+ durability?: Durability;
32
+ tags?: string[];
33
+ createdAt?: string;
34
+ }
35
+ /** Bootstrap response — formatted context block. */
36
+ export interface BootstrapResult {
37
+ context: string;
38
+ memoryCount: number;
39
+ soulCount: number;
40
+ tokenEstimate: number;
41
+ }
42
+ /** Client configuration. */
43
+ export interface FlairClientConfig {
44
+ /** Flair server URL. Default: http://localhost:9926 */
45
+ url?: string;
46
+ /** Agent ID for authentication and data scoping. */
47
+ agentId: string;
48
+ /** Path to Ed25519 private key file. Auto-resolved if omitted. */
49
+ keyPath?: string;
50
+ /** Request timeout in ms. Default: 10000 */
51
+ timeoutMs?: number;
52
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tpsdev-ai/flair-client",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight client for Flair — identity, memory, and soul for AI agents. Zero heavy dependencies.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "LICENSE",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "license": "Apache-2.0",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/tpsdev-ai/flair.git",
33
+ "directory": "packages/flair-client"
34
+ },
35
+ "homepage": "https://tps.dev/#flair",
36
+ "keywords": [
37
+ "flair",
38
+ "ai",
39
+ "agent",
40
+ "memory",
41
+ "identity",
42
+ "client",
43
+ "ed25519"
44
+ ],
45
+ "devDependencies": {
46
+ "typescript": "5.9.3"
47
+ }
48
+ }