prjct-cli 0.13.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
}
|
|
@@ -16,6 +16,7 @@ interface DailyStats {
|
|
|
16
16
|
date: string
|
|
17
17
|
tasks: number
|
|
18
18
|
ships: number
|
|
19
|
+
partial: number
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export async function GET() {
|
|
@@ -37,16 +38,19 @@ export async function GET() {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
// Aggregate by date
|
|
40
|
-
const dailyMap = new Map<string, { tasks: number; ships: number }>()
|
|
41
|
+
const dailyMap = new Map<string, { tasks: number; ships: number; partial: number }>()
|
|
41
42
|
|
|
42
|
-
// Calculate date range (last
|
|
43
|
+
// Calculate date range (last 30 days)
|
|
43
44
|
const endDate = new Date()
|
|
44
45
|
const startDate = new Date()
|
|
45
|
-
startDate.setDate(startDate.getDate() -
|
|
46
|
+
startDate.setDate(startDate.getDate() - 30)
|
|
46
47
|
|
|
47
48
|
for (const projectId of projects) {
|
|
48
|
-
|
|
49
|
+
// Skip hidden directories
|
|
50
|
+
if (projectId.startsWith('.')) continue
|
|
49
51
|
|
|
52
|
+
// Read from memory/context.jsonl (legacy)
|
|
53
|
+
const contextPath = join(globalStorage, projectId, 'memory', 'context.jsonl')
|
|
50
54
|
try {
|
|
51
55
|
const content = await fs.readFile(contextPath, 'utf-8')
|
|
52
56
|
const lines = content.trim().split('\n').filter(Boolean)
|
|
@@ -62,12 +66,12 @@ export async function GET() {
|
|
|
62
66
|
if (eventDate < startDate || eventDate > endDate) continue
|
|
63
67
|
|
|
64
68
|
const dateKey = eventDate.toISOString().split('T')[0]
|
|
65
|
-
const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0 }
|
|
69
|
+
const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
|
|
66
70
|
|
|
67
71
|
const eventType = event.type || event.action
|
|
68
72
|
|
|
69
73
|
// Count tasks completed
|
|
70
|
-
if (eventType === 'task_complete' || eventType === 'task_completed') {
|
|
74
|
+
if (eventType === 'task_complete' || eventType === 'task_completed' || eventType === 'session_completed') {
|
|
71
75
|
current.tasks++
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -76,6 +80,11 @@ export async function GET() {
|
|
|
76
80
|
current.ships++
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
// Count partial/abandoned sessions
|
|
84
|
+
if (eventType === 'session_partial' || eventType === 'session_abandoned') {
|
|
85
|
+
current.partial++
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
dailyMap.set(dateKey, current)
|
|
80
89
|
} catch {
|
|
81
90
|
// Skip malformed lines
|
|
@@ -84,21 +93,94 @@ export async function GET() {
|
|
|
84
93
|
} catch {
|
|
85
94
|
// Skip projects without context.jsonl
|
|
86
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
|
+
}
|
|
87
162
|
}
|
|
88
163
|
|
|
89
|
-
//
|
|
90
|
-
const data: DailyStats[] =
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
93
172
|
tasks: stats.tasks,
|
|
94
|
-
ships: stats.ships
|
|
95
|
-
|
|
96
|
-
|
|
173
|
+
ships: stats.ships,
|
|
174
|
+
partial: stats.partial
|
|
175
|
+
})
|
|
176
|
+
currentDate.setDate(currentDate.getDate() + 1)
|
|
177
|
+
}
|
|
97
178
|
|
|
98
179
|
// Calculate totals for summary
|
|
99
180
|
const totals = {
|
|
100
181
|
tasks: data.reduce((sum, d) => sum + d.tasks, 0),
|
|
101
|
-
ships: data.reduce((sum, d) => sum + d.ships, 0)
|
|
182
|
+
ships: data.reduce((sum, d) => sum + d.ships, 0),
|
|
183
|
+
partial: data.reduce((sum, d) => sum + d.partial, 0)
|
|
102
184
|
}
|
|
103
185
|
|
|
104
186
|
return NextResponse.json({
|
|
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from 'next/font/google'
|
|
|
3
3
|
import './globals.css'
|
|
4
4
|
import { Providers } from '@/components/Providers'
|
|
5
5
|
import { AppSidebar } from '@/components/AppSidebar'
|
|
6
|
+
import { TerminalDock } from '@/components/TerminalDock'
|
|
6
7
|
|
|
7
8
|
const geistSans = Geist({
|
|
8
9
|
variable: '--font-geist-sans',
|
|
@@ -44,6 +45,7 @@ export default function RootLayout({
|
|
|
44
45
|
{children}
|
|
45
46
|
</main>
|
|
46
47
|
</div>
|
|
48
|
+
<TerminalDock />
|
|
47
49
|
</Providers>
|
|
48
50
|
</body>
|
|
49
51
|
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { getProject } from '@/lib/services/projects.server'
|
|
3
|
+
import { getProjectEmoji } from '@/lib/project-colors'
|
|
4
|
+
|
|
5
|
+
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
|
6
|
+
const { id: projectId } = await params
|
|
7
|
+
const project = await getProject(projectId)
|
|
8
|
+
const projectName = project?.name ?? projectId
|
|
9
|
+
const emoji = getProjectEmoji(projectId)
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
title: `${emoji} ${projectName} / Code / p.`,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function CodeLayout({ children }: { children: React.ReactNode }) {
|
|
17
|
+
return children
|
|
18
|
+
}
|