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