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.
- package/CHANGELOG.md +19 -0
- package/bin/dev.js +217 -0
- package/bin/prjct +10 -0
- package/bin/serve.js +78 -0
- package/core/bus/index.js +322 -0
- package/core/command-registry.js +65 -0
- package/core/domain/snapshot-manager.js +375 -0
- package/core/plugin/hooks.js +313 -0
- package/core/plugin/index.js +52 -0
- package/core/plugin/loader.js +331 -0
- package/core/plugin/registry.js +325 -0
- package/core/plugins/webhook.js +143 -0
- package/core/session/index.js +449 -0
- package/core/session/metrics.js +293 -0
- package/package.json +28 -4
- package/packages/shared/dist/index.d.ts +615 -0
- package/packages/shared/dist/index.js +204 -0
- package/packages/shared/package.json +29 -0
- package/packages/shared/src/index.ts +9 -0
- package/packages/shared/src/schemas.ts +124 -0
- package/packages/shared/src/types.ts +187 -0
- package/packages/shared/src/utils.ts +148 -0
- package/packages/shared/tsconfig.json +18 -0
- package/packages/web/README.md +36 -0
- package/packages/web/app/api/claude/sessions/route.ts +44 -0
- package/packages/web/app/api/claude/status/route.ts +34 -0
- package/packages/web/app/api/projects/[id]/delete/route.ts +21 -0
- package/packages/web/app/api/projects/[id]/icon/route.ts +33 -0
- package/packages/web/app/api/projects/[id]/route.ts +29 -0
- package/packages/web/app/api/projects/[id]/stats/route.ts +36 -0
- package/packages/web/app/api/projects/[id]/status/route.ts +21 -0
- package/packages/web/app/api/projects/route.ts +16 -0
- package/packages/web/app/api/sessions/history/route.ts +122 -0
- package/packages/web/app/api/stats/route.ts +38 -0
- package/packages/web/app/error.tsx +34 -0
- package/packages/web/app/favicon.ico +0 -0
- package/packages/web/app/globals.css +155 -0
- package/packages/web/app/layout.tsx +43 -0
- package/packages/web/app/loading.tsx +7 -0
- package/packages/web/app/not-found.tsx +25 -0
- package/packages/web/app/page.tsx +227 -0
- package/packages/web/app/project/[id]/error.tsx +41 -0
- package/packages/web/app/project/[id]/loading.tsx +9 -0
- package/packages/web/app/project/[id]/not-found.tsx +27 -0
- package/packages/web/app/project/[id]/page.tsx +253 -0
- package/packages/web/app/project/[id]/stats/page.tsx +447 -0
- package/packages/web/app/sessions/page.tsx +165 -0
- package/packages/web/app/settings/page.tsx +150 -0
- package/packages/web/components/AppSidebar.tsx +113 -0
- package/packages/web/components/CommandButton.tsx +39 -0
- package/packages/web/components/ConnectionStatus.tsx +29 -0
- package/packages/web/components/Logo.tsx +65 -0
- package/packages/web/components/MarkdownContent.tsx +123 -0
- package/packages/web/components/ProjectAvatar.tsx +54 -0
- package/packages/web/components/TechStackBadges.tsx +20 -0
- package/packages/web/components/TerminalTab.tsx +84 -0
- package/packages/web/components/TerminalTabs.tsx +210 -0
- package/packages/web/components/charts/SessionsChart.tsx +172 -0
- package/packages/web/components/providers.tsx +45 -0
- package/packages/web/components/ui/alert-dialog.tsx +157 -0
- package/packages/web/components/ui/badge.tsx +46 -0
- package/packages/web/components/ui/button.tsx +60 -0
- package/packages/web/components/ui/card.tsx +92 -0
- package/packages/web/components/ui/chart.tsx +385 -0
- package/packages/web/components/ui/dropdown-menu.tsx +257 -0
- package/packages/web/components/ui/scroll-area.tsx +58 -0
- package/packages/web/components/ui/sheet.tsx +139 -0
- package/packages/web/components/ui/tabs.tsx +66 -0
- package/packages/web/components/ui/tooltip.tsx +61 -0
- package/packages/web/components.json +22 -0
- package/packages/web/context/TerminalContext.tsx +45 -0
- package/packages/web/context/TerminalTabsContext.tsx +136 -0
- package/packages/web/eslint.config.mjs +18 -0
- package/packages/web/hooks/useClaudeTerminal.ts +375 -0
- package/packages/web/hooks/useProjectStats.ts +38 -0
- package/packages/web/hooks/useProjects.ts +73 -0
- package/packages/web/hooks/useStats.ts +28 -0
- package/packages/web/lib/format.ts +23 -0
- package/packages/web/lib/parse-prjct-files.ts +1122 -0
- package/packages/web/lib/projects.ts +452 -0
- package/packages/web/lib/pty.ts +101 -0
- package/packages/web/lib/query-config.ts +44 -0
- package/packages/web/lib/utils.ts +6 -0
- package/packages/web/next-env.d.ts +6 -0
- package/packages/web/next.config.ts +7 -0
- package/packages/web/package.json +53 -0
- package/packages/web/postcss.config.mjs +7 -0
- package/packages/web/public/file.svg +1 -0
- package/packages/web/public/globe.svg +1 -0
- package/packages/web/public/next.svg +1 -0
- package/packages/web/public/vercel.svg +1 -0
- package/packages/web/public/window.svg +1 -0
- package/packages/web/server.ts +262 -0
- package/packages/web/tsconfig.json +34 -0
- package/templates/commands/done.md +176 -54
- package/templates/commands/history.md +176 -0
- package/templates/commands/init.md +28 -1
- package/templates/commands/now.md +191 -9
- package/templates/commands/pause.md +176 -12
- package/templates/commands/redo.md +142 -0
- package/templates/commands/resume.md +166 -62
- package/templates/commands/serve.md +121 -0
- package/templates/commands/ship.md +45 -1
- package/templates/commands/sync.md +34 -1
- 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.
|
|
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
|
+
}
|