@withone/cli 1.57.0 → 1.57.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.
- package/README.md +1 -1
- package/dist/{chunk-KAF5SVJS.js → chunk-GKAIRI4Y.js} +1 -1
- package/dist/{chunk-VH46MAE4.js → chunk-IMU2X3ZV.js} +335 -102
- package/dist/{chunk-XWTV7FA3.js → chunk-YTM6HJKA.js} +1 -1
- package/dist/index.js +17 -14
- package/dist/{migrate-IJI3BTWL.js → migrate-TACDLGPD.js} +2 -2
- package/dist/{runtime-65IPTPVD.js → runtime-SX36HGBA.js} +1 -1
- package/dist/{schema-OO4WK5ND.js → schema-RJC2WQQ6.js} +1 -1
- package/dist/{sql-QIVDWTL3.js → sql-SYCWUPNP.js} +2 -2
- package/package.json +1 -1
- package/skills/one/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -377,7 +377,7 @@ Note: execution responses are never cached — the action always runs live. Only
|
|
|
377
377
|
|
|
378
378
|
### `one mem` — unified memory store
|
|
379
379
|
|
|
380
|
-
One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled `embedded-postgres` plugin, with a `postgres` plugin for remote/self-hosted) that backs both user-authored notes and synced platform data. **Zero-config** — the first `one mem` call on a fresh machine auto-initializes the cluster at `~/.one/pg/cluster/` and writes a daemon PID file so subsequent CLI invocations reuse it.
|
|
380
|
+
One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled `embedded-postgres` plugin, with a `postgres` plugin for remote/self-hosted) that backs both user-authored notes and synced platform data. **Zero-config** — the first `one mem` call on a fresh machine auto-initializes the cluster at `~/.one/pg/cluster/` and writes a daemon PID file so subsequent CLI invocations reuse it. The daemon listens on port 5434, moving to the next free port if another program already holds it; pin a port with `one mem config set embedded-postgres.port <n>`.
|
|
381
381
|
|
|
382
382
|
```bash
|
|
383
383
|
# User memories — works immediately on a new install
|
|
@@ -1456,12 +1456,333 @@ var postgresPlugin = {
|
|
|
1456
1456
|
};
|
|
1457
1457
|
|
|
1458
1458
|
// src/lib/memory/plugins/embedded-postgres/index.ts
|
|
1459
|
+
import path2 from "path";
|
|
1460
|
+
|
|
1461
|
+
// src/lib/memory/plugins/embedded-postgres/daemon.ts
|
|
1459
1462
|
import fs from "fs";
|
|
1460
1463
|
import net from "net";
|
|
1461
1464
|
import path from "path";
|
|
1462
1465
|
import { createRequire } from "module";
|
|
1463
|
-
import { spawn } from "child_process";
|
|
1466
|
+
import { spawn, spawnSync } from "child_process";
|
|
1464
1467
|
var requireFromHere = createRequire(import.meta.url);
|
|
1468
|
+
var PG_PORT_OFFSET = 1e3;
|
|
1469
|
+
var PORT_SEARCH_SPAN = 20;
|
|
1470
|
+
var LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
1471
|
+
var LOG_KEEP_BYTES = 1024 * 1024;
|
|
1472
|
+
var DEFAULT_STOP_TIMEOUT_MS = 15e3;
|
|
1473
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1474
|
+
function pidFilePath(dataDir) {
|
|
1475
|
+
return path.join(dataDir, ".pgserve.json");
|
|
1476
|
+
}
|
|
1477
|
+
function readPidFile(dataDir) {
|
|
1478
|
+
try {
|
|
1479
|
+
return JSON.parse(fs.readFileSync(pidFilePath(dataDir), "utf8"));
|
|
1480
|
+
} catch {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function writePidFile(dataDir, rec) {
|
|
1485
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
1486
|
+
fs.writeFileSync(pidFilePath(dataDir), JSON.stringify(rec, null, 2), { mode: 384 });
|
|
1487
|
+
}
|
|
1488
|
+
function isPidAlive(pid) {
|
|
1489
|
+
try {
|
|
1490
|
+
process.kill(pid, 0);
|
|
1491
|
+
return true;
|
|
1492
|
+
} catch {
|
|
1493
|
+
return false;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async function waitForExit(pid, timeoutMs) {
|
|
1497
|
+
const deadline = Date.now() + timeoutMs;
|
|
1498
|
+
while (isPidAlive(pid)) {
|
|
1499
|
+
if (Date.now() >= deadline) return false;
|
|
1500
|
+
await sleep(50);
|
|
1501
|
+
}
|
|
1502
|
+
return true;
|
|
1503
|
+
}
|
|
1504
|
+
function signal(pid, sig) {
|
|
1505
|
+
try {
|
|
1506
|
+
process.kill(pid, sig);
|
|
1507
|
+
} catch {
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function canBind(host, port) {
|
|
1511
|
+
return new Promise((resolve) => {
|
|
1512
|
+
const server = net.createServer();
|
|
1513
|
+
server.once("error", () => resolve(false));
|
|
1514
|
+
server.listen({ port, host, exclusive: true }, () => server.close(() => resolve(true)));
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
async function portPairFree(host, port) {
|
|
1518
|
+
return port + PG_PORT_OFFSET <= 65535 && await canBind(host, port) && await canBind("127.0.0.1", port + PG_PORT_OFFSET);
|
|
1519
|
+
}
|
|
1520
|
+
async function choosePort(host, preferred, explicit) {
|
|
1521
|
+
if (await portPairFree(host, preferred)) return preferred;
|
|
1522
|
+
const hint = 'set a different "memory.embedded-postgres.port" in ~/.one/config.json';
|
|
1523
|
+
if (explicit) {
|
|
1524
|
+
throw new Error(
|
|
1525
|
+
`Port ${preferred} (or ${preferred + PG_PORT_OFFSET}, its internal Postgres port) on ${host} is already in use by another program, so the embedded Postgres can't start there. Free it, or ${hint}.`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
for (let port = preferred + 1; port <= preferred + PORT_SEARCH_SPAN; port++) {
|
|
1529
|
+
if (await portPairFree(host, port)) return port;
|
|
1530
|
+
}
|
|
1531
|
+
throw new Error(
|
|
1532
|
+
`Ports ${preferred}-${preferred + PORT_SEARCH_SPAN} on ${host} are all already in use, so the embedded Postgres can't start. Free one, or ${hint}.`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
function readRange(file, start, length) {
|
|
1536
|
+
const fd = fs.openSync(file, "r");
|
|
1537
|
+
try {
|
|
1538
|
+
const buf = Buffer.alloc(length);
|
|
1539
|
+
const n = fs.readSync(fd, buf, 0, length, start);
|
|
1540
|
+
return buf.subarray(0, n);
|
|
1541
|
+
} finally {
|
|
1542
|
+
fs.closeSync(fd);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
function fileSize(file) {
|
|
1546
|
+
try {
|
|
1547
|
+
return fs.statSync(file).size;
|
|
1548
|
+
} catch {
|
|
1549
|
+
return 0;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function trimLog(logPath, maxBytes = LOG_MAX_BYTES, keepBytes = LOG_KEEP_BYTES) {
|
|
1553
|
+
const size = fileSize(logPath);
|
|
1554
|
+
if (size <= maxBytes) return;
|
|
1555
|
+
const tail = readRange(logPath, size - keepBytes, keepBytes);
|
|
1556
|
+
const nl = tail.indexOf(10);
|
|
1557
|
+
const kept = nl === -1 ? tail : tail.subarray(nl + 1);
|
|
1558
|
+
const notice = `[one] pgserve.log passed ${maxBytes} bytes; kept the last ${kept.length} (${(/* @__PURE__ */ new Date()).toISOString()})
|
|
1559
|
+
`;
|
|
1560
|
+
fs.writeFileSync(logPath, Buffer.concat([Buffer.from(notice), kept]));
|
|
1561
|
+
}
|
|
1562
|
+
function logSince(logPath, offset, maxBytes = 2e3) {
|
|
1563
|
+
const size = fileSize(logPath);
|
|
1564
|
+
const start = Math.max(offset, size - maxBytes);
|
|
1565
|
+
if (size <= start) return "";
|
|
1566
|
+
return readRange(logPath, start, size - start).toString("utf8").replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
1567
|
+
}
|
|
1568
|
+
function lockIsStale(lockPath, ttlMs) {
|
|
1569
|
+
try {
|
|
1570
|
+
if (Date.now() - fs.statSync(lockPath).mtimeMs > ttlMs) return true;
|
|
1571
|
+
const { pid } = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
1572
|
+
return typeof pid === "number" && !isPidAlive(pid);
|
|
1573
|
+
} catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
async function withStartupLock(dataDir, waitMs, fn) {
|
|
1578
|
+
const lockPath = path.join(dataDir, ".pgserve.lock");
|
|
1579
|
+
const deadline = Date.now() + waitMs;
|
|
1580
|
+
for (; ; ) {
|
|
1581
|
+
try {
|
|
1582
|
+
fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), { flag: "wx" });
|
|
1583
|
+
break;
|
|
1584
|
+
} catch (err) {
|
|
1585
|
+
if (err.code !== "EEXIST") throw err;
|
|
1586
|
+
}
|
|
1587
|
+
if (lockIsStale(lockPath, waitMs)) {
|
|
1588
|
+
fs.rmSync(lockPath, { force: true });
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (Date.now() >= deadline) {
|
|
1592
|
+
throw new Error(`Timed out waiting for another \`one\` process to start the embedded Postgres (lock: ${lockPath}).`);
|
|
1593
|
+
}
|
|
1594
|
+
await sleep(100);
|
|
1595
|
+
}
|
|
1596
|
+
try {
|
|
1597
|
+
return await fn();
|
|
1598
|
+
} finally {
|
|
1599
|
+
fs.rmSync(lockPath, { force: true });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function readPostmasterPid(clusterDir) {
|
|
1603
|
+
try {
|
|
1604
|
+
const lines = fs.readFileSync(path.join(clusterDir, "postmaster.pid"), "utf8").split("\n");
|
|
1605
|
+
const pid = Number.parseInt(lines[0] ?? "", 10);
|
|
1606
|
+
const port = Number.parseInt(lines[3] ?? "", 10);
|
|
1607
|
+
return pid > 0 && port > 0 ? { pid, port } : null;
|
|
1608
|
+
} catch {
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function pgserveOwner(postmasterPid, deps) {
|
|
1613
|
+
const ppid = deps.processInfo(postmasterPid)?.ppid;
|
|
1614
|
+
if (!ppid || ppid <= 1) return null;
|
|
1615
|
+
const command = deps.processInfo(ppid)?.command ?? "";
|
|
1616
|
+
return /pgserve[\\/]bin[\\/]postgres-server\.js/.test(command) ? ppid : null;
|
|
1617
|
+
}
|
|
1618
|
+
async function stopStaleCluster(clusterDir, deps, timeoutMs) {
|
|
1619
|
+
const holder = readPostmasterPid(clusterDir);
|
|
1620
|
+
if (!holder || !isPidAlive(holder.pid)) return;
|
|
1621
|
+
if (await deps.probe(holder.port, "postmaster") !== "ours") return;
|
|
1622
|
+
const owner = pgserveOwner(holder.pid, deps);
|
|
1623
|
+
if (owner !== null) {
|
|
1624
|
+
signal(owner, "SIGTERM");
|
|
1625
|
+
if (!await waitForExit(owner, timeoutMs)) signal(owner, "SIGKILL");
|
|
1626
|
+
}
|
|
1627
|
+
if (!isPidAlive(holder.pid)) return;
|
|
1628
|
+
signal(holder.pid, "SIGINT");
|
|
1629
|
+
if (!await waitForExit(holder.pid, timeoutMs)) {
|
|
1630
|
+
throw new Error(
|
|
1631
|
+
`The embedded Postgres (PID ${holder.pid}) is holding ${clusterDir} and did not stop. Stop it (kill ${holder.pid}) and retry.`
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
async function findHealthyDaemon(dataDir, deps) {
|
|
1636
|
+
const rec = readPidFile(dataDir);
|
|
1637
|
+
if (!rec || !isPidAlive(rec.pid)) return null;
|
|
1638
|
+
return await deps.probe(rec.port, "router") === "ours" ? rec.port : null;
|
|
1639
|
+
}
|
|
1640
|
+
function pgserveArgs(cfg, clusterDir, port) {
|
|
1641
|
+
const args = [
|
|
1642
|
+
"--data",
|
|
1643
|
+
clusterDir,
|
|
1644
|
+
"--port",
|
|
1645
|
+
String(port),
|
|
1646
|
+
"--host",
|
|
1647
|
+
cfg.host,
|
|
1648
|
+
"--log",
|
|
1649
|
+
cfg.logLevel,
|
|
1650
|
+
"--no-stats",
|
|
1651
|
+
// Single process. In cluster mode a worker that can't bind the port is
|
|
1652
|
+
// re-forked immediately, forever, each crash logging a full stack trace.
|
|
1653
|
+
"--no-cluster"
|
|
1654
|
+
];
|
|
1655
|
+
if (cfg.pgvector) args.push("--pgvector");
|
|
1656
|
+
return args;
|
|
1657
|
+
}
|
|
1658
|
+
async function waitUntilServing(child, cfg, port, deps, log) {
|
|
1659
|
+
const deadline = Date.now() + cfg.startupTimeoutMs;
|
|
1660
|
+
const details = () => {
|
|
1661
|
+
const tail = logSince(log.path, log.offset);
|
|
1662
|
+
return `
|
|
1663
|
+
Log: ${log.path}${tail ? `
|
|
1664
|
+
${tail}` : ""}`;
|
|
1665
|
+
};
|
|
1666
|
+
for (; ; ) {
|
|
1667
|
+
const exit = child.exitStatus();
|
|
1668
|
+
if (exit) throw new Error(`pgserve exited (${exit}) before it was ready.${details()}`);
|
|
1669
|
+
if (await deps.probe(port, "router") === "ours") return;
|
|
1670
|
+
if (Date.now() >= deadline) {
|
|
1671
|
+
throw new Error(
|
|
1672
|
+
`pgserve did not start serving on ${cfg.host}:${port} within ${Math.round(cfg.startupTimeoutMs / 1e3)}s.${details()}`
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
await sleep(250);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async function ensureRunning(cfg, deps) {
|
|
1679
|
+
const reused = await findHealthyDaemon(cfg.dataDir, deps);
|
|
1680
|
+
if (reused !== null) return reused;
|
|
1681
|
+
fs.mkdirSync(cfg.dataDir, { recursive: true });
|
|
1682
|
+
const stopTimeoutMs = deps.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
|
|
1683
|
+
return withStartupLock(cfg.dataDir, cfg.startupTimeoutMs + 3 * stopTimeoutMs, async () => {
|
|
1684
|
+
const raced = await findHealthyDaemon(cfg.dataDir, deps);
|
|
1685
|
+
if (raced !== null) return raced;
|
|
1686
|
+
const clusterDir = path.join(cfg.dataDir, "cluster");
|
|
1687
|
+
fs.mkdirSync(clusterDir, { recursive: true });
|
|
1688
|
+
await stopStaleCluster(clusterDir, deps, stopTimeoutMs);
|
|
1689
|
+
const port = await choosePort(cfg.host, cfg.port, cfg.portExplicit);
|
|
1690
|
+
const logPath = path.join(cfg.dataDir, "pgserve.log");
|
|
1691
|
+
trimLog(logPath);
|
|
1692
|
+
const log = { path: logPath, offset: fileSize(logPath) };
|
|
1693
|
+
const fd = fs.openSync(logPath, "a");
|
|
1694
|
+
let child;
|
|
1695
|
+
try {
|
|
1696
|
+
child = deps.spawnPgserve(pgserveArgs(cfg, clusterDir, port), fd);
|
|
1697
|
+
} finally {
|
|
1698
|
+
fs.closeSync(fd);
|
|
1699
|
+
}
|
|
1700
|
+
await waitUntilServing(child, cfg, port, deps, log);
|
|
1701
|
+
writePidFile(cfg.dataDir, {
|
|
1702
|
+
pid: child.pid,
|
|
1703
|
+
port,
|
|
1704
|
+
dataDir: cfg.dataDir,
|
|
1705
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1706
|
+
});
|
|
1707
|
+
return port;
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
function sameDir(a, b) {
|
|
1711
|
+
const norm = (p) => {
|
|
1712
|
+
let r = path.resolve(p);
|
|
1713
|
+
try {
|
|
1714
|
+
r = fs.realpathSync.native(r);
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
1718
|
+
};
|
|
1719
|
+
return a !== "" && norm(a) === norm(b);
|
|
1720
|
+
}
|
|
1721
|
+
async function probeDataDirectory(Client, target, clusterDir) {
|
|
1722
|
+
const client = new Client({ ...target, connectionTimeoutMillis: 3e3, query_timeout: 3e3 });
|
|
1723
|
+
client.on("error", () => {
|
|
1724
|
+
});
|
|
1725
|
+
try {
|
|
1726
|
+
await client.connect();
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
return err.code === "ECONNREFUSED" ? "down" : "other";
|
|
1729
|
+
}
|
|
1730
|
+
try {
|
|
1731
|
+
const { rows } = await client.query("SHOW data_directory");
|
|
1732
|
+
return sameDir(String(rows[0]?.data_directory ?? ""), clusterDir) ? "ours" : "other";
|
|
1733
|
+
} catch {
|
|
1734
|
+
return "other";
|
|
1735
|
+
} finally {
|
|
1736
|
+
await client.end().catch(() => {
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
function resolvePgserveBin() {
|
|
1741
|
+
const pkg = requireFromHere.resolve("pgserve/package.json");
|
|
1742
|
+
return path.resolve(path.dirname(pkg), "bin/pgserve-wrapper.cjs");
|
|
1743
|
+
}
|
|
1744
|
+
function spawnPgserve(args, logFd, cwd) {
|
|
1745
|
+
const child = spawn(process.execPath, [resolvePgserveBin(), ...args], {
|
|
1746
|
+
detached: true,
|
|
1747
|
+
stdio: ["ignore", logFd, logFd],
|
|
1748
|
+
// pgserve resolves the bundled bun binary relative to its own package
|
|
1749
|
+
// location, not cwd, so cwd doesn't matter — but pin it for clarity.
|
|
1750
|
+
cwd
|
|
1751
|
+
});
|
|
1752
|
+
let status = null;
|
|
1753
|
+
child.once("error", (err) => {
|
|
1754
|
+
status = `failed to start: ${err.message}`;
|
|
1755
|
+
});
|
|
1756
|
+
child.once("exit", (code, sig) => {
|
|
1757
|
+
status = sig ? `signal ${sig}` : `code ${code}`;
|
|
1758
|
+
});
|
|
1759
|
+
child.unref();
|
|
1760
|
+
if (typeof child.pid !== "number") {
|
|
1761
|
+
throw new Error("Failed to spawn pgserve \u2014 no PID returned.");
|
|
1762
|
+
}
|
|
1763
|
+
return { pid: child.pid, exitStatus: () => status };
|
|
1764
|
+
}
|
|
1765
|
+
function processInfo(pid) {
|
|
1766
|
+
if (process.platform === "win32") return null;
|
|
1767
|
+
const res = spawnSync("ps", ["-o", "ppid=", "-o", "command=", "-p", String(pid)], { encoding: "utf8" });
|
|
1768
|
+
const match = /^(\d+)\s+(.*)$/.exec((res.stdout ?? "").trim());
|
|
1769
|
+
return match ? { ppid: Number(match[1]), command: match[2] } : null;
|
|
1770
|
+
}
|
|
1771
|
+
function nodeDaemonDeps(cfg, Client, router) {
|
|
1772
|
+
const clusterDir = path.join(cfg.dataDir, "cluster");
|
|
1773
|
+
const direct = { user: "postgres", password: "postgres", database: "postgres" };
|
|
1774
|
+
return {
|
|
1775
|
+
probe: (port, via) => probeDataDirectory(
|
|
1776
|
+
Client,
|
|
1777
|
+
via === "router" ? { host: cfg.host, port, ...router } : { host: "127.0.0.1", port, ...direct },
|
|
1778
|
+
clusterDir
|
|
1779
|
+
),
|
|
1780
|
+
spawnPgserve: (args, logFd) => spawnPgserve(args, logFd, cfg.dataDir),
|
|
1781
|
+
processInfo
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/lib/memory/plugins/embedded-postgres/index.ts
|
|
1465
1786
|
var BASE_CAPABILITIES = {
|
|
1466
1787
|
vectorSearch: true,
|
|
1467
1788
|
fullTextSearch: true,
|
|
@@ -1477,13 +1798,14 @@ var DEFAULTS = {
|
|
|
1477
1798
|
// `path.join(homeDir(), ...)` here would capture the home directory before
|
|
1478
1799
|
// ONE_HOME could take effect. See lib/home.ts.
|
|
1479
1800
|
get dataDir() {
|
|
1480
|
-
return
|
|
1801
|
+
return path2.join(homeDir(), ".one", "pg");
|
|
1481
1802
|
},
|
|
1482
1803
|
database: "one_mem",
|
|
1483
1804
|
schema: "public",
|
|
1484
1805
|
pgvector: true,
|
|
1485
1806
|
host: "127.0.0.1",
|
|
1486
1807
|
port: 5434,
|
|
1808
|
+
portExplicit: false,
|
|
1487
1809
|
// pgserve auto-provisions databases; the bundled superuser is `postgres`
|
|
1488
1810
|
// with no password by default. We don't expose the password to the user
|
|
1489
1811
|
// since the daemon only listens on 127.0.0.1.
|
|
@@ -1501,6 +1823,7 @@ function parseConfig2(raw) {
|
|
|
1501
1823
|
pgvector: r.pgvector ?? DEFAULTS.pgvector,
|
|
1502
1824
|
host: r.host ?? DEFAULTS.host,
|
|
1503
1825
|
port: r.port ?? DEFAULTS.port,
|
|
1826
|
+
portExplicit: r.port !== void 0,
|
|
1504
1827
|
user: r.user ?? DEFAULTS.user,
|
|
1505
1828
|
password: r.password ?? DEFAULTS.password,
|
|
1506
1829
|
logLevel: r.logLevel ?? DEFAULTS.logLevel,
|
|
@@ -1547,104 +1870,6 @@ function wrapClient2(pool) {
|
|
|
1547
1870
|
}
|
|
1548
1871
|
};
|
|
1549
1872
|
}
|
|
1550
|
-
function pidFilePath(dataDir) {
|
|
1551
|
-
return path.join(dataDir, ".pgserve.json");
|
|
1552
|
-
}
|
|
1553
|
-
function readPidFile(dataDir) {
|
|
1554
|
-
try {
|
|
1555
|
-
const raw = fs.readFileSync(pidFilePath(dataDir), "utf8");
|
|
1556
|
-
return JSON.parse(raw);
|
|
1557
|
-
} catch {
|
|
1558
|
-
return null;
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
function writePidFile(dataDir, rec) {
|
|
1562
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
1563
|
-
fs.writeFileSync(pidFilePath(dataDir), JSON.stringify(rec, null, 2), { mode: 384 });
|
|
1564
|
-
}
|
|
1565
|
-
function isPidAlive(pid) {
|
|
1566
|
-
try {
|
|
1567
|
-
process.kill(pid, 0);
|
|
1568
|
-
return true;
|
|
1569
|
-
} catch {
|
|
1570
|
-
return false;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
async function isPortListening(host, port, timeoutMs = 1e3) {
|
|
1574
|
-
return new Promise((resolve) => {
|
|
1575
|
-
const socket = new net.Socket();
|
|
1576
|
-
let resolved = false;
|
|
1577
|
-
const finish = (ok) => {
|
|
1578
|
-
if (resolved) return;
|
|
1579
|
-
resolved = true;
|
|
1580
|
-
socket.destroy();
|
|
1581
|
-
resolve(ok);
|
|
1582
|
-
};
|
|
1583
|
-
socket.setTimeout(timeoutMs);
|
|
1584
|
-
socket.once("connect", () => finish(true));
|
|
1585
|
-
socket.once("timeout", () => finish(false));
|
|
1586
|
-
socket.once("error", () => finish(false));
|
|
1587
|
-
socket.connect(port, host);
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
async function waitForPort(host, port, deadlineAt) {
|
|
1591
|
-
while (Date.now() < deadlineAt) {
|
|
1592
|
-
if (await isPortListening(host, port)) return;
|
|
1593
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
1594
|
-
}
|
|
1595
|
-
throw new Error(
|
|
1596
|
-
`pgserve did not start listening on ${host}:${port} within ${Math.round((deadlineAt - Date.now() + 3e4) / 1e3)}s. Check the data dir for the postgres log, or remove ${path.basename(pidFilePath("<dataDir>"))} and retry.`
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
function resolvePgserveBin() {
|
|
1600
|
-
const pkg = requireFromHere.resolve("pgserve/package.json");
|
|
1601
|
-
return path.resolve(path.dirname(pkg), "bin/pgserve-wrapper.cjs");
|
|
1602
|
-
}
|
|
1603
|
-
async function ensureRunning(cfg) {
|
|
1604
|
-
const existing = readPidFile(cfg.dataDir);
|
|
1605
|
-
if (existing && isPidAlive(existing.pid)) {
|
|
1606
|
-
if (await isPortListening(cfg.host, existing.port)) {
|
|
1607
|
-
cfg.port = existing.port;
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
fs.mkdirSync(cfg.dataDir, { recursive: true });
|
|
1612
|
-
const clusterDir = path.join(cfg.dataDir, "cluster");
|
|
1613
|
-
fs.mkdirSync(clusterDir, { recursive: true });
|
|
1614
|
-
const args = [
|
|
1615
|
-
"--data",
|
|
1616
|
-
clusterDir,
|
|
1617
|
-
"--port",
|
|
1618
|
-
String(cfg.port),
|
|
1619
|
-
"--host",
|
|
1620
|
-
cfg.host,
|
|
1621
|
-
"--log",
|
|
1622
|
-
cfg.logLevel,
|
|
1623
|
-
"--no-stats"
|
|
1624
|
-
];
|
|
1625
|
-
if (cfg.pgvector) args.push("--pgvector");
|
|
1626
|
-
const bin = resolvePgserveBin();
|
|
1627
|
-
const out = fs.openSync(path.join(cfg.dataDir, "pgserve.log"), "a");
|
|
1628
|
-
const child = spawn(process.execPath, [bin, ...args], {
|
|
1629
|
-
detached: true,
|
|
1630
|
-
stdio: ["ignore", out, out],
|
|
1631
|
-
// pgserve resolves the bundled bun binary relative to its own package
|
|
1632
|
-
// location, not cwd, so cwd doesn't matter — but pin it for clarity.
|
|
1633
|
-
cwd: cfg.dataDir
|
|
1634
|
-
});
|
|
1635
|
-
child.unref();
|
|
1636
|
-
if (typeof child.pid !== "number") {
|
|
1637
|
-
throw new Error("Failed to spawn pgserve \u2014 no PID returned.");
|
|
1638
|
-
}
|
|
1639
|
-
const deadlineAt = Date.now() + cfg.startupTimeoutMs;
|
|
1640
|
-
await waitForPort(cfg.host, cfg.port, deadlineAt);
|
|
1641
|
-
writePidFile(cfg.dataDir, {
|
|
1642
|
-
pid: child.pid,
|
|
1643
|
-
port: cfg.port,
|
|
1644
|
-
dataDir: cfg.dataDir,
|
|
1645
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1646
|
-
});
|
|
1647
|
-
}
|
|
1648
1873
|
async function probePgvector(client) {
|
|
1649
1874
|
try {
|
|
1650
1875
|
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
@@ -1662,12 +1887,20 @@ var LazyEmbeddedPostgresBackend = class {
|
|
|
1662
1887
|
}
|
|
1663
1888
|
async ensure() {
|
|
1664
1889
|
if (this.backend) return this.backend;
|
|
1665
|
-
await ensureRunning(this.config);
|
|
1666
1890
|
const pgMod = await import("pg").catch((err) => {
|
|
1667
1891
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1668
1892
|
throw new Error(`pg is not installed. Run \`npm i pg\` or pick a different backend. (${msg})`);
|
|
1669
1893
|
});
|
|
1670
|
-
const
|
|
1894
|
+
const pgExports = pgMod.Pool ? pgMod : pgMod.default;
|
|
1895
|
+
const PoolCtor = pgExports.Pool;
|
|
1896
|
+
this.config.port = await ensureRunning(
|
|
1897
|
+
this.config,
|
|
1898
|
+
nodeDaemonDeps(this.config, pgExports.Client, {
|
|
1899
|
+
user: this.config.user,
|
|
1900
|
+
password: this.config.password,
|
|
1901
|
+
database: this.config.database
|
|
1902
|
+
})
|
|
1903
|
+
);
|
|
1671
1904
|
const pool = new PoolCtor({
|
|
1672
1905
|
host: this.config.host,
|
|
1673
1906
|
port: this.config.port,
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
} from "./chunk-QCXFSOTS.js";
|
|
36
36
|
import {
|
|
37
37
|
memSqlCommand
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-GKAIRI4Y.js";
|
|
39
39
|
import {
|
|
40
40
|
collectIdentityKeys,
|
|
41
41
|
countRecords,
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
writeDraftProfile,
|
|
67
67
|
writePageToMemory,
|
|
68
68
|
writeProfile
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-YTM6HJKA.js";
|
|
70
70
|
import {
|
|
71
71
|
getByDotPath
|
|
72
72
|
} from "./chunk-44CV5IMX.js";
|
|
@@ -100,7 +100,7 @@ import {
|
|
|
100
100
|
listBackendPlugins,
|
|
101
101
|
loadBackendFromConfig,
|
|
102
102
|
updateRecord
|
|
103
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-IMU2X3ZV.js";
|
|
104
104
|
import {
|
|
105
105
|
DEFAULT_MEMORY_CONFIG,
|
|
106
106
|
defaultSearchableText,
|
|
@@ -7231,7 +7231,7 @@ async function syncModel(api, profile, options) {
|
|
|
7231
7231
|
updateModelState(platform, model, { status: "failed", pagesProcessed, lastCursor }),
|
|
7232
7232
|
(async () => {
|
|
7233
7233
|
try {
|
|
7234
|
-
const { getBackend: getBackend2 } = await import("./runtime-
|
|
7234
|
+
const { getBackend: getBackend2 } = await import("./runtime-SX36HGBA.js");
|
|
7235
7235
|
const backend = await getBackend2();
|
|
7236
7236
|
await Promise.race([
|
|
7237
7237
|
backend.close(),
|
|
@@ -7597,7 +7597,7 @@ async function syncModel(api, profile, options) {
|
|
|
7597
7597
|
db.exec(`DROP TABLE IF EXISTS _seen_ids`);
|
|
7598
7598
|
}
|
|
7599
7599
|
if (options.toMemory !== false) {
|
|
7600
|
-
const backend = await (await import("./runtime-
|
|
7600
|
+
const backend = await (await import("./runtime-SX36HGBA.js")).getBackend();
|
|
7601
7601
|
const type = `${platform}/${model}`;
|
|
7602
7602
|
const existing = await backend.listKeysByType(type);
|
|
7603
7603
|
const sourcePrefix = `${type}:`;
|
|
@@ -7698,7 +7698,7 @@ async function syncModel(api, profile, options) {
|
|
|
7698
7698
|
let statusCounts;
|
|
7699
7699
|
if (options.toMemory !== false) {
|
|
7700
7700
|
try {
|
|
7701
|
-
const backend = await (await import("./runtime-
|
|
7701
|
+
const backend = await (await import("./runtime-SX36HGBA.js")).getBackend();
|
|
7702
7702
|
const typeName = `${platform}/${model}`;
|
|
7703
7703
|
const [active, archived] = await Promise.all([
|
|
7704
7704
|
backend.count(typeName, { status: "active" }),
|
|
@@ -9420,7 +9420,7 @@ ${result.total} results`);
|
|
|
9420
9420
|
}
|
|
9421
9421
|
}
|
|
9422
9422
|
async function syncSqlCommand(platformModel, sql) {
|
|
9423
|
-
const { syncSqlCommand: runSyncSql } = await import("./sql-
|
|
9423
|
+
const { syncSqlCommand: runSyncSql } = await import("./sql-SYCWUPNP.js");
|
|
9424
9424
|
await runSyncSql(platformModel, sql);
|
|
9425
9425
|
}
|
|
9426
9426
|
async function syncDeleteCommand(platformModel, options) {
|
|
@@ -9498,7 +9498,7 @@ async function syncDeleteCommand(platformModel, options) {
|
|
|
9498
9498
|
async function maybeAutoMigrateLegacy(platform, models) {
|
|
9499
9499
|
const dbSize = getDatabaseSize(platform);
|
|
9500
9500
|
if (!dbSize || dbSize === "0 B") return;
|
|
9501
|
-
const { getBackend: getBackend2 } = await import("./runtime-
|
|
9501
|
+
const { getBackend: getBackend2 } = await import("./runtime-SX36HGBA.js");
|
|
9502
9502
|
const backend = await getBackend2();
|
|
9503
9503
|
let memoryHasData = false;
|
|
9504
9504
|
for (const model of models) {
|
|
@@ -9514,7 +9514,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
|
|
|
9514
9514
|
` detected legacy .one/sync/data/${platform}.db (${dbSize}) \u2014 auto-migrating into memory before sync.
|
|
9515
9515
|
`
|
|
9516
9516
|
);
|
|
9517
|
-
const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-
|
|
9517
|
+
const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-TACDLGPD.js");
|
|
9518
9518
|
await memMigrateCommand3({ platform, yes: true });
|
|
9519
9519
|
return;
|
|
9520
9520
|
}
|
|
@@ -9523,7 +9523,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
|
|
|
9523
9523
|
initialValue: true
|
|
9524
9524
|
});
|
|
9525
9525
|
if (p7.isCancel(shouldMigrate) || !shouldMigrate) return;
|
|
9526
|
-
const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-
|
|
9526
|
+
const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-TACDLGPD.js");
|
|
9527
9527
|
await memMigrateCommand2({ platform, yes: true });
|
|
9528
9528
|
}
|
|
9529
9529
|
async function syncSuggestSearchableCommand(platformModel, options = {}) {
|
|
@@ -9584,7 +9584,7 @@ async function syncSuggestSearchableCommand(platformModel, options = {}) {
|
|
|
9584
9584
|
async function syncListCommand(platform) {
|
|
9585
9585
|
const profiles = listProfiles(platform);
|
|
9586
9586
|
const state = await readSyncState();
|
|
9587
|
-
const { getBackend: getBackend2 } = await import("./runtime-
|
|
9587
|
+
const { getBackend: getBackend2 } = await import("./runtime-SX36HGBA.js");
|
|
9588
9588
|
const backend = await getBackend2();
|
|
9589
9589
|
const syncs = await Promise.all(profiles.map(async (p10) => {
|
|
9590
9590
|
const modelState = state[p10.platform]?.[p10.model];
|
|
@@ -9860,7 +9860,7 @@ function registerSyncSubcommands(sync) {
|
|
|
9860
9860
|
await syncSqlCommand(platformModel, sql);
|
|
9861
9861
|
});
|
|
9862
9862
|
sync.command("schema <platform/model>").description("Inspect the JSON structure of synced records (field paths, types, examples) \u2014 useful before writing `sync sql` queries").action(async (platformModel) => {
|
|
9863
|
-
const { syncSchemaCommand } = await import("./schema-
|
|
9863
|
+
const { syncSchemaCommand } = await import("./schema-RJC2WQQ6.js");
|
|
9864
9864
|
await syncSchemaCommand(platformModel);
|
|
9865
9865
|
});
|
|
9866
9866
|
sync.command("delete <platform/model>").description('Delete records from local sync data (e.g. one sync delete notion/pages --id "abc-123")').option("--id <value>", "Delete record by ID").option("--where <conditions>", 'Delete records matching conditions (e.g. "status=archived")').option("--where-sql <predicate>", `Delete using a raw SQL WHERE clause (e.g. "json_extract(data, '$.type') = 'promotion'")`).option("--yes", "Skip confirmation prompt").action(async (platformModel, options) => {
|
|
@@ -11627,6 +11627,8 @@ The unified memory store is a local, pluggable database (\`embedded-postgres\` b
|
|
|
11627
11627
|
The first \`one mem\` call on a fresh install auto-initializes:
|
|
11628
11628
|
- Backend: \`embedded-postgres\` (Postgres 18 via pgserve; downloads ~52MB of binaries on first use, ~2s on later boots)
|
|
11629
11629
|
- Cluster path: \`~/.one/pg/cluster/\`, daemon PID/port at \`~/.one/pg/.pgserve.json\`
|
|
11630
|
+
- Port: \`5434\` by default. If another program already holds it (a database tunnel, another Postgres), the daemon starts on the next free port and records it in \`.pgserve.json\`. A port pinned with \`mem config set embedded-postgres.port <n>\` is never moved \u2014 a conflict there is an error naming the port.
|
|
11631
|
+
- Self-healing: a running daemon is reused only when the Postgres on its port reports this cluster's data directory. A Postgres left holding the cluster by a dead or stuck pgserve is stopped and a fresh daemon started. \`pgserve.log\` is trimmed to its last 1 MB once it passes 10 MB.
|
|
11630
11632
|
- Embedding provider: \`openai\` if a key is already resolvable AND pgvector is loadable; else \`none\`
|
|
11631
11633
|
|
|
11632
11634
|
If pgvector isn't installed in the bundled Postgres (the default binary distribution doesn't ship it yet), the schema applies cleanly without an \`embedding\` column, all FTS paths work, and \`mem doctor\` emits an \`_upgrade\` block pointing at \`brew install pgvector\` when an OpenAI key is present.
|
|
@@ -12277,8 +12279,9 @@ Every \`sync X\` command is also exposed as \`mem sync X\` \u2014 same handlers,
|
|
|
12277
12279
|
logs/{platform}.log # cron run logs
|
|
12278
12280
|
locks/{platform}_{model}/ # cross-process sync locks
|
|
12279
12281
|
~/.one/pg/cluster/ # unified memory store \u2014 embedded Postgres data dir
|
|
12280
|
-
~/.one/pg/.pgserve.json # daemon PID/port file
|
|
12281
|
-
~/.one/pg
|
|
12282
|
+
~/.one/pg/.pgserve.json # daemon PID/port file
|
|
12283
|
+
~/.one/pg/.pgserve.lock # held while a CLI process starts the daemon
|
|
12284
|
+
~/.one/pg/pgserve.log # daemon stdout/stderr (trimmed to 1 MB past 10 MB)
|
|
12282
12285
|
~/.one/config.json # apiKey + openaiApiKey + memory config block
|
|
12283
12286
|
~/.one/sync/
|
|
12284
12287
|
schedules.json # global schedule registry
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
dotPathToJsonbExpr,
|
|
4
4
|
memMigrateCommand,
|
|
5
5
|
reviveStringifiedJson
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YTM6HJKA.js";
|
|
7
7
|
import "./chunk-44CV5IMX.js";
|
|
8
8
|
import "./chunk-OLQ55K6B.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-IMU2X3ZV.js";
|
|
10
10
|
import "./chunk-YMUY55U2.js";
|
|
11
11
|
import "./chunk-6UB623BG.js";
|
|
12
12
|
export {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
memSqlCommand,
|
|
3
3
|
syncSqlCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GKAIRI4Y.js";
|
|
5
5
|
import "./chunk-OLQ55K6B.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-IMU2X3ZV.js";
|
|
7
7
|
import "./chunk-YMUY55U2.js";
|
|
8
8
|
import "./chunk-6UB623BG.js";
|
|
9
9
|
export {
|
package/package.json
CHANGED
package/skills/one/SKILL.md
CHANGED
|
@@ -192,7 +192,7 @@ Knowledge and search responses are cached locally (`~/.one/cache/`). Subsequent
|
|
|
192
192
|
|
|
193
193
|
One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled `embedded-postgres` plugin, with a `postgres` plugin available for remote/self-hosted Postgres) that backs both user-authored notes and synced platform data. `one mem <cmd>` is the primary surface; `one sync` is a namespaced alias (`one mem sync ...`) that writes synced rows into the same store.
|
|
194
194
|
|
|
195
|
-
**Zero-config.** The first `one mem` call on a new machine auto-initializes — no separate `mem init` step required. The `embedded-postgres` plugin downloads its Postgres binaries on first run (~52MB) and writes a daemon PID/port file at `~/.one/pg/.pgserve.json` so subsequent CLI invocations reuse the running cluster. If an OpenAI key is already resolvable (env, `.onerc`, or `config.openaiApiKey`), embeddings enable automatically and search becomes hybrid FTS + semantic. Otherwise you get FTS-only with a structured `_upgrade` hint on every response telling the user how to upgrade.
|
|
195
|
+
**Zero-config.** The first `one mem` call on a new machine auto-initializes — no separate `mem init` step required. The `embedded-postgres` plugin downloads its Postgres binaries on first run (~52MB) and writes a daemon PID/port file at `~/.one/pg/.pgserve.json` so subsequent CLI invocations reuse the running cluster. The daemon listens on 5434, or the next free port if another program holds it; pin one with `one --agent mem config set embedded-postgres.port <n>` (a conflict on a pinned port is an error, not a silent move). If an OpenAI key is already resolvable (env, `.onerc`, or `config.openaiApiKey`), embeddings enable automatically and search becomes hybrid FTS + semantic. Otherwise you get FTS-only with a structured `_upgrade` hint on every response telling the user how to upgrade.
|
|
196
196
|
|
|
197
197
|
**Listing synced rows.** `mem list <type>` takes a positional namespaced type — there is no `--platform` flag. Synced rows live under `<platform>/<model>` types:
|
|
198
198
|
|