@tpsdev-ai/openclaw-flair 0.1.3 → 0.2.0
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/index.ts +16 -47
- package/key-resolver.ts +84 -0
- package/package.json +4 -3
package/index.ts
CHANGED
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
import { randomUUID, createHash } from "node:crypto";
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
|
-
import { resolve
|
|
19
|
+
import { resolve } from "node:path";
|
|
20
20
|
import { Type } from "@sinclair/typebox";
|
|
21
21
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
22
|
+
import { resolveKeyPath, loadPrivateKey, resolveAgentId } from "./key-resolver.js";
|
|
22
23
|
|
|
23
24
|
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
24
25
|
|
|
@@ -36,23 +37,6 @@ const DEFAULT_URL = "http://127.0.0.1:9926";
|
|
|
36
37
|
const DEFAULT_MAX_RECALL = 5;
|
|
37
38
|
const DEFAULT_MAX_BOOTSTRAP_TOKENS = 4000;
|
|
38
39
|
|
|
39
|
-
// ─── Key Resolution (separated from network client for security scanner) ──────
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Resolve the default Flair key path for an agent.
|
|
43
|
-
* Checks FLAIR_KEY_DIR env var first, then standard ~/.flair/keys/ path.
|
|
44
|
-
*/
|
|
45
|
-
function resolveDefaultKeyPath(agentId: string): string | null {
|
|
46
|
-
const keyDirEnv = process.env.FLAIR_KEY_DIR;
|
|
47
|
-
if (keyDirEnv) {
|
|
48
|
-
const envPath = resolve(keyDirEnv, `${agentId}.key`);
|
|
49
|
-
if (existsSync(envPath)) return envPath;
|
|
50
|
-
}
|
|
51
|
-
const standard = resolve(homedir(), ".flair", "keys", `${agentId}.key`);
|
|
52
|
-
if (existsSync(standard)) return standard;
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
40
|
// ─── Flair HTTP Client ────────────────────────────────────────────────────────
|
|
57
41
|
|
|
58
42
|
class FlairMemoryClient {
|
|
@@ -63,35 +47,15 @@ class FlairMemoryClient {
|
|
|
63
47
|
constructor(config: FlairMemoryConfig) {
|
|
64
48
|
this.baseUrl = (config.url ?? DEFAULT_URL).replace(/\/$/, "");
|
|
65
49
|
this.agentId = config.agentId;
|
|
66
|
-
this.keyPath = config.keyPath
|
|
67
|
-
? resolve(config.keyPath.replace(/^~/, homedir()))
|
|
68
|
-
: resolveDefaultKeyPath(config.agentId);
|
|
50
|
+
this.keyPath = resolveKeyPath(config.agentId, config.keyPath);
|
|
69
51
|
}
|
|
70
52
|
|
|
71
53
|
private buildAuthHeader(method: string, path: string): Record<string, string> {
|
|
72
|
-
if (!this.keyPath
|
|
54
|
+
if (!this.keyPath) return {};
|
|
73
55
|
try {
|
|
74
|
-
const { sign: ed25519Sign,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// - base64-encoded seed (legacy format)
|
|
78
|
-
const fileBuf = readFileSync(this.keyPath);
|
|
79
|
-
let rawBuf: Buffer;
|
|
80
|
-
if (fileBuf.length === 32) {
|
|
81
|
-
// Raw binary seed
|
|
82
|
-
rawBuf = fileBuf;
|
|
83
|
-
} else {
|
|
84
|
-
// Try base64 decode
|
|
85
|
-
rawBuf = Buffer.from(fileBuf.toString("utf-8").trim(), "base64");
|
|
86
|
-
}
|
|
87
|
-
let privateKey: ReturnType<typeof createPrivateKey>;
|
|
88
|
-
if (rawBuf.length === 32) {
|
|
89
|
-
// Raw Ed25519 seed — wrap in PKCS8 DER envelope
|
|
90
|
-
const pkcs8Header = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
91
|
-
privateKey = createPrivateKey({ key: Buffer.concat([pkcs8Header, rawBuf]), format: "der", type: "pkcs8" });
|
|
92
|
-
} else {
|
|
93
|
-
privateKey = createPrivateKey({ key: rawBuf, format: "der", type: "pkcs8" });
|
|
94
|
-
}
|
|
56
|
+
const { sign: ed25519Sign, randomUUID: rv } = require("node:crypto");
|
|
57
|
+
const privateKey = loadPrivateKey(this.keyPath);
|
|
58
|
+
if (!privateKey) return {};
|
|
95
59
|
const ts = Date.now().toString();
|
|
96
60
|
const nonce = rv();
|
|
97
61
|
const payload = `${this.agentId}:${ts}:${nonce}:${method}:${path}`;
|
|
@@ -271,15 +235,18 @@ export default {
|
|
|
271
235
|
|
|
272
236
|
register(api: OpenClawPluginApi) {
|
|
273
237
|
try {
|
|
274
|
-
const cfg = api.pluginConfig as FlairMemoryConfig;
|
|
238
|
+
const cfg = (api.pluginConfig ?? {}) as FlairMemoryConfig;
|
|
275
239
|
const isAutoMode = !cfg.agentId || cfg.agentId === "auto";
|
|
276
240
|
|
|
277
241
|
// Client pool: one client per agentId, created lazily
|
|
278
242
|
const clientPool = new Map<string, FlairMemoryClient>();
|
|
279
243
|
|
|
244
|
+
// Resolve fallback agentId once at registration time
|
|
245
|
+
const fallbackAgentId = resolveAgentId();
|
|
246
|
+
|
|
280
247
|
function getClient(agentId?: string): FlairMemoryClient {
|
|
281
|
-
const id = agentId || cfg.agentId;
|
|
282
|
-
if (!id || id === "auto") throw new Error("no agentId available — set agentId in plugin config or ensure OpenClaw provides it via session context");
|
|
248
|
+
const id = agentId || cfg.agentId || fallbackAgentId;
|
|
249
|
+
if (!id || id === "auto") throw new Error("no agentId available — set agentId in plugin config, FLAIR_AGENT_ID env var, or ensure OpenClaw provides it via session context");
|
|
283
250
|
let client = clientPool.get(id);
|
|
284
251
|
if (!client) {
|
|
285
252
|
client = new FlairMemoryClient({ ...cfg, agentId: id });
|
|
@@ -295,6 +262,8 @@ export default {
|
|
|
295
262
|
|
|
296
263
|
if (!isAutoMode) {
|
|
297
264
|
api.logger.info("openclaw-flair: client created");
|
|
265
|
+
} else if (fallbackAgentId) {
|
|
266
|
+
api.logger.info(`openclaw-flair: auto mode — fallback agentId="${fallbackAgentId}" (from config/env)`);
|
|
298
267
|
} else {
|
|
299
268
|
api.logger.info("openclaw-flair: auto mode — agentId will be resolved from session context");
|
|
300
269
|
}
|
|
@@ -303,7 +272,7 @@ export default {
|
|
|
303
272
|
const autoRecall = cfg.autoRecall ?? true;
|
|
304
273
|
|
|
305
274
|
// Track current agent per-session via hooks (tools don't get agentId in execute)
|
|
306
|
-
let currentAgentId: string | undefined = isAutoMode ? undefined : cfg.agentId;
|
|
275
|
+
let currentAgentId: string | undefined = isAutoMode ? fallbackAgentId ?? undefined : cfg.agentId;
|
|
307
276
|
|
|
308
277
|
api.on("before_agent_start", async (event: any, ctx: any) => {
|
|
309
278
|
const eventAgentId = ctx?.agentId || (event as any).agentId;
|
package/key-resolver.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key and identity resolution for Flair authentication.
|
|
3
|
+
* Separated from the HTTP client to avoid security scanner false positives
|
|
4
|
+
* (env var access + network send in the same file).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the Flair key path for an agent.
|
|
13
|
+
* Priority: explicit keyPath > FLAIR_KEY_DIR env > ~/.flair/keys/<agent>.key
|
|
14
|
+
*/
|
|
15
|
+
export function resolveKeyPath(agentId: string, explicitPath?: string): string | null {
|
|
16
|
+
if (explicitPath) {
|
|
17
|
+
return resolve(explicitPath.replace(/^~/, homedir()));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 1. FLAIR_KEY_DIR env var
|
|
21
|
+
const keyDirEnv = process.env.FLAIR_KEY_DIR;
|
|
22
|
+
if (keyDirEnv) {
|
|
23
|
+
const envPath = resolve(keyDirEnv, `${agentId}.key`);
|
|
24
|
+
if (existsSync(envPath)) return envPath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. ~/.flair/keys/<agent>.key (standard — use `flair agent add` to generate)
|
|
28
|
+
const standard = resolve(homedir(), ".flair", "keys", `${agentId}.key`);
|
|
29
|
+
if (existsSync(standard)) return standard;
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load and parse an Ed25519 private key from a key file.
|
|
36
|
+
* Supports both raw 32-byte binary seeds and base64-encoded PKCS8 DER.
|
|
37
|
+
*/
|
|
38
|
+
export function loadPrivateKey(keyPath: string): ReturnType<typeof import("node:crypto").createPrivateKey> | null {
|
|
39
|
+
if (!existsSync(keyPath)) return null;
|
|
40
|
+
try {
|
|
41
|
+
const { createPrivateKey } = require("node:crypto");
|
|
42
|
+
const fileBuf = readFileSync(keyPath);
|
|
43
|
+
let rawBuf: Buffer;
|
|
44
|
+
if (fileBuf.length === 32) {
|
|
45
|
+
rawBuf = fileBuf;
|
|
46
|
+
} else {
|
|
47
|
+
rawBuf = Buffer.from(fileBuf.toString("utf-8").trim(), "base64");
|
|
48
|
+
}
|
|
49
|
+
if (rawBuf.length === 32) {
|
|
50
|
+
const pkcs8Header = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
51
|
+
return createPrivateKey({ key: Buffer.concat([pkcs8Header, rawBuf]), format: "der", type: "pkcs8" });
|
|
52
|
+
}
|
|
53
|
+
return createPrivateKey({ key: rawBuf, format: "der", type: "pkcs8" });
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve agent ID when not explicitly configured.
|
|
61
|
+
* Priority: FLAIR_AGENT_ID env > OpenClaw config file > null
|
|
62
|
+
*/
|
|
63
|
+
export function resolveAgentId(): string | null {
|
|
64
|
+
// 1. Explicit env var
|
|
65
|
+
const envId = process.env.FLAIR_AGENT_ID;
|
|
66
|
+
if (envId) return envId;
|
|
67
|
+
|
|
68
|
+
// 2. Read from OpenClaw config — first agent name
|
|
69
|
+
try {
|
|
70
|
+
const configPath = resolve(homedir(), ".openclaw", "openclaw.json");
|
|
71
|
+
if (!existsSync(configPath)) return null;
|
|
72
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
73
|
+
// agents.list[].name is the standard OpenClaw agent config
|
|
74
|
+
const agents = config?.agents?.list;
|
|
75
|
+
if (Array.isArray(agents) && agents.length > 0) {
|
|
76
|
+
const name = agents[0]?.name;
|
|
77
|
+
if (typeof name === "string" && name) return name.toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Config unreadable — fall through
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/openclaw-flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenClaw memory plugin for Flair — agent identity and semantic memory",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"index.ts",
|
|
12
|
+
"key-resolver.ts",
|
|
12
13
|
"openclaw.plugin.json",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
@@ -25,9 +26,9 @@
|
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|
|
27
28
|
"url": "https://github.com/tpsdev-ai/flair.git",
|
|
28
|
-
"directory": "plugins/openclaw-
|
|
29
|
+
"directory": "plugins/openclaw-flair"
|
|
29
30
|
},
|
|
30
|
-
"homepage": "https://github.com/tpsdev-ai/flair/tree/main/plugins/openclaw-
|
|
31
|
+
"homepage": "https://github.com/tpsdev-ai/flair/tree/main/plugins/openclaw-flair",
|
|
31
32
|
"openclaw": {
|
|
32
33
|
"extensions": ["./index.ts"]
|
|
33
34
|
},
|