@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.
- package/README.md +4 -1
- package/dist/build-info.json +3 -3
- package/dist/cli.js +1914 -15328
- package/dist/commands/agent.js +453 -0
- package/dist/commands/attention.js +121 -0
- package/dist/commands/backup.js +115 -0
- package/dist/commands/bootstrap.js +91 -0
- package/dist/commands/bridge.js +608 -0
- package/dist/commands/deploy.js +180 -0
- package/dist/commands/doctor.js +1654 -0
- package/dist/commands/export.js +110 -0
- package/dist/commands/federation.js +1575 -0
- package/dist/commands/fleet.js +73 -0
- package/dist/commands/grant.js +109 -0
- package/dist/commands/hook.js +193 -0
- package/dist/commands/idp.js +193 -0
- package/dist/commands/import.js +134 -0
- package/dist/commands/init.js +1203 -0
- package/dist/commands/inspect.js +45 -0
- package/dist/commands/keys.js +187 -0
- package/dist/commands/mcp.js +707 -0
- package/dist/commands/memory.js +501 -0
- package/dist/commands/migrate-harness-memory.js +270 -0
- package/dist/commands/orgevent.js +138 -0
- package/dist/commands/presence.js +76 -0
- package/dist/commands/principal.js +338 -0
- package/dist/commands/quality.js +1164 -0
- package/dist/commands/reembed.js +296 -0
- package/dist/commands/relationship.js +76 -0
- package/dist/commands/rem.js +1048 -0
- package/dist/commands/restore.js +130 -0
- package/dist/commands/search.js +244 -0
- package/dist/commands/service.js +315 -0
- package/dist/commands/session.js +184 -0
- package/dist/commands/soul.js +155 -0
- package/dist/commands/status.js +914 -0
- package/dist/commands/test.js +93 -0
- package/dist/commands/uninstall.js +143 -0
- package/dist/commands/upgrade.js +1592 -0
- package/dist/commands/workspace.js +114 -0
- package/dist/deploy.js +24 -0
- package/dist/fabric-npm-install.js +87 -0
- package/dist/federation-verify.js +498 -0
- package/dist/fleet-verify.js +144 -21
- package/dist/install/clients.js +167 -0
- package/dist/lib/auth-resolve.js +76 -1
- package/dist/lib/daemon-liveness.js +131 -2
- package/dist/lib/doctor-config-path.js +61 -0
- package/dist/lib/doctor-federation-driver.js +189 -0
- package/dist/lib/doctor-run.js +40 -0
- package/dist/lib/entity-vocab-cli.js +3 -3
- package/dist/lib/federation-pair-identity.js +47 -0
- package/dist/lib/launchd-repair.js +199 -0
- package/dist/lib/ops-api-bind.js +115 -0
- package/dist/lib/owned-pins.js +219 -0
- package/dist/lib/stabilize-mqtt-network.js +123 -0
- package/dist/lib/uninstall-purge.js +218 -0
- package/dist/rem/restore.js +8 -10
- package/dist/resources/AgentReadPosition.js +74 -0
- package/dist/resources/Federation.js +8 -2
- package/dist/resources/Memory.js +4 -3
- package/dist/resources/MemoryBootstrap.js +41 -25
- package/dist/resources/MemoryCandidate.js +5 -6
- package/dist/resources/OrgEventCatchup.js +126 -47
- package/dist/resources/agent-read-position-lib.js +83 -0
- package/dist/resources/agent-read-position.js +120 -0
- package/dist/resources/embeddings-boot.js +32 -0
- package/dist/resources/federation-peer-liveness.js +73 -0
- package/dist/resources/health.js +68 -19
- package/dist/resources/mcp-tools.js +43 -279
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +59 -18
- package/dist/resources/migrations/embedding-stamp.js +20 -1
- package/dist/resources/migrations/recheck.js +43 -0
- package/dist/resources/migrations/runner.js +6 -1
- package/dist/resources/migrations/stamp-outstanding.js +171 -0
- package/dist/resources/migrations/visibility-backfill.js +2 -2
- package/dist/resources/org-event-catchup-lib.js +47 -0
- package/dist/resources/record-owner-guard.js +1 -0
- package/dist/stamp-migration-verify.js +163 -0
- package/dist/stamp-outstanding.js +144 -0
- package/docs/api-reference.md +4 -2
- package/docs/deploying-on-fabric.md +11 -10
- package/docs/deployment.md +3 -1
- package/docs/federation.md +19 -0
- package/docs/hosted-on-fabric.md +3 -3
- package/docs/quickstart.md +2 -1
- package/docs/releasing.md +15 -7
- package/docs/spoke-bringup.md +10 -5
- package/docs/standalone-local.md +3 -1
- package/docs/upgrade.md +25 -6
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/LICENSE +19 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/README.md +22 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/dist/index.d.ts +70 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/dist/index.js +665 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/package.json +46 -0
- package/package.json +9 -4
- package/schemas/agent.graphql +15 -0
- package/templates/launchd/start-flair-with-admin-pass.sh +73 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { resolveAdminUser } from "../lib/auth-resolve.js";
|
|
2
|
+
import * as render from "../render.js";
|
|
3
|
+
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
let cli;
|
|
7
|
+
/** Bind the cli-locals this module depends on. */
|
|
8
|
+
export function bindCli(fns) {
|
|
9
|
+
cli = fns;
|
|
10
|
+
}
|
|
11
|
+
function addSharedCredentialOptions(...args) {
|
|
12
|
+
return cli.addSharedCredentialOptions(...args);
|
|
13
|
+
}
|
|
14
|
+
function applyAdminPassFile(...args) {
|
|
15
|
+
return cli.applyAdminPassFile(...args);
|
|
16
|
+
}
|
|
17
|
+
function resolveHttpPort(...args) {
|
|
18
|
+
return cli.resolveHttpPort(...args);
|
|
19
|
+
}
|
|
20
|
+
export function register(program) {
|
|
21
|
+
// ─── flair backup ────────────────────────────────────────────────────────────
|
|
22
|
+
addSharedCredentialOptions(program
|
|
23
|
+
.command("backup")
|
|
24
|
+
.description("Export agents, memories, and souls to a JSON archive")
|
|
25
|
+
.option("--output <path>", "Output file path (default: ~/.flair/backups/flair-backup-<timestamp>.json)")
|
|
26
|
+
.option("--agents <ids>", "Comma-separated agent IDs to include (default: all)")
|
|
27
|
+
.option("--port <port>", "Harper HTTP port")
|
|
28
|
+
.option("--url <url>", "Flair base URL (overrides --port)")).action(async (opts) => {
|
|
29
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
30
|
+
applyAdminPassFile(opts);
|
|
31
|
+
// Env is a second-class fallback after the explicit flags (same order
|
|
32
|
+
// backup used before the shared helper). FLAIR_ADMIN_PASS is still
|
|
33
|
+
// accepted so existing scripts keep working.
|
|
34
|
+
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
35
|
+
const adminUser = resolveAdminUser(opts.adminUser);
|
|
36
|
+
if (!adminPass) {
|
|
37
|
+
console.error("Error: --admin-pass, --admin-pass-file, or FLAIR_ADMIN_PASS required for backup");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
// flair#968: `flair backup > file.json` captures the progress report, not
|
|
41
|
+
// the archive (which goes to --output, defaulting to ~/.flair/backups/...).
|
|
42
|
+
// The result was exit 0 and a plausible-looking file of a few hundred bytes —
|
|
43
|
+
// a false success immediately before a destructive upgrade.
|
|
44
|
+
//
|
|
45
|
+
// When stdout is not a TTY, route progress output to stderr. The archive
|
|
46
|
+
// still goes to --output / the default path. This makes `flair backup >
|
|
47
|
+
// file.json` produce an EMPTY file — unmistakably not a valid archive —
|
|
48
|
+
// while leaving default-path callers (schedulers, cron) completely
|
|
49
|
+
// unaffected.
|
|
50
|
+
const log = process.stdout.isTTY
|
|
51
|
+
? console.log.bind(console)
|
|
52
|
+
: console.error.bind(console);
|
|
53
|
+
const auth = `Basic ${Buffer.from(`${adminUser}:${adminPass}`).toString("base64")}`;
|
|
54
|
+
async function adminGet(path) {
|
|
55
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
56
|
+
headers: { Authorization: auth },
|
|
57
|
+
signal: AbortSignal.timeout(10_000),
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const text = await res.text().catch(() => "");
|
|
61
|
+
throw new Error(`GET ${path} failed (${res.status}): ${text}`);
|
|
62
|
+
}
|
|
63
|
+
return res.json();
|
|
64
|
+
}
|
|
65
|
+
log("Fetching agents...");
|
|
66
|
+
const allAgents = await adminGet("/Agent/");
|
|
67
|
+
const filterIds = opts.agents ? opts.agents.split(",").map((s) => s.trim()) : null;
|
|
68
|
+
const agents = filterIds ? allAgents.filter((a) => filterIds.includes(a.id)) : allAgents;
|
|
69
|
+
log(`Fetching memories for ${agents.length} agent(s)...`);
|
|
70
|
+
const memories = [];
|
|
71
|
+
for (const agent of agents) {
|
|
72
|
+
try {
|
|
73
|
+
const agentMemories = await adminGet(`/Memory/?agentId=${encodeURIComponent(agent.id)}`);
|
|
74
|
+
if (Array.isArray(agentMemories))
|
|
75
|
+
memories.push(...agentMemories);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.warn(` Warning: could not fetch memories for ${agent.id}: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
log("Fetching souls...");
|
|
82
|
+
const souls = [];
|
|
83
|
+
for (const agent of agents) {
|
|
84
|
+
try {
|
|
85
|
+
const agentSouls = await adminGet(`/Soul/?agentId=${encodeURIComponent(agent.id)}`);
|
|
86
|
+
if (Array.isArray(agentSouls))
|
|
87
|
+
souls.push(...agentSouls);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.warn(` Warning: could not fetch souls for ${agent.id}: ${err.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const backup = {
|
|
94
|
+
version: 1,
|
|
95
|
+
createdAt: new Date().toISOString(),
|
|
96
|
+
source: baseUrl,
|
|
97
|
+
agents,
|
|
98
|
+
memories,
|
|
99
|
+
souls,
|
|
100
|
+
};
|
|
101
|
+
// Determine output path
|
|
102
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
103
|
+
const defaultOutput = join(homedir(), ".flair", "backups", `flair-backup-${timestamp}.json`);
|
|
104
|
+
const outputPath = opts.output ?? defaultOutput;
|
|
105
|
+
mkdirSync(join(outputPath, ".."), { recursive: true });
|
|
106
|
+
const tmp = outputPath + ".tmp";
|
|
107
|
+
writeFileSync(tmp, JSON.stringify(backup, null, 2) + "\n", "utf-8");
|
|
108
|
+
renameSync(tmp, outputPath);
|
|
109
|
+
log(`\n${render.icons.ok} ${render.wrap(render.c.green, "Backup complete")}`);
|
|
110
|
+
log(render.kv("Agents", render.wrap(render.c.bold, String(agents.length))));
|
|
111
|
+
log(render.kv("Memories", render.wrap(render.c.bold, String(memories.length))));
|
|
112
|
+
log(render.kv("Souls", render.wrap(render.c.bold, String(souls.length))));
|
|
113
|
+
log(render.kv("Output", render.wrap(render.c.dim, outputPath)));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { authedRequest } from "../lib/auth-resolve.js";
|
|
2
|
+
import * as render from "../render.js";
|
|
3
|
+
let cli;
|
|
4
|
+
/** Bind the cli-locals this module depends on. */
|
|
5
|
+
export function bindCli(fns) {
|
|
6
|
+
cli = fns;
|
|
7
|
+
}
|
|
8
|
+
function resolveBaseUrl(...args) {
|
|
9
|
+
return cli.resolveBaseUrl(...args);
|
|
10
|
+
}
|
|
11
|
+
function resolveSigningAgentId(...args) {
|
|
12
|
+
return cli.resolveSigningAgentId(...args);
|
|
13
|
+
}
|
|
14
|
+
export function register(program) {
|
|
15
|
+
// ─── flair bootstrap ─────────────────────────────────────────────────────────
|
|
16
|
+
//
|
|
17
|
+
// `flair bootstrap` prints agent context (soul, memories) and a structured budget
|
|
18
|
+
// footer summarizing token usage and memory inclusion/truncation. The footer is
|
|
19
|
+
// parseable for downstream agents to react to budget pressure.
|
|
20
|
+
//
|
|
21
|
+
// Budget footer format (printed to stderr):
|
|
22
|
+
// [budget: <used>/<max> tokens, <included> included, <truncated> truncated]
|
|
23
|
+
//
|
|
24
|
+
// Fields:
|
|
25
|
+
// - tokens: estimated tokens used / max budget
|
|
26
|
+
// - included: number of memories/soul entries included in context
|
|
27
|
+
// - truncated: number of memories excluded due to token budget
|
|
28
|
+
//
|
|
29
|
+
// When truncated > 0, the agent should consider asking for more context or reducing scope.
|
|
30
|
+
program
|
|
31
|
+
.command("bootstrap")
|
|
32
|
+
.description("Cold-start context: get soul + recent memories as formatted text")
|
|
33
|
+
.option("--agent <id>", "Agent ID (or set FLAIR_AGENT_ID env)")
|
|
34
|
+
.option("--max-tokens <n>", "Maximum tokens in output", "4000")
|
|
35
|
+
.option("--port <port>", "Harper HTTP port")
|
|
36
|
+
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
37
|
+
.option("--target <url>", "Remote Flair URL (env: FLAIR_TARGET; alias for --url)")
|
|
38
|
+
.option("--key <path>", "Ed25519 private key path")
|
|
39
|
+
.option("--json", "Emit JSON {context, tokenEstimate, memoriesIncluded, ...} (also: pipe + FLAIR_OUTPUT=json)")
|
|
40
|
+
.action(async (opts) => {
|
|
41
|
+
const { agentId, source } = resolveSigningAgentId(opts, "bootstrap");
|
|
42
|
+
if (!agentId) {
|
|
43
|
+
console.error(`${render.icons.error} --agent <id> required (or set FLAIR_AGENT_ID)`);
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
const baseUrl = resolveBaseUrl(opts);
|
|
47
|
+
const mode = render.resolveOutputMode(opts);
|
|
48
|
+
try {
|
|
49
|
+
// flair#747: routed through the shared resolver — --key is an
|
|
50
|
+
// explicit tier-1 override (as before), now additionally backstopped
|
|
51
|
+
// by env admin-pass / ~/.flair/admin-pass / the Ed25519 floor if
|
|
52
|
+
// neither --key nor the agent's own resolveKeyPath(agentId) lookup
|
|
53
|
+
// finds a usable key. Previously this only ever tried Ed25519 (no
|
|
54
|
+
// admin fallback at all) and sent NO Authorization header when no key
|
|
55
|
+
// was found, relying on Harper's local passthrough.
|
|
56
|
+
const result = (await authedRequest("POST", "/BootstrapMemories", { agentId, maxTokens: parseInt(opts.maxTokens, 10) }, { baseUrl, agentId, agentIdSource: source, explicitKeyPath: opts.key }));
|
|
57
|
+
if (mode === "json") {
|
|
58
|
+
// Agent-first: emit the full server response, augmented with the cap
|
|
59
|
+
// that was requested. Includes context, sections, tokenEstimate, etc.
|
|
60
|
+
console.log(render.asJSON({ ...result, maxTokens: parseInt(opts.maxTokens, 10) }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Human mode: print context to stdout, budget footer to stderr (parseable).
|
|
64
|
+
if (result.context) {
|
|
65
|
+
console.log(result.context);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.error(`${render.icons.error} No context available.`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
// flair#1199 — the budget footer reflects the PROSE the human injects
|
|
72
|
+
// (stdout = result.context), not the full serialized payload. tokenEstimate
|
|
73
|
+
// now measures the whole response (structured containers + prose), which
|
|
74
|
+
// the CLI's structured fields the human doesn't read would inflate.
|
|
75
|
+
const tokensUsed = typeof result.context === "string" && result.context.length > 0
|
|
76
|
+
? Math.ceil(result.context.length / 4)
|
|
77
|
+
: (result.tokenEstimate ?? 0);
|
|
78
|
+
const maxTokens = parseInt(opts.maxTokens, 10);
|
|
79
|
+
const included = result.memoriesIncluded ?? 0;
|
|
80
|
+
const truncated = result.memoriesTruncated ?? 0;
|
|
81
|
+
const tokenPct = maxTokens > 0 ? (tokensUsed / maxTokens) * 100 : 0;
|
|
82
|
+
const tokenIcon = tokenPct >= 90 ? render.icons.warn : tokenPct >= 70 ? render.icons.info : render.icons.ok;
|
|
83
|
+
const truncIcon = truncated > 0 ? render.icons.warn : render.icons.ok;
|
|
84
|
+
console.error(`${tokenIcon} budget ${tokensUsed}/${maxTokens} tokens (${tokenPct.toFixed(0)}%) ${render.icons.bullet} ${render.icons.ok} ${included} included ${render.icons.bullet} ${truncIcon} ${truncated} truncated`);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.error(`${render.icons.error} Bootstrap failed: ${err.message}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|