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 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Custom Next.js server with WebSocket support for PTY
3
+ *
4
+ * PTY sessions are managed here (not in API routes) to share memory context
5
+ */
6
+
7
+ import { createServer } from 'http'
8
+ import next from 'next'
9
+ import { WebSocketServer, WebSocket } from 'ws'
10
+ import * as pty from 'node-pty'
11
+ import type { IPty } from 'node-pty'
12
+
13
+ const dev = process.env.NODE_ENV !== 'production'
14
+ const hostname = 'localhost'
15
+ const port = parseInt(process.env.PORT || '9472', 10)
16
+
17
+ // PTY Sessions stored in server memory
18
+ interface Session {
19
+ pty: IPty
20
+ projectDir: string
21
+ createdAt: Date
22
+ hasStartedClaude: boolean // Track if claude command was sent
23
+ }
24
+
25
+ const sessions = new Map<string, Session>()
26
+
27
+ function createSession(sessionId: string, projectDir: string): { pty: IPty; isNew: boolean } {
28
+ const existing = sessions.get(sessionId)
29
+
30
+ // If session exists for this project, reuse it (allows multiple tabs)
31
+ if (existing) {
32
+ console.log(`[PTY] Reusing existing session: ${sessionId}`)
33
+ return { pty: existing.pty, isNew: false }
34
+ }
35
+
36
+ const shell = process.platform === 'win32' ? 'cmd.exe' : 'bash'
37
+ const args = process.platform === 'win32' ? [] : ['-l']
38
+
39
+ const ptyProcess = pty.spawn(shell, args, {
40
+ name: 'xterm-256color',
41
+ cols: 120,
42
+ rows: 30,
43
+ cwd: projectDir,
44
+ env: {
45
+ ...process.env,
46
+ TERM: 'xterm-256color',
47
+ COLORTERM: 'truecolor'
48
+ }
49
+ })
50
+
51
+ sessions.set(sessionId, {
52
+ pty: ptyProcess,
53
+ projectDir,
54
+ createdAt: new Date(),
55
+ hasStartedClaude: false
56
+ })
57
+
58
+ // NOTE: Don't auto-start claude here - let the WebSocket handler do it
59
+ // once the client is connected and ready to receive output
60
+
61
+ return { pty: ptyProcess, isNew: true }
62
+ }
63
+
64
+ function getSession(sessionId: string): IPty | null {
65
+ return sessions.get(sessionId)?.pty || null
66
+ }
67
+
68
+ function killSession(sessionId: string): void {
69
+ const session = sessions.get(sessionId)
70
+ if (session) {
71
+ try { session.pty.kill() } catch {}
72
+ sessions.delete(sessionId)
73
+ }
74
+ }
75
+
76
+ function resizeSession(sessionId: string, cols: number, rows: number): void {
77
+ const session = sessions.get(sessionId)
78
+ if (session) {
79
+ try { session.pty.resize(cols, rows) } catch {}
80
+ }
81
+ }
82
+
83
+ const app = next({ dev, hostname, port })
84
+ const handle = app.getRequestHandler()
85
+
86
+ app.prepare().then(() => {
87
+ const server = createServer(async (req, res) => {
88
+ const url = new URL(req.url || '', `http://${req.headers.host}`)
89
+
90
+ // Handle session creation directly in server (bypasses API route isolation)
91
+ if (url.pathname === '/api/claude/sessions' && req.method === 'POST') {
92
+ let body = ''
93
+ req.on('data', chunk => { body += chunk })
94
+ req.on('end', () => {
95
+ try {
96
+ const { sessionId, projectDir } = JSON.parse(body)
97
+ if (!sessionId || !projectDir) {
98
+ res.statusCode = 400
99
+ res.setHeader('Content-Type', 'application/json')
100
+ res.end(JSON.stringify({ success: false, error: 'sessionId and projectDir required' }))
101
+ return
102
+ }
103
+
104
+ const { isNew } = createSession(sessionId, projectDir)
105
+ console.log(`[PTY] ${isNew ? 'Created' : 'Reusing'} session: ${sessionId} for ${projectDir}`)
106
+
107
+ res.statusCode = 200
108
+ res.setHeader('Content-Type', 'application/json')
109
+ res.end(JSON.stringify({ success: true, data: { sessionId, projectDir, isNew } }))
110
+ } catch (err) {
111
+ console.error('[PTY] Error creating session:', err)
112
+ res.statusCode = 500
113
+ res.setHeader('Content-Type', 'application/json')
114
+ res.end(JSON.stringify({ success: false, error: 'Failed to create session' }))
115
+ }
116
+ })
117
+ return
118
+ }
119
+
120
+ // Handle session list
121
+ if (url.pathname === '/api/claude/sessions' && req.method === 'GET') {
122
+ const list = Array.from(sessions.entries()).map(([id, s]) => ({
123
+ sessionId: id,
124
+ projectDir: s.projectDir,
125
+ createdAt: s.createdAt
126
+ }))
127
+ res.statusCode = 200
128
+ res.setHeader('Content-Type', 'application/json')
129
+ res.end(JSON.stringify({ success: true, data: list }))
130
+ return
131
+ }
132
+
133
+ // All other requests go to Next.js
134
+ try {
135
+ await handle(req, res)
136
+ } catch (err) {
137
+ console.error('Error handling request:', err)
138
+ res.statusCode = 500
139
+ res.end('Internal Server Error')
140
+ }
141
+ })
142
+
143
+ // WebSocket server for PTY communication
144
+ const wss = new WebSocketServer({ noServer: true })
145
+
146
+ // Heartbeat interval to detect dead connections (30 seconds)
147
+ const HEARTBEAT_INTERVAL = 30000
148
+
149
+ const heartbeatInterval = setInterval(() => {
150
+ wss.clients.forEach((ws) => {
151
+ const extWs = ws as WebSocket & { isAlive?: boolean; sessionId?: string }
152
+ if (extWs.isAlive === false) {
153
+ console.log(`[WS] Terminating dead connection: ${extWs.sessionId}`)
154
+ if (extWs.sessionId) {
155
+ killSession(extWs.sessionId)
156
+ }
157
+ return ws.terminate()
158
+ }
159
+ extWs.isAlive = false
160
+ ws.ping()
161
+ })
162
+ }, HEARTBEAT_INTERVAL)
163
+
164
+ // Cleanup on server close
165
+ wss.on('close', () => {
166
+ clearInterval(heartbeatInterval)
167
+ })
168
+
169
+ server.on('upgrade', (request, socket, head) => {
170
+ const url = new URL(request.url || '', `http://${request.headers.host}`)
171
+
172
+ if (url.pathname.startsWith('/ws/claude/')) {
173
+ wss.handleUpgrade(request, socket, head, (ws) => {
174
+ wss.emit('connection', ws, request)
175
+ })
176
+ }
177
+ // Other upgrades (HMR) pass through to Next.js
178
+ })
179
+
180
+ wss.on('connection', (ws: WebSocket, request) => {
181
+ const url = new URL(request.url || '', `http://${request.headers.host}`)
182
+ const sessionId = url.pathname.replace('/ws/claude/', '')
183
+
184
+ console.log(`[WS] New PTY connection for session: ${sessionId}`)
185
+
186
+ // Mark connection as alive and store sessionId for heartbeat
187
+ const extWs = ws as WebSocket & { isAlive?: boolean; sessionId?: string }
188
+ extWs.isAlive = true
189
+ extWs.sessionId = sessionId
190
+
191
+ // Handle pong response
192
+ ws.on('pong', () => {
193
+ extWs.isAlive = true
194
+ })
195
+
196
+ const session = sessions.get(sessionId)
197
+
198
+ if (!session) {
199
+ console.log(`[WS] Session not found: ${sessionId}`)
200
+ ws.send(JSON.stringify({ type: 'error', message: 'Session not found' }))
201
+ ws.close()
202
+ return
203
+ }
204
+
205
+ const ptyProcess = session.pty
206
+
207
+ // Register data handler FIRST before sending any commands
208
+ const dataHandler = ptyProcess.onData((data: string) => {
209
+ if (ws.readyState === WebSocket.OPEN) {
210
+ ws.send(JSON.stringify({ type: 'output', data }))
211
+ }
212
+ })
213
+
214
+ ws.send(JSON.stringify({ type: 'connected', sessionId }))
215
+
216
+ // Auto-start Claude CLI only once, when first client connects
217
+ if (!session.hasStartedClaude) {
218
+ session.hasStartedClaude = true
219
+ console.log(`[WS] Starting Claude CLI for session: ${sessionId}`)
220
+ setTimeout(() => {
221
+ ptyProcess.write('claude\r')
222
+ }, 200) // Small delay to ensure client is ready
223
+ }
224
+
225
+ const exitHandler = ptyProcess.onExit(({ exitCode }) => {
226
+ if (ws.readyState === WebSocket.OPEN) {
227
+ ws.send(JSON.stringify({ type: 'exit', code: exitCode }))
228
+ }
229
+ killSession(sessionId)
230
+ })
231
+
232
+ ws.on('message', (message: Buffer) => {
233
+ try {
234
+ const { type, data, cols, rows } = JSON.parse(message.toString())
235
+ switch (type) {
236
+ case 'input':
237
+ ptyProcess?.write(data)
238
+ break
239
+ case 'resize':
240
+ if (cols && rows) resizeSession(sessionId, cols, rows)
241
+ break
242
+ }
243
+ } catch (err) {
244
+ console.error('[WS] Error:', err)
245
+ }
246
+ })
247
+
248
+ ws.on('close', () => {
249
+ console.log(`[WS] PTY connection closed: ${sessionId}`)
250
+ dataHandler.dispose()
251
+ exitHandler.dispose()
252
+ })
253
+
254
+ ws.on('error', (error) => {
255
+ console.error(`[WS] Error for ${sessionId}:`, error)
256
+ })
257
+ })
258
+
259
+ server.listen(port, () => {
260
+ console.log(`> prjct ready on http://${hostname}:${port}`)
261
+ })
262
+ })
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
@@ -1,15 +1,17 @@
1
1
  ---
