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,66 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
|
5
|
-
|
|
6
|
-
import { cn } from "@/lib/utils"
|
|
7
|
-
|
|
8
|
-
function Tabs({
|
|
9
|
-
className,
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
12
|
-
return (
|
|
13
|
-
<TabsPrimitive.Root
|
|
14
|
-
data-slot="tabs"
|
|
15
|
-
className={cn("flex flex-col gap-2", className)}
|
|
16
|
-
{...props}
|
|
17
|
-
/>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function TabsList({
|
|
22
|
-
className,
|
|
23
|
-
...props
|
|
24
|
-
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
25
|
-
return (
|
|
26
|
-
<TabsPrimitive.List
|
|
27
|
-
data-slot="tabs-list"
|
|
28
|
-
className={cn(
|
|
29
|
-
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
|
30
|
-
className
|
|
31
|
-
)}
|
|
32
|
-
{...props}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function TabsTrigger({
|
|
38
|
-
className,
|
|
39
|
-
...props
|
|
40
|
-
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
41
|
-
return (
|
|
42
|
-
<TabsPrimitive.Trigger
|
|
43
|
-
data-slot="tabs-trigger"
|
|
44
|
-
className={cn(
|
|
45
|
-
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
46
|
-
className
|
|
47
|
-
)}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function TabsContent({
|
|
54
|
-
className,
|
|
55
|
-
...props
|
|
56
|
-
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
57
|
-
return (
|
|
58
|
-
<TabsPrimitive.Content
|
|
59
|
-
data-slot="tabs-content"
|
|
60
|
-
className={cn("flex-1 outline-none", className)}
|
|
61
|
-
{...props}
|
|
62
|
-
/>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
-
|
|
6
|
-
import { cn } from "@/lib/utils"
|
|
7
|
-
|
|
8
|
-
function TooltipProvider({
|
|
9
|
-
delayDuration = 0,
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
-
return (
|
|
13
|
-
<TooltipPrimitive.Provider
|
|
14
|
-
data-slot="tooltip-provider"
|
|
15
|
-
delayDuration={delayDuration}
|
|
16
|
-
{...props}
|
|
17
|
-
/>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function Tooltip({
|
|
22
|
-
...props
|
|
23
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
-
return (
|
|
25
|
-
<TooltipProvider>
|
|
26
|
-
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
27
|
-
</TooltipProvider>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function TooltipTrigger({
|
|
32
|
-
...props
|
|
33
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
34
|
-
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function TooltipContent({
|
|
38
|
-
className,
|
|
39
|
-
sideOffset = 0,
|
|
40
|
-
children,
|
|
41
|
-
...props
|
|
42
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
43
|
-
return (
|
|
44
|
-
<TooltipPrimitive.Portal>
|
|
45
|
-
<TooltipPrimitive.Content
|
|
46
|
-
data-slot="tooltip-content"
|
|
47
|
-
sideOffset={sideOffset}
|
|
48
|
-
className={cn(
|
|
49
|
-
"bg-popover text-popover-foreground border shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
50
|
-
className
|
|
51
|
-
)}
|
|
52
|
-
{...props}
|
|
53
|
-
>
|
|
54
|
-
{children}
|
|
55
|
-
<TooltipPrimitive.Arrow className="bg-popover fill-popover z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
|
56
|
-
</TooltipPrimitive.Content>
|
|
57
|
-
</TooltipPrimitive.Portal>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
-
"style": "new-york",
|
|
4
|
-
"rsc": true,
|
|
5
|
-
"tsx": true,
|
|
6
|
-
"tailwind": {
|
|
7
|
-
"config": "",
|
|
8
|
-
"css": "app/globals.css",
|
|
9
|
-
"baseColor": "neutral",
|
|
10
|
-
"cssVariables": true,
|
|
11
|
-
"prefix": ""
|
|
12
|
-
},
|
|
13
|
-
"iconLibrary": "lucide",
|
|
14
|
-
"aliases": {
|
|
15
|
-
"components": "@/components",
|
|
16
|
-
"utils": "@/lib/utils",
|
|
17
|
-
"ui": "@/components/ui",
|
|
18
|
-
"lib": "@/lib",
|
|
19
|
-
"hooks": "@/hooks"
|
|
20
|
-
},
|
|
21
|
-
"registries": {}
|
|
22
|
-
}
|
|
@@ -1,538 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Global Terminal Context - Manage terminal sessions across ALL projects
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Multi-project session management (Map<projectId, sessions[]>)
|
|
8
|
-
* - Bottom dock panel UI state (height, open/closed)
|
|
9
|
-
* - Full-screen mode for code page
|
|
10
|
-
* - Persistence to localStorage
|
|
11
|
-
* - Cross-page navigation support
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { createContext, useContext, useCallback, useState, useRef, useEffect, ReactNode } from 'react'
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Types
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
export interface GlobalTerminalSession {
|
|
21
|
-
id: string
|
|
22
|
-
projectId: string
|
|
23
|
-
projectName: string
|
|
24
|
-
projectPath: string
|
|
25
|
-
createdAt: Date
|
|
26
|
-
isConnected: boolean
|
|
27
|
-
isLoading: boolean
|
|
28
|
-
label: string
|
|
29
|
-
panel: 'left' | 'right' // Which panel this session belongs to
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ProjectTerminals {
|
|
33
|
-
projectId: string
|
|
34
|
-
projectName: string
|
|
35
|
-
projectPath: string
|
|
36
|
-
sessions: GlobalTerminalSession[]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface GlobalTerminalContextType {
|
|
40
|
-
// Session management
|
|
41
|
-
projectSessions: Map<string, ProjectTerminals>
|
|
42
|
-
activeProjectId: string | null
|
|
43
|
-
activeSessionId: string | null
|
|
44
|
-
|
|
45
|
-
// Dock UI state
|
|
46
|
-
isDockOpen: boolean
|
|
47
|
-
dockHeight: number
|
|
48
|
-
isFullScreen: boolean // When true, terminal takes full main area (code page)
|
|
49
|
-
isSplitEnabled: boolean // Split view mode
|
|
50
|
-
secondActiveSessionId: string | null // Second panel session (for split)
|
|
51
|
-
|
|
52
|
-
// Session actions
|
|
53
|
-
createSessionForProject: (projectId: string, projectName: string, projectPath: string, panel?: 'left' | 'right') => string
|
|
54
|
-
closeSession: (sessionId: string) => void
|
|
55
|
-
switchProject: (projectId: string) => void
|
|
56
|
-
switchSession: (sessionId: string, panel?: 'left' | 'right') => void
|
|
57
|
-
updateSession: (sessionId: string, updates: Partial<GlobalTerminalSession>) => void
|
|
58
|
-
|
|
59
|
-
// Dock UI actions
|
|
60
|
-
openDock: () => void
|
|
61
|
-
closeDock: () => void
|
|
62
|
-
toggleDock: () => void
|
|
63
|
-
setDockHeight: (height: number) => void
|
|
64
|
-
setFullScreen: (isFullScreen: boolean) => void
|
|
65
|
-
setSplitEnabled: (enabled: boolean) => void
|
|
66
|
-
|
|
67
|
-
// Helpers
|
|
68
|
-
getActiveSession: () => GlobalTerminalSession | null
|
|
69
|
-
getSecondActiveSession: () => GlobalTerminalSession | null
|
|
70
|
-
getProjectSessions: (projectId: string) => GlobalTerminalSession[]
|
|
71
|
-
getTotalSessionCount: () => number
|
|
72
|
-
getConnectedSessionCount: () => number
|
|
73
|
-
getAllSessions: () => GlobalTerminalSession[]
|
|
74
|
-
getLeftPanelSessions: () => GlobalTerminalSession[]
|
|
75
|
-
getRightPanelSessions: () => GlobalTerminalSession[]
|
|
76
|
-
moveSessionToPanel: (sessionId: string, panel: 'left' | 'right') => void
|
|
77
|
-
|
|
78
|
-
// Input/focus refs
|
|
79
|
-
sendCommandToActive: (command: string) => void
|
|
80
|
-
registerSendInput: (sessionId: string, fn: (data: string) => void) => void
|
|
81
|
-
registerFocusTerminal: (sessionId: string, fn: () => void) => void
|
|
82
|
-
focusActiveTerminal: () => void
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ============================================================================
|
|
86
|
-
// Constants
|
|
87
|
-
// ============================================================================
|
|
88
|
-
|
|
89
|
-
const STORAGE_KEYS = {
|
|
90
|
-
dockHeight: 'prjct-terminal-dock-height',
|
|
91
|
-
dockOpen: 'prjct-terminal-dock-open',
|
|
92
|
-
activeProject: 'prjct-terminal-active-project',
|
|
93
|
-
splitEnabled: 'prjct-terminal-split-enabled',
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const DEFAULT_DOCK_HEIGHT = 300
|
|
97
|
-
const MIN_DOCK_HEIGHT = 150
|
|
98
|
-
// Dynamic max height - 90% of viewport
|
|
99
|
-
const getMaxDockHeight = () => typeof window !== 'undefined' ? window.innerHeight * 0.9 : 800
|
|
100
|
-
|
|
101
|
-
// ============================================================================
|
|
102
|
-
// Context
|
|
103
|
-
// ============================================================================
|
|
104
|
-
|
|
105
|
-
const GlobalTerminalContext = createContext<GlobalTerminalContextType | null>(null)
|
|
106
|
-
|
|
107
|
-
let sessionCounter = 0
|
|
108
|
-
|
|
109
|
-
// ============================================================================
|
|
110
|
-
// Provider
|
|
111
|
-
// ============================================================================
|
|
112
|
-
|
|
113
|
-
export function GlobalTerminalProvider({ children }: { children: ReactNode }) {
|
|
114
|
-
// Session state
|
|
115
|
-
const [projectSessions, setProjectSessions] = useState<Map<string, ProjectTerminals>>(new Map())
|
|
116
|
-
const [activeProjectId, setActiveProjectId] = useState<string | null>(null)
|
|
117
|
-
const [activeSessionId, setActiveSessionId] = useState<string | null>(null)
|
|
118
|
-
|
|
119
|
-
// Dock UI state
|
|
120
|
-
const [isDockOpen, setIsDockOpen] = useState(false)
|
|
121
|
-
const [dockHeight, setDockHeightState] = useState(DEFAULT_DOCK_HEIGHT)
|
|
122
|
-
const [isFullScreen, setIsFullScreen] = useState(false)
|
|
123
|
-
const [isSplitEnabled, setIsSplitEnabledState] = useState(false)
|
|
124
|
-
const [secondActiveSessionId, setSecondActiveSessionId] = useState<string | null>(null)
|
|
125
|
-
|
|
126
|
-
// Refs for input/focus functions per session
|
|
127
|
-
const sendInputRefs = useRef<Map<string, (data: string) => void>>(new Map())
|
|
128
|
-
const focusTerminalRefs = useRef<Map<string, () => void>>(new Map())
|
|
129
|
-
|
|
130
|
-
// -------------------------------------------------------------------------
|
|
131
|
-
// Load persisted state from localStorage
|
|
132
|
-
// -------------------------------------------------------------------------
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
if (typeof window === 'undefined') return
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const savedDockHeight = localStorage.getItem(STORAGE_KEYS.dockHeight)
|
|
138
|
-
if (savedDockHeight) {
|
|
139
|
-
const parsed = parseInt(savedDockHeight, 10)
|
|
140
|
-
if (!isNaN(parsed)) {
|
|
141
|
-
setDockHeightState(Math.max(MIN_DOCK_HEIGHT, Math.min(parsed, getMaxDockHeight())))
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const savedDockOpen = localStorage.getItem(STORAGE_KEYS.dockOpen)
|
|
146
|
-
if (savedDockOpen) {
|
|
147
|
-
setIsDockOpen(JSON.parse(savedDockOpen))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const savedActiveProject = localStorage.getItem(STORAGE_KEYS.activeProject)
|
|
151
|
-
if (savedActiveProject) {
|
|
152
|
-
setActiveProjectId(savedActiveProject)
|
|
153
|
-
}
|
|
154
|
-
} catch (e) {
|
|
155
|
-
console.warn('Failed to load terminal state from localStorage:', e)
|
|
156
|
-
}
|
|
157
|
-
}, [])
|
|
158
|
-
|
|
159
|
-
// -------------------------------------------------------------------------
|
|
160
|
-
// Persist state to localStorage
|
|
161
|
-
// -------------------------------------------------------------------------
|
|
162
|
-
const setDockHeight = useCallback((newHeight: number) => {
|
|
163
|
-
const maxHeight = getMaxDockHeight()
|
|
164
|
-
const clampedHeight = Math.max(MIN_DOCK_HEIGHT, Math.min(newHeight, maxHeight))
|
|
165
|
-
setDockHeightState(clampedHeight)
|
|
166
|
-
if (typeof window !== 'undefined') {
|
|
167
|
-
localStorage.setItem(STORAGE_KEYS.dockHeight, String(clampedHeight))
|
|
168
|
-
}
|
|
169
|
-
}, [])
|
|
170
|
-
|
|
171
|
-
const setSplitEnabled = useCallback((enabled: boolean) => {
|
|
172
|
-
setIsSplitEnabledState(enabled)
|
|
173
|
-
if (typeof window !== 'undefined') {
|
|
174
|
-
localStorage.setItem(STORAGE_KEYS.splitEnabled, String(enabled))
|
|
175
|
-
}
|
|
176
|
-
// If disabling split, clear second panel
|
|
177
|
-
if (!enabled) {
|
|
178
|
-
setSecondActiveSessionId(null)
|
|
179
|
-
}
|
|
180
|
-
}, [])
|
|
181
|
-
|
|
182
|
-
// -------------------------------------------------------------------------
|
|
183
|
-
// Session management
|
|
184
|
-
// -------------------------------------------------------------------------
|
|
185
|
-
const createSessionForProject = useCallback((projectId: string, projectName: string, projectPath: string, panel: 'left' | 'right' = 'left') => {
|
|
186
|
-
sessionCounter++
|
|
187
|
-
const newSession: GlobalTerminalSession = {
|
|
188
|
-
id: `pty_${projectId}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
189
|
-
projectId,
|
|
190
|
-
projectName,
|
|
191
|
-
projectPath,
|
|
192
|
-
createdAt: new Date(),
|
|
193
|
-
isConnected: false,
|
|
194
|
-
isLoading: true,
|
|
195
|
-
label: `Terminal ${sessionCounter}`,
|
|
196
|
-
panel,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
setProjectSessions(prev => {
|
|
200
|
-
const newMap = new Map(prev)
|
|
201
|
-
const existing = newMap.get(projectId)
|
|
202
|
-
|
|
203
|
-
if (existing) {
|
|
204
|
-
newMap.set(projectId, {
|
|
205
|
-
...existing,
|
|
206
|
-
sessions: [...existing.sessions, newSession],
|
|
207
|
-
})
|
|
208
|
-
} else {
|
|
209
|
-
newMap.set(projectId, {
|
|
210
|
-
projectId,
|
|
211
|
-
projectName,
|
|
212
|
-
projectPath,
|
|
213
|
-
sessions: [newSession],
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return newMap
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
// Set as active based on panel
|
|
221
|
-
setActiveProjectId(projectId)
|
|
222
|
-
if (panel === 'right') {
|
|
223
|
-
setSecondActiveSessionId(newSession.id)
|
|
224
|
-
} else {
|
|
225
|
-
setActiveSessionId(newSession.id)
|
|
226
|
-
}
|
|
227
|
-
localStorage.setItem(STORAGE_KEYS.activeProject, projectId)
|
|
228
|
-
|
|
229
|
-
// Open dock if not open
|
|
230
|
-
setIsDockOpen(true)
|
|
231
|
-
localStorage.setItem(STORAGE_KEYS.dockOpen, 'true')
|
|
232
|
-
|
|
233
|
-
return newSession.id
|
|
234
|
-
}, [])
|
|
235
|
-
|
|
236
|
-
const closeSession = useCallback((sessionId: string) => {
|
|
237
|
-
setProjectSessions(prev => {
|
|
238
|
-
const newMap = new Map(prev)
|
|
239
|
-
|
|
240
|
-
// Find which project this session belongs to
|
|
241
|
-
for (const [projectId, project] of newMap) {
|
|
242
|
-
const sessionIndex = project.sessions.findIndex(s => s.id === sessionId)
|
|
243
|
-
if (sessionIndex !== -1) {
|
|
244
|
-
const newSessions = project.sessions.filter(s => s.id !== sessionId)
|
|
245
|
-
|
|
246
|
-
if (newSessions.length === 0) {
|
|
247
|
-
// Remove project entirely if no sessions left
|
|
248
|
-
newMap.delete(projectId)
|
|
249
|
-
} else {
|
|
250
|
-
newMap.set(projectId, { ...project, sessions: newSessions })
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Update active session if needed
|
|
254
|
-
if (sessionId === activeSessionId) {
|
|
255
|
-
if (newSessions.length > 0) {
|
|
256
|
-
setActiveSessionId(newSessions[newSessions.length - 1].id)
|
|
257
|
-
} else {
|
|
258
|
-
// Switch to another project's session or null
|
|
259
|
-
const remainingProjects = Array.from(newMap.values())
|
|
260
|
-
if (remainingProjects.length > 0) {
|
|
261
|
-
const nextProject = remainingProjects[0]
|
|
262
|
-
setActiveProjectId(nextProject.projectId)
|
|
263
|
-
setActiveSessionId(nextProject.sessions[nextProject.sessions.length - 1]?.id || null)
|
|
264
|
-
} else {
|
|
265
|
-
setActiveProjectId(null)
|
|
266
|
-
setActiveSessionId(null)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
break
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return newMap
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
// Cleanup refs
|
|
279
|
-
sendInputRefs.current.delete(sessionId)
|
|
280
|
-
focusTerminalRefs.current.delete(sessionId)
|
|
281
|
-
}, [activeSessionId])
|
|
282
|
-
|
|
283
|
-
const switchProject = useCallback((projectId: string) => {
|
|
284
|
-
setActiveProjectId(projectId)
|
|
285
|
-
localStorage.setItem(STORAGE_KEYS.activeProject, projectId)
|
|
286
|
-
|
|
287
|
-
// Switch to first session of this project
|
|
288
|
-
const project = projectSessions.get(projectId)
|
|
289
|
-
if (project && project.sessions.length > 0) {
|
|
290
|
-
setActiveSessionId(project.sessions[0].id)
|
|
291
|
-
}
|
|
292
|
-
}, [projectSessions])
|
|
293
|
-
|
|
294
|
-
const switchSession = useCallback((sessionId: string, panel: 'left' | 'right' = 'left') => {
|
|
295
|
-
if (panel === 'right' && isSplitEnabled) {
|
|
296
|
-
setSecondActiveSessionId(sessionId)
|
|
297
|
-
} else {
|
|
298
|
-
setActiveSessionId(sessionId)
|
|
299
|
-
|
|
300
|
-
// Also update active project if session is from different project
|
|
301
|
-
for (const [projectId, project] of projectSessions) {
|
|
302
|
-
if (project.sessions.some(s => s.id === sessionId)) {
|
|
303
|
-
if (projectId !== activeProjectId) {
|
|
304
|
-
setActiveProjectId(projectId)
|
|
305
|
-
localStorage.setItem(STORAGE_KEYS.activeProject, projectId)
|
|
306
|
-
}
|
|
307
|
-
break
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}, [projectSessions, activeProjectId, isSplitEnabled])
|
|
312
|
-
|
|
313
|
-
const updateSession = useCallback((sessionId: string, updates: Partial<GlobalTerminalSession>) => {
|
|
314
|
-
setProjectSessions(prev => {
|
|
315
|
-
const newMap = new Map(prev)
|
|
316
|
-
|
|
317
|
-
for (const [projectId, project] of newMap) {
|
|
318
|
-
const sessionIndex = project.sessions.findIndex(s => s.id === sessionId)
|
|
319
|
-
if (sessionIndex !== -1) {
|
|
320
|
-
const newSessions = [...project.sessions]
|
|
321
|
-
newSessions[sessionIndex] = { ...newSessions[sessionIndex], ...updates }
|
|
322
|
-
newMap.set(projectId, { ...project, sessions: newSessions })
|
|
323
|
-
break
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return newMap
|
|
328
|
-
})
|
|
329
|
-
}, [])
|
|
330
|
-
|
|
331
|
-
// -------------------------------------------------------------------------
|
|
332
|
-
// Dock UI actions
|
|
333
|
-
// -------------------------------------------------------------------------
|
|
334
|
-
const openDock = useCallback(() => {
|
|
335
|
-
setIsDockOpen(true)
|
|
336
|
-
localStorage.setItem(STORAGE_KEYS.dockOpen, 'true')
|
|
337
|
-
}, [])
|
|
338
|
-
|
|
339
|
-
const closeDock = useCallback(() => {
|
|
340
|
-
setIsDockOpen(false)
|
|
341
|
-
localStorage.setItem(STORAGE_KEYS.dockOpen, 'false')
|
|
342
|
-
}, [])
|
|
343
|
-
|
|
344
|
-
const toggleDock = useCallback(() => {
|
|
345
|
-
const newValue = !isDockOpen
|
|
346
|
-
setIsDockOpen(newValue)
|
|
347
|
-
localStorage.setItem(STORAGE_KEYS.dockOpen, String(newValue))
|
|
348
|
-
}, [isDockOpen])
|
|
349
|
-
|
|
350
|
-
const setFullScreen = useCallback((value: boolean) => {
|
|
351
|
-
setIsFullScreen(value)
|
|
352
|
-
}, [])
|
|
353
|
-
|
|
354
|
-
// -------------------------------------------------------------------------
|
|
355
|
-
// Helpers
|
|
356
|
-
// -------------------------------------------------------------------------
|
|
357
|
-
const getActiveSession = useCallback((): GlobalTerminalSession | null => {
|
|
358
|
-
if (!activeSessionId) return null
|
|
359
|
-
for (const project of projectSessions.values()) {
|
|
360
|
-
const session = project.sessions.find(s => s.id === activeSessionId)
|
|
361
|
-
if (session) return session
|
|
362
|
-
}
|
|
363
|
-
return null
|
|
364
|
-
}, [projectSessions, activeSessionId])
|
|
365
|
-
|
|
366
|
-
const getSecondActiveSession = useCallback((): GlobalTerminalSession | null => {
|
|
367
|
-
if (!secondActiveSessionId) return null
|
|
368
|
-
for (const project of projectSessions.values()) {
|
|
369
|
-
const session = project.sessions.find(s => s.id === secondActiveSessionId)
|
|
370
|
-
if (session) return session
|
|
371
|
-
}
|
|
372
|
-
return null
|
|
373
|
-
}, [projectSessions, secondActiveSessionId])
|
|
374
|
-
|
|
375
|
-
const getProjectSessions = useCallback((projectId: string): GlobalTerminalSession[] => {
|
|
376
|
-
return projectSessions.get(projectId)?.sessions || []
|
|
377
|
-
}, [projectSessions])
|
|
378
|
-
|
|
379
|
-
const getAllSessions = useCallback((): GlobalTerminalSession[] => {
|
|
380
|
-
const allSessions: GlobalTerminalSession[] = []
|
|
381
|
-
for (const project of projectSessions.values()) {
|
|
382
|
-
allSessions.push(...project.sessions)
|
|
383
|
-
}
|
|
384
|
-
return allSessions
|
|
385
|
-
}, [projectSessions])
|
|
386
|
-
|
|
387
|
-
const getTotalSessionCount = useCallback((): number => {
|
|
388
|
-
let count = 0
|
|
389
|
-
for (const project of projectSessions.values()) {
|
|
390
|
-
count += project.sessions.length
|
|
391
|
-
}
|
|
392
|
-
return count
|
|
393
|
-
}, [projectSessions])
|
|
394
|
-
|
|
395
|
-
const getConnectedSessionCount = useCallback((): number => {
|
|
396
|
-
let count = 0
|
|
397
|
-
for (const project of projectSessions.values()) {
|
|
398
|
-
count += project.sessions.filter(s => s.isConnected).length
|
|
399
|
-
}
|
|
400
|
-
return count
|
|
401
|
-
}, [projectSessions])
|
|
402
|
-
|
|
403
|
-
const getLeftPanelSessions = useCallback((): GlobalTerminalSession[] => {
|
|
404
|
-
return getAllSessions().filter(s => s.panel === 'left')
|
|
405
|
-
}, [getAllSessions])
|
|
406
|
-
|
|
407
|
-
const getRightPanelSessions = useCallback((): GlobalTerminalSession[] => {
|
|
408
|
-
return getAllSessions().filter(s => s.panel === 'right')
|
|
409
|
-
}, [getAllSessions])
|
|
410
|
-
|
|
411
|
-
const moveSessionToPanel = useCallback((sessionId: string, panel: 'left' | 'right') => {
|
|
412
|
-
setProjectSessions(prev => {
|
|
413
|
-
const newMap = new Map(prev)
|
|
414
|
-
for (const [projectId, project] of newMap) {
|
|
415
|
-
const sessionIndex = project.sessions.findIndex(s => s.id === sessionId)
|
|
416
|
-
if (sessionIndex !== -1) {
|
|
417
|
-
const newSessions = [...project.sessions]
|
|
418
|
-
newSessions[sessionIndex] = { ...newSessions[sessionIndex], panel }
|
|
419
|
-
newMap.set(projectId, { ...project, sessions: newSessions })
|
|
420
|
-
break
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return newMap
|
|
424
|
-
})
|
|
425
|
-
}, [])
|
|
426
|
-
|
|
427
|
-
// -------------------------------------------------------------------------
|
|
428
|
-
// Input/Focus management
|
|
429
|
-
// -------------------------------------------------------------------------
|
|
430
|
-
const registerSendInput = useCallback((sessionId: string, fn: (data: string) => void) => {
|
|
431
|
-
sendInputRefs.current.set(sessionId, fn)
|
|
432
|
-
}, [])
|
|
433
|
-
|
|
434
|
-
const registerFocusTerminal = useCallback((sessionId: string, fn: () => void) => {
|
|
435
|
-
focusTerminalRefs.current.set(sessionId, fn)
|
|
436
|
-
}, [])
|
|
437
|
-
|
|
438
|
-
const focusActiveTerminal = useCallback(() => {
|
|
439
|
-
if (!activeSessionId) return
|
|
440
|
-
const focusFn = focusTerminalRefs.current.get(activeSessionId)
|
|
441
|
-
if (focusFn) focusFn()
|
|
442
|
-
}, [activeSessionId])
|
|
443
|
-
|
|
444
|
-
const sendCommandToActive = useCallback((command: string) => {
|
|
445
|
-
if (!activeSessionId) return
|
|
446
|
-
const sendFn = sendInputRefs.current.get(activeSessionId)
|
|
447
|
-
const session = getActiveSession()
|
|
448
|
-
if (sendFn && session?.isConnected) {
|
|
449
|
-
sendFn(command + '\n')
|
|
450
|
-
// Auto-focus terminal after sending command
|
|
451
|
-
const focusFn = focusTerminalRefs.current.get(activeSessionId)
|
|
452
|
-
if (focusFn) focusFn()
|
|
453
|
-
}
|
|
454
|
-
}, [activeSessionId, getActiveSession])
|
|
455
|
-
|
|
456
|
-
// -------------------------------------------------------------------------
|
|
457
|
-
// Page unload protection
|
|
458
|
-
// -------------------------------------------------------------------------
|
|
459
|
-
const hasConnectedSessions = getConnectedSessionCount() > 0
|
|
460
|
-
|
|
461
|
-
useEffect(() => {
|
|
462
|
-
if (!hasConnectedSessions) return
|
|
463
|
-
|
|
464
|
-
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
465
|
-
e.preventDefault()
|
|
466
|
-
e.returnValue = 'You have active terminal sessions. Are you sure you want to leave?'
|
|
467
|
-
return e.returnValue
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
471
|
-
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
472
|
-
}, [hasConnectedSessions])
|
|
473
|
-
|
|
474
|
-
// -------------------------------------------------------------------------
|
|
475
|
-
// Render
|
|
476
|
-
// -------------------------------------------------------------------------
|
|
477
|
-
return (
|
|
478
|
-
<GlobalTerminalContext.Provider value={{
|
|
479
|
-
// Session state
|
|
480
|
-
projectSessions,
|
|
481
|
-
activeProjectId,
|
|
482
|
-
activeSessionId,
|
|
483
|
-
|
|
484
|
-
// Dock UI state
|
|
485
|
-
isDockOpen,
|
|
486
|
-
dockHeight,
|
|
487
|
-
isFullScreen,
|
|
488
|
-
isSplitEnabled,
|
|
489
|
-
secondActiveSessionId,
|
|
490
|
-
|
|
491
|
-
// Session actions
|
|
492
|
-
createSessionForProject,
|
|
493
|
-
closeSession,
|
|
494
|
-
switchProject,
|
|
495
|
-
switchSession,
|
|
496
|
-
updateSession,
|
|
497
|
-
|
|
498
|
-
// Dock UI actions
|
|
499
|
-
openDock,
|
|
500
|
-
closeDock,
|
|
501
|
-
toggleDock,
|
|
502
|
-
setDockHeight,
|
|
503
|
-
setFullScreen,
|
|
504
|
-
setSplitEnabled,
|
|
505
|
-
|
|
506
|
-
// Helpers
|
|
507
|
-
getActiveSession,
|
|
508
|
-
getSecondActiveSession,
|
|
509
|
-
getProjectSessions,
|
|
510
|
-
getTotalSessionCount,
|
|
511
|
-
getConnectedSessionCount,
|
|
512
|
-
getAllSessions,
|
|
513
|
-
getLeftPanelSessions,
|
|
514
|
-
getRightPanelSessions,
|
|
515
|
-
moveSessionToPanel,
|
|
516
|
-
|
|
517
|
-
// Input/focus
|
|
518
|
-
sendCommandToActive,
|
|
519
|
-
registerSendInput,
|
|
520
|
-
registerFocusTerminal,
|
|
521
|
-
focusActiveTerminal,
|
|
522
|
-
}}>
|
|
523
|
-
{children}
|
|
524
|
-
</GlobalTerminalContext.Provider>
|
|
525
|
-
)
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// ============================================================================
|
|
529
|
-
// Hook
|
|
530
|
-
// ============================================================================
|
|
531
|
-
|
|
532
|
-
export function useGlobalTerminal() {
|
|
533
|
-
const context = useContext(GlobalTerminalContext)
|
|
534
|
-
if (!context) {
|
|
535
|
-
throw new Error('useGlobalTerminal must be used within GlobalTerminalProvider')
|
|
536
|
-
}
|
|
537
|
-
return context
|
|
538
|
-
}
|