prjct-cli 0.18.1 → 0.19.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 (245) hide show
  1. package/CHANGELOG.md +53 -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/subagent-generation.md +8 -6
  11. package/templates/commands/done.md +57 -258
  12. package/templates/commands/now.md +72 -277
  13. package/templates/commands/ship.md +55 -261
  14. package/templates/commands/sync.md +7 -7
  15. package/templates/commands/test.md +328 -21
  16. package/templates/global/CLAUDE.md +40 -205
  17. package/templates/global/docs/agents.md +88 -0
  18. package/templates/global/docs/architecture.md +103 -0
  19. package/templates/global/docs/commands.md +98 -0
  20. package/templates/global/docs/validation.md +95 -0
  21. package/templates/mcp-config.json +36 -0
  22. package/bin/dev.js +0 -216
  23. package/bin/serve.js +0 -361
  24. package/packages/web/README.md +0 -36
  25. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  26. package/packages/web/app/api/claude/status/route.ts +0 -34
  27. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  28. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  29. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  30. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  31. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  32. package/packages/web/app/api/projects/route.ts +0 -16
  33. package/packages/web/app/api/sessions/current/route.ts +0 -132
  34. package/packages/web/app/api/sessions/history/route.ts +0 -204
  35. package/packages/web/app/error.tsx +0 -34
  36. package/packages/web/app/favicon.ico +0 -0
  37. package/packages/web/app/globals.css +0 -198
  38. package/packages/web/app/layout.tsx +0 -53
  39. package/packages/web/app/loading.tsx +0 -7
  40. package/packages/web/app/not-found.tsx +0 -25
  41. package/packages/web/app/page.tsx +0 -12
  42. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  43. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  44. package/packages/web/app/project/[id]/error.tsx +0 -41
  45. package/packages/web/app/project/[id]/loading.tsx +0 -9
  46. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  47. package/packages/web/app/project/[id]/page.tsx +0 -384
  48. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  49. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  50. package/packages/web/app/sessions/page.tsx +0 -165
  51. package/packages/web/app/settings/page.tsx +0 -151
  52. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  53. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  54. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  55. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  56. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  57. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  58. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  59. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  60. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  61. package/packages/web/components/AgentsCard/index.ts +0 -2
  62. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  63. package/packages/web/components/AppSidebar/index.ts +0 -1
  64. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  65. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  66. package/packages/web/components/BackLink/index.ts +0 -2
  67. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  68. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  69. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  70. package/packages/web/components/BentoCard/index.ts +0 -2
  71. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  72. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  73. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  74. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  75. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  76. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  77. package/packages/web/components/BentoGrid/index.ts +0 -2
  78. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  79. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  80. package/packages/web/components/BlockersCard/index.ts +0 -2
  81. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  82. package/packages/web/components/CommandBar/index.ts +0 -1
  83. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  84. package/packages/web/components/CommandButton/index.ts +0 -1
  85. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  86. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  87. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  88. package/packages/web/components/DashboardContent/index.ts +0 -1
  89. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  90. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  91. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  92. package/packages/web/components/DateGroup/index.ts +0 -2
  93. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  94. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  95. package/packages/web/components/EmptyState/index.ts +0 -2
  96. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  97. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  98. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  99. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  100. package/packages/web/components/EventRow/index.ts +0 -2
  101. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  102. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  103. package/packages/web/components/ExpandButton/index.ts +0 -2
  104. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  105. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  106. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  107. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  108. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  109. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  110. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  111. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  112. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  113. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  114. package/packages/web/components/HeroSection/index.ts +0 -2
  115. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  116. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  117. package/packages/web/components/IdeasCard/index.ts +0 -2
  118. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  119. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  120. package/packages/web/components/InsightMessage/index.ts +0 -2
  121. package/packages/web/components/Logo/Logo.tsx +0 -65
  122. package/packages/web/components/Logo/index.ts +0 -1
  123. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  124. package/packages/web/components/MarkdownContent/index.ts +0 -1
  125. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  126. package/packages/web/components/MasonryGrid/index.ts +0 -1
  127. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  128. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  129. package/packages/web/components/MomentumWidget/index.ts +0 -2
  130. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  131. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  132. package/packages/web/components/NowCard/index.ts +0 -2
  133. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  134. package/packages/web/components/PageHeader/index.ts +0 -1
  135. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  136. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  137. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  138. package/packages/web/components/ProgressRing/index.ts +0 -2
  139. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  140. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  141. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  142. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  143. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  144. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  145. package/packages/web/components/Providers/Providers.tsx +0 -48
  146. package/packages/web/components/Providers/index.ts +0 -1
  147. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  148. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  149. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  150. package/packages/web/components/QueueCard/index.ts +0 -2
  151. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  152. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  153. package/packages/web/components/RecoverCard/index.ts +0 -2
  154. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  155. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  156. package/packages/web/components/RoadmapCard/index.ts +0 -2
  157. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  158. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  159. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  160. package/packages/web/components/ShipsCard/index.ts +0 -2
  161. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  162. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  163. package/packages/web/components/SparklineChart/index.ts +0 -2
  164. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  165. package/packages/web/components/StatsMasonry/index.ts +0 -1
  166. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  167. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  168. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  169. package/packages/web/components/StreakCard/index.ts +0 -2
  170. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  171. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  172. package/packages/web/components/TasksCounter/index.ts +0 -2
  173. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  174. package/packages/web/components/TechStackBadges/index.ts +0 -1
  175. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  176. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  177. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  178. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  179. package/packages/web/components/TerminalDock/index.ts +0 -2
  180. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  181. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  182. package/packages/web/components/TerminalTabs/index.ts +0 -1
  183. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  184. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  185. package/packages/web/components/VelocityBadge/index.ts +0 -2
  186. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  187. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  188. package/packages/web/components/VelocityCard/index.ts +0 -2
  189. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  190. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  191. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  192. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  193. package/packages/web/components/WeeklyReports/index.ts +0 -4
  194. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  195. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  196. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  197. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  198. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  199. package/packages/web/components/ui/badge.tsx +0 -46
  200. package/packages/web/components/ui/button.tsx +0 -60
  201. package/packages/web/components/ui/card.tsx +0 -92
  202. package/packages/web/components/ui/chart.tsx +0 -385
  203. package/packages/web/components/ui/dialog.tsx +0 -143
  204. package/packages/web/components/ui/drawer.tsx +0 -135
  205. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  206. package/packages/web/components/ui/input.tsx +0 -21
  207. package/packages/web/components/ui/scroll-area.tsx +0 -58
  208. package/packages/web/components/ui/select.tsx +0 -187
  209. package/packages/web/components/ui/sheet.tsx +0 -139
  210. package/packages/web/components/ui/tabs.tsx +0 -66
  211. package/packages/web/components/ui/tooltip.tsx +0 -61
  212. package/packages/web/components.json +0 -22
  213. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  214. package/packages/web/context/TerminalContext.tsx +0 -45
  215. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  216. package/packages/web/eslint.config.mjs +0 -18
  217. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  218. package/packages/web/hooks/useProjectStats.ts +0 -93
  219. package/packages/web/hooks/useProjects.ts +0 -73
  220. package/packages/web/lib/actions/projects.ts +0 -15
  221. package/packages/web/lib/commands.ts +0 -81
  222. package/packages/web/lib/format.ts +0 -23
  223. package/packages/web/lib/generate-week-report.ts +0 -285
  224. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  225. package/packages/web/lib/project-colors.ts +0 -58
  226. package/packages/web/lib/projects.ts +0 -506
  227. package/packages/web/lib/pty.ts +0 -101
  228. package/packages/web/lib/query-config.ts +0 -44
  229. package/packages/web/lib/services/index.ts +0 -9
  230. package/packages/web/lib/services/projects.server.ts +0 -66
  231. package/packages/web/lib/services/stats.server.ts +0 -562
  232. package/packages/web/lib/unified-loader.ts +0 -396
  233. package/packages/web/lib/utils.ts +0 -6
  234. package/packages/web/next-env.d.ts +0 -6
  235. package/packages/web/next.config.ts +0 -7
  236. package/packages/web/package.json +0 -57
  237. package/packages/web/postcss.config.mjs +0 -7
  238. package/packages/web/public/file.svg +0 -1
  239. package/packages/web/public/globe.svg +0 -1
  240. package/packages/web/public/next.svg +0 -1
  241. package/packages/web/public/vercel.svg +0 -1
  242. package/packages/web/public/window.svg +0 -1
  243. package/packages/web/server.ts +0 -312
  244. package/packages/web/tsconfig.json +0 -34
  245. 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
- }