@tpsdev-ai/flair 0.4.6 → 0.4.8
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 +12 -17
- package/dist/resources/auth-middleware.js +7 -87
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1031,7 +1031,7 @@ program
|
|
|
1031
1031
|
.action(async (opts) => {
|
|
1032
1032
|
const platform = process.platform;
|
|
1033
1033
|
const port = readPortFromConfig() ?? DEFAULT_PORT;
|
|
1034
|
-
// Stop first
|
|
1034
|
+
// Stop first: remove launchd service on macOS, then kill by port on all platforms
|
|
1035
1035
|
if (platform === "darwin") {
|
|
1036
1036
|
const label = "ai.tpsdev.flair";
|
|
1037
1037
|
const plistPath = join(homedir(), "Library", "LaunchAgents", `${label}.plist`);
|
|
@@ -1045,27 +1045,22 @@ program
|
|
|
1045
1045
|
unlinkSync(plistPath);
|
|
1046
1046
|
console.log("✅ Launchd service removed");
|
|
1047
1047
|
}
|
|
1048
|
-
else {
|
|
1049
|
-
console.log("No launchd service found — skipping");
|
|
1050
|
-
}
|
|
1051
1048
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
process.kill(Number(pid.trim()), "SIGTERM");
|
|
1061
|
-
}
|
|
1062
|
-
catch { }
|
|
1049
|
+
// Kill any process still on the port (covers direct-start, no-service, or failed unload)
|
|
1050
|
+
try {
|
|
1051
|
+
const { execSync } = await import("node:child_process");
|
|
1052
|
+
const lsof = execSync(`lsof -ti :${port}`, { encoding: "utf-8" }).trim();
|
|
1053
|
+
if (lsof) {
|
|
1054
|
+
for (const pid of lsof.split("\n")) {
|
|
1055
|
+
try {
|
|
1056
|
+
process.kill(Number(pid.trim()), "SIGTERM");
|
|
1063
1057
|
}
|
|
1058
|
+
catch { }
|
|
1064
1059
|
}
|
|
1060
|
+
console.log("✅ Flair process stopped");
|
|
1065
1061
|
}
|
|
1066
|
-
catch { /* not running */ }
|
|
1067
|
-
console.log("✅ Flair process stopped");
|
|
1068
1062
|
}
|
|
1063
|
+
catch { /* not running */ }
|
|
1069
1064
|
// Remove config
|
|
1070
1065
|
const cfgPath = configPath();
|
|
1071
1066
|
if (existsSync(cfgPath)) {
|
|
@@ -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.8",
|
|
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",
|