@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,114 @@
1
+ import { resolveKeyPath, buildEd25519Auth } from "../lib/auth-resolve.js";
2
+ let cli;
3
+ /** Bind shared CLI helpers. cli.ts calls this immediately before register(program). */
4
+ export function bindCli(fns) {
5
+ cli = fns;
6
+ }
7
+ function resolveBaseUrl(opts) {
8
+ return cli.resolveBaseUrl(opts);
9
+ }
10
+ function resolveSigningAgentId(opts, command) {
11
+ return cli.resolveSigningAgentId(opts, command);
12
+ }
13
+ function parseEntitiesOptionOrExit(csv) {
14
+ return cli.parseEntitiesOptionOrExit(csv);
15
+ }
16
+ // ─── flair workspace ─────────────────────────────────────────────────────────
17
+ //
18
+ // Coordination write surface (Kris #510). `workspace set` writes the
19
+ // agent's OWN WorkspaceState via a signed PUT /WorkspaceState/{id}. Identity
20
+ // is asserted by including agentId in the body — the server never trusts it
21
+ // blindly, it 403s any mismatch against the Ed25519 signature's agentId
22
+ // (WorkspaceState.put(), resources/WorkspaceState.ts), so this is a
23
+ // self-declaration the server verifies 1:1, not attribution-from-body.
24
+ //
25
+ // (flair#679, measured against a real spawned Harper): table-backed resources
26
+ // only accept writes via PUT /<Table>/<id> — a bare POST /WorkspaceState 405s
27
+ // ("does not have a post method implemented to handle HTTP method POST"),
28
+ // same restriction documented in resources/Memory.ts and already fixed for
29
+ // `soul set` (#498). WorkspaceState.ts DOES define a post() method, but
30
+ // Harper's REST layer never routes a real HTTP POST to it — post() is only
31
+ // reachable via in-process resource instantiation, never the wire. put(),
32
+ // unlike post(), does NOT default createdAt/timestamp/agentId — the CLI
33
+ // supplies them all explicitly below.
34
+ export const MAX_WORKSPACE_FIELD_LENGTH = 2000;
35
+ /** Register the `flair workspace` command group (flair#1635). */
36
+ export function register(program) {
37
+ const ENTITIES_OPTION_DESCRIPTION = cli.ENTITIES_OPTION_DESCRIPTION;
38
+ const workspace = program.command("workspace").description("Manage agent workspace state (The Office Space)");
39
+ workspace
40
+ .command("set")
41
+ .description("Set your agent's current workspace state (PUT /WorkspaceState/{id})")
42
+ .requiredOption("--ref <ref>", "Workspace ref (branch, worktree, or task ref)")
43
+ .option("--label <text>", "Human-readable label for this workspace")
44
+ .option("--provider <name>", "Provider/runtime (e.g. claude-code, openclaw)", "cli")
45
+ .option("--task <id>", "Task/issue id this workspace is attached to")
46
+ .option("--phase <phase>", "Current phase (e.g. design, implement, review)")
47
+ .option("--summary <text>", "Short summary of current workspace state")
48
+ .option("--entities <csv>", ENTITIES_OPTION_DESCRIPTION)
49
+ .option("--agent <id>", "Agent ID (env: FLAIR_AGENT_ID)")
50
+ .option("--port <port>", "Harper HTTP port")
51
+ .option("--target <url>", "Remote Flair URL (env: FLAIR_TARGET)")
52
+ .action(async (opts) => {
53
+ const { agentId } = resolveSigningAgentId(opts, "workspace set");
54
+ if (!agentId) {
55
+ console.error("Error: agent ID required. Pass --agent <id> or set FLAIR_AGENT_ID environment variable.");
56
+ process.exit(1);
57
+ }
58
+ // Validate field lengths (free text → cap to bound the write).
59
+ for (const [name, val] of [["ref", opts.ref], ["label", opts.label], ["summary", opts.summary]]) {
60
+ if (val && String(val).length > MAX_WORKSPACE_FIELD_LENGTH) {
61
+ console.error(`Error: --${name} exceeds ${MAX_WORKSPACE_FIELD_LENGTH} character limit (got ${String(val).length}).`);
62
+ process.exit(1);
63
+ }
64
+ }
65
+ // flair#1288: validate --entities before any key/network work; exits 1
66
+ // with the canonical format-and-type-set message on any malformed value.
67
+ const entities = opts.entities ? parseEntitiesOptionOrExit(String(opts.entities)) : undefined;
68
+ const keyPath = resolveKeyPath(agentId);
69
+ if (!keyPath) {
70
+ console.error(`Error: private key not found for agent '${agentId}'. Check ~/.flair/keys/ or set FLAIR_KEY_DIR.`);
71
+ process.exit(1);
72
+ }
73
+ const baseUrl = resolveBaseUrl(opts).replace(/\/$/, "");
74
+ // Deterministic id (agentId:ref) — re-running `workspace set` for the same
75
+ // ref overwrites the same record, which is intentional (one row per
76
+ // agent+ref, not an append log).
77
+ const id = `${agentId}:${opts.ref}`;
78
+ const auth = buildEd25519Auth(agentId, "PUT", `/WorkspaceState/${id}`, keyPath);
79
+ // agentId IS included in the body now — WorkspaceState.put() (unlike
80
+ // post()) does not auto-attribute from the signature, it 403s any
81
+ // mismatch. This is a self-declaration the server verifies against the
82
+ // signature, not a forgeable claim.
83
+ const now = new Date().toISOString();
84
+ const body = {
85
+ id,
86
+ agentId,
87
+ ref: opts.ref,
88
+ provider: opts.provider ?? "cli",
89
+ timestamp: now,
90
+ createdAt: now,
91
+ };
92
+ if (opts.label)
93
+ body.label = opts.label;
94
+ if (opts.task)
95
+ body.taskId = opts.task;
96
+ if (opts.phase)
97
+ body.phase = opts.phase;
98
+ if (opts.summary)
99
+ body.summary = opts.summary;
100
+ if (entities && entities.length > 0)
101
+ body.entities = entities;
102
+ const res = await fetch(`${baseUrl}/WorkspaceState/${id}`, {
103
+ method: "PUT",
104
+ headers: { "Content-Type": "application/json", Authorization: auth },
105
+ body: JSON.stringify(body),
106
+ });
107
+ if (!res.ok) {
108
+ const text = await res.text().catch(() => "");
109
+ console.error(`Error: PUT /WorkspaceState/${id} failed (${res.status}): ${text}`);
110
+ process.exit(1);
111
+ }
112
+ console.log(`✓ Workspace state updated for '${agentId}': ref=${opts.ref}${opts.phase ? `, phase=${opts.phase}` : ""}`);
113
+ });
114
+ }
package/dist/deploy.js CHANGED
@@ -6,6 +6,8 @@ import { fileURLToPath } from "node:url";
6
6
  import { createRequire } from "node:module";
