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,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Colors - Consistent color generation based on projectId
|
|
3
|
-
*
|
|
4
|
-
* Uses a hash of the projectId to generate a consistent color
|
|
5
|
-
* that matches between UI elements and browser tab titles.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Color palette - visually distinct colors that work well in both
|
|
9
|
-
// Tailwind classes and as emojis
|
|
10
|
-
export const PROJECT_COLORS = [
|
|
11
|
-
{ name: 'red', emoji: '🔴', bg: 'bg-red-500', text: 'text-red-500' },
|
|
12
|
-
{ name: 'orange', emoji: '🟠', bg: 'bg-orange-500', text: 'text-orange-500' },
|
|
13
|
-
{ name: 'yellow', emoji: '🟡', bg: 'bg-yellow-500', text: 'text-yellow-500' },
|
|
14
|
-
{ name: 'green', emoji: '🟢', bg: 'bg-green-500', text: 'text-green-500' },
|
|
15
|
-
{ name: 'blue', emoji: '🔵', bg: 'bg-blue-500', text: 'text-blue-500' },
|
|
16
|
-
{ name: 'purple', emoji: '🟣', bg: 'bg-purple-500', text: 'text-purple-500' },
|
|
17
|
-
{ name: 'brown', emoji: '🟤', bg: 'bg-amber-700', text: 'text-amber-700' },
|
|
18
|
-
] as const
|
|
19
|
-
|
|
20
|
-
export type ProjectColor = typeof PROJECT_COLORS[number]
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Simple hash function for strings
|
|
24
|
-
*/
|
|
25
|
-
function hashString(str: string): number {
|
|
26
|
-
let hash = 0
|
|
27
|
-
for (let i = 0; i < str.length; i++) {
|
|
28
|
-
const char = str.charCodeAt(i)
|
|
29
|
-
hash = ((hash << 5) - hash) + char
|
|
30
|
-
hash = hash & hash // Convert to 32bit integer
|
|
31
|
-
}
|
|
32
|
-
return Math.abs(hash)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get a consistent color for a project based on its ID
|
|
37
|
-
*/
|
|
38
|
-
export function getProjectColor(projectId: string): ProjectColor {
|
|
39
|
-
const hash = hashString(projectId)
|
|
40
|
-
const index = hash % PROJECT_COLORS.length
|
|
41
|
-
return PROJECT_COLORS[index]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get just the emoji for a project (for use in titles)
|
|
46
|
-
* Returns a neutral symbol instead of colored circles
|
|
47
|
-
*/
|
|
48
|
-
export function getProjectEmoji(projectId: string): string {
|
|
49
|
-
return '▸'
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get the Tailwind background class for a project
|
|
54
|
-
* Returns neutral color - projects no longer have color-coded backgrounds
|
|
55
|
-
*/
|
|
56
|
-
export function getProjectBgClass(projectId: string): string {
|
|
57
|
-
return 'bg-muted'
|
|
58
|
-
}
|
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project utilities for prjct
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { promises as fs } from 'fs'
|
|
6
|
-
import { join, dirname } from 'path'
|
|
7
|
-
import { homedir } from 'os'
|
|
8
|
-
import { exec } from 'child_process'
|
|
9
|
-
import { promisify } from 'util'
|
|
10
|
-
import { listSessions } from './pty'
|
|
11
|
-
|
|
12
|
-
const execAsync = promisify(exec)
|
|
13
|
-
|
|
14
|
-
export const GLOBAL_STORAGE = join(homedir(), '.prjct-cli', 'projects')
|
|
15
|
-
export const TRASH_PATH = join(homedir(), '.prjct-cli', '.trash')
|
|
16
|
-
|
|
17
|
-
// Cache for project paths (projectId -> real path)
|
|
18
|
-
const projectPathCache = new Map<string, string>()
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Scan common directories for .prjct/prjct.config.json files
|
|
22
|
-
*/
|
|
23
|
-
export async function scanForProjects(): Promise<Map<string, string>> {
|
|
24
|
-
const searchPaths = [
|
|
25
|
-
join(homedir(), 'Apps'),
|
|
26
|
-
join(homedir(), 'Projects'),
|
|
27
|
-
join(homedir(), 'Documents'),
|
|
28
|
-
join(homedir(), 'Development'),
|
|
29
|
-
join(homedir(), 'Code'),
|
|
30
|
-
join(homedir(), 'dev'),
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
for (const searchPath of searchPaths) {
|
|
34
|
-
try {
|
|
35
|
-
const { stdout } = await execAsync(
|
|
36
|
-
`find "${searchPath}" -maxdepth 4 -type f -name "prjct.config.json" -path "*/.prjct/*" 2>/dev/null`,
|
|
37
|
-
{ timeout: 5000 }
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
const configFiles = stdout.trim().split('\n').filter(Boolean)
|
|
41
|
-
|
|
42
|
-
for (const configFile of configFiles) {
|
|
43
|
-
try {
|
|
44
|
-
const content = await fs.readFile(configFile, 'utf-8')
|
|
45
|
-
const config = JSON.parse(content)
|
|
46
|
-
if (config.projectId) {
|
|
47
|
-
const projectPath = dirname(dirname(configFile))
|
|
48
|
-
projectPathCache.set(config.projectId, projectPath)
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
// Skip invalid config files
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
// Skip directories that don't exist or are not accessible
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return projectPathCache
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Extract project path from CLAUDE.md
|
|
64
|
-
*/
|
|
65
|
-
export function extractProjectPath(claudeMd: string): string | null {
|
|
66
|
-
const pathMatch = claudeMd.match(/(?:Path|Location|Directory):\s*`?([^\n`]+)`?/i)
|
|
67
|
-
if (pathMatch) return pathMatch[1].trim()
|
|
68
|
-
|
|
69
|
-
const infoMatch = claudeMd.match(/\*\*Path\*\*:\s*`?([^\n`]+)`?/i)
|
|
70
|
-
if (infoMatch) return infoMatch[1].trim()
|
|
71
|
-
|
|
72
|
-
return null
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get all projects with rich metadata
|
|
77
|
-
*/
|
|
78
|
-
export async function getProjects() {
|
|
79
|
-
if (projectPathCache.size === 0) {
|
|
80
|
-
await scanForProjects()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const projects = []
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const dirs = await fs.readdir(GLOBAL_STORAGE)
|
|
87
|
-
|
|
88
|
-
for (const projectId of dirs) {
|
|
89
|
-
// Skip hidden directories like .trash
|
|
90
|
-
if (projectId.startsWith('.')) continue
|
|
91
|
-
|
|
92
|
-
const storagePath = join(GLOBAL_STORAGE, projectId)
|
|
93
|
-
|
|
94
|
-
// Try project.json first (source of truth)
|
|
95
|
-
let name: string = projectId
|
|
96
|
-
let repoPath: string | null = null
|
|
97
|
-
let techStack: string[] = []
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const projectJsonPath = join(storagePath, 'project.json')
|
|
101
|
-
const projectJson = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
|
|
102
|
-
name = projectJson.name || projectId
|
|
103
|
-
repoPath = projectJson.repoPath || null
|
|
104
|
-
techStack = projectJson.techStack || []
|
|
105
|
-
} catch {
|
|
106
|
-
// Fallback to CLAUDE.md for name/repoPath only
|
|
107
|
-
// techStack comes from project.json (populated by /p:sync)
|
|
108
|
-
try {
|
|
109
|
-
const claudeMd = await fs.readFile(join(storagePath, 'CLAUDE.md'), 'utf-8')
|
|
110
|
-
const nameMatch = claudeMd.match(/# (.+) - Project Context/)
|
|
111
|
-
if (nameMatch) name = nameMatch[1]
|
|
112
|
-
|
|
113
|
-
const cachedPath = projectPathCache.get(projectId)
|
|
114
|
-
repoPath = cachedPath || extractProjectPath(claudeMd)
|
|
115
|
-
} catch {
|
|
116
|
-
// Skip projects without valid config
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Get current task
|
|
122
|
-
let currentTask: string | null = null
|
|
123
|
-
try {
|
|
124
|
-
const nowContent = await fs.readFile(join(storagePath, 'core', 'now.md'), 'utf-8')
|
|
125
|
-
// Skip headers like "# NOW", "# Current Task" and find the actual task content
|
|
126
|
-
// Look for **bold text** (task description) or first non-header, non-metadata line
|
|
127
|
-
const boldMatch = nowContent.match(/\*\*([^*]+)\*\*/)
|
|
128
|
-
if (boldMatch && boldMatch[1].trim() && !boldMatch[1].includes(':')) {
|
|
129
|
-
currentTask = boldMatch[1].trim()
|
|
130
|
-
} else {
|
|
131
|
-
// Find first content line that's not a header, metadata, or empty
|
|
132
|
-
const lines = nowContent.split('\n')
|
|
133
|
-
for (const line of lines) {
|
|
134
|
-
const trimmed = line.trim()
|
|
135
|
-
// Skip headers, empty lines, metadata lines (key: value), and "No active/current task" messages
|
|
136
|
-
if (trimmed &&
|
|
137
|
-
!trimmed.startsWith('#') &&
|
|
138
|
-
!trimmed.toLowerCase().includes('no active task') &&
|
|
139
|
-
!trimmed.toLowerCase().includes('no current task') &&
|
|
140
|
-
!trimmed.match(/^(Feature|Started|Status|Agent):/i) &&
|
|
141
|
-
!trimmed.startsWith('**') &&
|
|
142
|
-
!trimmed.startsWith('-')) {
|
|
143
|
-
currentTask = trimmed
|
|
144
|
-
break
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// Truncate if too long
|
|
149
|
-
if (currentTask && currentTask.length > 60) {
|
|
150
|
-
currentTask = currentTask.substring(0, 57) + '...'
|
|
151
|
-
}
|
|
152
|
-
} catch {}
|
|
153
|
-
|
|
154
|
-
// Get session status and last activity
|
|
155
|
-
let hasActiveSession = false
|
|
156
|
-
let lastActivity: string | null = null
|
|
157
|
-
|
|
158
|
-
// Check for real PTY sessions (actual Claude sessions in memory)
|
|
159
|
-
try {
|
|
160
|
-
const activeSessions = listSessions()
|
|
161
|
-
hasActiveSession = activeSessions.some(s => s.projectDir === repoPath)
|
|
162
|
-
} catch {}
|
|
163
|
-
|
|
164
|
-
// Try current session for lastActivity only
|
|
165
|
-
try {
|
|
166
|
-
const sessionPath = join(storagePath, 'sessions', 'current.json')
|
|
167
|
-
const sessionData = JSON.parse(await fs.readFile(sessionPath, 'utf-8'))
|
|
168
|
-
lastActivity = sessionData.startedAt || sessionData.updatedAt
|
|
169
|
-
} catch {}
|
|
170
|
-
|
|
171
|
-
// If no session, get last modified time from key files
|
|
172
|
-
if (!lastActivity) {
|
|
173
|
-
const filesToCheck = [
|
|
174
|
-
join(storagePath, 'core', 'now.md'),
|
|
175
|
-
join(storagePath, 'core', 'next.md'),
|
|
176
|
-
join(storagePath, 'planning', 'ideas.md'),
|
|
177
|
-
join(storagePath, 'progress', 'shipped.md'),
|
|
178
|
-
join(storagePath, 'memory', 'context.jsonl'),
|
|
179
|
-
join(storagePath, 'CLAUDE.md')
|
|
180
|
-
]
|
|
181
|
-
|
|
182
|
-
let latestMtime = 0
|
|
183
|
-
for (const filePath of filesToCheck) {
|
|
184
|
-
try {
|
|
185
|
-
const stat = await fs.stat(filePath)
|
|
186
|
-
if (stat.mtimeMs > latestMtime) {
|
|
187
|
-
latestMtime = stat.mtimeMs
|
|
188
|
-
}
|
|
189
|
-
} catch {}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (latestMtime > 0) {
|
|
193
|
-
lastActivity = new Date(latestMtime).toISOString()
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Count ideas, next tasks, and shipped items
|
|
198
|
-
let ideasCount = 0
|
|
199
|
-
let nextTasksCount = 0
|
|
200
|
-
let shippedCount = 0
|
|
201
|
-
try {
|
|
202
|
-
const ideasContent = await fs.readFile(join(storagePath, 'planning', 'ideas.md'), 'utf-8')
|
|
203
|
-
ideasCount = (ideasContent.match(/^- /gm) || []).length
|
|
204
|
-
} catch {}
|
|
205
|
-
try {
|
|
206
|
-
const nextContent = await fs.readFile(join(storagePath, 'core', 'next.md'), 'utf-8')
|
|
207
|
-
nextTasksCount = (nextContent.match(/^- /gm) || []).length
|
|
208
|
-
} catch {}
|
|
209
|
-
try {
|
|
210
|
-
const shippedContent = await fs.readFile(join(storagePath, 'progress', 'shipped.md'), 'utf-8')
|
|
211
|
-
// Count shipped items: either "- **Name**" or "### Name" format
|
|
212
|
-
const bulletItems = (shippedContent.match(/^- \*\*/gm) || []).length
|
|
213
|
-
const headingItems = (shippedContent.match(/^### /gm) || []).length
|
|
214
|
-
shippedCount = bulletItems + headingItems
|
|
215
|
-
} catch {}
|
|
216
|
-
|
|
217
|
-
// Find favicon/icon in project repo
|
|
218
|
-
let iconPath: string | null = null
|
|
219
|
-
if (repoPath) {
|
|
220
|
-
const iconPatterns = [
|
|
221
|
-
'public/favicon.ico',
|
|
222
|
-
'public/favicon.svg',
|
|
223
|
-
'public/icon.svg',
|
|
224
|
-
'public/icon.png',
|
|
225
|
-
'public/logo.svg',
|
|
226
|
-
'public/logo.png',
|
|
227
|
-
'app/favicon.ico',
|
|
228
|
-
'app/icon.svg',
|
|
229
|
-
'app/icon.png',
|
|
230
|
-
'favicon.ico',
|
|
231
|
-
'favicon.svg'
|
|
232
|
-
]
|
|
233
|
-
for (const pattern of iconPatterns) {
|
|
234
|
-
try {
|
|
235
|
-
const fullPath = join(repoPath, pattern)
|
|
236
|
-
await fs.access(fullPath)
|
|
237
|
-
iconPath = fullPath
|
|
238
|
-
break
|
|
239
|
-
} catch {}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
projects.push({
|
|
244
|
-
id: projectId,
|
|
245
|
-
name,
|
|
246
|
-
path: repoPath || storagePath,
|
|
247
|
-
repoPath,
|
|
248
|
-
storagePath,
|
|
249
|
-
currentTask,
|
|
250
|
-
hasActiveSession,
|
|
251
|
-
lastActivity,
|
|
252
|
-
ideasCount,
|
|
253
|
-
nextTasksCount,
|
|
254
|
-
shippedCount,
|
|
255
|
-
techStack,
|
|
256
|
-
iconPath
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
} catch {
|
|
260
|
-
// Storage directory doesn't exist
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Sort by lastActivity (most recent first), then by name
|
|
264
|
-
projects.sort((a, b) => {
|
|
265
|
-
if (a.lastActivity && b.lastActivity) {
|
|
266
|
-
return new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime()
|
|
267
|
-
}
|
|
268
|
-
if (a.lastActivity) return -1
|
|
269
|
-
if (b.lastActivity) return 1
|
|
270
|
-
return a.name.localeCompare(b.name)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
return projects
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Get project by ID
|
|
278
|
-
*/
|
|
279
|
-
export async function getProject(projectId: string) {
|
|
280
|
-
const storagePath = join(GLOBAL_STORAGE, projectId)
|
|
281
|
-
|
|
282
|
-
// 1. Try to read from project.json (source of truth for dashboard)
|
|
283
|
-
let repoPath: string | null = null
|
|
284
|
-
let name: string = projectId
|
|
285
|
-
let version: string | null = null
|
|
286
|
-
let stack: string | null = null
|
|
287
|
-
let filesCount: string | null = null
|
|
288
|
-
let commitsCount: string | null = null
|
|
289
|
-
let techStack: string[] = []
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
const projectJsonPath = join(storagePath, 'project.json')
|
|
293
|
-
const projectJson = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
|
|
294
|
-
repoPath = projectJson.repoPath || null
|
|
295
|
-
name = projectJson.name || projectId
|
|
296
|
-
version = projectJson.version || null
|
|
297
|
-
stack = projectJson.stack || null
|
|
298
|
-
filesCount = projectJson.fileCount ? String(projectJson.fileCount) : null
|
|
299
|
-
commitsCount = projectJson.commitCount ? String(projectJson.commitCount) : null
|
|
300
|
-
techStack = projectJson.techStack || []
|
|
301
|
-
} catch {
|
|
302
|
-
// project.json doesn't exist - fallback to scan
|
|
303
|
-
if (projectPathCache.size === 0) {
|
|
304
|
-
await scanForProjects()
|
|
305
|
-
}
|
|
306
|
-
repoPath = projectPathCache.get(projectId) || null
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
const claudeMd = await fs.readFile(join(storagePath, 'CLAUDE.md'), 'utf-8')
|
|
311
|
-
|
|
312
|
-
// If still no repoPath, try extracting from CLAUDE.md
|
|
313
|
-
if (!repoPath) {
|
|
314
|
-
repoPath = extractProjectPath(claudeMd)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// If name is still projectId, try CLAUDE.md
|
|
318
|
-
if (name === projectId) {
|
|
319
|
-
const nameMatch = claudeMd.match(/# (.+) - Project Context/)
|
|
320
|
-
if (nameMatch) name = nameMatch[1]
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
let currentSession = null
|
|
324
|
-
try {
|
|
325
|
-
const sessionPath = join(storagePath, 'sessions', 'current.json')
|
|
326
|
-
const sessionData = await fs.readFile(sessionPath, 'utf-8')
|
|
327
|
-
currentSession = JSON.parse(sessionData)
|
|
328
|
-
} catch {}
|
|
329
|
-
|
|
330
|
-
let currentTask = null
|
|
331
|
-
try {
|
|
332
|
-
const nowPath = join(storagePath, 'core', 'now.md')
|
|
333
|
-
currentTask = await fs.readFile(nowPath, 'utf-8')
|
|
334
|
-
} catch {}
|
|
335
|
-
|
|
336
|
-
// Count shipped and queue items (DRY - same logic as getProjects)
|
|
337
|
-
let shippedCount = 0
|
|
338
|
-
let nextTasksCount = 0
|
|
339
|
-
try {
|
|
340
|
-
const shippedContent = await fs.readFile(join(storagePath, 'progress', 'shipped.md'), 'utf-8')
|
|
341
|
-
const bulletItems = (shippedContent.match(/^- \*\*/gm) || []).length
|
|
342
|
-
const headingItems = (shippedContent.match(/^### /gm) || []).length
|
|
343
|
-
shippedCount = bulletItems + headingItems
|
|
344
|
-
} catch {}
|
|
345
|
-
try {
|
|
346
|
-
const nextContent = await fs.readFile(join(storagePath, 'core', 'next.md'), 'utf-8')
|
|
347
|
-
nextTasksCount = (nextContent.match(/^- /gm) || []).length
|
|
348
|
-
} catch {}
|
|
349
|
-
|
|
350
|
-
const total = shippedCount + nextTasksCount
|
|
351
|
-
const completionRate = total > 0 ? Math.round((shippedCount / total) * 100) : 0
|
|
352
|
-
|
|
353
|
-
// Fallback: Extract stats from claudeMd Quick Reference table if not from project.json
|
|
354
|
-
if (!version) {
|
|
355
|
-
const versionMatch = claudeMd.match(/\*\*Version\*\*\s*\|\s*([^\n|]+)/)
|
|
356
|
-
if (versionMatch) version = versionMatch[1].trim()
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (!stack) {
|
|
360
|
-
const stackMatch = claudeMd.match(/\*\*Stack\*\*\s*\|\s*([^\n|]+)/)
|
|
361
|
-
if (stackMatch) stack = stackMatch[1].trim()
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (!filesCount) {
|
|
365
|
-
const filesMatch = claudeMd.match(/\*\*Files\*\*\s*\|\s*([^\n|]+)/)
|
|
366
|
-
if (filesMatch) filesCount = filesMatch[1].trim()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!commitsCount) {
|
|
370
|
-
const commitsMatch = claudeMd.match(/\*\*Commits\*\*\s*\|\s*([^\n|]+)/)
|
|
371
|
-
if (commitsMatch) commitsCount = commitsMatch[1].trim()
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Find favicon/icon in project repo
|
|
375
|
-
let iconPath: string | null = null
|
|
376
|
-
if (repoPath) {
|
|
377
|
-
const iconPatterns = [
|
|
378
|
-
'public/favicon.ico',
|
|
379
|
-
'public/favicon.svg',
|
|
380
|
-
'public/icon.svg',
|
|
381
|
-
'public/icon.png',
|
|
382
|
-
'public/logo.svg',
|
|
383
|
-
'public/logo.png',
|
|
384
|
-
'app/favicon.ico',
|
|
385
|
-
'app/icon.svg',
|
|
386
|
-
'app/icon.png',
|
|
387
|
-
'favicon.ico',
|
|
388
|
-
'favicon.svg'
|
|
389
|
-
]
|
|
390
|
-
for (const pattern of iconPatterns) {
|
|
391
|
-
try {
|
|
392
|
-
const fullPath = join(repoPath, pattern)
|
|
393
|
-
await fs.access(fullPath)
|
|
394
|
-
iconPath = fullPath
|
|
395
|
-
break
|
|
396
|
-
} catch {}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return {
|
|
401
|
-
id: projectId,
|
|
402
|
-
name,
|
|
403
|
-
path: storagePath, // Storage path (for prjct data)
|
|
404
|
-
repoPath, // Real repository path (for terminal/Claude)
|
|
405
|
-
storagePath,
|
|
406
|
-
claudeMd,
|
|
407
|
-
currentSession,
|
|
408
|
-
currentTask,
|
|
409
|
-
// Parsed stats
|
|
410
|
-
version,
|
|
411
|
-
stack,
|
|
412
|
-
filesCount,
|
|
413
|
-
commitsCount,
|
|
414
|
-
techStack,
|
|
415
|
-
iconPath,
|
|
416
|
-
// Counts (DRY - same source as dashboard)
|
|
417
|
-
shippedCount,
|
|
418
|
-
nextTasksCount,
|
|
419
|
-
completionRate
|
|
420
|
-
}
|
|
421
|
-
} catch {
|
|
422
|
-
return null
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Move project to trash (soft delete)
|
|
428
|
-
*/
|
|
429
|
-
export async function moveToTrash(projectId: string) {
|
|
430
|
-
const sourcePath = join(GLOBAL_STORAGE, projectId)
|
|
431
|
-
const trashPath = join(TRASH_PATH, projectId)
|
|
432
|
-
|
|
433
|
-
// Verify source exists
|
|
434
|
-
try {
|
|
435
|
-
await fs.access(sourcePath)
|
|
436
|
-
} catch {
|
|
437
|
-
throw new Error(`Project ${projectId} not found`)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Create trash directory if it doesn't exist
|
|
441
|
-
await fs.mkdir(TRASH_PATH, { recursive: true })
|
|
442
|
-
|
|
443
|
-
// Move to trash
|
|
444
|
-
await fs.rename(sourcePath, trashPath)
|
|
445
|
-
|
|
446
|
-
// Write deletion metadata
|
|
447
|
-
const deletedAt = new Date().toISOString()
|
|
448
|
-
await fs.writeFile(
|
|
449
|
-
join(trashPath, '.deleted'),
|
|
450
|
-
JSON.stringify({ deletedAt, projectId })
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
return { trashedAt: deletedAt }
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Get project status
|
|
458
|
-
*/
|
|
459
|
-
export async function getProjectStatus(projectId: string) {
|
|
460
|
-
const projectPath = join(GLOBAL_STORAGE, projectId)
|
|
461
|
-
|
|
462
|
-
let session = null
|
|
463
|
-
let repoPath: string | null = null
|
|
464
|
-
try {
|
|
465
|
-
const sessionPath = join(projectPath, 'sessions', 'current.json')
|
|
466
|
-
session = JSON.parse(await fs.readFile(sessionPath, 'utf-8'))
|
|
467
|
-
} catch {}
|
|
468
|
-
|
|
469
|
-
// Get repoPath from project.json for PTY session check
|
|
470
|
-
try {
|
|
471
|
-
const projectJsonPath = join(projectPath, 'project.json')
|
|
472
|
-
const projectJson = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
|
|
473
|
-
repoPath = projectJson.repoPath || null
|
|
474
|
-
} catch {}
|
|
475
|
-
|
|
476
|
-
// Check for real PTY sessions
|
|
477
|
-
let hasActiveSession = false
|
|
478
|
-
if (repoPath) {
|
|
479
|
-
try {
|
|
480
|
-
const activeSessions = listSessions()
|
|
481
|
-
hasActiveSession = activeSessions.some(s => s.projectDir === repoPath)
|
|
482
|
-
} catch {}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
let ideas: string[] = []
|
|
486
|
-
try {
|
|
487
|
-
const ideasPath = join(projectPath, 'planning', 'ideas.md')
|
|
488
|
-
const content = await fs.readFile(ideasPath, 'utf-8')
|
|
489
|
-
ideas = content.split('\n').filter(l => l.startsWith('- ')).slice(0, 5)
|
|
490
|
-
} catch {}
|
|
491
|
-
|
|
492
|
-
let nextTasks: string[] = []
|
|
493
|
-
try {
|
|
494
|
-
const nextPath = join(projectPath, 'core', 'next.md')
|
|
495
|
-
const content = await fs.readFile(nextPath, 'utf-8')
|
|
496
|
-
nextTasks = content.split('\n').filter(l => l.startsWith('- ')).slice(0, 5)
|
|
497
|
-
} catch {}
|
|
498
|
-
|
|
499
|
-
return {
|
|
500
|
-
projectId,
|
|
501
|
-
session,
|
|
502
|
-
hasActiveSession,
|
|
503
|
-
ideas,
|
|
504
|
-
nextTasks
|
|
505
|
-
}
|
|
506
|
-
}
|
package/packages/web/lib/pty.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PTY Manager - Handle Claude Code CLI sessions via pseudo-terminal
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as pty from 'node-pty'
|
|
6
|
-
import type { IPty } from 'node-pty'
|
|
7
|
-
|
|
8
|
-
interface Session {
|
|
9
|
-
pty: IPty
|
|
10
|
-
projectDir: string
|
|
11
|
-
createdAt: Date
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const sessions = new Map<string, Session>()
|
|
15
|
-
|
|
16
|
-
export function createClaudeSession(sessionId: string, projectDir: string): IPty {
|
|
17
|
-
// Kill existing session if any
|
|
18
|
-
const existing = sessions.get(sessionId)
|
|
19
|
-
if (existing) {
|
|
20
|
-
try {
|
|
21
|
-
existing.pty.kill()
|
|
22
|
-
} catch {
|
|
23
|
-
// Ignore
|
|
24
|
-
}
|
|
25
|
-
sessions.delete(sessionId)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Spawn claude CLI
|
|
29
|
-
const shell = process.platform === 'win32' ? 'cmd.exe' : 'bash'
|
|
30
|
-
const args = process.platform === 'win32' ? [] : ['-l']
|
|
31
|
-
|
|
32
|
-
const ptyProcess = pty.spawn(shell, args, {
|
|
33
|
-
name: 'xterm-256color',
|
|
34
|
-
cols: 120,
|
|
35
|
-
rows: 30,
|
|
36
|
-
cwd: projectDir,
|
|
37
|
-
env: {
|
|
38
|
-
...process.env,
|
|
39
|
-
TERM: 'xterm-256color',
|
|
40
|
-
COLORTERM: 'truecolor'
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Store session
|
|
45
|
-
sessions.set(sessionId, {
|
|
46
|
-
pty: ptyProcess,
|
|
47
|
-
projectDir,
|
|
48
|
-
createdAt: new Date()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Auto-start Claude Code CLI
|
|
52
|
-
setTimeout(() => {
|
|
53
|
-
ptyProcess.write('claude\r')
|
|
54
|
-
}, 500)
|
|
55
|
-
|
|
56
|
-
return ptyProcess
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getSession(sessionId: string): IPty | null {
|
|
60
|
-
const session = sessions.get(sessionId)
|
|
61
|
-
return session?.pty || null
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function killSession(sessionId: string): boolean {
|
|
65
|
-
const session = sessions.get(sessionId)
|
|
66
|
-
if (session) {
|
|
67
|
-
try {
|
|
68
|
-
session.pty.kill()
|
|
69
|
-
} catch {
|
|
70
|
-
// Ignore
|
|
71
|
-
}
|
|
72
|
-
sessions.delete(sessionId)
|
|
73
|
-
return true
|
|
74
|
-
}
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function resizeSession(sessionId: string, cols: number, rows: number): boolean {
|
|
79
|
-
const session = sessions.get(sessionId)
|
|
80
|
-
if (session) {
|
|
81
|
-
try {
|
|
82
|
-
session.pty.resize(cols, rows)
|
|
83
|
-
return true
|
|
84
|
-
} catch {
|
|
85
|
-
return false
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function listSessions(): { sessionId: string; projectDir: string; createdAt: Date }[] {
|
|
92
|
-
const result: { sessionId: string; projectDir: string; createdAt: Date }[] = []
|
|
93
|
-
sessions.forEach((session, sessionId) => {
|
|
94
|
-
result.push({
|
|
95
|
-
sessionId,
|
|
96
|
-
projectDir: session.projectDir,
|
|
97
|
-
createdAt: session.createdAt
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { UseQueryOptions } from '@tanstack/react-query'
|
|
2
|
-
|
|
3
|
-
// Refresh intervals in milliseconds
|
|
4
|
-
export const REFRESH_INTERVALS = {
|
|
5
|
-
realtime: 2000, // 2s - for active sessions, connection status
|
|
6
|
-
fast: 5000, // 5s - for project status, current task
|
|
7
|
-
normal: 10000, // 10s - for project list, stats
|
|
8
|
-
slow: 30000, // 30s - for historical data
|
|
9
|
-
} as const
|
|
10
|
-
|
|
11
|
-
// Default query options for different data freshness needs
|
|
12
|
-
export const queryPresets = {
|
|
13
|
-
// For data that needs to feel "live" (sessions, connection status)
|
|
14
|
-
realtime: {
|
|
15
|
-
staleTime: 0,
|
|
16
|
-
refetchInterval: REFRESH_INTERVALS.realtime,
|
|
17
|
-
refetchOnWindowFocus: true,
|
|
18
|
-
refetchOnReconnect: true,
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
// For frequently changing data (project status, tasks)
|
|
22
|
-
fast: {
|
|
23
|
-
staleTime: REFRESH_INTERVALS.fast / 2,
|
|
24
|
-
refetchInterval: REFRESH_INTERVALS.fast,
|
|
25
|
-
refetchOnWindowFocus: true,
|
|
26
|
-
refetchOnReconnect: true,
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
// For moderately changing data (project list, stats)
|
|
30
|
-
normal: {
|
|
31
|
-
staleTime: REFRESH_INTERVALS.normal / 2,
|
|
32
|
-
refetchInterval: REFRESH_INTERVALS.normal,
|
|
33
|
-
refetchOnWindowFocus: true,
|
|
34
|
-
refetchOnReconnect: true,
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
// For rarely changing data (settings, historical)
|
|
38
|
-
slow: {
|
|
39
|
-
staleTime: REFRESH_INTERVALS.slow / 2,
|
|
40
|
-
refetchInterval: REFRESH_INTERVALS.slow,
|
|
41
|
-
refetchOnWindowFocus: true,
|
|
42
|
-
refetchOnReconnect: false,
|
|
43
|
-
},
|
|
44
|
-
} as const satisfies Record<string, Partial<UseQueryOptions>>
|