@tpsdev-ai/flair 0.4.0 → 0.4.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +154 -44
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -415,7 +415,7 @@ agent
415
415
  .command("add <id>")
416
416
  .description("Register a new agent in a running Flair instance")
417
417
  .option("--name <name>", "Display name (defaults to id)")
418
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
418
+ .option("--port <port>", "Harper HTTP port")
419
419
  .option("--admin-pass <pass>", "Admin password for registration")
420
420
  .option("--keys-dir <dir>", "Directory for Ed25519 keys")
421
421
  .option("--ops-port <port>", "Harper operations API port")
@@ -465,7 +465,7 @@ agent
465
465
  agent
466
466
  .command("rotate-key <id>")
467
467
  .description("Rotate an agent's Ed25519 keypair")
468
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
468
+ .option("--port <port>", "Harper HTTP port")
469
469
  .option("--ops-port <port>", "Harper operations API port")
470
470
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
471
471
  .option("--keys-dir <dir>", "Directory for Ed25519 keys")
@@ -543,7 +543,7 @@ agent
543
543
  .command("remove <id>")
544
544
  .description("Remove an agent and all its data from Flair")
545
545
  .option("--keep-keys", "Do not delete key files from disk")
546
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
546
+ .option("--port <port>", "Harper HTTP port")
547
547
  .option("--ops-port <port>", "Harper operations API port")
548
548
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
549
549
  .option("--keys-dir <dir>", "Directory for Ed25519 keys")
@@ -655,7 +655,7 @@ program
655
655
  .command("grant <from-agent> <to-agent>")
656
656
  .description("Grant an agent read access to another agent's memories")
657
657
  .option("--scope <scope>", "Grant scope: read or search", "read")
658
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
658
+ .option("--port <port>", "Harper HTTP port")
659
659
  .option("--ops-port <port>", "Harper operations API port")
660
660
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
661
661
  .option("--keys-dir <dir>", "Directory for Ed25519 keys (for from-agent Ed25519 auth)")
@@ -704,7 +704,7 @@ program
704
704
  program
705
705
  .command("revoke <from-agent> <to-agent>")
706
706
  .description("Revoke a memory grant between two agents")
707
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
707
+ .option("--port <port>", "Harper HTTP port")
708
708
  .option("--ops-port <port>", "Harper operations API port")
709
709
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
710
710
  .action(async (fromAgent, toAgent, opts) => {
@@ -1185,36 +1185,146 @@ program
1185
1185
  if (failed > 0)
1186
1186
  process.exit(1);
1187
1187
  });
