@tekmidian/pai 0.13.4 → 0.13.5

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.
@@ -6,7 +6,7 @@ import "../db-CmYbAVCD.mjs";
6
6
  import "../helpers-IjZkXBhj.mjs";
7
7
  import "../embeddings-BJPOcbik.mjs";
8
8
  import "../search-HcdKtMla.mjs";
9
- import "../pick-CmG7SCtp.mjs";
9
+ import "../pick-CsHZ8Abv.mjs";
10
10
  import "../kg-extraction-C8DEUHTS.mjs";
11
11
  import "../factory-Q88X1bAN.mjs";
12
12
  import "../config-C8m-tPhP.mjs";
@@ -5,7 +5,7 @@ import "../db-CmYbAVCD.mjs";
5
5
  import "../helpers-IjZkXBhj.mjs";
6
6
  import "../embeddings-BJPOcbik.mjs";
7
7
  import "../search-HcdKtMla.mjs";
8
- import { C as registerDaemonCommands, D as registerProjectsCommands, E as registerRegistryCommands, O as findMovedPath, S as registerBackupCommands, T as registerMemoryCommands, _ as registerObservationCommands, a as cmdPauseAll, b as registerSetupCommand, c as cmdPause, d as registerKgCommands, f as registerTopicCommands, g as registerSkillCommands, h as registerUpdateCommand, i as cmdClearNames, k as resolveIdentifier, l as registerHelpCommand, m as registerNotifyCommands, n as cmdFind, o as cmdGoto, p as registerTaskCommands, r as cmdList, s as cmdEnd, t as cmdPick, u as registerDbCommands, v as registerZettelCommands, w as registerMcpCommands, x as registerRestoreCommands, y as registerObsidianCommands } from "../pick-CmG7SCtp.mjs";
8
+ import { C as registerDaemonCommands, D as registerProjectsCommands, E as registerRegistryCommands, O as findMovedPath, S as registerBackupCommands, T as registerMemoryCommands, _ as registerObservationCommands, a as cmdPauseAll, b as registerSetupCommand, c as cmdPause, d as registerKgCommands, f as registerTopicCommands, g as registerSkillCommands, h as registerUpdateCommand, i as cmdClearNames, k as resolveIdentifier, l as registerHelpCommand, m as registerNotifyCommands, n as cmdFind, o as cmdGoto, p as registerTaskCommands, r as cmdList, s as cmdEnd, t as cmdPick, u as registerDbCommands, v as registerZettelCommands, w as registerMcpCommands, x as registerRestoreCommands, y as registerObsidianCommands } from "../pick-CsHZ8Abv.mjs";
9
9
  import "../kg-extraction-C8DEUHTS.mjs";
10
10
  import "../factory-Q88X1bAN.mjs";
11
11
  import "../config-C8m-tPhP.mjs";
@@ -2670,6 +2670,86 @@ function registerRestoreCommands(program) {
2670
2670
  });
2671
2671
  }
2672
2672
 
2673
+ //#endregion
2674
+ //#region src/config/json-store.ts
2675
+ /**
2676
+ * json-store.ts — read/write JSON config files without destroying them
2677
+ *
2678
+ * The failure this exists to prevent:
2679
+ *
2680
+ * try { return JSON.parse(read(path)); } catch { return {}; }
2681
+ * ... later ...
2682
+ * write(path, JSON.stringify(ourData));
2683
+ *
2684
+ * An unreadable file becomes an empty object, and the next write makes that
2685
+ * permanent. Silently, exit code 0. This shape appeared three times in this
2686
+ * repo against three different files, and twice in AIBroker.
2687
+ *
2688
+ * The distinction that matters is between *missing* and *unreadable*:
2689
+ *
2690
+ * missing — legitimate first run. Start fresh; writing is safe.
2691
+ * unreadable — the file exists and we could not parse it. Those bytes are
2692
+ * the only copy of something. Never overwrite them.
2693
+ *
2694
+ * Collapsing the second into the first is the bug.
2695
+ *
2696
+ * NOT everything deserves this guard. For a transient buffer — an undelivered
2697
+ * message queue, a cache — starting fresh IS the correct recovery, and
2698
+ * refusing to write would disable the feature permanently. Use `writeJsonAtomic`
2699
+ * alone there: it still prevents a crash from truncating a good file, without
2700
+ * blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.
2701
+ */
2702
+ /**
2703
+ * Read a JSON file, distinguishing "absent" from "damaged".
2704
+ *
2705
+ * @param path file to read
2706
+ * @param label how to name it to the user, e.g. "~/.claude.json"
2707
+ * @throws if the file exists but cannot be read or parsed
2708
+ */
2709
+ function readJsonStrict(path, label = path) {
2710
+ if (!existsSync(path)) return {};
2711
+ let raw;
2712
+ try {
2713
+ raw = readFileSync(path, "utf8");
2714
+ } catch (e) {
2715
+ throw new Error(`Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\nRefusing to continue — writing now would replace its contents with ours alone.`);
2716
+ }
2717
+ try {
2718
+ return JSON.parse(raw);
2719
+ } catch (e) {
2720
+ throw new Error(`${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\nRefusing to continue — overwriting it would destroy whatever it holds.\nRepair the file, or move it aside and re-run this command.`);
2721
+ }
2722
+ }
2723
+ /**
2724
+ * Write JSON without risking the existing file.
2725
+ *
2726
+ * Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file
2727
+ * and renames. Rename is atomic within a filesystem, so a crash mid-write
2728
+ * leaves the original intact rather than truncated — which is how these files
2729
+ * become corrupt in the first place.
2730
+ */
2731
+ function writeJsonAtomic(path, data, opts = {}) {
2732
+ const { backup = true, label = path } = opts;
2733
+ const serialized = JSON.stringify(data, null, 2) + "\n";
2734
+ const dir = dirname(path);
2735
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
2736
+ if (backup && existsSync(path)) try {
2737
+ copyFileSync(path, `${path}.bak-pai`);
2738
+ } catch (e) {
2739
+ throw new Error(`Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\nRefusing to write without a backup.`);
2740
+ }
2741
+ const tmp = `${path}.tmp-pai-${process.pid}`;
2742
+ try {
2743
+ writeFileSync(tmp, serialized, "utf8");
2744
+ renameSync(tmp, path);
2745
+ } catch (e) {
2746
+ try {
2747
+ if (existsSync(tmp)) unlinkSync(tmp);
2748
+ } catch {}
2749
+ throw new Error(`Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\nThe original is unchanged.`);
2750
+ }
2751
+ }
2752
+
2673
2753
  //#endregion