2
- allowed-tools: [Read, Write]
3
- description: 'Complete current task'
2
+ allowed-tools: [Read, Write, Bash]
3
+ description: 'Complete current task with session metrics'
4
4
  timestamp-rule: 'GetTimestamp() for all timestamps'
5
5
  ---
6
6
 
7
- # /p:done - Complete Current Task
7
+ # /p:done - Complete Current Task with Session Metrics
8
8
 
9
9
  ## Context Variables
10
10
  - `{projectId}`: From `.prjct/prjct.config.json`
11
11
  - `{globalPath}`: `~/.prjct-cli/projects/{projectId}`
12
12
  - `{nowPath}`: `{globalPath}/core/now.md`
13
+ - `{sessionPath}`: `{globalPath}/sessions/current.json`
14
+ - `{archiveDir}`: `{globalPath}/sessions/archive`
13
15
  - `{memoryPath}`: `{globalPath}/memory/context.jsonl`
14
16
  - `{metricsPath}`: `{globalPath}/progress/metrics.md`
15
17
 
@@ -22,116 +24,236 @@ IF file not found:
22
24
  OUTPUT: "No prjct project. Run /p:init first."
23
25
  STOP
24
26
 
25
- ## Step 2: Validate Active Task
27
+ ## Step 2: Check Session State
26
28
 
