@tpsdev-ai/flair 0.28.0 → 0.30.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 +1 -1
- package/dist/bridges/builtins/index.js +1 -1
- package/dist/bridges/discover.js +1 -1
- package/dist/bridges/runtime/load-plugin.js +1 -1
- package/dist/bridges/scaffold.js +3 -3
- package/dist/bridges/types.js +1 -1
- package/dist/cli-shim.cjs +1 -1
- package/dist/cli.js +357 -130
- package/dist/deploy.js +29 -19
- package/dist/fabric-upgrade.js +61 -23
- package/dist/fleet-verify.js +3 -3
- package/dist/lib/mcp-enable.js +2 -2
- package/dist/rem/runner.js +2 -2
- package/dist/rem/scheduler.js +226 -17
- package/dist/resources/A2AAdapter.js +1 -1
- package/dist/resources/Admin.js +1 -1
- package/dist/resources/AdminConnectors.js +1 -1
- package/dist/resources/AdminDashboard.js +1 -1
- package/dist/resources/AdminIdp.js +1 -1
- package/dist/resources/AdminInstance.js +1 -1
- package/dist/resources/AdminMemory.js +1 -1
- package/dist/resources/AdminPrincipals.js +1 -1
- package/dist/resources/Agent.js +1 -1
- package/dist/resources/AgentCard.js +1 -1
- package/dist/resources/AgentSeed.js +1 -1
- package/dist/resources/AttentionQuery.js +1 -1
- package/dist/resources/Credential.js +1 -1
- package/dist/resources/Federation.js +1 -1
- package/dist/resources/Instance.js +1 -1
- package/dist/resources/Integration.js +1 -1
- package/dist/resources/MCPClientMetadata.js +1 -1
- package/dist/resources/Memory.js +1 -1
- package/dist/resources/MemoryBootstrap.js +1 -1
- package/dist/resources/MemoryCandidate.js +168 -0
- package/dist/resources/MemoryConsolidate.js +1 -1
- package/dist/resources/MemoryDedupStats.js +1 -1
- package/dist/resources/MemoryFeed.js +1 -1
- package/dist/resources/MemoryGrant.js +1 -1
- package/dist/resources/MemoryMaintenance.js +1 -1
- package/dist/resources/MemoryReflect.js +3 -3
- package/dist/resources/MemoryReindex.js +1 -1
- package/dist/resources/MemoryUsage.js +1 -1
- package/dist/resources/OAuth.js +1 -1
- package/dist/resources/OAuthClient.js +1 -1
- package/dist/resources/OrgEvent.js +1 -1
- package/dist/resources/OrgEventCatchup.js +1 -1
- package/dist/resources/OrgEventMaintenance.js +1 -1
- package/dist/resources/PairingToken.js +1 -1
- package/dist/resources/Peer.js +1 -1
- package/dist/resources/Presence.js +3 -3
- package/dist/resources/RecordUsage.js +16 -2
- package/dist/resources/Relationship.js +1 -1
- package/dist/resources/SemanticSearch.js +1 -1
- package/dist/resources/SkillScan.js +1 -1
- package/dist/resources/Soul.js +1 -1
- package/dist/resources/SoulFeed.js +1 -1
- package/dist/resources/WorkspaceLatest.js +1 -1
- package/dist/resources/WorkspaceState.js +1 -1
- package/dist/resources/XAA.js +1 -1
- package/dist/resources/a2a-url.js +1 -1
- package/dist/resources/agent-auth.js +1 -1
- package/dist/resources/agentcard-fields.js +1 -1
- package/dist/resources/auth-middleware.js +1 -1
- package/dist/resources/embeddings-boot.js +4 -4
- package/dist/resources/embeddings-provider.js +13 -43
- package/dist/resources/federation-cleanup.js +5 -5
- package/dist/resources/federation-nonce-store.js +5 -5
- package/dist/resources/health.js +29 -6
- package/dist/resources/instance-identity.js +1 -1
- package/dist/resources/mcp-client-metadata-fields.js +1 -1
- package/dist/resources/mcp-handler.js +1 -1
- package/dist/resources/mcp-oauth.js +2 -2
- package/dist/resources/mcp-tools.js +9 -1
- package/dist/resources/memory-read-scope.js +1 -1
- package/dist/resources/memory-reflect-lib.js +4 -4
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +1 -1
- package/dist/resources/migrations/embedding-stamp.js +3 -3
- package/dist/resources/migrations/graph-heal.js +2 -2
- package/dist/resources/migrations/ledger.js +1 -1
- package/dist/resources/migrations/registry.js +14 -5
- package/dist/resources/migrations/runner.js +64 -11
- package/dist/resources/migrations/synthetic-test-migration.js +1 -1
- package/dist/resources/migrations/visibility-backfill.js +232 -0
- package/dist/resources/models-dir.js +55 -0
- package/dist/resources/record-types.js +30 -2
- package/dist/resources/rerank-provider.js +24 -4
- package/dist/resources/semantic-retrieval-core.js +1 -1
- package/dist/resources/usage-recording.js +73 -7
- package/dist/resources/version.js +33 -0
- package/dist/src/rem/scheduler.js +449 -0
- package/docs/assets/flair-cross-orchestrator.cast +1 -1
- package/docs/bridges.md +3 -3
- package/docs/deployment.md +1 -0
- package/docs/mcp-clients.md +3 -1
- package/docs/n8n.md +1 -2
- package/docs/notes/rem-ux.md +1 -1
- package/docs/rem.md +2 -0
- package/docs/supply-chain-policy.md +4 -4
- package/docs/upgrade.md +2 -2
- package/package.json +4 -4
- package/schemas/memory.graphql +27 -12
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REM nightly scheduler — platform-native scheduler install/uninstall.
|
|
3
|
+
*
|
|
4
|
+
* Per FLAIR-NIGHTLY-REM § 3 (`flair rem nightly enable|disable`). Renders
|
|
5
|
+
* launchd plist (macOS) or systemd timer+service (Linux) from templates,
|
|
6
|
+
* deploys a shim script to `~/.flair/bin/flair-rem-nightly`, and loads the
|
|
7
|
+
* job into the user-session scheduler.
|
|
8
|
+
*
|
|
9
|
+
* Templates use `{{KEY}}` placeholders — single-pass substitution. The full
|
|
10
|
+
* placeholder set is enumerated in `interface SchedulerSubstitutions` so
|
|
11
|
+
* adding a new key requires touching both this module and the template.
|
|
12
|
+
*
|
|
13
|
+
* No daemon code lives here — the scheduler invokes the shim, the shim
|
|
14
|
+
* invokes `flair rem nightly run-once`, the runner module does the work.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, rmSync } from "node:fs";
|
|
17
|
+
import { resolve, dirname } from "node:path";
|
|
18
|
+
import { homedir, platform } from "node:os";
|
|
19
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
export const SHIM_PATH_DEFAULT = resolve(homedir(), ".flair", "bin", "flair-rem-nightly");
|
|
22
|
+
export const LAUNCHD_PLIST_PATH = resolve(homedir(), "Library", "LaunchAgents", "dev.flair.rem.nightly.plist");
|
|
23
|
+
export const SYSTEMD_USER_DIR = resolve(homedir(), ".config", "systemd", "user");
|
|
24
|
+
export const SYSTEMD_TIMER_PATH = resolve(SYSTEMD_USER_DIR, "flair-rem-nightly.timer");
|
|
25
|
+
export const SYSTEMD_SERVICE_PATH = resolve(SYSTEMD_USER_DIR, "flair-rem-nightly.service");
|
|
26
|
+
function detectPlatform(override) {
|
|
27
|
+
if (override)
|
|
28
|
+
return override;
|
|
29
|
+
const p = platform();
|
|
30
|
+
if (p === "darwin")
|
|
31
|
+
return "darwin";
|
|
32
|
+
if (p === "linux")
|
|
33
|
+
return "linux";
|
|
34
|
+
throw new Error(`unsupported platform for REM nightly scheduler: ${p} (only darwin and linux)`);
|
|
35
|
+
}
|
|
36
|
+
function defaultTemplateRoot() {
|
|
37
|
+
// Templates live alongside dist/ in the published package and alongside
|
|
38
|
+
// src/rem/ in the source tree. Walk up from this file until we find
|
|
39
|
+
// a directory containing templates/.
|
|
40
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const candidates = [
|
|
42
|
+
resolve(here, "..", "..", "templates"),
|
|
43
|
+
resolve(here, "..", "..", "..", "templates"),
|
|
44
|
+
resolve(here, "..", "templates"),
|
|
45
|
+
];
|
|
46
|
+
for (const c of candidates) {
|
|
47
|
+
if (existsSync(c))
|
|
48
|
+
return c;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`unable to locate templates directory (looked in: ${candidates.join(", ")})`);
|
|
51
|
+
}
|
|
52
|
+
export function renderTemplate(text, subs) {
|
|
53
|
+
return text.replace(/\{\{([A-Z_]+)\}\}/g, (_, key) => {
|
|
54
|
+
const value = subs[key];
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
throw new Error(`unknown template placeholder: ${key}`);
|
|
57
|
+
return String(value);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function readTemplate(rootDir, relativePath) {
|
|
61
|
+
const full = resolve(rootDir, relativePath);
|
|
62
|
+
if (!existsSync(full)) {
|
|
63
|
+
throw new Error(`template not found: ${full}`);
|
|
64
|
+
}
|
|
65
|
+
return readFileSync(full, "utf-8");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validates the hour:minute schedule. Throws on invalid input rather than
|
|
69
|
+
* silently coercing — surface bad input at the install boundary.
|
|
70
|
+
*/
|
|
71
|
+
function validateSchedule(hour, minute) {
|
|
72
|
+
if (!Number.isInteger(hour) || hour < 0 || hour > 23) {
|
|
73
|
+
throw new Error(`hour must be an integer 0-23, got ${hour}`);
|
|
74
|
+
}
|
|
75
|
+
if (!Number.isInteger(minute) || minute < 0 || minute > 59) {
|
|
76
|
+
throw new Error(`minute must be an integer 0-59, got ${minute}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function buildSubstitutions(opts, shimPath, flairBin) {
|
|
80
|
+
validateSchedule(opts.hour, opts.minute);
|
|
81
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(opts.agentId)) {
|
|
82
|
+
throw new Error(`invalid agent id: ${opts.agentId}`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
FLAIR_BIN: flairBin,
|
|
86
|
+
SHIM_PATH: shimPath,
|
|
87
|
+
HOME: homedir(),
|
|
88
|
+
AGENT_ID: opts.agentId,
|
|
89
|
+
FLAIR_URL: opts.flairUrl,
|
|
90
|
+
HOUR: String(opts.hour),
|
|
91
|
+
HOUR_PAD: String(opts.hour).padStart(2, "0"),
|
|
92
|
+
MINUTE: String(opts.minute),
|
|
93
|
+
MINUTE_PAD: String(opts.minute).padStart(2, "0"),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function writeFileWithDir(path, contents, mode = 0o600) {
|
|
97
|
+
const dir = dirname(path);
|
|
98
|
+
if (!existsSync(dir))
|
|
99
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
100
|
+
writeFileSync(path, contents, { mode });
|
|
101
|
+
}
|
|
102
|
+
// 30s ceiling on launchctl/systemctl invocations so a hung service manager
|
|
103
|
+
// can't block the CLI indefinitely. Sherlock #415 follow-up.
|
|
104
|
+
const SPAWN_TIMEOUT_MS = 30_000;
|
|
105
|
+
// Status-check spawns (launchctl print / systemctl is-active) return
|
|
106
|
+
// near-instantly when the service manager is reachable, and fail fast (no
|
|
107
|
+
// hang) when it isn't (e.g. "Failed to connect to bus"). A short ceiling
|
|
108
|
+
// keeps `flair rem nightly status` and the Health endpoint responsive even
|
|
109
|
+
// when the query is inconclusive.
|
|
110
|
+
const STATUS_CHECK_TIMEOUT_MS = 5_000;
|
|
111
|
+
function spawnReport(cmd, timeoutMs = SPAWN_TIMEOUT_MS) {
|
|
112
|
+
const r = spawnSync(cmd[0], cmd.slice(1), {
|
|
113
|
+
encoding: "buffer",
|
|
114
|
+
timeout: timeoutMs,
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
code: r.status,
|
|
118
|
+
stdout: r.stdout?.toString("utf-8") ?? "",
|
|
119
|
+
stderr: r.stderr?.toString("utf-8") ?? "",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Command to genuinely query the platform scheduler's active/loaded state
|
|
124
|
+
* for a given install (as opposed to the shape of `loadCommand`, which
|
|
125
|
+
* *installs* it). Shared by the sync and async active-state checks below so
|
|
126
|
+
* the two can't drift.
|
|
127
|
+
*/
|
|
128
|
+
function activeCheckCommand(plat) {
|
|
129
|
+
if (plat === "darwin") {
|
|
130
|
+
return ["launchctl", "print", `gui/${process.getuid?.() ?? ""}/dev.flair.rem.nightly`];
|
|
131
|
+
}
|
|
132
|
+
return ["systemctl", "--user", "is-active", "flair-rem-nightly.timer"];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Interprets the result of `activeCheckCommand()`. Shared by the sync
|
|
136
|
+
* (CLI) and async (server) callers.
|
|
137
|
+
*
|
|
138
|
+
* - true — the service manager confirms the job is loaded/active.
|
|
139
|
+
* - false — confirmed NOT active. This includes the "no session bus" case
|
|
140
|
+
* (flair#850): when `systemctl --user` can't reach a bus, it fails
|
|
141
|
+
* before printing a status word ("Failed to connect to bus: No medium
|
|
142
|
+
* found") — but nothing CAN be running without a bus, so `false` is the
|
|
143
|
+
* honest answer, not "unknown".
|
|
144
|
+
* - null — genuinely inconclusive (the command itself couldn't run at
|
|
145
|
+
* all — e.g. the binary is missing — with no output to interpret).
|
|
146
|
+
*/
|
|
147
|
+
export function interpretActiveResult(plat, code, stdout, stderr) {
|
|
148
|
+
const noOutput = !stdout.trim() && !stderr.trim();
|
|
149
|
+
if (plat === "darwin") {
|
|
150
|
+
if (code === 0)
|
|
151
|
+
return true;
|
|
152
|
+
if (code === null && noOutput)
|
|
153
|
+
return null; // spawn itself failed — inconclusive
|
|
154
|
+
return false; // launchctl ran and reported not-loaded
|
|
155
|
+
}
|
|
156
|
+
const out = stdout.trim();
|
|
157
|
+
if (out === "active" || out === "activating")
|
|
158
|
+
return true;
|
|
159
|
+
if (out === "inactive" || out === "failed" || out === "unknown")
|
|
160
|
+
return false;
|
|
161
|
+
if (code === null && noOutput)
|
|
162
|
+
return null; // spawn itself failed — inconclusive
|
|
163
|
+
return false; // covers the no-bus case: empty stdout, nonzero/failed exit
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Synchronous active-state check for CLI use (`flair rem nightly status`).
|
|
167
|
+
* Blocking is fine here — this is a one-shot process and the caller wants
|
|
168
|
+
* the answer before printing anything.
|
|
169
|
+
*/
|
|
170
|
+
function queryActiveState(plat) {
|
|
171
|
+
const [cmd, ...args] = activeCheckCommand(plat);
|
|
172
|
+
const r = spawnReport([cmd, ...args], STATUS_CHECK_TIMEOUT_MS);
|
|
173
|
+
return interpretActiveResult(plat, r.code, r.stdout, r.stderr);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Async, non-blocking equivalent for server contexts (the Health endpoint)
|
|
177
|
+
* where a synchronous subprocess would stall the request-handling thread.
|
|
178
|
+
* Same semantics as `queryActiveState()`.
|
|
179
|
+
*/
|
|
180
|
+
export async function queryActiveStateAsync(plat, timeoutMs = STATUS_CHECK_TIMEOUT_MS) {
|
|
181
|
+
const [cmd, ...args] = activeCheckCommand(plat);
|
|
182
|
+
return new Promise((resolvePromise) => {
|
|
183
|
+
let settled = false;
|
|
184
|
+
let child;
|
|
185
|
+
const finish = (result) => {
|
|
186
|
+
if (settled)
|
|
187
|
+
return;
|
|
188
|
+
settled = true;
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
resolvePromise(result);
|
|
191
|
+
};
|
|
192
|
+
const timer = setTimeout(() => {
|
|
193
|
+
try {
|
|
194
|
+
child?.kill("SIGKILL");
|
|
195
|
+
}
|
|
196
|
+
catch { /* best-effort */ }
|
|
197
|
+
finish(null);
|
|
198
|
+
}, timeoutMs);
|
|
199
|
+
try {
|
|
200
|
+
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
finish(null);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let stdout = "";
|
|
207
|
+
let stderr = "";
|
|
208
|
+
child.stdout?.on("data", (d) => { stdout += d.toString("utf-8"); });
|
|
209
|
+
child.stderr?.on("data", (d) => { stderr += d.toString("utf-8"); });
|
|
210
|
+
child.on("error", () => finish(null));
|
|
211
|
+
child.on("close", (code) => finish(interpretActiveResult(plat, code, stdout, stderr)));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Human remedy text for a failed scheduler-load attempt (flair#850). Covers
|
|
216
|
+
* the one root cause traced so far: a missing systemd user session bus,
|
|
217
|
+
* which blocks `systemctl --user` entirely in ssh-without-lingering,
|
|
218
|
+
* container, and CI contexts. Returns null when the failure doesn't match a
|
|
219
|
+
* known pattern — the caller already prints the raw stderr, so the operator
|
|
220
|
+
* still has something to go on.
|
|
221
|
+
*/
|
|
222
|
+
export function describeLoadFailure(plat, loadResult) {
|
|
223
|
+
const stderr = loadResult.stderr || "";
|
|
224
|
+
if (plat === "linux" && /failed to connect to bus/i.test(stderr)) {
|
|
225
|
+
return ("No systemd user session bus is available in this session (common over ssh without lingering, " +
|
|
226
|
+
"in containers, or under CI). Fix: enable lingering for this user — `loginctl enable-linger <user>` " +
|
|
227
|
+
"— then re-run `flair rem nightly enable`.");
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Formats the `flair rem nightly enable` report from an `EnableResult`.
|
|
233
|
+
* Pulled out of the CLI action so the success-vs-failure decision (flair#850:
|
|
234
|
+
* do not print a success headline before activation is known to have
|
|
235
|
+
* succeeded) is unit-testable without spawning a real launchctl/systemctl or
|
|
236
|
+
* parsing CLI argv.
|
|
237
|
+
*
|
|
238
|
+
* `r.loadResult` is only set when the load command actually ran (the CLI
|
|
239
|
+
* never sets `skipLoad`). A missing `loadResult` (test-only path) is treated
|
|
240
|
+
* as success — matches the CLI's real-world behavior, which always runs the
|
|
241
|
+
* load command and therefore always gets a `loadResult`.
|
|
242
|
+
*/
|
|
243
|
+
export function formatEnableReport(r, input) {
|
|
244
|
+
const { hour, minute, agentId, flairUrl } = input;
|
|
245
|
+
const scheduleTime = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
246
|
+
const activationFailed = !!r.loadResult && r.loadResult.code !== 0;
|
|
247
|
+
if (activationFailed) {
|
|
248
|
+
const lr = r.loadResult;
|
|
249
|
+
const lines = [
|
|
250
|
+
`⚠️ REM nightly scheduler files written but NOT activated (${r.platform})`,
|
|
251
|
+
` Schedule: ${scheduleTime} local time — NOT scheduled (see below)`,
|
|
252
|
+
` Scheduler: ${r.schedulerPath}`,
|
|
253
|
+
` Shim: ${r.shimPath}`,
|
|
254
|
+
` Agent: ${agentId}`,
|
|
255
|
+
` Flair URL: ${flairUrl}`,
|
|
256
|
+
` Activation: ${r.loadCommand.join(" ")} → code ${lr.code}`,
|
|
257
|
+
];
|
|
258
|
+
if (lr.stderr)
|
|
259
|
+
lines.push(` stderr: ${lr.stderr.trim()}`);
|
|
260
|
+
const remedy = describeLoadFailure(r.platform, lr);
|
|
261
|
+
lines.push("");
|
|
262
|
+
lines.push(remedy ? ` ${remedy}` : ` Re-run the activation command above manually to see the full diagnostic.`);
|
|
263
|
+
lines.push("");
|
|
264
|
+
lines.push(` Nothing is scheduled until activation succeeds. Check anytime with: flair rem nightly status`);
|
|
265
|
+
return { lines, ok: false };
|
|
266
|
+
}
|
|
267
|
+
const lines = [
|
|
268
|
+
`✅ REM nightly scheduler enabled (${r.platform})`,
|
|
269
|
+
` Schedule: ${scheduleTime} local time`,
|
|
270
|
+
` Scheduler: ${r.schedulerPath}`,
|
|
271
|
+
` Shim: ${r.shimPath}`,
|
|
272
|
+
` Agent: ${agentId}`,
|
|
273
|
+
` Flair URL: ${flairUrl}`,
|
|
274
|
+
];
|
|
275
|
+
if (r.loadResult) {
|
|
276
|
+
lines.push(` Load: ${r.loadCommand.join(" ")} → ok`);
|
|
277
|
+
}
|
|
278
|
+
lines.push("");
|
|
279
|
+
lines.push(`Tip: run \`flair rem nightly run-once --dry-run\` to verify the cycle works`);
|
|
280
|
+
lines.push(` before the first scheduled fire. Disable with \`flair rem nightly disable\`.`);
|
|
281
|
+
return { lines, ok: true };
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Formats the `flair rem nightly status` report from a `SchedulerStatus`.
|
|
285
|
+
* Extracted for the same testability reason as `formatEnableReport()`
|
|
286
|
+
* (flair#850): status must report genuine active state, not file presence.
|
|
287
|
+
*/
|
|
288
|
+
export function formatStatusReport(s) {
|
|
289
|
+
const activeTxt = s.active === true ? "yes" : s.active === false ? "no" : "unknown";
|
|
290
|
+
const lines = [
|
|
291
|
+
`REM nightly scheduler (${s.platform}):`,
|
|
292
|
+
` Active: ${activeTxt}`,
|
|
293
|
+
` Installed: ${s.installed ? "yes" : "no"}`,
|
|
294
|
+
` Scheduler: ${s.schedulerPath}`,
|
|
295
|
+
` Shim: ${s.shimPath}${s.shimExists ? "" : " (missing)"}`,
|
|
296
|
+
];
|
|
297
|
+
if (!s.installed) {
|
|
298
|
+
lines.push("");
|
|
299
|
+
lines.push(`Enable with: flair rem nightly enable --agent <id> [--at HH:MM]`);
|
|
300
|
+
}
|
|
301
|
+
else if (s.active === false) {
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push(`Files are written but nothing is scheduled — the job is not loaded/active.`);
|
|
304
|
+
lines.push(`Re-run: flair rem nightly enable --agent <id> [--at HH:MM]`);
|
|
305
|
+
}
|
|
306
|
+
// Status is informational — it does not itself signal process failure,
|
|
307
|
+
// consistent with the pre-existing "not installed" case never exiting
|
|
308
|
+
// nonzero. `ok` here only reflects whether the headline claims success.
|
|
309
|
+
return { lines, ok: s.active !== false };
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Installs the platform-native scheduler entry and the shim script.
|
|
313
|
+
*
|
|
314
|
+
* macOS: writes ~/Library/LaunchAgents/dev.flair.rem.nightly.plist + bootstraps it via launchctl.
|
|
315
|
+
* Linux: writes ~/.config/systemd/user/flair-rem-nightly.{timer,service} + enables the timer.
|
|
316
|
+
*
|
|
317
|
+
* In both cases, also deploys ~/.flair/bin/flair-rem-nightly as the shim
|
|
318
|
+
* the scheduler invokes.
|
|
319
|
+
*/
|
|
320
|
+
export function enableScheduler(opts) {
|
|
321
|
+
const plat = detectPlatform(opts.platformOverride);
|
|
322
|
+
const flairBin = opts.flairBin ?? process.argv[1] ?? "flair";
|
|
323
|
+
const shimPath = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
|
|
324
|
+
const templateRoot = opts.templateRootOverride ?? defaultTemplateRoot();
|
|
325
|
+
const subs = buildSubstitutions(opts, shimPath, flairBin);
|
|
326
|
+
// 1. Deploy the shim (always — both platforms invoke it).
|
|
327
|
+
const shimContents = renderTemplate(readTemplate(templateRoot, "bin/flair-rem-nightly.sh.tmpl"), subs);
|
|
328
|
+
writeFileWithDir(shimPath, shimContents, 0o700);
|
|
329
|
+
chmodSync(shimPath, 0o700);
|
|
330
|
+
// 2. Write the scheduler entry.
|
|
331
|
+
if (plat === "darwin") {
|
|
332
|
+
const plistPath = opts.launchdPlistOverride ?? LAUNCHD_PLIST_PATH;
|
|
333
|
+
const plistContents = renderTemplate(readTemplate(templateRoot, "launchd/dev.flair.rem.nightly.plist.tmpl"), subs);
|
|
334
|
+
writeFileWithDir(plistPath, plistContents, 0o600);
|
|
335
|
+
const loadCommand = ["launchctl", "bootstrap", `gui/${process.getuid?.() ?? ""}`, plistPath];
|
|
336
|
+
let loadResult;
|
|
337
|
+
if (!opts.skipLoad) {
|
|
338
|
+
// Bootout first in case a prior install left the job loaded.
|
|
339
|
+
spawnReport(["launchctl", "bootout", `gui/${process.getuid?.() ?? ""}`, plistPath]);
|
|
340
|
+
loadResult = spawnReport(loadCommand);
|
|
341
|
+
}
|
|
342
|
+
return { platform: plat, shimPath, schedulerPath: plistPath, loadCommand, loadResult };
|
|
343
|
+
}
|
|
344
|
+
// Linux: systemd user units.
|
|
345
|
+
const timerPath = opts.systemdTimerOverride ?? SYSTEMD_TIMER_PATH;
|
|
346
|
+
const servicePath = opts.systemdServiceOverride ?? SYSTEMD_SERVICE_PATH;
|
|
347
|
+
const serviceContents = renderTemplate(readTemplate(templateRoot, "systemd/flair-rem-nightly.service.tmpl"), subs);
|
|
348
|
+
const timerContents = renderTemplate(readTemplate(templateRoot, "systemd/flair-rem-nightly.timer.tmpl"), subs);
|
|
349
|
+
writeFileWithDir(servicePath, serviceContents, 0o600);
|
|
350
|
+
writeFileWithDir(timerPath, timerContents, 0o600);
|
|
351
|
+
const loadCommand = ["systemctl", "--user", "enable", "--now", "flair-rem-nightly.timer"];
|
|
352
|
+
let loadResult;
|
|
353
|
+
if (!opts.skipLoad) {
|
|
354
|
+
spawnReport(["systemctl", "--user", "daemon-reload"]);
|
|
355
|
+
loadResult = spawnReport(loadCommand);
|
|
356
|
+
}
|
|
357
|
+
return { platform: plat, shimPath, schedulerPath: timerPath, loadCommand, loadResult };
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Removes the scheduler entry. Audit log + snapshots are preserved.
|
|
361
|
+
*/
|
|
362
|
+
export function disableScheduler(opts = {}) {
|
|
363
|
+
const plat = detectPlatform(opts.platformOverride);
|
|
364
|
+
const removed = [];
|
|
365
|
+
if (plat === "darwin") {
|
|
366
|
+
const plistPath = opts.launchdPlistOverride ?? LAUNCHD_PLIST_PATH;
|
|
367
|
+
const unloadCommand = ["launchctl", "bootout", `gui/${process.getuid?.() ?? ""}`, plistPath];
|
|
368
|
+
let unloadResult;
|
|
369
|
+
if (existsSync(plistPath)) {
|
|
370
|
+
if (!opts.skipUnload) {
|
|
371
|
+
unloadResult = spawnReport(unloadCommand);
|
|
372
|
+
}
|
|
373
|
+
rmSync(plistPath, { force: true });
|
|
374
|
+
removed.push(plistPath);
|
|
375
|
+
}
|
|
376
|
+
if (opts.removeShim) {
|
|
377
|
+
const shim = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
|
|
378
|
+
if (existsSync(shim)) {
|
|
379
|
+
rmSync(shim, { force: true });
|
|
380
|
+
removed.push(shim);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return { platform: plat, removed, unloadCommand, unloadResult };
|
|
384
|
+
}
|
|
385
|
+
const timerPath = opts.systemdTimerOverride ?? SYSTEMD_TIMER_PATH;
|
|
386
|
+
const servicePath = opts.systemdServiceOverride ?? SYSTEMD_SERVICE_PATH;
|
|
387
|
+
const unloadCommand = ["systemctl", "--user", "disable", "--now", "flair-rem-nightly.timer"];
|
|
388
|
+
let unloadResult;
|
|
389
|
+
if (existsSync(timerPath) || existsSync(servicePath)) {
|
|
390
|
+
if (!opts.skipUnload) {
|
|
391
|
+
unloadResult = spawnReport(unloadCommand);
|
|
392
|
+
spawnReport(["systemctl", "--user", "daemon-reload"]);
|
|
393
|
+
}
|
|
394
|
+
if (existsSync(timerPath)) {
|
|
395
|
+
rmSync(timerPath, { force: true });
|
|
396
|
+
removed.push(timerPath);
|
|
397
|
+
}
|
|
398
|
+
if (existsSync(servicePath)) {
|
|
399
|
+
rmSync(servicePath, { force: true });
|
|
400
|
+
removed.push(servicePath);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (opts.removeShim) {
|
|
404
|
+
const shim = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
|
|
405
|
+
if (existsSync(shim)) {
|
|
406
|
+
rmSync(shim, { force: true });
|
|
407
|
+
removed.push(shim);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return { platform: plat, removed, unloadCommand, unloadResult };
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Reports whether the scheduler is installed AND whether it is genuinely
|
|
414
|
+
* active. File presence alone proves the templates were written — it does
|
|
415
|
+
* NOT prove `launchctl`/`systemctl` successfully loaded the job (flair#850).
|
|
416
|
+
*/
|
|
417
|
+
export function schedulerStatus(opts = {}) {
|
|
418
|
+
const plat = detectPlatform(opts.platformOverride);
|
|
419
|
+
const shimPath = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
|
|
420
|
+
let schedulerPath;
|
|
421
|
+
let installed;
|
|
422
|
+
if (plat === "darwin") {
|
|
423
|
+
schedulerPath = opts.launchdPlistOverride ?? LAUNCHD_PLIST_PATH;
|
|
424
|
+
installed = existsSync(schedulerPath);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
schedulerPath = opts.systemdTimerOverride ?? SYSTEMD_TIMER_PATH;
|
|
428
|
+
const servicePath = opts.systemdServiceOverride ?? SYSTEMD_SERVICE_PATH;
|
|
429
|
+
installed = existsSync(schedulerPath) && existsSync(servicePath);
|
|
430
|
+
}
|
|
431
|
+
let active;
|
|
432
|
+
if (!installed) {
|
|
433
|
+
active = false; // nothing written — definitely nothing active
|
|
434
|
+
}
|
|
435
|
+
else if (opts.skipActiveCheck) {
|
|
436
|
+
active = null; // caller opted out — unknown, not a claim either way
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
active = queryActiveState(plat);
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
platform: plat,
|
|
443
|
+
installed,
|
|
444
|
+
active,
|
|
445
|
+
schedulerPath,
|
|
446
|
+
shimPath,
|
|
447
|
+
shimExists: existsSync(shimPath),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[0.001, "o", " Flair \u00b7 same identity, every orchestrator\r\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n"]
|
|
4
4
|
[1.3, "o", "$ # One agent identity. One memory store. Three orchestrators.\r\n"]
|
|
5
5
|
[1.2, "o", "$ flair init --client all --agent flint\r\n"]
|
|
6
|
-
[0.5, "o", " \u001b[32m\u2713\u001b[0m Claude Code wired (~/.claude
|
|
6
|
+
[0.5, "o", " \u001b[32m\u2713\u001b[0m Claude Code wired (~/.claude.json)\r\n"]
|
|
7
7
|
[0.18, "o", " \u001b[32m\u2713\u001b[0m Codex CLI wired (~/.codex/config.toml)\r\n"]
|
|
8
8
|
[0.18, "o", " \u001b[32m\u2713\u001b[0m Gemini CLI wired (~/.gemini/settings.json)\r\n\r\n"]
|
|
9
9
|
[1.0, "o", " Each MCP client now connects to: http://127.0.0.1:9926\r\n"]
|
package/docs/bridges.md
CHANGED
|
@@ -101,7 +101,7 @@ A few things worth knowing:
|
|
|
101
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
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
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 [
|
|
104
|
+
- **Errors are structured.** Every error includes `bridge`, `op`, `path`, `record`, `field`, `expected`, `got`, `hint` (per [Error format](#error-format) below). The `hint` is the part you act on; the rest is for an LLM to self-correct without operator help.
|
|
105
105
|
|
|
106
106
|
## Shape A — Declarative YAML
|
|
107
107
|
|
|
@@ -286,6 +286,6 @@ If you want an agent to write a bridge for you, here's the one-shot prompt:
|
|
|
286
286
|
|
|
287
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
288
|
|
|
289
|
-
##
|
|
289
|
+
## Authority
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
This document is the authoritative contract for the bridge plugin system: the record schema, both plugin shapes, discovery, distribution, trust, round-trip testing, and the error format are all specified above. There is no separate design spec — the original planning document was retired once the system shipped, and its design-rationale and milestone-scoping sections are preserved in git history rather than maintained here.
|
package/docs/deployment.md
CHANGED
|
@@ -179,6 +179,7 @@ Set these in the Flair process environment (`~/Library/LaunchAgents/ai.tpsdev.fl
|
|
|
179
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
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
181
|
| `HTTP_PORT` | Override the Harper HTTP port. Useful for sandboxes; production deployments should configure the port in `config.yaml` instead. | Rare. |
|
|
182
|
+
| `FLAIR_OPS_BIND` | Bind address for the Harper **ops API**. Resolution order: `flair init --ops-bind` > this variable > the `opsBind` key `flair init` persists in `~/.flair/config.yaml` > `127.0.0.1`. Every Flair-managed Harper start re-asserts the resolved value, so the persisted key is what makes a choice survive `flair restart` / `flair upgrade`. | Only for deployments that genuinely need remote ops admin (multi-host / Fabric) — set it to `0.0.0.0`, or record it once with `flair init --ops-bind 0.0.0.0`. Single-host installs want the loopback default. |
|
|
182
183
|
|
|
183
184
|
---
|
|
184
185
|
|
package/docs/mcp-clients.md
CHANGED
|
@@ -38,9 +38,11 @@ Flair runs as a local server at `http://127.0.0.1:19926` by default. The MCP ser
|
|
|
38
38
|
|
|
39
39
|
Pick whichever you use. The MCP server is the same package; only the config syntax differs.
|
|
40
40
|
|
|
41
|
+
> **Pin the version.** The snippets below use the bare package name for readability. `flair init` wires clients to a **pinned** spec (`@tpsdev-ai/flair-mcp@<version>`) on purpose: an unpinned reference re-resolves to whatever is currently published on every agent session, so any future publish reaches your machine silently. If you wire by hand, append the version you intend to run — `@tpsdev-ai/flair-mcp@0.28.0` — and bump it deliberately. `flair init` is the easier path and does this for you.
|
|
42
|
+
|
|
41
43
|
### Claude Code
|
|
42
44
|
|
|
43
|
-
The canonical approach is the `claude mcp add` CLI (writes to `~/.claude
|
|
45
|
+
The canonical approach is the `claude mcp add` CLI (writes to `~/.claude.json`):
|
|
44
46
|
|
|
45
47
|
```bash
|
|
46
48
|
claude mcp add flair --scope user \
|
package/docs/n8n.md
CHANGED
|
@@ -93,7 +93,7 @@ If any of those don't hold, use Flair's CLI / SDK clients (which support per-age
|
|
|
93
93
|
|
|
94
94
|
## Get By Tag — coming soon
|
|
95
95
|
|
|
96
|
-
The Flair Search node currently exposes Semantic Search and Get By Subject. **Get By Tag** is deferred until `flair-client.memory.list` exposes a `tags` filter
|
|
96
|
+
The Flair Search node currently exposes Semantic Search and Get By Subject. **Get By Tag** is deferred until `flair-client.memory.list` exposes a `tags` filter. Workaround for now: use Semantic Search and let the model filter results by tags in the response.
|
|
97
97
|
|
|
98
98
|
## Worked examples
|
|
99
99
|
|
|
@@ -117,6 +117,5 @@ Same Flair instance, same memories, different surfaces.
|
|
|
117
117
|
|
|
118
118
|
## See also
|
|
119
119
|
|
|
120
|
-
- [Spec — `@tpsdev-ai/n8n-nodes-flair`](https://github.com/tpsdev-ai/flair/blob/main/specs/N8N-NODE-q3qf.md) — implementation plan, design decisions, anti-patterns
|
|
121
120
|
- [Bridges](./bridges.md) — how Flair memories flow between hosts and instances
|
|
122
121
|
- [Federation](./federation.md) — hub-and-spoke replication
|
package/docs/notes/rem-ux.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# REM UX — trigger model, attach semantics, review loop
|
|
2
2
|
|
|
3
|
-
> Design note accompanying REM slice 2 (#707). Describes the intended user experience of in-process distillation so the CLI/docs surfaces stay coherent as the feature grows.
|
|
3
|
+
> Design note accompanying REM slice 2 (#707). Describes the intended user experience of in-process distillation so the CLI/docs surfaces stay coherent as the feature grows. See [`docs/rem.md`](../rem.md) for the REM configuration and command reference.
|
|
4
4
|
|
|
5
5
|
## Triggers — three, nothing implicit
|
|
6
6
|
|
package/docs/rem.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
REM (Reflect · Extract · Merge) is Flair's memory-curation cycle: it reads an agent's recent memories, distills them into candidate insights, and stages those candidates for explicit human/agent review — nothing is ever auto-promoted. `flair rem rapid` runs it on demand; `flair rem nightly enable` runs it on a schedule. See [`docs/notes/rem-ux.md`](notes/rem-ux.md) for the full trigger model, locality guarantees, and the review-loop UX this page's commands feed into.
|
|
4
4
|
|
|
5
|
+
> **⚠️ Prerequisite: a configured generative backend.** All REM commands (`rapid`, `nightly`, `candidates`, `promote`, `reject`) require Harper's `models.generate()` to be wired — without it, REM calls fail with `Reflection error: No generative backend configured`. Set up a backend first (see [Configuration](#configuration) below) before running any REM command. The fastest path is Ollama with a non-thinking model, which needs zero credentials and keeps all traffic local.
|
|
6
|
+
|
|
5
7
|
## Configuration
|
|
6
8
|
|
|
7
9
|
Distillation runs **server-side**, via Harper's model-access API (`models.generate()`). Flair ships zero provider code — which backend answers a REM call is entirely a Harper `models:` configuration decision.
|
|
@@ -19,13 +19,13 @@ We don't pull in any newly-published dep version for **at least 7 days** after i
|
|
|
19
19
|
|
|
20
20
|
#### 1a. Keep-current allow-list
|
|
21
21
|
|
|
22
|
-
Some deps are tightly coupled to Flair's runtime correctness — Harper bug fixes and security patches land in
|
|
22
|
+
Some deps are tightly coupled to Flair's runtime correctness — Harper bug fixes and security patches land in `harper`, embedding-pipeline fixes land in `harper-fabric-embeddings`. We accept the bake-time risk and pull these eagerly. Current allow-list:
|
|
23
23
|
|
|
24
24
|
| Package | Why kept current |
|
|
25
25
|
|---------|------------------|
|
|
26
|
-
|
|
|
26
|
+
| `harper` | Foundational. Vector-index and HNSW correctness fixes land here; we want them ASAP. High-volume upstream, fast detection if compromised. **Bare name, not `@harperfast/harper`** — the two are permanent lockstep publishes of the same source, and depending on the scoped one made every install materialise both copies (~213 MB), because `@harperfast/oauth` peers on the bare name and npm cannot dedupe across two names. Aligned in flair#870. |
|
|
27
27
|
| `harper-fabric-embeddings` | Embedding model loader. Coupled to Harper version. Same trust-and-volume reasoning. |
|
|
28
|
-
| `@harperfast/oauth` | Same high-trust
|
|
28
|
+
| `@harperfast/oauth` | Same high-trust upstream owner as `harper`. Used ONLY by the **default-OFF** native-MCP OAuth surface (`FLAIR_MCP_OAUTH`), which dynamically imports it only when the flag is on — it is **not loaded in the shipped default build**, so bake-time exposure is zero until an operator explicitly opts in. Pinned to the exact version whose `withMCPAuth` API the surface was built against; the API surface (not a floating range) is what we depend on. |
|
|
29
29
|
|
|
30
30
|
Adding to this list is a deliberate decision. The bar:
|
|
31
31
|
- The upstream is well-known and high-volume (gets eyeballs fast).
|
|
@@ -75,7 +75,7 @@ Only Nathan publishes to npm (per the existing MFA boundary). Flint preps the re
|
|
|
75
75
|
|
|
76
76
|
### `.github/renovate.json` — deliberate, cooldown-gated update proposals
|
|
77
77
|
|
|
78
|
-
Renovate opens PRs to propose dependency updates so we don't drift behind upstream indefinitely — but on our terms, not the registry's. It is configured to never auto-merge (`automerge: false`), to pin (`rangeStrategy: "pin"`, consistent with §2), and to respect the bake-time cooldown (`minimumReleaseAge: "7 days"`, matching `FLAIR_DEP_MIN_AGE_DAYS` in `check-dep-ages.mjs`) so it only proposes versions that have already cleared the detection window. Non-major updates are grouped; majors land as isolated PRs. The keep-current allow-list (
|
|
78
|
+
Renovate opens PRs to propose dependency updates so we don't drift behind upstream indefinitely — but on our terms, not the registry's. It is configured to never auto-merge (`automerge: false`), to pin (`rangeStrategy: "pin"`, consistent with §2), and to respect the bake-time cooldown (`minimumReleaseAge: "7 days"`, matching `FLAIR_DEP_MIN_AGE_DAYS` in `check-dep-ages.mjs`) so it only proposes versions that have already cleared the detection window. Non-major updates are grouped; majors land as isolated PRs. The keep-current allow-list (`harper`, `harper-fabric-embeddings`, `@harperfast/oauth`) mirrors the script's `DEFAULT_KEEP_CURRENT` — keep the two in lockstep when either changes. Vulnerability alerts bypass the cooldown. Every Renovate PR still runs the full CI suite (including the bake-time and workspace-deps gates) and is K&S-reviewed before merge.
|
|
79
79
|
|
|
80
80
|
### `scripts/check-workspace-deps.mjs` (already shipped, PR #368)
|
|
81
81
|
|
package/docs/upgrade.md
CHANGED
|
@@ -177,7 +177,7 @@ FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> \
|
|
|
177
177
|
(or `--fabric-password-file <path>` instead of the `FABRIC_PASSWORD` env var — reads the
|
|
178
178
|
password from a file, chmod 600). This resolves the target version (latest published
|
|
179
179
|
`@tpsdev-ai/flair`, or pin one with `--version`), stages a clean deployable with the
|
|
180
|
-
required
|
|
180
|
+
required `harper` version pin applied (`--harper-version` to override),
|
|
181
181
|
confirms the staged Harper build before deploying, then reuses `flair deploy` to push it
|
|
182
182
|
and verifies the result. `--check` shows the version diff and plan without deploying
|
|
183
183
|
anything; `--yes` skips the confirmation prompt for scripted use.
|
|
@@ -223,7 +223,7 @@ Exit codes:
|
|
|
223
223
|
*Flair's own* federation peer table, not Harper Fabric's own cluster-replication
|
|
224
224
|
nodes. Harper's `cluster_status` operation (the one that would answer "what nodes are
|
|
225
225
|
in this cluster and are they in sync") is harper-pro-only and unavailable in the OSS
|
|
226
|
-
|
|
226
|
+
`harper` build this CLI ships — there is no way for this CLI to enumerate
|
|
227
227
|
Fabric's own replication topology, on the origin or anywhere else. A Fabric replica
|
|
228
228
|
that was never separately paired as a Flair federation peer (`flair federation pair`)
|
|
229
229
|
is invisible to this sweep: `0 peers known` means "0 peers on file," never "0 peers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"packageManager": "bun@1.3.10",
|
|
5
5
|
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
|
|
6
6
|
"type": "module",
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
"node": ">=22"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@harperfast/harper": "5.1.22",
|
|
60
59
|
"@harperfast/oauth": "2.4.0",
|
|
61
60
|
"@types/js-yaml": "4.0.9",
|
|
62
61
|
"commander": "14.0.3",
|
|
62
|
+
"harper": "5.1.22",
|
|
63
63
|
"harper-fabric-embeddings": "^0.5.0",
|
|
64
64
|
"jose": "6.2.2",
|
|
65
|
-
"js-yaml": "4.
|
|
65
|
+
"js-yaml": "4.3.0",
|
|
66
66
|
"node-llama-cpp": "3.18.1",
|
|
67
|
-
"tar": "7.5.
|
|
67
|
+
"tar": "7.5.20",
|
|
68
68
|
"tweetnacl": "1.0.3"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|