@tpsdev-ai/flair-mcp 0.32.0 → 0.34.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.
@@ -26,6 +26,23 @@
26
26
  * A hard timeout (FLAIR_HOOK_TIMEOUT_MS, default 8s) wraps the bootstrap call
27
27
  * so a stalled Flair daemon can't hang session startup; on timeout we no-op.
28
28
  *
29
+ * That guarantee covers everything from the moment this binary starts running.
30
+ * It cannot cover the case where the binary never runs at all — an `npx`
31
+ * invocation that stops resolving after a Node runtime change (flair#1007) —
32
+ * because the guard would be behind the door it is meant to guard. That half
33
+ * is owned by the command string registered in settings.json; see
34
+ * buildSessionStartHookCommand in the CLI's src/doctor-client.ts.
35
+ *
36
+ * PROBE MODE (flair#1007)
37
+ * ----------------------
38
+ * `flair doctor` needs to answer "does the command registered in settings.json
39
+ * still resolve and execute?" — the check whose absence turned an orphaned
40
+ * shim into an opaque, unattributed error on every session. Setting
41
+ * FLAIR_HOOK_PROBE makes this binary answer that and nothing else: it prints
42
+ * its inert output and exits immediately, before reading stdin, constructing a
43
+ * client, touching the network or writing presence. Being reached at all IS
44
+ * the answer.
45
+ *
29
46
  * AUTO-PRESENCE (flair#598)
30
47
  * -------------------------
31
48
  * A session starting is the clearest "this agent is alive" signal flair-mcp
@@ -43,13 +60,15 @@
43
60
  * FLAIR_KEY_PATH (default ~/.flair/keys/<agent>.key via flair-client)
44
61
  * FLAIR_HOOK_TIMEOUT_MS (default 8000; clamped 500..30000)
45
62
  * FLAIR_PRESENCE_TIMEOUT_MS (default 3000; clamped 500..10000 — see ./presence.ts)
63
+ * FLAIR_HOOK_PROBE (unset by default — see PROBE MODE above)
46
64
  *
47
- * USAGE — register in ~/.claude/settings.json:
65
+ * USAGE — register with `flair hook install`, or by hand in
66
+ * ~/.claude/settings.json:
48
67
  * {
49
68
  * "hooks": {
50
69
  * "SessionStart": [
51
70
  * { "hooks": [ { "type": "command",
52
- * "command": "FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start" } ] }
71
+ * "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'" } ] }
53
72
  * ]
54
73
  * }
55
74
  * }
@@ -70,6 +89,12 @@ interface BootstrapClient extends Partial<PresencePoster> {
70
89
  context?: string;
71
90
  } | undefined>;
72
91
  }