29
+ ### Try structured session first
30
+ READ: `{sessionPath}`
31
+
32
+ IF file exists:
33
+ PARSE as JSON
34
+ EXTRACT: {session} object
35
+ GOTO Step 3 (Session Completion)
36
+
37
+ ### Fallback to legacy now.md
27
38
  READ: `{nowPath}`
28
39
 
29
40
  IF empty OR contains "No current task":
30
41
  OUTPUT: "⚠️ No active task to complete. Use /p:now to start one."
31
42
  STOP
32
43
 
33
- ## Step 3: Extract Task Data
44
+ ## Step 3: Session Completion
34
45
 
35
- From NOW file content, extract:
46
+ ### Calculate Final Duration
47
+ SET: {now} = GetTimestamp()
36
48
 
37
- 1. **Task name**: Text between `**` markers
38
- - Pattern: `**(.+?)**`
39
- - Example: `**implement auth**` → "implement auth"
49
+ For each event in {session.timeline}:
50
+ Track start/resume/pause/complete times
51
+ Calculate total active time
40
52
 
41
- 2. **Start time**: Text after "Started:"
42
- - Pattern: `Started: (.+)`
43
- - Example: `Started: 11/28/2025, 2:30:00 PM`
53
+ SET: {duration} = total active seconds
54
+ SET: {durationFormatted} = format as "Xh Ym" or "Xm"
44
55
 
45
- 3. **Calculate duration**:
46
- - Current: GetTimestamp()
47
- - Duration: current - started
48
- - Format: "Xh Ym" (e.g., "2h 15m")
49
- - If < 1 hour: "Xm"
50
- - If < 1 minute: "< 1m"
56
+ ### Calculate Git Metrics
57
+ BASH: `git rev-list --count --since="{session.startedAt}" HEAD 2>/dev/null || echo "0"`
58
+ CAPTURE as {commits}
51
59
 
