prjct-cli 0.10.14 → 0.11.1

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 (105) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/bin/dev.js +217 -0
  3. package/bin/prjct +10 -0
  4. package/bin/serve.js +78 -0
  5. package/core/bus/index.js +322 -0
  6. package/core/command-registry.js +65 -0
  7. package/core/domain/snapshot-manager.js +375 -0
  8. package/core/plugin/hooks.js +313 -0
  9. package/core/plugin/index.js +52 -0
  10. package/core/plugin/loader.js +331 -0
  11. package/core/plugin/registry.js +325 -0
  12. package/core/plugins/webhook.js +143 -0
  13. package/core/session/index.js +449 -0
  14. package/core/session/metrics.js +293 -0
  15. package/package.json +28 -4
  16. package/packages/shared/dist/index.d.ts +615 -0
  17. package/packages/shared/dist/index.js +204 -0
  18. package/packages/shared/package.json +29 -0
  19. package/packages/shared/src/index.ts +9 -0
  20. package/packages/shared/src/schemas.ts +124 -0
  21. package/packages/shared/src/types.ts +187 -0
  22. package/packages/shared/src/utils.ts +148 -0
  23. package/packages/shared/tsconfig.json +18 -0
  24. package/packages/web/README.md +36 -0
  25. package/packages/web/app/api/claude/sessions/route.ts +44 -0
  26. package/packages/web/app/api/claude/status/route.ts +34 -0
  27. package/packages/web/app/api/projects/[id]/delete/route.ts +21 -0
  28. package/packages/web/app/api/projects/[id]/icon/route.ts +33 -0
  29. package/packages/web/app/api/projects/[id]/route.ts +29 -0
  30. package/packages/web/app/api/projects/[id]/stats/route.ts +36 -0
  31. package/packages/web/app/api/projects/[id]/status/route.ts +21 -0
  32. package/packages/web/app/api/projects/route.ts +16 -0
  33. package/packages/web/app/api/sessions/history/route.ts +122 -0
  34. package/packages/web/app/api/stats/route.ts +38 -0
  35. package/packages/web/app/error.tsx +34 -0
  36. package/packages/web/app/favicon.ico +0 -0
  37. package/packages/web/app/globals.css +155 -0
  38. package/packages/web/app/layout.tsx +43 -0
  39. package/packages/web/app/loading.tsx +7 -0
  40. package/packages/web/app/not-found.tsx +25 -0
  41. package/packages/web/app/page.tsx +227 -0
  42. package/packages/web/app/project/[id]/error.tsx +41 -0
  43. package/packages/web/app/project/[id]/loading.tsx +9 -0
  44. package/packages/web/app/project/[id]/not-found.tsx +27 -0
  45. package/packages/web/app/project/[id]/page.tsx +253 -0
  46. package/packages/web/app/project/[id]/stats/page.tsx +447 -0
  47. package/packages/web/app/sessions/page.tsx +165 -0
  48. package/packages/web/app/settings/page.tsx +150 -0
  49. package/packages/web/components/AppSidebar.tsx +113 -0
  50. package/packages/web/components/CommandButton.tsx +39 -0
  51. package/packages/web/components/ConnectionStatus.tsx +29 -0
  52. package/packages/web/components/Logo.tsx +65 -0
  53. package/packages/web/components/MarkdownContent.tsx +123 -0
  54. package/packages/web/components/ProjectAvatar.tsx +54 -0
  55. package/packages/web/components/TechStackBadges.tsx +20 -0
  56. package/packages/web/components/TerminalTab.tsx +84 -0
  57. package/packages/web/components/TerminalTabs.tsx +210 -0
  58. package/packages/web/components/charts/SessionsChart.tsx +172 -0
  59. package/packages/web/components/providers.tsx +45 -0
  60. package/packages/web/components/ui/alert-dialog.tsx +157 -0
  61. package/packages/web/components/ui/badge.tsx +46 -0
  62. package/packages/web/components/ui/button.tsx +60 -0
  63. package/packages/web/components/ui/card.tsx +92 -0
  64. package/packages/web/components/ui/chart.tsx +385 -0
  65. package/packages/web/components/ui/dropdown-menu.tsx +257 -0
  66. package/packages/web/components/ui/scroll-area.tsx +58 -0
  67. package/packages/web/components/ui/sheet.tsx +139 -0
  68. package/packages/web/components/ui/tabs.tsx +66 -0
  69. package/packages/web/components/ui/tooltip.tsx +61 -0
  70. package/packages/web/components.json +22 -0
  71. package/packages/web/context/TerminalContext.tsx +45 -0
  72. package/packages/web/context/TerminalTabsContext.tsx +136 -0
  73. package/packages/web/eslint.config.mjs +18 -0
  74. package/packages/web/hooks/useClaudeTerminal.ts +375 -0
  75. package/packages/web/hooks/useProjectStats.ts +38 -0
  76. package/packages/web/hooks/useProjects.ts +73 -0
  77. package/packages/web/hooks/useStats.ts +28 -0
  78. package/packages/web/lib/format.ts +23 -0
  79. package/packages/web/lib/parse-prjct-files.ts +1122 -0
  80. package/packages/web/lib/projects.ts +452 -0
  81. package/packages/web/lib/pty.ts +101 -0
  82. package/packages/web/lib/query-config.ts +44 -0
  83. package/packages/web/lib/utils.ts +6 -0
  84. package/packages/web/next-env.d.ts +6 -0
  85. package/packages/web/next.config.ts +7 -0
  86. package/packages/web/package.json +53 -0
  87. package/packages/web/postcss.config.mjs +7 -0
  88. package/packages/web/public/file.svg +1 -0
  89. package/packages/web/public/globe.svg +1 -0
  90. package/packages/web/public/next.svg +1 -0
  91. package/packages/web/public/vercel.svg +1 -0
  92. package/packages/web/public/window.svg +1 -0
  93. package/packages/web/server.ts +262 -0
  94. package/packages/web/tsconfig.json +34 -0
  95. package/templates/commands/done.md +176 -54
  96. package/templates/commands/history.md +176 -0
  97. package/templates/commands/init.md +28 -1
  98. package/templates/commands/now.md +191 -9
  99. package/templates/commands/pause.md +176 -12
  100. package/templates/commands/redo.md +142 -0
  101. package/templates/commands/resume.md +166 -62
  102. package/templates/commands/serve.md +121 -0
  103. package/templates/commands/ship.md +45 -1
  104. package/templates/commands/sync.md +34 -1
  105. package/templates/commands/undo.md +152 -0
