@tekmidian/pai 0.32.2 → 0.33.0

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 (30) hide show
  1. package/dist/{checkpoint-block-DYq9RmMU.mjs → checkpoint-block-D3rm4dAJ.mjs} +4 -4
  2. package/dist/checkpoint-block-D3rm4dAJ.mjs.map +1 -0
  3. package/dist/cli/index.mjs +4 -4
  4. package/dist/cli/program.mjs +4 -4
  5. package/dist/config-CcdkNSWa.mjs.map +1 -1
  6. package/dist/daemon/index.mjs +4 -4
  7. package/dist/{daemon-BhbX7XHt.mjs → daemon-BlaOnldQ.mjs} +5 -5
  8. package/dist/{daemon-BhbX7XHt.mjs.map → daemon-BlaOnldQ.mjs.map} +1 -1
  9. package/dist/{factory-hmDHxTaJ.mjs → factory-Cd94gzNz.mjs} +2 -2
  10. package/dist/{factory-hmDHxTaJ.mjs.map → factory-Cd94gzNz.mjs.map} +1 -1
  11. package/dist/hooks/load-project-context.mjs.map +1 -1
  12. package/dist/{main-resolver-DjyUDJrv.mjs → main-resolver-qIjvdJsb.mjs} +27 -12
  13. package/dist/main-resolver-qIjvdJsb.mjs.map +1 -0
  14. package/dist/{pick-BBb6PiKa.mjs → pick-CX1A7ffC.mjs} +116 -17
  15. package/dist/pick-CX1A7ffC.mjs.map +1 -0
  16. package/dist/{postgres-5IpjMwo_.mjs → postgres-L6TIAIGV.mjs} +2 -2
  17. package/dist/postgres-L6TIAIGV.mjs.map +1 -0
  18. package/dist/search-C32zQ0V0.mjs.map +1 -1
  19. package/dist/{work-queue-worker-CGMXpO_4.mjs → work-queue-worker-DgoTjWfd.mjs} +2 -2
  20. package/dist/{work-queue-worker-CGMXpO_4.mjs.map → work-queue-worker-DgoTjWfd.mjs.map} +1 -1
  21. package/docs/commands/README.md +2 -0
  22. package/docs/commands/project.md +22 -0
  23. package/docs/commands/projects.md +22 -0
  24. package/package.json +1 -1
  25. package/src/hooks/session-stop.sh +47 -9
  26. package/src/hooks/ts/session-start/load-project-context.ts +2 -2
  27. package/dist/checkpoint-block-DYq9RmMU.mjs.map +0 -1
  28. package/dist/main-resolver-DjyUDJrv.mjs.map +0 -1
  29. package/dist/pick-BBb6PiKa.mjs.map +0 -1
  30. package/dist/postgres-5IpjMwo_.mjs.map +0 -1
@@ -7,13 +7,13 @@ import { i as indexProject, n as indexAll, t as embedChunks } from "./sync--BoxB
7
7
  import { t as STOP_WORDS } from "./stop-words-BaMEGVeY.mjs";
8
8
  import { n as populateSlugs, r as searchMemory } from "./search-C32zQ0V0.mjs";
9
9
  import { n as formatDetection, r as formatDetectionJson, t as detectProject } from "./detect-Bf2z-oKB.mjs";
10
- import { _ as transcriptFiles, a as readBodyFile, c as loadScanConfig, d as saveScanConfig, f as upsertProject, g as scanTranscriptFolders, h as findMovedProjects, i as findNotesDir$1, m as claudeProjectsDir, n as applyContinue, o as extractAndStoreTriples, p as upsertSession, r as findLatestNote, s as cmdScan, t as appendCheckpointToNote, u as resolveHome } from "./checkpoint-block-DYq9RmMU.mjs";
10
+ import { _ as transcriptFiles, a as readBodyFile, c as loadScanConfig, d as saveScanConfig, f as upsertProject, g as scanTranscriptFolders, h as findMovedProjects, i as findNotesDir$1, m as claudeProjectsDir, n as applyContinue, o as extractAndStoreTriples, p as upsertSession, r as findLatestNote, s as cmdScan, t as appendCheckpointToNote, u as resolveHome } from "./checkpoint-block-D3rm4dAJ.mjs";
11
11
  import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-B0P1TvUr.mjs";
