@knpkv/jira-clockify 1.1.5 → 1.3.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/README.md +9 -2
- package/dist/package.json +10 -9
- package/dist/src/cli/fetchTicket.js +2 -1
- package/dist/src/cli/fetchTicket.js.map +1 -1
- package/dist/src/cli/fuzzySelect.js.map +1 -1
- package/dist/src/cli/layers.js +18 -0
- package/dist/src/cli/layers.js.map +1 -1
- package/dist/src/cli/setup.js +1 -1
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/timer/edit.js +8 -8
- package/dist/src/cli/timer/edit.js.map +1 -1
- package/dist/src/cli/timer/start.js +2 -2
- package/dist/src/cli/timer/start.js.map +1 -1
- package/dist/src/cli/timer/status.js +117 -66
- package/dist/src/cli/timer/status.js.map +1 -1
- package/dist/src/cli/timer/stop.js +1 -1
- package/dist/src/cli/timer/stop.js.map +1 -1
- package/dist/src/services/ClockifyAuth.js.map +1 -1
- package/dist/src/services/ConfigService.js +11 -15
- package/dist/src/services/ConfigService.js.map +1 -1
- package/dist/src/services/HomeDirectory.js.map +1 -1
- package/dist/src/services/ReconcileService.js +6 -6
- package/dist/src/services/ReconcileService.js.map +1 -1
- package/dist/src/services/StateWriter.js +8 -8
- package/dist/src/services/StateWriter.js.map +1 -1
- package/dist/src/services/TicketService.js +13 -10
- package/dist/src/services/TicketService.js.map +1 -1
- package/dist/src/services/TimerService.js +20 -22
- package/dist/src/services/TimerService.js.map +1 -1
- package/dist/src/tui/App.js +1 -1
- package/dist/src/tui/App.js.map +1 -1
- package/dist/src/tui/components/BigTimer.js +0 -1
- package/dist/src/tui/components/BigTimer.js.map +1 -1
- package/dist/src/tui/components/PopupInput.js +2 -1
- package/dist/src/tui/components/PopupInput.js.map +1 -1
- package/dist/src/tui/components/TicketRow.js +1 -1
- package/dist/src/tui/components/TicketRow.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/nvim/lua/jcf/state.lua +328 -12
- package/package.json +14 -13
package/nvim/lua/jcf/state.lua
CHANGED
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
local M = {}
|
|
2
2
|
local uv = vim.loop or vim.uv
|
|
3
3
|
local cached = { active = false }
|
|
4
|
-
local
|
|
4
|
+
local last_key = nil
|
|
5
|
+
local last_path = nil
|
|
5
6
|
local poll_timer = nil
|
|
7
|
+
local poll_job = nil
|
|
8
|
+
-- Config captured by `start_poll`, so the command and its lock/stamp paths stay
|
|
9
|
+
-- stable for the lifetime of the timer.
|
|
10
|
+
local poll_config = nil
|
|
11
|
+
-- The interval `start_poll` was given. Needed away from that function because it
|
|
12
|
+
-- is also the machine's poll budget, not just this editor's tick spacing.
|
|
13
|
+
local poll_interval_ms = nil
|
|
14
|
+
local poll_once
|
|
15
|
+
local arm_poll
|
|
16
|
+
|
|
17
|
+
-- The cache key has to change on every write. `mtime.sec` alone does not: a
|
|
18
|
+
-- write landing in the same filesystem second as the previous read is
|
|
19
|
+
-- invisible, so starting a timer right after a statusline redraw can leave
|
|
20
|
+
-- `cached.active` false indefinitely. That used to be a cosmetic lag, but the
|
|
21
|
+
-- poll below is now gated on `.active` — a stale "inactive" reading suppresses
|
|
22
|
+
-- the very poll that would refresh the file, and nothing recovers it. Include
|
|
23
|
+
-- sub-second mtime and size so same-second writes are still observed. Aliasing
|
|
24
|
+
-- would need whole-second mtimes *and* an identical file size across the
|
|
25
|
+
-- inactive->active transition, which the two JSON payloads never have. If it
|
|
26
|
+
-- somehow happened, nothing periodic would clear it — no jcf process rewrites
|
|
27
|
+
-- this file on a schedule — so recovery would wait for the user's next jcf
|
|
28
|
+
-- command.
|
|
29
|
+
local function stat_key(stat)
|
|
30
|
+
return string.format("%d.%09d:%d", stat.mtime.sec, stat.mtime.nsec or 0, stat.size or 0)
|
|
31
|
+
end
|
|
6
32
|
|
|
7
33
|
function M.read(state_path)
|
|
8
34
|
local path = state_path or vim.fn.expand("~/.jcf/state.json")
|
|
9
35
|
local stat = uv.fs_stat(path)
|
|
10
36
|
if not stat then
|
|
37
|
+
-- The file is gone, so whatever we cached describes a world that no longer
|
|
38
|
+
-- exists. Handing back a stale `active` reading would keep the poll below
|
|
39
|
+
-- spawning `jcf timer status` every tick against a timer that isn't there.
|
|
40
|
+
cached = { active = false }
|
|
41
|
+
last_key = nil
|
|
42
|
+
last_path = nil
|
|
11
43
|
return cached
|
|
12
44
|
end
|
|
13
|
-
|
|
45
|
+
local key = stat_key(stat)
|
|
46
|
+
if path == last_path and key == last_key then
|
|
14
47
|
return cached
|
|
15
48
|
end
|
|
16
|
-
|
|
49
|
+
last_path = path
|
|
50
|
+
last_key = key
|
|
17
51
|
local f = io.open(path, "r")
|
|
18
52
|
if f then
|
|
19
53
|
local content = f:read("*a")
|
|
@@ -27,22 +61,291 @@ function M.read(state_path)
|
|
|
27
61
|
end
|
|
28
62
|
|
|
29
63
|
-- Periodically run `jcf timer status` to sync state file with Clockify
|
|
30
|
-
-- This detects externally stopped timers
|
|
64
|
+
-- This detects externally stopped timers.
|
|
65
|
+
--
|
|
66
|
+
-- The poll must never stack. `jcf timer status` does network I/O and can hang
|
|
67
|
+
-- indefinitely, and one nvim runs per project, so every leaked process is
|
|
68
|
+
-- multiplied by the number of open editors. Hence: at most one job in flight,
|
|
69
|
+
-- owned by nvim (never detached, so VimLeave and jobstop can actually kill it),
|
|
70
|
+
-- under a watchdog. When no timer is running locally there is nothing to
|
|
71
|
+
-- reconcile, so the spawn is skipped entirely. That bounds one editor to one
|
|
72
|
+
-- poll; the lock below bounds the whole machine to one, which is the part that
|
|
73
|
+
-- actually decides the cost when many editors are open.
|
|
74
|
+
-- Backstop only. `jcf timer status` bounds every network call it can make, but
|
|
75
|
+
-- not all at the same value: an expired Jira token costs up to
|
|
76
|
+
-- `REFRESH_TIMEOUT` (30s, in @knpkv/jira-cli's JiraAuth) before any command
|
|
77
|
+
-- body runs, then the four Clockify calls cost up to `API_TIMEOUT` (10s each,
|
|
78
|
+
-- in src/cli/timer/status.ts). So ~70s of bounded work plus process startup.
|
|
79
|
+
-- The watchdog sits well above that — set near it, nvim would be the thing
|
|
80
|
+
-- killing a merely slow but healthy poll, and a kill landing inside the state
|
|
81
|
+
-- file's write-then-rename leaves a stray `state.json.tmp`. Recompute this
|
|
82
|
+
-- from those two constants by name if either changes.
|
|
83
|
+
local POLL_TIMEOUT_MS = 120000
|
|
84
|
+
-- How long our own SIGTERM gets before we escalate. We signal the pid directly
|
|
85
|
+
-- rather than via `jobstop`, because `jobstop` starts nvim's own kill timer and
|
|
86
|
+
-- escalates to SIGKILL after about two seconds — far too short for an in-flight
|
|
87
|
+
-- OAuth rotation, and not a window we can lengthen.
|
|
88
|
+
--
|
|
89
|
+
-- Must exceed `REFRESH_TIMEOUT` (30s, in @knpkv/jira-cli's JiraAuth), or we
|
|
90
|
+
-- escalate while the very rotation this grace exists for is still legitimately
|
|
91
|
+
-- running.
|
|
92
|
+
--
|
|
93
|
+
-- This is a courtesy, not a guarantee. On `VimLeave` nvim exits immediately and
|
|
94
|
+
-- hard-kills surviving jobs, so no grace elapses at all there — a cost of
|
|
95
|
+
-- owning the job rather than detaching it, which is what makes it reapable at
|
|
96
|
+
-- all. The CLI narrows the damage: it refuses to discard a stored token unless
|
|
97
|
+
-- Atlassian explicitly said the grant was invalid, so an interrupted refresh
|
|
98
|
+
-- normally costs a retry rather than the session. That is narrowing, not
|
|
99
|
+
-- immunity — a hard kill after Atlassian has already rotated the token still
|
|
100
|
+
-- loses the replacement, and the next refresh then legitimately reports
|
|
101
|
+
-- `invalid_grant`. No client-side design can close that window.
|
|
102
|
+
local POLL_STOP_GRACE_MS = 35000
|
|
103
|
+
|
|
104
|
+
-- ---------------------------------------------------------------------------
|
|
105
|
+
-- Cross-editor lock: one `jcf timer status` per machine, not per editor.
|
|
106
|
+
-- ---------------------------------------------------------------------------
|
|
107
|
+
--
|
|
108
|
+
-- The single-flight guard above is process-local. It bounds one nvim to one
|
|
109
|
+
-- poll and says nothing about the others, so with an editor open per project it
|
|
110
|
+
-- does not bound anything that matters: 19 editors on a 30s timer is 19 cold
|
|
111
|
+
-- Node starts every 30s, each making the same four Clockify calls to reconcile
|
|
112
|
+
-- the same single global timer.
|
|
113
|
+
--
|
|
114
|
+
-- The work is inherently shared — one `~/.jcf/state.json`, one running timer —
|
|
115
|
+
-- so exactly one editor should do it and the rest should read the file it
|
|
116
|
+
-- refreshes, which `M.read` already does off an mtime cache. Coordination uses
|
|
117
|
+
-- util-linux `flock`: every editor starts a non-blocking contender, the kernel
|
|
118
|
+
-- lets exactly one exec `jcf`, and every loser exits without starting Node.
|
|
119
|
+
-- `--no-fork` leaves the lock attached to the `jcf` process itself. If nvim is
|
|
120
|
+
-- SIGKILLed, its child therefore keeps the lock until the poll really exits.
|
|
121
|
+
--
|
|
122
|
+
-- The lock is half of it. It bounds how many polls run *at once*, and because
|
|
123
|
+
-- it is released as soon as the CLI exits, it says nothing about how often they
|
|
124
|
+
-- run — 19 editors would still spawn ~19 times per interval, just never two at
|
|
125
|
+
-- the same moment. The poll stamp below is what bounds the rate, and the two
|
|
126
|
+
-- together are what make the "per machine" claim above true.
|
|
127
|
+
--
|
|
128
|
+
-- No directory is created for it. The lock sits beside the CLI's fixed state
|
|
129
|
+
-- authority. Neovim's configurable `state_path` only changes which file the UI
|
|
130
|
+
-- reads; `jcf timer status` still mutates `~/.jcf/state.json`, so every editor
|
|
131
|
+
-- must coordinate on that one path regardless of its display configuration.
|
|
132
|
+
local function cli_state_path()
|
|
133
|
+
return vim.fn.expand("~/.jcf/state.json")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
local function lock_path()
|
|
137
|
+
return vim.fn.fnamemodify(cli_state_path(), ":h") .. "/poll.lock"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
-- ---------------------------------------------------------------------------
|
|
141
|
+
-- Poll stamp: the lock bounds overlap, this bounds rate.
|
|
142
|
+
-- ---------------------------------------------------------------------------
|
|
143
|
+
--
|
|
144
|
+
-- The lock is held only while `jcf timer status` runs — a second or two — and
|
|
145
|
+
-- released the moment it exits. That makes two polls never overlap, which is not
|
|
146
|
+
-- the same as making them rare: 19 editors on a 30s timer tick every ~1.6s
|
|
147
|
+
-- between them, and by then the lock is free again, so nearly every tick would
|
|
148
|
+
-- still spawn. The lock alone therefore delivers ~19 polls per interval, the
|
|
149
|
+
-- number it exists to avoid.
|
|
150
|
+
--
|
|
151
|
+
-- What actually bounds the machine is a record that survives the release. Each
|
|
152
|
+
-- finished attempt writes a stamp file before releasing the lock, and a tick
|
|
153
|
+
-- that finds the stamp younger than one interval skips. Failed attempts count:
|
|
154
|
+
-- otherwise every de-phased editor would retry the same persistent failure in
|
|
155
|
+
-- turn. Readers keep the last state file until the next attempt. That is the
|
|
156
|
+
-- whole rate limit, and it is what lets the editors stay de-phased without
|
|
157
|
+
-- multiplying the work.
|
|
158
|
+
--
|
|
159
|
+
-- The CLI owns the write so no successor can acquire the lock between process
|
|
160
|
+
-- exit and the stamp becoming visible.
|
|
161
|
+
local function stamp_path()
|
|
162
|
+
return vim.fn.fnamemodify(cli_state_path(), ":h") .. "/poll.stamp"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
local function wall_clock_ms()
|
|
166
|
+
local sec, usec = uv.gettimeofday()
|
|
167
|
+
return (sec * 1000) + math.floor(usec / 1000)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
-- A watchdog escalation bypasses the CLI's Effect finalizers. Write the failed
|
|
171
|
+
-- attempt synchronously while the child still owns `poll.lock`, so another
|
|
172
|
+
-- editor cannot acquire the lock between SIGKILL and the shared rate bound.
|
|
173
|
+
local function stamp_failed_attempt()
|
|
174
|
+
local fd = uv.fs_open(stamp_path(), "w", 384) -- 0600
|
|
175
|
+
if not fd then
|
|
176
|
+
return
|
|
177
|
+
end
|
|
178
|
+
uv.fs_write(fd, tostring(wall_clock_ms()), -1)
|
|
179
|
+
uv.fs_close(fd)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
-- Whether some editor on this machine already reconciled within the interval.
|
|
183
|
+
-- Read from the stamp's own mtime, so a torn or truncated write still carries a
|
|
184
|
+
-- usable time and no parsing can fail here.
|
|
185
|
+
local function polled_recently()
|
|
186
|
+
if not poll_interval_ms then
|
|
187
|
+
return false
|
|
188
|
+
end
|
|
189
|
+
local stat = uv.fs_stat(stamp_path())
|
|
190
|
+
if not stat then
|
|
191
|
+
return false
|
|
192
|
+
end
|
|
193
|
+
local stamped_at_ms = (stat.mtime.sec * 1000) + math.floor((stat.mtime.nsec or 0) / 1000000)
|
|
194
|
+
local age_ms = wall_clock_ms() - stamped_at_ms
|
|
195
|
+
-- A stamp dated in the future is a clock that moved backwards, not a poll that
|
|
196
|
+
-- has not happened yet; treating it as recent would wedge polling until the
|
|
197
|
+
-- clock caught up.
|
|
198
|
+
if age_ms < 0 then
|
|
199
|
+
return false
|
|
200
|
+
end
|
|
201
|
+
return age_ms < poll_interval_ms
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
local function own_pid()
|
|
205
|
+
if uv.os_getpid then
|
|
206
|
+
return uv.os_getpid()
|
|
207
|
+
end
|
|
208
|
+
return vim.fn.getpid()
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
-- One-shot scheduling keeps this editor's cadence anchored to completion, not
|
|
212
|
+
-- start. A repeating timer would tick one interval after start, see the fresh
|
|
213
|
+
-- completion stamp, skip, and wait a second whole interval before trying again.
|
|
214
|
+
arm_poll = function(delay_ms)
|
|
215
|
+
if not poll_timer or not poll_config then
|
|
216
|
+
return
|
|
217
|
+
end
|
|
218
|
+
poll_timer:stop()
|
|
219
|
+
poll_timer:start(delay_ms, 0, vim.schedule_wrap(function()
|
|
220
|
+
poll_once(poll_config)
|
|
221
|
+
end))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
-- Ask `job` to stop, then escalate if it does not. Signals the pid directly
|
|
225
|
+
-- rather than calling `jobstop`, which starts nvim's own ~2s kill timer and
|
|
226
|
+
-- would SIGKILL the CLI long before an in-flight OAuth rotation could finish.
|
|
227
|
+
--
|
|
228
|
+
-- `flock --no-fork` execs the CLI, so the pid we signal is also the process
|
|
229
|
+
-- holding the kernel lock. The lock cannot be released before that process is
|
|
230
|
+
-- dead.
|
|
231
|
+
local function terminate(job)
|
|
232
|
+
local signalled, pid = pcall(vim.fn.jobpid, job)
|
|
233
|
+
if signalled and pid > 0 then
|
|
234
|
+
uv.kill(pid, "sigterm")
|
|
235
|
+
else
|
|
236
|
+
stamp_failed_attempt()
|
|
237
|
+
vim.fn.jobstop(job) -- no pid to signal; fall back to nvim's own teardown
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
vim.defer_fn(function()
|
|
241
|
+
if poll_job ~= job then
|
|
242
|
+
return
|
|
243
|
+
end
|
|
244
|
+
if vim.fn.jobwait({ job }, 0)[1] ~= -1 then
|
|
245
|
+
poll_job = nil -- already exited without `on_exit` reaching us
|
|
246
|
+
arm_poll(poll_interval_ms)
|
|
247
|
+
return
|
|
248
|
+
end
|
|
249
|
+
-- SIGTERM was not enough. Escalate, and let `on_exit` do the releasing.
|
|
250
|
+
local ok, live_pid = pcall(vim.fn.jobpid, job)
|
|
251
|
+
if ok and live_pid > 0 then
|
|
252
|
+
stamp_failed_attempt()
|
|
253
|
+
uv.kill(live_pid, "sigkill")
|
|
254
|
+
return
|
|
255
|
+
end
|
|
256
|
+
-- No pid to escalate against, so nothing further will make this job report.
|
|
257
|
+
-- `jobstop` once more, then release on the next proof rather than wedging
|
|
258
|
+
-- the poll for the rest of the session.
|
|
259
|
+
stamp_failed_attempt()
|
|
260
|
+
vim.fn.jobstop(job)
|
|
261
|
+
vim.defer_fn(function()
|
|
262
|
+
if poll_job == job and vim.fn.jobwait({ job }, 0)[1] ~= -1 then
|
|
263
|
+
poll_job = nil
|
|
264
|
+
arm_poll(poll_interval_ms)
|
|
265
|
+
end
|
|
266
|
+
end, POLL_STOP_GRACE_MS)
|
|
267
|
+
end, POLL_STOP_GRACE_MS)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
poll_once = function(config)
|
|
271
|
+
if poll_job then
|
|
272
|
+
arm_poll(poll_interval_ms)
|
|
273
|
+
return -- previous poll still in flight; do not stack
|
|
274
|
+
end
|
|
275
|
+
if not M.read(cli_state_path()).active then
|
|
276
|
+
arm_poll(poll_interval_ms)
|
|
277
|
+
return -- no local timer, nothing to reconcile
|
|
278
|
+
end
|
|
279
|
+
if polled_recently() then
|
|
280
|
+
arm_poll(poll_interval_ms)
|
|
281
|
+
return -- someone already reconciled this interval; read their result instead
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
-- `job` is declared first so on_exit closes over it, not a global.
|
|
285
|
+
local job
|
|
286
|
+
job = vim.fn.jobstart({
|
|
287
|
+
"flock",
|
|
288
|
+
"--no-fork",
|
|
289
|
+
"--nonblock",
|
|
290
|
+
"--conflict-exit-code",
|
|
291
|
+
"75",
|
|
292
|
+
lock_path(),
|
|
293
|
+
config.binary or "jcf",
|
|
294
|
+
"timer",
|
|
295
|
+
"status",
|
|
296
|
+
"--nvim-poll-stamp",
|
|
297
|
+
stamp_path(),
|
|
298
|
+
"--nvim-poll-interval-ms",
|
|
299
|
+
tostring(poll_interval_ms),
|
|
300
|
+
}, {
|
|
301
|
+
on_stdout = function() end,
|
|
302
|
+
on_stderr = function() end,
|
|
303
|
+
on_exit = function()
|
|
304
|
+
if poll_job == job then
|
|
305
|
+
poll_job = nil
|
|
306
|
+
arm_poll(poll_interval_ms)
|
|
307
|
+
end
|
|
308
|
+
end,
|
|
309
|
+
})
|
|
310
|
+
if job <= 0 then
|
|
311
|
+
arm_poll(poll_interval_ms)
|
|
312
|
+
return -- failed to spawn (`flock` missing); try again next tick
|
|
313
|
+
end
|
|
314
|
+
poll_job = job
|
|
315
|
+
|
|
316
|
+
vim.defer_fn(function()
|
|
317
|
+
if poll_job ~= job then
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
terminate(job)
|
|
321
|
+
end, POLL_TIMEOUT_MS)
|
|
322
|
+
end
|
|
323
|
+
|
|
31
324
|
function M.start_poll(config, interval_ms)
|
|
32
325
|
if poll_timer then
|
|
33
326
|
return
|
|
34
327
|
end
|
|
35
328
|
interval_ms = interval_ms or 30000 -- 30s default
|
|
329
|
+
if type(interval_ms) ~= "number" or interval_ms <= 0 or interval_ms % 1 ~= 0 then
|
|
330
|
+
return -- non-positive and non-integral values disable polling
|
|
331
|
+
end
|
|
332
|
+
poll_config = config
|
|
333
|
+
poll_interval_ms = interval_ms
|
|
334
|
+
|
|
335
|
+
-- Spread the first tick across a whole interval, keyed off the pid, so editors
|
|
336
|
+
-- opened in a batch (a session restore, a fleet of worktrees) do not line up on
|
|
337
|
+
-- the same millisecond forever after. Derived from the pid rather than
|
|
338
|
+
-- `math.random`, which is unseeded per process and would hand every editor the
|
|
339
|
+
-- same offset.
|
|
340
|
+
--
|
|
341
|
+
-- De-phasing is only safe because the stamp bounds the rate. Spreading the
|
|
342
|
+
-- ticks removes the collisions the lock would otherwise resolve, so with the
|
|
343
|
+
-- lock alone this would make things worse, not better: every tick would find
|
|
344
|
+
-- a free lock and spawn.
|
|
345
|
+
local first_ms = own_pid() % interval_ms
|
|
36
346
|
|
|
37
347
|
poll_timer = uv.new_timer()
|
|
38
|
-
|
|
39
|
-
-- jcf timer status updates ~/.jcf/state.json and detects external stops
|
|
40
|
-
vim.fn.jobstart({ config.binary or "jcf", "timer", "status" }, {
|
|
41
|
-
on_stdout = function() end,
|
|
42
|
-
on_stderr = function() end,
|
|
43
|
-
detach = true,
|
|
44
|
-
})
|
|
45
|
-
end))
|
|
348
|
+
arm_poll(first_ms)
|
|
46
349
|
end
|
|
47
350
|
|
|
48
351
|
function M.stop_poll()
|
|
@@ -51,6 +354,19 @@ function M.stop_poll()
|
|
|
51
354
|
poll_timer:close()
|
|
52
355
|
poll_timer = nil
|
|
53
356
|
end
|
|
357
|
+
if not poll_job then
|
|
358
|
+
return
|
|
359
|
+
end
|
|
360
|
+
if poll_job then
|
|
361
|
+
-- Exactly the watchdog's sequence, for the same reason: `jobstop` here
|
|
362
|
+
-- would hand the CLI nvim's ~2s kill timer and could SIGKILL it between the
|
|
363
|
+
-- OAuth grant and the rotated token being persisted. Teardown and
|
|
364
|
+
-- reconfiguration deserve the same grace as a timeout. (On `VimLeave` nvim
|
|
365
|
+
-- exits before any of it elapses — the CLI's own retry-not-delete rule is
|
|
366
|
+
-- what covers that case.)
|
|
367
|
+
stamp_failed_attempt()
|
|
368
|
+
terminate(poll_job)
|
|
369
|
+
end
|
|
54
370
|
end
|
|
55
371
|
|
|
56
372
|
return M
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/jira-clockify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "TUI for Jira-Clockify time tracking, attachable to neovim",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -14,26 +14,27 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
16
|
"nvim",
|
|
17
|
+
"!nvim/test",
|
|
17
18
|
"skills"
|
|
18
19
|
],
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@effect/atom-react": "4.0.0-
|
|
21
|
-
"@effect/platform-node": "4.0.0-
|
|
21
|
+
"@effect/atom-react": "4.0.0-rc.109",
|
|
22
|
+
"@effect/platform-node": "4.0.0-rc.109",
|
|
22
23
|
"@opentui/core": "0.5.1",
|
|
23
24
|
"@opentui/react": "0.5.1",
|
|
24
|
-
"effect": "4.0.0-
|
|
25
|
-
"react": "^19.2.
|
|
26
|
-
"@knpkv/agent-skills": "^0.
|
|
27
|
-
"@knpkv/atlassian-common": "^1.
|
|
28
|
-
"@knpkv/clockify-api-client": "^1.
|
|
29
|
-
"@knpkv/jira-api-client": "^1.
|
|
30
|
-
"@knpkv/jira-cli": "^1.
|
|
25
|
+
"effect": "4.0.0-rc.109",
|
|
26
|
+
"react": "^19.2.8",
|
|
27
|
+
"@knpkv/agent-skills": "^0.3.1",
|
|
28
|
+
"@knpkv/atlassian-common": "^1.4.1",
|
|
29
|
+
"@knpkv/clockify-api-client": "^1.1.1",
|
|
30
|
+
"@knpkv/jira-api-client": "^1.1.1",
|
|
31
|
+
"@knpkv/jira-cli": "^1.3.1"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@effect/vitest": "4.0.0-
|
|
34
|
+
"@effect/vitest": "4.0.0-rc.109",
|
|
34
35
|
"@types/node": "latest",
|
|
35
|
-
"@types/react": "^19.2.
|
|
36
|
-
"tsx": "^4.23.
|
|
36
|
+
"@types/react": "^19.2.18",
|
|
37
|
+
"tsx": "^4.23.12",
|
|
37
38
|
"vitest": "^4.1.10"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|