@tpsdev-ai/flair 0.4.7 → 0.4.11
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
CHANGED
|
@@ -462,10 +462,39 @@ program
|
|
|
462
462
|
}
|
|
463
463
|
console.log(`\n Claude Code: Add to your CLAUDE.md:`);
|
|
464
464
|
console.log(` At the start of every session, run mcp__flair__bootstrap before responding.`);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
465
|
+
// Auto-wire MCP config into ~/.claude.json if Claude Code is installed
|
|
466
|
+
const claudeJsonPath = join(homedir(), ".claude.json");
|
|
467
|
+
const mcpEnv = { FLAIR_AGENT_ID: agentId, FLAIR_URL: httpUrl };
|
|
468
|
+
const flairMcpConfig = {
|
|
469
|
+
type: "stdio",
|
|
470
|
+
command: "flair-mcp",
|
|
471
|
+
args: [],
|
|
472
|
+
env: mcpEnv,
|
|
473
|
+
};
|
|
474
|
+
try {
|
|
475
|
+
if (existsSync(claudeJsonPath)) {
|
|
476
|
+
const claudeJson = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
|
|
477
|
+
const existing = claudeJson.mcpServers?.flair;
|
|
478
|
+
if (existing && existing.env?.FLAIR_URL === httpUrl && existing.env?.FLAIR_AGENT_ID === agentId) {
|
|
479
|
+
console.log(`\n MCP config already set in ~/.claude.json ✓`);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
claudeJson.mcpServers = claudeJson.mcpServers || {};
|
|
483
|
+
claudeJson.mcpServers.flair = flairMcpConfig;
|
|
484
|
+
writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
|
|
485
|
+
console.log(`\n MCP config written to ~/.claude.json ✓`);
|
|
486
|
+
console.log(` Restart Claude Code to pick up the new config.`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
console.log(`\n MCP config (add to ~/.claude.json):`);
|
|
491
|
+
console.log(` { "mcpServers": { "flair": ${JSON.stringify(flairMcpConfig)} } }`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
console.log(`\n MCP config (add manually to ~/.claude.json):`);
|
|
496
|
+
console.log(` { "mcpServers": { "flair": ${JSON.stringify(flairMcpConfig)} } }`);
|
|
497
|
+
}
|
|
469
498
|
});
|
|
470
499
|
// ─── flair agent ─────────────────────────────────────────────────────────────
|
|
471
500
|
const agent = program.command("agent").description("Manage Flair agents");
|
|
@@ -89,12 +89,10 @@ export class SemanticSearch extends Resource {
|
|
|
89
89
|
if (!qEmb && q) {
|
|
90
90
|
// Always attempt embedding generation — getEmbedding() handles init internally.
|
|
91
91
|
// Don't gate on getMode() which may return "none" before init completes in worker threads.
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
qEmb = await getEmbedding(String(q).slice(0, 8000));
|
|
95
|
-
}
|
|
96
|
-
catch { }
|
|
92
|
+
try {
|
|
93
|
+
qEmb = await getEmbedding(String(q).slice(0, 8000));
|
|
97
94
|
}
|
|
95
|
+
catch { }
|
|
98
96
|
}
|
|
99
97
|
// ─── Temporal intent detection ────────────────────────────────────────────
|
|
100
98
|
let sinceDate = since ? new Date(since) : null;
|
|
@@ -145,8 +143,6 @@ export class SemanticSearch extends Resource {
|
|
|
145
143
|
agentConditions.push({ attribute: "visibility", comparator: "equals", value: "office" });
|
|
146
144
|
conditions.push({ operator: "or", conditions: agentConditions });
|
|
147
145
|
}
|
|
148
|
-
// Exclude archived records. Use "not equals true" instead of "equals false"
|
|
149
|
-
// so records without the archived field (default: not archived) are included.
|
|
150
146
|
// Exclude archived records. Use "not_equal" (Harper v5 comparator) instead of
|
|
151
147
|
// "equals false" so records without the archived field are included.
|
|
152
148
|
conditions.push({ attribute: "archived", comparator: "not_equal", value: true });
|
package/dist/resources/Soul.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { databases } from "@harperfast/harper";
|
|
2
|
+
function enforceAgentScope(self, data) {
|
|
3
|
+
const authenticatedAgent = self.request?.headers?.get?.("x-tps-agent");
|
|
4
|
+
const callerIsAdmin = self.request?.tpsAgentIsAdmin === true;
|
|
5
|
+
if (authenticatedAgent && !callerIsAdmin && data?.agentId && data.agentId !== authenticatedAgent) {
|
|
6
|
+
return new Response(JSON.stringify({
|
|
7
|
+
error: "forbidden: agentId must match authenticated agent",
|
|
8
|
+
}), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
2
12
|
export class Soul extends databases.flair.Soul {
|
|
3
13
|
async post(content, context) {
|
|
14
|
+
const denied = enforceAgentScope(this, content);
|
|
15
|
+
if (denied)
|
|
16
|
+
return denied;
|
|
4
17
|
content.durability ||= "permanent";
|
|
5
18
|
content.createdAt = new Date().toISOString();
|
|
6
19
|
content.updatedAt = content.createdAt;
|
|
7
20
|
return super.post(content, context);
|
|
8
21
|
}
|
|
22
|
+
async put(content, context) {
|
|
23
|
+
const denied = enforceAgentScope(this, content);
|
|
24
|
+
if (denied)
|
|
25
|
+
return denied;
|
|
26
|
+
content.updatedAt = new Date().toISOString();
|
|
27
|
+
return super.put(content, context);
|
|
28
|
+
}
|
|
9
29
|
}
|
|
@@ -357,93 +357,13 @@ server.http(async (request, nextLayer) => {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
-
// ──
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const clone = request.clone();
|
|
368
|
-
const body = await clone.json();
|
|
369
|
-
if (body?.agentId && body.agentId !== agentId) {
|
|
370
|
-
return new Response(JSON.stringify({
|
|
371
|
-
error: "forbidden: agentId must match authenticated agent",
|
|
372
|
-
}), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
return new Response(JSON.stringify({ error: "malformed_request_body" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
// ── BootstrapMemories: agentId must match authenticated agent ───────────────
|
|
380
|
-
if (!request.tpsAgentIsAdmin &&
|
|
381
|
-
method === "POST" &&
|
|
382
|
-
(url.pathname === "/BootstrapMemories" || url.pathname === "/BootstrapMemories/")) {
|
|
383
|
-
try {
|
|
384
|
-
const clone = request.clone();
|
|
385
|
-
const body = await clone.json();
|
|
386
|
-
if (body?.agentId && body.agentId !== agentId) {
|
|
387
|
-
return new Response(JSON.stringify({
|
|
388
|
-
error: "forbidden: agentId must match authenticated agent",
|
|
389
|
-
}), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
catch {
|
|
393
|
-
return new Response(JSON.stringify({ error: "malformed_request_body" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
// ── Memory POST (create): agentId must match authenticated agent ────────────
|
|
397
|
-
if (!request.tpsAgentIsAdmin &&
|
|
398
|
-
method === "POST" &&
|
|
399
|
-
(url.pathname === "/Memory" || url.pathname === "/Memory/")) {
|
|
400
|
-
try {
|
|
401
|
-
const clone = request.clone();
|
|
402
|
-
const body = await clone.json();
|
|
403
|
-
if (body?.agentId && body.agentId !== agentId) {
|
|
404
|
-
return new Response(JSON.stringify({
|
|
405
|
-
error: "forbidden: cannot create memories for another agent",
|
|
406
|
-
}), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
catch {
|
|
410
|
-
return new Response(JSON.stringify({ error: "malformed_request_body" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// ── Soul POST/PUT: agentId must match authenticated agent ───────────────────
|
|
414
|
-
if (!request.tpsAgentIsAdmin &&
|
|
415
|
-
(method === "POST" || method === "PUT") &&
|
|
416
|
-
(url.pathname === "/Soul" || url.pathname === "/Soul/" || url.pathname.startsWith("/Soul/"))) {
|
|
417
|
-
try {
|
|
418
|
-
const clone = request.clone();
|
|
419
|
-
const body = await clone.json();
|
|
420
|
-
if (body?.agentId && body.agentId !== agentId) {
|
|
421
|
-
return new Response(JSON.stringify({
|
|
422
|
-
error: "forbidden: cannot write another agent's soul",
|
|
423
|
-
}), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
return new Response(JSON.stringify({ error: "malformed_request_body" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
// ── Memory PUT: agentId must match authenticated agent ──────────────────────
|
|
431
|
-
if (!request.tpsAgentIsAdmin &&
|
|
432
|
-
method === "PUT" &&
|
|
433
|
-
(url.pathname === "/Memory" || url.pathname === "/Memory/" || url.pathname.startsWith("/Memory/"))) {
|
|
434
|
-
try {
|
|
435
|
-
const clone = request.clone();
|
|
436
|
-
const body = await clone.json();
|
|
437
|
-
if (body?.agentId && body.agentId !== agentId) {
|
|
438
|
-
return new Response(JSON.stringify({
|
|
439
|
-
error: "forbidden: cannot write memories for another agent",
|
|
440
|
-
}), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
catch {
|
|
444
|
-
return new Response(JSON.stringify({ error: "malformed_request_body" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
445
|
-
}
|
|
446
|
-
}
|
|
360
|
+
// ── Mutation scoping: agentId in body must match authenticated agent ────────
|
|
361
|
+
// The resource handlers also enforce this (defense-in-depth), but rejecting
|
|
362
|
+
// early avoids unnecessary work. We don't use request.clone().json() because
|
|
363
|
+
// Harper's Request is not a Web API Request — it wraps a Node.js stream.
|
|
364
|
+
// Instead, the resource-level check (e.g. BootstrapMemories line 58) handles
|
|
365
|
+
// body-level enforcement since it receives the parsed data from Harper's REST
|
|
366
|
+
// layer. The middleware's job is identity verification (done above).
|
|
447
367
|
// ── Memory GET: non-admin can only read own memories (by ID) ────────────────
|
|
448
368
|
if (!request.tpsAgentIsAdmin && method === "GET") {
|
|
449
369
|
if (url.pathname.startsWith("/Memory/")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|