@fatdoge/wtree 0.1.9 → 0.2.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.
- package/README.en.md +66 -1
- package/README.md +60 -1
- package/api/cli/wtree.ts +218 -78
- package/dist/assets/index-C78PMV-C.js +179 -0
- package/dist/assets/index-CR9jga1C.css +1 -0
- package/dist/index.html +2 -2
- package/dist-node/api/cli/wtree.js +236 -81
- package/package.json +3 -1
- package/skills/wtree/SKILL.md +162 -0
- package/src/App.tsx +2 -2
- package/src/pages/CreateWorktree.tsx +4 -5
- package/src/pages/SettingsPage.tsx +10 -7
- package/src/pages/Worktrees.tsx +13 -29
- package/dist/assets/index-AspflbWf.js +0 -179
- package/dist/assets/index-DXiZ6dVD.css +0 -1
- package/src/components/ToastHost.tsx +0 -41
- package/src/stores/toastStore.ts +0 -29
|
@@ -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: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}.fixed{position:fixed}.inset-0{top:0;right:0;bottom:0;left:0}.z-50{z-index:50}.order-1{order:1}.order-2{order:2}.col-span-12{grid-column:span 12 / span 12}.col-span-2{grid-column:span 2 / span 2}.col-span-3{grid-column:span 3 / span 3}.col-span-5{grid-column:span 5 / span 5}.mx-auto{margin-left:auto;margin-right:auto}.mb-0\.5{margin-bottom:.125rem}.mb-4{margin-bottom:1rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-full{height:100%}.max-h-\[520px\]{max-height:520px}.w-4{width:1rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-md{max-width:28rem}.max-w-screen-xl{max-width:1280px}.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.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-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.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))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.bg-rose-100{--tw-bg-opacity: 1;background-color:rgb(255 228 230 / var(--tw-bg-opacity, 1))}.bg-rose-600{--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity, 1))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.bg-slate-900\/50{background-color:#0f172a80}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.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-6{padding-top:1.5rem;padding-bottom:1.5rem}.pt-2{padding-top:.5rem}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.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-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-rose-600{--tw-text-opacity: 1;color:rgb(225 29 72 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.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)}.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-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-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}:root{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";line-height:1.5;font-weight:400;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html,body,#root{height:100%}body{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}body:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-400::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity, 1))}.hover\:bg-rose-700:hover{--tw-bg-opacity: 1;background-color:rgb(190 18 60 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-200:hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.hover\:text-slate-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.hover\:text-slate-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.focus\:border-indigo-500:focus{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--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(1px + 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)}.focus\:ring-2:focus{--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(2px + 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)}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity, 1))}.focus\:ring-slate-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(148 163 184 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.dark\:border-slate-700:is(.dark *){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.dark\:border-slate-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity, 1))}.dark\:border-slate-800\/50:is(.dark *){border-color:#1e293b80}.dark\:bg-black\/50:is(.dark *){background-color:#00000080}.dark\:bg-indigo-500:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity, 1))}.dark\:bg-rose-500:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.dark\:bg-rose-500\/10:is(.dark *){background-color:#f43f5e1a}.dark\:bg-slate-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-900\/60:is(.dark *){background-color:#0f172a99}.dark\:bg-slate-950:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity, 1))}.dark\:text-emerald-400:is(.dark *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:text-rose-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.dark\:text-slate-100:is(.dark *){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.dark\:text-slate-200:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.dark\:text-slate-300:is(.dark *){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.dark\:text-slate-400:is(.dark *){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.dark\:text-slate-500:is(.dark *){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:placeholder\:text-slate-500:is(.dark *)::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:placeholder\:text-slate-500:is(.dark *)::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:hover\:bg-indigo-600:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-rose-600:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-slate-700:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-slate-800:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-slate-900\/40:hover:is(.dark *){background-color:#0f172a66}.dark\:hover\:text-slate-100:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.dark\:hover\:text-slate-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.dark\:focus\:border-indigo-500:focus:is(.dark *){--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity, 1))}.dark\:focus\:ring-indigo-500:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity, 1))}.dark\:focus\:ring-slate-700:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(51 65 85 / var(--tw-ring-opacity, 1))}@media(min-width:640px){.sm\:w-auto{width:auto}.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}}@media(min-width:768px){.md\:mt-0{margin-top:0}.md\:block{display:block}.md\:inline{display:inline}.md\:grid{display:grid}.md\:hidden{display:none}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:justify-end{justify-content:flex-end}.md\:border-b-0{border-bottom-width:0px}.md\:py-2{padding-top:.5rem;padding-bottom:.5rem}}@media(min-width:1024px){.lg\:order-1{order:1}.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3 / span 3}.lg\:col-span-5{grid-column:span 5 / span 5}.lg\:col-span-6{grid-column:span 6 / span 6}.lg\:col-span-7{grid-column:span 7 / span 7}.lg\:col-span-9{grid-column:span 9 / span 9}.lg\:mb-0{margin-bottom:0}}
|
package/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>TreeLab - Git Worktree Manager</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-C78PMV-C.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CR9jga1C.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -21,6 +21,14 @@ function parseArgs(argv) {
|
|
|
21
21
|
noOpen: false,
|
|
22
22
|
repo: '',
|
|
23
23
|
port: undefined,
|
|
24
|
+
json: false,
|
|
25
|
+
yes: false,
|
|
26
|
+
force: false,
|
|
27
|
+
dir: '',
|
|
28
|
+
base: '',
|
|
29
|
+
editor: undefined,
|
|
30
|
+
noEditor: false,
|
|
31
|
+
noInstall: false,
|
|
24
32
|
};
|
|
25
33
|
const positional = [];
|
|
26
34
|
while (args.length) {
|
|
@@ -43,6 +51,38 @@ function parseArgs(argv) {
|
|
|
43
51
|
flags.port = v;
|
|
44
52
|
continue;
|
|
45
53
|
}
|
|
54
|
+
if (a === '--json') {
|
|
55
|
+
flags.json = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (a === '--yes' || a === '-y') {
|
|
59
|
+
flags.yes = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (a === '--force' || a === '-f') {
|
|
63
|
+
flags.force = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (a === '--dir') {
|
|
67
|
+
flags.dir = String(args.shift() || '');
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (a === '--base') {
|
|
71
|
+
flags.base = String(args.shift() || '');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (a === '--editor') {
|
|
75
|
+
flags.editor = String(args.shift() || '');
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (a === '--no-editor') {
|
|
79
|
+
flags.noEditor = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (a === '--no-install') {
|
|
83
|
+
flags.noInstall = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
46
86
|
if (a.startsWith('--'))
|
|
47
87
|
continue;
|
|
48
88
|
positional.push(a);
|
|
@@ -80,8 +120,12 @@ function parseCommand(positional) {
|
|
|
80
120
|
}
|
|
81
121
|
return { command: 'interactive', rest: positional };
|
|
82
122
|
}
|
|
83
|
-
function printWorktreeList(rootDir) {
|
|
123
|
+
function printWorktreeList(rootDir, json = false) {
|
|
84
124
|
const items = listWorktrees(rootDir);
|
|
125
|
+
if (json) {
|
|
126
|
+
console.info(JSON.stringify(items, null, 2));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
85
129
|
if (items.length === 0) {
|
|
86
130
|
console.info('未读取到 worktree。');
|
|
87
131
|
return;
|
|
@@ -98,7 +142,7 @@ function printHelp() {
|
|
|
98
142
|
console.info(' wtree');
|
|
99
143
|
console.info(' wtree list');
|
|
100
144
|
console.info(' wtree create [branch]');
|
|
101
|
-
console.info(' wtree delete');
|
|
145
|
+
console.info(' wtree delete [branch|path ...]');
|
|
102
146
|
console.info(' wtree open [path|branch]');
|
|
103
147
|
console.info(' wtree lock [path|branch]');
|
|
104
148
|
console.info(' wtree unlock [path|branch]');
|
|
@@ -108,7 +152,23 @@ function printHelp() {
|
|
|
108
152
|
console.info(' wtree config set <key> <value>');
|
|
109
153
|
console.info(' wtree --ui [--repo <path>] [--no-open] [--port <number>]');
|
|
110
154
|
console.info('');
|
|
155
|
+
console.info('选项:');
|
|
156
|
+
console.info(' --json 以 JSON 格式输出 (适合脚本/agent 使用)');
|
|
157
|
+
console.info(' --yes, -y 自动确认所有提示');
|
|
158
|
+
console.info(' --force, -f 强制操作 (如强制删除有未提交更改的 worktree)');
|
|
159
|
+
console.info(' --dir <path> 指定 worktree 目录路径 (相对于 git 根目录)');
|
|
160
|
+
console.info(' --base <ref> 创建新分支时的基准引用 (如 main, origin/main)');
|
|
161
|
+
console.info(' --editor <name> 创建后使用指定编辑器打开 (trae, cursor, code, none)');
|
|
162
|
+
console.info(' --no-editor 创建后不打开编辑器');
|
|
163
|
+
console.info(' --no-install 创建后不自动安装依赖');
|
|
164
|
+
console.info('');
|
|
111
165
|
console.info('可用配置 key: baseDir, openCommand, editorCommand');
|
|
166
|
+
console.info('');
|
|
167
|
+
console.info('非交互示例:');
|
|
168
|
+
console.info(' wtree list --json');
|
|
169
|
+
console.info(' wtree create feat/x --yes --no-editor --no-install --json');
|
|
170
|
+
console.info(' wtree create feat/new --base main --yes --dir worktrees/feat-new --json');
|
|
171
|
+
console.info(' wtree delete feat/old --yes --force --json');
|
|
112
172
|
}
|
|
113
173
|
function resolveWorktree(rootDir, key) {
|
|
114
174
|
const items = listWorktrees(rootDir);
|
|
@@ -276,18 +336,20 @@ async function main() {
|
|
|
276
336
|
process.on('SIGTERM', close);
|
|
277
337
|
return;
|
|
278
338
|
}
|
|
279
|
-
|
|
339
|
+
if (!flags.json) {
|
|
340
|
+
console.info(chalk.blue(`检测到git repo根目录 ${rootDir},将在这里运行git命令`));
|
|
341
|
+
}
|
|
280
342
|
const { command, rest } = parseCommand(positional);
|
|
281
343
|
if (command === 'list') {
|
|
282
|
-
printWorktreeList(rootDir);
|
|
344
|
+
printWorktreeList(rootDir, flags.json);
|
|
283
345
|
return;
|
|
284
346
|
}
|
|
285
347
|
if (command === 'create') {
|
|
286
|
-
await createWorktree({ rootDir }, rest[0]);
|
|
348
|
+
await createWorktree({ rootDir, flags }, rest[0]);
|
|
287
349
|
return;
|
|
288
350
|
}
|
|
289
351
|
if (command === 'delete') {
|
|
290
|
-
await deleteWorktree({ rootDir });
|
|
352
|
+
await deleteWorktree({ rootDir, flags }, rest);
|
|
291
353
|
return;
|
|
292
354
|
}
|
|
293
355
|
if (command === 'open') {
|
|
@@ -329,7 +391,7 @@ async function main() {
|
|
|
329
391
|
}
|
|
330
392
|
const directBranch = rest[0];
|
|
331
393
|
const action = await getUserAction(directBranch);
|
|
332
|
-
const ctx = { rootDir };
|
|
394
|
+
const ctx = { rootDir, flags };
|
|
333
395
|
if (action === 'create') {
|
|
334
396
|
await createWorktree(ctx, directBranch);
|
|
335
397
|
}
|
|
@@ -374,21 +436,28 @@ async function getUserAction(directBranch) {
|
|
|
374
436
|
return action;
|
|
375
437
|
}
|
|
376
438
|
async function createWorktree(ctx, directBranch) {
|
|
377
|
-
const { rootDir } = ctx;
|
|
439
|
+
const { rootDir, flags } = ctx;
|
|
378
440
|
const defaultBranch = git(rootDir, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']).stdout
|
|
379
441
|
.replace(/^origin\//, '')
|
|
380
442
|
.trim() || 'master';
|
|
381
443
|
const { sourceType, selection } = await selectSource(rootDir, directBranch, defaultBranch);
|
|
382
|
-
const { targetBranch, baseRef, isNewBranch } = await resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch);
|
|
383
|
-
const { targetDir, dirName } = await selectTargetDir(rootDir, targetBranch);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
444
|
+
const { targetBranch, baseRef, isNewBranch } = await resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch, flags);
|
|
445
|
+
const { targetDir, dirName } = await selectTargetDir(rootDir, targetBranch, flags);
|
|
446
|
+
if (!flags.json) {
|
|
447
|
+
console.info(chalk.green(`\n准备创建 Worktree:`));
|
|
448
|
+
console.info(` 分支: ${targetBranch}`);
|
|
449
|
+
console.info(` 目录: ${targetDir}`);
|
|
450
|
+
console.info(` 来源: ${baseRef || 'Existing Local'}`);
|
|
451
|
+
}
|
|
388
452
|
await createGitWorktree(rootDir, targetDir, targetBranch, baseRef, isNewBranch, sourceType, defaultBranch);
|
|
389
453
|
await setupWorktreeEnv(rootDir, targetDir, dirName);
|
|
390
|
-
await installDependencies(targetDir);
|
|
391
|
-
await openInIDE(targetDir);
|
|
454
|
+
await installDependencies(targetDir, flags.noInstall);
|
|
455
|
+
await openInIDE(targetDir, flags);
|
|
456
|
+
if (flags.json) {
|
|
457
|
+
const items = listWorktrees(rootDir);
|
|
458
|
+
const created = items.find(x => path.resolve(x.path) === path.resolve(targetDir));
|
|
459
|
+
console.info(JSON.stringify({ ok: true, data: created || null }));
|
|
460
|
+
}
|
|
392
461
|
}
|
|
393
462
|
async function selectSource(rootDir, directBranch, defaultBranch) {
|
|
394
463
|
let sourceType;
|
|
@@ -430,7 +499,7 @@ async function selectSource(rootDir, directBranch, defaultBranch) {
|
|
|
430
499
|
}
|
|
431
500
|
return { sourceType, selection };
|
|
432
501
|
}
|
|
433
|
-
async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch) {
|
|
502
|
+
async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch, flags) {
|
|
434
503
|
let targetBranch = '';
|
|
435
504
|
let baseRef = '';
|
|
436
505
|
let isNewBranch = false;
|
|
@@ -470,20 +539,26 @@ async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, d
|
|
|
470
539
|
isNewBranch = true;
|
|
471
540
|
}
|
|
472
541
|
else {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
type: 'confirm',
|
|
476
|
-
name: 'createNew',
|
|
477
|
-
message: `分支 ${targetBranch} 不存在。是否基于 ${defaultBranch} 创建新分支?`,
|
|
478
|
-
default: true,
|
|
479
|
-
},
|
|
480
|
-
]);
|
|
481
|
-
if (createNew) {
|
|
482
|
-
baseRef = defaultBranch;
|
|
542
|
+
if (flags.yes) {
|
|
543
|
+
baseRef = flags.base || defaultBranch;
|
|
483
544
|
isNewBranch = true;
|
|
484
545
|
}
|
|
485
546
|
else {
|
|
486
|
-
|
|
547
|
+
const { createNew } = await inquirer.prompt([
|
|
548
|
+
{
|
|
549
|
+
type: 'confirm',
|
|
550
|
+
name: 'createNew',
|
|
551
|
+
message: `分支 ${targetBranch} 不存在。是否基于 ${defaultBranch} 创建新分支?`,
|
|
552
|
+
default: true,
|
|
553
|
+
},
|
|
554
|
+
]);
|
|
555
|
+
if (createNew) {
|
|
556
|
+
baseRef = defaultBranch;
|
|
557
|
+
isNewBranch = true;
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
487
562
|
}
|
|
488
563
|
}
|
|
489
564
|
}
|
|
@@ -539,16 +614,26 @@ async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, d
|
|
|
539
614
|
}
|
|
540
615
|
return { targetBranch, baseRef, isNewBranch };
|
|
541
616
|
}
|
|
542
|
-
async function selectTargetDir(rootDir, targetBranch) {
|
|
617
|
+
async function selectTargetDir(rootDir, targetBranch, flags) {
|
|
543
618
|
const defaultDirName = `worktrees/${targetBranch.split('/').join('-')}`;
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
619
|
+
let dirName;
|
|
620
|
+
if (flags.dir) {
|
|
621
|
+
dirName = flags.dir;
|
|
622
|
+
}
|
|
623
|
+
else if (flags.yes) {
|
|
624
|
+
dirName = defaultDirName;
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
const result = await inquirer.prompt([
|
|
628
|
+
{
|
|
629
|
+
type: 'input',
|
|
630
|
+
name: 'dirName',
|
|
631
|
+
message: `请输入 Worktree 目录路径 (相对于 Git 根目录, 默认: ${defaultDirName}):`,
|
|
632
|
+
default: defaultDirName,
|
|
633
|
+
},
|
|
634
|
+
]);
|
|
635
|
+
dirName = result.dirName;
|
|
636
|
+
}
|
|
552
637
|
const targetDir = path.resolve(rootDir, dirName);
|
|
553
638
|
if (fs.existsSync(targetDir)) {
|
|
554
639
|
console.error(chalk.red(`目录 ${targetDir} 已存在!`));
|
|
@@ -594,7 +679,9 @@ async function setupWorktreeEnv(rootDir, targetDir, dirName) {
|
|
|
594
679
|
}
|
|
595
680
|
}
|
|
596
681
|
}
|
|
597
|
-
async function installDependencies(targetDir) {
|
|
682
|
+
async function installDependencies(targetDir, skip = false) {
|
|
683
|
+
if (skip)
|
|
684
|
+
return;
|
|
598
685
|
if (!fs.existsSync(path.join(targetDir, 'package.json')))
|
|
599
686
|
return;
|
|
600
687
|
try {
|
|
@@ -615,7 +702,20 @@ function hasCommand(cmd) {
|
|
|
615
702
|
return false;
|
|
616
703
|
}
|
|
617
704
|
}
|
|
618
|
-
async function openInIDE(targetDir) {
|
|
705
|
+
async function openInIDE(targetDir, flags) {
|
|
706
|
+
if (flags.noEditor)
|
|
707
|
+
return;
|
|
708
|
+
if (flags.editor !== undefined) {
|
|
709
|
+
if (flags.editor === 'none' || flags.editor === '')
|
|
710
|
+
return;
|
|
711
|
+
try {
|
|
712
|
+
execSync(`${flags.editor} "${targetDir}"`, { stdio: 'ignore' });
|
|
713
|
+
}
|
|
714
|
+
catch (e) {
|
|
715
|
+
void e;
|
|
716
|
+
}
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
619
719
|
const editors = [];
|
|
620
720
|
if (hasCommand('trae'))
|
|
621
721
|
editors.push({ name: `在 Trae 中打开 (trae ${targetDir})`, value: 'trae' });
|
|
@@ -644,34 +744,63 @@ async function openInIDE(targetDir) {
|
|
|
644
744
|
void e;
|
|
645
745
|
}
|
|
646
746
|
}
|
|
647
|
-
async function deleteWorktree(ctx) {
|
|
648
|
-
const
|
|
649
|
-
const
|
|
747
|
+
async function deleteWorktree(ctx, targets = []) {
|
|
748
|
+
const { rootDir, flags } = ctx;
|
|
749
|
+
const worktrees = getWorktreeList(rootDir);
|
|
750
|
+
const choices = getDeletableWorktrees(rootDir, worktrees);
|
|
650
751
|
if (choices.length === 0) {
|
|
651
|
-
|
|
752
|
+
if (flags.json) {
|
|
753
|
+
console.info(JSON.stringify({ ok: true, data: [], message: 'No deletable worktrees' }));
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
console.warn(chalk.yellow('没有可删除的 Worktree (除了主 Worktree)'));
|
|
757
|
+
}
|
|
652
758
|
return;
|
|
653
759
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
760
|
+
let targetPaths;
|
|
761
|
+
if (targets.length > 0) {
|
|
762
|
+
// Non-interactive: resolve each target to a worktree path
|
|
763
|
+
targetPaths = [];
|
|
764
|
+
for (const key of targets) {
|
|
765
|
+
const wt = resolveWorktree(rootDir, key);
|
|
766
|
+
if (!wt) {
|
|
767
|
+
console.error(chalk.red(`未找到 worktree: ${key}`));
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
if (path.resolve(wt.path) === path.resolve(rootDir)) {
|
|
771
|
+
console.error(chalk.red(`不能删除主 worktree: ${key}`));
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
targetPaths.push(wt.path);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
// Interactive: checkbox prompt
|
|
779
|
+
const result = await inquirer.prompt([
|
|
780
|
+
{
|
|
781
|
+
type: 'checkbox',
|
|
782
|
+
name: 'targetPaths',
|
|
783
|
+
message: '请选择要删除的 Worktree:',
|
|
784
|
+
choices,
|
|
785
|
+
validate: (answer) => (answer.length > 0 ? true : '请至少选择一个'),
|
|
786
|
+
},
|
|
787
|
+
]);
|
|
788
|
+
targetPaths = result.targetPaths;
|
|
789
|
+
}
|
|
790
|
+
if (!flags.yes) {
|
|
791
|
+
const { confirmDelete } = await inquirer.prompt([
|
|
792
|
+
{
|
|
793
|
+
type: 'confirm',
|
|
794
|
+
name: 'confirmDelete',
|
|
795
|
+
message: `确定要删除这 ${targetPaths.length} 个 Worktree 吗?`,
|
|
796
|
+
default: false,
|
|
797
|
+
},
|
|
798
|
+
]);
|
|
799
|
+
if (!confirmDelete)
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
673
802
|
for (const targetPath of targetPaths) {
|
|
674
|
-
await deleteSingleWorktree(
|
|
803
|
+
await deleteSingleWorktree(rootDir, targetPath, flags);
|
|
675
804
|
}
|
|
676
805
|
}
|
|
677
806
|
function getWorktreeList(rootDir) {
|
|
@@ -690,29 +819,55 @@ function getDeletableWorktrees(rootDir, worktrees) {
|
|
|
690
819
|
return { name: `${wt.branch || 'HEAD'} (${relativePath})`, value: wt.path };
|
|
691
820
|
});
|
|
692
821
|
}
|
|
693
|
-
async function deleteSingleWorktree(rootDir, targetPath) {
|
|
822
|
+
async function deleteSingleWorktree(rootDir, targetPath, flags) {
|
|
694
823
|
try {
|
|
695
824
|
gitOrThrow(rootDir, ['worktree', 'remove', targetPath], 'WORKTREE_REMOVE');
|
|
696
|
-
|
|
825
|
+
if (flags.json) {
|
|
826
|
+
console.info(JSON.stringify({ ok: true, removed: targetPath }));
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
console.info(chalk.green(`成功删除: ${targetPath}`));
|
|
830
|
+
}
|
|
697
831
|
}
|
|
698
832
|
catch (e) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
833
|
+
if (flags.force) {
|
|
834
|
+
try {
|
|
835
|
+
gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE');
|
|
836
|
+
if (flags.json) {
|
|
837
|
+
console.info(JSON.stringify({ ok: true, removed: targetPath, forced: true }));
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
console.info(chalk.green(`成功强制删除: ${targetPath}`));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch (forceErr) {
|
|
844
|
+
if (flags.json) {
|
|
845
|
+
console.error(JSON.stringify({ ok: false, error: errMsg(forceErr), path: targetPath }));
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
713
851
|
}
|
|
714
|
-
|
|
715
|
-
console.error(chalk.red(
|
|
852
|
+
else {
|
|
853
|
+
console.error(chalk.red(`删除失败: ${errMsg(e)}`));
|
|
854
|
+
const { force } = await inquirer.prompt([
|
|
855
|
+
{
|
|
856
|
+
type: 'confirm',
|
|
857
|
+
name: 'force',
|
|
858
|
+
message: '删除失败 (可能有未提交的更改). 强制删除吗?',
|
|
859
|
+
default: false,
|
|
860
|
+
},
|
|
861
|
+
]);
|
|
862
|
+
if (!force)
|
|
863
|
+
return;
|
|
864
|
+
try {
|
|
865
|
+
gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE');
|
|
866
|
+
console.info(chalk.green(`成功强制删除: ${targetPath}`));
|
|
867
|
+
}
|
|
868
|
+
catch (forceErr) {
|
|
869
|
+
console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`));
|
|
870
|
+
}
|
|
716
871
|
}
|
|
717
872
|
}
|
|
718
873
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fatdoge/wtree",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "CLI + UI tool for managing git worktrees",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"git",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"api",
|
|
31
31
|
"src",
|
|
32
32
|
"shared",
|
|
33
|
+
"skills",
|
|
33
34
|
"index.html",
|
|
34
35
|
"vite.config.ts",
|
|
35
36
|
"tailwind.config.js",
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
"react-i18next": "^16.5.7",
|
|
69
70
|
"react-router-dom": "^7.3.0",
|
|
70
71
|
"serve-static": "^2.2.1",
|
|
72
|
+
"sonner": "^2.0.7",
|
|
71
73
|
"tailwind-merge": "^3.0.2",
|
|
72
74
|
"tailwindcss": "^3.4.17",
|
|
73
75
|
"vite": "^6.3.5",
|