@tpsdev-ai/flair 0.4.2 → 0.4.4
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 +126 -44
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -39,6 +39,46 @@ function readPortFromConfig() {
|
|
|
39
39
|
catch { /* ignore */ }
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
|
+
// Unified port resolution: --port flag > FLAIR_URL env > config file > default
|
|
43
|
+
// Every command that talks to Harper MUST use these helpers.
|
|
44
|
+
function resolveHttpPort(opts) {
|
|
45
|
+
if (opts.port !== undefined && opts.port !== null) {
|
|
46
|
+
const n = Number(opts.port);
|
|
47
|
+
if (!isNaN(n) && n > 0)
|
|
48
|
+
return n;
|
|
49
|
+
}
|
|
50
|
+
const envUrl = process.env.FLAIR_URL;
|
|
51
|
+
if (envUrl) {
|
|
52
|
+
const m = envUrl.match(/:(\d+)/);
|
|
53
|
+
if (m)
|
|
54
|
+
return Number(m[1]);
|
|
55
|
+
}
|
|
56
|
+
return readPortFromConfig() ?? DEFAULT_PORT;
|
|
57
|
+
}
|
|
58
|
+
// Ops port resolution: --ops-port flag > FLAIR_OPS_PORT env > config opsPort > httpPort - 1
|
|
59
|
+
function resolveOpsPort(opts) {
|
|
60
|
+
if (opts.opsPort !== undefined && opts.opsPort !== null) {
|
|
61
|
+
const n = Number(opts.opsPort);
|
|
62
|
+
if (!isNaN(n) && n > 0)
|
|
63
|
+
return n;
|
|
64
|
+
}
|
|
65
|
+
const envOps = process.env.FLAIR_OPS_PORT;
|
|
66
|
+
if (envOps)
|
|
67
|
+
return Number(envOps);
|
|
68
|
+
// Try reading from config
|
|
69
|
+
try {
|
|
70
|
+
const p = configPath();
|
|
71
|
+
if (existsSync(p)) {
|
|
72
|
+
const yaml = readFileSync(p, "utf-8");
|
|
73
|
+
const m = yaml.match(/opsPort:\s*(\d+)/);
|
|
74
|
+
if (m)
|
|
75
|
+
return Number(m[1]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { /* ignore */ }
|
|
79
|
+
// Default: httpPort - 1
|
|
80
|
+
return resolveHttpPort(opts) - 1;
|
|
81
|
+
}
|
|
42
82
|
function writeConfig(port) {
|
|
43
83
|
const p = configPath();
|
|
44
84
|
mkdirSync(join(homedir(), ".flair"), { recursive: true });
|
|
@@ -251,8 +291,8 @@ program
|
|
|
251
291
|
.option("--skip-soul", "Skip interactive personality setup")
|
|
252
292
|
.action(async (opts) => {
|
|
253
293
|
const agentId = opts.agentId;
|
|
254
|
-
const httpPort =
|
|
255
|
-
const opsPort =
|
|
294
|
+
const httpPort = resolveHttpPort(opts);
|
|
295
|
+
const opsPort = resolveOpsPort(opts);
|
|
256
296
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
257
297
|
const dataDir = opts.dataDir ?? defaultDataDir();
|
|
258
298
|
// Admin password: generate if not provided, NEVER written to disk
|
|
@@ -278,6 +318,12 @@ program
|
|
|
278
318
|
if (!bin)
|
|
279
319
|
throw new Error("@harperfast/harper not found in node_modules.\nRun: npm install @harperfast/harper");
|
|
280
320
|
mkdirSync(dataDir, { recursive: true });
|
|
321
|
+
// Detect whether Harper has already been installed in this data dir.
|
|
322
|
+
// harper-config.yaml is created during install — its presence means
|
|
323
|
+
// install already ran. Re-running install against an existing data dir
|
|
324
|
+
// crashes in Harper v5 beta.6+ (checkForExistingInstall queries the
|
|
325
|
+
// database before the env is initialized).
|
|
326
|
+
const alreadyInstalled = existsSync(join(dataDir, "harper-config.yaml"));
|
|
281
327
|
const opsSocket = join(dataDir, "operations-server");
|
|
282
328
|
const harperSetConfig = JSON.stringify({
|
|
283
329
|
rootPath: dataDir,
|
|
@@ -287,8 +333,17 @@ program
|
|
|
287
333
|
localStudio: { enabled: false },
|
|
288
334
|
authentication: { authorizeLocal: true, enableSessions: true },
|
|
289
335
|
});
|
|
336
|
+
// Isolate from any global Harper install. Harper's installer reads
|
|
337
|
+
// ~/.harperdb/hdb_boot_properties.file to detect prior installs —
|
|
338
|
+
// if one exists (unrelated to flair), checkForExistingInstall crashes
|
|
339
|
+
// in beta.6+ querying databases with an uninitialized env. Setting
|
|
340
|
+
// HOME to flair's own dir prevents the subprocess from seeing the
|
|
341
|
+
// global boot file. Flair never uses the boot file (relies on
|
|
342
|
+
// ROOTPATH), so this is safe.
|
|
343
|
+
const flairHome = join(dataDir, ".."); // ~/.flair
|
|
290
344
|
const env = {
|
|
291
345
|
...process.env,
|
|
346
|
+
HOME: flairHome,
|
|
292
347
|
ROOTPATH: dataDir,
|
|
293
348
|
HARPER_SET_CONFIG: harperSetConfig,
|
|
294
349
|
DEFAULTS_MODE: "dev",
|
|
@@ -300,25 +355,27 @@ program
|
|
|
300
355
|
OPERATIONSAPI_NETWORK_PORT: String(opsPort),
|
|
301
356
|
LOCAL_STUDIO: "false",
|
|
302
357
|
};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
//
|
|
358
|
+
if (alreadyInstalled) {
|
|
359
|
+
console.log("Existing Harper installation found — skipping install.");
|
|
360
|
+
console.log("If something is wrong, run: flair doctor");
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log("Installing Harper...");
|
|
364
|
+
await new Promise((resolve, reject) => {
|
|
365
|
+
let output = "";
|
|
366
|
+
const install = spawn(process.execPath, [bin, "install"], { cwd: flairPackageDir(), env });
|
|
367
|
+
install.stdout?.on("data", (d) => { output += d.toString(); });
|
|
368
|
+
install.stderr?.on("data", (d) => { output += d.toString(); });
|
|
369
|
+
install.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`Harper install failed (${code}): ${output}`)));
|
|
370
|
+
install.on("error", reject);
|
|
371
|
+
setTimeout(() => { install.kill(); reject(new Error(`Harper install timed out: ${output}`)); }, 60_000);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Start Harper with flair loaded as a component (the "." arg).
|
|
375
|
+
// ROOTPATH in env points to the data dir; authorizeLocal and thread
|
|
376
|
+
// count are set via HARPER_SET_CONFIG — no need for dev mode.
|
|
320
377
|
console.log(`Starting Harper on port ${httpPort}...`);
|
|
321
|
-
const proc = spawn(process.execPath, [bin, "
|
|
378
|
+
const proc = spawn(process.execPath, [bin, "run", "."], { cwd: flairPackageDir(), env, detached: true, stdio: "ignore" });
|
|
322
379
|
proc.unref();
|
|
323
380
|
}
|
|
324
381
|
console.log("Waiting for Harper health check...");
|
|
@@ -425,8 +482,8 @@ agent
|
|
|
425
482
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
426
483
|
.option("--ops-port <port>", "Harper operations API port")
|
|
427
484
|
.action(async (id, opts) => {
|
|
428
|
-
const httpPort =
|
|
429
|
-
const opsPort =
|
|
485
|
+
const httpPort = resolveHttpPort(opts);
|
|
486
|
+
const opsPort = resolveOpsPort(opts);
|
|
430
487
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
431
488
|
const adminPass = opts.adminPass;
|
|
432
489
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
@@ -462,7 +519,32 @@ agent
|
|
|
462
519
|
agent
|
|
463
520
|
.command("list")
|
|
464
521
|
.description("List all agents")
|
|
465
|
-
.
|
|
522
|
+
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
523
|
+
.option("--port <port>", "Harper HTTP port")
|
|
524
|
+
.action(async (opts) => {
|
|
525
|
+
const port = resolveHttpPort(opts);
|
|
526
|
+
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
527
|
+
if (adminPass) {
|
|
528
|
+
// Use admin basic auth against ops API to list agents directly
|
|
529
|
+
const opsPort = resolveOpsPort(opts);
|
|
530
|
+
const auth = Buffer.from(`${DEFAULT_ADMIN_USER}:${adminPass}`).toString("base64");
|
|
531
|
+
const res = await fetch(`http://127.0.0.1:${opsPort}/`, {
|
|
532
|
+
method: "POST",
|
|
533
|
+
headers: { "Content-Type": "application/json", Authorization: `Basic ${auth}` },
|
|
534
|
+
body: JSON.stringify({ operation: "sql", sql: "SELECT id, name, createdAt FROM flair.Agent ORDER BY createdAt" }),
|
|
535
|
+
});
|
|
536
|
+
if (!res.ok) {
|
|
537
|
+
const text = await res.text().catch(() => "");
|
|
538
|
+
console.error(`Error: ${res.status} ${text}`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
console.log(JSON.stringify(await res.json(), null, 2));
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
// Try agent-authed API (requires FLAIR_AGENT_ID to be set)
|
|
545
|
+
console.log(JSON.stringify(await api("GET", "/Agent"), null, 2));
|
|
546
|
+
}
|
|
547
|
+
});
|
|
466
548
|
agent
|
|
467
549
|
.command("show <id>")
|
|
468
550
|
.description("Show agent details")
|
|
@@ -475,8 +557,8 @@ agent
|
|
|
475
557
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
476
558
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
477
559
|
.action(async (id, opts) => {
|
|
478
|
-
const httpPort =
|
|
479
|
-
const opsPort =
|
|
560
|
+
const httpPort = resolveHttpPort(opts);
|
|
561
|
+
const opsPort = resolveOpsPort(opts);
|
|
480
562
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
481
563
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
482
564
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
@@ -554,7 +636,7 @@ agent
|
|
|
554
636
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
555
637
|
.option("--force", "Skip interactive confirmation (required when stdin is not a TTY)")
|
|
556
638
|
.action(async (id, opts) => {
|
|
557
|
-
const opsPort =
|
|
639
|
+
const opsPort = resolveOpsPort(opts);
|
|
558
640
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
559
641
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
560
642
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
@@ -665,8 +747,8 @@ program
|
|
|
665
747
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
666
748
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys (for from-agent Ed25519 auth)")
|
|
667
749
|
.action(async (fromAgent, toAgent, opts) => {
|
|
668
|
-
const httpPort =
|
|
669
|
-
const opsPort =
|
|
750
|
+
const httpPort = resolveHttpPort(opts);
|
|
751
|
+
const opsPort = resolveOpsPort(opts);
|
|
670
752
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
671
753
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
672
754
|
const scope = opts.scope ?? "read";
|
|
@@ -713,8 +795,8 @@ program
|
|
|
713
795
|
.option("--ops-port <port>", "Harper operations API port")
|
|
714
796
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
715
797
|
.action(async (fromAgent, toAgent, opts) => {
|
|
716
|
-
const httpPort =
|
|
717
|
-
const opsPort =
|
|
798
|
+
const httpPort = resolveHttpPort(opts);
|
|
799
|
+
const opsPort = resolveOpsPort(opts);
|
|
718
800
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
719
801
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
720
802
|
if (!adminPass) {
|
|
@@ -753,7 +835,7 @@ program
|
|
|
753
835
|
.option("--port <port>", "Harper HTTP port")
|
|
754
836
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
755
837
|
.action(async (opts) => {
|
|
756
|
-
const port =
|
|
838
|
+
const port = resolveHttpPort(opts);
|
|
757
839
|
const baseUrl = opts.url ?? `http://127.0.0.1:${port}`;
|
|
758
840
|
let healthy = false;
|
|
759
841
|
let agentCount = null;
|
|
@@ -831,7 +913,7 @@ program
|
|
|
831
913
|
.description("Stop the running Flair (Harper) instance")
|
|
832
914
|
.option("--port <port>", "Harper HTTP port")
|
|
833
915
|
.action(async (opts) => {
|
|
834
|
-
const port =
|
|
916
|
+
const port = resolveHttpPort(opts);
|
|
835
917
|
const platform = process.platform;
|
|
836
918
|
if (platform === "darwin") {
|
|
837
919
|
// macOS: try launchd first
|
|
@@ -874,7 +956,7 @@ program
|
|
|
874
956
|
.description("Restart the Flair (Harper) instance")
|
|
875
957
|
.option("--port <port>", "Harper HTTP port")
|
|
876
958
|
.action(async (opts) => {
|
|
877
|
-
const port =
|
|
959
|
+
const port = resolveHttpPort(opts);
|
|
878
960
|
const platform = process.platform;
|
|
879
961
|
if (platform === "darwin") {
|
|
880
962
|
const label = "ai.tpsdev.flair";
|
|
@@ -1034,7 +1116,7 @@ program
|
|
|
1034
1116
|
.option("--batch-size <n>", "Records per batch", "50")
|
|
1035
1117
|
.option("--delay-ms <ms>", "Delay between batches (ms)", "100")
|
|
1036
1118
|
.action(async (opts) => {
|
|
1037
|
-
const port =
|
|
1119
|
+
const port = resolveHttpPort(opts);
|
|
1038
1120
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
1039
1121
|
const agentId = opts.agent;
|
|
1040
1122
|
const staleOnly = opts.staleOnly ?? false;
|
|
@@ -1115,7 +1197,7 @@ program
|
|
|
1115
1197
|
.requiredOption("--agent <id>", "Agent ID to test with")
|
|
1116
1198
|
.option("--port <port>", "Harper HTTP port")
|
|
1117
1199
|
.action(async (opts) => {
|
|
1118
|
-
const port =
|
|
1200
|
+
const port = resolveHttpPort(opts);
|
|
1119
1201
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
1120
1202
|
const agentId = opts.agent;
|
|
1121
1203
|
const keysDir = defaultKeysDir();
|
|
@@ -1198,7 +1280,7 @@ program
|
|
|
1198
1280
|
.option("--fix", "Automatically fix issues where possible")
|
|
1199
1281
|
.option("--dry-run", "Show what --fix would do without making changes")
|
|
1200
1282
|
.action(async (opts) => {
|
|
1201
|
-
const port =
|
|
1283
|
+
const port = resolveHttpPort(opts);
|
|
1202
1284
|
const autoFix = opts.fix ?? false;
|
|
1203
1285
|
const dryRun = opts.dryRun ?? false;
|
|
1204
1286
|
if (dryRun && !autoFix) {
|
|
@@ -1472,7 +1554,7 @@ program
|
|
|
1472
1554
|
.option("--key <path>", "Ed25519 private key path")
|
|
1473
1555
|
.action(async (query, opts) => {
|
|
1474
1556
|
try {
|
|
1475
|
-
const baseUrl = opts.url || `http://127.0.0.1:${opts
|
|
1557
|
+
const baseUrl = opts.url || `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1476
1558
|
const headers = { "content-type": "application/json" };
|
|
1477
1559
|
const keyPath = opts.key || resolveKeyPath(opts.agent);
|
|
1478
1560
|
if (keyPath) {
|
|
@@ -1516,7 +1598,7 @@ program
|
|
|
1516
1598
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1517
1599
|
.option("--key <path>", "Ed25519 private key path")
|
|
1518
1600
|
.action(async (opts) => {
|
|
1519
|
-
const baseUrl = opts.url || `http://127.0.0.1:${opts
|
|
1601
|
+
const baseUrl = opts.url || `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1520
1602
|
try {
|
|
1521
1603
|
const headers = { "content-type": "application/json" };
|
|
1522
1604
|
const keyPath = opts.key || resolveKeyPath(opts.agent);
|
|
@@ -1566,7 +1648,7 @@ program
|
|
|
1566
1648
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1567
1649
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1568
1650
|
.action(async (opts) => {
|
|
1569
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1651
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1570
1652
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1571
1653
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
1572
1654
|
if (!adminPass) {
|
|
@@ -1646,7 +1728,7 @@ program
|
|
|
1646
1728
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1647
1729
|
.option("--dry-run", "Show what would be imported without making changes")
|
|
1648
1730
|
.action(async (backupPath, opts) => {
|
|
1649
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1731
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1650
1732
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1651
1733
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
1652
1734
|
const dryRun = Boolean(opts.dryRun);
|
|
@@ -1763,7 +1845,7 @@ program
|
|
|
1763
1845
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1764
1846
|
.option("--keys-dir <dir>", "Keys directory", defaultKeysDir())
|
|
1765
1847
|
.action(async (agentId, opts) => {
|
|
1766
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1848
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1767
1849
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1768
1850
|
if (!adminPass) {
|
|
1769
1851
|
console.error("Error: --admin-pass or FLAIR_ADMIN_PASS required");
|
|
@@ -1850,8 +1932,8 @@ program
|
|
|
1850
1932
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1851
1933
|
.option("--keys-dir <dir>", "Keys directory", defaultKeysDir())
|
|
1852
1934
|
.action(async (importPath, opts) => {
|
|
1853
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1854
|
-
const opsPort =
|
|
1935
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1936
|
+
const opsPort = resolveOpsPort(opts);
|
|
1855
1937
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1856
1938
|
if (!adminPass) {
|
|
1857
1939
|
console.error("Error: --admin-pass or FLAIR_ADMIN_PASS required");
|
|
@@ -1979,7 +2061,7 @@ program
|
|
|
1979
2061
|
const fromDir = opts.from;
|
|
1980
2062
|
const toDir = opts.to;
|
|
1981
2063
|
const dryRun = opts.dryRun ?? false;
|
|
1982
|
-
const opsPort =
|
|
2064
|
+
const opsPort = resolveOpsPort(opts);
|
|
1983
2065
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1984
2066
|
if (!existsSync(fromDir)) {
|
|
1985
2067
|
console.log(`Old keys directory not found: ${fromDir}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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",
|