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.
Files changed (246) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/CLAUDE.md +74 -211
  3. package/core/agentic/prompt-builder.ts +3 -7
  4. package/core/command-registry/optional-commands.ts +0 -20
  5. package/core/infrastructure/command-installer/command-installer.ts +8 -1
  6. package/core/infrastructure/command-installer/global-config.ts +31 -1
  7. package/core/infrastructure/command-installer/index.ts +1 -1
  8. package/core/infrastructure/setup.ts +3 -0
  9. package/package.json +3 -17
  10. package/templates/agentic/agents/uxui.md +210 -0
  11. package/templates/commands/bug.md +219 -41
  12. package/templates/commands/done.md +57 -258
  13. package/templates/commands/feature.md +368 -80
  14. package/templates/commands/now.md +72 -277
  15. package/templates/commands/ship.md +167 -246
  16. package/templates/commands/sync.md +62 -3
  17. package/templates/commands/test.md +160 -20
  18. package/templates/global/CLAUDE.md +40 -205
  19. package/templates/global/docs/agents.md +88 -0
  20. package/templates/global/docs/architecture.md +103 -0
  21. package/templates/global/docs/commands.md +98 -0
  22. package/templates/global/docs/validation.md +95 -0
  23. package/bin/dev.js +0 -216
  24. package/bin/serve.js +0 -361
  25. package/packages/web/README.md +0 -36
  26. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  27. package/packages/web/app/api/claude/status/route.ts +0 -34
  28. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  29. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  30. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  31. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  32. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  33. package/packages/web/app/api/projects/route.ts +0 -16
  34. package/packages/web/app/api/sessions/current/route.ts +0 -132
  35. package/packages/web/app/api/sessions/history/route.ts +0 -204
  36. package/packages/web/app/error.tsx +0 -34
  37. package/packages/web/app/favicon.ico +0 -0
  38. package/packages/web/app/globals.css +0 -198
  39. package/packages/web/app/layout.tsx +0 -53
  40. package/packages/web/app/loading.tsx +0 -7
  41. package/packages/web/app/not-found.tsx +0 -25
  42. package/packages/web/app/page.tsx +0 -12
  43. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  44. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  45. package/packages/web/app/project/[id]/error.tsx +0 -41
  46. package/packages/web/app/project/[id]/loading.tsx +0 -9
  47. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  48. package/packages/web/app/project/[id]/page.tsx +0 -384
  49. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  50. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  51. package/packages/web/app/sessions/page.tsx +0 -165
  52. package/packages/web/app/settings/page.tsx +0 -151
  53. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  54. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  55. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  56. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  57. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  58. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  59. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  60. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  61. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  62. package/packages/web/components/AgentsCard/index.ts +0 -2
  63. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  64. package/packages/web/components/AppSidebar/index.ts +0 -1
  65. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  66. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  67. package/packages/web/components/BackLink/index.ts +0 -2
  68. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  69. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  70. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  71. package/packages/web/components/BentoCard/index.ts +0 -2
  72. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  73. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  74. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  75. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  76. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  77. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  78. package/packages/web/components/BentoGrid/index.ts +0 -2
  79. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  80. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  81. package/packages/web/components/BlockersCard/index.ts +0 -2
  82. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  83. package/packages/web/components/CommandBar/index.ts +0 -1
  84. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  85. package/packages/web/components/CommandButton/index.ts +0 -1
  86. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  87. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  88. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  89. package/packages/web/components/DashboardContent/index.ts +0 -1
  90. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  91. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  92. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  93. package/packages/web/components/DateGroup/index.ts +0 -2
  94. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  95. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  96. package/packages/web/components/EmptyState/index.ts +0 -2
  97. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  98. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  99. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  100. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  101. package/packages/web/components/EventRow/index.ts +0 -2
  102. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  103. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  104. package/packages/web/components/ExpandButton/index.ts +0 -2
  105. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  106. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  107. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  108. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  109. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  110. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  111. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  112. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  113. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  114. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  115. package/packages/web/components/HeroSection/index.ts +0 -2
  116. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  117. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  118. package/packages/web/components/IdeasCard/index.ts +0 -2
  119. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  120. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  121. package/packages/web/components/InsightMessage/index.ts +0 -2
  122. package/packages/web/components/Logo/Logo.tsx +0 -65
  123. package/packages/web/components/Logo/index.ts +0 -1
  124. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  125. package/packages/web/components/MarkdownContent/index.ts +0 -1
  126. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  127. package/packages/web/components/MasonryGrid/index.ts +0 -1
  128. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  129. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  130. package/packages/web/components/MomentumWidget/index.ts +0 -2
  131. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  132. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  133. package/packages/web/components/NowCard/index.ts +0 -2
  134. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  135. package/packages/web/components/PageHeader/index.ts +0 -1
  136. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  137. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  138. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  139. package/packages/web/components/ProgressRing/index.ts +0 -2
  140. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  141. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  142. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  143. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  144. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  145. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  146. package/packages/web/components/Providers/Providers.tsx +0 -48
  147. package/packages/web/components/Providers/index.ts +0 -1
  148. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  149. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  150. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  151. package/packages/web/components/QueueCard/index.ts +0 -2
  152. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  153. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  154. package/packages/web/components/RecoverCard/index.ts +0 -2
  155. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  156. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  157. package/packages/web/components/RoadmapCard/index.ts +0 -2
  158. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  159. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  160. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  161. package/packages/web/components/ShipsCard/index.ts +0 -2
  162. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  163. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  164. package/packages/web/components/SparklineChart/index.ts +0 -2
  165. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  166. package/packages/web/components/StatsMasonry/index.ts +0 -1
  167. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  168. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  169. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  170. package/packages/web/components/StreakCard/index.ts +0 -2
  171. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  172. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  173. package/packages/web/components/TasksCounter/index.ts +0 -2
  174. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  175. package/packages/web/components/TechStackBadges/index.ts +0 -1
  176. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  177. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  178. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  179. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  180. package/packages/web/components/TerminalDock/index.ts +0 -2
  181. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  182. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  183. package/packages/web/components/TerminalTabs/index.ts +0 -1
  184. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  185. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  186. package/packages/web/components/VelocityBadge/index.ts +0 -2
  187. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  188. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  189. package/packages/web/components/VelocityCard/index.ts +0 -2
  190. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  191. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  192. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  193. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  194. package/packages/web/components/WeeklyReports/index.ts +0 -4
  195. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  196. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  197. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  198. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  199. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  200. package/packages/web/components/ui/badge.tsx +0 -46
  201. package/packages/web/components/ui/button.tsx +0 -60
  202. package/packages/web/components/ui/card.tsx +0 -92
  203. package/packages/web/components/ui/chart.tsx +0 -385
  204. package/packages/web/components/ui/dialog.tsx +0 -143
  205. package/packages/web/components/ui/drawer.tsx +0 -135
  206. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  207. package/packages/web/components/ui/input.tsx +0 -21
  208. package/packages/web/components/ui/scroll-area.tsx +0 -58
  209. package/packages/web/components/ui/select.tsx +0 -187
  210. package/packages/web/components/ui/sheet.tsx +0 -139
  211. package/packages/web/components/ui/tabs.tsx +0 -66
  212. package/packages/web/components/ui/tooltip.tsx +0 -61
  213. package/packages/web/components.json +0 -22
  214. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  215. package/packages/web/context/TerminalContext.tsx +0 -45
  216. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  217. package/packages/web/eslint.config.mjs +0 -18
  218. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  219. package/packages/web/hooks/useProjectStats.ts +0 -93
  220. package/packages/web/hooks/useProjects.ts +0 -73
  221. package/packages/web/lib/actions/projects.ts +0 -15
  222. package/packages/web/lib/commands.ts +0 -81
  223. package/packages/web/lib/format.ts +0 -23
  224. package/packages/web/lib/generate-week-report.ts +0 -285
  225. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  226. package/packages/web/lib/project-colors.ts +0 -58
  227. package/packages/web/lib/projects.ts +0 -506
  228. package/packages/web/lib/pty.ts +0 -101
  229. package/packages/web/lib/query-config.ts +0 -44
  230. package/packages/web/lib/services/index.ts +0 -9
  231. package/packages/web/lib/services/projects.server.ts +0 -66
  232. package/packages/web/lib/services/stats.server.ts +0 -562
  233. package/packages/web/lib/unified-loader.ts +0 -396
  234. package/packages/web/lib/utils.ts +0 -6
  235. package/packages/web/next-env.d.ts +0 -6
  236. package/packages/web/next.config.ts +0 -7
  237. package/packages/web/package.json +0 -57
  238. package/packages/web/postcss.config.mjs +0 -7
  239. package/packages/web/public/file.svg +0 -1
  240. package/packages/web/public/globe.svg +0 -1
  241. package/packages/web/public/next.svg +0 -1
  242. package/packages/web/public/vercel.svg +0 -1
  243. package/packages/web/public/window.svg +0 -1
  244. package/packages/web/server.ts +0 -312
  245. package/packages/web/tsconfig.json +0 -34
  246. package/templates/commands/serve.md +0 -121
