@tpsdev-ai/flair 0.52.0 → 0.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +4 -1
  2. package/dist/build-info.json +3 -3
  3. package/dist/cli.js +1914 -15328
  4. package/dist/commands/agent.js +453 -0
  5. package/dist/commands/attention.js +121 -0
  6. package/dist/commands/backup.js +115 -0
  7. package/dist/commands/bootstrap.js +91 -0
  8. package/dist/commands/bridge.js +608 -0
  9. package/dist/commands/deploy.js +180 -0
  10. package/dist/commands/doctor.js +1654 -0
  11. package/dist/commands/export.js +110 -0
  12. package/dist/commands/federation.js +1575 -0
  13. package/dist/commands/fleet.js +73 -0
  14. package/dist/commands/grant.js +109 -0
  15. package/dist/commands/hook.js +193 -0
  16. package/dist/commands/idp.js +193 -0
  17. package/dist/commands/import.js +134 -0
  18. package/dist/commands/init.js +1203 -0
  19. package/dist/commands/inspect.js +45 -0
  20. package/dist/commands/keys.js +187 -0
  21. package/dist/commands/mcp.js +707 -0
  22. package/dist/commands/memory.js +501 -0
  23. package/dist/commands/migrate-harness-memory.js +270 -0
  24. package/dist/commands/orgevent.js +138 -0
  25. package/dist/commands/presence.js +76 -0
  26. package/dist/commands/principal.js +338 -0
  27. package/dist/commands/quality.js +1164 -0
  28. package/dist/commands/reembed.js +296 -0
  29. package/dist/commands/relationship.js +76 -0
  30. package/dist/commands/rem.js +1048 -0
  31. package/dist/commands/restore.js +130 -0
  32. package/dist/commands/search.js +244 -0
  33. package/dist/commands/service.js +315 -0
  34. package/dist/commands/session.js +184 -0
  35. package/dist/commands/soul.js +155 -0
  36. package/dist/commands/status.js +914 -0
  37. package/dist/commands/test.js +93 -0
  38. package/dist/commands/uninstall.js +143 -0
  39. package/dist/commands/upgrade.js +1592 -0
  40. package/dist/commands/workspace.js +114 -0
  41. package/dist/deploy.js +24 -0
  42. package/dist/fabric-npm-install.js +87 -0
  43. package/dist/federation-verify.js +498 -0
  44. package/dist/fleet-verify.js +144 -21
  45. package/dist/install/clients.js +167 -0
  46. package/dist/lib/auth-resolve.js +76 -1
  47. package/dist/lib/daemon-liveness.js +131 -2
  48. package/dist/lib/doctor-config-path.js +61 -0
  49. package/dist/lib/doctor-federation-driver.js +189 -0
  50. package/dist/lib/doctor-run.js +40 -0
  51. package/dist/lib/entity-vocab-cli.js +3 -3
  52. package/dist/lib/federation-pair-identity.js +47 -0
  53. package/dist/lib/launchd-repair.js +199 -0
  54. package/dist/lib/ops-api-bind.js +115 -0
  55. package/dist/lib/owned-pins.js +219 -0
  56. package/dist/lib/stabilize-mqtt-network.js +123 -0
  57. package/dist/lib/uninstall-purge.js +218 -0
  58. package/dist/rem/restore.js +8 -10
  59. package/dist/resources/AgentReadPosition.js +74 -0
  60. package/dist/resources/Federation.js +8 -2
  61. package/dist/resources/Memory.js +4 -3
  62. package/dist/resources/MemoryBootstrap.js +41 -25
  63. package/dist/resources/MemoryCandidate.js +5 -6
  64. package/dist/resources/OrgEventCatchup.js +126 -47
  65. package/dist/resources/agent-read-position-lib.js +83 -0
  66. package/dist/resources/agent-read-position.js +120 -0
  67. package/dist/resources/embeddings-boot.js +32 -0
  68. package/dist/resources/federation-peer-liveness.js +73 -0
  69. package/dist/resources/health.js +68 -19
  70. package/dist/resources/mcp-tools.js +43 -279
  71. package/dist/resources/memory-visibility.js +3 -3
  72. package/dist/resources/migration-boot.js +59 -18
  73. package/dist/resources/migrations/embedding-stamp.js +20 -1
  74. package/dist/resources/migrations/recheck.js +43 -0
  75. package/dist/resources/migrations/runner.js +6 -1
  76. package/dist/resources/migrations/stamp-outstanding.js +171 -0
  77. package/dist/resources/migrations/visibility-backfill.js +2 -2
  78. package/dist/resources/org-event-catchup-lib.js +47 -0
  79. package/dist/resources/record-owner-guard.js +1 -0
  80. package/dist/stamp-migration-verify.js +163 -0
  81. package/dist/stamp-outstanding.js +144 -0
  82. package/docs/api-reference.md +4 -2
  83. package/docs/deploying-on-fabric.md +11 -10
  84. package/docs/deployment.md +3 -1
  85. package/docs/federation.md +19 -0
  86. package/docs/hosted-on-fabric.md +3 -3
  87. package/docs/quickstart.md +2 -1
  88. package/docs/releasing.md +15 -7
  89. package/docs/spoke-bringup.md +10 -5
  90. package/docs/standalone-local.md +3 -1
  91. package/docs/upgrade.md +25 -6
  92. package/node_modules/@tpsdev-ai/flair-tool-descriptors/LICENSE +19 -0
  93. package/node_modules/@tpsdev-ai/flair-tool-descriptors/README.md +22 -0
  94. package/node_modules/@tpsdev-ai/flair-tool-descriptors/dist/index.d.ts +70 -0
  95. package/node_modules/@tpsdev-ai/flair-tool-descriptors/dist/index.js +665 -0
  96. package/node_modules/@tpsdev-ai/flair-tool-descriptors/package.json +46 -0
  97. package/package.json +9 -4
  98. package/schemas/agent.graphql +15 -0
  99. package/templates/launchd/start-flair-with-admin-pass.sh +73 -0
