prjct-cli 0.18.2 → 0.20.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/CHANGELOG.md +82 -0
- package/CLAUDE.md +74 -211
- package/core/agentic/prompt-builder.ts +3 -7
- package/core/command-registry/optional-commands.ts +0 -20
- package/core/infrastructure/command-installer/command-installer.ts +8 -1
- package/core/infrastructure/command-installer/global-config.ts +31 -1
- package/core/infrastructure/command-installer/index.ts +1 -1
- package/core/infrastructure/setup.ts +3 -0
- package/package.json +3 -17
- package/templates/agentic/agents/uxui.md +210 -0
- package/templates/commands/bug.md +219 -41
- package/templates/commands/done.md +57 -258
- package/templates/commands/feature.md +368 -80
- package/templates/commands/now.md +72 -277
- package/templates/commands/ship.md +167 -246
- package/templates/commands/sync.md +62 -3
- package/templates/commands/test.md +160 -20
- package/templates/global/CLAUDE.md +40 -205
- package/templates/global/docs/agents.md +88 -0
- package/templates/global/docs/architecture.md +103 -0
- package/templates/global/docs/commands.md +98 -0
- package/templates/global/docs/validation.md +95 -0
- package/bin/dev.js +0 -216
- package/bin/serve.js +0 -361
- package/packages/web/README.md +0 -36
- package/packages/web/app/api/claude/sessions/route.ts +0 -44
- package/packages/web/app/api/claude/status/route.ts +0 -34
- package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
- package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
- package/packages/web/app/api/projects/[id]/route.ts +0 -29
- package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
- package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
- package/packages/web/app/api/projects/route.ts +0 -16
- package/packages/web/app/api/sessions/current/route.ts +0 -132
- package/packages/web/app/api/sessions/history/route.ts +0 -204
- package/packages/web/app/error.tsx +0 -34
- package/packages/web/app/favicon.ico +0 -0
- package/packages/web/app/globals.css +0 -198
- package/packages/web/app/layout.tsx +0 -53
- package/packages/web/app/loading.tsx +0 -7
- package/packages/web/app/not-found.tsx +0 -25
- package/packages/web/app/page.tsx +0 -12
- package/packages/web/app/project/[id]/code/layout.tsx +0 -18
- package/packages/web/app/project/[id]/code/page.tsx +0 -408
- package/packages/web/app/project/[id]/error.tsx +0 -41
- package/packages/web/app/project/[id]/loading.tsx +0 -9
- package/packages/web/app/project/[id]/not-found.tsx +0 -27
- package/packages/web/app/project/[id]/page.tsx +0 -384
- package/packages/web/app/project/[id]/reports/page.tsx +0 -59
- package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
- package/packages/web/app/sessions/page.tsx +0 -165
- package/packages/web/app/settings/page.tsx +0 -151
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
- package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
- package/packages/web/components/ActivityTimeline/index.ts +0 -2
- package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
- package/packages/web/components/AgentsCard/index.ts +0 -2
- package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
- package/packages/web/components/AppSidebar/index.ts +0 -1
- package/packages/web/components/BackLink/BackLink.tsx +0 -18
- package/packages/web/components/BackLink/BackLink.types.ts +0 -5
- package/packages/web/components/BackLink/index.ts +0 -2
- package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
- package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
- package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
- package/packages/web/components/BentoCard/index.ts +0 -2
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
- package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
- package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
- package/packages/web/components/BentoGrid/index.ts +0 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
- package/packages/web/components/BlockersCard/index.ts +0 -2
- package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
- package/packages/web/components/CommandBar/index.ts +0 -1
- package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
- package/packages/web/components/CommandButton/index.ts +0 -1
- package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
- package/packages/web/components/ConnectionStatus/index.ts +0 -1
- package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
- package/packages/web/components/DashboardContent/index.ts +0 -1
- package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
- package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
- package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
- package/packages/web/components/DateGroup/index.ts +0 -2
- package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
- package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
- package/packages/web/components/EmptyState/index.ts +0 -2
- package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
- package/packages/web/components/EventRow/EventRow.tsx +0 -49
- package/packages/web/components/EventRow/EventRow.types.ts +0 -7
- package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
- package/packages/web/components/EventRow/index.ts +0 -2
- package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
- package/packages/web/components/ExpandButton/index.ts +0 -2
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
- package/packages/web/components/HealthGradientBackground/index.ts +0 -2
- package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
- package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
- package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
- package/packages/web/components/HeroSection/hooks/index.ts +0 -2
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
- package/packages/web/components/HeroSection/index.ts +0 -2
- package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
- package/packages/web/components/IdeasCard/index.ts +0 -2
- package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
- package/packages/web/components/InsightMessage/index.ts +0 -2
- package/packages/web/components/Logo/Logo.tsx +0 -65
- package/packages/web/components/Logo/index.ts +0 -1
- package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
- package/packages/web/components/MarkdownContent/index.ts +0 -1
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
- package/packages/web/components/MasonryGrid/index.ts +0 -1
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
- package/packages/web/components/MomentumWidget/index.ts +0 -2
- package/packages/web/components/NowCard/NowCard.tsx +0 -118
- package/packages/web/components/NowCard/NowCard.types.ts +0 -16
- package/packages/web/components/NowCard/index.ts +0 -2
- package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
- package/packages/web/components/PageHeader/index.ts +0 -1
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
- package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
- package/packages/web/components/ProgressRing/index.ts +0 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
- package/packages/web/components/ProjectAvatar/index.ts +0 -1
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
- package/packages/web/components/ProjectColorDot/index.ts +0 -1
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
- package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
- package/packages/web/components/Providers/Providers.tsx +0 -48
- package/packages/web/components/Providers/index.ts +0 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
- package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
- package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
- package/packages/web/components/QueueCard/index.ts +0 -2
- package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
- package/packages/web/components/RecoverCard/index.ts +0 -2
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
- package/packages/web/components/RoadmapCard/index.ts +0 -2
- package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
- package/packages/web/components/ShipsCard/index.ts +0 -2
- package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
- package/packages/web/components/SparklineChart/index.ts +0 -2
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
- package/packages/web/components/StatsMasonry/index.ts +0 -1
- package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
- package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
- package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
- package/packages/web/components/StreakCard/index.ts +0 -2
- package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
- package/packages/web/components/TasksCounter/index.ts +0 -2
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
- package/packages/web/components/TechStackBadges/index.ts +0 -1
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
- package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
- package/packages/web/components/TerminalDock/index.ts +0 -2
- package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
- package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
- package/packages/web/components/TerminalTabs/index.ts +0 -1
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
- package/packages/web/components/VelocityBadge/index.ts +0 -2
- package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
- package/packages/web/components/VelocityCard/index.ts +0 -2
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
- package/packages/web/components/WeeklyReports/index.ts +0 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
- package/packages/web/components/WeeklySparkline/index.ts +0 -2
- package/packages/web/components/charts/SessionsChart.tsx +0 -175
- package/packages/web/components/ui/alert-dialog.tsx +0 -157
- package/packages/web/components/ui/badge.tsx +0 -46
- package/packages/web/components/ui/button.tsx +0 -60
- package/packages/web/components/ui/card.tsx +0 -92
- package/packages/web/components/ui/chart.tsx +0 -385
- package/packages/web/components/ui/dialog.tsx +0 -143
- package/packages/web/components/ui/drawer.tsx +0 -135
- package/packages/web/components/ui/dropdown-menu.tsx +0 -257
- package/packages/web/components/ui/input.tsx +0 -21
- package/packages/web/components/ui/scroll-area.tsx +0 -58
- package/packages/web/components/ui/select.tsx +0 -187
- package/packages/web/components/ui/sheet.tsx +0 -139
- package/packages/web/components/ui/tabs.tsx +0 -66
- package/packages/web/components/ui/tooltip.tsx +0 -61
- package/packages/web/components.json +0 -22
- package/packages/web/context/GlobalTerminalContext.tsx +0 -538
- package/packages/web/context/TerminalContext.tsx +0 -45
- package/packages/web/context/TerminalTabsContext.tsx +0 -181
- package/packages/web/eslint.config.mjs +0 -18
- package/packages/web/hooks/useClaudeTerminal.ts +0 -425
- package/packages/web/hooks/useProjectStats.ts +0 -93
- package/packages/web/hooks/useProjects.ts +0 -73
- package/packages/web/lib/actions/projects.ts +0 -15
- package/packages/web/lib/commands.ts +0 -81
- package/packages/web/lib/format.ts +0 -23
- package/packages/web/lib/generate-week-report.ts +0 -285
- package/packages/web/lib/parse-prjct-files.ts +0 -1123
- package/packages/web/lib/project-colors.ts +0 -58
- package/packages/web/lib/projects.ts +0 -506
- package/packages/web/lib/pty.ts +0 -101
- package/packages/web/lib/query-config.ts +0 -44
- package/packages/web/lib/services/index.ts +0 -9
- package/packages/web/lib/services/projects.server.ts +0 -66
- package/packages/web/lib/services/stats.server.ts +0 -562
- package/packages/web/lib/unified-loader.ts +0 -396
- package/packages/web/lib/utils.ts +0 -6
- package/packages/web/next-env.d.ts +0 -6
- package/packages/web/next.config.ts +0 -7
- package/packages/web/package.json +0 -57
- package/packages/web/postcss.config.mjs +0 -7
- package/packages/web/public/file.svg +0 -1
- package/packages/web/public/globe.svg +0 -1
- package/packages/web/public/next.svg +0 -1
- package/packages/web/public/vercel.svg +0 -1
- package/packages/web/public/window.svg +0 -1
- package/packages/web/server.ts +0 -312
- package/packages/web/tsconfig.json +0 -34
- package/templates/commands/serve.md +0 -121
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Badge } from '@/components/ui/badge'
|
|
2
|
-
|
|
3
|
-
interface TechStackBadgesProps {
|
|
4
|
-
techStack: string[] | Record<string, string[]> | undefined
|
|
5
|
-
max?: number
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function normalizeTechStack(techStack: string[] | Record<string, string[]> | undefined): string[] {
|
|
9
|
-
if (!techStack) return []
|
|
10
|
-
if (Array.isArray(techStack)) return techStack
|
|
11
|
-
// Handle object format: { languages: [...], frameworks: [...] }
|
|
12
|
-
return Object.values(techStack).flat()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function TechStackBadges({ techStack, max = 4 }: TechStackBadgesProps) {
|
|
16
|
-
const normalized = normalizeTechStack(techStack)
|
|
17
|
-
if (normalized.length === 0) return null
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className="flex gap-1">
|
|
21
|
-
{normalized.slice(0, max).map((tech) => (
|
|
22
|
-
<Badge key={tech} variant="secondary" className="text-xs px-1.5 py-0">
|
|
23
|
-
{tech}
|
|
24
|
-
</Badge>
|
|
25
|
-
))}
|
|
26
|
-
</div>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { TechStackBadges } from './TechStackBadges'
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { ChevronUp } from 'lucide-react'
|
|
4
|
-
import { cn } from '@/lib/utils'
|
|
5
|
-
|
|
6
|
-
interface DockToggleTabProps {
|
|
7
|
-
sessionCount: number
|
|
8
|
-
connectedCount: number
|
|
9
|
-
onClick: () => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function DockToggleTab({ sessionCount, connectedCount, onClick }: DockToggleTabProps) {
|
|
13
|
-
const hasActiveSessions = sessionCount > 0
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<button
|
|
17
|
-
onClick={onClick}
|
|
18
|
-
className={cn(
|
|
19
|
-
'fixed bottom-0 left-1/2 -translate-x-1/2 z-40',
|
|
20
|
-
'flex items-center gap-2 px-4 py-2 rounded-t-lg',
|
|
21
|
-
'bg-orange-500 hover:bg-orange-600 text-white',
|
|
22
|
-
'shadow-lg transition-all duration-200',
|
|
23
|
-
'hover:-translate-y-0.5'
|
|
24
|
-
)}
|
|
25
|
-
>
|
|
26
|
-
<ChevronUp className="w-4 h-4" />
|
|
27
|
-
</button>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TerminalDock v3.1
|
|
5
|
-
* - Drawer with native overlay when >50% (respects sidebar)
|
|
6
|
-
* - Commands on left side inside drawer
|
|
7
|
-
* - Chrome-style tabs
|
|
8
|
-
* - Rename tabs via double-click
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
12
|
-
import { usePathname, useParams } from 'next/navigation'
|
|
13
|
-
import { useGlobalTerminal } from '@/context/GlobalTerminalContext'
|
|
14
|
-
import { TerminalDockTab } from './TerminalDockTab'
|
|
15
|
-
import { TerminalTabBar } from './TerminalTabBar'
|
|
16
|
-
import { DockToggleTab } from './DockToggleTab'
|
|
17
|
-
import { ProjectSelectorModal } from '@/components/ProjectSelectorModal'
|
|
18
|
-
import { CommandBar } from '@/components/CommandBar'
|
|
19
|
-
import { cn } from '@/lib/utils'
|
|
20
|
-
import { Minus, SplitSquareHorizontal } from 'lucide-react'
|
|
21
|
-
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'
|
|
22
|
-
import {
|
|
23
|
-
Drawer,
|
|
24
|
-
DrawerContent,
|
|
25
|
-
DrawerTitle,
|
|
26
|
-
} from '@/components/ui/drawer'
|
|
27
|
-
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
|
|
28
|
-
import {
|
|
29
|
-
Panel,
|
|
30
|
-
PanelGroup,
|
|
31
|
-
PanelResizeHandle,
|
|
32
|
-
} from 'react-resizable-panels'
|
|
33
|
-
|
|
34
|
-
// Threshold for showing drawer with overlay (50% of viewport)
|
|
35
|
-
const DRAWER_THRESHOLD = 0.5
|
|
36
|
-
|
|
37
|
-
export function TerminalDock() {
|
|
38
|
-
const pathname = usePathname()
|
|
39
|
-
const params = useParams()
|
|
40
|
-
const {
|
|
41
|
-
isDockOpen,
|
|
42
|
-
dockHeight,
|
|
43
|
-
isFullScreen,
|
|
44
|
-
isSplitEnabled,
|
|
45
|
-
setDockHeight,
|
|
46
|
-
openDock,
|
|
47
|
-
closeDock,
|
|
48
|
-
setSplitEnabled,
|
|
49
|
-
projectSessions,
|
|
50
|
-
activeSessionId,
|
|
51
|
-
secondActiveSessionId,
|
|
52
|
-
switchSession,
|
|
53
|
-
closeSession,
|
|
54
|
-
createSessionForProject,
|
|
55
|
-
sendCommandToActive,
|
|
56
|
-
getActiveSession,
|
|
57
|
-
getAllSessions,
|
|
58
|
-
getLeftPanelSessions,
|
|
59
|
-
getRightPanelSessions,
|
|
60
|
-
getTotalSessionCount,
|
|
61
|
-
getConnectedSessionCount,
|
|
62
|
-
updateSession,
|
|
63
|
-
} = useGlobalTerminal()
|
|
64
|
-
|
|
65
|
-
const dockRef = useRef<HTMLDivElement>(null)
|
|
66
|
-
const [isResizing, setIsResizing] = useState(false)
|
|
67
|
-
const [showProjectSelector, setShowProjectSelector] = useState(false)
|
|
68
|
-
const [projectSelectorPanel, setProjectSelectorPanel] = useState<'left' | 'right'>('left')
|
|
69
|
-
const [viewportHeight, setViewportHeight] = useState(800)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Update viewport height on mount and resize
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
const updateViewport = () => setViewportHeight(window.innerHeight)
|
|
75
|
-
updateViewport()
|
|
76
|
-
window.addEventListener('resize', updateViewport)
|
|
77
|
-
return () => window.removeEventListener('resize', updateViewport)
|
|
78
|
-
}, [])
|
|
79
|
-
|
|
80
|
-
// Get all sessions
|
|
81
|
-
const allSessions = getAllSessions()
|
|
82
|
-
const activeSession = getActiveSession()
|
|
83
|
-
const isConnected = activeSession?.isConnected ?? false
|
|
84
|
-
const totalSessions = getTotalSessionCount()
|
|
85
|
-
const connectedSessions = getConnectedSessionCount()
|
|
86
|
-
|
|
87
|
-
// Always use drawer mode
|
|
88
|
-
const useDrawerMode = true
|
|
89
|
-
|
|
90
|
-
// Hide dock on code page (full-screen terminal)
|
|
91
|
-
const isCodePage = pathname?.includes('/code')
|
|
92
|
-
|
|
93
|
-
// Get current project context from URL
|
|
94
|
-
const currentProjectId = params?.id as string | undefined
|
|
95
|
-
|
|
96
|
-
// Resize handlers (only for non-drawer mode)
|
|
97
|
-
const startResize = useCallback((e: React.MouseEvent) => {
|
|
98
|
-
e.preventDefault()
|
|
99
|
-
setIsResizing(true)
|
|
100
|
-
}, [])
|
|
101
|
-
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!isResizing) return
|
|
104
|
-
|
|
105
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
106
|
-
const newHeight = window.innerHeight - e.clientY
|
|
107
|
-
setDockHeight(newHeight)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const handleMouseUp = () => {
|
|
111
|
-
setIsResizing(false)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
document.addEventListener('mousemove', handleMouseMove)
|
|
115
|
-
document.addEventListener('mouseup', handleMouseUp)
|
|
116
|
-
|
|
117
|
-
return () => {
|
|
118
|
-
document.removeEventListener('mousemove', handleMouseMove)
|
|
119
|
-
document.removeEventListener('mouseup', handleMouseUp)
|
|
120
|
-
}
|
|
121
|
-
}, [isResizing, setDockHeight])
|
|
122
|
-
|
|
123
|
-
// Handle new terminal - creates session directly in the specified panel
|
|
124
|
-
const handleNewTerminal = useCallback((panel: 'left' | 'right' = 'left') => {
|
|
125
|
-
if (currentProjectId) {
|
|
126
|
-
const project = projectSessions.get(currentProjectId)
|
|
127
|
-
if (project) {
|
|
128
|
-
createSessionForProject(project.projectId, project.projectName, project.projectPath, panel)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
setProjectSelectorPanel(panel)
|
|
133
|
-
setShowProjectSelector(true)
|
|
134
|
-
}, [currentProjectId, projectSessions, createSessionForProject])
|
|
135
|
-
|
|
136
|
-
// Handle project selection from modal
|
|
137
|
-
const handleSelectProject = useCallback((projectId: string, projectName: string, projectPath: string) => {
|
|
138
|
-
createSessionForProject(projectId, projectName, projectPath, projectSelectorPanel)
|
|
139
|
-
setShowProjectSelector(false)
|
|
140
|
-
}, [createSessionForProject, projectSelectorPanel])
|
|
141
|
-
|
|
142
|
-
// Handle command click
|
|
143
|
-
const handleCommand = useCallback((cmd: string) => {
|
|
144
|
-
sendCommandToActive(cmd)
|
|
145
|
-
}, [sendCommandToActive])
|
|
146
|
-
|
|
147
|
-
// Handle close session
|
|
148
|
-
const handleCloseSession = useCallback((sessionId: string) => {
|
|
149
|
-
const disconnectKey = `terminal_disconnect_${sessionId}`
|
|
150
|
-
const disconnectFn = (window as unknown as Record<string, () => void>)[disconnectKey]
|
|
151
|
-
if (disconnectFn) {
|
|
152
|
-
disconnectFn()
|
|
153
|
-
}
|
|
154
|
-
closeSession(sessionId)
|
|
155
|
-
}, [closeSession])
|
|
156
|
-
|
|
157
|
-
// Handle rename session
|
|
158
|
-
const handleRenameSession = useCallback((sessionId: string, newLabel: string) => {
|
|
159
|
-
updateSession(sessionId, { label: newLabel })
|
|
160
|
-
}, [updateSession])
|
|
161
|
-
|
|
162
|
-
// Don't render if on code page (full-screen mode)
|
|
163
|
-
if (isCodePage || isFullScreen) {
|
|
164
|
-
return null
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Show toggle tab when dock is closed
|
|
168
|
-
if (!isDockOpen) {
|
|
169
|
-
return (
|
|
170
|
-
<DockToggleTab
|
|
171
|
-
sessionCount={totalSessions}
|
|
172
|
-
connectedCount={connectedSessions}
|
|
173
|
-
onClick={openDock}
|
|
174
|
-
/>
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// Terminal content (sessions)
|
|
180
|
-
const TerminalContent = (
|
|
181
|
-
<div className="flex-1 relative overflow-hidden">
|
|
182
|
-
{allSessions.length === 0 ? (
|
|
183
|
-
<div className="flex flex-col items-center justify-center h-full text-muted-foreground text-sm gap-2">
|
|
184
|
-
<span>No terminal sessions</span>
|
|
185
|
-
<button
|
|
186
|
-
onClick={() => handleNewTerminal('left')}
|
|
187
|
-
className="text-muted-foreground hover:text-foreground underline"
|
|
188
|
-
>
|
|
189
|
-
Open a terminal
|
|
190
|
-
</button>
|
|
191
|
-
</div>
|
|
192
|
-
) : isSplitEnabled ? (
|
|
193
|
-
<PanelGroup direction="horizontal">
|
|
194
|
-
<Panel defaultSize={50} minSize={20}>
|
|
195
|
-
<div className="h-full flex flex-col">
|
|
196
|
-
<div className="border-b border-border bg-muted/30">
|
|
197
|
-
<TerminalTabBar
|
|
198
|
-
sessions={getLeftPanelSessions()}
|
|
199
|
-
activeSessionId={activeSessionId}
|
|
200
|
-
onSwitchSession={(id) => switchSession(id, 'left')}
|
|
201
|
-
onCloseSession={handleCloseSession}
|
|
202
|
-
onNewTerminal={() => handleNewTerminal('left')}
|
|
203
|
-
onRenameSession={handleRenameSession}
|
|
204
|
-
/>
|
|
205
|
-
</div>
|
|
206
|
-
<div className="flex-1 relative">
|
|
207
|
-
{getLeftPanelSessions().length > 0 ? (
|
|
208
|
-
getLeftPanelSessions().map((session) => (
|
|
209
|
-
<TerminalDockTab
|
|
210
|
-
key={session.id}
|
|
211
|
-
session={session}
|
|
212
|
-
isActive={session.id === activeSessionId}
|
|
213
|
-
/>
|
|
214
|
-
))
|
|
215
|
-
) : (
|
|
216
|
-
<div className="absolute inset-0 flex items-center justify-center bg-card/95 text-muted-foreground text-sm">
|
|
217
|
-
<button
|
|
218
|
-
onClick={() => handleNewTerminal('left')}
|
|
219
|
-
className="text-muted-foreground hover:text-foreground underline"
|
|
220
|
-
>
|
|
221
|
-
Open terminal in left panel
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
</Panel>
|
|
228
|
-
|
|
229
|
-
<PanelResizeHandle className="w-1 bg-border hover:bg-muted transition-colors" />
|
|
230
|
-
|
|
231
|
-
<Panel defaultSize={50} minSize={20}>
|
|
232
|
-
<div className="h-full flex flex-col">
|
|
233
|
-
<div className="border-b border-border bg-muted/30">
|
|
234
|
-
<TerminalTabBar
|
|
235
|
-
sessions={getRightPanelSessions()}
|
|
236
|
-
activeSessionId={secondActiveSessionId}
|
|
237
|
-
onSwitchSession={(id) => switchSession(id, 'right')}
|
|
238
|
-
onCloseSession={handleCloseSession}
|
|
239
|
-
onNewTerminal={() => handleNewTerminal('right')}
|
|
240
|
-
onRenameSession={handleRenameSession}
|
|
241
|
-
/>
|
|
242
|
-
</div>
|
|
243
|
-
<div className="flex-1 relative">
|
|
244
|
-
{getRightPanelSessions().length > 0 ? (
|
|
245
|
-
getRightPanelSessions().map((session) => (
|
|
246
|
-
<TerminalDockTab
|
|
247
|
-
key={`right-${session.id}`}
|
|
248
|
-
session={session}
|
|
249
|
-
isActive={session.id === secondActiveSessionId}
|
|
250
|
-
/>
|
|
251
|
-
))
|
|
252
|
-
) : (
|
|
253
|
-
<div className="absolute inset-0 flex items-center justify-center bg-card/95 text-muted-foreground text-sm">
|
|
254
|
-
<button
|
|
255
|
-
onClick={() => handleNewTerminal('right')}
|
|
256
|
-
className="text-muted-foreground hover:text-foreground underline"
|
|
257
|
-
>
|
|
258
|
-
Open terminal in right panel
|
|
259
|
-
</button>
|
|
260
|
-
</div>
|
|
261
|
-
)}
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
</Panel>
|
|
265
|
-
</PanelGroup>
|
|
266
|
-
) : (
|
|
267
|
-
<div className="h-full flex flex-col">
|
|
268
|
-
<div className="border-b border-border bg-muted/30">
|
|
269
|
-
<TerminalTabBar
|
|
270
|
-
sessions={allSessions}
|
|
271
|
-
activeSessionId={activeSessionId}
|
|
272
|
-
onSwitchSession={(id) => switchSession(id)}
|
|
273
|
-
onCloseSession={handleCloseSession}
|
|
274
|
-
onNewTerminal={() => handleNewTerminal('left')}
|
|
275
|
-
onRenameSession={handleRenameSession}
|
|
276
|
-
/>
|
|
277
|
-
</div>
|
|
278
|
-
<div className="flex-1 relative">
|
|
279
|
-
{allSessions.map((session) => (
|
|
280
|
-
<TerminalDockTab
|
|
281
|
-
key={session.id}
|
|
282
|
-
session={session}
|
|
283
|
-
isActive={session.id === activeSessionId}
|
|
284
|
-
/>
|
|
285
|
-
))}
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
)}
|
|
289
|
-
</div>
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
// Header with split/minimize (right side)
|
|
293
|
-
const HeaderActions = (
|
|
294
|
-
<div className="flex items-center gap-1 px-2">
|
|
295
|
-
<Tooltip>
|
|
296
|
-
<TooltipTrigger asChild>
|
|
297
|
-
<button
|
|
298
|
-
onClick={() => setSplitEnabled(!isSplitEnabled)}
|
|
299
|
-
className={cn(
|
|
300
|
-
'p-1.5 rounded transition-colors',
|
|
301
|
-
isSplitEnabled
|
|
302
|
-
? 'bg-accent text-accent-foreground'
|
|
303
|
-
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
304
|
-
)}
|
|
305
|
-
>
|
|
306
|
-
<SplitSquareHorizontal className="w-4 h-4" />
|
|
307
|
-
</button>
|
|
308
|
-
</TooltipTrigger>
|
|
309
|
-
<TooltipContent>{isSplitEnabled ? 'Single view' : 'Split view'}</TooltipContent>
|
|
310
|
-
</Tooltip>
|
|
311
|
-
|
|
312
|
-
<Tooltip>
|
|
313
|
-
<TooltipTrigger asChild>
|
|
314
|
-
<button
|
|
315
|
-
onClick={closeDock}
|
|
316
|
-
className="p-1.5 rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
317
|
-
>
|
|
318
|
-
<Minus className="w-4 h-4" />
|
|
319
|
-
</button>
|
|
320
|
-
</TooltipTrigger>
|
|
321
|
-
<TooltipContent>Minimize</TooltipContent>
|
|
322
|
-
</Tooltip>
|
|
323
|
-
</div>
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
return (
|
|
327
|
-
<TooltipProvider>
|
|
328
|
-
{useDrawerMode ? (
|
|
329
|
-
// Simple drawer
|
|
330
|
-
<Drawer open={isDockOpen} onOpenChange={(open) => !open && closeDock()}>
|
|
331
|
-
<DrawerContent className="h-[60vh] flex flex-col">
|
|
332
|
-
<VisuallyHidden>
|
|
333
|
-
<DrawerTitle>Terminal</DrawerTitle>
|
|
334
|
-
</VisuallyHidden>
|
|
335
|
-
|
|
336
|
-
{/* Header: commands left, actions right */}
|
|
337
|
-
<div className="flex items-center justify-between border-b border-border bg-card/95 shrink-0">
|
|
338
|
-
<CommandBar isConnected={isConnected} onCommand={handleCommand} />
|
|
339
|
-
{HeaderActions}
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
{TerminalContent}
|
|
343
|
-
</DrawerContent>
|
|
344
|
-
</Drawer>
|
|
345
|
-
) : (
|
|
346
|
-
// Regular dock when <=50%
|
|
347
|
-
<div
|
|
348
|
-
ref={dockRef}
|
|
349
|
-
className={cn(
|
|
350
|
-
'relative flex flex-col shrink-0',
|
|
351
|
-
'bg-card border-t border-border',
|
|
352
|
-
isResizing && 'select-none'
|
|
353
|
-
)}
|
|
354
|
-
style={{ height: dockHeight }}
|
|
355
|
-
>
|
|
356
|
-
{/* Resize Handle */}
|
|
357
|
-
<div
|
|
358
|
-
className={cn(
|
|
359
|
-
'absolute top-0 left-0 right-0 h-2 cursor-ns-resize z-10',
|
|
360
|
-
'hover:bg-muted transition-colors',
|
|
361
|
-
'before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-border',
|
|
362
|
-
isResizing && 'bg-muted'
|
|
363
|
-
)}
|
|
364
|
-
onMouseDown={startResize}
|
|
365
|
-
>
|
|
366
|
-
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-12 h-1 rounded-full bg-muted-foreground/30" />
|
|
367
|
-
</div>
|
|
368
|
-
|
|
369
|
-
{/* Header: commands left, actions right */}
|
|
370
|
-
<div className="flex items-center justify-between border-b border-border bg-card/95 shrink-0 mt-2">
|
|
371
|
-
<CommandBar isConnected={isConnected} onCommand={handleCommand} />
|
|
372
|
-
{HeaderActions}
|
|
373
|
-
</div>
|
|
374
|
-
|
|
375
|
-
{TerminalContent}
|
|
376
|
-
</div>
|
|
377
|
-
)}
|
|
378
|
-
|
|
379
|
-
<ProjectSelectorModal
|
|
380
|
-
isOpen={showProjectSelector}
|
|
381
|
-
onClose={() => setShowProjectSelector(false)}
|
|
382
|
-
onSelectProject={handleSelectProject}
|
|
383
|
-
/>
|
|
384
|
-
</TooltipProvider>
|
|
385
|
-
)
|
|
386
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TerminalDockTab - Single terminal instance for the dock
|
|
5
|
-
* With ResizeObserver for proper fit on resize
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useEffect, useRef, useCallback } from 'react'
|
|
9
|
-
import { useClaudeTerminal } from '@/hooks/useClaudeTerminal'
|
|
10
|
-
import { useGlobalTerminal, type GlobalTerminalSession } from '@/context/GlobalTerminalContext'
|
|
11
|
-
|
|
12
|
-
interface TerminalDockTabProps {
|
|
13
|
-
session: GlobalTerminalSession
|
|
14
|
-
isActive: boolean
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function TerminalDockTab({ session, isActive }: TerminalDockTabProps) {
|
|
18
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
19
|
-
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
20
|
-
const hasInitializedRef = useRef(false)
|
|
21
|
-
const hasConnectedRef = useRef(false)
|
|
22
|
-
const fitTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
23
|
-
const { updateSession, registerSendInput, registerFocusTerminal, dockHeight } = useGlobalTerminal()
|
|
24
|
-
|
|
25
|
-
const handleConnect = useCallback(() => {
|
|
26
|
-
updateSession(session.id, { isConnected: true, isLoading: false })
|
|
27
|
-
}, [session.id, updateSession])
|
|
28
|
-
|
|
29
|
-
const handleDisconnect = useCallback(() => {
|
|
30
|
-
updateSession(session.id, { isConnected: false, isLoading: false })
|
|
31
|
-
}, [session.id, updateSession])
|
|
32
|
-
|
|
33
|
-
const handleError = useCallback((error: string) => {
|
|
34
|
-
console.error(`[TerminalDock ${session.id}] Error:`, error)
|
|
35
|
-
updateSession(session.id, { isLoading: false })
|
|
36
|
-
}, [session.id, updateSession])
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
initTerminal,
|
|
40
|
-
connect,
|
|
41
|
-
disconnect,
|
|
42
|
-
sendInput,
|
|
43
|
-
focusTerminal,
|
|
44
|
-
fit,
|
|
45
|
-
} = useClaudeTerminal({
|
|
46
|
-
sessionId: session.id,
|
|
47
|
-
projectDir: session.projectPath,
|
|
48
|
-
onConnect: handleConnect,
|
|
49
|
-
onDisconnect: handleDisconnect,
|
|
50
|
-
onError: handleError,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Debounced fit function
|
|
54
|
-
const debouncedFit = useCallback(() => {
|
|
55
|
-
if (fitTimeoutRef.current) {
|
|
56
|
-
clearTimeout(fitTimeoutRef.current)
|
|
57
|
-
}
|
|
58
|
-
fitTimeoutRef.current = setTimeout(() => {
|
|
59
|
-
if (isActive && hasInitializedRef.current) {
|
|
60
|
-
fit()
|
|
61
|
-
}
|
|
62
|
-
}, 50)
|
|
63
|
-
}, [fit, isActive])
|
|
64
|
-
|
|
65
|
-
// Initialize terminal AND connect - only once
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (containerRef.current && !hasInitializedRef.current) {
|
|
68
|
-
hasInitializedRef.current = true
|
|
69
|
-
|
|
70
|
-
initTerminal(containerRef.current).then(() => {
|
|
71
|
-
if (!hasConnectedRef.current) {
|
|
72
|
-
hasConnectedRef.current = true
|
|
73
|
-
connect()
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
}, []) // Empty deps - run only on mount
|
|
78
|
-
|
|
79
|
-
// Re-fit terminal when tab becomes active or dock height changes
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (isActive && hasInitializedRef.current) {
|
|
82
|
-
requestAnimationFrame(() => {
|
|
83
|
-
fit()
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
}, [isActive, fit, dockHeight])
|
|
87
|
-
|
|
88
|
-
// ResizeObserver for container size changes
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (!wrapperRef.current) return
|
|
91
|
-
|
|
92
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
93
|
-
debouncedFit()
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
resizeObserver.observe(wrapperRef.current)
|
|
97
|
-
|
|
98
|
-
return () => {
|
|
99
|
-
resizeObserver.disconnect()
|
|
100
|
-
if (fitTimeoutRef.current) {
|
|
101
|
-
clearTimeout(fitTimeoutRef.current)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}, [debouncedFit])
|
|
105
|
-
|
|
106
|
-
// Register sendInput and focusTerminal for this session
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
registerSendInput(session.id, sendInput)
|
|
109
|
-
registerFocusTerminal(session.id, focusTerminal)
|
|
110
|
-
}, [session.id, sendInput, focusTerminal, registerSendInput, registerFocusTerminal])
|
|
111
|
-
|
|
112
|
-
// Expose disconnect for external use
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
const key = `terminal_disconnect_${session.id}`
|
|
115
|
-
;(window as unknown as Record<string, () => void>)[key] = disconnect
|
|
116
|
-
return () => {
|
|
117
|
-
delete (window as unknown as Record<string, () => void>)[key]
|
|
118
|
-
}
|
|
119
|
-
}, [session.id, disconnect])
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<div
|
|
123
|
-
ref={wrapperRef}
|
|
124
|
-
className="absolute inset-0 bg-[#0a0a0f] px-2 py-2"
|
|
125
|
-
style={{ display: isActive ? 'block' : 'none' }}
|
|
126
|
-
>
|
|
127
|
-
<div ref={containerRef} className="h-full w-full" />
|
|
128
|
-
</div>
|
|
129
|
-
)
|
|
130
|
-
}
|