hillclimb 0.1.6 → 0.1.7

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 (2) hide show
  1. package/dist/cli.js +70 -45
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -501,6 +501,7 @@ import os2 from "os";
501
501
  import path5 from "path";
502
502
  var HOOK_CMD = (sub) => `npx hillclimb@latest ${sub}`;
503
503
  var GIT_TRACES_CMD = (tool) => `npx hillclimb@latest git-traces --tool=${tool}`;
504
+ var CLAUDE_SESSIONEND_UPLOAD_TIMEOUT_SECONDS = 30;
504
505
  var TOOLS = [
505
506
  {
506
507
  tool: "claude",
@@ -508,7 +509,11 @@ var TOOLS = [
508
509
  settingsFile: ".claude/settings.local.json",
509
510
  format: "claude",
510
511
  events: [
511
- { eventName: "SessionEnd", command: HOOK_CMD("upload") },
512
+ {
513
+ eventName: "SessionEnd",
514
+ command: HOOK_CMD("upload"),
515
+ timeout: CLAUDE_SESSIONEND_UPLOAD_TIMEOUT_SECONDS
516
+ },
512
517
  { eventName: "SessionStart", command: GIT_TRACES_CMD("claude") },
513
518
  { eventName: "Stop", command: GIT_TRACES_CMD("claude") },
514
519
  { eventName: "SessionEnd", command: GIT_TRACES_CMD("claude") }
@@ -593,11 +598,7 @@ function isDir(p7) {
593
598
  }
594
599
  function copilotChatDetect() {
595
600
  const home = os2.homedir();
596
- const suffix = path5.join(
597
- "User",
598
- "globalStorage",
599
- "github.copilot-chat"
600
- );
601
+ const suffix = path5.join("User", "globalStorage", "github.copilot-chat");
601
602
  const candidates = [
602
603
  path5.join(home, "Library", "Application Support", "Code", suffix),
603
604
  path5.join(home, ".config", "Code", suffix),
@@ -605,6 +606,10 @@ function copilotChatDetect() {
605
606
  ].filter((p7) => p7 !== null);
606
607
  return candidates.some(isDir);
607
608
  }
609
+ function ensureHooks(settings) {
610
+ if (!settings.hooks) settings.hooks = {};
611
+ return settings.hooks;
612
+ }
608
613
  function settingsPath(repoRoot, def) {
609
614
  return path5.join(repoRoot, def.settingsFile);
610
615
  }
@@ -624,31 +629,45 @@ async function writeJson(file, obj) {
624
629
  await fs4.promises.writeFile(file, `${JSON.stringify(obj, null, 2)}
625
630
  `);
626
631
  }
627
- function claudeHookPresent(matchers, command) {
632
+ function claudeHookPresent(matchers, command, options = {}) {
628
633
  return matchers.some(
629
- (m) => (m.hooks ?? []).some(
630
- (h) => h.type === "command" && h.command === command
631
- )
634
+ (m) => (m.hooks ?? []).some((h) => {
635
+ if (h.type !== "command" || h.command !== command) return false;
636
+ if (options.timeout !== void 0 && h.timeout !== options.timeout) {
637
+ return false;
638
+ }
639
+ return true;
640
+ })
632
641
  );
633
642
  }
634
- function claudeInstall(settings, eventName, command) {
635
- if (!settings.hooks) settings.hooks = {};
636
- if (!settings.hooks[eventName]) settings.hooks[eventName] = [];
637
- const matchers = settings.hooks[eventName];
638
- if (claudeHookPresent(matchers, command)) return false;
643
+ function claudeInstall(settings, eventName, command, options = {}) {
644
+ const hooks = ensureHooks(settings);
645
+ if (!hooks[eventName]) hooks[eventName] = [];
646
+ const matchers = hooks[eventName];
647
+ for (const matcher of matchers) {
648
+ for (const hook of matcher.hooks ?? []) {
649
+ if (hook.type !== "command" || hook.command !== command) continue;
650
+ if (options.timeout === void 0 || hook.timeout === options.timeout) {
651
+ return false;
652
+ }
653
+ hook.timeout = options.timeout;
654
+ return true;
655
+ }
656
+ }
639
657
  matchers.push({
640
658
  matcher: "",
641
- hooks: [{ type: "command", command }]
659
+ hooks: [{ type: "command", command, ...options }]
642
660
  });
643
661
  return true;
644
662
  }
645
- function claudeCheck(settings, eventName, command) {
663
+ function claudeCheck(settings, eventName, command, options = {}) {
646
664
  const matchers = settings.hooks?.[eventName] ?? [];
647
- return claudeHookPresent(matchers, command);
665
+ return claudeHookPresent(matchers, command, options);
648
666
  }
649
667
  function claudeUninstall(settings, eventName, command) {
650
- const matchers = settings.hooks?.[eventName];
651
- if (!matchers || matchers.length === 0) return false;
668
+ const hooks = settings.hooks;
669
+ const matchers = hooks?.[eventName];
670
+ if (!hooks || !matchers || matchers.length === 0) return false;
652
671
  let removedAny = false;
653
672
  const next = [];
654
673
  for (const m of matchers) {
@@ -660,10 +679,10 @@ function claudeUninstall(settings, eventName, command) {
660
679
  }
661
680
  if (!removedAny) return false;
662
681
  if (next.length > 0) {
663
- settings.hooks[eventName] = next;
682
+ hooks[eventName] = next;
664
683
  } else {
665
- delete settings.hooks[eventName];
666
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
684
+ delete hooks[eventName];
685
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
667
686
  }
668
687
  return true;
669
688
  }
@@ -672,9 +691,9 @@ function cursorHookPresent(entries, command) {
672
691
  }
673
692
  function cursorInstall(settings, eventName, command) {
674
693
  if (!settings.version) settings.version = 1;
675
- if (!settings.hooks) settings.hooks = {};
676
- if (!settings.hooks[eventName]) settings.hooks[eventName] = [];
677
- const entries = settings.hooks[eventName];
694
+ const hooks = ensureHooks(settings);
695
+ if (!hooks[eventName]) hooks[eventName] = [];
696
+ const entries = hooks[eventName];
678
697
  if (cursorHookPresent(entries, command)) return false;
679
698
  entries.push({ command });
680
699
  return true;
@@ -684,15 +703,16 @@ function cursorCheck(settings, eventName, command) {
684
703
  return cursorHookPresent(entries, command);
685
704
  }
686
705
  function cursorUninstall(settings, eventName, command) {
687
- const entries = settings.hooks?.[eventName];
688
- if (!entries || entries.length === 0) return false;
706
+ const hooks = settings.hooks;
707
+ const entries = hooks?.[eventName];
708
+ if (!hooks || !entries || entries.length === 0) return false;
689
709
  const next = entries.filter((e) => e.command !== command);
690
710
  if (next.length === entries.length) return false;
691
711
  if (next.length > 0) {
692
- settings.hooks[eventName] = next;
712
+ hooks[eventName] = next;
693
713
  } else {
694
- delete settings.hooks[eventName];
695
- if (Object.keys(settings.hooks).length === 0) {
714
+ delete hooks[eventName];
715
+ if (Object.keys(hooks).length === 0) {
696
716
  delete settings.hooks;
697
717
  delete settings.version;
698
718
  }
@@ -703,9 +723,9 @@ function copilotHookPresent(entries, command) {
703
723
  return entries.some((e) => e.type === "command" && e.command === command);
704
724
  }
705
725
  function copilotInstall(settings, eventName, command) {
706
- if (!settings.hooks) settings.hooks = {};
707
- if (!settings.hooks[eventName]) settings.hooks[eventName] = [];
708
- const entries = settings.hooks[eventName];
726
+ const hooks = ensureHooks(settings);
727
+ if (!hooks[eventName]) hooks[eventName] = [];
728
+ const entries = hooks[eventName];
709
729
  if (copilotHookPresent(entries, command)) return false;
710
730
  entries.push({ type: "command", command });
711
731
  return true;
@@ -715,17 +735,18 @@ function copilotCheck(settings, eventName, command) {
715
735
  return copilotHookPresent(entries, command);
716
736
  }
717
737
  function copilotUninstall(settings, eventName, command) {
718
- const entries = settings.hooks?.[eventName];
719
- if (!entries || entries.length === 0) return false;
738
+ const hooks = settings.hooks;
739
+ const entries = hooks?.[eventName];
740
+ if (!hooks || !entries || entries.length === 0) return false;
720
741
  const next = entries.filter(
721
742
  (e) => !(e.type === "command" && e.command === command)
722
743
  );
723
744
  if (next.length === entries.length) return false;
724
745
  if (next.length > 0) {
725
- settings.hooks[eventName] = next;
746
+ hooks[eventName] = next;
726
747
  } else {
727
- delete settings.hooks[eventName];
728
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
748
+ delete hooks[eventName];
749
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
729
750
  }
730
751
  return true;
731
752
  }
@@ -897,24 +918,24 @@ async function opencodeCheck(file) {
897
918
  return false;
898
919
  }
899
920
  }
900
- function install(settings, format, eventName, command) {
921
+ function install(settings, format, eventName, command, options = {}) {
901
922
  switch (format) {
902
923
  case "cursor":
903
924
  return cursorInstall(settings, eventName, command);
904
925
  case "copilot":
905
926
  return copilotInstall(settings, eventName, command);
906
927
  default:
907
- return claudeInstall(settings, eventName, command);
928
+ return claudeInstall(settings, eventName, command, options);
908
929
  }
909
930
  }
910
- function check(settings, format, eventName, command) {
931
+ function check(settings, format, eventName, command, options = {}) {
911
932
  switch (format) {
912
933
  case "cursor":
913
934
  return cursorCheck(settings, eventName, command);
914
935
  case "copilot":
915
936
  return copilotCheck(settings, eventName, command);
916
937
  default:
917
- return claudeCheck(settings, eventName, command);
938
+ return claudeCheck(settings, eventName, command, options);
918
939
  }
919
940
  }
920
941
  function uninstall(settings, format, eventName, command) {
@@ -961,7 +982,9 @@ async function installHooksForTool(repoRoot, def) {
961
982
  mutated = true;
962
983
  }
963
984
  }
964
- const added = install(settings, def.format, evt.eventName, evt.command);
985
+ const added = install(settings, def.format, evt.eventName, evt.command, {
986
+ timeout: evt.timeout
987
+ });
965
988
  if (added) {
966
989
  installed++;
967
990
  mutated = true;
@@ -985,7 +1008,9 @@ async function checkHooksForTool(repoRoot, def) {
985
1008
  const settings = await readJson(file);
986
1009
  const missing = [];
987
1010
  for (const evt of def.events) {
988
- if (!check(settings, def.format, evt.eventName, evt.command)) {
1011
+ if (!check(settings, def.format, evt.eventName, evt.command, {
1012
+ timeout: evt.timeout
1013
+ })) {
989
1014
  missing.push(`${evt.eventName}:${evt.command}`);
990
1015
  }
991
1016
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",