@tpsdev-ai/flair 0.4.0 → 0.4.2
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 +272 -45
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,12 @@ function defaultDataDir() {
|
|
|
19
19
|
return join(homedir(), ".flair", "data");
|
|
20
20
|
}
|
|
21
21
|
function configPath() {
|
|
22
|
-
|
|
22
|
+
// Check both .yaml and .yml extensions
|
|
23
|
+
const yamlPath = join(homedir(), ".flair", "config.yaml");
|
|
24
|
+
const ymlPath = join(homedir(), ".flair", "config.yml");
|
|
25
|
+
if (existsSync(ymlPath) && !existsSync(yamlPath))
|
|
26
|
+
return ymlPath;
|
|
27
|
+
return yamlPath;
|
|
23
28
|
}
|
|
24
29
|
function readPortFromConfig() {
|
|
25
30
|
try {
|
|
@@ -415,7 +420,7 @@ agent
|
|
|
415
420
|
.command("add <id>")
|
|
416
421
|
.description("Register a new agent in a running Flair instance")
|
|
417
422
|
.option("--name <name>", "Display name (defaults to id)")
|
|
418
|
-
.option("--port <port>", "Harper HTTP port"
|
|
423
|
+
.option("--port <port>", "Harper HTTP port")
|
|
419
424
|
.option("--admin-pass <pass>", "Admin password for registration")
|
|
420
425
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
421
426
|
.option("--ops-port <port>", "Harper operations API port")
|
|
@@ -465,7 +470,7 @@ agent
|
|
|
465
470
|
agent
|
|
466
471
|
.command("rotate-key <id>")
|
|
467
472
|
.description("Rotate an agent's Ed25519 keypair")
|
|
468
|
-
.option("--port <port>", "Harper HTTP port"
|
|
473
|
+
.option("--port <port>", "Harper HTTP port")
|
|
469
474
|
.option("--ops-port <port>", "Harper operations API port")
|
|
470
475
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
471
476
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
@@ -543,7 +548,7 @@ agent
|
|
|
543
548
|
.command("remove <id>")
|
|
544
549
|
.description("Remove an agent and all its data from Flair")
|
|
545
550
|
.option("--keep-keys", "Do not delete key files from disk")
|
|
546
|
-
.option("--port <port>", "Harper HTTP port"
|
|
551
|
+
.option("--port <port>", "Harper HTTP port")
|
|
547
552
|
.option("--ops-port <port>", "Harper operations API port")
|
|
548
553
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
549
554
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
@@ -655,7 +660,7 @@ program
|
|
|
655
660
|
.command("grant <from-agent> <to-agent>")
|
|
656
661
|
.description("Grant an agent read access to another agent's memories")
|
|
657
662
|
.option("--scope <scope>", "Grant scope: read or search", "read")
|
|
658
|
-
.option("--port <port>", "Harper HTTP port"
|
|
663
|
+
.option("--port <port>", "Harper HTTP port")
|
|
659
664
|
.option("--ops-port <port>", "Harper operations API port")
|
|
660
665
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
661
666
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys (for from-agent Ed25519 auth)")
|
|
@@ -704,7 +709,7 @@ program
|
|
|
704
709
|
program
|
|
705
710
|
.command("revoke <from-agent> <to-agent>")
|
|
706
711
|
.description("Revoke a memory grant between two agents")
|
|
707
|
-
.option("--port <port>", "Harper HTTP port"
|
|
712
|
+
.option("--port <port>", "Harper HTTP port")
|
|
708
713
|
.option("--ops-port <port>", "Harper operations API port")
|
|
709
714
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
710
715
|
.action(async (fromAgent, toAgent, opts) => {
|
|
@@ -1185,36 +1190,258 @@ program
|
|
|
1185
1190
|
if (failed > 0)
|
|
1186
1191
|
process.exit(1);
|
|
1187
1192
|
});
|
|
1188
|
-
// ───
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
.
|
|
1192
|
-
.
|
|
1193
|
-
.option("--
|
|
1194
|
-
.
|
|
1195
|
-
const kp = nacl.sign.keyPair();
|
|
1196
|
-
const now = new Date().toISOString();
|
|
1197
|
-
const agentRecord = await api("POST", "/Agent", {
|
|
1198
|
-
id: opts.id, name: opts.name, role: opts.role,
|
|
1199
|
-
publicKey: b64(kp.publicKey), createdAt: now, updatedAt: now,
|
|
1200
|
-
});
|
|
1201
|
-
console.log(JSON.stringify({ agent: agentRecord, privateKey: b64(kp.secretKey) }, null, 2));
|
|
1202
|
-
});
|
|
1203
|
-
identity.command("show").argument("<id>").action(async (id) => console.log(JSON.stringify(await api("GET", `/Agent/${id}`), null, 2)));
|
|
1204
|
-
identity.command("list").action(async () => console.log(JSON.stringify(await api("GET", "/Agent"), null, 2)));
|
|
1205
|
-
identity.command("add-integration")
|
|
1206
|
-
.requiredOption("--agent <agentId>")
|
|
1207
|
-
.requiredOption("--platform <platform>")
|
|
1208
|
-
.requiredOption("--encrypted-credential <ciphertext>")
|
|
1193
|
+
// ─── flair doctor ─────────────────────────────────────────────────────────────
|
|
1194
|
+
program
|
|
1195
|
+
.command("doctor")
|
|
1196
|
+
.description("Diagnose common Flair problems and suggest fixes")
|
|
1197
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1198
|
+
.option("--fix", "Automatically fix issues where possible")
|
|
1199
|
+
.option("--dry-run", "Show what --fix would do without making changes")
|
|
1209
1200
|
.action(async (opts) => {
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1201
|
+
const port = opts.port ? Number(opts.port) : (readPortFromConfig() ?? DEFAULT_PORT);
|
|
1202
|
+
const autoFix = opts.fix ?? false;
|
|
1203
|
+
const dryRun = opts.dryRun ?? false;
|
|
1204
|
+
if (dryRun && !autoFix) {
|
|
1205
|
+
console.log(" ℹ️ --dry-run only has effect with --fix\n");
|
|
1206
|
+
}
|
|
1207
|
+
let effectivePort = port;
|
|
1208
|
+
let baseUrl = `http://127.0.0.1:${port}`;
|
|
1209
|
+
let issues = 0;
|
|
1210
|
+
let harperResponding = false;
|
|
1211
|
+
console.log("\n🩺 Flair Doctor\n");
|
|
1212
|
+
// Helper: try to reach Harper on a given port
|
|
1213
|
+
async function probePort(p) {
|
|
1214
|
+
try {
|
|
1215
|
+
const res = await fetch(`http://127.0.0.1:${p}/Health`, { signal: AbortSignal.timeout(3000) });
|
|
1216
|
+
return res.status > 0;
|
|
1217
|
+
}
|
|
1218
|
+
catch {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
// Helper: discover what port a Harper PID is listening on
|
|
1223
|
+
async function discoverPortFromPid(pid) {
|
|
1224
|
+
// Defense-in-depth: caller already validates, but re-check here
|
|
1225
|
+
if (!/^\d+$/.test(pid))
|
|
1226
|
+
return null;
|
|
1227
|
+
try {
|
|
1228
|
+
const { execSync } = await import("node:child_process");
|
|
1229
|
+
const out = execSync(`lsof -aPi -p ${pid} -sTCP:LISTEN -Fn 2>/dev/null || true`, { encoding: "utf-8" });
|
|
1230
|
+
const match = out.match(/:(\d+)$/m);
|
|
1231
|
+
if (match)
|
|
1232
|
+
return Number(match[1]);
|
|
1233
|
+
}
|
|
1234
|
+
catch { /* ignore */ }
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
// 1. Port check — is something listening?
|
|
1238
|
+
// First, check PID file so we can cross-reference
|
|
1239
|
+
const dataDir0 = defaultDataDir();
|
|
1240
|
+
const pidFile0 = join(dataDir0, "hdb.pid");
|
|
1241
|
+
let pidAlive = false;
|
|
1242
|
+
let pidValue = "";
|
|
1243
|
+
if (existsSync(pidFile0)) {
|
|
1244
|
+
const rawPid = (await import("node:fs")).readFileSync(pidFile0, "utf-8").trim();
|
|
1245
|
+
// Strict integer validation — PID must be purely numeric to prevent injection
|
|
1246
|
+
if (/^\d+$/.test(rawPid)) {
|
|
1247
|
+
pidValue = rawPid;
|
|
1248
|
+
try {
|
|
1249
|
+
process.kill(Number(pidValue), 0);
|
|
1250
|
+
pidAlive = true;
|
|
1251
|
+
}
|
|
1252
|
+
catch { /* dead */ }
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
console.log(` ⚠️ PID file contains non-numeric value: ${pidFile0} — skipping`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (await probePort(port)) {
|
|
1259
|
+
console.log(` ✅ Harper responding on port ${port}`);
|
|
1260
|
+
harperResponding = true;
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
// Port didn't respond — but if PID is alive, try to find the real port
|
|
1264
|
+
let discoveredPort = null;
|
|
1265
|
+
if (pidAlive) {
|
|
1266
|
+
discoveredPort = await discoverPortFromPid(pidValue);
|
|
1267
|
+
if (discoveredPort && discoveredPort !== port && await probePort(discoveredPort)) {
|
|
1268
|
+
console.log(` ⚠️ Harper not on expected port ${port}, but responding on port ${discoveredPort} (PID ${pidValue})`);
|
|
1269
|
+
console.log(` Your config says port ${port} but Harper is actually running on ${discoveredPort}`);
|
|
1270
|
+
if (autoFix) {
|
|
1271
|
+
if (dryRun) {
|
|
1272
|
+
console.log(` Would update config to port ${discoveredPort}`);
|
|
1273
|
+
}
|
|
1274
|
+
else {
|
|
1275
|
+
writeConfig(discoveredPort);
|
|
1276
|
+
console.log(` ✅ Updated config to port ${discoveredPort}`);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
console.log(` Fix: flair doctor --fix (updates config to match running port)`);
|
|
1281
|
+
}
|
|
1282
|
+
effectivePort = discoveredPort;
|
|
1283
|
+
baseUrl = `http://127.0.0.1:${discoveredPort}`;
|
|
1284
|
+
harperResponding = true;
|
|
1285
|
+
issues++;
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
console.log(` ❌ Harper process alive (PID ${pidValue}) but not responding on any detected port`);
|
|
1289
|
+
console.log(` Fix: flair restart`);
|
|
1290
|
+
issues++;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
// No live PID — Harper genuinely isn't running
|
|
1295
|
+
// Check if something else grabbed the port
|
|
1296
|
+
try {
|
|
1297
|
+
const { execSync } = await import("node:child_process");
|
|
1298
|
+
const lsof = execSync(`lsof -ti :${port}`, { encoding: "utf-8" }).trim();
|
|
1299
|
+
if (lsof) {
|
|
1300
|
+
console.log(` ❌ Nothing responding on port ${port} (port occupied by PID ${lsof})`);
|
|
1301
|
+
console.log(` Fix: kill ${lsof} && flair restart`);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
console.log(` ❌ Harper is not running`);
|
|
1305
|
+
console.log(` Fix: flair restart`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
catch {
|
|
1309
|
+
console.log(` ❌ Harper is not running`);
|
|
1310
|
+
if (autoFix) {
|
|
1311
|
+
if (dryRun) {
|
|
1312
|
+
console.log(` Would run: flair restart`);
|
|
1313
|
+
}
|
|
1314
|
+
else {
|
|
1315
|
+
console.log(` Attempting restart...`);
|
|
1316
|
+
try {
|
|
1317
|
+
const { execSync } = await import("node:child_process");
|
|
1318
|
+
execSync(`${process.argv[0]} ${process.argv[1]} restart --port ${port}`, { stdio: "inherit" });
|
|
1319
|
+
console.log(` ✅ Restart attempted`);
|
|
1320
|
+
}
|
|
1321
|
+
catch {
|
|
1322
|
+
console.log(` ❌ Restart failed — try: flair init --agent-id <your-agent>`);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
console.log(` Fix: flair restart`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
issues++;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
// 2. Keys directory
|
|
1334
|
+
const keysDir = defaultKeysDir();
|
|
1335
|
+
if (existsSync(keysDir)) {
|
|
1336
|
+
const keyFiles = (await import("node:fs")).readdirSync(keysDir).filter((f) => f.endsWith(".key"));
|
|
1337
|
+
if (keyFiles.length > 0) {
|
|
1338
|
+
console.log(` ✅ Keys found: ${keyFiles.length} agent(s) in ${keysDir}`);
|
|
1339
|
+
}
|
|
1340
|
+
else {
|
|
1341
|
+
console.log(` ❌ Keys directory exists but no .key files found`);
|
|
1342
|
+
console.log(` Fix: flair init --agent-id <your-agent>`);
|
|
1343
|
+
issues++;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
console.log(` ❌ Keys directory missing: ${keysDir}`);
|
|
1348
|
+
console.log(` Fix: flair init --agent-id <your-agent>`);
|
|
1349
|
+
issues++;
|
|
1350
|
+
}
|
|
1351
|
+
// 3. Config file
|
|
1352
|
+
const cfgPath = configPath();
|
|
1353
|
+
if (existsSync(cfgPath)) {
|
|
1354
|
+
const savedPort = readPortFromConfig();
|
|
1355
|
+
console.log(` ✅ Config: ${cfgPath} (port: ${savedPort ?? "default"})`);
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
console.log(` ⚠️ No config file at ${cfgPath} — using defaults`);
|
|
1359
|
+
}
|
|
1360
|
+
// 4. Embeddings check (only if Harper is responding)
|
|
1361
|
+
if (harperResponding) {
|
|
1362
|
+
try {
|
|
1363
|
+
const testRes = await fetch(`${baseUrl}/SemanticSearch`, {
|
|
1364
|
+
method: "POST",
|
|
1365
|
+
headers: { "Content-Type": "application/json" },
|
|
1366
|
+
body: JSON.stringify({ q: "test", limit: 1 }),
|
|
1367
|
+
signal: AbortSignal.timeout(10000),
|
|
1368
|
+
});
|
|
1369
|
+
if (testRes.ok) {
|
|
1370
|
+
const data = await testRes.json();
|
|
1371
|
+
if (data._warning) {
|
|
1372
|
+
console.log(` ⚠️ Embeddings: keyword-only (${data._warning})`);
|
|
1373
|
+
console.log(` Semantic search quality is degraded`);
|
|
1374
|
+
console.log(` Check: ls ~/.npm-global/lib/node_modules/@tpsdev-ai/flair/models/`);
|
|
1375
|
+
issues++;
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
console.log(` ✅ Embeddings: semantic search operational`);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
else if (testRes.status === 401) {
|
|
1382
|
+
console.log(` ⚠️ Embeddings: cannot verify (auth required for SemanticSearch)`);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
catch { /* fetch error, already flagged */ }
|
|
1386
|
+
}
|
|
1387
|
+
// 5. Stale PID file (skip if already reported in port check)
|
|
1388
|
+
const dataDir = defaultDataDir();
|
|
1389
|
+
const pidFile = join(dataDir, "hdb.pid");
|
|
1390
|
+
if (existsSync(pidFile)) {
|
|
1391
|
+
const pidContent = (await import("node:fs")).readFileSync(pidFile, "utf-8").trim();
|
|
1392
|
+
try {
|
|
1393
|
+
process.kill(Number(pidContent), 0);
|
|
1394
|
+
if (harperResponding) {
|
|
1395
|
+
console.log(` ✅ PID file: ${pidFile} (process ${pidContent} is alive)`);
|
|
1396
|
+
}
|
|
1397
|
+
// If not responding, we already reported the issue in step 1
|
|
1398
|
+
}
|
|
1399
|
+
catch {
|
|
1400
|
+
console.log(` ❌ Stale PID file: ${pidFile} (process ${pidContent} is dead)`);
|
|
1401
|
+
if (autoFix) {
|
|
1402
|
+
if (dryRun) {
|
|
1403
|
+
console.log(` Would remove: ${pidFile}`);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
(await import("node:fs")).unlinkSync(pidFile);
|
|
1407
|
+
console.log(` ✅ Removed stale PID file`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
console.log(` Fix: rm ${pidFile} && flair restart`);
|
|
1412
|
+
}
|
|
1413
|
+
issues++;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
// 6. Data directory
|
|
1417
|
+
if (existsSync(dataDir)) {
|
|
1418
|
+
console.log(` ✅ Data directory: ${dataDir}`);
|
|
1419
|
+
}
|
|
1420
|
+
else {
|
|
1421
|
+
// Check ~/harper/ (common alternative)
|
|
1422
|
+
const altDir = join(homedir(), "harper");
|
|
1423
|
+
if (existsSync(altDir)) {
|
|
1424
|
+
console.log(` ⚠️ Data at ~/harper/ (not ~/.flair/data) — old install location`);
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
console.log(` ❌ No data directory found`);
|
|
1428
|
+
console.log(` Fix: flair init --agent-id <your-agent>`);
|
|
1429
|
+
issues++;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
// Summary
|
|
1433
|
+
console.log("");
|
|
1434
|
+
if (issues === 0) {
|
|
1435
|
+
console.log(" 🟢 No issues found");
|
|
1436
|
+
}
|
|
1437
|
+
else {
|
|
1438
|
+
console.log(` 🔴 ${issues} issue${issues > 1 ? "s" : ""} found — see fixes above`);
|
|
1439
|
+
}
|
|
1440
|
+
console.log("");
|
|
1441
|
+
if (issues > 0)
|
|
1442
|
+
process.exit(1);
|
|
1217
1443
|
});
|
|
1444
|
+
// ─── Memory and Soul commands ────────────────────────────────────────────────
|
|
1218
1445
|
const memory = program.command("memory").description("Manage agent memories");
|
|
1219
1446
|
memory.command("add").requiredOption("--agent <id>").requiredOption("--content <text>")
|
|
1220
1447
|
.option("--durability <d>", "standard").option("--tags <csv>")
|
|
@@ -1240,7 +1467,7 @@ program
|
|
|
1240
1467
|
.description("Search memories by meaning (shortcut for memory search)")
|
|
1241
1468
|
.requiredOption("--agent <id>", "Agent ID")
|
|
1242
1469
|
.option("--limit <n>", "Max results", "5")
|
|
1243
|
-
.option("--port <port>", "Harper HTTP port"
|
|
1470
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1244
1471
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1245
1472
|
.option("--key <path>", "Ed25519 private key path")
|
|
1246
1473
|
.action(async (query, opts) => {
|
|
@@ -1285,7 +1512,7 @@ program
|
|
|
1285
1512
|
.description("Cold-start context: get soul + recent memories as formatted text")
|
|
1286
1513
|
.requiredOption("--agent <id>", "Agent ID")
|
|
1287
1514
|
.option("--max-tokens <n>", "Maximum tokens in output", "4000")
|
|
1288
|
-
.option("--port <port>", "Harper HTTP port"
|
|
1515
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1289
1516
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1290
1517
|
.option("--key <path>", "Ed25519 private key path")
|
|
1291
1518
|
.action(async (opts) => {
|
|
@@ -1335,7 +1562,7 @@ program
|
|
|
1335
1562
|
.description("Export agents, memories, and souls to a JSON archive")
|
|
1336
1563
|
.option("--output <path>", "Output file path (default: ~/.flair/backups/flair-backup-<timestamp>.json)")
|
|
1337
1564
|
.option("--agents <ids>", "Comma-separated agent IDs to include (default: all)")
|
|
1338
|
-
.option("--port <port>", "Harper HTTP port"
|
|
1565
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1339
1566
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1340
1567
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1341
1568
|
.action(async (opts) => {
|
|
@@ -1414,7 +1641,7 @@ program
|
|
|
1414
1641
|
.description("Import a Flair backup archive")
|
|
1415
1642
|
.option("--merge", "Add/update records without deleting existing (default)")
|
|
1416
1643
|
.option("--replace", "Delete all existing data for backed-up agents first, then import")
|
|
1417
|
-
.option("--port <port>", "Harper HTTP port"
|
|
1644
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1418
1645
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1419
1646
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1420
1647
|
.option("--dry-run", "Show what would be imported without making changes")
|
|
@@ -1531,7 +1758,7 @@ program
|
|
|
1531
1758
|
.description("Export a single agent's identity (soul + memories) to a portable file")
|
|
1532
1759
|
.option("--output <path>", "Output file path")
|
|
1533
1760
|
.option("--include-key", "Include private key in export (UNENCRYPTED — keep the output file secure)")
|
|
1534
|
-
.option("--port <port>", "Harper HTTP port"
|
|
1761
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1535
1762
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1536
1763
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1537
1764
|
.option("--keys-dir <dir>", "Keys directory", defaultKeysDir())
|
|
@@ -1617,7 +1844,7 @@ program
|
|
|
1617
1844
|
program
|
|
1618
1845
|
.command("import <path>")
|
|
1619
1846
|
.description("Import an agent from an export file into this Flair instance")
|
|
1620
|
-
.option("--port <port>", "Harper HTTP port"
|
|
1847
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1621
1848
|
.option("--ops-port <port>", "Harper operations API port")
|
|
1622
1849
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1623
1850
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
@@ -1741,11 +1968,11 @@ program
|
|
|
1741
1968
|
// ─── flair migrate-keys ───────────────────────────────────────────────────────
|
|
1742
1969
|
program
|
|
1743
1970
|
.command("migrate-keys")
|
|
1744
|
-
.description("Migrate agent keys from
|
|
1745
|
-
.option("--from <dir>", "
|
|
1971
|
+
.description("Migrate agent keys from old path (~/.tps/secrets/flair/) to ~/.flair/keys/")
|
|
1972
|
+
.option("--from <dir>", "Old keys directory", join(homedir(), ".tps", "secrets", "flair"))
|
|
1746
1973
|
.option("--to <dir>", "New keys directory", defaultKeysDir())
|
|
1747
1974
|
.option("--dry-run", "Show what would be migrated without copying")
|
|
1748
|
-
.option("--port <port>", "Harper HTTP port
|
|
1975
|
+
.option("--port <port>", "Harper HTTP port")
|
|
1749
1976
|
.option("--ops-port <port>", "Harper operations API port")
|
|
1750
1977
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1751
1978
|
.action(async (opts) => {
|
|
@@ -1755,7 +1982,7 @@ program
|
|
|
1755
1982
|
const opsPort = opts.opsPort ? Number(opts.opsPort) : Number(opts.port) + 1;
|
|
1756
1983
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1757
1984
|
if (!existsSync(fromDir)) {
|
|
1758
|
-
console.log(`
|
|
1985
|
+
console.log(`Old keys directory not found: ${fromDir}`);
|
|
1759
1986
|
console.log("Nothing to migrate.");
|
|
1760
1987
|
process.exit(0);
|
|
1761
1988
|
}
|
|
@@ -1797,7 +2024,7 @@ program
|
|
|
1797
2024
|
else {
|
|
1798
2025
|
console.log(`\n✅ Migration complete: ${migrated} migrated, ${skipped} skipped`);
|
|
1799
2026
|
if (migrated > 0) {
|
|
1800
|
-
console.log(`\
|
|
2027
|
+
console.log(`\nOld keys preserved at ${fromDir} (delete manually when confirmed working).`);
|
|
1801
2028
|
// Optionally verify keys match Flair records
|
|
1802
2029
|
if (adminPass) {
|
|
1803
2030
|
console.log("\nVerifying migrated keys against Flair...");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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",
|