@@ -1,257 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { promises as fs } from 'fs'
3
- import { join } from 'path'
4
- import { homedir } from 'os'
5
-
6
- export const dynamic = 'force-dynamic'
7
-
8
- interface SessionEvent {
9
- ts?: string
10
- timestamp?: string
11
- type?: string
12
- action?: string
13
- }
14
-
15
- type MomentumStatus = 'hot' | 'active' | 'cooling' | 'cold'
16
-
17
- interface MomentumData {
18
- dailyTasks: number[]
19
- totalTasks: number
20
- totalShips: number
21
- lastActivityDate: string | null
22
- daysSinceActivity: number
23
- streak: number
24
- status: MomentumStatus
25
- message: string
26
- }
27
-
28
- function getStatus(
29
- daysSinceActivity: number,
30
- totalTasks: number,
31
- dailyTasks: number[],
32
- streak: number
33
- ): { status: MomentumStatus; message: string } {
34
- // Abandoned - 7+ days without activity
35
- if (daysSinceActivity >= 7) {
36
- return { status: 'cold', message: 'Miss you!' }
37
- }
38
-
39
- // No activity ever
40
- if (totalTasks === 0) {
41
- return { status: 'active', message: 'Start building!' }
42
- }
43
-
44
- // Check if trending up (recent days > earlier days)
45
- const recentDays = dailyTasks.slice(-3).reduce((a, b) => a + b, 0)
46
- const earlierDays = dailyTasks.slice(0, 4).reduce((a, b) => a + b, 0)
47
- const isTrendingUp = recentDays > earlierDays || streak >= 2
48
-
49
- // Hot - trending up or on a streak
50
- if (isTrendingUp && daysSinceActivity <= 1) {
51
- return { status: 'hot', message: streak >= 2 ? `${streak} day streak!` : 'On fire!' }
52
- }
53
-
54
- // Normal activity - neutral
55
- if (daysSinceActivity <= 3) {
56
- return { status: 'active', message: `${totalTasks} this week` }
57
- }
58
-
59
- // Cooling down but not abandoned
60
- return { status: 'cooling', message: `${daysSinceActivity}d ago` }
61
- }
62
-
63
- export async function GET(
64
- request: Request,
65
- { params }: { params: Promise<{ id: string }> }
66
- ) {
67
- try {
68
- const { id: projectId } = await params
69
- const globalStorage = join(homedir(), '.prjct-cli', 'projects')
70
- const projectPath = join(globalStorage, projectId)
71
-
72
- // Check if project exists
73
- try {
74
- await fs.access(projectPath)
75
- } catch {
76
- return NextResponse.json(
77
- { success: false, error: 'Project not found' },
78
- { status: 404 }
79
- )
80
- }
81
-
82
- // Calculate date range (last 7 days)
83
- const endDate = new Date()
84
- const startDate = new Date()
85
- startDate.setDate(startDate.getDate() - 6) // 7 days including today
86
- startDate.setHours(0, 0, 0, 0)
87
-
88
- const dailyMap = new Map<string, { tasks: number; ships: number }>()
89
- let lastActivityDate: Date | null = null
90
-
91
- // Read from memory/context.jsonl (legacy)
92
- const contextPath = join(projectPath, 'memory', 'context.jsonl')
93
- try {
94
- const content = await fs.readFile(contextPath, 'utf-8')
95
- const lines = content.trim().split('\n').filter(Boolean)
96
-
97
- for (const line of lines) {
98
- try {
99
- const event: SessionEvent = JSON.parse(line)
100
- const timestamp = event.ts || event.timestamp
101
- if (!timestamp) continue
102
-
103
- const eventDate = new Date(timestamp)
104
- if (isNaN(eventDate.getTime())) continue
105
-
106
- const eventType = event.type || event.action
107
-
108
- // Track last activity for any relevant event
109
- if (eventType === 'task_complete' || eventType === 'task_completed' ||
110
- eventType === 'feature_ship' || eventType === 'feature_shipped') {
111
- if (!lastActivityDate || eventDate > lastActivityDate) {
112
- lastActivityDate = eventDate
113
- }
114
- }
115
-
116
- // Only count last 7 days for sparkline
117
- if (eventDate < startDate || eventDate > endDate) continue
118
-
119
- const dateKey = eventDate.toISOString().split('T')[0]
120
- const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0 }
121
-
122
- if (eventType === 'task_complete' || eventType === 'task_completed') {
123
- current.tasks++
124
- }
125
- if (eventType === 'feature_ship' || eventType === 'feature_shipped') {
126
- current.ships++
127
- }
128
-
129
- dailyMap.set(dateKey, current)
130
- } catch {
131
- // Skip malformed lines
132
- }
133
- }
134
- } catch {
135
- // No context.jsonl
136
- }
137
-
138
- // Read from progress/sessions/{YYYY-MM}/{date}.jsonl (new format)
139
- const sessionsDir = join(projectPath, 'progress', 'sessions')
140
- try {
141
- const monthDirs = await fs.readdir(sessionsDir)
142
- for (const monthDir of monthDirs) {
143
- if (!monthDir.match(/^\d{4}-\d{2}$/)) continue
144
-
145
- const monthPath = join(sessionsDir, monthDir)
146
- try {
147
- const dayFiles = await fs.readdir(monthPath)
148
- for (const dayFile of dayFiles) {
149
- if (!dayFile.endsWith('.jsonl')) continue
150
-
151
- const dayPath = join(monthPath, dayFile)
152
- try {
153
- const content = await fs.readFile(dayPath, 'utf-8')
154
- const lines = content.trim().split('\n').filter(Boolean)
155
-
156
- for (const line of lines) {
157
- try {
158
- const event: SessionEvent = JSON.parse(line)
159
- const timestamp = event.ts || event.timestamp
160
- if (!timestamp) continue
161
-
162
- const eventDate = new Date(timestamp)
163
- if (isNaN(eventDate.getTime())) continue
164
-
165
- const eventType = event.type || event.action
166
-
167
- // Track last activity
168
- if (eventType === 'task_complete' || eventType === 'task_completed' ||
169
- eventType === 'feature_ship' || eventType === 'feature_shipped') {
170
- if (!lastActivityDate || eventDate > lastActivityDate) {
171
- lastActivityDate = eventDate
172
- }
173
- }
174
-
175
- // Only count last 7 days for sparkline
176
- if (eventDate < startDate || eventDate > endDate) continue
177
-
178
- const dateKey = eventDate.toISOString().split('T')[0]
179
- const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0 }
180
-
181
- if (eventType === 'task_complete' || eventType === 'task_completed') {
182
- current.tasks++
183
- }
184
- if (eventType === 'feature_ship' || eventType === 'feature_shipped') {
185
- current.ships++
186
- }
187
-
188
- dailyMap.set(dateKey, current)
189
- } catch {
190
- // Skip malformed lines
191
- }
192
- }
193
- } catch {
194
- // Skip unreadable files
195
- }
196
- }
197
- } catch {
198
- // Skip unreadable month directories
199
- }
200
- }
201
- } catch {
202
- // No sessions directory
203
- }
204
-
205
- // Generate daily tasks array for sparkline (7 days)
206
- const dailyTasks: number[] = []
207
- let totalTasks = 0
208
- let totalShips = 0
209
- let streak = 0
210
- let streakBroken = false
211
-
212
- const currentDate = new Date(startDate)
213
- while (currentDate <= endDate) {
214
- const dateKey = currentDate.toISOString().split('T')[0]
215
- const stats = dailyMap.get(dateKey) || { tasks: 0, ships: 0 }
216
- dailyTasks.push(stats.tasks + stats.ships)
217
- totalTasks += stats.tasks
218
- totalShips += stats.ships
219
- currentDate.setDate(currentDate.getDate() + 1)
220
- }
221
-
222
- // Calculate streak (consecutive days with activity from today backwards)
223
- for (let i = dailyTasks.length - 1; i >= 0; i--) {
224
- if (dailyTasks[i] > 0 && !streakBroken) {
225
- streak++
226
- } else if (dailyTasks[i] === 0 && i < dailyTasks.length - 1) {
227
- streakBroken = true
228
- }
229
- }
230
-
231
- // Calculate days since last activity
232
- const daysSinceActivity = lastActivityDate
233
- ? Math.floor((endDate.getTime() - lastActivityDate.getTime()) / (1000 * 60 * 60 * 24))
234
- : 999
235
-
236
- const { status, message } = getStatus(daysSinceActivity, totalTasks, dailyTasks, streak)
237
-
238
- const data: MomentumData = {
239
- dailyTasks,
240
- totalTasks,
241
- totalShips,
242
- lastActivityDate: lastActivityDate?.toISOString() || null,
243
- daysSinceActivity,
244
- streak,
245
- status,
246
- message
247
- }
248
-
249
- return NextResponse.json({ success: true, data })
250
- } catch (error) {
251
- console.error('Momentum API error:', error)
252
- return NextResponse.json(
253
- { success: false, error: 'Failed to fetch momentum data' },
254
- { status: 500 }
255
- )
256
- }
257
- }
@@ -1,29 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { getProject } from '@/lib/projects'
3
-
4
- export const dynamic = 'force-dynamic'
5
-
6
- export async function GET(
7
- request: Request,
8
- { params }: { params: Promise<{ id: string }> }
9
- ) {
10
- const { id } = await params
11
-
12
- try {
13
- const project = await getProject(id)
14
-
15
- if (!project) {
16
- return NextResponse.json(
17
- { success: false, error: 'Project not found' },
18
- { status: 404 }
19
- )
20
- }
21
-
22
- return NextResponse.json({ success: true, data: project })
23
- } catch {
24
- return NextResponse.json(
25
- { success: false, error: 'Failed to get project' },
26
- { status: 500 }
27
- )
28
- }
29
- }
@@ -1,41 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server'
2
- import { getProjectStats, getRawProjectFiles } from '@/lib/parse-prjct-files'
3
-
4
- /**
5
- * GET /api/projects/[id]/stats
6
- *
7
- * MD-First Architecture: Returns stats parsed from MD files.
8
- */
9
- export async function GET(
10
- request: NextRequest,
11
- { params }: { params: Promise<{ id: string }> }
12
- ) {
13
- const { id: projectId } = await params
14
-
15
- if (!projectId) {
16
- return NextResponse.json(
17
- { success: false, error: 'Project ID required' },
18
- { status: 400 }
19
- )
20
- }
21
-
22
- try {
23
- const [stats, raw] = await Promise.all([
24
- getProjectStats(projectId),
25
- getRawProjectFiles(projectId)
26
- ])
27
-
28
- return NextResponse.json({
29
- success: true,
30
- version: 'md-first',
31
- data: stats,
32
- raw
33
- })
34
- } catch (error) {
35
- console.error('[API] Error getting project stats:', error)
36
- return NextResponse.json(
37
- { success: false, error: 'Failed to get project stats' },
38
- { status: 500 }
39
- )
40
- }
41
- }
@@ -1,21 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { getProjectStatus } from '@/lib/projects'
3
-
4
- export const dynamic = 'force-dynamic'
5
-
6
- export async function GET(
7
- request: Request,
8
- { params }: { params: Promise<{ id: string }> }
9
- ) {
10
- const { id } = await params
11
-
12
- try {
13
- const status = await getProjectStatus(id)
14
- return NextResponse.json({ success: true, data: status })
15
- } catch {
16
- return NextResponse.json(
17
- { success: false, error: 'Failed to get status' },
18
- { status: 500 }
19
- )
20
- }
21
- }
@@ -1,16 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { getProjects } from '@/lib/projects'
3
-
4
- export const dynamic = 'force-dynamic'
5
-
6
- export async function GET() {
7
- try {
8
- const projects = await getProjects()
9
- return NextResponse.json({ success: true, data: projects })
10
- } catch {
11
- return NextResponse.json(
12
- { success: false, error: 'Failed to list projects' },
13
- { status: 500 }
14
- )
15
- }
16
- }
@@ -1,132 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { promises as fs } from 'fs'
3
- import { join } from 'path'
4
- import { homedir } from 'os'
5
-
6
- export const dynamic = 'force-dynamic'
7
-
8
- interface Session {
9
- id: string
10
- projectId?: string
11
- task: string
12
- status: string
13
- startedAt: string
14
- timeline?: Array<{ type: string; at: string }>
15
- context?: {
16
- prompt?: string
17
- promptLength?: number
18
- files?: string[]
19
- }
20
- }
21
-
22
- interface AbandonedSession {
23
- id: string
24
- task: string
25
- projectId: string
26
- projectName?: string
27
- startedAt: string
28
- lastActivity?: string
29
- hoursAgo: number
30
- prompt?: string
31
- }
32
-
33
- export async function GET(request: Request) {
34
- try {
35
- const { searchParams } = new URL(request.url)
36
- const targetProjectId = searchParams.get('projectId')
37
-
38
- const globalStorage = join(homedir(), '.prjct-cli', 'projects')
39
-
40
- let projects: string[]
41
- try {
42
- projects = await fs.readdir(globalStorage)
43
- } catch {
44
- return NextResponse.json({
45
- success: true,
46
- data: {
47
- currentSession: null,
48
- abandonedSessions: []
49
- }
50
- })
51
- }
52
-
53
- const abandonedSessions: AbandonedSession[] = []
54
- let currentSession: Session | null = null
55
-
56
- const now = Date.now()
57
- const ABANDON_THRESHOLD_HOURS = 8
58
-
59
- for (const projectId of projects) {
60
- // Skip hidden directories
61
- if (projectId.startsWith('.')) continue
62
-
63
- const sessionPath = join(globalStorage, projectId, 'sessions', 'current.json')
64
-
65
- try {
66
- const content = await fs.readFile(sessionPath, 'utf-8')
67
- if (!content.trim()) continue
68
-
69
- const session: Session = JSON.parse(content)
70
-
71
- // Skip if not active
72
- if (session.status !== 'active') continue
73
-
74
- // Get last activity timestamp
75
- const lastActivity = session.timeline?.[session.timeline.length - 1]?.at || session.startedAt
76
- const lastActivityDate = new Date(lastActivity)
77
- if (isNaN(lastActivityDate.getTime())) continue
78
-
79
- const hoursAgo = (now - lastActivityDate.getTime()) / (1000 * 60 * 60)
80
-
81
- // If this is the target project and session is recent, it's the current session
82
- if (projectId === targetProjectId && hoursAgo < ABANDON_THRESHOLD_HOURS) {
83
- currentSession = session
84
- }
85
- // If session is old (>8 hours), it's considered abandoned
86
- // Only show abandoned sessions for the target project
87
- else if (hoursAgo >= ABANDON_THRESHOLD_HOURS && projectId === targetProjectId) {
88
- // Try to get project name from project.json
89
- let projectName: string | undefined
90
- try {
91
- const projectJsonPath = join(globalStorage, projectId, 'project.json')
92
- const projectJson = await fs.readFile(projectJsonPath, 'utf-8')
93
- const projectData = JSON.parse(projectJson)
94
- projectName = projectData.name
95
- } catch {
96
- // Ignore - project name is optional
97
- }
98
-
99
- abandonedSessions.push({
100
- id: session.id,
101
- task: session.task,
102
- projectId,
103
- projectName,
104
- startedAt: session.startedAt,
105
- lastActivity,
106
- hoursAgo: Math.round(hoursAgo),
107
- prompt: session.context?.prompt
108
- })
109
- }
110
- } catch {
111
- // Skip projects without valid session files
112
- }
113
- }
114
-
115
- // Sort abandoned sessions by most recent first
116
- abandonedSessions.sort((a, b) => a.hoursAgo - b.hoursAgo)
117
-
118
- return NextResponse.json({
119
- success: true,
120
- data: {
121
- currentSession,
122
- abandonedSessions
123
- }
124
- })
125
- } catch (error) {
126
- console.error('Sessions current error:', error)
127
- return NextResponse.json(
128
- { success: false, error: 'Failed to fetch sessions' },
129
- { status: 500 }
130
- )
131
- }
132
- }
@@ -1,204 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { promises as fs } from 'fs'
3
- import { join } from 'path'
4
- import { homedir } from 'os'
5
-
6
- export const dynamic = 'force-dynamic'
7
-
8
- interface SessionEvent {
9
- ts?: string
10
- timestamp?: string
11
- type?: string
12
- action?: string
13
- }
14
-
15
- interface DailyStats {
16
- date: string
17
- tasks: number
18
- ships: number
19
- partial: number
20
- }
21
-
22
- export async function GET() {
23
- try {
24
- const globalStorage = join(homedir(), '.prjct-cli', 'projects')
25
-
26
- let projects: string[]
27
- try {
28
- projects = await fs.readdir(globalStorage)
29
- } catch {
30
- return NextResponse.json({
31
- success: true,
32
- data: {
33
- chartData: [],
34
- totals: { tasks: 0, ships: 0 },
35
- dateRange: { start: '', end: '' }
36
- }
37
- })
38
- }
39
-
40
- // Aggregate by date
41
- const dailyMap = new Map<string, { tasks: number; ships: number; partial: number }>()
42
-
43
- // Calculate date range (last 30 days)
44
- const endDate = new Date()
45
- const startDate = new Date()
46
- startDate.setDate(startDate.getDate() - 30)
47
-
48
- for (const projectId of projects) {
49
- // Skip hidden directories
50
- if (projectId.startsWith('.')) continue
51
-
52
- // Read from memory/context.jsonl (legacy)
53
- const contextPath = join(globalStorage, projectId, 'memory', 'context.jsonl')
54
- try {
55
- const content = await fs.readFile(contextPath, 'utf-8')
56
- const lines = content.trim().split('\n').filter(Boolean)
57
-
58
- for (const line of lines) {
59
- try {
60
- const event: SessionEvent = JSON.parse(line)
61
- const timestamp = event.ts || event.timestamp
62
- if (!timestamp) continue
63
-
64
- const eventDate = new Date(timestamp)
65
- if (isNaN(eventDate.getTime())) continue
66
- if (eventDate < startDate || eventDate > endDate) continue
67
-
68
- const dateKey = eventDate.toISOString().split('T')[0]
69
- const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
70
-
71
- const eventType = event.type || event.action
72
-
73
- // Count tasks completed
74
- if (eventType === 'task_complete' || eventType === 'task_completed' || eventType === 'session_completed') {
75
- current.tasks++
76
- }
77
-
78
- // Count features shipped
79
- if (eventType === 'feature_ship' || eventType === 'feature_shipped') {
80
- current.ships++
81
- }
82
-
83
- // Count partial/abandoned sessions
84
- if (eventType === 'session_partial' || eventType === 'session_abandoned') {
85
- current.partial++
86
- }
87
-
88
- dailyMap.set(dateKey, current)
89
- } catch {
90
- // Skip malformed lines
91
- }
92
- }
93
- } catch {
94
- // Skip projects without context.jsonl
95
- }
96
-
97
- // Read from progress/sessions/{YYYY-MM}/{date}.jsonl (new format)
98
- const sessionsDir = join(globalStorage, projectId, 'progress', 'sessions')
99
- try {
100
- const monthDirs = await fs.readdir(sessionsDir)
101
- for (const monthDir of monthDirs) {
102
- // Skip non-directory entries
103
- if (!monthDir.match(/^\d{4}-\d{2}$/)) continue
104
-
105
- const monthPath = join(sessionsDir, monthDir)
106
- try {
107
- const dayFiles = await fs.readdir(monthPath)
108
- for (const dayFile of dayFiles) {
109
- if (!dayFile.endsWith('.jsonl')) continue
110
-
111
- const dayPath = join(monthPath, dayFile)
112
- try {
113
- const content = await fs.readFile(dayPath, 'utf-8')
114
- const lines = content.trim().split('\n').filter(Boolean)
115
-
116
- for (const line of lines) {
117
- try {
118
- const event: SessionEvent = JSON.parse(line)
119
- const timestamp = event.ts || event.timestamp
120
- if (!timestamp) continue
121
-
122
- const eventDate = new Date(timestamp)
123
- if (isNaN(eventDate.getTime())) continue
124
- if (eventDate < startDate || eventDate > endDate) continue
125
-
126
- const dateKey = eventDate.toISOString().split('T')[0]
127
- const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
128
-
129
- const eventType = event.type || event.action
130
-
131
- // Count tasks completed
132
- if (eventType === 'task_complete' || eventType === 'task_completed') {
133
- current.tasks++
134
- }
135
-
136
- // Count features shipped
137
- if (eventType === 'feature_ship' || eventType === 'feature_shipped') {
138
- current.ships++
139
- }
140
-
141
- // Count partial/abandoned sessions
142
- if (eventType === 'session_partial' || eventType === 'session_abandoned') {
143
- current.partial++
144
- }
145
-
146
- dailyMap.set(dateKey, current)
147
- } catch {
148
- // Skip malformed lines
149
- }
150
- }
151
- } catch {
152
- // Skip unreadable files
153
- }
154
- }
155
- } catch {
156
- // Skip unreadable month directories
157
- }
158
- }
159
- } catch {
160
- // Skip projects without sessions directory
161
- }
162
- }
163
-
164
- // Generate all dates in range (90 days), filling gaps with zeros
165
- const data: DailyStats[] = []
166
- const currentDate = new Date(startDate)
167
- while (currentDate <= endDate) {
168
- const dateKey = currentDate.toISOString().split('T')[0]
169
- const stats = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
170
- data.push({
171
- date: dateKey,
172
- tasks: stats.tasks,
173
- ships: stats.ships,
174
- partial: stats.partial
175
- })
176
- currentDate.setDate(currentDate.getDate() + 1)
177
- }
178
-
179
- // Calculate totals for summary
180
- const totals = {
181
- tasks: data.reduce((sum, d) => sum + d.tasks, 0),
182
- ships: data.reduce((sum, d) => sum + d.ships, 0),
183
- partial: data.reduce((sum, d) => sum + d.partial, 0)
184
- }
185
-
186
- return NextResponse.json({
187
- success: true,
188
- data: {
189
- chartData: data,
190
- totals,
191
- dateRange: {
192
- start: startDate.toISOString().split('T')[0],
193
- end: endDate.toISOString().split('T')[0]
194
- }
195
- }
196
- })
197
- } catch (error) {
198
- console.error('Sessions history error:', error)
199
- return NextResponse.json(
200
- { success: false, error: 'Failed to fetch session history' },
201
- { status: 500 }
202
- )
203
- }
204
- }