52
- ## Step 4: Clear Task File
60
+ BASH: `git diff --stat HEAD~{commits} 2>/dev/null || git diff --stat`
61
+ PARSE output for:
62
+ - {filesChanged}: number of files
63
+ - {linesAdded}: insertions
64
+ - {linesRemoved}: deletions
53
65
 
54
- WRITE: `{nowPath}`
66
+ ### Update Session Object
67
+ ```json
68
+ {
69
+ "id": "{session.id}",
70
+ "projectId": "{projectId}",
71
+ "task": "{session.task}",
72
+ "status": "completed",
73
+ "startedAt": "{session.startedAt}",
74
+ "pausedAt": null,
75
+ "completedAt": "{now}",
76
+ "duration": {duration},
77
+ "metrics": {
78
+ "filesChanged": {filesChanged},
79
+ "linesAdded": {linesAdded},
80
+ "linesRemoved": {linesRemoved},
81
+ "commits": {commits},
82
+ "snapshots": {session.metrics.snapshots}
83
+ },
84
+ "timeline": [
85
+ ...{session.timeline},
86
+ {"type": "complete", "at": "{now}"}
87
+ ]
88
+ }
89
+ ```
90
+
91
+ ## Step 4: Archive Session
92
+
93
+ ### Create Archive Directory
94
+ GET: {yearMonth} = YYYY-MM from {now}
95
+ ENSURE: `{archiveDir}/{yearMonth}` exists
96
+
97
+ BASH: `mkdir -p {archiveDir}/{yearMonth}`
98
+
99
+ ### Write Archived Session
100
+ WRITE: `{archiveDir}/{yearMonth}/{session.id}.json`
101
+ Content: Updated session object from Step 3
102
+
103
+ ## Step 5: Clear Current Session
104
+
105
+ DELETE: `{sessionPath}`
55
106
 
56
- Content (exact):
107
+ OR WRITE empty state:
108
+ WRITE: `{sessionPath}`
109
+ Content:
110
+ ```json
111
+ {}
112
+ ```
113
+
114
+ ## Step 6: Update Legacy now.md
115
+
116
+ WRITE: `{nowPath}`
117
+ Content:
57
118
  ```markdown
58
119
  # NOW
59
120
 
60
121
  No current task. Use `/p:now` to set focus.
61
122
  ```
62
123
 
63
- ## Step 5: Log to Memory
124
+ ## Step 7: Log to Memory
64
125
 
65
126
  APPEND to: `{memoryPath}`
66
127
 
67
128
  Single line (JSONL format):
68
129
  ```json
69
- {"timestamp":"{GetTimestamp()}","action":"task_completed","task":"{task}","duration":"{duration}"}
130
+ {"timestamp":"{now}","action":"session_completed","sessionId":"{session.id}","task":"{session.task}","duration":{duration},"metrics":{"files":{filesChanged},"added":{linesAdded},"removed":{linesRemoved},"commits":{commits}}}
70
131
  ```
71
132
 
72
- ## Step 6: Update Metrics (Optional)
133
+ ## Step 8: Update Metrics Summary
134
+
135
+ READ: `{metricsPath}` (create if not exists)
73
136
 
74
- IF `{metricsPath}` exists:
75
- READ current content
76
- APPEND new entry with task and duration
137
+ ### Append Daily Entry
138
+ GET: {date} = YYYY-MM-DD from {now}
139
+
140
+ INSERT or UPDATE entry for {date}:
141
+ ```markdown
142
+ ### {date}
143
+ - **{session.task}** ({durationFormatted})
144
+ - Files: {filesChanged} | +{linesAdded}/-{linesRemoved} | Commits: {commits}
145
+ ```
77
146
 
78
147
  ## Output
79
148
 
80
149
  SUCCESS:
81
150
  ```
82
- ✅ {task} ({duration})
151
+ ✅ {session.task} ({durationFormatted})
152
+
153
+ Session: {session.id}
154
+ Files: {filesChanged} | +{linesAdded}/-{linesRemoved}
155
+ Commits: {commits}
83
156
 
84
157
  Next:
85
158
  • /p:now - Start next task
86
159
  • /p:ship - Ship completed work
87
- • /p:next - See priority queue
160
+ • /p:progress - View metrics
88
161
  ```
89
162
 
90
163
  ## Error Handling
