@tpsdev-ai/flair 0.42.0 → 0.44.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/dist/cli.js +67 -34
- package/dist/doctor-client.js +14 -2
- package/dist/hook-install.js +1 -1
- package/dist/lib/mcp-enable.js +7 -30
- package/docs/deploying-on-fabric.md +40 -7
- package/docs/hosted-on-fabric.md +30 -1
- package/docs/mcp-clients.md +1 -1
- package/docs/quickstart.md +2 -0
- package/docs/troubleshooting.md +11 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -136,6 +136,7 @@ function shouldShowInlineSecretWarning(optValue, fromEnv, secretFlagNames, flagN
|
|
|
136
136
|
// ─── Defaults ────────────────────────────────────────────────────────────────
|
|
137
137
|
const DEFAULT_PORT = 19926;
|
|
138
138
|
const DEFAULT_OPS_PORT = 19925;
|
|
139
|
+
const FABRIC_OPS_PORT = 9925;
|
|
139
140
|
const DEFAULT_ADMIN_USER = "admin";
|
|
140
141
|
const STARTUP_TIMEOUT_MS = 60_000;
|
|
141
142
|
const HEALTH_POLL_INTERVAL_MS = 500;
|
|
@@ -1076,10 +1077,15 @@ function resolveOpsTarget(opts) {
|
|
|
1076
1077
|
return opts.opsTarget || process.env.FLAIR_OPS_TARGET || undefined;
|
|
1077
1078
|
}
|
|
1078
1079
|
/** Derive the ops API URL from a Flair base URL.
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1080
|
+
* https with effective port 443 (no explicit port, or explicit :443): returns
|
|
1081
|
+
* <host>:9925 (FABRIC_OPS_PORT) — the Fabric managed case where port-1/:442
|
|
1082
|
+
* is a dead-end.
|
|
1083
|
+
* All other cases unchanged:
|
|
1084
|
+
* https with non-443 explicit port → port-1 (self-hosted TLS: 19926→19925, 8443→8442)
|
|
1085
|
+
* http with explicit port → port-1 (19926→19925)
|
|
1086
|
+
* http with no port → DEFAULT_OPS_PORT (19925)
|
|
1087
|
+
* Bare hosts are normalised to https:// (effective-443 → Fabric path).
|
|
1088
|
+
* Throws on unparseable URLs or out-of-range ports.
|
|
1083
1089
|
*/
|
|
1084
1090
|
/** Compute the effective ops API URL for remote commands.
|
|
1085
1091
|
* - If --ops-target is set, use it directly (no derivation).
|
|
@@ -1099,6 +1105,17 @@ function resolveOpsUrlFromTarget(targetUrl) {
|
|
|
1099
1105
|
// Normalise bare hosts: add https:// prefix so URL parser can handle them.
|
|
1100
1106
|
const normalised = targetUrl.includes("://") ? targetUrl : `https://${targetUrl}`;
|
|
1101
1107
|
const url = new URL(normalised);
|
|
1108
|
+
// https target with effective port 443 (no explicit port, or explicit :443):
|
|
1109
|
+
// this is the Fabric managed case — the ops API is on the well-known Fabric
|
|
1110
|
+
// ops port, never REST-adjacent. The port-1 / :442 logic is a dead-end here.
|
|
1111
|
+
if (url.protocol === "https:" && (url.port === "" || url.port === "443")) {
|
|
1112
|
+
url.port = String(FABRIC_OPS_PORT);
|
|
1113
|
+
return url.toString().replace(/\/$/, "");
|
|
1114
|
+
}
|
|
1115
|
+
// All other cases: unchanged port-1 convention.
|
|
1116
|
+
// https with non-443 explicit port → port-1 (self-hosted TLS: 19926→19925, 8443→8442)
|
|
1117
|
+
// http with explicit port → port-1 (19926→19925)
|
|
1118
|
+
// http with no port → DEFAULT_OPS_PORT (19925)
|
|
1102
1119
|
const port = parseInt(url.port, 10);
|
|
1103
1120
|
if (!isNaN(port) && port > 0 && port <= 65535) {
|
|
1104
1121
|
const opsPort = port - 1;
|
|
@@ -1111,13 +1128,8 @@ function resolveOpsUrlFromTarget(targetUrl) {
|
|
|
1111
1128
|
if (url.port !== "" && url.port !== undefined) {
|
|
1112
1129
|
throw new Error(`Invalid target port: ${url.port} (must be 1-65535)`);
|
|
1113
1130
|
}
|
|
1114
|
-
// No explicit port —
|
|
1115
|
-
|
|
1116
|
-
url.port = "442";
|
|
1117
|
-
}
|
|
1118
|
-
else {
|
|
1119
|
-
url.port = String(DEFAULT_OPS_PORT);
|
|
1120
|
-
}
|
|
1131
|
+
// No explicit port on http — use the default ops port.
|
|
1132
|
+
url.port = String(DEFAULT_OPS_PORT);
|
|
1121
1133
|
return url.toString().replace(/\/$/, "");
|
|
1122
1134
|
}
|
|
1123
1135
|
/**
|
|
@@ -1782,12 +1794,21 @@ export async function seedFederationInstanceViaOpsApi(opsPortOrUrl, instanceId,
|
|
|
1782
1794
|
export async function callOpsApi(opsUrl, body, user, pass) {
|
|
1783
1795
|
const url = `${opsUrl.replace(/\/$/, "")}/`;
|
|
1784
1796
|
const auth = Buffer.from(`${user}:${pass}`).toString("base64");
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1797
|
+
let res;
|
|
1798
|
+
try {
|
|
1799
|
+
res = await fetch(url, {
|
|
1800
|
+
method: "POST",
|
|
1801
|
+
headers: { "Content-Type": "application/json", ...(auth ? { Authorization: `Basic ${auth}` } : {}) },
|
|
1802
|
+
body: JSON.stringify(body),
|
|
1803
|
+
signal: AbortSignal.timeout(30_000),
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
catch (err) {
|
|
1807
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1808
|
+
throw new Error(`ops API unreachable at ${opsUrl} (derived from --target). ` +
|
|
1809
|
+
`Set --ops-target or FLAIR_OPS_TARGET to override. ` +
|
|
1810
|
+
`(${message})`);
|
|
1811
|
+
}
|
|
1791
1812
|
if (!res.ok) {
|
|
1792
1813
|
const text = await res.text().catch(() => "");
|
|
1793
1814
|
throw new Error(`Ops API call failed (${res.status}): ${text}`);
|
|
@@ -12085,22 +12106,34 @@ program
|
|
|
12085
12106
|
const hook = inspectSessionStartHook(homedir());
|
|
12086
12107
|
if (hook.present) {
|
|
12087
12108
|
if (hook.execution === "broken") {
|
|
12088
|
-
//
|
|
12089
|
-
//
|
|
12090
|
-
//
|
|
12091
|
-
// cold
|
|
12092
|
-
//
|
|
12093
|
-
//
|
|
12094
|
-
//
|
|
12095
|
-
// the
|
|
12096
|
-
//
|
|
12097
|
-
//
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12109
|
+
// Two very different states that share one probe outcome:
|
|
12110
|
+
//
|
|
12111
|
+
// 1. Silenced (current) command that didn't run — the npx cache
|
|
12112
|
+
// is cold, the machine is offline, or the adapter hasn't been
|
|
12113
|
+
// fetched yet. On a fresh install this is NORMAL: the hook is
|
|
12114
|
+
// wired but no Claude Code session has exercised it yet.
|
|
12115
|
+
// Report as informational, not a warning, and never suggest
|
|
12116
|
+
// reinstall — the setup is correct, the environment just
|
|
12117
|
+
// hasn't warmed yet.
|
|
12118
|
+
//
|
|
12119
|
+
// 2. Unsilenced (legacy) command that didn't run — the hook has
|
|
12120
|
+
// been in place long enough that a cold cache is not the
|
|
12121
|
+
// explanation. This IS a genuine failure: warn and name the
|
|
12122
|
+
// actual state with a fitting remedy.
|
|
12123
|
+
if (hook.silenced) {
|
|
12124
|
+
console.log(` ${render.icons.ok} SessionStart hook: wired in ${render.wrap(render.c.dim, hook.path)} — not yet exercised`);
|
|
12125
|
+
console.log(` ${render.wrap(render.c.dim, hook.detail ?? "")}`);
|
|
12126
|
+
console.log(` ${render.wrap(render.c.dim, "The hook is correctly wired but the adapter has not been fetched yet.")}`);
|
|
12127
|
+
console.log(` ${render.wrap(render.c.dim, "This is normal on a fresh install — the first Claude Code session will warm the npx cache.")}`);
|
|
12128
|
+
}
|
|
12129
|
+
else {
|
|
12130
|
+
console.log(` ${render.icons.warn} SessionStart hook: wired in ${render.wrap(render.c.dim, hook.path)}, but its command did not run just now`);
|
|
12131
|
+
console.log(` ${render.wrap(render.c.dim, hook.detail ?? "")}`);
|
|
12132
|
+
console.log(` ${render.wrap(render.c.dim, "The hook command could not be executed. Check that npx can resolve")}`);
|
|
12133
|
+
console.log(` ${render.wrap(render.c.dim, "@tpsdev-ai/flair-mcp — a cold cache, missing global install, or network")}`);
|
|
12134
|
+
console.log(` ${render.wrap(render.c.dim, "issue can prevent the adapter from running on its first invocation.")}`);
|
|
12135
|
+
console.log(` ${render.wrap(render.c.dim, "Fix:")} flair doctor --fix ${render.wrap(render.c.dim, "(rewrites the hook to the current silent-failure form)")}`);
|
|
12136
|
+
}
|
|
12104
12137
|
}
|
|
12105
12138
|
else if (hook.execution === "unknown") {
|
|
12106
12139
|
console.log(` ${render.icons.warn} SessionStart hook: wired in ${render.wrap(render.c.dim, hook.path)}, but could not be verified ${render.wrap(render.c.dim, `(${hook.detail ?? "no detail"})`)}`);
|
|
@@ -16058,7 +16091,7 @@ if (import.meta.main) {
|
|
|
16058
16091
|
// ─── Exported for testing ─────────────────────────────────────────────────────
|
|
16059
16092
|
export { runCli, resolveKeyPath, buildEd25519Auth, readPortFromConfig, readOpsBindFromConfig, readOpsPortFromConfig, writeConfig, resolveHttpPort, resolveOpsPort, resolveOpsBindHost,
|
|
16060
16093
|
// Harper's own config — the per-instance port record (flair#914)
|
|
16061
|
-
harperConfigPath, readHarperConfig, readPortFromHarperConfig, persistDefaultInstallCoordinates, resolveTarget, resolveOpsTarget, resolveEffectiveOpsUrl, resolveOpsUrlFromTarget, signRequestBody, b64, b64url, program, api, VALID_PRESENCE_ACTIVITIES, MAX_TASK_LENGTH, MAX_WORKSPACE_FIELD_LENGTH, MAX_ORGEVENT_SUMMARY_LENGTH, MAX_ORGEVENT_DETAIL_LENGTH, isLocalBase, isLikelyRealSecret, shouldShowInlineSecretWarning, parseTokenFromFile, resolveLocalAdminPass, readAdminPassFileSecure,
|
|
16094
|
+
harperConfigPath, readHarperConfig, readPortFromHarperConfig, persistDefaultInstallCoordinates, resolveTarget, resolveOpsTarget, resolveEffectiveOpsUrl, resolveOpsUrlFromTarget, FABRIC_OPS_PORT, signRequestBody, b64, b64url, program, api, VALID_PRESENCE_ACTIVITIES, MAX_TASK_LENGTH, MAX_WORKSPACE_FIELD_LENGTH, MAX_ORGEVENT_SUMMARY_LENGTH, MAX_ORGEVENT_DETAIL_LENGTH, isLocalBase, isLikelyRealSecret, shouldShowInlineSecretWarning, parseTokenFromFile, resolveLocalAdminPass, readAdminPassFileSecure,
|
|
16062
16095
|
// launchd label (flair#693)
|
|
16063
16096
|
LEGACY_LAUNCHD_LABEL, launchdLabel, launchdPlistPath, cleanupLegacyLaunchdPlist, resolveLaunchdLabel, migrateLegacyLaunchdLabel, ensureLaunchdServiceLoaded,
|
|
16064
16097
|
// launchd management observation (flair#1022)
|
package/dist/doctor-client.js
CHANGED
|
@@ -41,7 +41,7 @@ export const SESSION_START_HOOK_MARKER = "flair-session-start";
|
|
|
41
41
|
//
|
|
42
42
|
// WHY THE INVOCATION IS WRAPPED
|
|
43
43
|
// -----------------------------
|
|
44
|
-
// The hook runs `npx -y @tpsdev-ai/flair-mcp flair-session-start`: it resolves
|
|
44
|
+
// The hook runs `npx -y -p @tpsdev-ai/flair-mcp flair-session-start`: it resolves
|
|
45
45
|
// a package binary through whatever Node runtime the user's shell happens to
|
|
46
46
|
// expose. Under a Node version manager, globally installed packages are
|
|
47
47
|
// per-runtime-version, so a routine and entirely unrelated runtime upgrade
|
|
@@ -117,7 +117,7 @@ export function buildSessionStartHookCommand(agentId, flairUrl) {
|
|
|
117
117
|
throw new Error(`Flair URL '${flairUrl}' contains characters that cannot be safely written into a shell hook command (allowed: letters, digits, . _ : / -)`);
|
|
118
118
|
}
|
|
119
119
|
const env = flairUrl ? `FLAIR_AGENT_ID=${agentId} FLAIR_URL=${flairUrl}` : `FLAIR_AGENT_ID=${agentId}`;
|
|
120
|
-
const invocation = `${env} npx -y @tpsdev-ai/flair-mcp ${SESSION_START_HOOK_MARKER}`;
|
|
120
|
+
const invocation = `${env} npx -y -p @tpsdev-ai/flair-mcp ${SESSION_START_HOOK_MARKER}`;
|
|
121
121
|
return `sh -c 'out=$(${invocation} 2>/dev/null) && printf %s "$out" || true'`;
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
@@ -525,6 +525,18 @@ export function inspectSessionStartHook(homeDir, opts = {}) {
|
|
|
525
525
|
const verdict = classifyHookProbe(outcome);
|
|
526
526
|
return { path: found.path, present: true, command, ours, silenced, upgradable, execution: verdict.execution, detail: verdict.detail };
|
|
527
527
|
}
|
|
528
|
+
export function classifyHookReadiness(report) {
|
|
529
|
+
if (!report.present)
|
|
530
|
+
return "absent";
|
|
531
|
+
if (!report.ours)
|
|
532
|
+
return "custom";
|
|
533
|
+
if (report.execution === "runs")
|
|
534
|
+
return "runs";
|
|
535
|
+
if (report.execution === "broken") {
|
|
536
|
+
return report.silenced ? "not-yet-exercised" : "genuinely-broken";
|
|
537
|
+
}
|
|
538
|
+
return "unverified";
|
|
539
|
+
}
|
|
528
540
|
/**
|
|
529
541
|
* Rewrite an existing Flair-authored hook command to the current canonical
|
|
530
542
|
* form, in place, preserving the agent id and URL the entry already carries —
|
package/dist/hook-install.js
CHANGED
|
@@ -344,7 +344,7 @@ export function hookStatus(homeDir, harness) {
|
|
|
344
344
|
}
|
|
345
345
|
const hookEntry = config.hooks.SessionStart[existing.groupIndex].hooks[existing.hookIndex];
|
|
346
346
|
const command = typeof hookEntry?.command === "string" ? hookEntry.command : "";
|
|
347
|
-
const correctShape = hookEntry?.type === "command" && command.includes(`npx -y @tpsdev-ai/flair-mcp ${SESSION_START_HOOK_MARKER}`);
|
|
347
|
+
const correctShape = hookEntry?.type === "command" && command.includes(`npx -y -p @tpsdev-ai/flair-mcp ${SESSION_START_HOOK_MARKER}`);
|
|
348
348
|
const env = parseHookCommandEnv(command);
|
|
349
349
|
return {
|
|
350
350
|
harness, path, wired: true, correctShape,
|
package/dist/lib/mcp-enable.js
CHANGED
|
@@ -286,10 +286,12 @@ export function readSigningKeyFile(path) {
|
|
|
286
286
|
/**
|
|
287
287
|
* The `@harperfast/oauth` config block, matching the installed 2.2.0
|
|
288
288
|
* package's field names (node_modules/@harperfast/oauth/dist/types.d.ts).
|
|
289
|
-
* Secrets are `${ENV_VAR}` placeholders — never literal values
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
289
|
+
* Secrets are `${ENV_VAR}` placeholders — never literal values.
|
|
290
|
+
*
|
|
291
|
+
* flair#1136: set_configuration delivery was removed. Fabric regenerates
|
|
292
|
+
* the root harperdb-config.yaml; the component's own config.yaml is the
|
|
293
|
+
* source of truth for the oauth block. This function builds the block that
|
|
294
|
+
* ships in config.yaml — it is never written to harperdb-config.yaml.
|
|
293
295
|
*
|
|
294
296
|
* flair#756: `dynamicClientRegistration: { enabled: false }` is written
|
|
295
297
|
* EXPLICITLY — never omitted. See the module header's "Leaving
|
|
@@ -634,32 +636,7 @@ export async function provisionIdpIdentityMapping(params, deps = {}) {
|
|
|
634
636
|
}
|
|
635
637
|
return { principalCreated, credentialId, credentialReused: Boolean(existing) };
|
|
636
638
|
}
|
|
637
|
-
|
|
638
|
-
* (whole-process restart) — the genuine Harper Operations API operations
|
|
639
|
-
* this module's header documents. Throws on either non-2xx response. */
|
|
640
|
-
export async function applyRemoteConfigAndRestart(params, deps = {}) {
|
|
641
|
-
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
642
|
-
const opsUrl = opsBaseUrl(params.opsPortOrUrl);
|
|
643
|
-
const authHeader = basicAuthHeader(params.adminUser, params.adminPass);
|
|
644
|
-
const setRes = await fetchImpl(opsUrl, {
|
|
645
|
-
method: "POST",
|
|
646
|
-
headers: { "Content-Type": "application/json", Authorization: authHeader },
|
|
647
|
-
body: JSON.stringify({ operation: "set_configuration", ...params.configBlock }),
|
|
648
|
-
});
|
|
649
|
-
if (!setRes.ok) {
|
|
650
|
-
const text = await setRes.text().catch(() => "");
|
|
651
|
-
throw new Error(`set_configuration failed (HTTP ${setRes.status}): ${text}`);
|
|
652
|
-
}
|
|
653
|
-
const restartRes = await fetchImpl(opsUrl, {
|
|
654
|
-
method: "POST",
|
|
655
|
-
headers: { "Content-Type": "application/json", Authorization: authHeader },
|
|
656
|
-
body: JSON.stringify({ operation: "restart" }),
|
|
657
|
-
});
|
|
658
|
-
if (!restartRes.ok) {
|
|
659
|
-
const text = await restartRes.text().catch(() => "");
|
|
660
|
-
throw new Error(`restart failed (HTTP ${restartRes.status}): ${text}`);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
639
|
+
// ─── Restart only ────────────────────────────────────────────────────────────
|
|
663
640
|
/** `restart` only — used by `disableMcp` (flag off + restart, no config
|
|
664
641
|
* rewrite: the `@harperfast/oauth` config block is left in place; it is
|
|
665
642
|
* inert whenever `FLAIR_MCP_OAUTH` is unset, per the byte-identical-boot
|
|
@@ -129,14 +129,17 @@ where nothing answers:
|
|
|
129
129
|
# --target https://<fabric-node>:19926/<instance> → ops derived as :19925 ✓
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
**Fabric's ops API runs on the same hostname at port 9925** <!-- docs-freshness-allow: Fabric ops API port, not legacy data port --> (the deploy/upgrade path already targets this port). For a managed `*.harperfabric.com` instance:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Same hostname, port 9925 — not port 442 <!-- docs-freshness-allow: Fabric ops API -->
|
|
136
|
+
flair init --target https://<cluster>.<org>.harperfabric.com \
|
|
137
|
+
--ops-target https://<cluster>.<org>.harperfabric.com:9925 <!-- docs-freshness-allow: Fabric ops API -->
|
|
138
|
+
```
|
|
139
|
+
|
|
132
140
|
**Pass `--ops-target <url>` explicitly** (or set `FLAIR_OPS_TARGET`) on any command that
|
|
133
141
|
touches the ops API: `init --target`, `agent add --target`, `federation token --target`.
|
|
134
142
|
|
|
135
|
-
> **Gap — needs a Fabric account to verify.** This guide does not state the correct
|
|
136
|
-
> ops-API URL for a managed `*.harperfabric.com` instance, or whether the ops API is
|
|
137
|
-
> reachable remotely there at all. The derivation above is certain (read from source);
|
|
138
|
-
> the right value to pass is not.
|
|
139
|
-
|
|
140
143
|
Precedence: `--target` > `--url` > `FLAIR_TARGET` > `FLAIR_URL` > localhost. For ops:
|
|
141
144
|
`--ops-target` > `FLAIR_OPS_TARGET` > derived > localhost.
|
|
142
145
|
|
|
@@ -250,8 +253,9 @@ and shells out to `lsof`. The command you'd reach for when something breaks is u
|
|
|
250
253
|
here. Unavailable too: `start`, `stop`, `restart`, `snapshot`, `reembed`, `rem`, `bridge`.
|
|
251
254
|
|
|
252
255
|
**Fabric's own cluster topology is invisible.** `fleet verify` sweeps *Flair's* federation
|
|
253
|
-
peer table, not Harper's cluster nodes
|
|
254
|
-
|
|
256
|
+
peer table, not Harper's cluster nodes. **`cluster_status` works on Fabric** — Fabric
|
|
257
|
+
always runs harper-pro (not the OSS harper build), so cluster_status is available over
|
|
258
|
+
the ops API. `0 peers known` means "0 on file", never "0 exist."
|
|
255
259
|
|
|
256
260
|
**There is no disk or quota telemetry.** `flair status` reports usage for two directories:
|
|
257
261
|
no free space, no total, no quota, no warning threshold, walk capped at six levels, no
|
|
@@ -263,6 +267,35 @@ with nothing saying so. The one indirect signal is a migration halting for space
|
|
|
263
267
|
> wouldn't matter: Flair calls it only as a post-failure convergence oracle and discards
|
|
264
268
|
> the `size` field. Component disk usage is invisible structurally.
|
|
265
269
|
|
|
270
|
+
### The `mcp.enabled` operator step
|
|
271
|
+
|
|
272
|
+
MCP is **off by default**. The shipped component `config.yaml` contains
|
|
273
|
+
`@harperfast/oauth` → `mcp` → `enabled: false`. Until [flair#1152](https://github.com/tpsdev-ai/flair/issues/1152)
|
|
274
|
+
lands (interpolate from env — *ON HOLD*), you must flip this manually:
|
|
275
|
+
|
|
276
|
+
1. In your deployed component's `config.yaml`, change:
|
|
277
|
+
```yaml
|
|
278
|
+
'@harperfast/oauth':
|
|
279
|
+
mcp:
|
|
280
|
+
enabled: true # was: false
|
|
281
|
+
```
|
|
282
|
+
2. Re-deploy the component so Harper picks up the new value.
|
|
283
|
+
3. **Verify the `/mcp` surface is actually serving** (the flag alone does not
|
|
284
|
+
guarantee it — a secret that is stored but never decrypted fails at self-verify):
|
|
285
|
+
```bash
|
|
286
|
+
# Check /mcp is reachable and returning MCP protocol (not a loopback proxy or 404)
|
|
287
|
+
curl -sf https://\<cluster\>.\<org\>.harperfabric.com/mcp
|
|
288
|
+
# Should return MCP JSON-RPC content; if you get HTML redirect or 404 the flag
|
|
289
|
+
# is not effective
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**⚠ SECURITY CAVEAT — the upgrade-reverts trap.** Any package update or fleet component
|
|
293
|
+
update re-ships the literal `enabled: false` and silently darkens a live `/mcp` surface.
|
|
294
|
+
You must **re-flip to `true` after every upgrade** and re-deploy. An updated component
|
|
295
|
+
without this re-flip will appear healthy (`/Health` green) while its MCP tools are
|
|
296
|
+
dark to every connected client. If you rely on MCP, add the re-flip to your upgrade
|
|
297
|
+
runbook.
|
|
298
|
+
|
|
266
299
|
### Known hazard: unbounded npm cache
|
|
267
300
|
|
|
268
301
|
**Open — [flair#886](https://github.com/tpsdev-ai/flair/issues/886).** Every deploy runs a
|
package/docs/hosted-on-fabric.md
CHANGED
|
@@ -79,6 +79,35 @@ The staging file is written in every case, so a fallback never strands you mid-r
|
|
|
79
79
|
|
|
80
80
|
`--secrets-mechanism <fabric-env-secrets|env-file>` remains an explicit override and skips the probe entirely.
|
|
81
81
|
|
|
82
|
+
### The `mcp.enabled` operator step (Fabric)
|
|
83
|
+
|
|
84
|
+
MCP is **off by default**. The shipped component `config.yaml` contains
|
|
85
|
+
`@harperfast/oauth` → `mcp` → `enabled: false`. Until [flair#1152](https://github.com/tpsdev-ai/flair/issues/1152)
|
|
86
|
+
lands (interpolate from env — *ON HOLD*), you must flip this manually:
|
|
87
|
+
|
|
88
|
+
1. In your deployed component's `config.yaml`, change:
|
|
89
|
+
```yaml
|
|
90
|
+
'@harperfast/oauth':
|
|
91
|
+
mcp:
|
|
92
|
+
enabled: true # was: false
|
|
93
|
+
```
|
|
94
|
+
2. Re-deploy the component so Harper picks up the new value.
|
|
95
|
+
3. **Verify the `/mcp` surface is actually serving** (the flag alone does not
|
|
96
|
+
guarantee it — a secret that is stored but never decrypted fails at self-verify):
|
|
97
|
+
```bash
|
|
98
|
+
# Check /mcp is reachable and returning MCP protocol (not a loopback proxy or 404)
|
|
99
|
+
curl -sf https://<cluster>.<org>.harperfabric.com/mcp
|
|
100
|
+
# Should return MCP JSON-RPC content; if you get HTML redirect or 404 the flag
|
|
101
|
+
# is not effective
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**⚠ SECURITY CAVEAT — the upgrade-reverts trap.** Any package update or fleet component
|
|
105
|
+
update re-ships the literal `enabled: false` and silently darkens a live `/mcp` surface.
|
|
106
|
+
You must **re-flip to `true` after every upgrade** and re-deploy. An updated component
|
|
107
|
+
without this re-flip will appear healthy (`/Health` green) while its MCP tools are
|
|
108
|
+
dark to every connected client. If you rely on MCP, add the re-flip to your upgrade
|
|
109
|
+
runbook.
|
|
110
|
+
|
|
82
111
|
---
|
|
83
112
|
|
|
84
113
|
## Agent authentication
|
|
@@ -140,7 +169,7 @@ flair fleet verify --target https://<cluster>.<org>.harperfabric.com
|
|
|
140
169
|
|
|
141
170
|
**`flair doctor`** takes no `--target` — it hardcodes localhost, reads a local PID file, and shells out to `lsof`. Unavailable too: `start`, `stop`, `restart`, `snapshot`, `reembed`, `rem`, `bridge`.
|
|
142
171
|
|
|
143
|
-
**Fabric's own cluster topology is invisible.** `fleet verify` sweeps *Flair's* federation peer table, not Harper's cluster nodes.
|
|
172
|
+
**Fabric's own cluster topology is invisible.** `fleet verify` sweeps *Flair's* federation peer table, not Harper's cluster nodes. **`cluster_status` works on Fabric** — Fabric always runs harper-pro (not the OSS harper build), so cluster_status is available over the ops API. `0 peers known` means "0 on file", never "0 exist."
|
|
144
173
|
|
|
145
174
|
---
|
|
146
175
|
|
package/docs/mcp-clients.md
CHANGED
|
@@ -109,7 +109,7 @@ Or wire it by hand — add a `SessionStart` hook to `~/.claude/settings.json`:
|
|
|
109
109
|
"hooks": [
|
|
110
110
|
{
|
|
111
111
|
"type": "command",
|
|
112
|
-
"command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'"
|
|
112
|
+
"command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y -p @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'"
|
|
113
113
|
}
|
|
114
114
|
]
|
|
115
115
|
}
|
package/docs/quickstart.md
CHANGED
|
@@ -154,6 +154,8 @@ flair search --agent local "native addon loading in sandboxed runtimes"
|
|
|
154
154
|
|
|
155
155
|
You searched for a concept, not the keywords. The line under each hit is its creation date, durability tier, and rank score.
|
|
156
156
|
|
|
157
|
+
> **When stdout is not a terminal** — piped to another command, captured in a script, or run in CI — the same `flair search` command emits a JSON array instead of the formatted prose above. Each hit is an object with `id`, `text`, `createdAt`, `durability`, and `_score` fields. Add `--explain` to include an `_explain` ranking breakdown on each hit. Use `flair search --json` to force JSON output even in a terminal, or `flair memory search` for the raw JSON form in all contexts.
|
|
158
|
+
|
|
157
159
|
> The percentage is a **rank-fusion score, not a similarity**. It is normalized so the top result is always near 100%. Read it as ordering within these results, never as confidence that the match is good.
|
|
158
160
|
|
|
159
161
|
Add `--explain` to see the ranking inputs per hit — the raw score, the composite score under `--scoring composite`, and the record's durability, age and usage count. When output is JSON (`--json`, or any time stdout is not a terminal) the same breakdown arrives as an `_explain` object on each hit, so scripts get it too. Use `--limit`, `--tag`, `--since 7d` to narrow the search. `flair memory search` runs the same query but always prints raw JSON — use it when piping to a script.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -23,7 +23,8 @@ If it fails to start:
|
|
|
23
23
|
lsof -i :19926
|
|
24
24
|
|
|
25
25
|
# Check logs (macOS)
|
|
26
|
-
cat ~/.flair/data/log/hdb.log | tail -50
|
|
26
|
+
cat ~/.flair/data/log/hdb.log | tail -50 # Harper ≤ 5.1 only (see below)
|
|
27
|
+
cat ~/.flair/data/log/system.log | tail -50 # Harper 5.2+ (live log)
|
|
27
28
|
|
|
28
29
|
# Check logs (Linux)
|
|
29
30
|
journalctl --user -u flair --since "10 minutes ago"
|
|
@@ -242,6 +243,14 @@ flair --help # all commands
|
|
|
242
243
|
flair <command> -h # command-specific help
|
|
243
244
|
```
|
|
244
245
|
|
|
245
|
-
|
|
246
|
+
**Log paths depend on Harper version:**
|
|
247
|
+
|
|
248
|
+
- **Harper ≤ 5.1:** `~/.flair/data/log/hdb.log`
|
|
249
|
+
- **Harper 5.2+:** `~/.flair/data/log/system.log`
|
|
250
|
+
|
|
251
|
+
> ⚠ **On Harper 5.2+, `hdb.log` freezes at the upgrade boundary.** The live log is
|
|
252
|
+
> `system.log`. The Harper ops `read_log` endpoint keeps serving the frozen `hdb.log`,
|
|
253
|
+
> so remote diagnosis reads days-stale entries that look current. Always check
|
|
254
|
+
> `system.log` after upgrading to 5.2+.
|
|
246
255
|
|
|
247
256
|
File issues: [github.com/tpsdev-ai/flair/issues](https://github.com/tpsdev-ai/flair/issues)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.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",
|