@tpsdev-ai/flair 0.4.1 → 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 +150 -33
- 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 {
|
|
@@ -1190,38 +1195,140 @@ program
|
|
|
1190
1195
|
.command("doctor")
|
|
1191
1196
|
.description("Diagnose common Flair problems and suggest fixes")
|
|
1192
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")
|
|
1193
1200
|
.action(async (opts) => {
|
|
1194
1201
|
const port = opts.port ? Number(opts.port) : (readPortFromConfig() ?? DEFAULT_PORT);
|
|
1195
|
-
const
|
|
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}`;
|
|
1196
1209
|
let issues = 0;
|
|
1210
|
+
let harperResponding = false;
|
|
1197
1211
|
console.log("\n🩺 Flair Doctor\n");
|
|
1198
|
-
//
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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;
|
|
1203
1220
|
}
|
|
1204
1221
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
//
|
|
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;
|
|
1208
1227
|
try {
|
|
1209
1228
|
const { execSync } = await import("node:child_process");
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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++;
|
|
1214
1286
|
}
|
|
1215
1287
|
else {
|
|
1216
|
-
console.log(`
|
|
1217
|
-
console.log(` Fix: flair
|
|
1288
|
+
console.log(` ❌ Harper process alive (PID ${pidValue}) but not responding on any detected port`);
|
|
1289
|
+
console.log(` Fix: flair restart`);
|
|
1290
|
+
issues++;
|
|
1218
1291
|
}
|
|
1219
1292
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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++;
|
|
1223
1331
|
}
|
|
1224
|
-
issues++;
|
|
1225
1332
|
}
|
|
1226
1333
|
// 2. Keys directory
|
|
1227
1334
|
const keysDir = defaultKeysDir();
|
|
@@ -1242,7 +1349,7 @@ program
|
|
|
1242
1349
|
issues++;
|
|
1243
1350
|
}
|
|
1244
1351
|
// 3. Config file
|
|
1245
|
-
const cfgPath =
|
|
1352
|
+
const cfgPath = configPath();
|
|
1246
1353
|
if (existsSync(cfgPath)) {
|
|
1247
1354
|
const savedPort = readPortFromConfig();
|
|
1248
1355
|
console.log(` ✅ Config: ${cfgPath} (port: ${savedPort ?? "default"})`);
|
|
@@ -1250,12 +1357,9 @@ program
|
|
|
1250
1357
|
else {
|
|
1251
1358
|
console.log(` ⚠️ No config file at ${cfgPath} — using defaults`);
|
|
1252
1359
|
}
|
|
1253
|
-
// 4. Embeddings check (only if Harper is
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
|
1360
|
+
// 4. Embeddings check (only if Harper is responding)
|
|
1361
|
+
if (harperResponding) {
|
|
1362
|
+
try {
|
|
1259
1363
|
const testRes = await fetch(`${baseUrl}/SemanticSearch`, {
|
|
1260
1364
|
method: "POST",
|
|
1261
1365
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1275,24 +1379,37 @@ program
|
|
|
1275
1379
|
}
|
|
1276
1380
|
}
|
|
1277
1381
|
else if (testRes.status === 401) {
|
|
1278
|
-
// Auth required — can't test embeddings without an agent key
|
|
1279
1382
|
console.log(` ⚠️ Embeddings: cannot verify (auth required for SemanticSearch)`);
|
|
1280
1383
|
}
|
|
1281
1384
|
}
|
|
1385
|
+
catch { /* fetch error, already flagged */ }
|
|
1282
1386
|
}
|
|
1283
|
-
|
|
1284
|
-
// 5. Stale PID file
|
|
1387
|
+
// 5. Stale PID file (skip if already reported in port check)
|
|
1285
1388
|
const dataDir = defaultDataDir();
|
|
1286
1389
|
const pidFile = join(dataDir, "hdb.pid");
|
|
1287
1390
|
if (existsSync(pidFile)) {
|
|
1288
1391
|
const pidContent = (await import("node:fs")).readFileSync(pidFile, "utf-8").trim();
|
|
1289
1392
|
try {
|
|
1290
|
-
process.kill(Number(pidContent), 0);
|
|
1291
|
-
|
|
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
|
|
1292
1398
|
}
|
|
1293
1399
|
catch {
|
|
1294
1400
|
console.log(` ❌ Stale PID file: ${pidFile} (process ${pidContent} is dead)`);
|
|
1295
|
-
|
|
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
|
+
}
|
|
1296
1413
|
issues++;
|
|
1297
1414
|
}
|
|
1298
1415
|
}
|
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",
|