91
164
 
92
- | Error | Response |
93
- |-------|----------|
94
- | Config not found | "No prjct project. Run /p:init first." |
95
- | Now.md empty | "⚠️ No active task. Use /p:now to start." |
96
- | Parse fails | Use task = "task", duration = "unknown", continue |
97
- | Write fails | Log warning, continue (non-critical) |
165
+ | Error | Response | Action |
166
+ |-------|----------|--------|
167
+ | Config not found | "No prjct project" | STOP |
168
+ | No session/task | "No active task" | STOP |
169
+ | Git fails | Use zeros for metrics | CONTINUE |
170
+ | Archive fails | Log warning | CONTINUE |
171
+ | Write fails | Log warning | CONTINUE |
98
172
 
99
173
  ## Examples
100
174
 
101
- ### Example 1: Success
102
- **now.md content:**
175
+ ### Example 1: Full Session Completion
176
+ **Session:**
177
+ ```json
178
+ {
179
+ "id": "sess_abc12345",
180
+ "task": "implement authentication",
181
+ "status": "active",
182
+ "startedAt": "2025-12-07T10:00:00.000Z",
183
+ "timeline": [
184
+ {"type": "start", "at": "2025-12-07T10:00:00.000Z"}
185
+ ]
186
+ }
103
187
  ```
104
- # NOW
105
188
 
106
- **implement authentication**
189
+ **Git activity:**
190
+ - 3 commits
191
+ - 5 files changed
192
+ - +120/-30 lines
107
193
 
108
- Started: 11/28/2025, 12:15:00 PM
194
+ **Output:**
109
195
  ```
196
+ ✅ implement authentication (2h 15m)
110
197
 
111
- **Current time:** 11/28/2025, 2:30:00 PM
112
- **Duration:** 2h 15m
113
- **Output:** `✅ implement authentication (2h 15m)`
198
+ Session: sess_abc12345
199
+ Files: 5 | +120/-30
200
+ Commits: 3
114
201
 
115
- ### Example 2: No Task
116
- **now.md content:**
202
+ Next:
203
+ • /p:now - Start next task
204
+ • /p:ship - Ship completed work
205
+ • /p:progress - View metrics
117
206
  ```
118
- # NOW
119
207
 
120
- No current task.
208
+ ### Example 2: Session with Pauses
209
+ **Session with multiple pause/resume:**
210
+ ```json
211
+ {
212
+ "id": "sess_xyz98765",
213
+ "task": "fix login bug",
214
+ "timeline": [
215
+ {"type": "start", "at": "2025-12-07T09:00:00.000Z"},
216
+ {"type": "pause", "at": "2025-12-07T10:00:00.000Z"},
217
+ {"type": "resume", "at": "2025-12-07T14:00:00.000Z"},
218
+ {"type": "pause", "at": "2025-12-07T15:30:00.000Z"},
219
+ {"type": "resume", "at": "2025-12-07T16:00:00.000Z"}
220
+ ]
221
+ }
222
+ ```
223
+
224
+ **Completion at 17:00:**
225
+ - Active time: 1h + 1.5h + 1h = 3.5h
226
+ - Duration: 3h 30m
227
+
228
+ **Output:**
121
229
  ```
230
+ ✅ fix login bug (3h 30m)
122
231
 
123
- **Output:** `⚠️ No active task to complete. Use /p:now to start one.`
232
+ Session: sess_xyz98765
233
+ Files: 2 | +45/-12
234
+ Commits: 1
124
235
 
125
- ### Example 3: Quick Task
236
+ Next:
237
+ • /p:now - Start next task
238
+ • /p:ship - Ship completed work
239
+ • /p:progress - View metrics
240
+ ```
241
+
242
+ ### Example 3: Legacy Fallback (No Session)
126
243
  **now.md content:**
127
244
  ```
128
245
  # NOW
129
246
 
130
- **fix typo in readme**
247
+ **quick fix**
131
248
 
132
- Started: 11/28/2025, 2:25:00 PM
249
+ Started: 2025-12-07T16:45:00.000Z
133
250
  ```
134
251
 
135
- **Current time:** 11/28/2025, 2:30:00 PM
136
- **Duration:** 5m
137
- **Output:** `✅ fix typo in readme (5m)`
252
+ **Output:**
253
+ ```
254
+ quick fix (15m)
255
+
256
+ Next:
257
+ • /p:now - Start next task
258
+ • /p:ship - Ship completed work
259
+ ```