1188
- // ─── Legacy identity/memory/soul commands (preserved) ────────────────────────
1189
- const identity = program.command("identity").description("Legacy identity commands");
1190
- identity.command("register")
1191
- .requiredOption("--id <id>")
1192
- .requiredOption("--name <name>")
1193
- .option("--role <role>")
1194
- .action(async (opts) => {
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>")
1188
+ // ─── flair doctor ─────────────────────────────────────────────────────────────
1189
+ program
1190
+ .command("doctor")
1191
+ .description("Diagnose common Flair problems and suggest fixes")
1192
+ .option("--port <port>", "Harper HTTP port")
1209
1193
  .action(async (opts) => {
1210
- const now = new Date().toISOString();
1211
- const out = await api("POST", "/Integration", {
1212
- id: `${opts.agent}:${opts.platform}`, agentId: opts.agent,
1213
- platform: opts.platform, encryptedCredential: opts.encryptedCredential,
1214
- createdAt: now, updatedAt: now,
1215
- });
1216
- console.log(JSON.stringify(out, null, 2));
1194
+ const port = opts.port ? Number(opts.port) : (readPortFromConfig() ?? DEFAULT_PORT);
1195
+ const baseUrl = `http://127.0.0.1:${port}`;
1196
+ let issues = 0;
1197
+ console.log("\n🩺 Flair Doctor\n");
1198
+ // 1. Port check — is something listening?
1199
+ try {
1200
+ const res = await fetch(`${baseUrl}/Health`, { signal: AbortSignal.timeout(3000) });
1201
+ if (res.status > 0) {
1202
+ console.log(` ✅ Harper responding on port ${port}`);
1203
+ }
1204
+ }
1205
+ catch {
1206
+ console.log(` ❌ Nothing responding on port ${port}`);
1207
+ // Check if port is in use by something else
1208
+ try {
1209
+ const { execSync } = await import("node:child_process");
1210
+ const lsof = execSync(`lsof -ti :${port}`, { encoding: "utf-8" }).trim();
1211
+ if (lsof) {
1212
+ console.log(` Port ${port} is in use by PID ${lsof} — might be a stale process`);
1213
+ console.log(` Fix: kill ${lsof} && flair restart`);
1214
+ }
1215
+ else {
1216
+ console.log(` Harper is not running`);
1217
+ console.log(` Fix: flair init --agent-id <your-agent>`);
1218
+ }
1219
+ }
1220
+ catch {
1221
+ console.log(` Harper is not running`);
1222
+ console.log(` Fix: flair init --agent-id <your-agent>`);
1223
+ }
1224
+ issues++;
1225
+ }
1226
+ // 2. Keys directory
1227
+ const keysDir = defaultKeysDir();
1228
+ if (existsSync(keysDir)) {
1229
+ const keyFiles = (await import("node:fs")).readdirSync(keysDir).filter((f) => f.endsWith(".key"));
1230
+ if (keyFiles.length > 0) {
1231
+ console.log(` ✅ Keys found: ${keyFiles.length} agent(s) in ${keysDir}`);
1232
+ }
1233
+ else {
1234
+ console.log(` ❌ Keys directory exists but no .key files found`);
1235
+ console.log(` Fix: flair init --agent-id <your-agent>`);
1236
+ issues++;
1237
+ }
1238
+ }
1239
+ else {
1240
+ console.log(` ❌ Keys directory missing: ${keysDir}`);
1241
+ console.log(` Fix: flair init --agent-id <your-agent>`);
1242
+ issues++;
1243
+ }
1244
+ // 3. Config file
1245
+ const cfgPath = join(homedir(), ".flair", "config.yaml");
1246
+ if (existsSync(cfgPath)) {
1247
+ const savedPort = readPortFromConfig();
1248
+ console.log(` ✅ Config: ${cfgPath} (port: ${savedPort ?? "default"})`);
1249
+ }
1250
+ else {
1251
+ console.log(` ⚠️ No config file at ${cfgPath} — using defaults`);
1252
+ }
1253
+ // 4. Embeddings check (only if Harper is running)
1254
+ try {
1255
+ const res = await fetch(`${baseUrl}/Health`, { signal: AbortSignal.timeout(3000) });
1256
+ if (res.status > 0) {
1257
+ // Try a test embedding via SemanticSearch with a dummy query
1258
+ // If embedding mode is "none", search will include _warning
1259
+ const testRes = await fetch(`${baseUrl}/SemanticSearch`, {
1260
+ method: "POST",
1261
+ headers: { "Content-Type": "application/json" },
1262
+ body: JSON.stringify({ q: "test", limit: 1 }),
1263
+ signal: AbortSignal.timeout(10000),
1264
+ });
1265
+ if (testRes.ok) {
1266
+ const data = await testRes.json();
1267
+ if (data._warning) {
1268
+ console.log(` ⚠️ Embeddings: keyword-only (${data._warning})`);
1269
+ console.log(` Semantic search quality is degraded`);
1270
+ console.log(` Check: ls ~/.npm-global/lib/node_modules/@tpsdev-ai/flair/models/`);
1271
+ issues++;
1272
+ }
1273
+ else {
1274
+ console.log(` ✅ Embeddings: semantic search operational`);
1275
+ }
1276
+ }
1277
+ else if (testRes.status === 401) {
1278
+ // Auth required — can't test embeddings without an agent key
1279
+ console.log(` ⚠️ Embeddings: cannot verify (auth required for SemanticSearch)`);
1280
+ }
1281
+ }
1282
+ }
1283
+ catch { /* Harper not running, already flagged above */ }
1284
+ // 5. Stale PID file
1285
+ const dataDir = defaultDataDir();
1286
+ const pidFile = join(dataDir, "hdb.pid");
1287
+ if (existsSync(pidFile)) {
1288
+ const pidContent = (await import("node:fs")).readFileSync(pidFile, "utf-8").trim();
1289
+ try {
1290
+ process.kill(Number(pidContent), 0); // check if process exists
1291
+ console.log(` ✅ PID file: ${pidFile} (process ${pidContent} is alive)`);
1292
+ }
1293
+ catch {
1294
+ console.log(` ❌ Stale PID file: ${pidFile} (process ${pidContent} is dead)`);
1295
+ console.log(` Fix: rm ${pidFile} && flair restart`);
1296
+ issues++;
1297
+ }
1298
+ }
1299
+ // 6. Data directory
1300
+ if (existsSync(dataDir)) {
1301
+ console.log(` ✅ Data directory: ${dataDir}`);
1302
+ }
1303
+ else {
1304
+ // Check ~/harper/ (common alternative)
1305
+ const altDir = join(homedir(), "harper");
1306
+ if (existsSync(altDir)) {
1307
+ console.log(` ⚠️ Data at ~/harper/ (not ~/.flair/data) — old install location`);
1308
+ }
1309
+ else {
1310
+ console.log(` ❌ No data directory found`);
1311
+ console.log(` Fix: flair init --agent-id <your-agent>`);
1312
+ issues++;
1313
+ }
1314
+ }
1315
+ // Summary
1316
+ console.log("");
1317
+ if (issues === 0) {
1318
+ console.log(" 🟢 No issues found");
1319
+ }
1320
+ else {
1321
+ console.log(` 🔴 ${issues} issue${issues > 1 ? "s" : ""} found — see fixes above`);
1322
+ }
1323
+ console.log("");
1324
+ if (issues > 0)
1325
+ process.exit(1);
1217
1326
  });
1327
+ // ─── Memory and Soul commands ────────────────────────────────────────────────
1218
1328
  const memory = program.command("memory").description("Manage agent memories");
1219
1329
  memory.command("add").requiredOption("--agent <id>").requiredOption("--content <text>")
1220
1330
  .option("--durability <d>", "standard").option("--tags <csv>")
@@ -1240,7 +1350,7 @@ program
1240
1350
  .description("Search memories by meaning (shortcut for memory search)")
1241
1351
  .requiredOption("--agent <id>", "Agent ID")
1242
1352
  .option("--limit <n>", "Max results", "5")
1243
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
1353
+ .option("--port <port>", "Harper HTTP port")
1244
1354
  .option("--url <url>", "Flair base URL (overrides --port)")
1245
1355
  .option("--key <path>", "Ed25519 private key path")
1246
1356
  .action(async (query, opts) => {
@@ -1285,7 +1395,7 @@ program
1285
1395
  .description("Cold-start context: get soul + recent memories as formatted text")
1286
1396
  .requiredOption("--agent <id>", "Agent ID")
1287
1397
  .option("--max-tokens <n>", "Maximum tokens in output", "4000")
1288
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
1398
+ .option("--port <port>", "Harper HTTP port")
1289
1399
  .option("--url <url>", "Flair base URL (overrides --port)")
1290
1400
  .option("--key <path>", "Ed25519 private key path")
1291
1401
  .action(async (opts) => {
@@ -1335,7 +1445,7 @@ program
1335
1445
  .description("Export agents, memories, and souls to a JSON archive")
1336
1446
  .option("--output <path>", "Output file path (default: ~/.flair/backups/flair-backup-<timestamp>.json)")
1337
1447
  .option("--agents <ids>", "Comma-separated agent IDs to include (default: all)")
1338
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
1448
+ .option("--port <port>", "Harper HTTP port")
1339
1449
  .option("--url <url>", "Flair base URL (overrides --port)")
1340
1450
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
1341
1451
  .action(async (opts) => {
@@ -1414,7 +1524,7 @@ program
1414
1524
  .description("Import a Flair backup archive")
1415
1525
  .option("--merge", "Add/update records without deleting existing (default)")
1416
1526
  .option("--replace", "Delete all existing data for backed-up agents first, then import")
1417
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
1527
+ .option("--port <port>", "Harper HTTP port")
1418
1528
  .option("--url <url>", "Flair base URL (overrides --port)")
1419
1529
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
1420
1530
  .option("--dry-run", "Show what would be imported without making changes")
@@ -1531,7 +1641,7 @@ program
1531
1641
  .description("Export a single agent's identity (soul + memories) to a portable file")
1532
1642
  .option("--output <path>", "Output file path")
1533
1643
  .option("--include-key", "Include private key in export (UNENCRYPTED — keep the output file secure)")
1534
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
1644
+ .option("--port <port>", "Harper HTTP port")
1535
1645
  .option("--url <url>", "Flair base URL (overrides --port)")
1536
1646
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
1537
1647
  .option("--keys-dir <dir>", "Keys directory", defaultKeysDir())
@@ -1617,7 +1727,7 @@ program
1617
1727
  program
1618
1728
  .command("import <path>")
1619
1729
  .description("Import an agent from an export file into this Flair instance")
1620
- .option("--port <port>", "Harper HTTP port", String(DEFAULT_PORT))
1730
+ .option("--port <port>", "Harper HTTP port")
1621
1731
  .option("--ops-port <port>", "Harper operations API port")
1622
1732
  .option("--url <url>", "Flair base URL (overrides --port)")
1623
1733
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
@@ -1741,11 +1851,11 @@ program
1741
1851
  // ─── flair migrate-keys ───────────────────────────────────────────────────────
1742
1852
  program
1743
1853
  .command("migrate-keys")
1744
- .description("Migrate agent keys from legacy path (~/.tps/secrets/flair/) to ~/.flair/keys/")
1745
- .option("--from <dir>", "Legacy keys directory", join(homedir(), ".tps", "secrets", "flair"))
1854
+ .description("Migrate agent keys from old path (~/.tps/secrets/flair/) to ~/.flair/keys/")
1855
+ .option("--from <dir>", "Old keys directory", join(homedir(), ".tps", "secrets", "flair"))
1746
1856
  .option("--to <dir>", "New keys directory", defaultKeysDir())
1747
1857
  .option("--dry-run", "Show what would be migrated without copying")
1748
- .option("--port <port>", "Harper HTTP port (for agent list)", String(DEFAULT_PORT))
1858
+ .option("--port <port>", "Harper HTTP port")
1749
1859
  .option("--ops-port <port>", "Harper operations API port")
1750
1860
  .option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
1751
1861
  .action(async (opts) => {
@@ -1755,7 +1865,7 @@ program
1755
1865
  const opsPort = opts.opsPort ? Number(opts.opsPort) : Number(opts.port) + 1;
1756
1866
  const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
1757
1867
  if (!existsSync(fromDir)) {
1758
- console.log(`Legacy keys directory not found: ${fromDir}`);
1868
+ console.log(`Old keys directory not found: ${fromDir}`);
1759
1869
  console.log("Nothing to migrate.");
1760
1870
  process.exit(0);
1761
1871
  }
@@ -1797,7 +1907,7 @@ program
1797
1907
  else {
1798
1908
  console.log(`\n✅ Migration complete: ${migrated} migrated, ${skipped} skipped`);
1799
1909
  if (migrated > 0) {
1800
- console.log(`\nLegacy keys preserved at ${fromDir} (delete manually when confirmed working).`);
1910
+ console.log(`\nOld keys preserved at ${fromDir} (delete manually when confirmed working).`);
1801
1911
  // Optionally verify keys match Flair records
1802
1912
  if (adminPass) {
1803
1913
  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.0",
3
+ "version": "0.4.1",
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",