cc-plan-viewer 0.1.0 → 0.2.2
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.
- package/README.md +47 -9
- package/dist/cli-bundle.mjs +28407 -0
- package/dist/client/assets/index-DjikHpVb.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/bin/cc-plan-viewer.js +336 -66
- package/dist/server/server/app.js +171 -0
- package/dist/server/server/app.test.js +154 -0
- package/dist/server/server/index.js +18 -157
- package/dist/server/server/lifecycle.test.js +101 -0
- package/dist/server/server/planParser.test.js +104 -0
- package/dist/server/server/planWatcher.test.js +100 -0
- package/dist/server/server/reviewStore.test.js +88 -0
- package/dist/server-bundle.mjs +27988 -0
- package/hooks/plan-viewer-hook.cjs +19 -16
- package/package.json +30 -9
- package/dist/client/assets/index-CFUAZEOC.css +0 -1
- /package/dist/client/assets/{index-B9Ku0w1F.js → index-BRrHI1zG.js} +0 -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:Inter,system-ui,sans-serif;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:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,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}}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.-left-10{left:-2.5rem}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.top-2{top:.5rem}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.float-right{float:right}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-lg{margin-bottom:24px}.mb-md{margin-bottom:16px}.mb-sm{margin-bottom:8px}.mb-xs{margin-bottom:4px}.ml-4{margin-left:1rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-lg{margin-top:24px}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-2{height:.5rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[80px\]{min-height:80px}.min-h-screen{min-height:100vh}.w-2{width:.5rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-\[300px\]{width:300px}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-\[1200px\]{max-width:1200px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.scale-\[1\.02\]{--tw-scale-x: 1.02;--tw-scale-y: 1.02;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))}.resize-none{resize:none}.resize-y{resize:vertical}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-lg{gap:24px}.gap-sm{gap:8px}.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-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))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:20px}.rounded-md{border-radius:12px}.rounded-sm{border-radius:6px}.border{border-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-none{border-style:none}.border-claude-accent-light{--tw-border-opacity: 1;border-color:rgb(205 115 87 / var(--tw-border-opacity, 1))}.border-claude-border-light{--tw-border-opacity: 1;border-color:rgb(223 215 198 / var(--tw-border-opacity, 1))}.bg-black\/40{background-color:#0006}.bg-claude-accent-light{--tw-bg-opacity: 1;background-color:rgb(205 115 87 / var(--tw-bg-opacity, 1))}.bg-claude-bg-light{--tw-bg-opacity: 1;background-color:rgb(245 240 229 / var(--tw-bg-opacity, 1))}.bg-claude-success-light{--tw-bg-opacity: 1;background-color:rgb(72 149 172 / var(--tw-bg-opacity, 1))}.bg-claude-surface-light{--tw-bg-opacity: 1;background-color:rgb(251 250 241 / var(--tw-bg-opacity, 1))}.bg-claude-surface-light\/80{background-color:#fbfaf1cc}.bg-claude-text-tertiary-light{--tw-bg-opacity: 1;background-color:rgb(142 131 112 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-3{padding:.75rem}.p-lg{padding:24px}.p-md{padding:16px}.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-md{padding-left:16px;padding-right:16px}.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-xl{padding-top:40px;padding-bottom:40px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-40{padding-bottom:10rem}.pl-2\.5{padding-left:.625rem}.pt-2\.5{padding-top:.625rem}.pt-md{padding-top:16px}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.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-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.text-claude-accent-light{--tw-text-opacity: 1;color:rgb(205 115 87 / var(--tw-text-opacity, 1))}.text-claude-text-primary-light{--tw-text-opacity: 1;color:rgb(40 34 24 / var(--tw-text-opacity, 1))}.text-claude-text-secondary-light{--tw-text-opacity: 1;color:rgb(102 102 86 / var(--tw-text-opacity, 1))}.text-claude-text-tertiary-light{--tw-text-opacity: 1;color:rgb(142 131 112 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.opacity-0{opacity:0}.opacity-100{opacity:1}.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-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px 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-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 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}.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)}.backdrop-blur-lg{--tw-backdrop-blur: blur(16px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.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}*{box-sizing:border-box}body{margin:0;font-family:Inter,system-ui,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.prose{color:#282218;line-height:1.75;max-width:none;font-size:15px}@media(prefers-color-scheme:dark){.prose{color:#ede7dc}}.prose h1,.prose h2,.prose h3,.prose h4,.prose h5,.prose h6{font-weight:700;line-height:1.35;margin-top:1.75em;margin-bottom:.6em;color:#1a1610}@media(prefers-color-scheme:dark){.prose h1,.prose h2,.prose h3,.prose h4,.prose h5,.prose h6{color:#f5f0e5}}.prose h1{font-size:1.75rem;margin-top:0}.prose h2{font-size:1.35rem;padding-bottom:.35em;border-bottom:1px solid #DFD7C6}.prose h3{font-size:1.15rem}.prose h4{font-size:1rem;font-weight:600}@media(prefers-color-scheme:dark){.prose h2{border-bottom-color:#44403b}}.prose p{margin-top:.75em;margin-bottom:.75em}.prose ul{margin-top:.5em;margin-bottom:.5em;padding-left:1.6em;list-style-type:disc}.prose ol{margin-top:.5em;margin-bottom:.5em;padding-left:1.6em;list-style-type:decimal}.prose li{margin-top:.3em;margin-bottom:.3em}.prose li::marker{color:#8e8370}@media(prefers-color-scheme:dark){.prose li::marker{color:#6d676a}}.prose strong{font-weight:700;color:#1a1610}@media(prefers-color-scheme:dark){.prose strong{color:#f5f0e5}}.prose em{font-style:italic}.prose a{color:#b05a3c;text-decoration:underline;text-underline-offset:2px;text-decoration-color:#b05a3c66}.prose a:hover{text-decoration-color:#b05a3c}@media(prefers-color-scheme:dark){.prose a{color:#d6896e;text-decoration-color:#d6896e66}.prose a:hover{text-decoration-color:#d6896e}}.prose code{font-family:SF Mono,Fira Code,JetBrains Mono,Cascadia Code,Menlo,Consolas,monospace;font-size:.85em;padding:.2em .45em;border-radius:5px;background:#e8e0d0;color:#5c3d2e;font-weight:500}@media(prefers-color-scheme:dark){.prose code{background:#382f2a;color:#e0c4a8}}.prose pre{margin-top:1em;margin-bottom:1em;padding:1.1em 1.3em;border-radius:10px;overflow-x:auto;background:#2b2420;border:1px solid #3D3530;line-height:1.6}@media(prefers-color-scheme:dark){.prose pre{background:#1a1514;border-color:#332c28}}.prose pre code{padding:0;background:none;border-radius:0;font-size:.8125em;font-weight:400;color:#e8ded0}.prose pre code .hljs-keyword,.prose pre code .hljs-selector-tag,.prose pre code .hljs-built_in{color:#e8a87c}.prose pre code .hljs-string,.prose pre code .hljs-attr{color:#a8c9a0}.prose pre code .hljs-number,.prose pre code .hljs-literal{color:#d4a574}.prose pre code .hljs-type,.prose pre code .hljs-title,.prose pre code .hljs-class .hljs-title{color:#c4b4e0;font-weight:500}.prose pre code .hljs-function,.prose pre code .hljs-title.function_{color:#8cc8d0}.prose pre code .hljs-comment,.prose pre code .hljs-doctag{color:#7a7068;font-style:italic}.prose pre code .hljs-variable,.prose pre code .hljs-template-variable{color:#e0c4a8}.prose pre code .hljs-property{color:#d0b8a0}.prose pre code .hljs-meta,.prose pre code .hljs-preprocessor{color:#b0a090}.prose pre code .hljs-punctuation{color:#9a8e80}.prose pre code .hljs-addition{color:#a8c9a0;background:#a8c9a01a}.prose pre code .hljs-deletion{color:#d08080;background:#d080801a}.prose blockquote{border-left:3px solid #CD7357;padding-left:1em;margin-left:0;margin-top:.75em;margin-bottom:.75em;color:#504838}@media(prefers-color-scheme:dark){.prose blockquote{border-left-color:#d67f63;color:#b8aea0}}.prose table{width:100%;border-collapse:collapse;margin-top:1em;margin-bottom:1em;font-size:.875em}.prose th,.prose td{border:1px solid #DFD7C6;padding:.6em .85em;text-align:left}.prose th{font-weight:600;background:#ede5d5;color:#1a1610}@media(prefers-color-scheme:dark){.prose th,.prose td{border-color:#44403b}.prose th{background:#332c28;color:#f5f0e5}}.prose hr{border:none;border-top:1px solid #DFD7C6;margin:2em 0}@media(prefers-color-scheme:dark){.prose hr{border-top-color:#44403b}}mark.comment-highlight{background-color:#cd735724;border-bottom:2px solid rgba(205,115,87,.45);padding:1px 0;border-radius:2px;cursor:pointer;transition:background-color .2s ease,border-color .2s ease;color:inherit}mark.comment-highlight:hover,mark.comment-highlight.active{background-color:#cd73574d;border-bottom-color:#cd7357cc}mark.comment-highlight-pending{background-color:#cd73572e;border-bottom:2px solid rgba(205,115,87,.5);padding:1px 0;border-radius:2px;color:inherit}@media(prefers-color-scheme:dark){mark.comment-highlight{background-color:#d67f6324;border-bottom-color:#d67f6373}mark.comment-highlight:hover,mark.comment-highlight.active{background-color:#d67f634d;border-bottom-color:#d67f63cc}mark.comment-highlight-pending{background-color:#d67f632e;border-bottom-color:#d67f6380}}::-moz-selection{background:#cd735738;color:inherit}::selection{background:#cd735738;color:inherit}@media(prefers-color-scheme:dark){::-moz-selection{background:#d67f6347}::selection{background:#d67f6347}}.placeholder\:text-claude-text-tertiary-light::-moz-placeholder{--tw-text-opacity: 1;color:rgb(142 131 112 / var(--tw-text-opacity, 1))}.placeholder\:text-claude-text-tertiary-light::placeholder{--tw-text-opacity: 1;color:rgb(142 131 112 / var(--tw-text-opacity, 1))}.hover\:scale-110:hover{--tw-scale-x: 1.1;--tw-scale-y: 1.1;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))}.hover\:bg-claude-border-light\/30:hover{background-color:#dfd7c64d}.hover\:bg-claude-border-light\/50:hover{background-color:#dfd7c680}.hover\:bg-red-50:hover{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\:text-claude-accent-light:hover{--tw-text-opacity: 1;color:rgb(205 115 87 / var(--tw-text-opacity, 1))}.hover\:text-claude-text-primary-light:hover{--tw-text-opacity: 1;color:rgb(40 34 24 / var(--tw-text-opacity, 1))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.hover\:opacity-90:hover{opacity:.9}.focus\:border-claude-accent-light:focus{--tw-border-opacity: 1;border-color:rgb(205 115 87 / var(--tw-border-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:768px){.md\:p-xl{padding:40px}}@media(min-width:1280px){.xl\:block{display:block}.xl\:hidden{display:none}}@media(prefers-color-scheme:dark){.dark\:border-claude-accent-dark{--tw-border-opacity: 1;border-color:rgb(214 127 99 / var(--tw-border-opacity, 1))}.dark\:border-claude-border-dark{--tw-border-opacity: 1;border-color:rgb(68 64 59 / var(--tw-border-opacity, 1))}.dark\:bg-claude-accent-dark{--tw-bg-opacity: 1;background-color:rgb(214 127 99 / var(--tw-bg-opacity, 1))}.dark\:bg-claude-bg-dark{--tw-bg-opacity: 1;background-color:rgb(32 25 24 / var(--tw-bg-opacity, 1))}.dark\:bg-claude-success-dark{--tw-bg-opacity: 1;background-color:rgb(111 187 148 / var(--tw-bg-opacity, 1))}.dark\:bg-claude-surface-dark{--tw-bg-opacity: 1;background-color:rgb(42 39 35 / var(--tw-bg-opacity, 1))}.dark\:bg-claude-surface-dark\/80{background-color:#2a2723cc}.dark\:bg-claude-text-tertiary-dark{--tw-bg-opacity: 1;background-color:rgb(109 103 106 / var(--tw-bg-opacity, 1))}.dark\:text-claude-accent-dark{--tw-text-opacity: 1;color:rgb(214 127 99 / var(--tw-text-opacity, 1))}.dark\:text-claude-text-primary-dark{--tw-text-opacity: 1;color:rgb(237 231 220 / var(--tw-text-opacity, 1))}.dark\:text-claude-text-secondary-dark{--tw-text-opacity: 1;color:rgb(158 153 149 / var(--tw-text-opacity, 1))}.dark\:text-claude-text-tertiary-dark{--tw-text-opacity: 1;color:rgb(109 103 106 / var(--tw-text-opacity, 1))}.dark\:placeholder\:text-claude-text-tertiary-dark::-moz-placeholder{--tw-text-opacity: 1;color:rgb(109 103 106 / var(--tw-text-opacity, 1))}.dark\:placeholder\:text-claude-text-tertiary-dark::placeholder{--tw-text-opacity: 1;color:rgb(109 103 106 / var(--tw-text-opacity, 1))}.dark\:hover\:bg-claude-border-dark\/30:hover{background-color:#44403b4d}.dark\:hover\:bg-claude-border-dark\/50:hover{background-color:#44403b80}.dark\:hover\:bg-red-900\/20:hover{background-color:#7f1d1d33}.dark\:hover\:text-claude-accent-dark:hover{--tw-text-opacity: 1;color:rgb(214 127 99 / var(--tw-text-opacity, 1))}.dark\:hover\:text-claude-text-primary-dark:hover{--tw-text-opacity: 1;color:rgb(237 231 220 / var(--tw-text-opacity, 1))}.dark\:focus\:border-claude-accent-dark:focus{--tw-border-opacity: 1;border-color:rgb(214 127 99 / var(--tw-border-opacity, 1))}}
|
package/dist/client/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-BRrHI1zG.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DjikHpVb.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
|
@@ -2,89 +2,136 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
+
import readline from 'node:readline';
|
|
5
6
|
import { execSync } from 'node:child_process';
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
7
|
+
// ─── Stable install location ───
|
|
8
|
+
const INSTALL_DIR = path.join(os.homedir(), '.cc-plan-viewer');
|
|
9
|
+
const INSTALLED_HOOK = path.join(INSTALL_DIR, 'plan-viewer-hook.cjs');
|
|
10
|
+
const INSTALLED_SERVER = path.join(INSTALL_DIR, 'server-bundle.mjs');
|
|
11
|
+
const INSTALLED_CLIENT = path.join(INSTALL_DIR, 'client');
|
|
12
|
+
// ─── Source paths (inside the npm package) ───
|
|
13
|
+
// When compiled: dist/server/bin/cc-plan-viewer.js
|
|
14
|
+
// Package root is 3 levels up: dist/server/bin → dist/server → dist → root
|
|
15
|
+
const PKG_ROOT_COMPILED = path.resolve(import.meta.dirname, '..', '..', '..');
|
|
16
|
+
// When running via tsx in dev: bin/ → root
|
|
17
|
+
const PKG_ROOT_DEV = path.resolve(import.meta.dirname, '..');
|
|
18
|
+
function getPkgRoot() {
|
|
19
|
+
// Check compiled path first (has dist/ dir)
|
|
20
|
+
if (fs.existsSync(path.join(PKG_ROOT_COMPILED, 'hooks', 'plan-viewer-hook.cjs'))) {
|
|
21
|
+
return PKG_ROOT_COMPILED;
|
|
22
|
+
}
|
|
23
|
+
return PKG_ROOT_DEV;
|
|
24
|
+
}
|
|
25
|
+
// ─── Settings file resolution ───
|
|
26
|
+
const DEFAULT_SETTINGS = path.join(os.homedir(), '.claude', 'settings.json');
|
|
27
|
+
function readSettings(settingsPath) {
|
|
16
28
|
try {
|
|
17
|
-
return JSON.parse(fs.readFileSync(
|
|
29
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
18
30
|
}
|
|
19
31
|
catch {
|
|
20
32
|
return {};
|
|
21
33
|
}
|
|
22
34
|
}
|
|
23
|
-
function writeSettings(settings) {
|
|
24
|
-
|
|
35
|
+
function writeSettings(settingsPath, settings) {
|
|
36
|
+
const dir = path.dirname(settingsPath);
|
|
37
|
+
if (!fs.existsSync(dir))
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
25
40
|
}
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
// ─── Multi-config detection ───
|
|
42
|
+
function findAllClaudeSettings() {
|
|
43
|
+
const home = os.homedir();
|
|
44
|
+
const found = new Set();
|
|
45
|
+
// Scan ~/.claude*/settings.json
|
|
46
|
+
try {
|
|
47
|
+
for (const name of fs.readdirSync(home)) {
|
|
48
|
+
if (!name.startsWith('.claude'))
|
|
49
|
+
continue;
|
|
50
|
+
const candidate = path.join(home, name, 'settings.json');
|
|
51
|
+
if (fs.existsSync(candidate))
|
|
52
|
+
found.add(candidate);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
56
|
+
// Include CLAUDE_CONFIG_DIR if set (may point outside ~/.claude*)
|
|
57
|
+
if (process.env.CLAUDE_CONFIG_DIR) {
|
|
58
|
+
const candidate = path.join(process.env.CLAUDE_CONFIG_DIR, 'settings.json');
|
|
59
|
+
if (fs.existsSync(candidate))
|
|
60
|
+
found.add(candidate);
|
|
61
|
+
}
|
|
62
|
+
return [...found].sort();
|
|
28
63
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
function tildePath(p) {
|
|
65
|
+
return p.replace(os.homedir(), '~');
|
|
66
|
+
}
|
|
67
|
+
async function promptMultiSelect(options, question, activeConfigDir) {
|
|
68
|
+
// Non-interactive: fall back to CLAUDE_CONFIG_DIR or all
|
|
69
|
+
if (!process.stdin.isTTY) {
|
|
70
|
+
if (activeConfigDir) {
|
|
71
|
+
const match = options.findIndex(p => p.startsWith(activeConfigDir));
|
|
72
|
+
return match >= 0 ? [match] : [0];
|
|
73
|
+
}
|
|
74
|
+
return options.map((_, i) => i);
|
|
34
75
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(`[cc-plan-viewer] Found ${options.length} Claude config directories:`);
|
|
79
|
+
options.forEach((opt, i) => {
|
|
80
|
+
const active = activeConfigDir && opt.startsWith(activeConfigDir) ? ' (active)' : '';
|
|
81
|
+
console.log(` ${i + 1}. ${tildePath(opt)}${active}`);
|
|
82
|
+
});
|
|
83
|
+
console.log('');
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
rl.question(`${question} (comma-separated numbers, or "all"): `, (answer) => {
|
|
86
|
+
rl.close();
|
|
87
|
+
const trimmed = answer.trim().toLowerCase();
|
|
88
|
+
if (trimmed === 'all' || trimmed === '') {
|
|
89
|
+
resolve(options.map((_, i) => i));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const indices = trimmed.split(',')
|
|
93
|
+
.map(s => parseInt(s.trim(), 10) - 1)
|
|
94
|
+
.filter(i => i >= 0 && i < options.length);
|
|
95
|
+
resolve(indices);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function addHookToSettings(settingsPath) {
|
|
100
|
+
const settings = readSettings(settingsPath);
|
|
38
101
|
if (!settings.hooks || typeof settings.hooks !== 'object') {
|
|
39
102
|
settings.hooks = {};
|
|
40
103
|
}
|
|
41
104
|
const hooks = settings.hooks;
|
|
42
|
-
// Ensure PostToolUse array exists
|
|
43
105
|
if (!Array.isArray(hooks.PostToolUse)) {
|
|
44
106
|
hooks.PostToolUse = [];
|
|
45
107
|
}
|
|
46
108
|
const hookCommand = getHookCommand();
|
|
47
|
-
//
|
|
48
|
-
|
|
109
|
+
// Remove any existing cc-plan-viewer hooks first (handles upgrades)
|
|
110
|
+
hooks.PostToolUse = hooks.PostToolUse.filter((entry) => {
|
|
49
111
|
if (typeof entry !== 'object' || entry === null)
|
|
50
|
-
return
|
|
112
|
+
return true;
|
|
51
113
|
const e = entry;
|
|
52
114
|
if (!Array.isArray(e.hooks))
|
|
53
|
-
return
|
|
54
|
-
return e.hooks.some((h) => {
|
|
115
|
+
return true;
|
|
116
|
+
return !e.hooks.some((h) => {
|
|
55
117
|
if (typeof h !== 'object' || h === null)
|
|
56
118
|
return false;
|
|
57
|
-
|
|
119
|
+
const cmd = h.command;
|
|
120
|
+
return typeof cmd === 'string' && cmd.includes('plan-viewer-hook');
|
|
58
121
|
});
|
|
59
122
|
});
|
|
60
|
-
|
|
61
|
-
console.log('[cc-plan-viewer] Hook already installed.');
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
// Add the hook entry
|
|
123
|
+
// Add fresh hook entry
|
|
65
124
|
hooks.PostToolUse.push({
|
|
66
125
|
matcher: 'Write|Edit',
|
|
67
|
-
hooks: [
|
|
68
|
-
{
|
|
69
|
-
type: 'command',
|
|
70
|
-
command: hookCommand,
|
|
71
|
-
},
|
|
72
|
-
],
|
|
126
|
+
hooks: [{ type: 'command', command: hookCommand }],
|
|
73
127
|
});
|
|
74
|
-
writeSettings(settings);
|
|
75
|
-
console.log('[cc-plan-viewer] Hook installed successfully.');
|
|
76
|
-
console.log(`[cc-plan-viewer] Hook script: ${resolvedHookSource}`);
|
|
77
|
-
console.log('[cc-plan-viewer] Added to: ~/.claude/settings.json (PostToolUse)');
|
|
128
|
+
writeSettings(settingsPath, settings);
|
|
78
129
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
const settings = readSettings();
|
|
130
|
+
function removeHookFromSettings(settingsPath) {
|
|
131
|
+
const settings = readSettings(settingsPath);
|
|
82
132
|
const hooks = settings.hooks;
|
|
83
|
-
if (!hooks?.PostToolUse || !Array.isArray(hooks.PostToolUse))
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const hookCommand = getHookCommand();
|
|
133
|
+
if (!hooks?.PostToolUse || !Array.isArray(hooks.PostToolUse))
|
|
134
|
+
return false;
|
|
88
135
|
const before = hooks.PostToolUse.length;
|
|
89
136
|
hooks.PostToolUse = hooks.PostToolUse.filter((entry) => {
|
|
90
137
|
if (typeof entry !== 'object' || entry === null)
|
|
@@ -99,16 +146,212 @@ function uninstall() {
|
|
|
99
146
|
return typeof cmd === 'string' && cmd.includes('plan-viewer-hook');
|
|
100
147
|
});
|
|
101
148
|
});
|
|
102
|
-
if (hooks.PostToolUse.length
|
|
103
|
-
|
|
149
|
+
if (hooks.PostToolUse.length < before) {
|
|
150
|
+
writeSettings(settingsPath, settings);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
function settingsHasHook(settingsPath) {
|
|
156
|
+
const settings = readSettings(settingsPath);
|
|
157
|
+
const hooks = settings.hooks;
|
|
158
|
+
if (!hooks?.PostToolUse || !Array.isArray(hooks.PostToolUse))
|
|
159
|
+
return false;
|
|
160
|
+
return hooks.PostToolUse.some((entry) => {
|
|
161
|
+
if (typeof entry !== 'object' || entry === null)
|
|
162
|
+
return false;
|
|
163
|
+
const e = entry;
|
|
164
|
+
if (!Array.isArray(e.hooks))
|
|
165
|
+
return false;
|
|
166
|
+
return e.hooks.some((h) => {
|
|
167
|
+
if (typeof h !== 'object' || h === null)
|
|
168
|
+
return false;
|
|
169
|
+
const cmd = h.command;
|
|
170
|
+
return typeof cmd === 'string' && cmd.includes('plan-viewer-hook');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// ─── Copy files to stable location ───
|
|
175
|
+
function copyDir(src, dest) {
|
|
176
|
+
if (!fs.existsSync(src))
|
|
177
|
+
return;
|
|
178
|
+
if (fs.existsSync(dest))
|
|
179
|
+
fs.rmSync(dest, { recursive: true });
|
|
180
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
181
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
182
|
+
const srcPath = path.join(src, entry.name);
|
|
183
|
+
const destPath = path.join(dest, entry.name);
|
|
184
|
+
if (entry.isDirectory()) {
|
|
185
|
+
copyDir(srcPath, destPath);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
fs.copyFileSync(srcPath, destPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function installFiles() {
|
|
193
|
+
const pkgRoot = getPkgRoot();
|
|
194
|
+
// Create install dir
|
|
195
|
+
if (!fs.existsSync(INSTALL_DIR)) {
|
|
196
|
+
fs.mkdirSync(INSTALL_DIR, { recursive: true });
|
|
197
|
+
}
|
|
198
|
+
// Copy hook
|
|
199
|
+
const hookSrc = path.join(pkgRoot, 'hooks', 'plan-viewer-hook.cjs');
|
|
200
|
+
if (fs.existsSync(hookSrc)) {
|
|
201
|
+
fs.copyFileSync(hookSrc, INSTALLED_HOOK);
|
|
202
|
+
}
|
|
203
|
+
// Copy bundled server (single file, all deps included)
|
|
204
|
+
const serverSrc = path.join(pkgRoot, 'dist', 'server-bundle.mjs');
|
|
205
|
+
if (fs.existsSync(serverSrc)) {
|
|
206
|
+
fs.copyFileSync(serverSrc, INSTALLED_SERVER);
|
|
207
|
+
}
|
|
208
|
+
// Copy client SPA
|
|
209
|
+
const clientSrc = path.join(pkgRoot, 'dist', 'client');
|
|
210
|
+
if (fs.existsSync(clientSrc)) {
|
|
211
|
+
copyDir(clientSrc, INSTALLED_CLIENT);
|
|
212
|
+
}
|
|
213
|
+
// Write version file
|
|
214
|
+
try {
|
|
215
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
216
|
+
fs.writeFileSync(path.join(INSTALL_DIR, 'version.json'), JSON.stringify({ version: pkg.version, installedAt: new Date().toISOString() }, null, 2), 'utf8');
|
|
217
|
+
}
|
|
218
|
+
catch { }
|
|
219
|
+
}
|
|
220
|
+
// ─── Hook management ───
|
|
221
|
+
function getHookCommand() {
|
|
222
|
+
return `node "${INSTALLED_HOOK}"`;
|
|
223
|
+
}
|
|
224
|
+
async function install() {
|
|
225
|
+
console.log('[cc-plan-viewer] Installing...');
|
|
226
|
+
console.log(`[cc-plan-viewer] Install dir: ${INSTALL_DIR}`);
|
|
227
|
+
// Copy files to stable location
|
|
228
|
+
installFiles();
|
|
229
|
+
console.log('[cc-plan-viewer] Files copied to ~/.cc-plan-viewer/');
|
|
230
|
+
patchHookPaths();
|
|
231
|
+
// Determine which settings files to update
|
|
232
|
+
const configIdx = process.argv.indexOf('--config');
|
|
233
|
+
if (configIdx !== -1 && process.argv[configIdx + 1]) {
|
|
234
|
+
// Explicit --config flag: use exactly that path
|
|
235
|
+
const settingsPath = path.resolve(process.argv[configIdx + 1]);
|
|
236
|
+
if (!fs.existsSync(path.dirname(settingsPath))) {
|
|
237
|
+
console.error(`[cc-plan-viewer] Directory does not exist: ${path.dirname(settingsPath)}`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
addHookToSettings(settingsPath);
|
|
241
|
+
console.log(`[cc-plan-viewer] Hook added to ${tildePath(settingsPath)}`);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
const allSettings = findAllClaudeSettings();
|
|
245
|
+
if (allSettings.length === 0) {
|
|
246
|
+
// No existing configs — create default
|
|
247
|
+
addHookToSettings(DEFAULT_SETTINGS);
|
|
248
|
+
console.log(`[cc-plan-viewer] Hook added to ${tildePath(DEFAULT_SETTINGS)}`);
|
|
249
|
+
}
|
|
250
|
+
else if (allSettings.length === 1) {
|
|
251
|
+
// Single config — use it directly
|
|
252
|
+
addHookToSettings(allSettings[0]);
|
|
253
|
+
console.log(`[cc-plan-viewer] Hook added to ${tildePath(allSettings[0])}`);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Multiple configs — prompt user
|
|
257
|
+
const selected = await promptMultiSelect(allSettings, 'Install hook in which configs?', process.env.CLAUDE_CONFIG_DIR);
|
|
258
|
+
if (selected.length === 0) {
|
|
259
|
+
console.log('[cc-plan-viewer] No configs selected. Hook not installed.');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
for (const idx of selected) {
|
|
263
|
+
addHookToSettings(allSettings[idx]);
|
|
264
|
+
console.log(`[cc-plan-viewer] Hook added to ${tildePath(allSettings[idx])}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log('[cc-plan-viewer] Hook installed successfully.');
|
|
270
|
+
console.log('');
|
|
271
|
+
console.log(' Next time Claude Code writes a plan, the viewer will open in your browser.');
|
|
272
|
+
console.log('');
|
|
273
|
+
console.log(' Update anytime: npx cc-plan-viewer@latest update');
|
|
274
|
+
console.log(' Uninstall: npx cc-plan-viewer uninstall');
|
|
275
|
+
}
|
|
276
|
+
function patchHookPaths() {
|
|
277
|
+
// The installed hook is at ~/.cc-plan-viewer/plan-viewer-hook.cjs
|
|
278
|
+
// The server bundle is at ~/.cc-plan-viewer/server-bundle.mjs
|
|
279
|
+
// We patch the hook to point to the bundled server
|
|
280
|
+
if (!fs.existsSync(INSTALLED_HOOK))
|
|
104
281
|
return;
|
|
282
|
+
let hookContent = fs.readFileSync(INSTALLED_HOOK, 'utf8');
|
|
283
|
+
// Replace server path to point to the bundled server in the same dir
|
|
284
|
+
hookContent = hookContent.replace(/const serverPath = path\.join\(__dirname, [^;]+;/, `const serverPath = path.join(__dirname, 'server-bundle.mjs');`);
|
|
285
|
+
fs.writeFileSync(INSTALLED_HOOK, hookContent, 'utf8');
|
|
286
|
+
}
|
|
287
|
+
function update() {
|
|
288
|
+
console.log('[cc-plan-viewer] Updating...');
|
|
289
|
+
if (!fs.existsSync(INSTALL_DIR)) {
|
|
290
|
+
console.log('[cc-plan-viewer] Not installed. Run: npx cc-plan-viewer install');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// Read old version
|
|
294
|
+
let oldVersion = 'unknown';
|
|
295
|
+
try {
|
|
296
|
+
const v = JSON.parse(fs.readFileSync(path.join(INSTALL_DIR, 'version.json'), 'utf8'));
|
|
297
|
+
oldVersion = v.version;
|
|
298
|
+
}
|
|
299
|
+
catch { }
|
|
300
|
+
// Copy new files
|
|
301
|
+
installFiles();
|
|
302
|
+
patchHookPaths();
|
|
303
|
+
// Read new version
|
|
304
|
+
let newVersion = 'unknown';
|
|
305
|
+
try {
|
|
306
|
+
const v = JSON.parse(fs.readFileSync(path.join(INSTALL_DIR, 'version.json'), 'utf8'));
|
|
307
|
+
newVersion = v.version;
|
|
308
|
+
}
|
|
309
|
+
catch { }
|
|
310
|
+
if (oldVersion === newVersion) {
|
|
311
|
+
console.log(`[cc-plan-viewer] Already on latest version (${newVersion}).`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
console.log(`[cc-plan-viewer] Updated: ${oldVersion} → ${newVersion}`);
|
|
105
315
|
}
|
|
106
|
-
|
|
107
|
-
|
|
316
|
+
console.log('[cc-plan-viewer] Files updated in ~/.cc-plan-viewer/');
|
|
317
|
+
}
|
|
318
|
+
async function uninstall() {
|
|
319
|
+
console.log('[cc-plan-viewer] Uninstalling...');
|
|
320
|
+
// Determine which settings files to clean up
|
|
321
|
+
const configIdx = process.argv.indexOf('--config');
|
|
322
|
+
if (configIdx !== -1 && process.argv[configIdx + 1]) {
|
|
323
|
+
const settingsPath = path.resolve(process.argv[configIdx + 1]);
|
|
324
|
+
if (removeHookFromSettings(settingsPath)) {
|
|
325
|
+
console.log(`[cc-plan-viewer] Hook removed from ${tildePath(settingsPath)}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const allSettings = findAllClaudeSettings();
|
|
330
|
+
const withHook = allSettings.filter(settingsHasHook);
|
|
331
|
+
if (withHook.length === 0) {
|
|
332
|
+
console.log('[cc-plan-viewer] No hooks found in any Claude config.');
|
|
333
|
+
}
|
|
334
|
+
else if (withHook.length === 1) {
|
|
335
|
+
removeHookFromSettings(withHook[0]);
|
|
336
|
+
console.log(`[cc-plan-viewer] Hook removed from ${tildePath(withHook[0])}`);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const selected = await promptMultiSelect(withHook, 'Remove hook from which configs?');
|
|
340
|
+
for (const idx of selected) {
|
|
341
|
+
removeHookFromSettings(withHook[idx]);
|
|
342
|
+
console.log(`[cc-plan-viewer] Hook removed from ${tildePath(withHook[idx])}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Remove installed files
|
|
347
|
+
if (fs.existsSync(INSTALL_DIR)) {
|
|
348
|
+
fs.rmSync(INSTALL_DIR, { recursive: true });
|
|
349
|
+
console.log('[cc-plan-viewer] Removed ~/.cc-plan-viewer/');
|
|
350
|
+
}
|
|
351
|
+
console.log('[cc-plan-viewer] Uninstalled.');
|
|
108
352
|
}
|
|
109
353
|
function start() {
|
|
110
354
|
console.log('[cc-plan-viewer] Starting server...');
|
|
111
|
-
// Import and run the server
|
|
112
355
|
import('../server/index.js');
|
|
113
356
|
}
|
|
114
357
|
function open(filename) {
|
|
@@ -124,14 +367,32 @@ function open(filename) {
|
|
|
124
367
|
console.log(`[cc-plan-viewer] Open this URL in your browser: ${url}`);
|
|
125
368
|
}
|
|
126
369
|
}
|
|
127
|
-
|
|
370
|
+
function version() {
|
|
371
|
+
try {
|
|
372
|
+
const v = JSON.parse(fs.readFileSync(path.join(INSTALL_DIR, 'version.json'), 'utf8'));
|
|
373
|
+
console.log(`Installed: ${v.version} (${v.installedAt})`);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
console.log('Not installed locally. Run: npx cc-plan-viewer install');
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const pkgRoot = getPkgRoot();
|
|
380
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
381
|
+
console.log(`Package: ${pkg.version}`);
|
|
382
|
+
}
|
|
383
|
+
catch { }
|
|
384
|
+
}
|
|
385
|
+
// ─── CLI ───
|
|
128
386
|
const command = process.argv[2];
|
|
129
387
|
switch (command) {
|
|
130
388
|
case 'install':
|
|
131
|
-
install();
|
|
389
|
+
await install();
|
|
390
|
+
break;
|
|
391
|
+
case 'update':
|
|
392
|
+
update();
|
|
132
393
|
break;
|
|
133
394
|
case 'uninstall':
|
|
134
|
-
uninstall();
|
|
395
|
+
await uninstall();
|
|
135
396
|
break;
|
|
136
397
|
case 'start':
|
|
137
398
|
start();
|
|
@@ -139,14 +400,23 @@ switch (command) {
|
|
|
139
400
|
case 'open':
|
|
140
401
|
open(process.argv[3]);
|
|
141
402
|
break;
|
|
403
|
+
case 'version':
|
|
404
|
+
version();
|
|
405
|
+
break;
|
|
142
406
|
default:
|
|
143
407
|
console.log(`
|
|
144
408
|
cc-plan-viewer — Browser-based review UI for Claude Code plans
|
|
145
409
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
410
|
+
Usage:
|
|
411
|
+
npx cc-plan-viewer install Install hook + viewer files
|
|
412
|
+
npx cc-plan-viewer install --config <path> Use specific settings.json path
|
|
413
|
+
npx cc-plan-viewer@latest update Update to latest version
|
|
414
|
+
npx cc-plan-viewer uninstall Remove hook + viewer files
|
|
415
|
+
npx cc-plan-viewer version Show installed version
|
|
416
|
+
|
|
417
|
+
When multiple Claude configs are detected (~/.claude*/settings.json),
|
|
418
|
+
you'll be prompted to choose which ones to install the hook in.
|
|
419
|
+
|
|
420
|
+
Files are installed to ~/.cc-plan-viewer/ so they persist across npm cache clears.
|
|
151
421
|
`);
|
|
152
422
|
}
|