12
12
  import { t as PaiClient } from "./ipc-client-aVKVERjJ.mjs";
13
13
  import { a as expandHome, c as UNROUTED, i as ensureConfigDir, n as CONFIG_FILE$2, o as loadConfig, s as OWNER_LABEL_PREFIX, t as CONFIG_DIR } from "./config-CcdkNSWa.mjs";
14
- import { i as humanDuration, t as createStorageBackend } from "./factory-hmDHxTaJ.mjs";
14
+ import { i as humanDuration, t as createStorageBackend } from "./factory-Cd94gzNz.mjs";
15
15
  import { s as kgQuery } from "./kg-entity-r8duqhi9.mjs";
16
- import { _ as scanSessions, a as renderDedupedSessions, c as probeResume, d as callAiBroker, f as fetchLiveSessions, g as resolveSessionByNameOrId, h as fmtAge, i as normalizeName$2, l as restoreTopLevel, m as sendToSession, o as hasConversation, p as revealItermSession, r as buildDeduped, s as launchInDir, u as printExitDir } from "./main-resolver-DjyUDJrv.mjs";
16
+ import { _ as scanSessions, a as renderDedupedSessions, c as probeResume, d as callAiBroker, f as fetchLiveSessions, g as resolveSessionByNameOrId, h as fmtAge, i as normalizeName$2, l as restoreTopLevel, m as sendToSession, o as hasConversation, p as revealItermSession, r as buildDeduped, s as launchInDir, u as printExitDir } from "./main-resolver-qIjvdJsb.mjs";
17
17
  import { appendFileSync, chmodSync, copyFileSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
18
18
  import { homedir, platform, tmpdir } from "node:os";
19
19
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -1277,7 +1277,7 @@ function cmdConfig(db, identifier, opts) {
1277
1277
  * `my project`. That looseness is the point, and the uniqueness rule below is
1278
1278
  * what keeps it safe.
1279
1279
  */
1280
- function norm(s) {
1280
+ function norm$1(s) {
1281
1281
  return s.normalize("NFC").toLowerCase().replace(/[^\p{Letter}\p{Number}]+/gu, "");
1282
1282
  }
1283
1283
  /** Hidden by Unix convention — tool state rather than a project directory. */
@@ -1322,9 +1322,9 @@ function stripOrderingPrefix(name) {
1322
1322
  */
1323
1323
  function matchKeys(name) {
1324
1324
  const keys = /* @__PURE__ */ new Set();
1325
- const bare = norm(name);
1325
+ const bare = norm$1(name);
1326
1326
  if (bare) keys.add(bare);
1327
- const stripped = norm(stripOrderingPrefix(name));
1327
+ const stripped = norm$1(stripOrderingPrefix(name));
1328
1328
  if (stripped) keys.add(stripped);
1329
1329
  return keys;
1330
1330
  }
@@ -1421,7 +1421,7 @@ function suggestByBasename(rootPath) {
1421
1421
  * merged out of this registry earlier the same day.
1422
1422
  *
1423
1423
  * The comparison must be by realpath, not string. String equality is precisely
1424
- * what missed it: the two paths share not one character after `/Users/i052341/`.
1424
+ * what missed it: the two paths share not one character after `/Users/owner/`.
1425
1425
  */
1426
1426
  function suggestMovedPath(rootPath, otherRoots = []) {
1427
1427
  const candidate = relocateRenamedAncestor(rootPath) ?? suggestByBasename(rootPath);
@@ -1730,6 +1730,102 @@ function cmdMerge(db, fromSlug, intoSlug, opts = {}) {
1730
1730
  console.log();
1731
1731
  }
1732
1732
 
1733
+ //#endregion
1734
+ //#region src/cli/commands/project/here.ts
1735
+ /** Normalise for name comparison: lowercase, collapse whitespace and hyphens. */
1736
+ function norm(s) {
1737
+ return s.toLowerCase().replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
1738
+ }
1739
+ /**
1740
+ * Find the project a human means by `name`.
1741
+ *
1742
+ * Exact display-name or slug first. Only if that finds nothing do we fall back
1743
+ * to requiring every query word to appear, which is deliberately narrower than
1744
+ * a substring match: it reaches "Jobs Search Beta" from "jobs beta" without
1745
+ * also reaching a sibling project that merely shares the first word.
1746
+ */
1747
+ function findProjectsByName(db, name) {
1748
+ const rows = db.prepare("SELECT id, slug, display_name, root_path, status FROM projects").all();
1749
+ const q = norm(name);
1750
+ const exact = rows.filter((r) => norm(r.display_name) === q || norm(r.slug) === q);
1751
+ if (exact.length > 0) return exact;
1752
+ const words = q.split(" ").filter(Boolean);
1753
+ if (words.length === 0) return [];
1754
+ return rows.filter((r) => {
1755
+ const hay = `${norm(r.display_name)} ${norm(r.slug)}`;
1756
+ return words.every((w) => hay.includes(w));
1757
+ });
1758
+ }
1759
+ /** Slug from a display name: lowercase, non-alphanumerics to hyphens. */
1760
+ function slugFromName(name) {
1761
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
1762
+ }
1763
+ function cmdHere(db, name, opts = {}) {
1764
+ let target;
1765
+ try {
1766
+ target = realpathSync(opts.cwd ?? process.cwd());
1767
+ } catch {
1768
+ console.error(err(`Cannot resolve the current directory.`));
1769
+ process.exit(1);
1770
+ return;
1771
+ }
1772
+ const encoded = encodeDir(target);
1773
+ const matches = findProjectsByName(db, name);
1774
+ if (matches.length > 1) {
1775
+ console.error(err(`"${name}" matches ${matches.length} projects — name one exactly:\n`) + matches.map((m) => dim$1(` ${m.slug} (${m.display_name})\n`)).join(""));
1776
+ process.exit(1);
1777
+ return;
1778
+ }
1779
+ const owner = db.prepare("SELECT id, slug, display_name FROM projects WHERE encoded_dir = ?").get(encoded);
1780
+ if (matches.length === 1) {
1781
+ const p = matches[0];
1782
+ if (owner && owner.id !== p.id) {
1783
+ console.error(err(`This directory is already registered to ${bold$1(owner.slug)}.\n`) + dim$1(` ${target}\n`) + dim$1(` Merge them: pai project merge ${owner.slug} ${p.slug} --execute`));
1784
+ process.exit(1);
1785
+ return;
1786
+ }
1787
+ let sameAlready = false;
1788
+ try {
1789
+ sameAlready = realpathSync(p.root_path) === target;
1790
+ } catch {
1791
+ sameAlready = false;
1792
+ }
1793
+ if (sameAlready) {
1794
+ console.log(ok$1(`Already set: ${bold$1(p.display_name)}`));
1795
+ console.log(dim$1(` ${target}`));
1796
+ return;
1797
+ }
1798
+ if (opts.dryRun) {
1799
+ console.log(dim$1(`Would repoint ${p.slug}:`));
1800
+ console.log(dim$1(` from ${p.root_path}`));
1801
+ console.log(dim$1(` to ${target}`));
1802
+ return;
1803
+ }
1804
+ db.prepare("UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?").run(target, encoded, now(), p.id);
1805
+ console.log(ok$1(`${bold$1(p.display_name)} is now here`));
1806
+ console.log(dim$1(` was: ${p.root_path}`));
1807
+ console.log(dim$1(` now: ${target}`));
1808
+ return;
1809
+ }
1810
+ if (owner) {
1811
+ console.error(err(`This directory is already registered as ${bold$1(owner.slug)} (${owner.display_name}).\n`) + dim$1(` Rename it instead: pai project edit ${owner.slug} --display-name "${name}"`));
1812
+ process.exit(1);
1813
+ return;
1814
+ }
1815
+ const slug = slugFromName(name);
1816
+ const finalSlug = db.prepare("SELECT 1 FROM projects WHERE slug = ?").get(slug) ? `${slug}-${encoded.slice(-6)}` : slug;
1817
+ if (opts.dryRun) {
1818
+ console.log(dim$1(`Would create ${finalSlug} (${name}) at ${target}`));
1819
+ return;
1820
+ }
1821
+ const ts = now();
1822
+ db.prepare(`INSERT INTO projects (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)
1823
+ VALUES (?, ?, ?, ?, 'local', 'active', ?, ?)`).run(finalSlug, name, target, encoded, ts, ts);
1824
+ console.log(ok$1(`Created ${bold$1(name)}`));
1825
+ console.log(dim$1(` slug: ${finalSlug}`));
1826
+ console.log(dim$1(` path: ${target}`));
1827
+ }
1828
+
1733
1829
  //#endregion
1734
1830
  //#region src/cli/commands/project/unregister.ts
1735
1831
  function cmdUnregister(db, slug, opts = {}) {
@@ -1777,7 +1873,7 @@ function cmdUnregister(db, slug, opts = {}) {
1777
1873
  //#region src/cli/commands/project/projects-index.ts
1778
1874
  function registerProjectsCommands(projectsCmd, getDb) {
1779
1875
  projectsCmd.command("list", { isDefault: true }).description("Show the unified deduped listing (same as `pai`).\nUse --all to include cold / zero-session / archived projects.").option("--all", "Show all entries including cold / zero-session / archived projects").option("-n, --n <count>", "Max rows to show", "20").action(async (opts) => {
1780
- const { cmdMain } = await import("./main-resolver-DjyUDJrv.mjs").then((n) => n.n);
1876
+ const { cmdMain } = await import("./main-resolver-qIjvdJsb.mjs").then((n) => n.n);
1781
1877
  await cmdMain(getDb(), void 0, void 0, opts);
1782
1878
  });
1783
1879
  projectsCmd.command("list-raw").description("Raw registry listing with slug, path, status, last_active columns").option("--all", "Include archived projects (default: active only)").option("--status <status>", "Filter by status: active | archived").option("--tag <tag>", "Filter by tag").option("--type <type>", "Filter by type").action((opts) => {
@@ -1840,6 +1936,9 @@ function registerProjectsCommands(projectsCmd, getDb) {
1840
1936
  projectsCmd.command("rebind <slug> <new-path>").description("Manually update the root_path for a project (for when auto-detect found multiple matches).\nValidates the new path exists and is a directory, then updates the registry.").action((slug, newPath) => {
1841
1937
  cmdRebind(getDb(), slug, newPath);
1842
1938
  });
1939
+ projectsCmd.command("here <name>").description("Declare that the CURRENT directory is this project — no slug, no path.\nRepoints an existing project of that name, or creates it. Stores the\ncanonical (realpath) location, so a symlinked parent cannot register the\nsame directory twice.").option("--dry-run", "Show what would change without writing").action((name, opts) => {
1940
+ cmdHere(getDb(), name, { dryRun: opts.dryRun });
1941
+ });
1843
1942
  projectsCmd.command("tag <slug> <tags...>").description("Add one or more tags to a project").action((slug, tags) => {
1844
1943
  cmdTag$1(getDb(), slug, tags);
1845
1944
  });
@@ -2848,7 +2947,7 @@ function registerMemoryCommands(memoryCmd, getDb) {
2848
2947
  * self-addressed, and no pattern is ever inferred.
2849
2948
  *
2850
2949
  * Aliases are NOT matched by pattern, deliberately. `matthias.nott+x@gmail.com`
2851
- * and `matthias.nott@gmail.com` are the same mailbox, and Gmail also ignores
2950
+ * and `owner.name@example.com` are the same mailbox, and Gmail also ignores
2852
2951
  * dots — but the general forms of those rules ("same local part before a plus",
2853
2952
  * "same domain") also match addresses belonging to other people. Gmail's own
2854
2953
  * normalisation is Gmail's; other providers treat `+` and `.` as ordinary
@@ -3484,7 +3583,7 @@ function cmdLogs(opts) {
3484
3583
  }
3485
3584
  function registerDaemonCommands(daemonCmd) {
3486
3585
  daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
3487
- const { serve } = await import("./daemon-BhbX7XHt.mjs").then((n) => n.t);
3586
+ const { serve } = await import("./daemon-BlaOnldQ.mjs").then((n) => n.t);
3488
3587
  const { loadConfig: lc, ensureConfigDir } = await import("./config-CcdkNSWa.mjs").then((n) => n.r);
3489
3588
  ensureConfigDir();
3490
3589
  await serve(lc());
@@ -5437,8 +5536,8 @@ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
5437
5536
  *
5438
5537
  * Ownership belongs to the sub-projects directly under the root — they are the
5439
5538
  * ones mirroring PAI projects. Anything deeper is a FOLDER: a way to group work
5440
- * inside one owner's inbox, not a new owner. So `Claude / Jobs Matthias /
5441
- * Executive Search` is eighteen tasks belonging to Jobs Matthias, filed under a
5539
+ * inside one owner's inbox, not a new owner. So `Claude / Jobs Alpha /
5540
+ * Executive Search` is eighteen tasks belonging to Jobs Alpha, filed under a
5442
5541
  * heading.
5443
5542
  *
5444
5543
  * This used to take the root and its direct children only. A grandchild was not
@@ -9519,7 +9618,7 @@ function reconcile(sessions, projects) {
9519
9618
  *
9520
9619
  * Ownership is decided by matching the container name against a curated alias.
9521
9620
  * A session's display name is almost never its PAI slug — "SL" is
9522
- * `seriousletter`, "Home" is `i052341` — so a freshly created sub-project
9621
+ * `seriousletter`, "Home" is `owner` — so a freshly created sub-project
9523
9622
  * routes nowhere until someone connects the two. That gap bit twice on
9524
9623
  * 2026-08-02: the project existed, looked addressable in every listing, and
9525
9624
  * tasks filed into it came back unrouted.
@@ -11248,7 +11347,7 @@ function cmdActive(db, opts) {
11248
11347
  async function cmdAutoRoute(opts) {
11249
11348
  const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("./auto-route-C8xAfsds.mjs");
11250
11349
  const { openRegistry } = await import("./db-BtuN768f.mjs").then((n) => n.t);
11251
- const { createStorageBackend } = await import("./factory-hmDHxTaJ.mjs").then((n) => n.n);
11350
+ const { createStorageBackend } = await import("./factory-Cd94gzNz.mjs").then((n) => n.n);
11252
11351
  const { loadConfig } = await import("./config-CcdkNSWa.mjs").then((n) => n.r);
11253
11352
  const config = loadConfig();
11254
11353
  const registryDb = openRegistry();
@@ -12757,7 +12856,7 @@ async function countVectorDbPaths(oldPaths) {
12757
12856
  if (oldPaths.length === 0) return 0;
12758
12857
  try {
12759
12858
  const { loadConfig } = await import("./config-CcdkNSWa.mjs").then((n) => n.r);
12760
- const { PostgresBackend } = await import("./postgres-5IpjMwo_.mjs");
12859
+ const { PostgresBackend } = await import("./postgres-L6TIAIGV.mjs");
12761
12860
  const config = loadConfig();
12762
12861
  if (config.storageBackend !== "postgres") return 0;
12763
12862
  const pgBackend = new PostgresBackend(config.postgres ?? {});
@@ -12778,7 +12877,7 @@ async function updateVectorDbPaths(moves) {
12778
12877
  if (moves.length === 0) return 0;
12779
12878
  try {
12780
12879
  const { loadConfig } = await import("./config-CcdkNSWa.mjs").then((n) => n.r);
12781
- const { PostgresBackend } = await import("./postgres-5IpjMwo_.mjs");
12880
+ const { PostgresBackend } = await import("./postgres-L6TIAIGV.mjs");
12782
12881
  const config = loadConfig();
12783
12882
  if (config.storageBackend !== "postgres") return 0;
12784
12883
  const pgBackend = new PostgresBackend(config.postgres ?? {});
@@ -14061,4 +14160,4 @@ async function cmdPick(db, opts = {}) {
14061
14160
 
14062
14161
  //#endregion
14063
14162
  export { registerProjectsCommands as A, registerRestoreCommands as C, registerIdentityCommands as D, registerMcpCommands as E, resolveIdentifier as M, registerMemoryCommands as O, registerSetupCommand as S, registerDaemonCommands as T, registerUpdateCommand as _, cmdEnd as a, registerZettelCommands as b, cmdGoto as c, registerHelpCommand as d, registerDbCommands as f, registerNotifyCommands as g, registerTaskCommands as h, cmdPauseAll as i, findMovedPath as j, registerRegistryCommands as k, cmdPause as l, registerTopicCommands as m, cmdFind as n, registerSessionCleanupCommand as o, registerKgCommands as p, cmdClearNames as r, registerSessionCommands as s, cmdPick as t, cmdList as u, registerSkillCommands as v, registerBackupCommands as w, registerObsidianCommands as x, registerObservationCommands as y };
14064
- //# sourceMappingURL=pick-BBb6PiKa.mjs.map
14163
+ //# sourceMappingURL=pick-CX1A7ffC.mjs.map