@tpsdev-ai/flair 0.4.2 → 0.4.3
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 +117 -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,
|
|
@@ -300,25 +346,27 @@ program
|
|
|
300
346
|
OPERATIONSAPI_NETWORK_PORT: String(opsPort),
|
|
301
347
|
LOCAL_STUDIO: "false",
|
|
302
348
|
};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
//
|
|
349
|
+
if (alreadyInstalled) {
|
|
350
|
+
console.log("Existing Harper installation found — skipping install.");
|
|
351
|
+
console.log("If something is wrong, run: flair doctor");
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
console.log("Installing Harper...");
|
|
355
|
+
await new Promise((resolve, reject) => {
|
|
356
|
+
let output = "";
|
|
357
|
+
const install = spawn(process.execPath, [bin, "install"], { cwd: flairPackageDir(), env });
|
|
358
|
+
install.stdout?.on("data", (d) => { output += d.toString(); });
|
|
359
|
+
install.stderr?.on("data", (d) => { output += d.toString(); });
|
|
360
|
+
install.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`Harper install failed (${code}): ${output}`)));
|
|
361
|
+
install.on("error", reject);
|
|
362
|
+
setTimeout(() => { install.kill(); reject(new Error(`Harper install timed out: ${output}`)); }, 60_000);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// Start Harper with flair loaded as a component (the "." arg).
|
|
366
|
+
// ROOTPATH in env points to the data dir; authorizeLocal and thread
|
|
367
|
+
// count are set via HARPER_SET_CONFIG — no need for dev mode.
|
|
320
368
|
console.log(`Starting Harper on port ${httpPort}...`);
|
|
321
|
-
const proc = spawn(process.execPath, [bin, "
|
|
369
|
+
const proc = spawn(process.execPath, [bin, "run", "."], { cwd: flairPackageDir(), env, detached: true, stdio: "ignore" });
|
|
322
370
|
proc.unref();
|
|
323
371
|
}
|
|
324
372
|
console.log("Waiting for Harper health check...");
|
|
@@ -425,8 +473,8 @@ agent
|
|
|
425
473
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
426
474
|
.option("--ops-port <port>", "Harper operations API port")
|
|
427
475
|
.action(async (id, opts) => {
|
|
428
|
-
const httpPort =
|
|
429
|
-
const opsPort =
|
|
476
|
+
const httpPort = resolveHttpPort(opts);
|
|
477
|
+
const opsPort = resolveOpsPort(opts);
|
|
430
478
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
431
479
|
const adminPass = opts.adminPass;
|
|
432
480
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
@@ -462,7 +510,32 @@ agent
|
|
|
462
510
|
agent
|
|
463
511
|
.command("list")
|
|
464
512
|
.description("List all agents")
|
|
465
|
-
.
|
|
513
|
+
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
514
|
+
.option("--port <port>", "Harper HTTP port")
|
|
515
|
+
.action(async (opts) => {
|
|
516
|
+
const port = resolveHttpPort(opts);
|
|
517
|
+
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
518
|
+
if (adminPass) {
|
|
519
|
+
// Use admin basic auth against ops API to list agents directly
|
|
520
|
+
const opsPort = resolveOpsPort(opts);
|
|
521
|
+
const auth = Buffer.from(`${DEFAULT_ADMIN_USER}:${adminPass}`).toString("base64");
|
|
522
|
+
const res = await fetch(`http://127.0.0.1:${opsPort}/`, {
|
|
523
|
+
method: "POST",
|
|
524
|
+
headers: { "Content-Type": "application/json", Authorization: `Basic ${auth}` },
|
|
525
|
+
body: JSON.stringify({ operation: "sql", sql: "SELECT id, name, createdAt FROM flair.Agent ORDER BY createdAt" }),
|
|
526
|
+
});
|
|
527
|
+
if (!res.ok) {
|
|
528
|
+
const text = await res.text().catch(() => "");
|
|
529
|
+
console.error(`Error: ${res.status} ${text}`);
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
console.log(JSON.stringify(await res.json(), null, 2));
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
// Try agent-authed API (requires FLAIR_AGENT_ID to be set)
|
|
536
|
+
console.log(JSON.stringify(await api("GET", "/Agent"), null, 2));
|
|
537
|
+
}
|
|
538
|
+
});
|
|
466
539
|
agent
|
|
467
540
|
.command("show <id>")
|
|
468
541
|
.description("Show agent details")
|
|
@@ -475,8 +548,8 @@ agent
|
|
|
475
548
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
476
549
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
477
550
|
.action(async (id, opts) => {
|
|
478
|
-
const httpPort =
|
|
479
|
-
const opsPort =
|
|
551
|
+
const httpPort = resolveHttpPort(opts);
|
|
552
|
+
const opsPort = resolveOpsPort(opts);
|
|
480
553
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
481
554
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
482
555
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
@@ -554,7 +627,7 @@ agent
|
|
|
554
627
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys")
|
|
555
628
|
.option("--force", "Skip interactive confirmation (required when stdin is not a TTY)")
|
|
556
629
|
.action(async (id, opts) => {
|
|
557
|
-
const opsPort =
|
|
630
|
+
const opsPort = resolveOpsPort(opts);
|
|
558
631
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
559
632
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
560
633
|
const keysDir = opts.keysDir ?? defaultKeysDir();
|
|
@@ -665,8 +738,8 @@ program
|
|
|
665
738
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
666
739
|
.option("--keys-dir <dir>", "Directory for Ed25519 keys (for from-agent Ed25519 auth)")
|
|
667
740
|
.action(async (fromAgent, toAgent, opts) => {
|
|
668
|
-
const httpPort =
|
|
669
|
-
const opsPort =
|
|
741
|
+
const httpPort = resolveHttpPort(opts);
|
|
742
|
+
const opsPort = resolveOpsPort(opts);
|
|
670
743
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
671
744
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
672
745
|
const scope = opts.scope ?? "read";
|
|
@@ -713,8 +786,8 @@ program
|
|
|
713
786
|
.option("--ops-port <port>", "Harper operations API port")
|
|
714
787
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
715
788
|
.action(async (fromAgent, toAgent, opts) => {
|
|
716
|
-
const httpPort =
|
|
717
|
-
const opsPort =
|
|
789
|
+
const httpPort = resolveHttpPort(opts);
|
|
790
|
+
const opsPort = resolveOpsPort(opts);
|
|
718
791
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
719
792
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
720
793
|
if (!adminPass) {
|
|
@@ -753,7 +826,7 @@ program
|
|
|
753
826
|
.option("--port <port>", "Harper HTTP port")
|
|
754
827
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
755
828
|
.action(async (opts) => {
|
|
756
|
-
const port =
|
|
829
|
+
const port = resolveHttpPort(opts);
|
|
757
830
|
const baseUrl = opts.url ?? `http://127.0.0.1:${port}`;
|
|
758
831
|
let healthy = false;
|
|
759
832
|
let agentCount = null;
|
|
@@ -831,7 +904,7 @@ program
|
|
|
831
904
|
.description("Stop the running Flair (Harper) instance")
|
|
832
905
|
.option("--port <port>", "Harper HTTP port")
|
|
833
906
|
.action(async (opts) => {
|
|
834
|
-
const port =
|
|
907
|
+
const port = resolveHttpPort(opts);
|
|
835
908
|
const platform = process.platform;
|
|
836
909
|
if (platform === "darwin") {
|
|
837
910
|
// macOS: try launchd first
|
|
@@ -874,7 +947,7 @@ program
|
|
|
874
947
|
.description("Restart the Flair (Harper) instance")
|
|
875
948
|
.option("--port <port>", "Harper HTTP port")
|
|
876
949
|
.action(async (opts) => {
|
|
877
|
-
const port =
|
|
950
|
+
const port = resolveHttpPort(opts);
|
|
878
951
|
const platform = process.platform;
|
|
879
952
|
if (platform === "darwin") {
|
|
880
953
|
const label = "ai.tpsdev.flair";
|
|
@@ -1034,7 +1107,7 @@ program
|
|
|
1034
1107
|
.option("--batch-size <n>", "Records per batch", "50")
|
|
1035
1108
|
.option("--delay-ms <ms>", "Delay between batches (ms)", "100")
|
|
1036
1109
|
.action(async (opts) => {
|
|
1037
|
-
const port =
|
|
1110
|
+
const port = resolveHttpPort(opts);
|
|
1038
1111
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
1039
1112
|
const agentId = opts.agent;
|
|
1040
1113
|
const staleOnly = opts.staleOnly ?? false;
|
|
@@ -1115,7 +1188,7 @@ program
|
|
|
1115
1188
|
.requiredOption("--agent <id>", "Agent ID to test with")
|
|
1116
1189
|
.option("--port <port>", "Harper HTTP port")
|
|
1117
1190
|
.action(async (opts) => {
|
|
1118
|
-
const port =
|
|
1191
|
+
const port = resolveHttpPort(opts);
|
|
1119
1192
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
1120
1193
|
const agentId = opts.agent;
|
|
1121
1194
|
const keysDir = defaultKeysDir();
|
|
@@ -1198,7 +1271,7 @@ program
|
|
|
1198
1271
|
.option("--fix", "Automatically fix issues where possible")
|
|
1199
1272
|
.option("--dry-run", "Show what --fix would do without making changes")
|
|
1200
1273
|
.action(async (opts) => {
|
|
1201
|
-
const port =
|
|
1274
|
+
const port = resolveHttpPort(opts);
|
|
1202
1275
|
const autoFix = opts.fix ?? false;
|
|
1203
1276
|
const dryRun = opts.dryRun ?? false;
|
|
1204
1277
|
if (dryRun && !autoFix) {
|
|
@@ -1472,7 +1545,7 @@ program
|
|
|
1472
1545
|
.option("--key <path>", "Ed25519 private key path")
|
|
1473
1546
|
.action(async (query, opts) => {
|
|
1474
1547
|
try {
|
|
1475
|
-
const baseUrl = opts.url || `http://127.0.0.1:${opts
|
|
1548
|
+
const baseUrl = opts.url || `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1476
1549
|
const headers = { "content-type": "application/json" };
|
|
1477
1550
|
const keyPath = opts.key || resolveKeyPath(opts.agent);
|
|
1478
1551
|
if (keyPath) {
|
|
@@ -1516,7 +1589,7 @@ program
|
|
|
1516
1589
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1517
1590
|
.option("--key <path>", "Ed25519 private key path")
|
|
1518
1591
|
.action(async (opts) => {
|
|
1519
|
-
const baseUrl = opts.url || `http://127.0.0.1:${opts
|
|
1592
|
+
const baseUrl = opts.url || `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1520
1593
|
try {
|
|
1521
1594
|
const headers = { "content-type": "application/json" };
|
|
1522
1595
|
const keyPath = opts.key || resolveKeyPath(opts.agent);
|
|
@@ -1566,7 +1639,7 @@ program
|
|
|
1566
1639
|
.option("--url <url>", "Flair base URL (overrides --port)")
|
|
1567
1640
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1568
1641
|
.action(async (opts) => {
|
|
1569
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1642
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1570
1643
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1571
1644
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
1572
1645
|
if (!adminPass) {
|
|
@@ -1646,7 +1719,7 @@ program
|
|
|
1646
1719
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1647
1720
|
.option("--dry-run", "Show what would be imported without making changes")
|
|
1648
1721
|
.action(async (backupPath, opts) => {
|
|
1649
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1722
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1650
1723
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1651
1724
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
1652
1725
|
const dryRun = Boolean(opts.dryRun);
|
|
@@ -1763,7 +1836,7 @@ program
|
|
|
1763
1836
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1764
1837
|
.option("--keys-dir <dir>", "Keys directory", defaultKeysDir())
|
|
1765
1838
|
.action(async (agentId, opts) => {
|
|
1766
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1839
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1767
1840
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1768
1841
|
if (!adminPass) {
|
|
1769
1842
|
console.error("Error: --admin-pass or FLAIR_ADMIN_PASS required");
|
|
@@ -1850,8 +1923,8 @@ program
|
|
|
1850
1923
|
.option("--admin-pass <pass>", "Admin password (or set FLAIR_ADMIN_PASS env)")
|
|
1851
1924
|
.option("--keys-dir <dir>", "Keys directory", defaultKeysDir())
|
|
1852
1925
|
.action(async (importPath, opts) => {
|
|
1853
|
-
const baseUrl = opts.url ?? `http://127.0.0.1:${opts
|
|
1854
|
-
const opsPort =
|
|
1926
|
+
const baseUrl = opts.url ?? `http://127.0.0.1:${resolveHttpPort(opts)}`;
|
|
1927
|
+
const opsPort = resolveOpsPort(opts);
|
|
1855
1928
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1856
1929
|
if (!adminPass) {
|
|
1857
1930
|
console.error("Error: --admin-pass or FLAIR_ADMIN_PASS required");
|
|
@@ -1979,7 +2052,7 @@ program
|
|
|
1979
2052
|
const fromDir = opts.from;
|
|
1980
2053
|
const toDir = opts.to;
|
|
1981
2054
|
const dryRun = opts.dryRun ?? false;
|
|
1982
|
-
const opsPort =
|
|
2055
|
+
const opsPort = resolveOpsPort(opts);
|
|
1983
2056
|
const adminPass = opts.adminPass ?? process.env.FLAIR_ADMIN_PASS ?? "";
|
|
1984
2057
|
if (!existsSync(fromDir)) {
|
|
1985
2058
|
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.3",
|
|
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",
|