@@ -0,0 +1,293 @@
1
+ /**
2
+ * SessionMetrics - Metrics Calculation and Aggregation
3
+ *
4
+ * Calculates productivity metrics from session data.
5
+ *
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const fs = require('fs').promises
10
+ const path = require('path')
11
+ const pathManager = require('../infrastructure/path-manager')
12
+
13
+ class SessionMetrics {
14
+ constructor(projectId) {
15
+ this.projectId = projectId
16
+ }
17
+
18
+ /**
19
+ * Get aggregated metrics for a time period
20
+ * @param {'day'|'week'|'month'|'all'} period
21
+ * @returns {Promise<Object>}
22
+ */
23
+ async getMetrics(period = 'week') {
24
+ const sessions = await this.getSessionsForPeriod(period)
25
+
26
+ return {
27
+ period,
28
+ totalSessions: sessions.length,
29
+ totalDuration: this.sumDurations(sessions),
30
+ totalDurationFormatted: this.formatDuration(this.sumDurations(sessions)),
31
+ averageDuration: this.averageDuration(sessions),
32
+ averageDurationFormatted: this.formatDuration(this.averageDuration(sessions)),
33
+ tasksCompleted: sessions.filter(s => s.status === 'completed').length,
34
+ filesChanged: this.sumMetric(sessions, 'filesChanged'),
35
+ linesAdded: this.sumMetric(sessions, 'linesAdded'),
36
+ linesRemoved: this.sumMetric(sessions, 'linesRemoved'),
37
+ commits: this.sumMetric(sessions, 'commits'),
38
+ productivityScore: this.calculateProductivityScore(sessions),
39
+ streak: await this.calculateStreak(),
40
+ byDay: this.groupByDay(sessions)
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get sessions for a given period
46
+ * @param {string} period
47
+ * @returns {Promise<Array>}
48
+ */
49
+ async getSessionsForPeriod(period) {
50
+ const globalPath = pathManager.getGlobalProjectPath(this.projectId)
51
+ const archiveDir = path.join(globalPath, 'sessions', 'archive')
52
+
53
+ const sessions = []
54
+ const cutoffDate = this.getCutoffDate(period)
55
+
56
+ try {
57
+ const months = await fs.readdir(archiveDir)
58
+
59
+ for (const month of months) {
60
+ const monthDir = path.join(archiveDir, month)
61
+ const files = await fs.readdir(monthDir)
62
+
63
+ for (const file of files) {
64
+ if (!file.endsWith('.json')) continue
65
+
66
+ const content = await fs.readFile(path.join(monthDir, file), 'utf-8')
67
+ const session = JSON.parse(content)
68
+
69
+ if (new Date(session.completedAt) >= cutoffDate) {
70
+ sessions.push(session)
71
+ }
72
+ }
73
+ }
74
+ } catch {
75
+ // Archive might not exist
76
+ }
77
+
78
+ // Also check current session
79
+ try {
80
+ const currentPath = path.join(globalPath, 'sessions', 'current.json')
81
+ const content = await fs.readFile(currentPath, 'utf-8')
82
+ const current = JSON.parse(content)
83
+ if (new Date(current.startedAt) >= cutoffDate) {
84
+ sessions.push(current)
85
+ }
86
+ } catch {
87
+ // No current session
88
+ }
89
+
90
+ return sessions
91
+ }
92
+
93
+ /**
94
+ * Get cutoff date for period
95
+ * @param {string} period
96
+ * @returns {Date}
97
+ */
98
+ getCutoffDate(period) {
99
+ const now = new Date()
100
+
101
+ switch (period) {
102
+ case 'day':
103
+ return new Date(now.setHours(0, 0, 0, 0))
104
+ case 'week':
105
+ const weekAgo = new Date(now)
106
+ weekAgo.setDate(weekAgo.getDate() - 7)
107
+ return weekAgo
108
+ case 'month':
109
+ const monthAgo = new Date(now)
110
+ monthAgo.setMonth(monthAgo.getMonth() - 1)
111
+ return monthAgo
112
+ case 'all':
113
+ default:
114
+ return new Date(0) // Beginning of time
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Sum durations from sessions
120
+ * @param {Array} sessions
121
+ * @returns {number} Total seconds
122
+ */
123
+ sumDurations(sessions) {
124
+ return sessions.reduce((sum, s) => sum + (s.duration || 0), 0)
125
+ }
126
+
127
+ /**
128
+ * Calculate average duration
129
+ * @param {Array} sessions
130
+ * @returns {number} Average seconds
131
+ */
132
+ averageDuration(sessions) {
133
+ if (sessions.length === 0) return 0
134
+ return Math.round(this.sumDurations(sessions) / sessions.length)
135
+ }
136
+
137
+ /**
138
+ * Sum a specific metric
139
+ * @param {Array} sessions
140
+ * @param {string} metric
141
+ * @returns {number}
142
+ */
143
+ sumMetric(sessions, metric) {
144
+ return sessions.reduce((sum, s) => {
145
+ return sum + (s.metrics?.[metric] || 0)
146
+ }, 0)
147
+ }
148
+
149
+ /**
150
+ * Calculate productivity score (0-100)
151
+ * Based on consistency, duration, and output
152
+ * @param {Array} sessions
153
+ * @returns {number}
154
+ */
155
+ calculateProductivityScore(sessions) {
156
+ if (sessions.length === 0) return 0
157
+
158
+ // Factors:
159
+ // 1. Session count (more = better, up to a point)
160
+ const sessionScore = Math.min(sessions.length / 10, 1) * 30
161
+
162
+ // 2. Average duration (25-90 min is optimal)
163
+ const avgMins = this.averageDuration(sessions) / 60
164
+ let durationScore = 0
165
+ if (avgMins >= 25 && avgMins <= 90) {
166
+ durationScore = 30
167
+ } else if (avgMins > 0) {
168
+ durationScore = Math.max(0, 30 - Math.abs(avgMins - 57.5) / 2)
169
+ }
170
+
171
+ // 3. Completion rate
172
+ const completedCount = sessions.filter(s => s.status === 'completed').length
173
+ const completionScore = (completedCount / sessions.length) * 20
174
+
175
+ // 4. Output (commits + files changed)
176
+ const totalOutput = this.sumMetric(sessions, 'commits') + this.sumMetric(sessions, 'filesChanged')
177
+ const outputScore = Math.min(totalOutput / 50, 1) * 20
178
+
179
+ return Math.round(sessionScore + durationScore + completionScore + outputScore)
180
+ }
181
+
182
+ /**
183
+ * Calculate current streak (consecutive days with sessions)
184
+ * @returns {Promise<number>}
185
+ */
186
+ async calculateStreak() {
187
+ const sessions = await this.getSessionsForPeriod('month')
188
+
189
+ // Get unique dates with sessions
190
+ const dates = new Set(
191
+ sessions.map(s => {
192
+ const d = new Date(s.completedAt || s.startedAt)
193
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`
194
+ })
195
+ )
196
+
197
+ // Count consecutive days from today
198
+ let streak = 0
199
+ const today = new Date()
200
+
201
+ for (let i = 0; i < 30; i++) {
202
+ const checkDate = new Date(today)
203
+ checkDate.setDate(checkDate.getDate() - i)
204
+ const key = `${checkDate.getFullYear()}-${checkDate.getMonth()}-${checkDate.getDate()}`
205
+
206
+ if (dates.has(key)) {
207
+ streak++
208
+ } else if (i > 0) {
209
+ break // Streak broken
210
+ }
211
+ }
212
+
213
+ return streak
214
+ }
215
+
216
+ /**
217
+ * Group sessions by day
218
+ * @param {Array} sessions
219
+ * @returns {Object}
220
+ */
221
+ groupByDay(sessions) {
222
+ const byDay = {}
223
+
224
+ for (const session of sessions) {
225
+ const date = new Date(session.completedAt || session.startedAt)
226
+ const key = date.toISOString().split('T')[0]
227
+
228
+ if (!byDay[key]) {
229
+ byDay[key] = {
230
+ sessions: 0,
231
+ duration: 0,
232
+ commits: 0
233
+ }
234
+ }
235
+
236
+ byDay[key].sessions++
237
+ byDay[key].duration += session.duration || 0
238
+ byDay[key].commits += session.metrics?.commits || 0
239
+ }
240
+
241
+ return byDay
242
+ }
243
+
244
+ /**
245
+ * Format duration as human readable
246
+ * @param {number} seconds
247
+ * @returns {string}
248
+ */
249
+ formatDuration(seconds) {
250
+ if (seconds < 60) return `${seconds}s`
251
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m`
252
+
253
+ const hours = Math.floor(seconds / 3600)
254
+ const minutes = Math.round((seconds % 3600) / 60)
255
+
256
+ if (minutes === 0) return `${hours}h`
257
+ return `${hours}h ${minutes}m`
258
+ }
259
+
260
+ /**
261
+ * Generate metrics summary for display
262
+ * @param {'day'|'week'|'month'} period
263
+ * @returns {Promise<string>}
264
+ */
265
+ async generateSummary(period = 'week') {
266
+ const m = await this.getMetrics(period)
267
+
268
+ const periodLabel = {
269
+ day: 'Today',
270
+ week: 'This Week',
271
+ month: 'This Month',
272
+ all: 'All Time'
273
+ }[period]
274
+
275
+ return `
276
+ ## ${periodLabel}
277
+
278
+ | Metric | Value |
279
+ |--------|-------|
280
+ | Sessions | ${m.totalSessions} |
281
+ | Total Time | ${m.totalDurationFormatted} |
282
+ | Avg Session | ${m.averageDurationFormatted} |
283
+ | Tasks Done | ${m.tasksCompleted} |
284
+ | Files Changed | ${m.filesChanged} |
285
+ | Lines +/- | +${m.linesAdded} / -${m.linesRemoved} |
286
+ | Commits | ${m.commits} |
287
+ | Streak | ${m.streak} days |
288
+ | Score | ${m.productivityScore}/100 |
289
+ `.trim()
290
+ }
291
+ }
292
+
293
+ module.exports = SessionMetrics
package/package.json CHANGED
@@ -1,16 +1,24 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.10.14",
3
+ "version": "0.11.1",
4
4
  "description": "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
5
5
  "main": "core/index.js",
6
6
  "bin": {
7
- "prjct": "bin/prjct"
7
+ "prjct": "bin/prjct",
8
+ "prjct-dev": "bin/dev.js",
9
+ "prjct-serve": "bin/serve.js"
8
10
  },
11
+ "workspaces": [
12
+ "packages/*"
13
+ ],
9
14
  "publishConfig": {
10
15
  "access": "public",
11
16
  "registry": "https://registry.npmjs.org"
12
17
  },
13
18
  "scripts": {
19
+ "dev": "turbo run dev",
20
+ "build": "turbo run build",
21
+ "serve": "turbo run dev --filter=@prjct/server --filter=@prjct/web",
14
22
  "postinstall": "node scripts/postinstall.js",
15
23
  "update-commands": "node -e \"const installer = require('./core/infrastructure/command-installer'); installer.syncCommands().then(r => console.log('Commands updated:', r)).catch(e => console.error('Error:', e.message))\"",
16
24
  "install-global": "./scripts/install.sh",
@@ -41,6 +49,7 @@
41
49
  "dependencies": {
42
50
  "chalk": "^4.1.2",
43
51
  "glob": "^10.3.10",
52
+ "lightningcss": "^1.30.2",
44
53
  "prompts": "^2.4.2"
45
54
  },
46
55
  "devDependencies": {
@@ -52,6 +61,7 @@
52
61
  "eslint-plugin-n": "^16.6.2",
53
62
  "eslint-plugin-promise": "^6.6.0",
54
63
  "prettier": "^3.6.2",
64
+ "turbo": "^2.3.0",
55
65
  "vitest": "^3.2.4"
56
66
  },
57
67
  "repository": {
@@ -62,12 +72,23 @@
62
72
  "url": "https://github.com/jlopezlira/prjct-cli/issues"
63
73
  },
64
74
  "homepage": "https://prjct.app",
75
+ "packageManager": "npm@10.2.4",
65
76
  "engines": {
66
77
  "node": ">=18.0.0"
67
78
  },
68
79
  "files": [
69
80
  "bin/",
70
81
  "core/",
82
+ "packages/shared/",
83
+ "packages/web/app/",
84
+ "packages/web/components/",
85
+ "packages/web/context/",
86
+ "packages/web/hooks/",
87
+ "packages/web/lib/",
88
+ "packages/web/public/",
89
+ "packages/web/*.ts",
90
+ "packages/web/*.json",
91
+ "packages/web/*.mjs",
71
92
  "templates/",
72
93
  "scripts/postinstall.js",
73
94
  "scripts/install.sh",
@@ -79,5 +100,8 @@
79
100
  "trustedDependencies": [
80
101
  "chalk",
81
102
  "prompts"
82
- ]
83
- }
103
+ ],
104
+ "overrides": {
105
+ "esbuild": "^0.25.0"
106
+ }
107
+ }