@@ -0,0 +1,93 @@
1
+ import * as render from "../render.js";
2
+ let cli;
3
+ /** Bind the cli-locals this module depends on. */
4
+ export function bindCli(fns) {
5
+ cli = fns;
6
+ }
7
+ function api(...args) {
8
+ return cli.api(...args);
9
+ }
10
+ function resolveBaseUrl(...args) {
11
+ return cli.resolveBaseUrl(...args);
12
+ }
13
+ export function register(program) {
14
+ // ─── flair test ───────────────────────────────────────────────────────────────
15
+ program
16
+ .command("test")
17
+ .description("Verify the full Flair stack: write, search, and delete a test memory")
18
+ .option("--agent <id>", "Agent ID (or set FLAIR_AGENT_ID env)")
19
+ .option("--port <port>", "Harper HTTP port")
20
+ .action(async (opts) => {
21
+ const agentId = opts.agent ?? process.env.FLAIR_AGENT_ID;
22
+ if (!agentId && !process.env.FLAIR_ADMIN_PASS) {
23
+ console.error(`${render.icons.error} ${render.wrap(render.c.red, "set --agent / FLAIR_AGENT_ID or FLAIR_ADMIN_PASS")}`);
24
+ process.exit(1);
25
+ }
26
+ // Single source of truth (flair#1351): banner prints the URL the test's
27
+ // own client uses. resolveBaseUrl is the existing CLI resolver; pass the
28
+ // same value through to api() so the two cannot diverge.
29
+ const baseUrl = resolveBaseUrl(opts);
30
+ console.log(`\n${render.wrap(render.c.bold, "Flair test")} ${render.wrap(render.c.dim, `(url: ${baseUrl})`)}\n`);
31
+ let passed = 0;
32
+ let failed = 0;
33
+ let memoryId = null;
34
+ const check = async (name, fn) => {
35
+ try {
36
+ const ok = await fn();
37
+ if (ok) {
38
+ console.log(` ${render.icons.ok} ${render.wrap(render.c.green, "PASS")} ${name}`);
39
+ passed++;
40
+ }
41
+ else {
42
+ console.log(` ${render.icons.error} ${render.wrap(render.c.red, "FAIL")} ${name}`);
43
+ failed++;
44
+ }
45
+ }
46
+ catch (e) {
47
+ const message = e instanceof Error ? e.message : String(e);
48
+ console.log(` ${render.icons.error} ${render.wrap(render.c.red, "FAIL")} ${name}: ${render.wrap(render.c.dim, message?.slice(0, 120))}`);
49
+ failed++;
50
+ }
51
+ };
52
+ // 1. Write a test memory via PUT /Memory/<id>.
53
+ // Schema only exposes PUT — POST returns 'Memory does not have a post method implemented'.
54
+ await check("Write test memory (PUT /Memory/<id>)", async () => {
55
+ const id = `flair-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
56
+ const body = {
57
+ id,
58
+ content: "flair test \u2014 this will be deleted",
59
+ durability: "ephemeral",
60
+ createdAt: new Date().toISOString(),
61
+ };
62
+ if (agentId)
63
+ body.agentId = agentId;
64
+ await api("PUT", `/Memory/${id}`, body, { baseUrl });
65
+ memoryId = id;
66
+ return true;
67
+ });
68
+ // 2. Search for the test memory via POST /SemanticSearch
69
+ await check("Search for test memory (POST /SemanticSearch)", async () => {
70
+ await new Promise(r => setTimeout(r, 1500)); // allow indexing
71
+ const body = { q: "flair test", limit: 5 };
72
+ if (agentId)
73
+ body.agentId = agentId;
74
+ const result = await api("POST", "/SemanticSearch", body, { baseUrl });
75
+ return (result?.results?.length ?? 0) > 0;
76
+ });
77
+ // 3. Delete the test memory via DELETE /Memory/<id>
78
+ await check("Delete test memory (DELETE /Memory/<id>)", async () => {
79
+ if (!memoryId) {
80
+ // If write returned ok without an id, skip deletion cleanly
81
+ console.log(` (skipped — no id returned from write step)`);
82
+ return true;
83
+ }
84
+ await api("DELETE", `/Memory/${memoryId}`, agentId ? { agentId } : undefined, { baseUrl });
85
+ return true;
86
+ });
87
+ const passColor = passed > 0 ? render.c.green : render.c.dim;
88
+ const failColor = failed > 0 ? render.c.red : render.c.dim;
89
+ console.log(`\n ${render.wrap(passColor, `${passed} passed`)} ${render.wrap(render.c.dim, "·")} ${render.wrap(failColor, `${failed} failed`)}`);
90
+ if (failed > 0)
91
+ process.exit(1);
92
+ });
93
+ }
@@ -0,0 +1,143 @@
1
+ import { formatPurgeReport, purgeFlairInstall, purgeHadFailures } from "../lib/uninstall-purge.js";
2
+ import { existsSync, unlinkSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ let cli;
5
+ /** Bind the cli-locals this module depends on. */
6
+ export function bindCli(fns) {
7
+ cli = fns;
8
+ }
9
+ function configPath(...args) {
10
+ return cli.configPath(...args);
11
+ }
12
+ function defaultDataDir(...args) {
13
+ return cli.defaultDataDir(...args);
14
+ }
15
+ function launchdLabel(...args) {
16
+ return cli.launchdLabel(...args);
17
+ }
18
+ function launchdPlistPath(...args) {
19
+ return cli.launchdPlistPath(...args);
20
+ }
21
+ function listeningPidsOnPort(...args) {
22
+ return cli.listeningPidsOnPort(...args);
23
+ }
24
+ function readHarperPid(...args) {
25
+ return cli.readHarperPid(...args);
26
+ }
27
+ function resolveHttpPort(...args) {
28
+ return cli.resolveHttpPort(...args);
29
+ }
30
+ export function register(program) {
31
+ const LEGACY_LAUNCHD_LABEL = cli.LEGACY_LAUNCHD_LABEL;
32
+ // ─── flair uninstall ──────────────────────────────────────────────────────────
33
+ program
34
+ .command("uninstall")
35
+ .description("Stop Flair and remove the launchd/systemd service")
36
+ .option("--purge", "Also remove data, keys, secrets, schedulers, and client wiring (destructive)")
37
+ .action(async (opts) => {
38
+ const platform = process.platform;
39
+ // Use the unified resolver: Harper's config > per-user config > default.
40
+ // A default of 19926 that is "present but wrong" beats the actual port
41
+ // Harper is serving on (flair#819). resolveHttpPort reads Harper's own
42
+ // config in the data directory, which is authoritative.
43
+ const port = resolveHttpPort({}, "address");
44
+ // Stop first: remove launchd service(s) on macOS, then kill by port on
45
+ // all platforms. Removes BOTH the new instance-scoped plist and a
46
+ // pre-flair#693 legacy plist if present — uninstall's job is to purge
47
+ // everything for this data dir, so it doesn't rely on resolveLaunchdLabel's
48
+ // "prefer new" pick alone (which would skip a stray legacy leftover).
49
+ if (platform === "darwin") {
50
+ const dataDir = defaultDataDir();
51
+ const candidatePlists = [launchdPlistPath(launchdLabel(dataDir)), launchdPlistPath(LEGACY_LAUNCHD_LABEL)];
52
+ let removedAny = false;
53
+ for (const plistPath of candidatePlists) {
54
+ if (existsSync(plistPath)) {
55
+ try {
56
+ const { execSync } = await import("node:child_process");
57
+ execSync(`launchctl unload "${plistPath}"`, { stdio: "pipe" });
58
+ }
59
+ catch { /* best effort */ }
60
+ unlinkSync(plistPath);
61
+ removedAny = true;
62
+ }
63
+ }
64
+ if (removedAny)
65
+ console.log("✅ Launchd service removed");
66
+ }
67
+ // Kill any process still on the port (covers direct-start, no-service, or
68
+ // failed unload). Listening sockets only, never our own PID — see
69
+ // parseListeningPids (flair#800/flair#905).
70
+ //
71
+ // Guard (flair#917): refuse to SIGTERM a PID that cannot be attributed to
72
+ // this Flair instance. A port is not an identity — something else can hold
73
+ // it. Killing the wrong PID and then purging data is the whole bug.
74
+ let refusedKill = false;
75
+ try {
76
+ const { execSync } = await import("node:child_process");
77
+ const pids = listeningPidsOnPort(port, (cmd) => execSync(cmd, { encoding: "utf-8" }));
78
+ if (pids.length > 0) {
79
+ // Verify ownership before killing: the PID must match this instance's
80
+ // recorded PID (hdb.pid). If no PID file exists, Harper is already
81
+ // stopped and the port is stale — safe to skip.
82
+ const dataDir = defaultDataDir();
83
+ const harperPid = readHarperPid(dataDir);
84
+ if (harperPid !== null) {
85
+ // PID file exists — the PID on the port must be Harper or we refuse.
86
+ if (!pids.includes(harperPid)) {
87
+ console.log(`⚠️ Process(es) on port ${port} (PID${pids.length > 1 ? "s" : ""}: ${pids.join(", ")}) `
88
+ + `do not match this Flair instance (PID ${harperPid}). `
89
+ + `Not killing — cannot attribute the process to this instance. `
90
+ + `Stop the process manually if it is not Flair.`);
91
+ refusedKill = true;
92
+ }
93
+ else {
94
+ for (const pid of pids) {
95
+ try {
96
+ process.kill(pid, "SIGTERM");
97
+ }
98
+ catch { }
99
+ }
100
+ await new Promise(r => setTimeout(r, 2000));
101
+ console.log("✅ Flair process stopped");
102
+ }
103
+ }
104
+ else {
105
+ // No PID file — Harper is not (or was not) running here.
106
+ // The port may be stale or held by something else; don't risk killing it.
107
+ console.log(`⚠️ Process(es) on port ${port} (PID${pids.length > 1 ? "s" : ""}: ${pids.join(", ")}) `
108
+ + `but no PID file in data directory — not a running Flair instance. `
109
+ + `Not killing — stop the process manually if it is not Flair.`);
110
+ refusedKill = true;
111
+ }
112
+ }
113
+ }
114
+ catch { /* not running */ }
115
+ // Always remove per-user config on uninstall.
116
+ {
117
+ const cfgPath = configPath();
118
+ if (existsSync(cfgPath)) {
119
+ const { unlinkSync } = await import("node:fs");
120
+ unlinkSync(cfgPath);
121
+ console.log("✅ Config removed");
122
+ }
123
+ }
124
+ if (opts.purge) {
125
+ if (refusedKill) {
126
+ console.log("\n⚠️ Skipping purge: could not attribute the process on port — data preserved.");
127
+ console.log("Stop the process manually, then re-run: flair uninstall --purge");
128
+ }
129
+ else {
130
+ const home = process.env.HOME ?? homedir();
131
+ const result = purgeFlairInstall({ homeDir: home });
132
+ const report = formatPurgeReport(result);
133
+ console.log(report.lines.join("\n"));
134
+ if (purgeHadFailures(result))
135
+ process.exit(1);
136
+ }
137
+ }
138
+ else {
139
+ console.log("\nData and keys preserved at ~/.flair/");
140
+ console.log("To remove everything: flair uninstall --purge");
141
+ }
142
+ });
143
+ }