@tpsdev-ai/flair 0.44.13 → 0.46.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/config.yaml +35 -2
- package/dist/cli.js +410 -21
- package/dist/doctor-client.js +266 -0
- package/dist/federation/scheduler.js +114 -9
- package/dist/hook-install.js +150 -1
- package/dist/lib/mcp-enable.js +59 -19
- package/dist/lib/safe-snapshot-extract.js +20 -2
- package/dist/lib/scheduler-platform.js +210 -1
- package/dist/rem/scheduler.js +113 -13
- package/dist/rem/snapshot.js +14 -13
- package/dist/resources/Memory.js +32 -1
- package/dist/resources/MemoryBootstrap.js +88 -51
- package/dist/resources/MemoryFeed.js +56 -1
- package/dist/resources/MemoryMaintenance.js +8 -2
- package/dist/resources/abstention.js +12 -9
- package/dist/resources/auth-middleware.js +11 -3
- package/dist/resources/bm25.js +7 -3
- package/dist/resources/mcp-oauth-flag.js +20 -0
- package/dist/resources/mcp-oauth.js +6 -1
- package/dist/resources/memory-visibility.js +48 -0
- package/dist/resources/semantic-retrieval-core.js +99 -19
- package/dist/src/lib/scheduler-platform.js +210 -1
- package/dist/src/rem/scheduler.js +113 -13
- package/docs/notes/mcp-oauth-model2.md +10 -3
- package/docs/quickstart-fabric.md +42 -3
- package/package.json +1 -1
- package/templates/bin/flair-federation-sync.sh.tmpl +8 -1
- package/templates/bin/flair-rem-nightly.sh.tmpl +8 -1
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
* No daemon code lives here — the scheduler invokes the shim, the shim
|
|
14
14
|
* invokes `flair rem nightly run-once`, the runner module does the work.
|
|
15
15
|
*/
|
|
16
|
-
import { existsSync, chmodSync, rmSync } from "node:fs";
|
|
16
|
+
import { existsSync, mkdirSync, chmodSync, rmSync } from "node:fs";
|
|
17
17
|
import { resolve, dirname } from "node:path";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
import { spawn } from "node:child_process";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { escapeXml } from "../lib/xml-escape.js";
|
|
22
|
-
import { detectPlatform as detectPlatformFor, spawnReport, readTemplate as readTemplateFrom, renderTemplateWith, writeFileWithDir, interpretActiveResult, describeLoadFailure as describeLoadFailureFor, STATUS_CHECK_TIMEOUT_MS, } from "../lib/scheduler-platform.js";
|
|
22
|
+
import { detectPlatform as detectPlatformFor, spawnReport, readTemplate as readTemplateFrom, renderTemplateWith, writeFileWithDir, interpretActiveResult, describeLoadFailure as describeLoadFailureFor, describeExitCode, resolveNodeBin, verifyFirstRun, STATUS_CHECK_TIMEOUT_MS, } from "../lib/scheduler-platform.js";
|
|
23
23
|
// Re-exported so this module's public surface is unchanged by the extraction
|
|
24
24
|
// into src/lib/scheduler-platform.ts (a second scheduler — `flair federation
|
|
25
25
|
// sync enable` — needs the identical launchctl/systemctl interpretation, and
|
|
@@ -87,15 +87,16 @@ function validateSchedule(hour, minute) {
|
|
|
87
87
|
throw new Error(`minute must be an integer 0-59, got ${minute}`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
function buildSubstitutions(opts, shimPath, flairBin) {
|
|
90
|
+
function buildSubstitutions(opts, shimPath, flairBin, nodeBin) {
|
|
91
91
|
validateSchedule(opts.hour, opts.minute);
|
|
92
92
|
if (!/^[a-zA-Z0-9_-]+$/.test(opts.agentId)) {
|
|
93
93
|
throw new Error(`invalid agent id: ${opts.agentId}`);
|
|
94
94
|
}
|
|
95
95
|
return {
|
|
96
96
|
FLAIR_BIN: flairBin,
|
|
97
|
+
NODE_BIN: nodeBin,
|
|
97
98
|
SHIM_PATH: shimPath,
|
|
98
|
-
HOME: homedir(),
|
|
99
|
+
HOME: opts.homeOverride ?? homedir(),
|
|
99
100
|
AGENT_ID: opts.agentId,
|
|
100
101
|
FLAIR_URL: opts.flairUrl,
|
|
101
102
|
HOUR: String(opts.hour),
|
|
@@ -183,10 +184,14 @@ export function describeLoadFailure(plat, loadResult) {
|
|
|
183
184
|
* succeeded) is unit-testable without spawning a real launchctl/systemctl or
|
|
184
185
|
* parsing CLI argv.
|
|
185
186
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
187
|
+
* flair#1231 deepened the #850 rule by one layer: activation exiting 0 proves
|
|
188
|
+
* the service manager ACCEPTED the job, not that the job can run — a stripped
|
|
189
|
+
* exec bit and a missing log directory both passed activation and killed the
|
|
190
|
+
* first real run invisibly. So the ✅ headline is now additionally gated on
|
|
191
|
+
* `firstRunVerified`: success may not be claimed until the thing the operator
|
|
192
|
+
* asked for — a REM run through the service manager — has been observed to
|
|
193
|
+
* happen once. A missing `loadResult`/`firstRun` (test-only skipLoad shape)
|
|
194
|
+
* therefore withholds the headline too, instead of being treated as success.
|
|
190
195
|
*/
|
|
191
196
|
export function formatEnableReport(r, input) {
|
|
192
197
|
const { hour, minute, agentId, flairUrl } = input;
|
|
@@ -212,6 +217,57 @@ export function formatEnableReport(r, input) {
|
|
|
212
217
|
lines.push(` Nothing is scheduled until activation succeeds. Check anytime with: flair rem nightly status`);
|
|
213
218
|
return { lines, ok: false };
|
|
214
219
|
}
|
|
220
|
+
if (!r.firstRunVerified) {
|
|
221
|
+
const fr = r.firstRun;
|
|
222
|
+
const headline = fr?.outcome === "run-failed"
|
|
223
|
+
? `⚠️ REM nightly scheduler installed but the first run FAILED (${describeExitCode(fr.exitCode)})`
|
|
224
|
+
: fr?.outcome === "timeout"
|
|
225
|
+
? `⚠️ REM nightly scheduler installed but the first run did not complete within ${Math.round(fr.budgetMs / 1000)}s — cannot confirm it works`
|
|
226
|
+
: fr?.outcome === "manager-unavailable"
|
|
227
|
+
? `⚠️ REM nightly scheduler installed but the service manager is unreachable — cannot verify the first run`
|
|
228
|
+
: fr?.outcome === "start-failed"
|
|
229
|
+
? `⚠️ REM nightly scheduler installed but the first run could not be started`
|
|
230
|
+
: `⚠️ REM nightly scheduler installed but the first run was never verified`;
|
|
231
|
+
const lines = [
|
|
232
|
+
headline,
|
|
233
|
+
` Schedule: ${scheduleTime} local time`,
|
|
234
|
+
` Scheduler: ${r.schedulerPath}`,
|
|
235
|
+
` Shim: ${r.shimPath}`,
|
|
236
|
+
` Agent: ${agentId}`,
|
|
237
|
+
` Flair URL: ${flairUrl}`,
|
|
238
|
+
];
|
|
239
|
+
if (r.loadResult)
|
|
240
|
+
lines.push(` Load: ${r.loadCommand.join(" ")} → ok`);
|
|
241
|
+
if (fr) {
|
|
242
|
+
lines.push(` First run: ${fr.detail}`);
|
|
243
|
+
if (fr.stderrTail) {
|
|
244
|
+
lines.push(` Log tail (${fr.logPath}):`);
|
|
245
|
+
for (const l of fr.stderrTail.split("\n"))
|
|
246
|
+
lines.push(` ${l}`);
|
|
247
|
+
}
|
|
248
|
+
else if (fr.logEmpty) {
|
|
249
|
+
lines.push(` Log file ${fr.logPath} exists but is EMPTY — the run died before writing anything.`);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
lines.push(` No log file at ${fr.logPath}.`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
lines.push("");
|
|
256
|
+
if (fr?.outcome === "timeout") {
|
|
257
|
+
lines.push(` The run may legitimately still be going (a REM cycle can be slow). Check the log above and`);
|
|
258
|
+
lines.push(` \`flair rem nightly status\`; no cycle has been CONFIRMED to work yet.`);
|
|
259
|
+
}
|
|
260
|
+
else if (fr?.outcome === "manager-unavailable") {
|
|
261
|
+
lines.push(` The scheduler files are installed, but launchctl/systemctl could not be consulted, so whether`);
|
|
262
|
+
lines.push(` the nightly cycle runs is UNKNOWN. Fix the service manager for this session, then re-run \`flair rem nightly enable\`.`);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
lines.push(` No REM cycle has run. Fix the cause above, then re-run \`flair rem nightly enable\`.`);
|
|
266
|
+
}
|
|
267
|
+
lines.push("");
|
|
268
|
+
lines.push(` Check anytime with: flair rem nightly status`);
|
|
269
|
+
return { lines, ok: false };
|
|
270
|
+
}
|
|
215
271
|
const lines = [
|
|
216
272
|
`✅ REM nightly scheduler enabled (${r.platform})`,
|
|
217
273
|
` Schedule: ${scheduleTime} local time`,
|
|
@@ -223,9 +279,9 @@ export function formatEnableReport(r, input) {
|
|
|
223
279
|
if (r.loadResult) {
|
|
224
280
|
lines.push(` Load: ${r.loadCommand.join(" ")} → ok`);
|
|
225
281
|
}
|
|
282
|
+
lines.push(` First run: completed through the service manager, exit 0`);
|
|
226
283
|
lines.push("");
|
|
227
|
-
lines.push(`
|
|
228
|
-
lines.push(` before the first scheduled fire. Disable with \`flair rem nightly disable\`.`);
|
|
284
|
+
lines.push(`Disable with \`flair rem nightly disable\`.`);
|
|
229
285
|
return { lines, ok: true };
|
|
230
286
|
}
|
|
231
287
|
/**
|
|
@@ -268,9 +324,29 @@ export function formatStatusReport(s) {
|
|
|
268
324
|
export function enableScheduler(opts) {
|
|
269
325
|
const plat = detectPlatform(opts.platformOverride);
|
|
270
326
|
const flairBin = opts.flairBin ?? process.argv[1] ?? "flair";
|
|
327
|
+
const nodeBin = resolveNodeBin(opts.nodeBin);
|
|
271
328
|
const shimPath = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
|
|
272
329
|
const templateRoot = opts.templateRootOverride ?? defaultTemplateRoot();
|
|
273
|
-
const subs = buildSubstitutions(opts, shimPath, flairBin);
|
|
330
|
+
const subs = buildSubstitutions(opts, shimPath, flairBin, nodeBin);
|
|
331
|
+
// 0. Create the log directory the unit files point stdout/stderr at.
|
|
332
|
+
// Nothing else ever creates it — launchd kills a job whose StandardOutPath
|
|
333
|
+
// directory is missing (spawn error 209) and systemd fails the unit (#1231).
|
|
334
|
+
//
|
|
335
|
+
// Mode 0700 is load-bearing, NOT cosmetic: REM's nightly log carries
|
|
336
|
+
// distillation CANDIDATE CONTENT — actual memory text, not just counts.
|
|
337
|
+
// Relaxing it to 0755 (e.g. "for shared debugging") would expose memory
|
|
338
|
+
// content to every local user.
|
|
339
|
+
const logsDir = resolve(subs.HOME, ".flair", "logs");
|
|
340
|
+
try {
|
|
341
|
+
mkdirSync(logsDir, { recursive: true, mode: 0o700 });
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
throw new Error(`could not create the scheduler log directory ${logsDir}: ${err?.message ?? err}. ` +
|
|
345
|
+
`The service manager writes the job's stdout/stderr there; without it the first run dies ` +
|
|
346
|
+
`before producing any output. Fix whatever blocks creating that directory, then re-run ` +
|
|
347
|
+
`\`flair rem nightly enable\`.`);
|
|
348
|
+
}
|
|
349
|
+
const stderrLogPath = resolve(logsDir, "rem-nightly.stderr.log");
|
|
274
350
|
// 1. Deploy the shim (always — both platforms invoke it).
|
|
275
351
|
const shimContents = renderTemplate(readTemplate(templateRoot, "bin/flair-rem-nightly.sh.tmpl"), subs);
|
|
276
352
|
writeFileWithDir(shimPath, shimContents, 0o700);
|
|
@@ -282,12 +358,26 @@ export function enableScheduler(opts) {
|
|
|
282
358
|
writeFileWithDir(plistPath, plistContents, 0o600);
|
|
283
359
|
const loadCommand = ["launchctl", "bootstrap", `gui/${process.getuid?.() ?? ""}`, plistPath];
|
|
284
360
|
let loadResult;
|
|
361
|
+
let firstRun;
|
|
285
362
|
if (!opts.skipLoad) {
|
|
286
363
|
// Bootout first in case a prior install left the job loaded.
|
|
287
364
|
spawnReport(["launchctl", "bootout", `gui/${process.getuid?.() ?? ""}`, plistPath]);
|
|
288
365
|
loadResult = spawnReport(loadCommand);
|
|
366
|
+
if (loadResult.code === 0) {
|
|
367
|
+
// Ordering gate (#1231): verify the first run ONLY after the load
|
|
368
|
+
// exited 0. A load failure is its own failure mode with its own
|
|
369
|
+
// remedy — kickstarting on top of it would blur which actor failed.
|
|
370
|
+
firstRun = verifyFirstRun({
|
|
371
|
+
plat,
|
|
372
|
+
darwinTarget: `gui/${process.getuid?.() ?? ""}/dev.flair.rem.nightly`,
|
|
373
|
+
stderrLogPath,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
289
376
|
}
|
|
290
|
-
return {
|
|
377
|
+
return {
|
|
378
|
+
platform: plat, shimPath, schedulerPath: plistPath, loadCommand, loadResult,
|
|
379
|
+
firstRunVerified: firstRun?.verified === true, firstRun,
|
|
380
|
+
};
|
|
291
381
|
}
|
|
292
382
|
// Linux: systemd user units.
|
|
293
383
|
const timerPath = opts.systemdTimerOverride ?? SYSTEMD_TIMER_PATH;
|
|
@@ -298,11 +388,21 @@ export function enableScheduler(opts) {
|
|
|
298
388
|
writeFileWithDir(timerPath, timerContents, 0o600);
|
|
299
389
|
const loadCommand = ["systemctl", "--user", "enable", "--now", "flair-rem-nightly.timer"];
|
|
300
390
|
let loadResult;
|
|
391
|
+
let firstRun;
|
|
301
392
|
if (!opts.skipLoad) {
|
|
302
393
|
spawnReport(["systemctl", "--user", "daemon-reload"]);
|
|
303
394
|
loadResult = spawnReport(loadCommand);
|
|
395
|
+
if (loadResult.code === 0) {
|
|
396
|
+
// Ordering gate (#1231): only after the load exited 0. Starts the
|
|
397
|
+
// SERVICE unit directly (oneshot ⇒ blocks until the run exits) rather
|
|
398
|
+
// than waiting for the nightly timer to fire.
|
|
399
|
+
firstRun = verifyFirstRun({ plat, linuxServiceUnit: "flair-rem-nightly.service", stderrLogPath });
|
|
400
|
+
}
|
|
304
401
|
}
|
|
305
|
-
return {
|
|
402
|
+
return {
|
|
403
|
+
platform: plat, shimPath, schedulerPath: timerPath, loadCommand, loadResult,
|
|
404
|
+
firstRunVerified: firstRun?.verified === true, firstRun,
|
|
405
|
+
};
|
|
306
406
|
}
|
|
307
407
|
/**
|
|
308
408
|
* Removes the scheduler entry. Audit log + snapshots are preserved.
|
|
@@ -59,9 +59,12 @@ never from the tool arguments (no forging of agentId / authorId).
|
|
|
59
59
|
clientId: ${OAUTH_GITHUB_CLIENT_ID}
|
|
60
60
|
clientSecret: ${OAUTH_GITHUB_CLIENT_SECRET}
|
|
61
61
|
mcp:
|
|
62
|
-
enabled:
|
|
62
|
+
enabled: ${FLAIR_MCP_OAUTH} # whole-token env reference (flair#1152) — the choice lives in the ENVIRONMENT, so a re-packed deploy can't revert it
|
|
63
63
|
issuer: ${FLAIR_MCP_ISSUER} # pin to your public origin — REQUIRED
|
|
64
|
-
resource
|
|
64
|
+
# NO resource key (flair#1180): the plugin derives <issuer>/mcp when it is
|
|
65
|
+
# absent. A composite like ${FLAIR_MCP_ISSUER}/mcp never interpolates
|
|
66
|
+
# (whole-token-only expansion) and fails every connect with
|
|
67
|
+
# invalid_target. Non-standard resource: set an explicit LITERAL URL.
|
|
65
68
|
accessTokenTtl: 900 # 5–15 min (Sherlock req 1) — short-lived
|
|
66
69
|
dynamicClientRegistration:
|
|
67
70
|
enabled: false # DCR is NOT SUPPORTED (flair#756) — explicit, not omitted (an absent block leaves DCR OPEN by the plugin's own default)
|
|
@@ -82,7 +85,11 @@ never from the tool arguments (no forging of agentId / authorId).
|
|
|
82
85
|
turning the surface on.
|
|
83
86
|
|
|
84
87
|
2. **Set the env:**
|
|
85
|
-
- `FLAIR_MCP_OAUTH=
|
|
88
|
+
- `FLAIR_MCP_OAUTH=true` — turns on the `/mcp` route registration AND the
|
|
89
|
+
component AS (flair#1152: `true` is the ONE value both readers accept —
|
|
90
|
+
flair's flag takes 1/true/yes/on, but the component's config read of the
|
|
91
|
+
same var accepts only "true"/"false" and deletes anything else, so `1`
|
|
92
|
+
gives you a guarded `/mcp` with no authorization server behind it).
|
|
86
93
|
- `FLAIR_MCP_ISSUER=https://your-public-origin` (or `FLAIR_PUBLIC_URL`).
|
|
87
94
|
- `FLAIR_MCP_JIT_PROVISION=1` — ONLY if you want unknown subjects
|
|
88
95
|
auto-provisioned (default OFF; pre-provision Agent+Credential otherwise).
|
|
@@ -50,7 +50,7 @@ The target defaults to `https://<cluster>.<org>.harperfabric.com`. Override with
|
|
|
50
50
|
|
|
51
51
|
A fleet-verify table follows. That HTTPS origin is your `FLAIR_URL`.
|
|
52
52
|
|
|
53
|
-
Then set an admin password in Fabric Studio (Cluster Settings → Admin). `flair agent add` against a remote instance requires `--admin-pass` — it will not reuse `~/.flair/admin-pass` or `FLAIR_ADMIN_PASS` from your laptop.
|
|
53
|
+
Then set an admin password in Fabric Studio (Cluster Settings → Admin). `flair agent add` against a remote instance requires an explicit `--admin-pass-file` or `--admin-pass` — it will not reuse `~/.flair/admin-pass` or `FLAIR_ADMIN_PASS` from your laptop.
|
|
54
54
|
|
|
55
55
|
## 3. Register an agent against the remote instance
|
|
56
56
|
|
|
@@ -61,10 +61,15 @@ On Fabric, ops lives on the **same hostname at port 9925**, not the CLI's defaul
|
|
|
61
61
|
```bash
|
|
62
62
|
export FLAIR_URL=https://<cluster>.<org>.harperfabric.com
|
|
63
63
|
|
|
64
|
+
# Keep the Fabric admin password in an owner-only file; the CLI reads it in-process.
|
|
65
|
+
printf '%s\n' '<fabric-admin-password>' > ~/.flair/fabric-admin-pass && chmod 600 ~/.flair/fabric-admin-pass
|
|
66
|
+
|
|
64
67
|
# Fabric ops is :9925 on the same host, not derived :442. docs-freshness-allow: Fabric ops API
|
|
65
|
-
flair agent add mybot --target "$FLAIR_URL" --ops-target https://<cluster>.<org>.harperfabric.com:9925 --admin-pass
|
|
68
|
+
flair agent add mybot --target "$FLAIR_URL" --ops-target https://<cluster>.<org>.harperfabric.com:9925 --admin-pass-file ~/.flair/fabric-admin-pass
|
|
66
69
|
```
|
|
67
70
|
|
|
71
|
+
`--admin-pass-file` reads the file inside the CLI process (mode `0600` enforced), so the password never appears in shell history **or** `ps`. Inline `--admin-pass <pass>` still works but lands in both; the older `--admin-pass "$(cat <path>)"` workaround stays out of history but is still visible to local `ps` while the command runs. Remote `agent add` honors **only** an explicit flag by design — `FLAIR_ADMIN_PASS` and `~/.flair/admin-pass` are this machine's *local* credentials and are never reused against a remote target.
|
|
72
|
+
|
|
68
73
|
```
|
|
69
74
|
Keypair written: ~/.flair/keys/mybot.key
|
|
70
75
|
✅ Agent 'mybot' (mybot) registered (ops: https://<cluster>.<org>.harperfabric.com:9925) <!-- docs-freshness-allow: Fabric ops API -->
|
|
@@ -86,7 +91,41 @@ In Cursor: **Plugins → Configure**
|
|
|
86
91
|
|
|
87
92
|
Those are the two plugin schema fields. Local Cursor's `npx` can use the key from step 3 at `~/.flair/keys/mybot.key`. A cloud agent's `npx` runs on a different machine — that VM needs the key (or host-env admin credentials). See [`packages/cursor-flair/README.md`](../packages/cursor-flair/README.md).
|
|
88
93
|
|
|
89
|
-
## 5.
|
|
94
|
+
## 5. Wire a Grok Bot agent
|
|
95
|
+
|
|
96
|
+
Same connector, different panel. This is the field-verified sequence — including the two places it fails first.
|
|
97
|
+
|
|
98
|
+
**One identity per agent.** Short lowercase ids (`grok-cos` for a Chief-of-Staff agent). Never share an id across agents, and never leave admin credentials as an agent's standing auth — the admin password is for registration, once.
|
|
99
|
+
|
|
100
|
+
1. In the Grok Bot agent's **Tools & MCPs** panel, add the Flair connector (the same `flair-mcp` stdio server the Cursor plugin runs):
|
|
101
|
+
|
|
102
|
+
| Variable | Value |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `FLAIR_URL` | `https://<cluster>.<org>.harperfabric.com` |
|
|
105
|
+
| `FLAIR_AGENT_ID` | `grok-cos` (this agent's own id) |
|
|
106
|
+
|
|
107
|
+
2. Ask for a bootstrap. **The first one returns 401.** That is fail-closed working as designed: the id is not registered yet and the machine has no key. Do not "fix" it by pasting admin credentials into the agent's environment.
|
|
108
|
+
|
|
109
|
+
3. Provide the Fabric admin password through the platform's secret mechanism — Grok Bot's secure secret card — never pasted in chat. It is needed once, for registration only.
|
|
110
|
+
|
|
111
|
+
4. On the machine that runs the MCP process (for Grok Bot, that is the agent VM): Node.js **22 or newer** — the field machine had 20 and had to upgrade before anything else worked. Then:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm i -g @tpsdev-ai/flair
|
|
115
|
+
|
|
116
|
+
# Fabric ops is :9925 on the same host, as in step 3. docs-freshness-allow: Fabric ops API
|
|
117
|
+
flair agent add grok-cos --target https://<cluster>.<org>.harperfabric.com --ops-target https://<cluster>.<org>.harperfabric.com:9925 --admin-pass "$(cat <pass-file>)"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Keep the pass file mode `0600`. Newer releases add `--admin-pass-file <path>`, which reads the file in-process (invisible to `ps`) — check `flair agent add --help` and prefer it when present.
|
|
121
|
+
|
|
122
|
+
5. **Restart the MCP process** if it started before the key existed — it does not pick the key up mid-session. Still 401 with the key on disk? Set the key path explicitly in the agent's MCP env: `FLAIR_KEY_PATH=~/.flair/keys/grok-cos.key`. Known papercut, tracked in [flair#1271](https://github.com/tpsdev-ai/flair/issues/1271).
|
|
123
|
+
|
|
124
|
+
6. Verify: ask the agent to "load my Flair bootstrap". You should get soul + memories **including shared org context** — findings written by teammate agents. A shared-visibility write from this agent is now readable by every org agent.
|
|
125
|
+
|
|
126
|
+
**Account-wide connectors are shared.** A connector added at the Grok Bot account level is one identity used by every agent on that account. For per-agent identity, give each agent its own MCP entry with its own `FLAIR_AGENT_ID` — and register each id (steps 2–5).
|
|
127
|
+
|
|
128
|
+
## 6. Verify
|
|
90
129
|
|
|
91
130
|
```bash
|
|
92
131
|
flair status --target "$FLAIR_URL"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.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",
|
|
@@ -25,4 +25,11 @@ if [ -n "${FLAIR_ADMIN_PASS_FILE:-}" ] && [ -f "${FLAIR_ADMIN_PASS_FILE}" ]; the
|
|
|
25
25
|
set -- "$@" --admin-pass-file "${FLAIR_ADMIN_PASS_FILE}"
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
-
exec
|
|
28
|
+
# Run the CLI under node rather than exec-ing it directly: `node <script>`
|
|
29
|
+
# needs READ permission only, so the shim keeps working when a deploy method
|
|
30
|
+
# (npm-pack tarball extraction) strips the exec bit from the script (#1231).
|
|
31
|
+
# NODE_BIN is an ABSOLUTE path resolved at enable time — the shim performs
|
|
32
|
+
# zero PATH lookups at run time, exactly like the old absolute-FLAIR_BIN
|
|
33
|
+
# form. Do not replace it with a bare `node`: that would hand binary
|
|
34
|
+
# selection to whatever PATH the service manager happens to carry.
|
|
35
|
+
exec "{{NODE_BIN}}" "{{FLAIR_BIN}}" federation sync "$@"
|
|
@@ -5,5 +5,12 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Single line that invokes the CLI's run-once subcommand — the runner module
|
|
7
7
|
# does all the work. Logs land in {{HOME}}/.flair/logs/.
|
|
8
|
+
#
|
|
9
|
+
# Run the CLI under node rather than exec-ing it directly: `node <script>`
|
|
10
|
+
# needs READ permission only, so the shim keeps working when a deploy method
|
|
11
|
+
# (npm-pack tarball extraction) strips the exec bit from the script (#1231).
|
|
12
|
+
# NODE_BIN is an ABSOLUTE path resolved at enable time — the shim performs
|
|
13
|
+
# zero PATH lookups at run time. Do not replace it with a bare `node`: that
|
|
14
|
+
# would hand binary selection to whatever PATH the service manager carries.
|
|
8
15
|
set -e
|
|
9
|
-
exec {{FLAIR_BIN}} rem nightly run-once
|
|
16
|
+
exec "{{NODE_BIN}}" "{{FLAIR_BIN}}" rem nightly run-once
|