vibe-pomo 0.1.0 → 0.1.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/README.md +10 -1
- package/README.zh.md +208 -0
- package/bin/pomodoro.mjs +23 -12
- package/install.mjs +11 -6
- package/package.json +1 -1
- package/src/daemon/ipc.mjs +2 -2
- package/src/shared/lockfile.mjs +8 -3
package/README.md
CHANGED
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
> You and your agent, both in flow.
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/vibe-pomo)
|
|
6
|
+
[](https://www.npmjs.com/package/vibe-pomo)
|
|
7
|
+
|
|
5
8
|
<!-- screenshot: dashboard terminal showing active sessions + project stats -->
|
|
6
9
|
|
|
10
|
+
<!-- README-I18N:START -->
|
|
11
|
+
|
|
12
|
+
**English** | [汉语](./README.zh.md)
|
|
13
|
+
|
|
14
|
+
<!-- README-I18N:END -->
|
|
15
|
+
|
|
7
16
|
---
|
|
8
17
|
|
|
9
18
|
## Why vibe-pomo
|
|
@@ -79,7 +88,7 @@ This gets saved alongside the agent's summary, giving you a dual-perspective rec
|
|
|
79
88
|
**Prerequisites:** Node.js 20+, Claude Code CLI
|
|
80
89
|
|
|
81
90
|
```bash
|
|
82
|
-
npm
|
|
91
|
+
npm i -g vibe-pomo
|
|
83
92
|
pomodoro install
|
|
84
93
|
```
|
|
85
94
|
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# vibe-pomo 🍅
|
|
2
|
+
|
|
3
|
+
> 你和你的 AI 代理,同时进入心流。
|
|
4
|
+
|
|
5
|
+
<!-- screenshot: dashboard terminal showing active sessions + project stats -->
|
|
6
|
+
|
|
7
|
+
<!-- README-I18N:START -->
|
|
8
|
+
|
|
9
|
+
[English](./README.md) | **汉语**
|
|
10
|
+
|
|
11
|
+
<!-- README-I18N:END -->
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 为什么选择 vibe-pomo
|
|
16
|
+
|
|
17
|
+
大多数 AI 编码工具都建立在一个假设上:你始终在旁边盯着。每一次工具调用、每一个决策、每一次完成——代理 ping 你、等你、打断你。每次交互看似微小,但累积起来代价巨大:你从未能获得超过几分钟的不间断专注。
|
|
18
|
+
|
|
19
|
+
**vibe-pomo 反转了这一切。** 启动一个番茄钟,把任务交给代理,然后离开。代理自主工作——通知静音、决策排队、零打扰。计时结束后,由*你*决定何时回来,而不是代理。
|
|
20
|
+
|
|
21
|
+
**双向深度专注。**
|
|
22
|
+
为自己屏蔽干扰,让代理同时运行不间断的工作会话。没有上下文切换,没有被动响应循环。两条并行的心流,在你准备好时汇聚。
|
|
23
|
+
|
|
24
|
+
**清楚地知道时间去哪了。**
|
|
25
|
+
每次会话都会记录代理完成的工作以及你自己做的事情。查看每个项目的专注时间、浏览会话历史,精确了解时间的流向——为个人复盘和项目规划提供清晰的记录。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 工作原理
|
|
30
|
+
|
|
31
|
+
两个终端,两个角色:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
终端 A — 守护进程(保持打开) 终端 B — Claude Code 对话
|
|
35
|
+
───────────────────────────────── ───────────────────────────────────────
|
|
36
|
+
$ pomodoro daemon /pomodoro 25m Fix auth bug
|
|
37
|
+
|
|
|
38
|
+
🍅 Pomodoro daemon running +---> 计时器窗口打开
|
|
39
|
+
代理开始工作
|
|
40
|
+
Active Sessions 通知静音
|
|
41
|
+
23:41 my-project Fix auth bug 工具调用自动批准
|
|
42
|
+
|
|
43
|
+
Project Focus Time
|
|
44
|
+
my-project ████████████░░ 3h 45m
|
|
45
|
+
|
|
46
|
+
Recent Sessions
|
|
47
|
+
my-project Fix auth bug
|
|
48
|
+
🤖 Rewrote JWT middleware
|
|
49
|
+
👤 Had a planning call
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
计时器窗口(每个会话)
|
|
54
|
+
──────────────────────────────────
|
|
55
|
+
🍅 Pomodoro
|
|
56
|
+
|
|
57
|
+
+02:13 OVERTIME
|
|
58
|
+
|
|
59
|
+
Task: Fix auth bug
|
|
60
|
+
|
|
61
|
+
Notifications
|
|
62
|
+
┌──────────────────────────────┐
|
|
63
|
+
│ Build passed │
|
|
64
|
+
│ Tests: 42 passed │
|
|
65
|
+
└──────────────────────────────┘
|
|
66
|
+
|
|
67
|
+
[E] End Session [B] Break [Q] Quit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
计时结束后,排队的通知会被释放,并提示你记录在此期间*你*做了什么:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
What did you do during this session?
|
|
74
|
+
(optional — press Enter to skip)
|
|
75
|
+
|
|
76
|
+
> Reviewed the RFC, had a planning call with the team
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
这条记录会与代理的总结一同保存,为每次会话提供双视角记录。
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 安装
|
|
84
|
+
|
|
85
|
+
**前置条件:** Node.js 20+、Claude Code CLI
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install -g vibe-pomo
|
|
89
|
+
pomodoro install
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`pomodoro install` 会在 `~/.claude/settings.json` 中注册三个 Claude Code 钩子,并安装 `/pomodoro`、`/pomodoro-stats` 和 `/pomodoro-stop` 斜杠命令:
|
|
93
|
+
|
|
94
|
+
- **PreToolUse** — 在会话期间自动批准所有工具调用
|
|
95
|
+
- **Notification** — 静默排队通知,直到计时结束
|
|
96
|
+
- **Stop** — 在会话结束前让代理保持等待状态
|
|
97
|
+
|
|
98
|
+
> 这三个钩子在**没有活跃番茄钟时会立即退出且无任何效果**,不会干扰其他任何 Claude Code 配置或斜杠命令框架。
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 使用方法
|
|
103
|
+
|
|
104
|
+
### 1. 启动守护进程(一次,保持该终端打开)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pomodoro daemon
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
显示实时仪表板:带倒计时的活跃会话、各项目专注时间及最近会话历史。
|
|
111
|
+
|
|
112
|
+
### 2. 开始一个会话
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# 在 Claude Code 中(推荐)
|
|
116
|
+
/pomodoro 25m Refactor the auth module
|
|
117
|
+
|
|
118
|
+
# 在任意终端中
|
|
119
|
+
pomodoro start 25m Refactor the auth module
|
|
120
|
+
pomodoro start Refactor the auth module # 使用默认时长
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
计时器窗口打开,代理开始工作,你自由了。
|
|
124
|
+
|
|
125
|
+
### 3. 会话进行中
|
|
126
|
+
|
|
127
|
+
代理自主工作——工具调用自动批准、通知排队、决策记录。你可以在不退出的情况下通过 Claude Code 查看进度:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
/pomodoro-stats 查看时间追踪统计
|
|
131
|
+
/pomodoro-stop 中断当前会话
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 4. 计时结束后
|
|
135
|
+
|
|
136
|
+
计时器切换为超时模式,排队的通知显示出来。按 **E** 结束会话,记录你的工作内容,然后在 `.claude/pomodoro-summary.md` 和 `.claude/pomodoro-pending.md` 中查看代理的总结及待处理决策。
|
|
137
|
+
|
|
138
|
+
### 5. 查看统计数据
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pomodoro stats
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
Project Focus Time
|
|
146
|
+
──────────────────────────────────────────────────────────────────
|
|
147
|
+
my-project ████████████████░░░░░░░░░░░░░░ 4h 20m
|
|
148
|
+
side-project ██████░░░░░░░░░░░░░░░░░░░░░░░░ 1h 45m
|
|
149
|
+
|
|
150
|
+
Recent Sessions
|
|
151
|
+
──────────────────────────────────────────────────────────────────
|
|
152
|
+
4/13 my-project Refactor auth module 28m completed
|
|
153
|
+
🤖 Rewrote JWT middleware, pending: refresh token expiry strategy
|
|
154
|
+
👤 Read RFC, had planning call with team
|
|
155
|
+
|
|
156
|
+
4/13 my-project Fix payment webhook 18m completed
|
|
157
|
+
🤖 Found and fixed Stripe signature validation bug
|
|
158
|
+
👤 Coffee, cleared inbox
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 配置
|
|
164
|
+
|
|
165
|
+
首次运行时会创建 `~/.claude/pomodoro.json`:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"defaultDurationMs": 1500000,
|
|
170
|
+
"decisionStrategy": "wait",
|
|
171
|
+
"terminalEmulator": "auto",
|
|
172
|
+
"soundOnOvertime": true
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| 选项 | 取值 | 说明 |
|
|
177
|
+
|------|------|------|
|
|
178
|
+
| `defaultDurationMs` | 毫秒数 | 默认会话时长(25 分钟 = `1500000`) |
|
|
179
|
+
| `decisionStrategy` | `"wait"` / `"break"` | 代理被阻塞时的策略:静默等待直到你结束会话(默认),或立即结束 |
|
|
180
|
+
| `terminalEmulator` | `"auto"` / 名称 | 计时器窗口使用的终端。从 `$TERM_PROGRAM`、`$KITTY_WINDOW_ID` 等自动检测 |
|
|
181
|
+
| `soundOnOvertime` | 布尔值 | 计时归零时播放提示音 |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 命令
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
pomodoro daemon 启动守护进程及实时仪表板
|
|
189
|
+
pomodoro start [dur] [task] 开始一个会话
|
|
190
|
+
pomodoro stop 中断当前会话
|
|
191
|
+
pomodoro stats 显示时间追踪统计
|
|
192
|
+
pomodoro install 向 Claude Code 注册钩子
|
|
193
|
+
pomodoro stop-daemon 停止全局守护进程
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
时长格式:`25m`、`1h`、`90s`,或纯数字(按分钟处理)。
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 兼容性
|
|
201
|
+
|
|
202
|
+
vibe-pomo 的钩子仅在守护进程运行时激活。没有活跃会话时,三个钩子均立即退出——无输出、无副作用。一个全局守护进程可同时处理你所有的 Claude Code 项目。
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 许可证
|
|
207
|
+
|
|
208
|
+
MIT
|
package/bin/pomodoro.mjs
CHANGED
|
@@ -9,7 +9,8 @@ import { MSG, DECISION } from '../src/shared/protocol.mjs'
|
|
|
9
9
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
11
|
const ROOT = join(__dirname, '..')
|
|
12
|
-
const
|
|
12
|
+
const isWin = process.platform === 'win32'
|
|
13
|
+
const TSX = join(ROOT, 'node_modules', '.bin', isWin ? 'tsx.cmd' : 'tsx')
|
|
13
14
|
|
|
14
15
|
const [,, cmd, ...args] = process.argv
|
|
15
16
|
|
|
@@ -53,7 +54,7 @@ await handler(args)
|
|
|
53
54
|
async function cmdDaemon() {
|
|
54
55
|
// Launch dashboard TUI (which also starts the daemon in-process)
|
|
55
56
|
const dashboardEntry = join(ROOT, 'src', 'tui', 'dashboard', 'index.tsx')
|
|
56
|
-
const child = spawn(TSX, [dashboardEntry], { stdio: 'inherit' })
|
|
57
|
+
const child = spawn(TSX, [dashboardEntry], { stdio: 'inherit', shell: isWin })
|
|
57
58
|
child.on('exit', (code) => process.exit(code ?? 0))
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -192,6 +193,10 @@ async function launchTerminal(tsx, entryFile, sessionId, termPref) {
|
|
|
192
193
|
|
|
193
194
|
function detectTerminals() {
|
|
194
195
|
const list = []
|
|
196
|
+
if (isWin) {
|
|
197
|
+
list.push('wt', 'cmd')
|
|
198
|
+
return list
|
|
199
|
+
}
|
|
195
200
|
if (process.env.KITTY_WINDOW_ID) list.push('kitty')
|
|
196
201
|
if (process.env.TERM_PROGRAM === 'WezTerm') list.push('wezterm')
|
|
197
202
|
list.push('gnome-terminal', 'xfce4-terminal', 'konsole', 'xterm', 'alacritty', 'wezterm', 'kitty')
|
|
@@ -199,16 +204,22 @@ function detectTerminals() {
|
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
function buildCmd(term, tsx, entryFile, sessionId) {
|
|
202
|
-
|
|
207
|
+
// Quote paths for Windows (handles spaces in paths like AppData\Roaming\npm\...)
|
|
208
|
+
const q = isWin ? (p) => `"${p}"` : (p) => p
|
|
209
|
+
const inner = isWin
|
|
210
|
+
? `${q(tsx)} ${q(entryFile)} ${sessionId}`
|
|
211
|
+
: `${tsx} ${entryFile} ${sessionId}`
|
|
203
212
|
switch (term) {
|
|
204
|
-
case '
|
|
205
|
-
case '
|
|
206
|
-
case '
|
|
207
|
-
case '
|
|
208
|
-
case '
|
|
209
|
-
case '
|
|
210
|
-
case '
|
|
211
|
-
|
|
213
|
+
case 'wt': return ['wt', 'new-tab', '--', 'cmd.exe', '/k', inner]
|
|
214
|
+
case 'cmd': return ['cmd.exe', '/c', `start cmd.exe /k "${inner}"`]
|
|
215
|
+
case 'gnome-terminal': return ['gnome-terminal', '--', 'bash', '-c', inner]
|
|
216
|
+
case 'xfce4-terminal': return ['xfce4-terminal', '-e', inner]
|
|
217
|
+
case 'konsole': return ['konsole', '-e', inner]
|
|
218
|
+
case 'xterm': return ['xterm', '-e', inner]
|
|
219
|
+
case 'alacritty': return ['alacritty', '-e', 'bash', '-c', inner]
|
|
220
|
+
case 'wezterm': return ['wezterm', 'start', '--', 'bash', '-c', inner]
|
|
221
|
+
case 'kitty': return ['kitty', 'bash', '-c', inner]
|
|
222
|
+
default: return null
|
|
212
223
|
}
|
|
213
224
|
}
|
|
214
225
|
|
|
@@ -218,7 +229,7 @@ function trySpawn(term, tsx, entryFile, sessionId) {
|
|
|
218
229
|
return new Promise((resolve) => {
|
|
219
230
|
let done = false
|
|
220
231
|
try {
|
|
221
|
-
const child = spawn(cmd[0], cmd.slice(1), { detached: true, stdio: 'ignore' })
|
|
232
|
+
const child = spawn(cmd[0], cmd.slice(1), { detached: true, stdio: 'ignore', shell: isWin })
|
|
222
233
|
child.on('error', () => { if (!done) { done = true; resolve(false) } })
|
|
223
234
|
child.on('spawn', () => { if (!done) { done = true; child.unref(); resolve(true) } })
|
|
224
235
|
setTimeout(() => { if (!done) { done = true; child.unref(); resolve(true) } }, 300)
|
package/install.mjs
CHANGED
|
@@ -15,6 +15,8 @@ const SETTINGS_PATH = join(homedir(), '.claude', 'settings.json')
|
|
|
15
15
|
|
|
16
16
|
export function runInstall() {
|
|
17
17
|
const nodeExec = process.execPath
|
|
18
|
+
// Quote paths containing spaces (common on Windows)
|
|
19
|
+
const q = (p) => p.includes(' ') ? `"${p}"` : p
|
|
18
20
|
|
|
19
21
|
const hookDefs = {
|
|
20
22
|
PreToolUse: [
|
|
@@ -22,7 +24,7 @@ export function runInstall() {
|
|
|
22
24
|
matcher: '.*',
|
|
23
25
|
hooks: [{
|
|
24
26
|
type: 'command',
|
|
25
|
-
command: `${nodeExec} ${join(HOOKS_DIR, 'preToolUse.mjs')}`,
|
|
27
|
+
command: `${q(nodeExec)} ${q(join(HOOKS_DIR, 'preToolUse.mjs'))}`,
|
|
26
28
|
}],
|
|
27
29
|
},
|
|
28
30
|
],
|
|
@@ -30,7 +32,7 @@ export function runInstall() {
|
|
|
30
32
|
{
|
|
31
33
|
hooks: [{
|
|
32
34
|
type: 'command',
|
|
33
|
-
command: `${nodeExec} ${join(HOOKS_DIR, 'notification.mjs')}`,
|
|
35
|
+
command: `${q(nodeExec)} ${q(join(HOOKS_DIR, 'notification.mjs'))}`,
|
|
34
36
|
}],
|
|
35
37
|
},
|
|
36
38
|
],
|
|
@@ -38,7 +40,7 @@ export function runInstall() {
|
|
|
38
40
|
{
|
|
39
41
|
hooks: [{
|
|
40
42
|
type: 'command',
|
|
41
|
-
command: `${nodeExec} ${join(HOOKS_DIR, 'stop.mjs')}`,
|
|
43
|
+
command: `${q(nodeExec)} ${q(join(HOOKS_DIR, 'stop.mjs'))}`,
|
|
42
44
|
}],
|
|
43
45
|
},
|
|
44
46
|
],
|
|
@@ -107,6 +109,7 @@ function installSlashCommand() {
|
|
|
107
109
|
function writePomodoroCommand(dest) {
|
|
108
110
|
const pomodoroPath = join(__dirname, 'bin', 'pomodoro.mjs')
|
|
109
111
|
const nodeExec = process.execPath
|
|
112
|
+
const q = (p) => p.includes(' ') ? `"${p}"` : p
|
|
110
113
|
writeFileSync(dest, `---
|
|
111
114
|
description: Start a Pomodoro focus session — agent works autonomously until timer ends
|
|
112
115
|
argument-hint: "[duration e.g. 25m] [task description]"
|
|
@@ -115,7 +118,7 @@ argument-hint: "[duration e.g. 25m] [task description]"
|
|
|
115
118
|
## Step 1: Start the Pomodoro timer
|
|
116
119
|
|
|
117
120
|
\`\`\`bash
|
|
118
|
-
${nodeExec} ${pomodoroPath} start $ARGUMENTS
|
|
121
|
+
${q(nodeExec)} ${q(pomodoroPath)} start $ARGUMENTS
|
|
119
122
|
\`\`\`
|
|
120
123
|
|
|
121
124
|
## Step 2: Work autonomously during the focus session
|
|
@@ -135,6 +138,7 @@ The user has started a Pomodoro focus session and **will not be checking the scr
|
|
|
135
138
|
function writeStatsCommand(dest) {
|
|
136
139
|
const pomodoroPath = join(__dirname, 'bin', 'pomodoro.mjs')
|
|
137
140
|
const nodeExec = process.execPath
|
|
141
|
+
const q = (p) => p.includes(' ') ? `"${p}"` : p
|
|
138
142
|
writeFileSync(dest, `---
|
|
139
143
|
description: Show Pomodoro time tracking statistics
|
|
140
144
|
---
|
|
@@ -142,7 +146,7 @@ description: Show Pomodoro time tracking statistics
|
|
|
142
146
|
Run the following command and display the output to the user:
|
|
143
147
|
|
|
144
148
|
\`\`\`bash
|
|
145
|
-
${nodeExec} ${pomodoroPath} stats
|
|
149
|
+
${q(nodeExec)} ${q(pomodoroPath)} stats
|
|
146
150
|
\`\`\`
|
|
147
151
|
`, 'utf8')
|
|
148
152
|
}
|
|
@@ -150,6 +154,7 @@ ${nodeExec} ${pomodoroPath} stats
|
|
|
150
154
|
function writeStopCommand(dest) {
|
|
151
155
|
const pomodoroPath = join(__dirname, 'bin', 'pomodoro.mjs')
|
|
152
156
|
const nodeExec = process.execPath
|
|
157
|
+
const q = (p) => p.includes(' ') ? `"${p}"` : p
|
|
153
158
|
writeFileSync(dest, `---
|
|
154
159
|
description: Break (stop) the current Pomodoro session
|
|
155
160
|
---
|
|
@@ -157,7 +162,7 @@ description: Break (stop) the current Pomodoro session
|
|
|
157
162
|
Run the following command to break the active Pomodoro session:
|
|
158
163
|
|
|
159
164
|
\`\`\`bash
|
|
160
|
-
${nodeExec} ${pomodoroPath} stop
|
|
165
|
+
${q(nodeExec)} ${q(pomodoroPath)} stop
|
|
161
166
|
\`\`\`
|
|
162
167
|
|
|
163
168
|
Then confirm to the user that the session has been stopped.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-pomo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "You and your agent, both in flow. A Pomodoro timer for Claude Code that keeps agents working autonomously while you stay deep in focus — uninterrupted.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/daemon/ipc.mjs
CHANGED
|
@@ -20,8 +20,8 @@ export class IpcServer {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async listen(socketPath) {
|
|
23
|
-
// Clean up stale socket file
|
|
24
|
-
if (existsSync(socketPath)) {
|
|
23
|
+
// Clean up stale socket file (Unix only — Windows named pipes don't need this)
|
|
24
|
+
if (process.platform !== 'win32' && existsSync(socketPath)) {
|
|
25
25
|
unlinkSync(socketPath)
|
|
26
26
|
}
|
|
27
27
|
|
package/src/shared/lockfile.mjs
CHANGED
|
@@ -5,7 +5,10 @@ import { join } from 'node:path'
|
|
|
5
5
|
|
|
6
6
|
const CLAUDE_DIR = join(homedir(), '.claude')
|
|
7
7
|
const LOCK_PATH = join(CLAUDE_DIR, 'pomodoro.lock')
|
|
8
|
-
|
|
8
|
+
// Windows requires named pipe paths; Unix uses a socket file
|
|
9
|
+
const SOCKET_PATH = process.platform === 'win32'
|
|
10
|
+
? '\\\\.\\pipe\\vibe-pomo'
|
|
11
|
+
: join(CLAUDE_DIR, 'pomodoro.sock')
|
|
9
12
|
|
|
10
13
|
export function getSocketPath() {
|
|
11
14
|
return SOCKET_PATH
|
|
@@ -30,7 +33,8 @@ export function readLock() {
|
|
|
30
33
|
process.kill(data.pid, 0)
|
|
31
34
|
} catch {
|
|
32
35
|
try { unlinkSync(LOCK_PATH) } catch {}
|
|
33
|
-
|
|
36
|
+
// Named pipes on Windows are kernel-managed; only unlink on Unix
|
|
37
|
+
if (process.platform !== 'win32') try { unlinkSync(SOCKET_PATH) } catch {}
|
|
34
38
|
return null
|
|
35
39
|
}
|
|
36
40
|
return data
|
|
@@ -42,7 +46,8 @@ export function writeLock(data) {
|
|
|
42
46
|
|
|
43
47
|
export function removeLock() {
|
|
44
48
|
try { unlinkSync(LOCK_PATH) } catch {}
|
|
45
|
-
|
|
49
|
+
// Named pipes on Windows are kernel-managed; only unlink on Unix
|
|
50
|
+
if (process.platform !== 'win32') try { unlinkSync(SOCKET_PATH) } catch {}
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/** Stable short ID for a project directory */
|