vibe-pomo 0.1.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/LICENSE +21 -0
- package/README.md +202 -0
- package/bin/pomodoro.mjs +249 -0
- package/install.mjs +170 -0
- package/package.json +49 -0
- package/src/daemon/db.mjs +209 -0
- package/src/daemon/index.mjs +212 -0
- package/src/daemon/ipc.mjs +96 -0
- package/src/daemon/notificationQueue.mjs +39 -0
- package/src/daemon/session.mjs +109 -0
- package/src/daemon/timer.mjs +100 -0
- package/src/hooks/notification.mjs +53 -0
- package/src/hooks/preToolUse.mjs +38 -0
- package/src/hooks/stop.mjs +70 -0
- package/src/shared/config.mjs +58 -0
- package/src/shared/ipcClient.mjs +73 -0
- package/src/shared/lockfile.mjs +51 -0
- package/src/shared/protocol.mjs +44 -0
- package/src/tui/App.tsx +149 -0
- package/src/tui/components/NotificationLog.tsx +60 -0
- package/src/tui/components/TaskBar.tsx +23 -0
- package/src/tui/components/Timer.tsx +47 -0
- package/src/tui/dashboard/ActiveSessions.tsx +67 -0
- package/src/tui/dashboard/DashboardApp.tsx +110 -0
- package/src/tui/dashboard/ProjectChart.tsx +60 -0
- package/src/tui/dashboard/RecentSessions.tsx +79 -0
- package/src/tui/dashboard/index.tsx +31 -0
- package/src/tui/index.tsx +56 -0
- package/src/tui/ipcClient.mjs +52 -0
- package/src/tui/timer/TimerApp.tsx +164 -0
- package/src/tui/timer/index.tsx +51 -0
- package/src/watcher.mjs +51 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Per-session Timer TUI.
|
|
4
|
+
* Launched in a new terminal window for each `pomodoro start`.
|
|
5
|
+
*
|
|
6
|
+
* Usage: tsx src/tui/timer/index.tsx <sessionId>
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import { render } from 'ink'
|
|
10
|
+
import { TimerApp } from './TimerApp.js'
|
|
11
|
+
import { sendAndReceive } from '../../shared/ipcClient.mjs'
|
|
12
|
+
import { MSG } from '../../shared/protocol.mjs'
|
|
13
|
+
|
|
14
|
+
const sessionId = process.argv[2]
|
|
15
|
+
if (!sessionId) {
|
|
16
|
+
console.error('Usage: pomodoro-timer <sessionId>')
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
// Fetch initial session state
|
|
22
|
+
let session: any = {}
|
|
23
|
+
try {
|
|
24
|
+
session = await sendAndReceive({ type: MSG.SESSION_QUERY, sessionId })
|
|
25
|
+
} catch {
|
|
26
|
+
console.error('Could not connect to Pomodoro daemon.')
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (session.error) {
|
|
31
|
+
console.error('Session not found:', sessionId)
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render(
|
|
36
|
+
<TimerApp
|
|
37
|
+
sessionId={sessionId}
|
|
38
|
+
task={session.task ?? ''}
|
|
39
|
+
plannedMs={session.plannedMs ?? 25 * 60 * 1000}
|
|
40
|
+
initialState={session.state}
|
|
41
|
+
initialRemaining={session.remaining}
|
|
42
|
+
initialOvertime={session.overtime}
|
|
43
|
+
/>,
|
|
44
|
+
{ exitOnCtrlC: false }
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main().catch((err) => {
|
|
49
|
+
console.error('Timer error:', err)
|
|
50
|
+
process.exit(1)
|
|
51
|
+
})
|
package/src/watcher.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle watcher.
|
|
3
|
+
*
|
|
4
|
+
* Monitors a target PID (the Claude Code process). When that process exits,
|
|
5
|
+
* automatically breaks the associated Pomodoro session.
|
|
6
|
+
*
|
|
7
|
+
* Spawned detached by `pomodoro start` when called from inside Claude Code.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node src/watcher.mjs <watchPid> <sessionId>
|
|
10
|
+
*/
|
|
11
|
+
import { sendAndReceive } from './shared/ipcClient.mjs'
|
|
12
|
+
import { readLock } from './shared/lockfile.mjs'
|
|
13
|
+
import { MSG } from './shared/protocol.mjs'
|
|
14
|
+
|
|
15
|
+
const [,, watchPidStr, sessionId] = process.argv
|
|
16
|
+
const watchPid = parseInt(watchPidStr, 10)
|
|
17
|
+
|
|
18
|
+
if (!watchPid || !sessionId) {
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isAlive(pid) {
|
|
23
|
+
try { process.kill(pid, 0); return true }
|
|
24
|
+
catch { return false }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function breakSession() {
|
|
28
|
+
const lock = readLock()
|
|
29
|
+
if (!lock) return
|
|
30
|
+
try {
|
|
31
|
+
await sendAndReceive({ type: MSG.SESSION_BREAK, sessionId })
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Poll every 3 seconds
|
|
36
|
+
const interval = setInterval(async () => {
|
|
37
|
+
if (!isAlive(watchPid)) {
|
|
38
|
+
clearInterval(interval)
|
|
39
|
+
await breakSession()
|
|
40
|
+
process.exit(0)
|
|
41
|
+
}
|
|
42
|
+
}, 3000)
|
|
43
|
+
|
|
44
|
+
// Don't keep event loop alive for other reasons
|
|
45
|
+
interval.unref()
|
|
46
|
+
|
|
47
|
+
// Also exit cleanly if daemon disappears
|
|
48
|
+
setTimeout(function check() {
|
|
49
|
+
if (!readLock()) { clearInterval(interval); process.exit(0) }
|
|
50
|
+
setTimeout(check, 10000).unref()
|
|
51
|
+
}, 10000).unref()
|