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,142 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useRef, useEffect } from 'react'
|
|
4
|
-
import { X, Plus } from 'lucide-react'
|
|
5
|
-
import { cn } from '@/lib/utils'
|
|
6
|
-
import type { GlobalTerminalSession } from '@/context/GlobalTerminalContext'
|
|
7
|
-
|
|
8
|
-
interface TerminalTabBarProps {
|
|
9
|
-
sessions: GlobalTerminalSession[]
|
|
10
|
-
activeSessionId: string | null
|
|
11
|
-
onSwitchSession: (sessionId: string) => void
|
|
12
|
-
onCloseSession: (sessionId: string) => void
|
|
13
|
-
onNewTerminal: () => void
|
|
14
|
-
onRenameSession: (sessionId: string, newLabel: string) => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function TerminalTabBar({
|
|
18
|
-
sessions,
|
|
19
|
-
activeSessionId,
|
|
20
|
-
onSwitchSession,
|
|
21
|
-
onCloseSession,
|
|
22
|
-
onNewTerminal,
|
|
23
|
-
onRenameSession,
|
|
24
|
-
}: TerminalTabBarProps) {
|
|
25
|
-
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
|
|
26
|
-
const [editValue, setEditValue] = useState('')
|
|
27
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
28
|
-
|
|
29
|
-
// Group sessions by project
|
|
30
|
-
const sessionsByProject = sessions.reduce((acc, session) => {
|
|
31
|
-
const existing = acc.find(g => g.projectId === session.projectId)
|
|
32
|
-
if (existing) {
|
|
33
|
-
existing.sessions.push(session)
|
|
34
|
-
} else {
|
|
35
|
-
acc.push({
|
|
36
|
-
projectId: session.projectId,
|
|
37
|
-
projectName: session.projectName,
|
|
38
|
-
sessions: [session],
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
return acc
|
|
42
|
-
}, [] as { projectId: string; projectName: string; sessions: GlobalTerminalSession[] }[])
|
|
43
|
-
|
|
44
|
-
// Focus input when editing starts
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (editingSessionId && inputRef.current) {
|
|
47
|
-
inputRef.current.focus()
|
|
48
|
-
inputRef.current.select()
|
|
49
|
-
}
|
|
50
|
-
}, [editingSessionId])
|
|
51
|
-
|
|
52
|
-
const handleDoubleClick = (session: GlobalTerminalSession) => {
|
|
53
|
-
setEditingSessionId(session.id)
|
|
54
|
-
setEditValue(session.label)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const handleBlur = () => {
|
|
58
|
-
if (editingSessionId && editValue.trim()) {
|
|
59
|
-
onRenameSession(editingSessionId, editValue.trim())
|
|
60
|
-
}
|
|
61
|
-
setEditingSessionId(null)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
65
|
-
if (e.key === 'Enter') {
|
|
66
|
-
handleBlur()
|
|
67
|
-
} else if (e.key === 'Escape') {
|
|
68
|
-
setEditingSessionId(null)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div className="flex items-center gap-0.5 flex-1 overflow-x-auto py-1 px-1">
|
|
74
|
-
{sessionsByProject.map((group, groupIndex) => (
|
|
75
|
-
<div key={group.projectId} className="flex items-center">
|
|
76
|
-
{/* Group separator */}
|
|
77
|
-
{groupIndex > 0 && (
|
|
78
|
-
<div className="w-px h-5 bg-border mx-2" />
|
|
79
|
-
)}
|
|
80
|
-
|
|
81
|
-
{/* Tabs for this project */}
|
|
82
|
-
{group.sessions.map((session) => {
|
|
83
|
-
const isActive = session.id === activeSessionId
|
|
84
|
-
const isEditing = session.id === editingSessionId
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<button
|
|
88
|
-
key={session.id}
|
|
89
|
-
onClick={() => onSwitchSession(session.id)}
|
|
90
|
-
onDoubleClick={() => handleDoubleClick(session)}
|
|
91
|
-
className={cn(
|
|
92
|
-
'group flex items-center gap-1.5 px-3 py-1.5 text-xs transition-all relative',
|
|
93
|
-
// Chrome-style: rounded top corners, flat bottom
|
|
94
|
-
'rounded-t-md',
|
|
95
|
-
isActive
|
|
96
|
-
? 'bg-card border-t border-l border-r border-border text-foreground -mb-px z-10'
|
|
97
|
-
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
|
98
|
-
)}
|
|
99
|
-
>
|
|
100
|
-
{/* Label - editable or display */}
|
|
101
|
-
{isEditing ? (
|
|
102
|
-
<input
|
|
103
|
-
ref={inputRef}
|
|
104
|
-
type="text"
|
|
105
|
-
value={editValue}
|
|
106
|
-
onChange={(e) => setEditValue(e.target.value)}
|
|
107
|
-
onBlur={handleBlur}
|
|
108
|
-
onKeyDown={handleKeyDown}
|
|
109
|
-
className="w-24 bg-transparent border-b border-orange-500 outline-none text-xs"
|
|
110
|
-
onClick={(e) => e.stopPropagation()}
|
|
111
|
-
/>
|
|
112
|
-
) : (
|
|
113
|
-
<span className="truncate max-w-[120px]">
|
|
114
|
-
{session.label}
|
|
115
|
-
</span>
|
|
116
|
-
)}
|
|
117
|
-
|
|
118
|
-
{/* Close button */}
|
|
119
|
-
<X
|
|
120
|
-
className="w-3 h-3 opacity-0 group-hover:opacity-100 hover:text-destructive transition-opacity shrink-0"
|
|
121
|
-
onClick={(e) => {
|
|
122
|
-
e.stopPropagation()
|
|
123
|
-
onCloseSession(session.id)
|
|
124
|
-
}}
|
|
125
|
-
/>
|
|
126
|
-
</button>
|
|
127
|
-
)
|
|
128
|
-
})}
|
|
129
|
-
</div>
|
|
130
|
-
))}
|
|
131
|
-
|
|
132
|
-
{/* Add Terminal Button */}
|
|
133
|
-
<button
|
|
134
|
-
onClick={onNewTerminal}
|
|
135
|
-
className="p-1.5 rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors ml-1"
|
|
136
|
-
title="New terminal"
|
|
137
|
-
>
|
|
138
|
-
<Plus className="w-4 h-4" />
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
)
|
|
142
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useCallback } from 'react'
|
|
4
|
-
import { useClaudeTerminal } from '@/hooks/useClaudeTerminal'
|
|
5
|
-
import { useTerminalTabs, type TerminalSession } from '@/context/TerminalTabsContext'
|
|
6
|
-
|
|
7
|
-
interface TerminalTabProps {
|
|
8
|
-
session: TerminalSession
|
|
9
|
-
projectDir: string
|
|
10
|
-
isActive: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function TerminalTab({ session, projectDir, isActive }: TerminalTabProps) {
|
|
14
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
15
|
-
const hasInitializedRef = useRef(false)
|
|
16
|
-
const hasConnectedRef = useRef(false)
|
|
17
|
-
const { updateSession, registerSendInput, registerFocusTerminal } = useTerminalTabs()
|
|
18
|
-
|
|
19
|
-
const handleConnect = useCallback(() => {
|
|
20
|
-
updateSession(session.id, { isConnected: true, isLoading: false })
|
|
21
|
-
}, [session.id, updateSession])
|
|
22
|
-
|
|
23
|
-
const handleDisconnect = useCallback(() => {
|
|
24
|
-
updateSession(session.id, { isConnected: false, isLoading: false })
|
|
25
|
-
}, [session.id, updateSession])
|
|
26
|
-
|
|
27
|
-
const handleError = useCallback((error: string) => {
|
|
28
|
-
console.error(`[Terminal ${session.id}] Error:`, error)
|
|
29
|
-
updateSession(session.id, { isLoading: false })
|
|
30
|
-
}, [session.id, updateSession])
|
|
31
|
-
|
|
32
|
-
const {
|
|
33
|
-
initTerminal,
|
|
34
|
-
connect,
|
|
35
|
-
disconnect,
|
|
36
|
-
sendInput,
|
|
37
|
-
focusTerminal,
|
|
38
|
-
fit,
|
|
39
|
-
} = useClaudeTerminal({
|
|
40
|
-
sessionId: session.id,
|
|
41
|
-
projectDir,
|
|
42
|
-
onConnect: handleConnect,
|
|
43
|
-
onDisconnect: handleDisconnect,
|
|
44
|
-
onError: handleError,
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// Initialize terminal AND connect - only once
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (containerRef.current && !hasInitializedRef.current) {
|
|
50
|
-
hasInitializedRef.current = true
|
|
51
|
-
|
|
52
|
-
initTerminal(containerRef.current).then(() => {
|
|
53
|
-
if (!hasConnectedRef.current) {
|
|
54
|
-
hasConnectedRef.current = true
|
|
55
|
-
connect()
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}, []) // Empty deps - run only on mount
|
|
60
|
-
|
|
61
|
-
// Re-fit terminal when tab becomes active (fixes resize issues when tab was hidden)
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (isActive && hasInitializedRef.current) {
|
|
64
|
-
// Use requestAnimationFrame to ensure container is fully visible
|
|
65
|
-
requestAnimationFrame(() => {
|
|
66
|
-
fit()
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
}, [isActive, fit])
|
|
70
|
-
|
|
71
|
-
// Register sendInput and focusTerminal for this session
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
registerSendInput(session.id, sendInput)
|
|
74
|
-
registerFocusTerminal(session.id, focusTerminal)
|
|
75
|
-
}, [session.id, sendInput, focusTerminal, registerSendInput, registerFocusTerminal])
|
|
76
|
-
|
|
77
|
-
// Expose disconnect for external use
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
// Store disconnect function on window for access from parent
|
|
80
|
-
const key = `terminal_disconnect_${session.id}`
|
|
81
|
-
;(window as unknown as Record<string, () => void>)[key] = disconnect
|
|
82
|
-
return () => {
|
|
83
|
-
delete (window as unknown as Record<string, () => void>)[key]
|
|
84
|
-
}
|
|
85
|
-
}, [session.id, disconnect])
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<div
|
|
89
|
-
className="absolute inset-0 bg-[#0a0a0f] px-2 py-2"
|
|
90
|
-
style={{ display: isActive ? 'block' : 'none' }}
|
|
91
|
-
>
|
|
92
|
-
<div ref={containerRef} className="h-full w-full" />
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useCallback, useState, useRef, useEffect } from 'react'
|
|
4
|
-
import { useTerminalTabs } from '@/context/TerminalTabsContext'
|
|
5
|
-
import { TerminalTab } from './TerminalTab'
|
|
6
|
-
import { Button } from '@/components/ui/button'
|
|
7
|
-
import {
|
|
8
|
-
AlertDialog,
|
|
9
|
-
AlertDialogAction,
|
|
10
|
-
AlertDialogCancel,
|
|
11
|
-
AlertDialogContent,
|
|
12
|
-
AlertDialogDescription,
|
|
13
|
-
AlertDialogFooter,
|
|
14
|
-
AlertDialogHeader,
|
|
15
|
-
AlertDialogTitle,
|
|
16
|
-
} from '@/components/ui/alert-dialog'
|
|
17
|
-
import { X, Terminal as TerminalIcon, Plus, Loader2, AlertTriangle } from 'lucide-react'
|
|
18
|
-
import { cn } from '@/lib/utils'
|
|
19
|
-
|
|
20
|
-
interface TerminalTabsProps {
|
|
21
|
-
projectDir: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function TerminalTabs({ projectDir }: TerminalTabsProps) {
|
|
25
|
-
const {
|
|
26
|
-
sessions,
|
|
27
|
-
activeSessionId,
|
|
28
|
-
createSession,
|
|
29
|
-
closeSession,
|
|
30
|
-
setActiveSession,
|
|
31
|
-
updateSession
|
|
32
|
-
} = useTerminalTabs()
|
|
33
|
-
|
|
34
|
-
const [sessionToClose, setSessionToClose] = useState<string | null>(null)
|
|
35
|
-
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
|
|
36
|
-
const [editValue, setEditValue] = useState('')
|
|
37
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
38
|
-
|
|
39
|
-
const handleCloseTab = useCallback((sessionId: string, e: React.MouseEvent) => {
|
|
40
|
-
e.stopPropagation()
|
|
41
|
-
const session = sessions.find(s => s.id === sessionId)
|
|
42
|
-
if (session?.isConnected) {
|
|
43
|
-
setSessionToClose(sessionId)
|
|
44
|
-
} else {
|
|
45
|
-
closeSession(sessionId)
|
|
46
|
-
}
|
|
47
|
-
}, [sessions, closeSession])
|
|
48
|
-
|
|
49
|
-
const handleConfirmClose = useCallback(() => {
|
|
50
|
-
if (sessionToClose) {
|
|
51
|
-
// Call disconnect on the terminal
|
|
52
|
-
const disconnectFn = (window as unknown as Record<string, () => void>)[`terminal_disconnect_${sessionToClose}`]
|
|
53
|
-
if (disconnectFn) disconnectFn()
|
|
54
|
-
closeSession(sessionToClose)
|
|
55
|
-
setSessionToClose(null)
|
|
56
|
-
}
|
|
57
|
-
}, [sessionToClose, closeSession])
|
|
58
|
-
|
|
59
|
-
const startEditing = useCallback((sessionId: string, currentLabel: string) => {
|
|
60
|
-
setEditingSessionId(sessionId)
|
|
61
|
-
setEditValue(currentLabel)
|
|
62
|
-
}, [])
|
|
63
|
-
|
|
64
|
-
const finishEditing = useCallback(() => {
|
|
65
|
-
if (editingSessionId && editValue.trim()) {
|
|
66
|
-
updateSession(editingSessionId, { label: editValue.trim() })
|
|
67
|
-
}
|
|
68
|
-
setEditingSessionId(null)
|
|
69
|
-
setEditValue('')
|
|
70
|
-
}, [editingSessionId, editValue, updateSession])
|
|
71
|
-
|
|
72
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
73
|
-
if (e.key === 'Enter') {
|
|
74
|
-
finishEditing()
|
|
75
|
-
} else if (e.key === 'Escape') {
|
|
76
|
-
setEditingSessionId(null)
|
|
77
|
-
setEditValue('')
|
|
78
|
-
}
|
|
79
|
-
}, [finishEditing])
|
|
80
|
-
|
|
81
|
-
// Focus input when editing starts
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (editingSessionId && inputRef.current) {
|
|
84
|
-
inputRef.current.focus()
|
|
85
|
-
inputRef.current.select()
|
|
86
|
-
}
|
|
87
|
-
}, [editingSessionId])
|
|
88
|
-
|
|
89
|
-
const hasNoSessions = sessions.length === 0
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<div className="flex flex-col h-full">
|
|
93
|
-
{/* Tab bar - scrollable on mobile */}
|
|
94
|
-
<div className="flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border min-h-[44px] md:min-h-[40px] overflow-x-auto scrollbar-hide">
|
|
95
|
-
{sessions.map(session => (
|
|
96
|
-
<div
|
|
97
|
-
key={session.id}
|
|
98
|
-
onClick={() => setActiveSession(session.id)}
|
|
99
|
-
onDoubleClick={() => startEditing(session.id, session.label)}
|
|
100
|
-
role="tab"
|
|
101
|
-
tabIndex={0}
|
|
102
|
-
onKeyDown={(e) => e.key === 'Enter' && setActiveSession(session.id)}
|
|
103
|
-
className={cn(
|
|
104
|
-
'flex items-center gap-2 px-3 py-2 md:py-1.5 rounded-md text-sm transition-colors cursor-pointer shrink-0',
|
|
105
|
-
'hover:bg-muted active:bg-muted/80',
|
|
106
|
-
'min-h-[36px] md:min-h-0', // Touch-friendly height on mobile
|
|
107
|
-
session.id === activeSessionId
|
|
108
|
-
? 'bg-muted text-foreground'
|
|
109
|
-
: 'text-muted-foreground'
|
|
110
|
-
)}
|
|
111
|
-
>
|
|
112
|
-
{session.isLoading ? (
|
|
113
|
-
<Loader2 className="w-3.5 h-3.5 animate-spin shrink-0" />
|
|
114
|
-
) : session.isConnected ? (
|
|
115
|
-
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
|
116
|
-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
|
|
117
|
-
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500" />
|
|
118
|
-
</span>
|
|
119
|
-
) : (
|
|
120
|
-
<span className="h-2.5 w-2.5 rounded-full bg-muted-foreground/50 shrink-0" />
|
|
121
|
-
)}
|
|
122
|
-
{editingSessionId === session.id ? (
|
|
123
|
-
<input
|
|
124
|
-
ref={inputRef}
|
|
125
|
-
type="text"
|
|
126
|
-
value={editValue}
|
|
127
|
-
onChange={(e) => setEditValue(e.target.value)}
|
|
128
|
-
onBlur={finishEditing}
|
|
129
|
-
onKeyDown={handleKeyDown}
|
|
130
|
-
onClick={(e) => e.stopPropagation()}
|
|
131
|
-
className="w-[100px] bg-background border border-border rounded px-1 py-0.5 text-sm outline-none focus:ring-1 focus:ring-ring"
|
|
132
|
-
/>
|
|
133
|
-
) : (
|
|
134
|
-
<span className="truncate max-w-[80px] md:max-w-[100px]">{session.label}</span>
|
|
135
|
-
)}
|
|
136
|
-
<button
|
|
137
|
-
onClick={(e) => handleCloseTab(session.id, e)}
|
|
138
|
-
className="p-1 md:p-0.5 rounded hover:bg-background/50 text-muted-foreground hover:text-foreground shrink-0"
|
|
139
|
-
>
|
|
140
|
-
<X className="w-4 h-4 md:w-3 md:h-3" />
|
|
141
|
-
</button>
|
|
142
|
-
</div>
|
|
143
|
-
))}
|
|
144
|
-
|
|
145
|
-
{/* New tab button - larger touch target on mobile */}
|
|
146
|
-
<Button
|
|
147
|
-
variant="ghost"
|
|
148
|
-
size="icon"
|
|
149
|
-
className="h-9 w-9 md:h-7 md:w-7 ml-1 shrink-0"
|
|
150
|
-
onClick={createSession}
|
|
151
|
-
>
|
|
152
|
-
<Plus className="w-5 h-5 md:w-4 md:h-4" />
|
|
153
|
-
</Button>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Terminal area */}
|
|
157
|
-
<div className="flex-1 relative">
|
|
158
|
-
{hasNoSessions ? (
|
|
159
|
-
<div className="absolute inset-0 flex items-center justify-center bg-background p-4">
|
|
160
|
-
<div className="text-center max-w-xs">
|
|
161
|
-
<div className="w-14 h-14 md:w-16 md:h-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-3 md:mb-4">
|
|
162
|
-
<TerminalIcon className="w-7 h-7 md:w-8 md:h-8 text-muted-foreground" />
|
|
163
|
-
</div>
|
|
164
|
-
<h2 className="text-base md:text-lg font-medium mb-1.5 md:mb-2">No active sessions</h2>
|
|
165
|
-
<p className="text-muted-foreground text-sm mb-3 md:mb-4">
|
|
166
|
-
Tap + to create a new terminal
|
|
167
|
-
</p>
|
|
168
|
-
<Button onClick={createSession} className="min-h-[44px]">
|
|
169
|
-
<Plus className="w-4 h-4 mr-2" />
|
|
170
|
-
New Terminal
|
|
171
|
-
</Button>
|
|
172
|
-
</div>
|
|
173
|
-
</div>
|
|
174
|
-
) : (
|
|
175
|
-
sessions.map(session => (
|
|
176
|
-
<TerminalTab
|
|
177
|
-
key={session.id}
|
|
178
|
-
session={session}
|
|
179
|
-
projectDir={projectDir}
|
|
180
|
-
isActive={session.id === activeSessionId}
|
|
181
|
-
/>
|
|
182
|
-
))
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
{/* Close confirmation dialog - responsive */}
|
|
187
|
-
<AlertDialog open={!!sessionToClose} onOpenChange={(open: boolean) => !open && setSessionToClose(null)}>
|
|
188
|
-
<AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
|
|
189
|
-
<AlertDialogHeader>
|
|
190
|
-
<AlertDialogTitle className="flex items-center gap-2">
|
|
191
|
-
<AlertTriangle className="w-5 h-5 text-destructive shrink-0" />
|
|
192
|
-
Close Terminal?
|
|
193
|
-
</AlertDialogTitle>
|
|
194
|
-
<AlertDialogDescription>
|
|
195
|
-
This terminal has an active session. Closing it will terminate the connection.
|
|
196
|
-
</AlertDialogDescription>
|
|
197
|
-
</AlertDialogHeader>
|
|
198
|
-
<AlertDialogFooter className="flex-col sm:flex-row gap-2">
|
|
199
|
-
<AlertDialogCancel className="w-full sm:w-auto">Cancel</AlertDialogCancel>
|
|
200
|
-
<AlertDialogAction
|
|
201
|
-
onClick={handleConfirmClose}
|
|
202
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 w-full sm:w-auto"
|
|
203
|
-
>
|
|
204
|
-
Close Terminal
|
|
205
|
-
</AlertDialogAction>
|
|
206
|
-
</AlertDialogFooter>
|
|
207
|
-
</AlertDialogContent>
|
|
208
|
-
</AlertDialog>
|
|
209
|
-
</div>
|
|
210
|
-
)
|
|
211
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { TerminalTabs } from './TerminalTabs'
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
2
|
-
import { cn } from '@/lib/utils'
|
|
3
|
-
import type { VelocityBadgeProps } from './VelocityBadge.types'
|
|
4
|
-
|
|
5
|
-
function getChangeColor(change: number): string {
|
|
6
|
-
if (change >= 10) return 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
|
7
|
-
if (change >= 0) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
|
8
|
-
if (change >= -10) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
|
9
|
-
return 'bg-red-500/10 text-red-600 dark:text-red-400'
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function VelocityBadge({ change }: VelocityBadgeProps) {
|
|
13
|
-
if (change === 0) return null
|
|
14
|
-
|
|
15
|
-
const isPositive = change >= 0
|
|
16
|
-
const Icon = isPositive ? TrendingUp : TrendingDown
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div className="flex items-center gap-2 mt-2">
|
|
20
|
-
<span
|
|
21
|
-
className={cn(
|
|
22
|
-
'inline-flex items-center gap-1 text-xs sm:text-sm font-medium px-2 py-0.5 rounded-md',
|
|
23
|
-
getChangeColor(change)
|
|
24
|
-
)}
|
|
25
|
-
>
|
|
26
|
-
<Icon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
|
27
|
-
{isPositive ? '+' : ''}{change}%
|
|
28
|
-
</span>
|
|
29
|
-
<span className="text-xs sm:text-sm text-muted-foreground">vs last week</span>
|
|
30
|
-
</div>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { SparklineChart } from '@/components/SparklineChart'
|
|
4
|
-
import { Zap, TrendingUp, TrendingDown, Target } from 'lucide-react'
|
|
5
|
-
import { cn } from '@/lib/utils'
|
|
6
|
-
import type { VelocityCardProps } from './VelocityCard.types'
|
|
7
|
-
|
|
8
|
-
function getChangeColor(change: number): string {
|
|
9
|
-
if (change >= 10) return 'text-emerald-600 dark:text-emerald-400'
|
|
10
|
-
if (change >= 0) return 'text-amber-600 dark:text-amber-400'
|
|
11
|
-
if (change >= -10) return 'text-amber-600 dark:text-amber-400'
|
|
12
|
-
return 'text-red-600 dark:text-red-400'
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function VelocityCard({
|
|
16
|
-
tasksPerDay,
|
|
17
|
-
weeklyData = [],
|
|
18
|
-
change = 0,
|
|
19
|
-
estimateAccuracy,
|
|
20
|
-
className,
|
|
21
|
-
}: VelocityCardProps) {
|
|
22
|
-
return (
|
|
23
|
-
<div className={cn(
|
|
24
|
-
'relative overflow-hidden rounded-xl border bg-card p-4',
|
|
25
|
-
className
|
|
26
|
-
)}>
|
|
27
|
-
<div className="flex items-center justify-between mb-3">
|
|
28
|
-
<div className="flex items-center gap-2">
|
|
29
|
-
<Zap className="h-4 w-4 text-muted-foreground" />
|
|
30
|
-
<span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
|
|
31
|
-
Velocity
|
|
32
|
-
</span>
|
|
33
|
-
</div>
|
|
34
|
-
{change !== 0 && (
|
|
35
|
-
<div className={cn("flex items-center gap-1", getChangeColor(change))}>
|
|
36
|
-
{change >= 0 ? (
|
|
37
|
-
<TrendingUp className="h-3.5 w-3.5" />
|
|
38
|
-
) : (
|
|
39
|
-
<TrendingDown className="h-3.5 w-3.5" />
|
|
40
|
-
)}
|
|
41
|
-
<span className="text-xs font-bold">
|
|
42
|
-
{change >= 0 ? '+' : ''}{change}%
|
|
43
|
-
</span>
|
|
44
|
-
</div>
|
|
45
|
-
)}
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div className="flex items-end justify-between gap-4">
|
|
49
|
-
<div>
|
|
50
|
-
<p className="text-3xl font-bold tabular-nums">{tasksPerDay}</p>
|
|
51
|
-
<p className="text-xs text-muted-foreground">tasks/day avg</p>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
{weeklyData.length > 0 && (
|
|
55
|
-
<div className="flex-1 max-w-[120px]">
|
|
56
|
-
<SparklineChart data={weeklyData} height={40} />
|
|
57
|
-
<p className="text-xs text-muted-foreground text-right mt-1">Last 7 days</p>
|
|
58
|
-
</div>
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
{estimateAccuracy !== undefined && estimateAccuracy > 0 && (
|
|
63
|
-
<div className="flex items-center gap-1.5 mt-3 pt-3 border-t">
|
|
64
|
-
<Target className="h-3 w-3 text-muted-foreground" />
|
|
65
|
-
<span className="text-xs text-muted-foreground">Estimate accuracy:</span>
|
|
66
|
-
<span className="text-xs font-bold text-muted-foreground">
|
|
67
|
-
{estimateAccuracy}%
|
|
68
|
-
</span>
|
|
69
|
-
</div>
|
|
70
|
-
)}
|
|
71
|
-
</div>
|
|
72
|
-
)
|
|
73
|
-
}
|