7
7
  import { COMPONENT_ENV_FILENAME, PUBLIC_URL_KEY, isLoopbackUrl, planComponentEnv, publicUrlRemedy, } from "./component-env.js";
8
8
  import { awaitOriginQuiescent, awaitReplicationConvergence, defaultConvergenceDeps, parseReplicationFailure, } from "./replication-convergence.js";
9
+ import { DEFAULT_STAMP_VERIFY_TIMEOUT_MS, verifyStampMigrationConverged } from "./stamp-migration-verify.js";
10
+ import { FABRIC_NPM_INSTALL_COMMAND } from "./fabric-npm-install.js";
9
11
  // Files that must be PRESENT for a deploy root to be usable at all — a
10
12
  // preflight sanity check, not the payload definition.
11
13
  //
@@ -218,6 +220,12 @@ export function buildHarperDeployArgs(opts, url, project) {
218
220
  `replicated=${opts.replicated !== false}`,
219
221
  `deployment_timeout=${deploymentTimeoutMs}`,
220
222
  `install_timeout=${installTimeoutMs}`,
223
+ // flair#886: Harper's default `npm install` writes every tarball into
224
+ // the node's `~/.npm/_cacache` and never evicts it. This command runs
225
+ // the same install against a disposable cache and deletes it after
226
+ // (see src/fabric-npm-install.ts). One argv element so Harper's
227
+ // `key=value` parser keeps the spaces inside the value.
228
+ `install_command=${FABRIC_NPM_INSTALL_COMMAND}`,
221
229
  ];
222
230
  // --ignore-replication-errors escape hatch. Only appended when
223
231
  // set — omitted entirely otherwise, so this is a no-op for every existing
@@ -929,6 +937,22 @@ export async function deploy(opts) {
929
937
  timeoutMs: opts.verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS,
930
938
  onProgress: opts.onProgress,
931
939
  });
940
+ // flair#1073: route verify proves the component is serving. It does not
941
+ // prove embedding-stamp re-embedded pre-flip rows. `upgrade --target`
942
+ // was reporting success while a Fabric corpus stayed split for days.
943
+ // Same --no-verify escape hatch; needs Basic admin (token-only skips).
944
+ if (opts.fabricUser && opts.fabricPassword) {
945
+ await verifyStampMigrationConverged({
946
+ baseUrl: url,
947
+ fabricUser: opts.fabricUser,
948
+ fabricPassword: opts.fabricPassword,
949
+ timeoutMs: opts.verifyTimeoutMs ?? DEFAULT_STAMP_VERIFY_TIMEOUT_MS,
950
+ onProgress: opts.onProgress,
951
+ });
952
+ }
953
+ else {
954
+ opts.onProgress?.("skipping embedding-stamp convergence check — needs --fabric-user/--fabric-password (token-only auth cannot read /HealthDetail)");
955
+ }
932
956
  // Only meaningful for a target that is not loopback: a local Harper SHOULD
