@fatdoge/wtree 0.1.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 (65) hide show
  1. package/README.en.md +113 -0
  2. package/README.md +136 -0
  3. package/api/app.ts +19 -0
  4. package/api/cli/wtree.ts +809 -0
  5. package/api/core/config.ts +26 -0
  6. package/api/core/exec.ts +55 -0
  7. package/api/core/git.ts +35 -0
  8. package/api/core/id.ts +8 -0
  9. package/api/core/open.ts +58 -0
  10. package/api/core/worktree.test.ts +33 -0
  11. package/api/core/worktree.ts +72 -0
  12. package/api/createApiApp.ts +33 -0
  13. package/api/index.ts +9 -0
  14. package/api/routes/worktrees.ts +255 -0
  15. package/api/server.ts +34 -0
  16. package/api/ui/startUiDev.ts +82 -0
  17. package/dist/assets/index-D9inyPb3.js +179 -0
  18. package/dist/assets/index-W34LSHWF.css +1 -0
  19. package/dist/favicon.svg +4 -0
  20. package/dist/index.html +354 -0
  21. package/dist-node/api/app.js +17 -0
  22. package/dist-node/api/cli/wtree.js +722 -0
  23. package/dist-node/api/cli/wtui.js +722 -0
  24. package/dist-node/api/core/config.js +21 -0
  25. package/dist-node/api/core/exec.js +24 -0
  26. package/dist-node/api/core/git.js +24 -0
  27. package/dist-node/api/core/id.js +6 -0
  28. package/dist-node/api/core/open.js +51 -0
  29. package/dist-node/api/core/worktree.js +58 -0
  30. package/dist-node/api/core/worktree.test.js +30 -0
  31. package/dist-node/api/createApiApp.js +26 -0
  32. package/dist-node/api/routes/worktrees.js +213 -0
  33. package/dist-node/api/server.js +29 -0
  34. package/dist-node/api/ui/startUiDev.js +65 -0
  35. package/dist-node/shared/wtui-types.js +1 -0
  36. package/index.html +24 -0
  37. package/package.json +89 -0
  38. package/postcss.config.js +10 -0
  39. package/shared/wtui-types.ts +36 -0
  40. package/src/App.tsx +28 -0
  41. package/src/assets/react.svg +1 -0
  42. package/src/components/Button.tsx +34 -0
  43. package/src/components/Empty.tsx +8 -0
  44. package/src/components/Input.tsx +16 -0
  45. package/src/components/Modal.tsx +33 -0
  46. package/src/components/ToastHost.tsx +42 -0
  47. package/src/hooks/useTheme.ts +29 -0
  48. package/src/i18n/index.ts +22 -0
  49. package/src/i18n/locales/en.json +145 -0
  50. package/src/i18n/locales/zh.json +145 -0
  51. package/src/index.css +24 -0
  52. package/src/lib/utils.ts +6 -0
  53. package/src/main.tsx +11 -0
  54. package/src/pages/CreateWorktree.tsx +181 -0
  55. package/src/pages/HelpPage.tsx +67 -0
  56. package/src/pages/Home.tsx +3 -0
  57. package/src/pages/SettingsPage.tsx +218 -0
  58. package/src/pages/Worktrees.tsx +354 -0
  59. package/src/stores/themeStore.ts +44 -0
  60. package/src/stores/toastStore.ts +29 -0
  61. package/src/stores/worktreeStore.ts +93 -0
  62. package/src/utils/api.ts +36 -0
  63. package/src/vite-env.d.ts +1 -0
  64. package/tailwind.config.js +13 -0
  65. package/vite.config.ts +46 -0
