whydid 1.0.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 (84) hide show
  1. package/README.md +232 -0
  2. package/dashboard/dist/android-chrome-192x192.png +0 -0
  3. package/dashboard/dist/android-chrome-512x512.png +0 -0
  4. package/dashboard/dist/apple-touch-icon.png +0 -0
  5. package/dashboard/dist/assets/index-DImKqodm.js +69 -0
  6. package/dashboard/dist/assets/index-mHXgqqkj.css +1 -0
  7. package/dashboard/dist/favicon-16x16.png +0 -0
  8. package/dashboard/dist/favicon-32x32.png +0 -0
  9. package/dashboard/dist/favicon.ico +0 -0
  10. package/dashboard/dist/index.html +24 -0
  11. package/dashboard/dist/logo-mark.svg +8 -0
  12. package/dashboard/dist/site.webmanifest +11 -0
  13. package/dist/adapters/claude-code.js +168 -0
  14. package/dist/adapters/hook-events.js +86 -0
  15. package/dist/adapters/sync-claude-code.js +85 -0
  16. package/dist/cli/ai-budget-warning.js +19 -0
  17. package/dist/cli/commands/agent-event.js +80 -0
  18. package/dist/cli/commands/checkpoint.js +36 -0
  19. package/dist/cli/commands/context.js +27 -0
  20. package/dist/cli/commands/daemon.js +139 -0
  21. package/dist/cli/commands/export.js +36 -0
  22. package/dist/cli/commands/init.js +63 -0
  23. package/dist/cli/commands/key.js +65 -0
  24. package/dist/cli/commands/license.js +57 -0
  25. package/dist/cli/commands/milestone.js +64 -0
  26. package/dist/cli/commands/prompt.js +75 -0
  27. package/dist/cli/commands/recap.js +48 -0
  28. package/dist/cli/commands/restore-fork.js +101 -0
  29. package/dist/cli/commands/status.js +26 -0
  30. package/dist/cli/commands/test.js +33 -0
  31. package/dist/cli/commands/timeline.js +23 -0
  32. package/dist/cli/commands/why.js +71 -0
  33. package/dist/cli/context.js +48 -0
  34. package/dist/cli/index.js +77 -0
  35. package/dist/cli/license-gate.js +26 -0
  36. package/dist/cli/preflight.js +28 -0
  37. package/dist/core/agent-context.js +46 -0
  38. package/dist/core/agent-hooks.js +68 -0
  39. package/dist/core/ai-budget.js +27 -0
  40. package/dist/core/chat-retrieval.js +36 -0
  41. package/dist/core/context-tree.js +135 -0
  42. package/dist/core/diff.js +30 -0
  43. package/dist/core/engine.js +321 -0
  44. package/dist/core/export.js +98 -0
  45. package/dist/core/file-history.js +31 -0
  46. package/dist/core/manifest.js +102 -0
  47. package/dist/core/recap.js +49 -0
  48. package/dist/core/test-runner.js +23 -0
  49. package/dist/daemon/listen.js +26 -0
  50. package/dist/daemon/pidfile.js +36 -0
  51. package/dist/daemon/server.js +554 -0
  52. package/dist/db/database.js +631 -0
  53. package/dist/db/schema.js +186 -0
  54. package/dist/git/git.js +22 -0
  55. package/dist/licensing/config.js +28 -0
  56. package/dist/licensing/license-client.js +45 -0
  57. package/dist/licensing/license-manager.js +89 -0
  58. package/dist/licensing/license-payload.js +12 -0
  59. package/dist/licensing/license-store.js +96 -0
  60. package/dist/licensing/machine-id.js +78 -0
  61. package/dist/licensing/secret-store.js +102 -0
  62. package/dist/licensing/verify-license.js +20 -0
  63. package/dist/llm/agent-prompt.js +36 -0
  64. package/dist/llm/annotate-checkpoint.js +67 -0
  65. package/dist/llm/annotate.js +11 -0
  66. package/dist/llm/anthropic-provider.js +97 -0
  67. package/dist/llm/chat.js +35 -0
  68. package/dist/llm/community-pricing.js +75 -0
  69. package/dist/llm/factory.js +17 -0
  70. package/dist/llm/http-error.js +25 -0
  71. package/dist/llm/noop-provider.js +19 -0
  72. package/dist/llm/openai-provider.js +85 -0
  73. package/dist/llm/pricing.js +55 -0
  74. package/dist/llm/prompt.js +103 -0
  75. package/dist/llm/provider.js +2 -0
  76. package/dist/llm/schema.js +49 -0
  77. package/dist/llm/social-post.js +49 -0
  78. package/dist/objects/store.js +31 -0
  79. package/dist/shared/config.js +45 -0
  80. package/dist/shared/redaction.js +18 -0
  81. package/dist/shared/types.js +2 -0
  82. package/dist/watcher/ignore-rules.js +92 -0
  83. package/dist/watcher/watcher.js +249 -0
  84. package/package.json +55 -0
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:IBM Plex Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width: 640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width: 768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width: 1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width: 1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width: 1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-\[5px_5px_0_5px\]{top:5px;right:5px;bottom:0;left:5px}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.left-3{left:.75rem}.right-2{right:.5rem}.top-2{top:.5rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.my-1\.5{margin-top:.375rem;margin-bottom:.375rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-2\.5{margin-top:.625rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-11{height:2.75rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-4{height:1rem}.h-8{height:2rem}.h-\[30px\]{height:30px}.h-\[38px\]{height:38px}.h-\[70vh\]{height:70vh}.h-auto{height:auto}.h-full{height:100%}.h-screen{height:100vh}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-96{max-height:24rem}.max-h-\[85vh\]{max-height:85vh}.min-h-0{min-height:0px}.w-1\.5{width:.375rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-24{width:6rem}.w-32{width:8rem}.w-4{width:1rem}.w-40{width:10rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-96{width:24rem}.w-\[2px\]{width:2px}.w-\[70px\]{width:70px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[120px\]{min-width:120px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.origin-bottom{transform-origin:bottom}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-crosshair{cursor:crosshair}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-3\.5{gap:.875rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-\[3px\]{gap:3px}.gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.gap-y-2{row-gap:.5rem}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-2\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.625rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-iron-line>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(58 51 42 / var(--tw-divide-opacity, 1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[5px\]{border-radius:5px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-t-full{border-top-left-radius:9999px;border-top-right-radius:9999px}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber{--tw-border-opacity: 1;border-color:rgb(255 154 61 / var(--tw-border-opacity, 1))}.border-amber-dim{--tw-border-opacity: 1;border-color:rgb(201 116 37 / var(--tw-border-opacity, 1))}.border-amber\/30{border-color:#ff9a3d4d}.border-danger{--tw-border-opacity: 1;border-color:rgb(255 107 87 / var(--tw-border-opacity, 1))}.border-danger\/30{border-color:#ff6b574d}.border-iron-line{--tw-border-opacity: 1;border-color:rgb(58 51 42 / var(--tw-border-opacity, 1))}.border-iron-line\/40{border-color:#3a332a66}.border-iron-lit{--tw-border-opacity: 1;border-color:rgb(85 73 58 / var(--tw-border-opacity, 1))}.border-iron\/30{border-color:#1b18154d}.border-paper-faint{--tw-border-opacity: 1;border-color:rgb(118 106 88 / var(--tw-border-opacity, 1))}.border-scope\/30{border-color:#63e6a34d}.border-transparent{border-color:transparent}.bg-\[\#100d0a\]{--tw-bg-opacity: 1;background-color:rgb(16 13 10 / var(--tw-bg-opacity, 1))}.bg-\[rgba\(0\,0\,0\,0\.2\)\]{background-color:#0003}.bg-\[rgba\(0\,0\,0\,0\.28\)\]{background-color:#00000047}.bg-amber{--tw-bg-opacity: 1;background-color:rgb(255 154 61 / var(--tw-bg-opacity, 1))}.bg-amber\/10{background-color:#ff9a3d1a}.bg-amber\/15{background-color:#ff9a3d26}.bg-black\/50{background-color:#00000080}.bg-black\/70{background-color:#000000b3}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(255 107 87 / var(--tw-bg-opacity, 1))}.bg-danger\/15{background-color:#ff6b5726}.bg-iron{--tw-bg-opacity: 1;background-color:rgb(27 24 21 / var(--tw-bg-opacity, 1))}.bg-iron-raised{--tw-bg-opacity: 1;background-color:rgb(36 31 26 / var(--tw-bg-opacity, 1))}.bg-paper{--tw-bg-opacity: 1;background-color:rgb(242 235 221 / var(--tw-bg-opacity, 1))}.bg-scope{--tw-bg-opacity: 1;background-color:rgb(99 230 163 / var(--tw-bg-opacity, 1))}.bg-scope\/15{background-color:#63e6a326}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-3{padding-bottom:.75rem}.pb-5{padding-bottom:1.25rem}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-body{font-family:IBM Plex Sans,-apple-system,sans-serif}.font-display{font-family:Major Mono Display,IBM Plex Mono,monospace}.font-mono{font-family:IBM Plex Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-amber{--tw-text-opacity: 1;color:rgb(255 154 61 / var(--tw-text-opacity, 1))}.text-amber-dim{--tw-text-opacity: 1;color:rgb(201 116 37 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(255 107 87 / var(--tw-text-opacity, 1))}.text-iron{--tw-text-opacity: 1;color:rgb(27 24 21 / var(--tw-text-opacity, 1))}.text-iron-line{--tw-text-opacity: 1;color:rgb(58 51 42 / var(--tw-text-opacity, 1))}.text-paper{--tw-text-opacity: 1;color:rgb(242 235 221 / var(--tw-text-opacity, 1))}.text-paper-dim{--tw-text-opacity: 1;color:rgb(168 155 134 / var(--tw-text-opacity, 1))}.text-paper-faint{--tw-text-opacity: 1;color:rgb(118 106 88 / var(--tw-text-opacity, 1))}.text-scope{--tw-text-opacity: 1;color:rgb(99 230 163 / var(--tw-text-opacity, 1))}.accent-amber{accent-color:#ff9a3d}.opacity-0{opacity:0}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}@keyframes whydid-pulse{0%,to{opacity:1;box-shadow:0 0 6px var(--tw-pulse-color, #63e6a3),0 0 13px #63e6a366}50%{opacity:.45;box-shadow:0 0 2px var(--tw-pulse-color, #63e6a3)}}.whydid-rec-dot{animation:whydid-pulse 2.2s ease-in-out infinite}@keyframes whydid-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.whydid-stagger-in{animation:whydid-fade-in .4s ease-out backwards}.placeholder\:text-paper-faint::-moz-placeholder{--tw-text-opacity: 1;color:rgb(118 106 88 / var(--tw-text-opacity, 1))}.placeholder\:text-paper-faint::placeholder{--tw-text-opacity: 1;color:rgb(118 106 88 / var(--tw-text-opacity, 1))}.first\:mt-0:first-child{margin-top:0}.hover\:border-amber-dim:hover{--tw-border-opacity: 1;border-color:rgb(201 116 37 / var(--tw-border-opacity, 1))}.hover\:border-danger:hover{--tw-border-opacity: 1;border-color:rgb(255 107 87 / var(--tw-border-opacity, 1))}.hover\:border-iron-lit:hover{--tw-border-opacity: 1;border-color:rgb(85 73 58 / var(--tw-border-opacity, 1))}.hover\:bg-\[\#ffb066\]:hover{--tw-bg-opacity: 1;background-color:rgb(255 176 102 / var(--tw-bg-opacity, 1))}.hover\:bg-amber:hover{--tw-bg-opacity: 1;background-color:rgb(255 154 61 / var(--tw-bg-opacity, 1))}.hover\:bg-amber\/10:hover{background-color:#ff9a3d1a}.hover\:bg-iron:hover{--tw-bg-opacity: 1;background-color:rgb(27 24 21 / var(--tw-bg-opacity, 1))}.hover\:bg-iron-raised:hover{--tw-bg-opacity: 1;background-color:rgb(36 31 26 / var(--tw-bg-opacity, 1))}.hover\:bg-iron\/10:hover{background-color:#1b18151a}.hover\:text-amber:hover{--tw-text-opacity: 1;color:rgb(255 154 61 / var(--tw-text-opacity, 1))}.hover\:text-danger:hover{--tw-text-opacity: 1;color:rgb(255 107 87 / var(--tw-text-opacity, 1))}.hover\:text-iron:hover{--tw-text-opacity: 1;color:rgb(27 24 21 / var(--tw-text-opacity, 1))}.hover\:text-paper:hover{--tw-text-opacity: 1;color:rgb(242 235 221 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-amber-dim:focus{--tw-border-opacity: 1;border-color:rgb(201 116 37 / var(--tw-border-opacity, 1))}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:bg-iron-raised{--tw-bg-opacity: 1;background-color:rgb(36 31 26 / var(--tw-bg-opacity, 1))}.group:hover .group-hover\:text-amber{--tw-text-opacity: 1;color:rgb(255 154 61 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width: 640px){.sm\:max-h-none{max-height:none}.sm\:w-72{width:18rem}.sm\:flex-shrink-0{flex-shrink:0}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:border-b-0{border-bottom-width:0px}.sm\:border-r{border-right-width:1px}.sm\:p-8{padding:2rem}}@media (min-width: 768px){.md\:static{position:static}.md\:hidden{display:none}.md\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}}
Binary file
Binary file
Binary file
@@ -0,0 +1,24 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>whydid</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any" />
8
+ <link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png" />
9
+ <link rel="icon" href="/logo-mark.svg" type="image/svg+xml" />
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
11
+ <link rel="manifest" href="/site.webmanifest" />
12
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
14
+ <link
15
+ href="https://fonts.googleapis.com/css2?family=Major+Mono+Display&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap"
16
+ rel="stylesheet"
17
+ />
18
+ <script type="module" crossorigin src="/assets/index-DImKqodm.js"></script>
19
+ <link rel="stylesheet" crossorigin href="/assets/index-mHXgqqkj.css">
20
+ </head>
21
+ <body class="bg-iron text-paper font-body">
22
+ <div id="root"></div>
23
+ </body>
24
+ </html>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="7" fill="#1b1815"/>
3
+ <circle cx="16" cy="16" r="10.2" fill="none" stroke="#ff9a3d" stroke-width="1.7" opacity="0.5"/>
4
+ <circle cx="16" cy="5.6" r="1.6" fill="#ff9a3d"/>
5
+ <circle cx="25" cy="20.8" r="1.6" fill="#ff9a3d"/>
6
+ <circle cx="7" cy="20.8" r="1.6" fill="#ff9a3d"/>
7
+ <circle cx="16" cy="16" r="4.1" fill="#63e6a3"/>
8
+ </svg>
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "whydid dashboard",
3
+ "short_name": "whydid",
4
+ "icons": [
5
+ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
6
+ { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
7
+ ],
8
+ "theme_color": "#1b1815",
9
+ "background_color": "#1b1815",
10
+ "display": "standalone"
11
+ }
@@ -0,0 +1,168 @@
1
+ import { existsSync, readdirSync, statSync, openSync, closeSync, readSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ export function claudeCodeProjectSlug(projectRoot) {
5
+ return projectRoot.replace(/[^a-zA-Z0-9]+/g, "-");
6
+ }
7
+ export function findClaudeCodeSessionDir(projectRoot, claudeProjectsDir = join(homedir(), ".claude", "projects")) {
8
+ const dir = join(claudeProjectsDir, claudeCodeProjectSlug(projectRoot));
9
+ return existsSync(dir) ? dir : null;
10
+ }
11
+ export function findLatestSessionFile(sessionDir) {
12
+ let latestFile = null;
13
+ let latestMtime = -1;
14
+ let entries;
15
+ try {
16
+ entries = readdirSync(sessionDir);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ for (const entry of entries) {
22
+ if (!entry.endsWith(".jsonl"))
23
+ continue;
24
+ const full = join(sessionDir, entry);
25
+ try {
26
+ const mtime = statSync(full).mtimeMs;
27
+ if (mtime > latestMtime) {
28
+ latestMtime = mtime;
29
+ latestFile = full;
30
+ }
31
+ }
32
+ catch {
33
+ // File disappeared between readdir and stat — skip it.
34
+ }
35
+ }
36
+ return latestFile;
37
+ }
38
+ /**
39
+ * Reads only the bytes appended since `fromOffset`, stopping at the last
40
+ * complete line so a write-in-progress never yields a truncated JSON line.
41
+ * Byte-accurate (not string-length-based) so it's safe with multi-byte UTF-8.
42
+ */
43
+ export function tailNewLines(filePath, fromOffset) {
44
+ let size;
45
+ try {
46
+ size = statSync(filePath).size;
47
+ }
48
+ catch {
49
+ return { rawLines: [], newOffset: fromOffset };
50
+ }
51
+ if (size <= fromOffset)
52
+ return { rawLines: [], newOffset: fromOffset };
53
+ const length = size - fromOffset;
54
+ const buffer = Buffer.alloc(length);
55
+ const fd = openSync(filePath, "r");
56
+ try {
57
+ readSync(fd, buffer, 0, length, fromOffset);
58
+ }
59
+ finally {
60
+ closeSync(fd);
61
+ }
62
+ const lastNewline = buffer.lastIndexOf(0x0a);
63
+ if (lastNewline === -1)
64
+ return { rawLines: [], newOffset: fromOffset };
65
+ const usable = buffer.subarray(0, lastNewline + 1);
66
+ const rawLines = usable.toString("utf-8").split("\n").filter((l) => l.trim() !== "");
67
+ return { rawLines, newOffset: fromOffset + usable.length };
68
+ }
69
+ function isRealHumanPrompt(entry) {
70
+ return (entry?.type === "user" &&
71
+ typeof entry.message?.content === "string" &&
72
+ entry.origin?.kind === "human" &&
73
+ typeof entry.uuid === "string" &&
74
+ typeof entry.timestamp === "string");
75
+ }
76
+ // Claude Code stamps `isCompactSummary: true` on the synthetic user-role
77
+ // message it injects when auto-compaction summarizes and continues a
78
+ // session — confirmed against a real transcript (it carries no `origin`
79
+ // field at all, so it would otherwise silently fail isRealHumanPrompt above
80
+ // and vanish from whydid's turn history without a trace).
81
+ function isCompactSummaryEntry(entry) {
82
+ return (entry?.type === "user" &&
83
+ entry.isCompactSummary === true &&
84
+ typeof entry.message?.content === "string" &&
85
+ typeof entry.uuid === "string" &&
86
+ typeof entry.timestamp === "string");
87
+ }
88
+ const FILE_TOUCHING_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
89
+ export class ClaudeCodeTailer {
90
+ filePath;
91
+ offset;
92
+ sequence = 0;
93
+ turns = [];
94
+ constructor(filePath, initialOffset = 0) {
95
+ this.filePath = filePath;
96
+ this.offset = initialOffset;
97
+ }
98
+ getOffset() {
99
+ return this.offset;
100
+ }
101
+ /** Parses newly-appended lines and returns the full known turn list so far. */
102
+ poll() {
103
+ const { rawLines, newOffset } = tailNewLines(this.filePath, this.offset);
104
+ this.offset = newOffset;
105
+ for (const line of rawLines) {
106
+ let entry;
107
+ try {
108
+ entry = JSON.parse(line);
109
+ }
110
+ catch {
111
+ continue;
112
+ }
113
+ if (isRealHumanPrompt(entry)) {
114
+ this.sequence += 1;
115
+ this.turns.push({
116
+ uuid: entry.uuid,
117
+ sequence: this.sequence,
118
+ prompt: entry.message.content,
119
+ timestamp: entry.timestamp,
120
+ title: null,
121
+ filesTouched: [],
122
+ complete: false,
123
+ kind: "prompt",
124
+ });
125
+ continue;
126
+ }
127
+ if (isCompactSummaryEntry(entry)) {
128
+ this.sequence += 1;
129
+ this.turns.push({
130
+ uuid: entry.uuid,
131
+ sequence: this.sequence,
132
+ prompt: entry.message.content,
133
+ timestamp: entry.timestamp,
134
+ title: "Context compacted — session resumed",
135
+ filesTouched: [],
136
+ complete: false,
137
+ kind: "compact_resume",
138
+ });
139
+ continue;
140
+ }
141
+ if (entry?.type === "ai-title" && typeof entry.aiTitle === "string") {
142
+ const target = this.turns.find((t) => t.title === null);
143
+ if (target)
144
+ target.title = entry.aiTitle;
145
+ continue;
146
+ }
147
+ if (entry?.type === "assistant" && Array.isArray(entry.message?.content)) {
148
+ const current = this.turns[this.turns.length - 1];
149
+ if (!current)
150
+ continue;
151
+ const touched = new Set(current.filesTouched);
152
+ for (const block of entry.message.content) {
153
+ if (block?.type === "tool_use" && FILE_TOUCHING_TOOLS.has(block.name) && typeof block.input?.file_path === "string") {
154
+ touched.add(block.input.file_path);
155
+ }
156
+ }
157
+ current.filesTouched = [...touched];
158
+ }
159
+ }
160
+ for (let i = 0; i < this.turns.length - 1; i++)
161
+ this.turns[i].complete = true;
162
+ return this.turns;
163
+ }
164
+ getLatestTurn() {
165
+ return this.turns[this.turns.length - 1];
166
+ }
167
+ }
168
+ //# sourceMappingURL=claude-code.js.map
@@ -0,0 +1,86 @@
1
+ import { createHash } from "node:crypto";
2
+ function text(value) {
3
+ return typeof value === "string" && value.length > 0 ? value : null;
4
+ }
5
+ function eventSessionId(agent, event) {
6
+ if (agent === "codex")
7
+ return text(event.session_id);
8
+ return text(event.conversation_id) ?? text(event.session_id);
9
+ }
10
+ function eventTurnId(agent, event) {
11
+ const externalId = agent === "codex" ? text(event.turn_id) : text(event.generation_id);
12
+ const sessionId = eventSessionId(agent, event);
13
+ return externalId && sessionId ? `${agent}:${sessionId}:${externalId}` : null;
14
+ }
15
+ export function recordAgentHookEvent(ctx, projectId, agent, event) {
16
+ const agentSessionId = eventSessionId(agent, event);
17
+ if (!agentSessionId)
18
+ return;
19
+ const timestamp = text(event.timestamp) ?? new Date().toISOString();
20
+ const eventName = text(event.hook_event_name) ?? "";
21
+ if (agent === "cursor" && eventName === "sessionStart")
22
+ return;
23
+ let session = ctx.db.getSessionByAgentSessionId(projectId, agent, agentSessionId);
24
+ if (!session) {
25
+ ctx.db.insertSession({
26
+ id: createHash("sha256").update(`${projectId}\0${agent}\0${agentSessionId}`).digest("hex").slice(0, 32),
27
+ projectId,
28
+ agent,
29
+ agentSessionId,
30
+ startedAt: timestamp,
31
+ metadataJson: JSON.stringify({ model: text(event.model) ?? text(event.model_id) }),
32
+ });
33
+ session = ctx.db.getSessionByAgentSessionId(projectId, agent, agentSessionId);
34
+ }
35
+ if (!session)
36
+ return;
37
+ if (eventName === "SessionEnd" || eventName === "sessionEnd") {
38
+ for (const activeTurn of ctx.db.listTurns(session.id).filter((turn) => turn.status === "in_progress")) {
39
+ ctx.db.updateTurnStatus(activeTurn.id, "completed", timestamp);
40
+ }
41
+ ctx.db.updateSessionEndedAt(session.id, timestamp);
42
+ return;
43
+ }
44
+ const promptEvent = agent === "codex" ? eventName === "UserPromptSubmit" : eventName === "beforeSubmitPrompt";
45
+ const completionEvent = agent === "codex"
46
+ ? eventName === "Stop" || eventName === "SessionEnd"
47
+ : eventName === "afterAgentResponse" || eventName === "stop" || eventName === "sessionEnd";
48
+ const turnId = eventTurnId(agent, event);
49
+ if (promptEvent && turnId) {
50
+ for (const projectActiveTurn of ctx.db.listActiveTurns(projectId)) {
51
+ if (projectActiveTurn.turnId !== turnId)
52
+ ctx.db.updateTurnStatus(projectActiveTurn.turnId, "completed", timestamp);
53
+ }
54
+ const previous = ctx.db.getLatestTurn(session.id);
55
+ if (previous?.id === turnId) {
56
+ ctx.db.insertTurn({
57
+ id: turnId,
58
+ sessionId: session.id,
59
+ sequence: previous.sequence,
60
+ prompt: text(event.prompt),
61
+ startedAt: previous.startedAt,
62
+ status: "in_progress",
63
+ });
64
+ return;
65
+ }
66
+ if (previous?.status === "in_progress")
67
+ ctx.db.updateTurnStatus(previous.id, "completed", timestamp);
68
+ ctx.db.insertTurn({
69
+ id: turnId,
70
+ sessionId: session.id,
71
+ sequence: (previous?.sequence ?? 0) + 1,
72
+ prompt: text(event.prompt),
73
+ startedAt: timestamp,
74
+ status: "in_progress",
75
+ metadataJson: JSON.stringify({ model: text(event.model) ?? text(event.model_id) }),
76
+ });
77
+ return;
78
+ }
79
+ if (completionEvent) {
80
+ const latest = ctx.db.getLatestTurn(session.id);
81
+ if (latest?.status === "in_progress" && (!turnId || latest.id === turnId)) {
82
+ ctx.db.updateTurnStatus(latest.id, "completed", timestamp);
83
+ }
84
+ }
85
+ }
86
+ //# sourceMappingURL=hook-events.js.map
@@ -0,0 +1,85 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { basename } from "node:path";
3
+ import { findClaudeCodeSessionDir, findLatestSessionFile, ClaudeCodeTailer } from "./claude-code.js";
4
+ export const CLAUDE_CODE_AGENT_ID = "claude-code";
5
+ // One tailer per session file, reused for the life of the daemon process.
6
+ const tailers = new Map();
7
+ function turnMetadataJson(turn) {
8
+ return JSON.stringify({ title: turn.title, filesTouched: turn.filesTouched });
9
+ }
10
+ /**
11
+ * Polls the project's most recent Claude Code session transcript (if any),
12
+ * persists any new turns to the sessions/turns tables, and reports whether a
13
+ * new turn has started since the last poll. Returns null when no Claude Code
14
+ * session is detectable for this project — callers must treat that as the
15
+ * normal "AI session context unavailable" case, not an error.
16
+ */
17
+ export function syncClaudeCodeTurns(ctx, projectId, claudeProjectsDir) {
18
+ const sessionDir = claudeProjectsDir
19
+ ? findClaudeCodeSessionDir(ctx.projectRoot, claudeProjectsDir)
20
+ : findClaudeCodeSessionDir(ctx.projectRoot);
21
+ if (!sessionDir)
22
+ return null;
23
+ const sessionFile = findLatestSessionFile(sessionDir);
24
+ if (!sessionFile)
25
+ return null;
26
+ let tailer = tailers.get(sessionFile);
27
+ if (!tailer) {
28
+ tailer = new ClaudeCodeTailer(sessionFile);
29
+ tailers.set(sessionFile, tailer);
30
+ }
31
+ const sequenceBefore = tailer.getLatestTurn()?.sequence ?? 0;
32
+ const turns = tailer.poll();
33
+ if (turns.length === 0)
34
+ return null;
35
+ const agentSessionId = basename(sessionFile).replace(/\.jsonl$/, "");
36
+ let session = ctx.db.getSessionByAgentSessionId(projectId, CLAUDE_CODE_AGENT_ID, agentSessionId);
37
+ if (!session) {
38
+ ctx.db.insertSession({
39
+ id: randomUUID(),
40
+ projectId,
41
+ agent: CLAUDE_CODE_AGENT_ID,
42
+ agentSessionId,
43
+ startedAt: turns[0].timestamp,
44
+ });
45
+ session = ctx.db.getSessionByAgentSessionId(projectId, CLAUDE_CODE_AGENT_ID, agentSessionId);
46
+ }
47
+ let latestTurnIsActive = false;
48
+ for (const turn of turns) {
49
+ const existingTurn = ctx.db.getTurn(turn.uuid);
50
+ if (turn.uuid === turns[turns.length - 1].uuid) {
51
+ latestTurnIsActive = !turn.complete && existingTurn?.status !== "completed";
52
+ }
53
+ ctx.db.insertTurn({
54
+ id: turn.uuid,
55
+ sessionId: session.id,
56
+ sequence: turn.sequence,
57
+ prompt: turn.prompt,
58
+ startedAt: turn.timestamp,
59
+ status: turn.complete || existingTurn?.status === "completed" ? "completed" : "in_progress",
60
+ metadataJson: turnMetadataJson(turn),
61
+ kind: turn.kind,
62
+ });
63
+ }
64
+ const latest = turns[turns.length - 1];
65
+ if (latestTurnIsActive) {
66
+ for (const activeTurn of ctx.db.listActiveTurns(projectId)) {
67
+ if (activeTurn.turnId !== latest.uuid)
68
+ ctx.db.updateTurnStatus(activeTurn.turnId, "completed");
69
+ }
70
+ }
71
+ const didAdvanceTurn = sequenceBefore > 0 && latest.sequence > sequenceBefore;
72
+ const previous = didAdvanceTurn ? turns[turns.length - 2] : undefined;
73
+ return {
74
+ sessionId: session.id,
75
+ currentTurnId: latest.uuid,
76
+ currentTurn: latest,
77
+ completedPreviousTurnId: previous?.uuid ?? null,
78
+ didAdvanceTurn,
79
+ };
80
+ }
81
+ /** For tests: drop cached tailers so a fresh poll re-reads from the given offset. */
82
+ export function resetClaudeCodeSyncCache() {
83
+ tailers.clear();
84
+ }
85
+ //# sourceMappingURL=sync-claude-code.js.map
@@ -0,0 +1,19 @@
1
+ import { computeCurrentAiBudget } from "../core/ai-budget.js";
2
+ /**
3
+ * The dashboard's AI Usage page shows budget status passively — it only
4
+ * helps if someone remembers to open it. This is the reach-you-anyway path:
5
+ * a terminal line on `whydid checkpoint`/`whydid status`, which happens
6
+ * without anyone deciding to go check.
7
+ */
8
+ export function printAiBudgetWarning(ctx, projectId, config) {
9
+ if (config.aiBudget.monthlyLimitUsd == null)
10
+ return;
11
+ const budget = computeCurrentAiBudget(ctx, projectId, config);
12
+ if (budget.status === "warning" && budget.spentUsd != null) {
13
+ console.log(` ⚠ AI spend this month: $${budget.spentUsd.toFixed(2)} of $${budget.limitUsd} (${Math.round(budget.percentUsed ?? 0)}%) — approaching your monthly budget.`);
14
+ }
15
+ else if (budget.status === "exceeded" && budget.spentUsd != null) {
16
+ console.log(` ⚠ AI spend this month: $${budget.spentUsd.toFixed(2)} — over your $${budget.limitUsd} monthly budget.`);
17
+ }
18
+ }
19
+ //# sourceMappingURL=ai-budget-warning.js.map
@@ -0,0 +1,80 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve, dirname, join } from "node:path";
3
+ import { Command } from "commander";
4
+ import { buildEngineContext } from "../context.js";
5
+ import { recordAgentHookEvent } from "../../adapters/hook-events.js";
6
+ function projectRootFor(startPath) {
7
+ let current = resolve(startPath);
8
+ while (true) {
9
+ if (existsSync(join(current, ".whydid", "db.sqlite")))
10
+ return current;
11
+ const parent = dirname(current);
12
+ if (parent === current)
13
+ return null;
14
+ current = parent;
15
+ }
16
+ }
17
+ /**
18
+ * Handles one raw hook payload for a supported agent: records it (best
19
+ * effort — a DB error must never surface to the calling agent as a hook
20
+ * failure) and returns the ack line to write to stdout, or null when the
21
+ * event doesn't call for one. Kept separate from stdin/stdout so it's
22
+ * directly testable without spawning a process or faking fd 0.
23
+ */
24
+ export function handleAgentEvent(agent, rawInput) {
25
+ let event = {};
26
+ try {
27
+ event = JSON.parse(rawInput);
28
+ }
29
+ catch {
30
+ // No valid JSON on stdin — nothing to record, but the agent still
31
+ // needs a response below so it doesn't treat the hook as hung.
32
+ }
33
+ const eventName = event.hook_event_name;
34
+ const shouldAckContinue = (agent === "codex" && eventName !== "SessionEnd") || (agent === "cursor" && eventName === "beforeSubmitPrompt");
35
+ try {
36
+ const eventPath = typeof event.cwd === "string"
37
+ ? event.cwd
38
+ : Array.isArray(event.workspace_roots) && typeof event.workspace_roots[0] === "string"
39
+ ? event.workspace_roots[0]
40
+ : process.cwd();
41
+ const projectRoot = projectRootFor(eventPath);
42
+ if (projectRoot) {
43
+ const ctx = buildEngineContext(projectRoot);
44
+ try {
45
+ const row = ctx.db.raw.prepare(`SELECT id FROM projects ORDER BY created_at ASC LIMIT 1`).get();
46
+ if (row)
47
+ recordAgentHookEvent(ctx, row.id, agent, event);
48
+ }
49
+ finally {
50
+ ctx.db.close();
51
+ }
52
+ }
53
+ }
54
+ catch {
55
+ // best-effort, see above
56
+ }
57
+ return shouldAckContinue ? '{"continue":true}\n' : null;
58
+ }
59
+ export function createAgentEventCommand() {
60
+ return new Command("agent-event")
61
+ .description("Record a supported coding-agent lifecycle event")
62
+ .argument("<agent>", "codex or cursor")
63
+ .action((agent) => {
64
+ const supportedAgent = agent === "codex" || agent === "cursor" ? agent : null;
65
+ if (!supportedAgent)
66
+ return;
67
+ const rawInput = (() => {
68
+ try {
69
+ return readFileSync(0, "utf-8");
70
+ }
71
+ catch {
72
+ return "";
73
+ }
74
+ })();
75
+ const ack = handleAgentEvent(supportedAgent, rawInput);
76
+ if (ack)
77
+ process.stdout.write(ack);
78
+ });
79
+ }
80
+ //# sourceMappingURL=agent-event.js.map
@@ -0,0 +1,36 @@
1
+ import { Command } from "commander";
2
+ import { requireEngineContext, requireProjectId } from "../context.js";
3
+ import { createCheckpoint } from "../../core/engine.js";
4
+ import { loadConfig } from "../../shared/config.js";
5
+ import { createLLMProvider } from "../../llm/factory.js";
6
+ import { annotateCheckpoint } from "../../llm/annotate-checkpoint.js";
7
+ import { FileSecretStore } from "../../licensing/secret-store.js";
8
+ import { printAiBudgetWarning } from "../ai-budget-warning.js";
9
+ export function createCheckpointCommand() {
10
+ return new Command("checkpoint").description("Manually create a checkpoint").action(async () => {
11
+ const ctx = requireEngineContext(process.cwd());
12
+ const result = await createCheckpoint(ctx);
13
+ if (!result.changed) {
14
+ console.log("No changes since the last checkpoint.");
15
+ ctx.db.close();
16
+ return;
17
+ }
18
+ console.log(`✓ Checkpoint created: #${result.stateId.slice(0, 8)} (${result.fileChanges.length} files changed)`);
19
+ const config = loadConfig(ctx.whydidDir);
20
+ const provider = await createLLMProvider(config.ai, new FileSecretStore(ctx.whydidDir));
21
+ const outcome = await annotateCheckpoint(ctx, provider, result.stateId, result.fileChanges, {
22
+ sendPrompts: config.privacy.sendPrompts,
23
+ sendDiffs: config.privacy.sendDiffs,
24
+ });
25
+ if (outcome.status === "generated") {
26
+ console.log(" AI annotation generated. Run `whydid why <file>` to see it.");
27
+ }
28
+ else if (outcome.status === "failed") {
29
+ console.log(" AI annotation unavailable (provider error). Checkpoint was still saved.");
30
+ }
31
+ const projectId = requireProjectId(ctx);
32
+ printAiBudgetWarning(ctx, projectId, config);
33
+ ctx.db.close();
34
+ });
35
+ }
36
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1,27 @@
1
+ import { Command } from "commander";
2
+ import { requireEngineContext, requireProjectId } from "../context.js";
3
+ import { regenerateAllContextFiles } from "../../core/context-tree.js";
4
+ export async function runContext(projectRoot) {
5
+ const ctx = requireEngineContext(projectRoot);
6
+ const projectId = requireProjectId(ctx);
7
+ const result = await regenerateAllContextFiles(ctx, projectId);
8
+ ctx.db.close();
9
+ return result;
10
+ }
11
+ export function createContextCommand() {
12
+ return new Command("context")
13
+ .description("Regenerate CONTEXT.md files from your project's annotated checkpoint history")
14
+ .action(async () => {
15
+ const { updatedFiles } = await runContext(process.cwd());
16
+ if (updatedFiles.length === 0) {
17
+ console.log("No annotated checkpoints yet — nothing to write.");
18
+ console.log("Context files are generated automatically once your AI provider is configured (see Settings) and checkpoints start getting real explanations.");
19
+ return;
20
+ }
21
+ console.log(`Updated ${updatedFiles.length} context file${updatedFiles.length === 1 ? "" : "s"}:\n`);
22
+ for (const f of updatedFiles)
23
+ console.log(` ${f}`);
24
+ console.log("\nThese are normal files in your working tree — review and commit them like any other change.");
25
+ });
26
+ }
27
+ //# sourceMappingURL=context.js.map