@tpsdev-ai/openclaw-flair 0.1.4 → 0.3.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 +41 -197
- package/key-resolver.ts +37 -0
- package/package.json +6 -4
package/index.ts
CHANGED
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
* - agent_end hook → auto-capture from conversation
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { 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
|
+
import { FlairClient } from "@tpsdev-ai/flair-client";
|
|
21
22
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
23
|
+
import { resolveAgentId } from "./key-resolver.js";
|
|
22
24
|
|
|
23
25
|
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
24
26
|
|
|
@@ -36,166 +38,8 @@ const DEFAULT_URL = "http://127.0.0.1:9926";
|
|
|
36
38
|
const DEFAULT_MAX_RECALL = 5;
|
|
37
39
|
const DEFAULT_MAX_BOOTSTRAP_TOKENS = 4000;
|
|
38
40
|
|
|
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
|
-
// ─── Flair HTTP Client ────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
class FlairMemoryClient {
|
|
59
|
-
private readonly baseUrl: string;
|
|
60
|
-
private readonly agentId: string;
|
|
61
|
-
private readonly keyPath: string | null;
|
|
62
|
-
|
|
63
|
-
constructor(config: FlairMemoryConfig) {
|
|
64
|
-
this.baseUrl = (config.url ?? DEFAULT_URL).replace(/\/$/, "");
|
|
65
|
-
this.agentId = config.agentId;
|
|
66
|
-
this.keyPath = config.keyPath
|
|
67
|
-
? resolve(config.keyPath.replace(/^~/, homedir()))
|
|
68
|
-
: resolveDefaultKeyPath(config.agentId);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private buildAuthHeader(method: string, path: string): Record<string, string> {
|
|
72
|
-
if (!this.keyPath || !existsSync(this.keyPath)) return {};
|
|
73
|
-
try {
|
|
74
|
-
const { sign: ed25519Sign, createPrivateKey, randomUUID: rv } = require("node:crypto");
|
|
75
|
-
// Read raw bytes — supports both:
|
|
76
|
-
// - 32-byte binary seed (written by `flair init`)
|
|
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
|
-
}
|
|
95
|
-
const ts = Date.now().toString();
|
|
96
|
-
const nonce = rv();
|
|
97
|
-
const payload = `${this.agentId}:${ts}:${nonce}:${method}:${path}`;
|
|
98
|
-
const sig = ed25519Sign(null, Buffer.from(payload), privateKey);
|
|
99
|
-
return { Authorization: `TPS-Ed25519 ${this.agentId}:${ts}:${nonce}:${sig.toString("base64")}` };
|
|
100
|
-
} catch {
|
|
101
|
-
return {};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
106
|
-
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
107
|
-
method,
|
|
108
|
-
headers: {
|
|
109
|
-
"Content-Type": "application/json",
|
|
110
|
-
...this.buildAuthHeader(method, path),
|
|
111
|
-
},
|
|
112
|
-
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
113
|
-
signal: AbortSignal.timeout(10_000),
|
|
114
|
-
});
|
|
115
|
-
if (!res.ok) {
|
|
116
|
-
const text = await res.text().catch(() => "");
|
|
117
|
-
throw new Error(`Flair ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
|
|
118
|
-
}
|
|
119
|
-
const text = await res.text();
|
|
120
|
-
return text ? (JSON.parse(text) as T) : ({} as T);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async writeMemory(id: string, content: string, opts: { durability?: string; type?: string; tags?: string[]; supersedes?: string } = {}): Promise<void> {
|
|
124
|
-
const record: Record<string, unknown> = {
|
|
125
|
-
id,
|
|
126
|
-
agentId: this.agentId,
|
|
127
|
-
content,
|
|
128
|
-
durability: opts.durability ?? "standard",
|
|
129
|
-
type: opts.type ?? "session",
|
|
130
|
-
createdAt: new Date().toISOString(),
|
|
131
|
-
};
|
|
132
|
-
if (opts.tags?.length) record.tags = opts.tags;
|
|
133
|
-
if (opts.supersedes) record.supersedes = opts.supersedes;
|
|
134
|
-
await this.request("PUT", `/Memory/${id}`, record);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async searchMemories(query: string, limit: number): Promise<Array<{ id: string; content: string; score: number; tags?: string[] }>> {
|
|
138
|
-
const result = await this.request<{ results?: Array<{ id: string; content: string; score?: number; similarity?: number; memory?: { id: string; content: string; tags?: string[] } }> }>(
|
|
139
|
-
"POST",
|
|
140
|
-
"/SemanticSearch",
|
|
141
|
-
{ agentId: this.agentId, q: query, limit },
|
|
142
|
-
);
|
|
143
|
-
return (result.results ?? []).map((r) => ({
|
|
144
|
-
id: r.id ?? r.memory?.id ?? "",
|
|
145
|
-
content: r.content ?? r.memory?.content ?? "",
|
|
146
|
-
score: r.score ?? r.similarity ?? 0,
|
|
147
|
-
tags: r.memory?.tags,
|
|
148
|
-
}));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async getMemory(id: string): Promise<{ id: string; content: string; createdAt?: string } | null> {
|
|
152
|
-
try {
|
|
153
|
-
return await this.request("GET", `/Memory/${id}`);
|
|
154
|
-
} catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async bootstrap(opts: { days?: number } = {}): Promise<string> {
|
|
160
|
-
try {
|
|
161
|
-
const days = opts.days ?? 7;
|
|
162
|
-
const since = new Date(Date.now() - days * 86_400_000).toISOString();
|
|
163
|
-
const result = await this.request<{ context?: string; text?: string }>(
|
|
164
|
-
"POST",
|
|
165
|
-
"/BootstrapMemories",
|
|
166
|
-
{ agentId: this.agentId, since },
|
|
167
|
-
);
|
|
168
|
-
return result.context ?? result.text ?? "";
|
|
169
|
-
} catch {
|
|
170
|
-
return "";
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async getSoul(key: string): Promise<{ id: string; key: string; value: string; contentHash?: string } | null> {
|
|
175
|
-
try {
|
|
176
|
-
return await this.request("GET", `/Soul/${this.agentId}-${key}`);
|
|
177
|
-
} catch {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async writeSoul(key: string, value: string, contentHash?: string): Promise<void> {
|
|
183
|
-
const record: Record<string, unknown> = {
|
|
184
|
-
id: `${this.agentId}-${key}`,
|
|
185
|
-
agentId: this.agentId,
|
|
186
|
-
key,
|
|
187
|
-
value,
|
|
188
|
-
durability: "permanent",
|
|
189
|
-
createdAt: new Date().toISOString(),
|
|
190
|
-
};
|
|
191
|
-
if (contentHash) record.contentHash = contentHash;
|
|
192
|
-
await this.request("PUT", `/Soul/${this.agentId}-${key}`, record);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
41
|
// ─── Workspace sync helpers ───────────────────────────────────────────────────
|
|
197
42
|
|
|
198
|
-
/** Files to sync from workspace to Flair soul entries (spec: OPS-workspace-sync) */
|
|
199
43
|
const WORKSPACE_SOUL_FILES: Record<string, string> = {
|
|
200
44
|
"SOUL.md": "soul",
|
|
201
45
|
"IDENTITY.md": "identity",
|
|
@@ -203,19 +47,14 @@ const WORKSPACE_SOUL_FILES: Record<string, string> = {
|
|
|
203
47
|
"AGENTS.md": "workspace-rules",
|
|
204
48
|
};
|
|
205
49
|
|
|
206
|
-
/** Max size for a single soul entry (chars). Files larger are truncated. */
|
|
207
50
|
const MAX_SOUL_VALUE = 8000;
|
|
208
51
|
|
|
209
52
|
function hashContent(content: string): string {
|
|
210
53
|
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
211
54
|
}
|
|
212
55
|
|
|
213
|
-
/**
|
|
214
|
-
* Sync workspace files to Flair soul entries.
|
|
215
|
-
* Only writes when content has changed (hash comparison).
|
|
216
|
-
*/
|
|
217
56
|
async function syncWorkspaceToFlair(
|
|
218
|
-
client:
|
|
57
|
+
client: FlairClient,
|
|
219
58
|
agentId: string,
|
|
220
59
|
logger: { info: Function; warn: Function },
|
|
221
60
|
): Promise<number> {
|
|
@@ -233,12 +72,10 @@ async function syncWorkspaceToFlair(
|
|
|
233
72
|
if (content.length > MAX_SOUL_VALUE) content = content.slice(0, MAX_SOUL_VALUE) + "\n…(truncated)";
|
|
234
73
|
|
|
235
74
|
const newHash = hashContent(content);
|
|
75
|
+
const existing = await client.soul.get(soulKey);
|
|
76
|
+
if ((existing as any)?.contentHash === newHash) continue;
|
|
236
77
|
|
|
237
|
-
|
|
238
|
-
const existing = await client.getSoul(soulKey);
|
|
239
|
-
if (existing?.contentHash === newHash) continue; // unchanged
|
|
240
|
-
|
|
241
|
-
await client.writeSoul(soulKey, content, newHash);
|
|
78
|
+
await client.soul.set(soulKey, content);
|
|
242
79
|
synced++;
|
|
243
80
|
logger.info(`openclaw-flair: synced ${filename} → soul:${soulKey} (hash=${newHash})`);
|
|
244
81
|
} catch (err: any) {
|
|
@@ -274,27 +111,35 @@ export default {
|
|
|
274
111
|
const cfg = (api.pluginConfig ?? {}) as FlairMemoryConfig;
|
|
275
112
|
const isAutoMode = !cfg.agentId || cfg.agentId === "auto";
|
|
276
113
|
|
|
277
|
-
// Client pool: one
|
|
278
|
-
const clientPool = new Map<string,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
114
|
+
// Client pool: one FlairClient per agentId, created lazily
|
|
115
|
+
const clientPool = new Map<string, FlairClient>();
|
|
116
|
+
|
|
117
|
+
// Resolve fallback agentId once at registration time
|
|
118
|
+
const fallbackAgentId = resolveAgentId();
|
|
119
|
+
|
|
120
|
+
function getClient(agentId?: string): FlairClient {
|
|
121
|
+
const id = agentId || cfg.agentId || fallbackAgentId;
|
|
122
|
+
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
123
|
let client = clientPool.get(id);
|
|
284
124
|
if (!client) {
|
|
285
|
-
client = new
|
|
125
|
+
client = new FlairClient({
|
|
126
|
+
url: cfg.url ?? DEFAULT_URL,
|
|
127
|
+
agentId: id,
|
|
128
|
+
keyPath: cfg.keyPath,
|
|
129
|
+
});
|
|
286
130
|
clientPool.set(id, client);
|
|
287
131
|
}
|
|
288
132
|
return client;
|
|
289
133
|
}
|
|
290
134
|
|
|
291
|
-
// For non-auto mode, pre-create the client
|
|
292
135
|
if (!isAutoMode) {
|
|
293
136
|
getClient(cfg.agentId);
|
|
294
137
|
}
|
|
295
138
|
|
|
296
139
|
if (!isAutoMode) {
|
|
297
140
|
api.logger.info("openclaw-flair: client created");
|
|
141
|
+
} else if (fallbackAgentId) {
|
|
142
|
+
api.logger.info(`openclaw-flair: auto mode — fallback agentId="${fallbackAgentId}" (from config/env)`);
|
|
298
143
|
} else {
|
|
299
144
|
api.logger.info("openclaw-flair: auto mode — agentId will be resolved from session context");
|
|
300
145
|
}
|
|
@@ -302,14 +147,12 @@ export default {
|
|
|
302
147
|
const autoCapture = cfg.autoCapture ?? true;
|
|
303
148
|
const autoRecall = cfg.autoRecall ?? true;
|
|
304
149
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
150
|
+
let currentAgentId: string | undefined = isAutoMode ? fallbackAgentId ?? undefined : cfg.agentId;
|
|
151
|
+
|
|
308
152
|
api.on("before_agent_start", async (event: any, ctx: any) => {
|
|
309
153
|
const eventAgentId = ctx?.agentId || (event as any).agentId;
|
|
310
154
|
if (eventAgentId) currentAgentId = eventAgentId;
|
|
311
155
|
|
|
312
|
-
// Sync workspace files → Flair soul entries (hash-based, only on change)
|
|
313
156
|
if (eventAgentId) {
|
|
314
157
|
try {
|
|
315
158
|
const client = getClient(eventAgentId);
|
|
@@ -320,9 +163,8 @@ export default {
|
|
|
320
163
|
}
|
|
321
164
|
}
|
|
322
165
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
function getCurrentClient(): FlairMemoryClient {
|
|
166
|
+
|
|
167
|
+
function getCurrentClient(): FlairClient {
|
|
326
168
|
return getClient(currentAgentId);
|
|
327
169
|
}
|
|
328
170
|
|
|
@@ -345,7 +187,7 @@ export default {
|
|
|
345
187
|
const { query, limit = maxRecall } = params as { query: string; limit?: number };
|
|
346
188
|
try {
|
|
347
189
|
const client = getCurrentClient();
|
|
348
|
-
const results = await client.
|
|
190
|
+
const results = await client.memory.search(query, { limit });
|
|
349
191
|
if (results.length === 0) {
|
|
350
192
|
return { content: [{ type: "text", text: "No relevant memories found." }], details: { count: 0 } };
|
|
351
193
|
}
|
|
@@ -398,10 +240,14 @@ export default {
|
|
|
398
240
|
durability?: string; type?: string; supersedes?: string;
|
|
399
241
|
};
|
|
400
242
|
try {
|
|
401
|
-
const agentId = currentAgentId || cfg.agentId;
|
|
402
243
|
const client = getCurrentClient();
|
|
403
|
-
const memId = `${agentId}-${Date.now()}`;
|
|
404
|
-
await client.
|
|
244
|
+
const memId = `${client.agentId}-${Date.now()}`;
|
|
245
|
+
await client.memory.write(text, {
|
|
246
|
+
id: memId,
|
|
247
|
+
tags,
|
|
248
|
+
durability: durability as any,
|
|
249
|
+
type: type as any,
|
|
250
|
+
});
|
|
405
251
|
return {
|
|
406
252
|
content: [{ type: "text", text: `Memory stored (id: ${memId})` }],
|
|
407
253
|
details: { id: memId },
|
|
@@ -429,7 +275,7 @@ export default {
|
|
|
429
275
|
const { id } = params as { id: string };
|
|
430
276
|
try {
|
|
431
277
|
const client = getCurrentClient();
|
|
432
|
-
const mem = await client.
|
|
278
|
+
const mem = await client.memory.get(id);
|
|
433
279
|
if (!mem) return { content: [{ type: "text", text: `Memory ${id} not found.` }], details: {} };
|
|
434
280
|
return {
|
|
435
281
|
content: [{ type: "text", text: mem.content }],
|
|
@@ -448,10 +294,10 @@ export default {
|
|
|
448
294
|
if (autoRecall) {
|
|
449
295
|
api.on("before_agent_start", async (event: any, ctx: any) => {
|
|
450
296
|
try {
|
|
451
|
-
// Ensure agentId is set (in case this fires before the tracking hook)
|
|
452
297
|
if (ctx?.agentId && !currentAgentId) currentAgentId = ctx.agentId;
|
|
453
298
|
const client = getCurrentClient();
|
|
454
|
-
const
|
|
299
|
+
const result = await client.bootstrap({ maxTokens: cfg.maxBootstrapTokens ?? DEFAULT_MAX_BOOTSTRAP_TOKENS });
|
|
300
|
+
const context = result.context;
|
|
455
301
|
if (context && typeof context === "string" && context.trim().length > 0) {
|
|
456
302
|
const truncated = context.slice(0, (cfg.maxBootstrapTokens ?? DEFAULT_MAX_BOOTSTRAP_TOKENS) * 4);
|
|
457
303
|
event.injectContext?.(`\n## Memory Context (from Flair)\n\n${truncated}\n`);
|
|
@@ -468,7 +314,6 @@ export default {
|
|
|
468
314
|
if (autoCapture) {
|
|
469
315
|
api.on("agent_end", async (event) => {
|
|
470
316
|
try {
|
|
471
|
-
const agentId = currentAgentId || cfg.agentId;
|
|
472
317
|
const client = getCurrentClient();
|
|
473
318
|
const messages = (event.messages ?? []) as Array<{ role: string; content?: string }>;
|
|
474
319
|
let stored = 0;
|
|
@@ -477,10 +322,9 @@ export default {
|
|
|
477
322
|
const text = typeof msg.content === "string" ? msg.content : "";
|
|
478
323
|
if (!text || !shouldCapture(text)) continue;
|
|
479
324
|
const excerpt = excerptForCapture(text);
|
|
480
|
-
|
|
481
|
-
await client.writeMemory(memId, excerpt, { type: "session", tags: ["auto-captured"] });
|
|
325
|
+
await client.memory.write(excerpt, { type: "session", tags: ["auto-captured"] });
|
|
482
326
|
stored++;
|
|
483
|
-
if (stored >= 3) break;
|
|
327
|
+
if (stored >= 3) break;
|
|
484
328
|
}
|
|
485
329
|
if (stored > 0) api.logger.info(`openclaw-flair: auto-captured ${stored} memories`);
|
|
486
330
|
} catch (err: any) {
|
package/key-resolver.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw-specific identity resolution.
|
|
3
|
+
*
|
|
4
|
+
* Key path and private key loading are now handled by @tpsdev-ai/flair-client.
|
|
5
|
+
* This file only contains resolveAgentId() which reads OpenClaw config — something
|
|
6
|
+
* the generic flair-client shouldn't know about.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve agent ID when not explicitly configured.
|
|
15
|
+
* Priority: FLAIR_AGENT_ID env > OpenClaw config file > null
|
|
16
|
+
*/
|
|
17
|
+
export function resolveAgentId(): string | null {
|
|
18
|
+
// 1. Explicit env var
|
|
19
|
+
const envId = process.env.FLAIR_AGENT_ID;
|
|
20
|
+
if (envId) return envId;
|
|
21
|
+
|
|
22
|
+
// 2. Read from OpenClaw config — first agent name
|
|
23
|
+
try {
|
|
24
|
+
const configPath = resolve(homedir(), ".openclaw", "openclaw.json");
|
|
25
|
+
if (!existsSync(configPath)) return null;
|
|
26
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
27
|
+
const agents = config?.agents?.list;
|
|
28
|
+
if (Array.isArray(agents) && agents.length > 0) {
|
|
29
|
+
const name = agents[0]?.name;
|
|
30
|
+
if (typeof name === "string" && name) return name.toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// Config unreadable — fall through
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/openclaw-flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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
|
},
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"node": ">=22"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@sinclair/typebox": "^0.34.0"
|
|
42
|
+
"@sinclair/typebox": "^0.34.0",
|
|
43
|
+
"@tpsdev-ai/flair-client": "^0.1.0"
|
|
42
44
|
}
|
|
43
45
|
}
|