@@ -0,0 +1,10 @@
1
+ /** WARNING: DON'T EDIT THIS FILE */
2
+ /** WARNING: DON'T EDIT THIS FILE */
3
+ /** WARNING: DON'T EDIT THIS FILE */
4
+
5
+ export default {
6
+ plugins: {
7
+ tailwindcss: {},
8
+ autoprefixer: {},
9
+ },
10
+ };
@@ -0,0 +1,36 @@
1
+ export type RepoInfo = {
2
+ rootPath: string
3
+ gitDirPath: string
4
+ }
5
+
6
+ export type WorktreeItem = {
7
+ id: string
8
+ path: string
9
+ head: string
10
+ branch?: string
11
+ isMain: boolean
12
+ isLocked: boolean
13
+ hasChanges?: boolean
14
+ }
15
+
16
+ export type CreateWorktreeRequest = {
17
+ ref: string
18
+ newBranch?: string
19
+ path: string
20
+ }
21
+
22
+ export type WtuiConfig = {
23
+ baseDir?: string
24
+ openCommand?: string
25
+ editorCommand?: string
26
+ }
27
+
28
+ export type ApiError = {
29
+ code: string
30
+ message: string
31
+ details?: string
32
+ }
33
+
34
+ export type ApiResult<T> =
35
+ | { ok: true; data: T }
36
+ | { ok: false; error: ApiError }
package/src/App.tsx ADDED
@@ -0,0 +1,28 @@
1
+ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
2
+ import { useEffect } from "react";
3
+ import Worktrees from "@/pages/Worktrees";
4
+ import CreateWorktree from "@/pages/CreateWorktree";
5
+ import SettingsPage from "@/pages/SettingsPage";
6
+ import HelpPage from "@/pages/HelpPage";
7
+ import ToastHost from "@/components/ToastHost";
8
+ import { useThemeStore } from "@/stores/themeStore";
9
+
10
+ export default function App() {
11
+ const initTheme = useThemeStore((s) => s.initTheme);
12
+
13
+ useEffect(() => {
14
+ initTheme();
15
+ }, [initTheme]);
16
+
17
+ return (
18
+ <Router>
19
+ <ToastHost />
20
+ <Routes>
21
+ <Route path="/" element={<Worktrees />} />
22
+ <Route path="/create" element={<CreateWorktree />} />
23
+ <Route path="/settings" element={<SettingsPage />} />
24
+ <Route path="/help" element={<HelpPage />} />
25
+ </Routes>
26
+ </Router>
27
+ );
28
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
@@ -0,0 +1,34 @@
1
+ import type { ButtonHTMLAttributes } from 'react'
2
+
3
+ type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
4
+ variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
5
+ size?: 'sm' | 'md'
6
+ }
7
+
8
+ export default function Button({
9
+ variant = 'primary',
10
+ size = 'md',
11
+ className = '',
12
+ ...props
13
+ }: Props) {
14
+ const baseStyle =
15
+ 'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:focus:ring-slate-700 disabled:opacity-50 disabled:cursor-not-allowed gap-2'
16
+
17
+ const variants = {
18
+ primary: 'bg-indigo-600 text-white hover:bg-indigo-700 dark:bg-indigo-500 dark:hover:bg-indigo-600',
19
+ secondary: 'bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700',
20
+ danger: 'bg-rose-600 text-white hover:bg-rose-700 dark:bg-rose-500 dark:hover:bg-rose-600',
21
+ ghost: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100',
22
+ }
23
+
24
+ const sizes = {
25
+ sm: 'text-xs px-3 py-1.5',
26
+ md: 'text-sm px-4 py-2',
27
+ lg: 'text-base px-6 py-3',
28
+ }
29
+
30
+ const cls = `${baseStyle} ${variants[variant]} ${sizes[size]} ${className}`
31
+
32
+ return <button className={cls} {...props} />
33
+ }
34
+
@@ -0,0 +1,8 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ // Empty component
4
+ export default function Empty() {
5
+ return (
6
+ <div className={cn('flex h-full items-center justify-center')}>Empty</div>
7
+ )
8
+ }
@@ -0,0 +1,16 @@
1
+ import { clsx } from 'clsx'
2
+ import type { InputHTMLAttributes } from 'react'
3
+
4
+ export default function Input({ className, ...props }: InputHTMLAttributes<HTMLInputElement>) {
5
+ return (
6
+ <input
7
+ className={clsx(
8
+ 'w-full rounded-md border border-slate-300 bg-white px-3 py-1.5 text-sm text-slate-900 placeholder:text-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500',
9
+ 'dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:placeholder:text-slate-500 dark:focus:border-indigo-500 dark:focus:ring-indigo-500',
10
+ className,
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
@@ -0,0 +1,33 @@
1
+ import { X } from 'lucide-react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ interface Props {
5
+ open: boolean
6
+ onClose: () => void
7
+ title: string
8
+ children: ReactNode
9
+ footer?: ReactNode
10
+ }
11
+
12
+ export default function Modal({ open, onClose, title, children, footer }: Props) {
13
+ if (!open) return null
14
+
15
+ return (
16
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 p-4 backdrop-blur-sm dark:bg-black/50">
17
+ <div className="w-full max-w-md rounded-xl border border-slate-200 bg-white shadow-xl dark:border-slate-800 dark:bg-slate-950">
18
+ <div className="flex items-center justify-between border-b border-slate-100 px-4 py-3 dark:border-slate-800">
19
+ <div className="font-semibold text-slate-900 dark:text-slate-100">{title}</div>
20
+ <button onClick={onClose} className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
21
+ <X className="h-4 w-4" />
22
+ </button>
23
+ </div>
24
+ <div className="p-4">{children}</div>
25
+ {footer ? (
26
+ <div className="flex justify-end gap-2 border-t border-slate-100 px-4 py-3 dark:border-slate-800">
27
+ {footer}
28
+ </div>
29
+ ) : null}
30
+ </div>
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,42 @@
1
+ import { X } from 'lucide-react'
2
+ import { useToastStore } from '@/stores/toastStore'
3
+
4
+ export default function ToastHost() {
5
+ const items = useToastStore((s) => s.items)
6
+ const remove = useToastStore((s) => s.remove)
7
+
8
+ return (
9
+ <div className="fixed right-4 top-4 z-50 flex w-[360px] max-w-[calc(100vw-2rem)] flex-col gap-2">
10
+ {items.map((t) => {
11
+ const accent =
12
+ t.type === 'success'
13
+ ? 'border-emerald-500/40 bg-emerald-500/10'
14
+ : t.type === 'error'
15
+ ? 'border-rose-500/40 bg-rose-500/10'
16
+ : 'border-sky-500/40 bg-sky-500/10'
17
+
18
+ return (
19
+ <div key={t.id} className={`rounded-lg border px-3 py-2 text-sm shadow-lg ${accent}`}>
20
+ <div className="flex items-start justify-between gap-3">
21
+ <div className="min-w-0">
22
+ <div className="truncate font-medium text-slate-100">{t.title}</div>
23
+ {t.detail ? (
24
+ <div className="mt-1 line-clamp-2 text-xs text-slate-300">{t.detail}</div>
25
+ ) : null}
26
+ </div>
27
+ <button
28
+ type="button"
29
+ onClick={() => remove(t.id)}
30
+ className="rounded p-1 text-slate-300 hover:bg-slate-800 hover:text-slate-100"
31
+ aria-label="关闭"
32
+ >
33
+ <X className="h-4 w-4" />
34
+ </button>
35
+ </div>
36
+ </div>
37
+ )
38
+ })}
39
+ </div>
40
+ )
41
+ }
42
+
@@ -0,0 +1,29 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ type Theme = 'light' | 'dark';
4
+
5
+ export function useTheme() {
6
+ const [theme, setTheme] = useState<Theme>(() => {
7
+ const savedTheme = localStorage.getItem('theme') as Theme;
8
+ if (savedTheme) {
9
+ return savedTheme;
10
+ }
11
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
12
+ });
13
+
14
+ useEffect(() => {
15
+ document.documentElement.classList.remove('light', 'dark');
16
+ document.documentElement.classList.add(theme);
17
+ localStorage.setItem('theme', theme);
18
+ }, [theme]);
19
+
20
+ const toggleTheme = () => {
21
+ setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
22
+ };
23
+
24
+ return {
25
+ theme,
26
+ toggleTheme,
27
+ isDark: theme === 'dark'
28
+ };
29
+ }
@@ -0,0 +1,22 @@
1
+ import i18n from 'i18next';
2
+ import { initReactI18next } from 'react-i18next';
3
+ import LanguageDetector from 'i18next-browser-languagedetector';
4
+
5
+ import en from './locales/en.json';
6
+ import zh from './locales/zh.json';
7
+
8
+ i18n
9
+ .use(LanguageDetector)
10
+ .use(initReactI18next)
11
+ .init({
12
+ resources: {
13
+ en: { translation: en },
14
+ zh: { translation: zh },
15
+ },
16
+ fallbackLng: 'en',
17
+ interpolation: {
18
+ escapeValue: false, // react already safes from xss
19
+ },
20
+ });
21
+
22
+ export default i18n;
@@ -0,0 +1,145 @@
1
+ {
2
+ "worktrees": {
3
+ "title": "TreeLab",
4
+ "subtitle": "Manage git worktrees in current repository via UI",
5
+ "table": {
6
+ "path": "Path",
7
+ "branch": "Branch / HEAD",
8
+ "flags": "Flags",
9
+ "actions": "Actions"
10
+ },
11
+ "empty": "No worktrees found. Make sure you started UI in a git repository.",
12
+ "details": "Details",
13
+ "detailsPanel": {
14
+ "path": "Path",
15
+ "branch": "Branch",
16
+ "main": "Main Worktree",
17
+ "yes": "Yes",
18
+ "no": "No",
19
+ "head": "HEAD"
20
+ },
21
+ "selectToView": "Select a worktree to view details.",
22
+ "help": {
23
+ "title": "Help",
24
+ "desc": "Check target path before deletion. Main worktree cannot be deleted.",
25
+ "viewCommands": "View Commands Reference"
26
+ },
27
+ "actions": {
28
+ "copy": "Copy",
29
+ "lock": "Lock",
30
+ "unlock": "Unlock",
31
+ "ide": "IDE",
32
+ "folder": "Folder",
33
+ "delete": "Delete"
34
+ },
35
+ "toast": {
36
+ "copySuccess": "Path copied",
37
+ "copyFailed": "Copy failed",
38
+ "lockSuccess": "Locked",
39
+ "lockFailed": "Lock failed",
40
+ "unlockSuccess": "Unlocked",
41
+ "unlockFailed": "Unlock failed",
42
+ "ideSuccess": "Opened in IDE",
43
+ "ideFailed": "Failed to open IDE",
44
+ "folderSuccess": "Folder opened",
45
+ "folderFailed": "Failed to open folder",
46
+ "deleteSuccess": "Deleted",
47
+ "forceDeleteSuccess": "Force deleted",
48
+ "deleteFailed": "Delete failed",
49
+ "deleteWarning": "Uncommitted changes or untracked files detected. Please confirm if you want to force delete."
50
+ },
51
+ "deleteModal": {
52
+ "title": "Confirm Deletion",
53
+ "forceTitle": "Force Delete Confirmation",
54
+ "desc": "The following worktree will be removed:",
55
+ "warning": "Uncommitted changes detected. Force deletion will lose these changes!",
56
+ "normalNote": "Git may refuse to delete if there are uncommitted changes.",
57
+ "cancel": "Cancel",
58
+ "confirm": "Delete",
59
+ "forceConfirm": "Force Delete",
60
+ "deleting": "Deleting..."
61
+ }
62
+ },
63
+ "create": {
64
+ "title": "Create Worktree",
65
+ "back": "Back",
66
+ "help": "Help",
67
+ "tabs": {
68
+ "existing": "Existing Branch/Commit",
69
+ "new": "New Branch"
70
+ },
71
+ "fields": {
72
+ "ref": "Ref (Branch name or commit)",
73
+ "refPlaceholder": "e.g. main / feature/x / 3a2f...",
74
+ "newBranch": "New Branch",
75
+ "newBranchPlaceholder": "e.g. feature/awesome",
76
+ "path": "Path (Relative to repo root or absolute path)",
77
+ "pathPlaceholder": "e.g. worktrees/feature-x"
78
+ },
79
+ "submit": "Create",
80
+ "preview": {
81
+ "title": "Will Execute",
82
+ "desc": "UI only calls local API, actual git commands are executed on your machine."
83
+ },
84
+ "result": {
85
+ "title": "Result",
86
+ "creating": "Creating...",
87
+ "success": "Created successfully",
88
+ "backToList": "Back to list",
89
+ "empty": "Results will be shown here after creation."
90
+ }
91
+ },
92
+ "settings": {
93
+ "title": "Settings",
94
+ "preferences": {
95
+ "title": "Preferences",
96
+ "theme": "Theme Mode",
97
+ "language": "Language",
98
+ "light": "Light",
99
+ "dark": "Dark",
100
+ "system": "System",
101
+ "en": "English",
102
+ "zh": "中文"
103
+ },
104
+ "cli": {
105
+ "title": "CLI Configuration",
106
+ "baseDir": "baseDir",
107
+ "baseDirPlaceholder": "Default storage location, e.g. ../",
108
+ "openCommand": "openCommand",
109
+ "openCommandPlaceholder": "Leave empty for auto-detect (open / start / xdg-open)",
110
+ "editorCommand": "editorCommand",
111
+ "editorCommandPlaceholder": "e.g. code / cursor / trae",
112
+ "reset": "Reset",
113
+ "save": "Save Config"
114
+ },
115
+ "maintenance": {
116
+ "title": "Maintenance",
117
+ "prune": "Prune Invalid Worktrees",
118
+ "pruneDesc": "Executes `git worktree prune` to remove worktree records that no longer exist."
119
+ },
120
+ "note": {
121
+ "title": "Note",
122
+ "desc": "Settings are saved in `~/.config/wtree/config.json`, UI reads and writes via local API."
123
+ },
124
+ "toast": {
125
+ "saveSuccess": "Configuration saved",
126
+ "saveFailed": "Failed to save configuration",
127
+ "pruneSuccess": "Prune completed",
128
+ "pruneFailed": "Prune failed"
129
+ }
130
+ },
131
+ "helpPage": {
132
+ "title": "Help",
133
+ "commands": "Commands Reference",
134
+ "commandsDesc": "Running without parameters will enter interactive CLI; adding --ui will start local UI.",
135
+ "faq": {
136
+ "title": "FAQ",
137
+ "q1": "Not in a git repository?",
138
+ "a1": "Run inside a repository, or use `wtree --repo /path/to/repo` to specify path.",
139
+ "q2": "Port already in use?",
140
+ "a2": "Use `wtree --ui --port 0` to let system auto-assign a port.",
141
+ "q3": "Delete failed (uncommitted changes)?",
142
+ "a3": "Force Delete is supported in both CLI and UI modes, but please note that uncommitted changes will be lost."
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,145 @@
1
+ {
2
+ "worktrees": {
3
+ "title": "TreeLab",
4
+ "subtitle": "通过 UI 管理当前仓库的 git worktree",
5
+ "table": {
6
+ "path": "路径",
7
+ "branch": "分支 / HEAD",
8
+ "flags": "标志",
9
+ "actions": "操作"
10
+ },
11
+ "empty": "未读取到 worktree。确认你在 git 仓库中启动了 UI。",
12
+ "details": "详情",
13
+ "detailsPanel": {
14
+ "path": "路径",
15
+ "branch": "分支",
16
+ "main": "主 Worktree",
17
+ "yes": "是",
18
+ "no": "否",
19
+ "head": "HEAD"
20
+ },
21
+ "selectToView": "选择一个 worktree 查看详情。",
22
+ "help": {
23
+ "title": "帮助",
24
+ "desc": "删除前确认目标路径,主 worktree 无法删除。",
25
+ "viewCommands": "查看命令速查"
26
+ },
27
+ "actions": {
28
+ "copy": "复制",
29
+ "lock": "锁定",
30
+ "unlock": "解锁",
31
+ "ide": "IDE",
32
+ "folder": "文件夹",
33
+ "delete": "删除"
34
+ },
35
+ "toast": {
36
+ "copySuccess": "已复制路径",
37
+ "copyFailed": "复制失败",
38
+ "lockSuccess": "已锁定",
39
+ "lockFailed": "锁定失败",
40
+ "unlockSuccess": "已解锁",
41
+ "unlockFailed": "解锁失败",
42
+ "ideSuccess": "已在 IDE 打开",
43
+ "ideFailed": "打开 IDE 失败",
44
+ "folderSuccess": "已打开文件夹",
45
+ "folderFailed": "打开文件夹失败",
46
+ "deleteSuccess": "已删除",
47
+ "forceDeleteSuccess": "已强制删除",
48
+ "deleteFailed": "删除失败",
49
+ "deleteWarning": "存在未提交更改或未跟踪文件。请确认是否强制删除。"
50
+ },
51
+ "deleteModal": {
52
+ "title": "确认删除",
53
+ "forceTitle": "强制删除确认",
54
+ "desc": "将移除以下 worktree:",
55
+ "warning": "检测到未提交更改。强制删除将丢失这些更改!",
56
+ "normalNote": "如果存在未提交更改,git 可能会拒绝删除。",
57
+ "cancel": "取消",
58
+ "confirm": "删除",
59
+ "forceConfirm": "强制删除",
60
+ "deleting": "删除中..."
61
+ }
62
+ },
63
+ "create": {
64
+ "title": "创建 Worktree",
65
+ "back": "返回",
66
+ "help": "帮助",
67
+ "tabs": {
68
+ "existing": "现有分支/提交",
69
+ "new": "新分支"
70
+ },
71
+ "fields": {
72
+ "ref": "Ref(分支名或 commit)",
73
+ "refPlaceholder": "例如 main / feature/x / 3a2f...",
74
+ "newBranch": "New Branch",
75
+ "newBranchPlaceholder": "例如 feature/awesome",
76
+ "path": "Path(相对 repo root 或绝对路径)",
77
+ "pathPlaceholder": "例如 worktrees/feature-x"
78
+ },
79
+ "submit": "创建",
80
+ "preview": {
81
+ "title": "将执行",
82
+ "desc": "UI 仅调用本地 API,实际执行 git 命令发生在你的机器上。"
83
+ },
84
+ "result": {
85
+ "title": "结果",
86
+ "creating": "正在创建…",
87
+ "success": "创建成功",
88
+ "backToList": "回到列表",
89
+ "empty": "提交创建后会在这里显示结果。"
90
+ }
91
+ },
92
+ "settings": {
93
+ "title": "设置",
94
+ "preferences": {
95
+ "title": "偏好设置",
96
+ "theme": "主题模式",
97
+ "language": "语言",
98
+ "light": "浅色",
99
+ "dark": "深色",
100
+ "system": "跟随系统",
101
+ "en": "English",
102
+ "zh": "中文"
103
+ },
104
+ "cli": {
105
+ "title": "CLI 配置",
106
+ "baseDir": "baseDir",
107
+ "baseDirPlaceholder": "默认存放在哪里,例如 ../",
108
+ "openCommand": "openCommand",
109
+ "openCommandPlaceholder": "留空自动检测 (open / start / xdg-open)",
110
+ "editorCommand": "editorCommand",
111
+ "editorCommandPlaceholder": "例如 code / cursor / trae",
112
+ "reset": "重置",
113
+ "save": "保存配置"
114
+ },
115
+ "maintenance": {
116
+ "title": "维护",
117
+ "prune": "清理无效 Worktree (Prune)",
118
+ "pruneDesc": "执行 `git worktree prune`,移除已不存在的 worktree 记录。"
119
+ },
120
+ "note": {
121
+ "title": "说明",
122
+ "desc": "设置保存在 `~/.config/wtree/config.json`,UI 通过本地 API 读写。"
123
+ },
124
+ "toast": {
125
+ "saveSuccess": "已保存配置",
126
+ "saveFailed": "保存失败",
127
+ "pruneSuccess": "清理完成",
128
+ "pruneFailed": "清理失败"
129
+ }
130
+ },
131
+ "helpPage": {
132
+ "title": "帮助",
133
+ "commands": "命令速查",
134
+ "commandsDesc": "默认不带参数会进入交互式命令行;加上 --ui 会启动本地 UI。",
135
+ "faq": {
136
+ "title": "常见问题",
137
+ "q1": "不在 git 仓库目录?",
138
+ "a1": "在仓库内运行,或用 `wtree --repo /path/to/repo` 指定路径。",
139
+ "q2": "端口被占用?",
140
+ "a2": "使用 `wtree --ui --port 0` 让系统自动分配端口。",
141
+ "q3": "删除失败(有未提交更改)?",
142
+ "a3": "在 CLI 模式和 UI 模式下均已支持强制删除(Force Delete),但请注意未提交的更改会丢失。"
143
+ }
144
+ }
145
+ }
package/src/index.css ADDED
@@ -0,0 +1,24 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
7
+ line-height: 1.5;
8
+ font-weight: 400;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ html,
17
+ body,
18
+ #root {
19
+ height: 100%;
20
+ }
21
+
22
+ body {
23
+ @apply bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-200;
24
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,11 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+ import './i18n'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>,
11
+ )