@tpsdev-ai/flair 0.26.0 → 0.27.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.
@@ -0,0 +1,291 @@
1
+ # Memory Bridges
2
+
3
+ > Pluggable import/export between Flair and foreign memory systems.
4
+
5
+ Flair stores memories in its own schema. The rest of the agent ecosystem stores memories too — agentic-stack's `.agent/` directory, Mem0, Letta, Zep, Anthropic memory, and more every week. **Bridges** let Flair speak those formats without a core-code change per integration.
6
+
7
+ The design goal is *agent-authorable first*: a capable agent should be able to ship a working bridge by reading this doc and running one command.
8
+
9
+ ## Two shapes
10
+
11
+ | Shape | When to use | Artifact |
12
+ |-------|-------------|----------|
13
+ | **File** (YAML descriptor) | Foreign system stores memories as files on disk (`.agent/`, JSONL, Markdown) | `.flair-bridge/<name>.yaml` in a project, or `~/.flair/bridges/<name>.yaml` user-wide |
14
+ | **API** (TypeScript plugin) | Foreign system exposes an HTTP API (Mem0, Letta, Zep, Anthropic memory) | npm package named `flair-bridge-<name>` |
15
+
16
+ Pick one. The runtime normalizes both into the same memory-record shape before import.
17
+
18
+ ## The memory record
19
+
20
+ Every bridge deals in `BridgeMemory` objects. Fields:
21
+
22
+ ```ts
23
+ interface BridgeMemory {
24
+ // Identity
25
+ id?: string // Flair ID if round-tripping; omit on first import
26
+ foreignId?: string // original ID in the foreign system (preserved)
27
+
28
+ // Content
29
+ content: string // REQUIRED
30
+ subject?: string
31
+ tags?: string[]
32
+ visibility?: "private" | "shared" | "public"
33
+
34
+ // Durability & lifecycle
35
+ durability?: "ephemeral" | "standard" | "persistent" | "permanent"
36
+ createdAt?: string // ISO-8601; defaults to now on import
37
+ validFrom?: string
38
+ validTo?: string
39
+ expiresAt?: string
40
+
41
+ // Ownership
42
+ agentId?: string // required on import unless --agent is passed
43
+
44
+ // Provenance
45
+ source?: string
46
+ derivedFrom?: string[]
47
+ }
48
+ ```
49
+
50
+ **Required on import:** `content` and (`agentId` or the `--agent` flag).
51
+
52
+ **Flair-owned, never set by a bridge:** `contentHash`, `embedding`, `embeddingModel`, `retrievalCount`, `lastRetrieved`, `promotionStatus`, `_safetyFlags`, any `*By` audit field. These are computed on ingest; if a bridge emits them they're ignored.
53
+
54
+ ## Commands
55
+
56
+ ```bash
57
+ flair bridge list # installed bridges
58
+ flair bridge scaffold <name> [--file|--api] # emit starter files
59
+ flair bridge import <name> [src] [opts] # foreign → Flair (Shape A YAML / built-ins)
60
+ flair bridge test <name> [--fixture <path>] # round-trip diff (coming in slice 3)
61
+ flair bridge export <name> <dst> [opts] # Flair → foreign (coming in slice 3)
62
+ ```
63
+
64
+ `list`, `scaffold`, and `import` are live as of 0.6.0+. `test` and `export` are stubbed with pointers to slice 3.
65
+
66
+ Common runtime options for `import`:
67
+
68
+ | Flag | Meaning |
69
+ |------|---------|
70
+ | `--agent <id>` | Default agent ID for memories that don't carry one (or set `FLAIR_AGENT_ID`) |
71
+ | `--cwd <dir>` | Filesystem root the descriptor's relative paths resolve against (default: cwd) |
72
+ | `--dry-run` | Parse + validate + count, don't write to Flair |
73
+ | `--port <port>` | Harper HTTP port |
74
+ | `--url <url>` | Flair base URL (overrides `--port`) |
75
+ | `--key <path>` | Ed25519 private key path (default: resolved from agent) |
76
+
77
+ ## Your first import (worked example: agentic-stack)
78
+
79
+ `agentic-stack` ships as a built-in. Drop into a directory that has agentic-stack lessons and run:
80
+
81
+ ```bash
82
+ $ flair bridge list
83
+ name kind source description
84
+ agentic-stack file builtin Import agentic-stack lessons.jsonl into Flair persistent memories
85
+
86
+ $ ls .agent/memory/semantic/
87
+ lessons.jsonl
88
+
89
+ $ flair bridge import agentic-stack --agent mybot --dry-run
90
+ agentic-stack: would import 47 memories. Re-run without --dry-run to write to Flair.
91
+
92
+ $ flair bridge import agentic-stack --agent mybot
93
+ 47 imported (lesson-47)
94
+ agentic-stack: imported 47/47 memories.
95
+ ```
96
+
97
+ Each lesson lands as a Flair memory tagged `source: "agentic-stack/lessons"` with `durability: persistent`, the foreign `id` preserved as `foreignId` (so re-importing is idempotent on the same source).
98
+
99
+ A few things worth knowing:
100
+
101
+ - **`--agent` is required** unless your descriptor maps an `agentId` column. If you forget, `flair bridge import` errors with a one-line operator-pointer hint plus a structured `BridgeRuntimeError` JSON on stderr.
102
+ - **`--dry-run` is your friend.** Validates the descriptor, parses every record, applies the mapping, but skips the PUT. Use it to confirm the count and check a few records before committing.
103
+ - **Output is throttled** to one progress line every 2 seconds (or every 25 records, whichever comes first), so big imports don't flood your terminal.
104
+ - **Errors are structured.** Every error includes `bridge`, `op`, `path`, `record`, `field`, `expected`, `got`, `hint` (per [§10 of the spec](../specs/FLAIR-BRIDGES.md#-10-error-format)). The `hint` is the part you act on; the rest is for an LLM to self-correct without operator help.
105
+
106
+ ## Shape A — Declarative YAML
107
+
108
+ For bridges whose source or target is a directory of files, write a YAML descriptor. No code required.
109
+
110
+ ```yaml
111
+ # .flair-bridge/agentic-stack.yaml
112
+ name: agentic-stack
113
+ version: 1
114
+ kind: file
115
+ description: "agentic-stack .agent/memory/semantic/lessons.jsonl"
116
+
117
+ detect:
118
+ anyExists:
119
+ - ".agent/AGENTS.md"
120
+ - ".agent/memory/semantic/lessons.jsonl"
121
+
122
+ import:
123
+ sources:
124
+ - path: ".agent/memory/semantic/lessons.jsonl"
125
+ format: jsonl
126
+ map:
127
+ content: "$.claim"
128
+ subject: "$.topic"
129
+ tags: "$.tags"
130
+ foreignId: "$.id"
131
+ durability: "persistent"
132
+ source: "agentic-stack/lessons"
133
+
134
+ export:
135
+ targets:
136
+ - path: ".agent/memory/semantic/lessons.jsonl"
137
+ format: jsonl
138
+ when: "durability in ['persistent', 'permanent']"
139
+ map:
140
+ id: "foreignId ?? id"
141
+ claim: "content"
142
+ topic: "subject"
143
+ tags: "tags"
144
+ ```
145
+
146
+ - **Path expressions:** JSONPath subset — `$.field`, `$.nested.field`, `$.array[*]`.
147
+ - **`when:`** boolean expression over `BridgeMemory` fields. Omit for "always".
148
+ - **Formats supported in 1.0:** `jsonl`, `json`, `yaml`, `markdown-frontmatter`.
149
+ - **What the runtime gives you:** file discovery, format parsing, schema validation, error reporting with `line:column`. You only describe the mapping.
150
+
151
+ ## Shape B — Code plugin
152
+
153
+ For bridges that talk to HTTP APIs:
154
+
155
+ ```ts
156
+ // flair-bridge-mem0/index.ts
157
+ import type { MemoryBridge, BridgeMemory, BridgeContext } from "@tpsdev-ai/flair";
158
+
159
+ export const bridge: MemoryBridge = {
160
+ name: "mem0",
161
+ version: 1,
162
+ kind: "api",
163
+
164
+ options: {
165
+ apiKey: { env: "MEM0_API_KEY", required: true, description: "Mem0 API token" },
166
+ userId: { required: true, description: "Mem0 user ID" },
167
+ baseUrl: { default: "https://api.mem0.ai" },
168
+ },
169
+
170
+ async *import(opts, ctx: BridgeContext) {
171
+ const res = await ctx.fetch(`${opts.baseUrl}/v1/memories/?user_id=${opts.userId}`, {
172
+ headers: { Authorization: `Token ${opts.apiKey}` },
173
+ });
174
+ const payload = await res.json();
175
+ for (const m of payload.results ?? []) {
176
+ yield {
177
+ foreignId: String(m.id),
178
+ content: String(m.memory),
179
+ createdAt: m.created_at,
180
+ tags: m.categories,
181
+ source: "mem0",
182
+ };
183
+ }
184
+ },
185
+
186
+ async export(memories, opts, ctx) {
187
+ for await (const m of memories) {
188
+ await ctx.fetch(`${opts.baseUrl}/v1/memories/`, {
189
+ method: "POST",
190
+ headers: { Authorization: `Token ${opts.apiKey}` },
191
+ body: JSON.stringify({
192
+ messages: [{ role: "user", content: m.content }],
193
+ user_id: opts.userId,
194
+ }),
195
+ });
196
+ }
197
+ },
198
+ };
199
+ ```
200
+
201
+ **`BridgeContext`** gives you:
202
+
203
+ - `ctx.fetch` — instrumented, rate-limited HTTP. **Always use this instead of the global `fetch`** — it's how the runtime applies per-bridge throttling and audit logging.
204
+ - `ctx.log` — structured `{debug, info, warn, error}` logger.
205
+ - `ctx.cache` — per-bridge key/value cache with optional TTL. Useful for pagination cursors, auth refresh, etc.
206
+
207
+ Code plugins have no direct filesystem or network access outside `ctx`. This keeps the surface area small and auditable.
208
+
209
+ ## Discovery
210
+
211
+ `flair bridge list` scans, in precedence order:
212
+
213
+ 1. **Built-ins** shipped inside `@tpsdev-ai/flair`
214
+ 2. **Project YAML** — `.flair-bridge/*.yaml` in the current working directory
215
+ 3. **User YAML** — `~/.flair/bridges/*.yaml`
216
+ 4. **npm packages** — anything matching `flair-bridge-*` or `@scope/flair-bridge-*` under `node_modules/`
217
+
218
+ Earlier sources win on name conflict. So a built-in adapter can't be accidentally shadowed by a same-named npm package, but a project-local YAML *can* intentionally override an npm bridge for local development.
219
+
220
+ ## Distribution
221
+
222
+ Publish bridges to npm as `flair-bridge-<name>`. Example `package.json`:
223
+
224
+ ```json
225
+ {
226
+ "name": "flair-bridge-mem0",
227
+ "version": "0.1.0",
228
+ "description": "Flair bridge for Mem0",
229
+ "main": "index.js",
230
+ "flair": { "kind": "api", "version": 1 },
231
+ "peerDependencies": { "@tpsdev-ai/flair": ">=0.6.0" }
232
+ }
233
+ ```
234
+
235
+ There's no registry to curate; npm is the registry. `flair bridge list` surfaces everything installed.
236
+
237
+ ## Round-trip testing
238
+
239
+ Every bridge ships a fixture. `flair bridge test <name>` will run (slice 2):
240
+
241
+ 1. Import the fixture → `BridgeMemory[]`
242
+ 2. Export those memories to a tmp target
243
+ 3. Re-import the tmp target → `BridgeMemory[]`
244
+ 4. Structural diff on the round-trip-stable fields: `content`, `subject`, `tags`, `durability`
245
+ 5. Pass iff the diff is empty
246
+
247
+ Pass = ship. Iterate against this signal.
248
+
249
+ ## Trust and security
250
+
251
+ Different sources have different blast radii.
252
+
253
+ | Source | Default trust |
254
+ |--------|---------------|
255
+ | Built-in | Allowed |
256
+ | Declarative YAML (no code) | Allowed; remote API calls require `--allow-remote` at invocation |
257
+ | Code plugin from npm | Requires `flair bridge allow <name>` on first use |
258
+ | Remote API calls | Always require `--allow-remote` on the invocation |
259
+
260
+ A code plugin is untrusted JavaScript. The explicit `allow` gate keeps an unreviewed npm package from executing on your machine the first time `flair bridge import` names it. VM isolation is a 1.1 hardening pass.
261
+
262
+ ## Error format
263
+
264
+ Every bridge error is structured:
265
+
266
+ ```json
267
+ {
268
+ "bridge": "agentic-stack",
269
+ "op": "import",
270
+ "path": ".agent/memory/semantic/lessons.jsonl",
271
+ "record": 42,
272
+ "field": "$.claim",
273
+ "expected": "string",
274
+ "got": "null",
275
+ "hint": "lessons.jsonl row 42 has no 'claim' field — skipped"
276
+ }
277
+ ```
278
+
279
+ Printed as a single JSON object per line when `--json`; human-formatted otherwise. Field paths, expected/got, and a hint — that's the minimum an LLM needs to self-correct without operator help.
280
+
281
+ ## Writing your own — the prompt
282
+
283
+ If you want an agent to write a bridge for you, here's the one-shot prompt:
284
+
285
+ > You are implementing a Flair bridge for **\<FOREIGN_SYSTEM\>**. Read the sections above on the memory record, on Shape A (file-format targets) or Shape B (API targets) depending on \<FOREIGN_SYSTEM\>, and on round-trip testing. Produce either `.flair-bridge/<name>.yaml` (file targets) or an `index.ts` implementing `MemoryBridge` (API targets). Include a fixture at `fixtures/<name>.fixture.json` or `fixtures/<name>.mock.json`. Run `flair bridge test <name>` and iterate until the round-trip diff is empty.
286
+
287
+ That's the bar. If an agent can't ship a working bridge from this doc plus the scaffold, the doc is the bug.
288
+
289
+ ## Full spec
290
+
291
+ The authoritative contract is in [`specs/FLAIR-BRIDGES.md`](../specs/FLAIR-BRIDGES.md). This doc is the user-facing view; the spec covers edge cases, future extensions, and design rationale.
@@ -0,0 +1,202 @@
1
+ # Flair + Claude Code
2
+
3
+ Give Claude Code persistent memory across sessions. Works with subagents too.
4
+
5
+ ## Setup (5 minutes)
6
+
7
+ ### 1. Install Flair
8
+
9
+ ```bash
10
+ npm install -g @tpsdev-ai/flair
11
+ ```
12
+
13
+ ### 2. Initialize
14
+
15
+ ```bash
16
+ flair init
17
+ ```
18
+
19
+ This starts a local Flair server (Harper) and creates `~/.flair/`.
20
+
21
+ ### 3. Create an agent identity
22
+
23
+ ```bash
24
+ # One agent per project, or one shared agent — your call
25
+ flair agent add my-project
26
+ ```
27
+
28
+ This generates an Ed25519 key pair at `~/.flair/keys/my-project.key` and registers the agent with Flair.
29
+
30
+ ### 4. Add to your project's CLAUDE.md
31
+
32
+ Copy this into your project's `CLAUDE.md` (or `.claude/settings.md`, `AGENTS.md`, etc.):
33
+
34
+ ---
35
+
36
+ > **Start of CLAUDE.md snippet** — copy everything between the lines.
37
+
38
+ ## Memory
39
+
40
+ You have persistent memory via Flair. Use it to remember context across sessions.
41
+
42
+ ### On session start
43
+
44
+ Run this FIRST, before doing anything else:
45
+
46
+ flair bootstrap --agent my-project --max-tokens 4000
47
+
48
+ Read the output — that's your soul and recent memories.
49
+
50
+ ### During work
51
+
52
+ - Remember something: `flair memory add --agent my-project --content "what you learned"`
53
+ - Search memory: `flair search "your query" --agent my-project`
54
+ - Store a lesson: `flair memory add --agent my-project --content "lesson text" --type lesson --durability persistent`
55
+ - Store a decision: `flair memory add --agent my-project --content "decision text" --type decision --durability persistent`
56
+
57
+ ### What to remember
58
+
59
+ - Lessons learned (bugs, workarounds, patterns)
60
+ - Decisions made (why we chose X over Y)
61
+ - Project-specific context (architecture, conventions, constraints)
62
+ - User preferences (coding style, review standards)
63
+
64
+ ### What NOT to remember
65
+
66
+ - Transient task details (what file am I editing right now)
67
+ - Things already in the codebase (read the code instead)
68
+ - Secrets or credentials (never store these in memory)
69
+
70
+ ### Durability levels
71
+
72
+ - persistent — survives indefinitely. Use for lessons, decisions, preferences.
73
+ - standard — default. Good for session context, observations.
74
+ - ephemeral — auto-expires after 24h. Use for temporary notes.
75
+
76
+ > **End of CLAUDE.md snippet.**
77
+
78
+ ---
79
+
80
+ That's it. Claude Code will now bootstrap context on start and store important things as it works.
81
+
82
+ ## Multiple Projects
83
+
84
+ Create a separate agent per project:
85
+
86
+ ```bash
87
+ flair agent add project-alpha
88
+ flair agent add project-beta
89
+ flair agent add infra-ops
90
+ ```
91
+
92
+ Each project's `CLAUDE.md` uses its own agent ID. Memories are fully isolated between projects.
93
+
94
+ ## Subagents
95
+
96
+ Claude Code subagents (spawned via `/run` or background tasks) can share the parent's memory:
97
+
98
+ ### Subagents
99
+ Subagents share memory with the parent session. Use the same agent ID:
100
+ FLAIR_AGENT_ID=my-project
101
+
102
+ When spawning subagents, pass the agent ID so they can access shared context.
103
+
104
+ Or give subagents their own identity for isolation:
105
+
106
+ ```bash
107
+ flair agent add my-project-review # code review subagent
108
+ flair agent add my-project-test # test runner subagent
109
+ ```
110
+
111
+ ## Environment Variables
112
+
113
+ Instead of passing `--agent` every time, set environment variables:
114
+
115
+ ```bash
116
+ # In your shell profile or .envrc
117
+ export FLAIR_AGENT_ID=my-project
118
+ export FLAIR_URL=http://localhost:19926 # default, only needed if custom
119
+ ```
120
+
121
+ Then the CLAUDE.md simplifies to:
122
+
123
+ ## Memory
124
+ - Bootstrap: `flair bootstrap`
125
+ - Remember: `flair memory add --content "what you learned"`
126
+ - Search: `flair search "your query"`
127
+
128
+ ## Soul (Personality / Context)
129
+
130
+ Want Claude Code to have consistent personality or project context? Set soul entries:
131
+
132
+ ```bash
133
+ # Project context
134
+ flair soul set --agent my-project --key project \
135
+ --value "E-commerce platform. Rust backend, React frontend. Ship quality over speed."
136
+
137
+ # Coding standards
138
+ flair soul set --agent my-project --key standards \
139
+ --value "Always write tests. Prefer composition over inheritance. No any types in TypeScript."
140
+
141
+ # Review guidelines
142
+ flair soul set --agent my-project --key review \
143
+ --value "Check for: error handling, edge cases, performance implications, security."
144
+ ```
145
+
146
+ Soul entries are included in every `flair bootstrap` — they're the persistent context that shapes how Claude Code thinks about your project.
147
+
148
+ ## Remote Flair
149
+
150
+ If you want to share memory across machines (e.g., work laptop + home setup):
151
+
152
+ ```bash
153
+ # On your server
154
+ npm install -g @tpsdev-ai/flair
155
+ flair init
156
+ flair agent add my-project
157
+
158
+ # On client machines
159
+ npm install -g @tpsdev-ai/flair # for the CLI
160
+ export FLAIR_URL=http://your-server:19926
161
+ export FLAIR_AGENT_ID=my-project
162
+ # Copy the key from the server:
163
+ scp server:~/.flair/keys/my-project.key ~/.flair/keys/
164
+ ```
165
+
166
+ Or use an SSH tunnel:
167
+
168
+ ```bash
169
+ ssh -f -N -L 19926:localhost:19926 your-server
170
+ # Now FLAIR_URL=http://localhost:19926 works
171
+ ```
172
+
173
+ ## Programmatic Access
174
+
175
+ For custom tooling, use the lightweight client library:
176
+
177
+ ```bash
178
+ npm install @tpsdev-ai/flair-client
179
+ ```
180
+
181
+ ```typescript
182
+ import { FlairClient } from '@tpsdev-ai/flair-client'
183
+
184
+ const flair = new FlairClient({ agentId: 'my-project' })
185
+
186
+ await flair.memory.write('learned that X causes Y', {
187
+ type: 'lesson',
188
+ durability: 'persistent',
189
+ })
190
+
191
+ const results = await flair.memory.search('what causes Y')
192
+ const context = await flair.bootstrap({ maxTokens: 4000 })
193
+ ```
194
+
195
+ ## Tips
196
+
197
+ - **Bootstrap is cheap.** Run it at the start of every session. It's one HTTP call.
198
+ - **Write lessons immediately.** Don't wait for the session to end — you might not get the chance.
199
+ - **Use durability wisely.** Most things are `standard`. Only promote to `persistent` for things that should survive months.
200
+ - **Search is semantic.** "deployment issues" finds memories about "CI pipeline failures" — you don't need exact keywords.
201
+ - **Temporal queries work.** "What happened today" and "what did we ship recently" are understood.
202
+ - **Dedup is automatic.** Writing the same fact twice won't create duplicates (0.7 similarity threshold).
@@ -0,0 +1,204 @@
1
+ # Deployment Guide
2
+
3
+ Run Flair on macOS, Linux, or Docker.
4
+
5
+ ## macOS (Apple Silicon)
6
+
7
+ ### Install
8
+
9
+ ```bash
10
+ npm install -g @tpsdev-ai/flair
11
+ flair init
12
+ ```
13
+
14
+ `flair init` will:
15
+ - Download Harper and the nomic-embed-text embedding model
16
+ - Create `~/.flair/` (config, data, keys)
17
+ - Generate admin credentials
18
+ - Install a launchd plist for auto-start on boot
19
+ - Start the server
20
+
21
+ ### Verify
22
+
23
+ ```bash
24
+ flair status
25
+ flair doctor
26
+ ```
27
+
28
+ ### Auto-start
29
+
30
+ `flair init` installs a launchd plist at `~/Library/LaunchAgents/ai.tpsdev.flair.plist`. Flair starts automatically on login and restarts if it crashes.
31
+
32
+ ```bash
33
+ # Manual control
34
+ flair stop
35
+ flair start
36
+ flair restart
37
+ ```
38
+
39
+ ### Port
40
+
41
+ Default port is `19926`. Override during init:
42
+
43
+ ```bash
44
+ flair init --port 8000
45
+ ```
46
+
47
+ Or edit `~/.flair/config.yaml` and restart.
48
+
49
+ ---
50
+
51
+ ## Linux
52
+
53
+ ### Prerequisites
54
+
55
+ - Node.js >= 22
56
+ - systemd (for auto-start)
57
+
58
+ ### Install
59
+
60
+ ```bash
61
+ npm install -g @tpsdev-ai/flair
62
+ flair init
63
+ ```
64
+
65
+ Same as macOS — detects the platform and generates a systemd unit file instead of a launchd plist.
66
+
67
+ ### Verify
68
+
69
+ ```bash
70
+ flair status
71
+ flair doctor
72
+ ```
73
+
74
+ ### Auto-start
75
+
76
+ The systemd unit file is installed at `~/.config/systemd/user/flair.service`.
77
+
78
+ ```bash
79
+ # Manual control
80
+ systemctl --user start flair
81
+ systemctl --user stop flair
82
+ systemctl --user restart flair
83
+
84
+ # View logs
85
+ journalctl --user -u flair -f
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Docker
91
+
92
+ ### Quick test (from-scratch validation)
93
+
94
+ ```bash
95
+ cd docker/
96
+ ./test-from-scratch.sh
97
+ ```
98
+
99
+ This runs a clean install in a container — useful for verifying the install path works on a fresh machine.
100
+
101
+ ### Production Docker
102
+
103
+ ```dockerfile
104
+ FROM node:22-slim
105
+ RUN npm install -g @tpsdev-ai/flair
106
+ RUN flair init --skip-soul
107
+ EXPOSE 19926
108
+ CMD ["flair", "start", "--foreground"]
109
+ ```
110
+
111
+ Note: embeddings run on CPU in Docker (no Metal acceleration). Performance is acceptable for small-to-medium memory stores (< 10K memories).
112
+
113
+ ---
114
+
115
+ ## Harper Fabric
116
+
117
+ Deploying to a Harper Fabric cluster is a different mechanism from the installs above — `flair deploy` pushes Flair as a cluster component instead of `npm install -g`. To upgrade an already-deployed Fabric instance in place, use `FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> flair upgrade --target <fabric-url>` (or `--fabric-password-file <path>` in place of the env var), not the local upgrade path. Inline `--fabric-user`/`--fabric-password` flags also work but are discouraged — both leak to shell history and `ps`. See [`docs/upgrade.md` — Upgrading a Fabric-deployed instance](upgrade.md#upgrading-a-fabric-deployed-instance) for the full walkthrough, including the automatic post-deploy fleet-convergence sweep.
118
+
119
+ ---
120
+
121
+ ## Remote Access
122
+
123
+ ### SSH tunnel (simplest)
124
+
125
+ ```bash
126
+ ssh -f -N -L 19926:localhost:19926 your-server
127
+ ```
128
+
129
+ Then set `FLAIR_URL=http://localhost:19926` on the client.
130
+
131
+ ### Direct network access
132
+
133
+ Edit `~/.flair/config.yaml`:
134
+
135
+ ```yaml
136
+ http:
137
+ port: 19926
138
+ host: 0.0.0.0 # listen on all interfaces
139
+ ```
140
+
141
+ **Security:** Flair uses Ed25519 authentication. Agents must present a valid signature to read or write. However, the `/Health` endpoint is unauthenticated. For internet-facing deployments, put Flair behind a reverse proxy with TLS.
142
+
143
+ ---
144
+
145
+ ## Configuration
146
+
147
+ All configuration lives in `~/.flair/`:
148
+
149
+ ```
150
+ ~/.flair/
151
+ ├── config.yaml # port, host, embedding model
152
+ ├── data/ # Harper database
153
+ ├── keys/ # Ed25519 keypairs per agent
154
+ └── backups/ # flair backup output
155
+ ```
156
+
157
+ ### Key config options (`~/.flair/config.yaml`)
158
+
159
+ ```yaml
160
+ http:
161
+ port: 19926 # API port (ops port = this - 1)
162
+ host: 127.0.0.1 # bind address
163
+
164
+ clustering:
165
+ nodeName: flair
166
+
167
+ logging:
168
+ level: warn
169
+ stdStreams: true
170
+ ```
171
+
172
+ ### Environment variables
173
+
174
+ Set these in the Flair process environment (`~/Library/LaunchAgents/ai.tpsdev.flair.plist` on macOS, the systemd unit on Linux, the component env on Fabric).
175
+
176
+ | Variable | What it does | When to set it |
177
+ |----------|--------------|----------------|
178
+ | `FLAIR_PUBLIC_URL` | The URL operators reach this Flair on (e.g. `https://flair.example.com`). Surfaced in the AdminInstance pane's Endpoints table and used by OAuth metadata + A2A discovery so external clients see a reachable URL. | **Always set on remote / Fabric / VPS deployments.** Local-only installs can leave it unset. |
179
+ | `HDB_ADMIN_PASSWORD` | Bootstrap password for the embedded Harper. After first start, the persisted user record is the source of truth; rotate via the Harper ops API, not by changing this env var. | Set at install time. See [secrets-and-keys.md](secrets-and-keys.md) for rotation. |
180
+ | `FLAIR_KEY_PASSPHRASE` | Passphrase used to derive the AES-256-GCM key that wraps federation private-key seeds at rest. Auto-generated to `~/.flair/keys/.passphrase` if unset. | Set explicitly for production federation deployments so the passphrase isn't auto-generated and lost on disk wipe. |
181
+ | `HTTP_PORT` | Override the Harper HTTP port. Useful for sandboxes; production deployments should configure the port in `config.yaml` instead. | Rare. |
182
+
183
+ ---
184
+
185
+ ## Backup & Restore
186
+
187
+ ```bash
188
+ # Backup all data (agents, memories, souls)
189
+ flair backup > ~/flair-backup-$(date +%Y%m%d).json
190
+
191
+ # Restore to a fresh instance
192
+ flair restore < ~/flair-backup-20260405.json
193
+ ```
194
+
195
+ Always backup before upgrades.
196
+
197
+ ---
198
+
199
+ ## Uninstall
200
+
201
+ ```bash
202
+ flair uninstall # stops server, removes ~/.flair/, removes launchd/systemd service
203
+ npm uninstall -g @tpsdev-ai/flair
204
+ ```