933
957
  // advertise loopback, and asserting otherwise there would be wrong.
934
958
  if (publicUrl) {
@@ -0,0 +1,87 @@
1
+ /**
2
+ * fabric-npm-install.ts — node-side `npm install` for Fabric deploys (flair#886).
3
+ *
4
+ * Harper's default component install writes every fetched tarball into the
5
+ * node's `~/.npm/_cacache` and never evicts it. A month of `flair deploy` /
6
+ * `flair upgrade --target` therefore leaves a growing cache on the hub —
7
+ * hundreds of MB per install once `node-llama-cpp` prebuilds are in the
8
+ * tree — while `get_components` and `system_information` never show it.
9
+ *
10
+ * This file is the `install_command` those two CLI paths pass to
11
+ * `harper deploy`. It runs `npm install` against a disposable cache
12
+ * directory and deletes that directory afterwards, so cache lifetime
13
+ * matches the deploy rather than the node.
14
+ *
15
+ * Harper splits `install_command` on spaces and spawns without a shell
16
+ * (`Application.installApplication`), so the command must be a single
17
+ * executable plus a path — no `sh -c`, no `&& rm`. The flags after
18
+ * `install` match Harper's own default when `install.command` is unset
19
+ * (`--force --ignore-scripts`); a custom command replaces that default
20
+ * entirely, and dropping either flag would change what the node runs.
21
+ */
22
+ import { spawnSync } from "node:child_process";
23
+ import { mkdtempSync, rmSync } from "node:fs";
24
+ import { tmpdir } from "node:os";
25
+ import { join, resolve } from "node:path";
26
+ import { pathToFileURL } from "node:url";
27
+ /** Path Harper runs, relative to the extracted component directory. */
28
+ export const FABRIC_NPM_INSTALL_ENTRY = "dist/fabric-npm-install.js";
29
+ /**
30
+ * Passed as `install_command=…` to `harper deploy`. Harper's
31
+ * `buildRequest` JSON-parses values that look like JSON; this string
32
+ * does not, so it stays a string. `split(" ")` must yield exactly
33
+ * `["node", FABRIC_NPM_INSTALL_ENTRY]`.
34
+ */
35
+ export const FABRIC_NPM_INSTALL_COMMAND = `node ${FABRIC_NPM_INSTALL_ENTRY}`;
36
+ /** Harper's default argv when `install.command` is unset, plus `--cache`. */
37
+ export const FABRIC_NPM_INSTALL_ARGS_BEFORE_CACHE = [
38
+ "install",
39
+ "--force",
40
+ "--ignore-scripts",
41
+ ];
42
+ /**
43
+ * Run `npm install` with `--cache` pointed at a fresh temp directory,
44
+ * then remove that directory on success or failure.
45
+ *
46
+ * Returns npm's exit status. Throws only when the spawn itself fails
47
+ * to start (ENOENT on `npm`, etc.) — the same split `fabric-upgrade`'s
48
+ * local staging install uses.
49
+ */
50
+ export function installWithEphemeralNpmCache(deps = {}) {
51
+ const spawn = deps.spawn ??
52
+ ((command, args, opts) => spawnSync(command, args, opts));
53
+ const mkdtemp = deps.mkdtemp ?? ((prefix) => mkdtempSync(prefix));
54
+ const rm = deps.rm ?? ((path) => rmSync(path, { recursive: true, force: true }));
55
+ const cwd = deps.cwd ?? process.cwd();
56
+ const tmp = deps.tmpdir ?? tmpdir();
57
+ const cacheDir = mkdtemp(join(tmp, "flair-npm-"));
58
+ try {
59
+ const result = spawn("npm", [...FABRIC_NPM_INSTALL_ARGS_BEFORE_CACHE, "--cache", cacheDir], { cwd, stdio: "inherit" });
60
+ if (result.error)
61
+ throw result.error;
62
+ return result.status ?? 1;
63
+ }
64
+ finally {
65
+ rm(cacheDir);
66
+ }
67
+ }
68
+ function invokedAsMain() {
69
+ const entry = process.argv[1];
70
+ if (!entry)
71
+ return false;
72
+ try {
73
+ return import.meta.url === pathToFileURL(resolve(entry)).href;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ if (invokedAsMain()) {
80
+ try {
81
+ process.exit(installWithEphemeralNpmCache());
82
+ }
83
+ catch (err) {
84
+ console.error(err instanceof Error ? err.message : err);
85
+ process.exit(1);
86
+ }
87
+ }