2674
2754
  //#region src/cli/commands/setup/utils.ts
2675
2755
  /**
@@ -2741,17 +2821,18 @@ async function promptYesNo(rl, question, defaultYes = true) {
2741
2821
  if (answer === "") return defaultYes;
2742
2822
  return answer.toLowerCase().startsWith("y");
2743
2823
  }
2824
+ /**
2825
+ * This file holds the Postgres connection string, the storage backend choice,
2826
+ * notification routing and any tracker API token — none of which the user can
2827
+ * reconstruct from memory. It previously returned {} on a parse failure and
2828
+ * then overwrote the file, so a damaged config was replaced by whatever the
2829
+ * current command happened to be setting.
2830
+ */
2744
2831
  function readConfigRaw() {
2745
- if (!existsSync(CONFIG_FILE$2)) return {};
2746
- try {
2747
- return JSON.parse(readFileSync(CONFIG_FILE$2, "utf-8"));
2748
- } catch {
2749
- return {};
2750
- }
2832
+ return readJsonStrict(CONFIG_FILE$2, "~/.config/pai/config.json");
2751
2833
  }
2752
2834
  function writeConfigRaw(data) {
2753
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2754
- writeFileSync(CONFIG_FILE$2, JSON.stringify(data, null, 2) + "\n", "utf-8");
2835
+ writeJsonAtomic(CONFIG_FILE$2, data, { label: "~/.config/pai/config.json" });
2755
2836
  }
2756
2837
  function mergeConfig(updates) {
2757
2838
  const current = readConfigRaw();
@@ -3434,27 +3515,18 @@ async function stepTsHooks(rl) {
3434
3515
 
3435
3516
  //#endregion
3436
3517
  //#region src/cli/commands/settings-manager.ts
3518
+ const SETTINGS_FILE = join(join(homedir(), ".claude"), "settings.json");
3437
3519
  /**
3438
- * settings-manager merge-not-overwrite utility for ~/.claude/settings.json
3439
- *
3440
- * Provides safe, idempotent writes to Claude Code's settings.json:
3441
- * - env vars: added only if the key is absent (never overwrites)
3442
- * - hooks: appended per hookType, deduplicated by command string
3443
- * - statusLine: written only if the key is not already present
3520
+ * ~/.claude/settings.json is Claude Code's own file, not ours: hooks, env,
3521
+ * permissions, statusline, enabled plugins. PAI only ever adds to it. Returning
3522
+ * {} for a damaged file and then writing meant a stray parse error could strip
3523
+ * every hook registration the user had.
3444
3524
  */
3445
- const CLAUDE_DIR = join(homedir(), ".claude");
3446
- const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
3447
3525
  function readSettingsJson() {
3448
- if (!existsSync(SETTINGS_FILE)) return {};
3449
- try {
3450
- return JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
3451
- } catch {
3452
- return {};
3453
- }
3526
+ return readJsonStrict(SETTINGS_FILE, "~/.claude/settings.json");
3454
3527
  }
3455
3528
  function writeSettingsJson(data) {
3456
- if (!existsSync(CLAUDE_DIR)) mkdirSync(CLAUDE_DIR, { recursive: true });
3457
- writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2) + "\n", "utf-8");
3529
+ writeJsonAtomic(SETTINGS_FILE, data, { label: "~/.claude/settings.json" });
3458
3530
  }
3459
3531
  /**
3460
3532
  * Merge env vars — add keys that are absent, never overwrite existing ones.
@@ -9061,4 +9133,4 @@ async function cmdPick(db, opts = {}) {
9061
9133
 
9062
9134
  //#endregion
9063
9135
  export { registerDaemonCommands as C, registerProjectsCommands as D, registerRegistryCommands as E, findMovedPath as O, registerBackupCommands as S, registerMemoryCommands as T, registerObservationCommands as _, cmdPauseAll as a, registerSetupCommand as b, cmdPause as c, registerKgCommands as d, registerTopicCommands as f, registerSkillCommands as g, registerUpdateCommand as h, cmdClearNames as i, resolveIdentifier as k, registerHelpCommand as l, registerNotifyCommands as m, cmdFind as n, cmdGoto as o, registerTaskCommands as p, cmdList as r, cmdEnd as s, cmdPick as t, registerDbCommands as u, registerZettelCommands as v, registerMcpCommands as w, registerRestoreCommands as x, registerObsidianCommands as y };
9064
- //# sourceMappingURL=pick-CmG7SCtp.mjs.map
9136
+ //# sourceMappingURL=pick-CsHZ8Abv.mjs.map