@tekmidian/pai 0.6.3 → 0.6.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.
@@ -4783,11 +4783,14 @@ async function stepSkillStubs(rl) {
4783
4783
  //#endregion
4784
4784
  //#region src/cli/commands/setup/steps/08-hooks.ts
4785
4785
  /** Step 7: Shell lifecycle hooks installation (pre-compact, session-stop, statusline). */
4786
+ const useSymlinks$1 = platform() !== "win32";
4786
4787
  async function stepHooks(rl) {
4787
4788
  section("Step 7: Lifecycle Hooks");
4788
4789
  line$1();
4789
4790
  line$1(" PAI hooks fire on session stop and context compaction to save state,");
4790
4791
  line$1(" update notes, and display live statusline information.");
4792
+ if (useSymlinks$1) line$1(" Files are symlinked so they auto-update when PAI is rebuilt.");
4793
+ else line$1(" Files are copied (Windows — re-run setup after PAI updates).");
4791
4794
  line$1();
4792
4795
  if (!await promptYesNo(rl, "Install PAI lifecycle hooks (session stop, pre-compact, statusline)?", true)) {
4793
4796
  console.log(c.dim(" Skipping hook installation."));
@@ -4800,29 +4803,38 @@ async function stepHooks(rl) {
4800
4803
  const hooksTarget = join(claudeDir, "Hooks");
4801
4804
  if (!existsSync(hooksTarget)) mkdirSync(hooksTarget, { recursive: true });
4802
4805
  let anyInstalled = false;
4803
- function installFile(src, dest, label) {
4806
+ function installLink(src, dest, label) {
4804
4807
  if (!existsSync(src)) {
4805
4808
  console.log(c.warn(` Source not found: ${src}`));
4806
4809
  return;
4807
4810
  }
4808
- const srcContent = readFileSync(src, "utf-8");
4809
- if (existsSync(dest)) {
4810
- if (srcContent === readFileSync(dest, "utf-8")) {
4811
- console.log(c.dim(` Unchanged: ${label}`));
4812
- return;
4813
- }
4811
+ const absSrc = resolve(src);
4812
+ if (existsSync(dest) || lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.()) try {
4813
+ const stat = lstatSync(dest);
4814
+ if (stat.isSymbolicLink()) {
4815
+ if (resolve(readlinkSync(dest)) === absSrc) {
4816
+ console.log(c.dim(` Current: ${label}`));
4817
+ return;
4818
+ }
4819
+ unlinkSync(dest);
4820
+ } else if (stat.isFile()) unlinkSync(dest);
4821
+ else return;
4822
+ } catch {}
4823
+ if (useSymlinks$1) symlinkSync(absSrc, dest);
4824
+ else {
4825
+ copyFileSync(src, dest);
4826
+ chmodSync(dest, 493);
4814
4827
  }
4815
- copyFileSync(src, dest);
4816
- chmodSync(dest, 493);
4817
- console.log(c.ok(`Installed: ${label}`));
4828
+ const verb = useSymlinks$1 ? "Linked" : "Installed";
4829
+ console.log(c.ok(`${verb}: ${label}`));
4818
4830
  anyInstalled = true;
4819
4831
  }
4820
4832
  line$1();
4821
- installFile(join(hooksDir, "pre-compact.sh"), join(hooksTarget, "pai-pre-compact.sh"), "pai-pre-compact.sh");
4822
- installFile(join(hooksDir, "session-stop.sh"), join(hooksTarget, "pai-session-stop.sh"), "pai-session-stop.sh");
4823
- if (statuslineSrc) installFile(statuslineSrc, join(claudeDir, "statusline-command.sh"), "statusline-command.sh");
4833
+ installLink(join(hooksDir, "pre-compact.sh"), join(hooksTarget, "pai-pre-compact.sh"), "pai-pre-compact.sh");
4834
+ installLink(join(hooksDir, "session-stop.sh"), join(hooksTarget, "pai-session-stop.sh"), "pai-session-stop.sh");
4835
+ if (statuslineSrc) installLink(statuslineSrc, join(claudeDir, "statusline-command.sh"), "statusline-command.sh");
4824
4836
  else console.log(c.warn(" statusline-command.sh not found — skipping statusline."));
4825
- if (tabColorSrc) installFile(tabColorSrc, join(claudeDir, "tab-color-command.sh"), "tab-color-command.sh");
4837
+ if (tabColorSrc) installLink(tabColorSrc, join(claudeDir, "tab-color-command.sh"), "tab-color-command.sh");
4826
4838
  else console.log(c.warn(" tab-color-command.sh not found — skipping tab color."));
4827
4839
  return anyInstalled;
4828
4840
  }
@@ -4830,11 +4842,14 @@ async function stepHooks(rl) {
4830
4842
  //#endregion
4831
4843
  //#region src/cli/commands/setup/steps/09-ts-hooks.ts
4832
4844
  /** Step 7b: TypeScript (.mjs) hooks installation to ~/.claude/Hooks/. */
4845
+ const useSymlinks = platform() !== "win32";
4833
4846
  async function stepTsHooks(rl) {
4834
4847
  section("Step 7b: TypeScript Hooks Installation");
4835
4848
  line$1();
4836
- line$1(" PAI ships 14 compiled TypeScript hooks (.mjs) that fire on session events,");
4849
+ line$1(" PAI ships compiled TypeScript hooks (.mjs) that fire on session events,");
4837
4850
  line$1(" tool use, and context compaction to capture context and update notes.");
4851
+ if (useSymlinks) line$1(" Files are symlinked so they auto-update when PAI is rebuilt.");
4852
+ else line$1(" Files are copied (Windows — re-run setup after PAI updates).");
4838
4853
  line$1();
4839
4854
  if (!await promptYesNo(rl, "Install PAI TypeScript hooks to ~/.claude/Hooks/?", true)) {
4840
4855
  console.log(c.dim(" Skipping TypeScript hooks installation."));
@@ -4860,30 +4875,32 @@ async function stepTsHooks(rl) {
4860
4875
  return false;
4861
4876
  }
4862
4877
  line$1();
4863
- let copiedCount = 0;
4878
+ let linkedCount = 0;
4864
4879
  let skippedCount = 0;
4865
4880
  let cleanedCount = 0;
4866
4881
  for (const filename of allFiles) {
4867
- const src = join(distHooksDir, filename);
4882
+ const src = resolve(join(distHooksDir, filename));
4868
4883
  const dest = join(hooksTarget, filename);
4869
- const srcContent = readFileSync(src, "utf-8");
4870
- if (existsSync(dest)) {
4871
- if (srcContent === readFileSync(dest, "utf-8")) {
4872
- console.log(c.dim(` Unchanged: ${filename}`));
4884
+ let needsLink = true;
4885
+ if (existsSync(dest) || lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.()) try {
4886
+ const stat = lstatSync(dest);
4887
+ if (stat.isSymbolicLink()) if (resolve(readlinkSync(dest)) === src) {
4888
+ console.log(c.dim(` Current: ${filename}`));
4873
4889
  skippedCount++;
4874
- const staleTsPath = join(hooksTarget, filename.replace(/\.mjs$/, ".ts"));
4875
- if (existsSync(staleTsPath)) {
4876
- unlinkSync(staleTsPath);
4877
- console.log(c.ok(`Cleaned up stale: ${filename.replace(/\.mjs$/, ".ts")}`));
4878
- cleanedCount++;
4879
- }
4880
- continue;
4890
+ needsLink = false;
4891
+ } else unlinkSync(dest);
4892
+ else if (stat.isFile()) unlinkSync(dest);
4893
+ } catch {}
4894
+ if (needsLink) {
4895
+ if (useSymlinks) symlinkSync(src, dest);
4896
+ else {
4897
+ copyFileSync(src, dest);
4898
+ chmodSync(dest, 493);
4881
4899
  }
4900
+ const verb = useSymlinks ? "Linked" : "Installed";
4901
+ console.log(c.ok(`${verb}: ${filename}`));
4902
+ linkedCount++;
4882
4903
  }
4883
- copyFileSync(src, dest);
4884
- chmodSync(dest, 493);
4885
- console.log(c.ok(`Installed: ${filename}`));
4886
- copiedCount++;
4887
4904
  const staleTsPath = join(hooksTarget, filename.replace(/\.mjs$/, ".ts"));
4888
4905
  if (existsSync(staleTsPath)) {
4889
4906
  unlinkSync(staleTsPath);
@@ -4892,14 +4909,15 @@ async function stepTsHooks(rl) {
4892
4909
  }
4893
4910
  }
4894
4911
  line$1();
4895
- if (copiedCount > 0 || cleanedCount > 0) {
4912
+ if (linkedCount > 0 || cleanedCount > 0) {
4896
4913
  const parts = [];
4897
- if (copiedCount > 0) parts.push(`${copiedCount} hook(s) installed`);
4898
- if (skippedCount > 0) parts.push(`${skippedCount} unchanged`);
4914
+ const verb = useSymlinks ? "linked" : "installed";
4915
+ if (linkedCount > 0) parts.push(`${linkedCount} hook(s) ${verb}`);
4916
+ if (skippedCount > 0) parts.push(`${skippedCount} current`);
4899
4917
  if (cleanedCount > 0) parts.push(`${cleanedCount} stale .ts file(s) cleaned up`);
4900
4918
  console.log(c.ok(parts.join(", ") + "."));
4901
- } else console.log(c.dim(` All ${skippedCount} hook(s) already up-to-date.`));
4902
- return copiedCount > 0 || cleanedCount > 0;
4919
+ } else console.log(c.dim(` All ${skippedCount} hook(s) already current.`));
4920
+ return linkedCount > 0 || cleanedCount > 0;
4903
4921
  }
4904
4922
 
4905
4923
  //#endregion