92
+ /**
93
+ * Probe mode (flair#1007) — see the module doc. Any non-empty value other
94
+ * than "0" enables it, so `FLAIR_HOOK_PROBE=1` and `FLAIR_HOOK_PROBE=true`
95
+ * both work and an accidentally-empty variable does not.
96
+ */
97
+ export declare function isProbeMode(env?: Record<string, string | undefined>): boolean;
73
98
  /**
74
99
  * Core hook logic, with injectable dependencies so it can be unit-tested
75
100
  * without a live Flair daemon. Returns the exact string to print to stdout.
@@ -26,6 +26,23 @@
26
26
  * A hard timeout (FLAIR_HOOK_TIMEOUT_MS, default 8s) wraps the bootstrap call
27
27
  * so a stalled Flair daemon can't hang session startup; on timeout we no-op.
28
28
  *
29
+ * That guarantee covers everything from the moment this binary starts running.
30
+ * It cannot cover the case where the binary never runs at all — an `npx`
31
+ * invocation that stops resolving after a Node runtime change (flair#1007) —
32
+ * because the guard would be behind the door it is meant to guard. That half
33
+ * is owned by the command string registered in settings.json; see
34
+ * buildSessionStartHookCommand in the CLI's src/doctor-client.ts.
35
+ *
36
+ * PROBE MODE (flair#1007)
37
+ * ----------------------
38
+ * `flair doctor` needs to answer "does the command registered in settings.json
39
+ * still resolve and execute?" — the check whose absence turned an orphaned
40
+ * shim into an opaque, unattributed error on every session. Setting
41
+ * FLAIR_HOOK_PROBE makes this binary answer that and nothing else: it prints
42
+ * its inert output and exits immediately, before reading stdin, constructing a
43
+ * client, touching the network or writing presence. Being reached at all IS
44
+ * the answer.
45
+ *
29
46
  * AUTO-PRESENCE (flair#598)
30
47
  * -------------------------
31
48
  * A session starting is the clearest "this agent is alive" signal flair-mcp
@@ -43,13 +60,15 @@
43
60
  * FLAIR_KEY_PATH (default ~/.flair/keys/<agent>.key via flair-client)
44
61
  * FLAIR_HOOK_TIMEOUT_MS (default 8000; clamped 500..30000)
45
62
  * FLAIR_PRESENCE_TIMEOUT_MS (default 3000; clamped 500..10000 — see ./presence.ts)
63
+ * FLAIR_HOOK_PROBE (unset by default — see PROBE MODE above)
46
64
  *
47
- * USAGE — register in ~/.claude/settings.json:
65
+ * USAGE — register with `flair hook install`, or by hand in
66
+ * ~/.claude/settings.json:
48
67
  * {
49
68
  * "hooks": {
50
69
  * "SessionStart": [
51
70
  * { "hooks": [ { "type": "command",
52
- * "command": "FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start" } ] }
71
+ * "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'" } ] }
53
72
  * ]
54
73
  * }
55
74
  * }
@@ -67,6 +86,15 @@ const TIMEOUT_FLOOR_MS = 500;
67
86
  const TIMEOUT_CEILING_MS = 30_000;
68
87
  /** Empty, inert hook output. Printing this is always a safe no-op. */
69
88
  const NOOP_OUTPUT = "{}";
89
+ /**
90
+ * Probe mode (flair#1007) — see the module doc. Any non-empty value other
91
+ * than "0" enables it, so `FLAIR_HOOK_PROBE=1` and `FLAIR_HOOK_PROBE=true`
92
+ * both work and an accidentally-empty variable does not.
93
+ */
94
+ export function isProbeMode(env = process.env) {
95
+ const raw = env.FLAIR_HOOK_PROBE;
96
+ return typeof raw === "string" && raw !== "" && raw !== "0";
97
+ }
70
98
  /** Resolve the bootstrap timeout from env, clamped to a sane range. */
71
99
  function resolveTimeoutMs() {
72
100
  const raw = process.env.FLAIR_HOOK_TIMEOUT_MS;
@@ -179,6 +207,13 @@ function defaultClientFactory(agentId) {
179
207
  /** Entry point. Reads stdin, runs the hook, prints the result, exits 0.
180
208
  * Wrapped so that even an unexpected throw degrades to a no-op. */
181
209
  async function main() {
210
+ // Probe mode short-circuits BEFORE readStdin() — reaching this line is the
211
+ // entire answer doctor is looking for, and a probe must cost nothing and
212
+ // change nothing (no stdin wait, no client, no bootstrap, no presence).
213
+ if (isProbeMode()) {
214
+ process.stdout.write(NOOP_OUTPUT);
215
+ return;
216
+ }
182
217
  let output = NOOP_OUTPUT;
183
218
  try {
184
219
  output = await runHook(await readStdin());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair-mcp",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "MCP server for Flair — persistent memory for Claude Code, Cursor, and any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@modelcontextprotocol/sdk": "1.27.1",
30
- "@tpsdev-ai/flair-client": "0.32.0",
30
+ "@tpsdev-ai/flair-client": "0.34.0",
31
31
  "zod": "4.3.6"
32
32
  },
33
33
  "license": "Apache-2.0",