claude-lock 0.2.0 → 0.2.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.
@@ -0,0 +1,1007 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ SOCKET_PATH
4
+ } from "./chunk-GSDKZKKT.js";
5
+
6
+ // ../tui/dist/index.js
7
+ import React8 from "react";
8
+ import { render } from "ink";
9
+ import React7, { useState as useState8 } from "react";
10
+ import { Box as Box8, Text as Text10, useInput as useInput8, useApp } from "ink";
11
+ import { useState, useEffect, useCallback, useRef } from "react";
12
+ import { createConnection } from "net";
13
+ import { useState as useState2 } from "react";
14
+ import { Text as Text3, Box, useInput } from "ink";
15
+ import { Text } from "ink";
16
+ import { jsx } from "react/jsx-runtime";
17
+ import { Text as Text2 } from "ink";
18
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
19
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
20
+ import { useState as useState3 } from "react";
21
+ import { Text as Text4, Box as Box2, useInput as useInput2 } from "ink";
22
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
23
+ import { useEffect as useEffect2, useState as useState6 } from "react";
24
+ import { Text as Text8, Box as Box6, useInput as useInput6 } from "ink";
25
+ import { Text as Text5, Box as Box3, useInput as useInput3 } from "ink";
26
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
27
+ import { useState as useState4 } from "react";
28
+ import { Text as Text6, Box as Box4, useInput as useInput4 } from "ink";
29
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
30
+ import { useState as useState5 } from "react";
31
+ import { Text as Text7, Box as Box5, useInput as useInput5 } from "ink";
32
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
33
+ import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
34
+ import { useState as useState7 } from "react";
35
+ import { Text as Text9, Box as Box7, useInput as useInput7 } from "ink";
36
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
37
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
38
+ function sendRequest(req) {
39
+ return new Promise((resolve, reject) => {
40
+ const socket = createConnection(SOCKET_PATH);
41
+ let buffer = "";
42
+ socket.on("connect", () => socket.write(JSON.stringify(req) + "\n"));
43
+ socket.on("data", (data) => {
44
+ buffer += data.toString();
45
+ const idx = buffer.indexOf("\n");
46
+ if (idx !== -1) {
47
+ resolve(JSON.parse(buffer.slice(0, idx)));
48
+ socket.end();
49
+ }
50
+ });
51
+ socket.on("error", reject);
52
+ socket.setTimeout(5e3, () => {
53
+ socket.destroy();
54
+ reject(new Error("Timeout"));
55
+ });
56
+ });
57
+ }
58
+ function useDaemonStatus(pollMs = 2e3) {
59
+ const [lock, setLock] = useState(null);
60
+ const [config, setConfig] = useState(null);
61
+ const [todayUsage, setTodayUsage] = useState(0);
62
+ const [connected, setConnected] = useState(null);
63
+ const prev = useRef({ lock: "", config: "", todayUsage: -1, connected: null });
64
+ const refresh = useCallback(async () => {
65
+ try {
66
+ const res = await sendRequest({ type: "status" });
67
+ const lockStr = JSON.stringify(res.lock);
68
+ if (lockStr !== prev.current.lock) {
69
+ prev.current.lock = lockStr;
70
+ setLock(res.lock);
71
+ }
72
+ const configStr = JSON.stringify(res.config);
73
+ if (configStr !== prev.current.config) {
74
+ prev.current.config = configStr;
75
+ setConfig(res.config);
76
+ }
77
+ if (res.todayUsageSeconds !== prev.current.todayUsage) {
78
+ prev.current.todayUsage = res.todayUsageSeconds;
79
+ setTodayUsage(res.todayUsageSeconds);
80
+ }
81
+ if (prev.current.connected !== true) {
82
+ prev.current.connected = true;
83
+ setConnected(true);
84
+ }
85
+ } catch {
86
+ if (prev.current.connected !== false) {
87
+ prev.current.connected = false;
88
+ setConnected(false);
89
+ }
90
+ }
91
+ }, []);
92
+ useEffect(() => {
93
+ refresh();
94
+ const interval = setInterval(refresh, pollMs);
95
+ return () => clearInterval(interval);
96
+ }, [refresh, pollMs]);
97
+ return { lock, config, todayUsage, connected, refresh };
98
+ }
99
+ function useStats(period = "week") {
100
+ const [days, setDays] = useState([]);
101
+ const [refreshKey, setRefreshKey] = useState(0);
102
+ useEffect(() => {
103
+ sendRequest({ type: "stats", period }).then((res) => setDays(res.days)).catch(() => {
104
+ });
105
+ }, [period, refreshKey]);
106
+ const refresh = useCallback(() => setRefreshKey((k) => k + 1), []);
107
+ return { days, refresh };
108
+ }
109
+ function useSchedules() {
110
+ const [schedules, setSchedules] = useState([]);
111
+ const refresh = useCallback(async () => {
112
+ try {
113
+ const res = await sendRequest({ type: "schedule-list" });
114
+ setSchedules(res.schedules);
115
+ } catch {
116
+ }
117
+ }, []);
118
+ useEffect(() => {
119
+ refresh();
120
+ }, [refresh]);
121
+ return { schedules, refresh };
122
+ }
123
+ function StatusBadge({ status, hardLock }) {
124
+ switch (status) {
125
+ case "unlocked":
126
+ return /* @__PURE__ */ jsx(Text, { color: "green", bold: true, children: "\u25CF UNLOCKED" });
127
+ case "locked":
128
+ return hardLock ? /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u25CF HARD LOCKED" }) : /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u25CF LOCKED" });
129
+ case "grace":
130
+ return /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "\u25CF GRACE PERIOD" });
131
+ }
132
+ }
133
+ function formatRemaining(ms) {
134
+ if (ms <= 0) return "expired";
135
+ const totalSec = Math.floor(ms / 1e3);
136
+ const h = Math.floor(totalSec / 3600);
137
+ const m = Math.floor(totalSec % 3600 / 60);
138
+ const s = totalSec % 60;
139
+ if (h > 0) return `${h}h ${m}m ${s}s`;
140
+ if (m > 0) return `${m}m ${s}s`;
141
+ return `${s}s`;
142
+ }
143
+ function CountdownTimer({ expiresAt, label = "Expires in" }) {
144
+ const remaining = new Date(expiresAt).getTime() - Date.now();
145
+ const color = remaining < 6e4 ? "red" : remaining < 3e5 ? "yellow" : "white";
146
+ return /* @__PURE__ */ jsxs(Text2, { children: [
147
+ label,
148
+ ": ",
149
+ /* @__PURE__ */ jsx2(Text2, { color, bold: true, children: formatRemaining(remaining) })
150
+ ] });
151
+ }
152
+ function formatDuration(seconds) {
153
+ if (seconds < 60) return `${seconds}s`;
154
+ const m = Math.floor(seconds / 60);
155
+ if (m < 60) return `${m}m`;
156
+ const h = Math.floor(m / 60);
157
+ return `${h}h ${m % 60}m`;
158
+ }
159
+ var LOCK_PRESETS = [
160
+ { key: "1", label: "30m", minutes: 30 },
161
+ { key: "2", label: "1h", minutes: 60 },
162
+ { key: "3", label: "2h", minutes: 120 },
163
+ { key: "4", label: "4h", minutes: 240 }
164
+ ];
165
+ function Dashboard({ lock, todayUsage, connected, onRefresh }) {
166
+ const [mode, setMode] = useState2("normal");
167
+ const [message, setMessage] = useState2(null);
168
+ useInput((input, key) => {
169
+ if (key.escape) {
170
+ setMode("normal");
171
+ setMessage(null);
172
+ return;
173
+ }
174
+ if (mode === "locking") {
175
+ const preset = LOCK_PRESETS.find((p) => p.key === input);
176
+ if (preset) {
177
+ sendRequest({ type: "lock", durationMinutes: preset.minutes }).then(
178
+ (res) => {
179
+ const lr = res;
180
+ if (lr.ok) {
181
+ setMessage(`Locked for ${preset.label}`);
182
+ } else {
183
+ setMessage(`Error: ${lr.error}`);
184
+ }
185
+ setMode("normal");
186
+ onRefresh();
187
+ }
188
+ );
189
+ }
190
+ return;
191
+ }
192
+ if (input === "l" && lock?.status === "unlocked") {
193
+ setMode("locking");
194
+ setMessage(null);
195
+ return;
196
+ }
197
+ if (input === "r" && lock?.status === "unlocked") {
198
+ sendRequest({ type: "stats-reset" }).then(() => onRefresh()).catch(() => {
199
+ });
200
+ }
201
+ });
202
+ if (connected === null) {
203
+ return /* @__PURE__ */ jsx3(Box, { padding: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Connecting to daemon..." }) });
204
+ }
205
+ if (!connected) {
206
+ return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", padding: 1, children: [
207
+ /* @__PURE__ */ jsx3(Text3, { color: "red", bold: true, children: "Daemon not connected" }),
208
+ /* @__PURE__ */ jsx3(Text3, { children: "Run `cc-lock install` to set up the daemon." })
209
+ ] });
210
+ }
211
+ if (!lock) {
212
+ return /* @__PURE__ */ jsx3(Box, { padding: 1, children: /* @__PURE__ */ jsx3(Text3, { children: "Loading..." }) });
213
+ }
214
+ return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", padding: 1, children: [
215
+ /* @__PURE__ */ jsx3(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Dashboard" }) }),
216
+ /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, children: [
217
+ /* @__PURE__ */ jsx3(Text3, { children: "Status: " }),
218
+ /* @__PURE__ */ jsx3(StatusBadge, { status: lock.status, hardLock: lock.hardLock })
219
+ ] }),
220
+ lock.status === "locked" && lock.expiresAt && /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, flexDirection: "column", children: [
221
+ /* @__PURE__ */ jsx3(CountdownTimer, { expiresAt: lock.expiresAt }),
222
+ /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
223
+ "Expires at",
224
+ " ",
225
+ new Date(lock.expiresAt).toLocaleTimeString()
226
+ ] }),
227
+ !lock.hardLock && /* @__PURE__ */ jsxs2(Text3, { children: [
228
+ "Bypass attempts: ",
229
+ lock.bypassAttempts
230
+ ] }),
231
+ lock.hardLock && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No bypass allowed" }),
232
+ !lock.hardLock && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Run `cc-lock unlock` to bypass" })
233
+ ] }),
234
+ lock.status === "grace" && lock.graceExpiresAt && /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, flexDirection: "column", children: [
235
+ /* @__PURE__ */ jsx3(
236
+ CountdownTimer,
237
+ {
238
+ expiresAt: lock.graceExpiresAt,
239
+ label: "Grace expires in"
240
+ }
241
+ ),
242
+ /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
243
+ "Lock re-engages at",
244
+ " ",
245
+ new Date(lock.graceExpiresAt).toLocaleTimeString()
246
+ ] })
247
+ ] }),
248
+ /* @__PURE__ */ jsx3(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs2(Text3, { children: [
249
+ "Today's usage:",
250
+ " ",
251
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: formatDuration(todayUsage) })
252
+ ] }) }),
253
+ lock.status === "unlocked" && (lock.pendingResumeKeys?.length ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, flexDirection: "column", children: [
254
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Sessions to resume:" }),
255
+ lock.pendingResumeKeys.map((key) => /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
256
+ " claude --resume ",
257
+ key
258
+ ] }, key))
259
+ ] }),
260
+ message && /* @__PURE__ */ jsx3(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: message }) }),
261
+ mode === "locking" ? /* @__PURE__ */ jsxs2(Box, { borderStyle: "round", paddingX: 1, flexDirection: "column", children: [
262
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Lock for how long?" }),
263
+ /* @__PURE__ */ jsxs2(Box, { marginTop: 1, children: [
264
+ LOCK_PRESETS.map((p) => /* @__PURE__ */ jsxs2(Text3, { children: [
265
+ "[",
266
+ p.key,
267
+ "] ",
268
+ p.label,
269
+ " "
270
+ ] }, p.key)),
271
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[Esc] Cancel" })
272
+ ] })
273
+ ] }) : /* @__PURE__ */ jsx3(Box, { borderStyle: "single", paddingX: 1, children: /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
274
+ lock.status === "unlocked" ? "[L] Lock [R] Reset today " : "",
275
+ "[Q] Quit"
276
+ ] }) })
277
+ ] });
278
+ }
279
+ function formatDuration2(seconds) {
280
+ if (seconds < 60) return `${seconds}s`;
281
+ const m = Math.floor(seconds / 60);
282
+ if (m < 60) return `${m}m`;
283
+ const h = Math.floor(m / 60);
284
+ return `${h}h ${m % 60}m`;
285
+ }
286
+ var PERIODS = [
287
+ { key: "1", id: "day", label: "Today" },
288
+ { key: "2", id: "week", label: "Week" },
289
+ { key: "3", id: "month", label: "Month" }
290
+ ];
291
+ function StatsScreen() {
292
+ const [period, setPeriod] = useState3("week");
293
+ const [confirmMode, setConfirmMode] = useState3(null);
294
+ const { days, refresh } = useStats(period);
295
+ useInput2((input, key) => {
296
+ if (confirmMode !== null) {
297
+ if (input.toLowerCase() === "y") {
298
+ sendRequest({ type: "stats-reset", all: confirmMode === "all" }).then(() => refresh()).catch(() => {
299
+ });
300
+ setConfirmMode(null);
301
+ } else if (input.toLowerCase() === "n" || key.escape) {
302
+ setConfirmMode(null);
303
+ }
304
+ return;
305
+ }
306
+ const p = PERIODS.find((p2) => p2.key === input);
307
+ if (p) {
308
+ setPeriod(p.id);
309
+ return;
310
+ }
311
+ if (input === "r") setConfirmMode("today");
312
+ else if (input === "x") setConfirmMode("all");
313
+ });
314
+ const totalSeconds = days.reduce((s, d) => s + d.totalSeconds, 0);
315
+ const totalSessions = days.reduce((s, d) => s + d.sessionCount, 0);
316
+ const totalBypasses = days.reduce((s, d) => s + d.bypassCount, 0);
317
+ const maxSeconds = Math.max(...days.map((d) => d.totalSeconds), 1);
318
+ return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", padding: 1, children: [
319
+ /* @__PURE__ */ jsxs3(Box2, { marginBottom: 1, children: [
320
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Usage Statistics " }),
321
+ PERIODS.map((p) => /* @__PURE__ */ jsxs3(Text4, { color: period === p.id ? "green" : void 0, bold: period === p.id, children: [
322
+ "[",
323
+ p.key,
324
+ "] ",
325
+ p.label,
326
+ " "
327
+ ] }, p.key))
328
+ ] }),
329
+ days.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No usage data for this period." }) : /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
330
+ days.map((day) => {
331
+ const barWidth = Math.max(
332
+ 1,
333
+ Math.round(day.totalSeconds / maxSeconds * 40)
334
+ );
335
+ const bar = "\u2588".repeat(barWidth);
336
+ const dayLabel = day.date.slice(5);
337
+ return /* @__PURE__ */ jsxs3(Box2, { children: [
338
+ /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
339
+ dayLabel,
340
+ " "
341
+ ] }),
342
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: bar }),
343
+ /* @__PURE__ */ jsxs3(Text4, { children: [
344
+ " ",
345
+ formatDuration2(day.totalSeconds)
346
+ ] })
347
+ ] }, day.date);
348
+ }),
349
+ /* @__PURE__ */ jsxs3(Box2, { marginTop: 1, flexDirection: "column", children: [
350
+ /* @__PURE__ */ jsxs3(Text4, { bold: true, children: [
351
+ "Total: ",
352
+ formatDuration2(totalSeconds),
353
+ " across ",
354
+ totalSessions,
355
+ " ",
356
+ "session",
357
+ totalSessions !== 1 ? "s" : ""
358
+ ] }),
359
+ totalBypasses > 0 && /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
360
+ "Bypasses: ",
361
+ totalBypasses
362
+ ] }),
363
+ days.length > 1 && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
364
+ "Avg: ",
365
+ formatDuration2(Math.round(totalSeconds / days.length)),
366
+ "/day"
367
+ ] })
368
+ ] })
369
+ ] }),
370
+ confirmMode !== null && /* @__PURE__ */ jsxs3(Box2, { marginTop: 1, borderStyle: "round", paddingX: 1, children: [
371
+ /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
372
+ confirmMode === "today" ? "Reset today's stats?" : "Reset ALL stats?",
373
+ " "
374
+ ] }),
375
+ /* @__PURE__ */ jsx4(Text4, { children: "[Y] Yes [N/Esc] Cancel" })
376
+ ] }),
377
+ confirmMode === null && /* @__PURE__ */ jsx4(Box2, { marginTop: 1, borderStyle: "single", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "[R] Reset today [X] Reset all" }) })
378
+ ] });
379
+ }
380
+ function TextInput({ value, onChange, onSubmit, onCancel, placeholder, label }) {
381
+ useInput3((input, key) => {
382
+ if (key.return) {
383
+ onSubmit(value);
384
+ return;
385
+ }
386
+ if (key.escape) {
387
+ onCancel();
388
+ return;
389
+ }
390
+ if (key.backspace || key.delete) {
391
+ onChange(value.slice(0, -1));
392
+ return;
393
+ }
394
+ if (input && !key.ctrl && !key.meta) {
395
+ onChange(value + input);
396
+ }
397
+ });
398
+ const displayValue = value !== "" ? value : placeholder ?? "";
399
+ const displayColor = value !== "" ? void 0 : "gray";
400
+ return /* @__PURE__ */ jsxs4(Box3, { children: [
401
+ label && /* @__PURE__ */ jsxs4(Text5, { children: [
402
+ label,
403
+ " "
404
+ ] }),
405
+ /* @__PURE__ */ jsxs4(Text5, { color: displayColor, children: [
406
+ displayValue,
407
+ "\u2588"
408
+ ] })
409
+ ] });
410
+ }
411
+ function SelectInput({ options, selectedIndex: initialIndex, onSelect, onCancel }) {
412
+ const [idx, setIdx] = useState4(initialIndex);
413
+ useInput4((_input, key) => {
414
+ if (key.escape) {
415
+ onCancel();
416
+ return;
417
+ }
418
+ if (key.return) {
419
+ onSelect(idx);
420
+ return;
421
+ }
422
+ if (key.upArrow) {
423
+ setIdx((i) => Math.max(0, i - 1));
424
+ return;
425
+ }
426
+ if (key.downArrow) {
427
+ setIdx((i) => Math.min(options.length - 1, i + 1));
428
+ return;
429
+ }
430
+ });
431
+ return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((opt, i) => /* @__PURE__ */ jsxs5(Text6, { color: i === idx ? "green" : void 0, children: [
432
+ i === idx ? "\u25B6 " : " ",
433
+ opt.label
434
+ ] }, opt.value)) });
435
+ }
436
+ function MultiSelect({ options, selected: initialSelected, onConfirm, onCancel }) {
437
+ const [idx, setIdx] = useState5(0);
438
+ const [selected, setSelected] = useState5(new Set(initialSelected));
439
+ useInput5((input, key) => {
440
+ if (key.escape) {
441
+ onCancel();
442
+ return;
443
+ }
444
+ if (key.return) {
445
+ onConfirm([...selected]);
446
+ return;
447
+ }
448
+ if (key.upArrow) {
449
+ setIdx((i) => Math.max(0, i - 1));
450
+ return;
451
+ }
452
+ if (key.downArrow) {
453
+ setIdx((i) => Math.min(options.length - 1, i + 1));
454
+ return;
455
+ }
456
+ if (input === " ") {
457
+ const val = options[idx].value;
458
+ setSelected((s) => {
459
+ const next = new Set(s);
460
+ if (next.has(val)) next.delete(val);
461
+ else next.add(val);
462
+ return next;
463
+ });
464
+ }
465
+ });
466
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
467
+ options.map((opt, i) => /* @__PURE__ */ jsxs6(Text7, { color: i === idx ? "green" : void 0, children: [
468
+ i === idx ? "\u25B6 " : " ",
469
+ selected.has(opt.value) ? "[\u2713] " : "[ ] ",
470
+ opt.label
471
+ ] }, opt.value)),
472
+ /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " [Space] Toggle [Enter] Confirm [Esc] Cancel" })
473
+ ] });
474
+ }
475
+ var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
476
+ var TYPE_OPTIONS = [
477
+ { label: "Daily (every day)", value: "daily" },
478
+ { label: "Weekdays (Mon\u2013Fri)", value: "weekdays" },
479
+ { label: "Weekends (Sat\u2013Sun)", value: "weekends" },
480
+ { label: "Custom days", value: "custom" }
481
+ ];
482
+ var DAY_OPTIONS = DAY_NAMES.map((name, i) => ({ label: name, value: String(i) }));
483
+ function isValidTime(t) {
484
+ if (!/^\d{2}:\d{2}$/.test(t)) return false;
485
+ const [h, m] = t.split(":").map(Number);
486
+ return h >= 0 && h <= 23 && m >= 0 && m <= 59;
487
+ }
488
+ function SchedulesScreen({ onFormActiveChange }) {
489
+ const { schedules, refresh } = useSchedules();
490
+ const [mode, setMode] = useState6("list");
491
+ const [selectedIdx, setSelectedIdx] = useState6(0);
492
+ const [step, setStep] = useState6(0);
493
+ const [name, setName] = useState6("");
494
+ const [typeIdx, setTypeIdx] = useState6(0);
495
+ const [startTime, setStartTime] = useState6("");
496
+ const [endTime, setEndTime] = useState6("");
497
+ const [wizardError, setWizardError] = useState6(null);
498
+ useEffect2(() => {
499
+ if (selectedIdx >= schedules.length && schedules.length > 0) {
500
+ setSelectedIdx(schedules.length - 1);
501
+ }
502
+ }, [schedules.length, selectedIdx]);
503
+ const enterList = () => {
504
+ setMode("list");
505
+ onFormActiveChange(false);
506
+ };
507
+ const cancelWizard = () => {
508
+ setMode("list");
509
+ onFormActiveChange(false);
510
+ setStep(0);
511
+ setName("");
512
+ setTypeIdx(0);
513
+ setStartTime("");
514
+ setEndTime("");
515
+ setWizardError(null);
516
+ };
517
+ const submitWizard = (days) => {
518
+ const scheduleType = TYPE_OPTIONS[typeIdx].value;
519
+ const schedule = {
520
+ name: name.trim(),
521
+ type: scheduleType,
522
+ startTime,
523
+ endTime,
524
+ enabled: true,
525
+ ...scheduleType === "custom" ? { days } : {}
526
+ };
527
+ sendRequest({ type: "schedule-add", schedule }).then((res) => {
528
+ const r = res;
529
+ if (!r.ok) {
530
+ setWizardError(r.error ?? "Failed to add schedule");
531
+ return;
532
+ }
533
+ refresh();
534
+ cancelWizard();
535
+ }).catch((err) => {
536
+ setWizardError(err instanceof Error ? err.message : "Unknown error");
537
+ });
538
+ };
539
+ useInput6(
540
+ (input, key) => {
541
+ if (mode === "confirm-delete") {
542
+ if (input.toLowerCase() === "y") {
543
+ const s = schedules[selectedIdx];
544
+ if (!s) {
545
+ enterList();
546
+ return;
547
+ }
548
+ sendRequest({ type: "schedule-remove", id: s.id }).then((res) => {
549
+ const r = res;
550
+ if (!r.ok) {
551
+ }
552
+ refresh();
553
+ }).catch(() => {
554
+ }).finally(() => enterList());
555
+ } else if (input.toLowerCase() === "n" || key.escape) {
556
+ enterList();
557
+ }
558
+ return;
559
+ }
560
+ if (key.upArrow) {
561
+ setSelectedIdx((i) => Math.max(0, i - 1));
562
+ return;
563
+ }
564
+ if (key.downArrow) {
565
+ setSelectedIdx((i) => Math.min(schedules.length - 1, i + 1));
566
+ return;
567
+ }
568
+ if ((input === " " || input === "T") && schedules.length > 0) {
569
+ const s = schedules[selectedIdx];
570
+ if (!s) return;
571
+ sendRequest({ type: "schedule-toggle", id: s.id, enabled: !s.enabled }).then((res) => {
572
+ const r = res;
573
+ if (r.ok) refresh();
574
+ }).catch(() => {
575
+ });
576
+ return;
577
+ }
578
+ if (input === "D" && schedules.length > 0) {
579
+ setMode("confirm-delete");
580
+ onFormActiveChange(true);
581
+ return;
582
+ }
583
+ if (input === "a") {
584
+ setStep(0);
585
+ setName("");
586
+ setTypeIdx(0);
587
+ setStartTime("");
588
+ setEndTime("");
589
+ setWizardError(null);
590
+ setMode("add");
591
+ onFormActiveChange(true);
592
+ return;
593
+ }
594
+ },
595
+ { isActive: mode !== "add" }
596
+ );
597
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", padding: 1, children: [
598
+ /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text8, { bold: true, children: "Schedules" }) }),
599
+ mode !== "add" && /* @__PURE__ */ jsxs7(Fragment, { children: [
600
+ schedules.length === 0 ? /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "No schedules configured." }) : /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: schedules.map((s, i) => /* @__PURE__ */ jsxs7(Box6, { marginBottom: 1, flexDirection: "column", children: [
601
+ /* @__PURE__ */ jsxs7(Box6, { children: [
602
+ /* @__PURE__ */ jsx7(Text8, { color: i === selectedIdx ? "green" : void 0, children: i === selectedIdx ? "\u25B6 " : " " }),
603
+ /* @__PURE__ */ jsxs7(Text8, { color: s.enabled ? "green" : "red", children: [
604
+ s.enabled ? "\u25CF" : "\u25CB",
605
+ " "
606
+ ] }),
607
+ /* @__PURE__ */ jsx7(Text8, { bold: i === selectedIdx, children: s.name }),
608
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
609
+ " ",
610
+ s.id.slice(0, 10)
611
+ ] })
612
+ ] }),
613
+ /* @__PURE__ */ jsx7(Box6, { paddingLeft: 4, children: /* @__PURE__ */ jsxs7(Text8, { children: [
614
+ s.startTime,
615
+ " \u2013 ",
616
+ s.endTime,
617
+ " ",
618
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: s.type === "custom" && s.days ? s.days.map((d) => DAY_NAMES[d]).join(", ") : s.type })
619
+ ] }) })
620
+ ] }, s.id)) }),
621
+ mode === "confirm-delete" && schedules[selectedIdx] && /* @__PURE__ */ jsxs7(Box6, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [
622
+ /* @__PURE__ */ jsxs7(Text8, { color: "yellow", children: [
623
+ 'Delete "',
624
+ schedules[selectedIdx].name,
625
+ '"?'
626
+ ] }),
627
+ /* @__PURE__ */ jsx7(Text8, { children: "[Y] Delete [N/Esc] Cancel" })
628
+ ] }),
629
+ mode === "list" && /* @__PURE__ */ jsx7(Box6, { marginTop: 1, borderStyle: "single", paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
630
+ "[\u2191/\u2193] Navigate",
631
+ " ",
632
+ "[Space/T] Toggle",
633
+ " ",
634
+ "[D] Delete",
635
+ " ",
636
+ "[A] Add"
637
+ ] }) })
638
+ ] }),
639
+ mode === "add" && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
640
+ /* @__PURE__ */ jsxs7(Box6, { marginBottom: 1, children: [
641
+ /* @__PURE__ */ jsx7(Text8, { bold: true, children: "New Schedule" }),
642
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " [Esc] Cancel" })
643
+ ] }),
644
+ wizardError && /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text8, { color: "red", children: wizardError }) }),
645
+ step === 0 && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
646
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "Step 1/4 \u2013 Name" }),
647
+ /* @__PURE__ */ jsx7(
648
+ TextInput,
649
+ {
650
+ label: "Name:",
651
+ value: name,
652
+ onChange: (v) => {
653
+ setName(v);
654
+ setWizardError(null);
655
+ },
656
+ onSubmit: (v) => {
657
+ if (!v.trim()) {
658
+ setWizardError("Name is required");
659
+ return;
660
+ }
661
+ setWizardError(null);
662
+ setStep(1);
663
+ },
664
+ onCancel: cancelWizard,
665
+ placeholder: "e.g. Morning focus"
666
+ }
667
+ )
668
+ ] }),
669
+ step === 1 && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
670
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "Step 2/4 \u2013 Type" }),
671
+ /* @__PURE__ */ jsx7(
672
+ SelectInput,
673
+ {
674
+ options: TYPE_OPTIONS,
675
+ selectedIndex: typeIdx,
676
+ onSelect: (idx) => {
677
+ setTypeIdx(idx);
678
+ setWizardError(null);
679
+ setStep(2);
680
+ },
681
+ onCancel: cancelWizard
682
+ }
683
+ )
684
+ ] }),
685
+ step === 2 && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
686
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "Step 3/4 \u2013 Start time (HH:MM)" }),
687
+ /* @__PURE__ */ jsx7(
688
+ TextInput,
689
+ {
690
+ label: "Start:",
691
+ value: startTime,
692
+ onChange: (v) => {
693
+ setStartTime(v);
694
+ setWizardError(null);
695
+ },
696
+ onSubmit: (v) => {
697
+ if (!isValidTime(v)) {
698
+ setWizardError("Enter time as HH:MM (e.g. 09:00)");
699
+ return;
700
+ }
701
+ setWizardError(null);
702
+ setStep(3);
703
+ },
704
+ onCancel: cancelWizard,
705
+ placeholder: "09:00"
706
+ }
707
+ )
708
+ ] }),
709
+ step === 3 && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
710
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "Step 4/4 \u2013 End time (HH:MM)" }),
711
+ /* @__PURE__ */ jsx7(
712
+ TextInput,
713
+ {
714
+ label: "End:",
715
+ value: endTime,
716
+ onChange: (v) => {
717
+ setEndTime(v);
718
+ setWizardError(null);
719
+ },
720
+ onSubmit: (v) => {
721
+ if (!isValidTime(v)) {
722
+ setWizardError("Enter time as HH:MM (e.g. 17:00)");
723
+ return;
724
+ }
725
+ if (v <= startTime) {
726
+ setWizardError("End time must be after start time");
727
+ return;
728
+ }
729
+ setWizardError(null);
730
+ const scheduleType = TYPE_OPTIONS[typeIdx].value;
731
+ if (scheduleType === "custom") {
732
+ setStep(4);
733
+ } else {
734
+ submitWizard([]);
735
+ }
736
+ },
737
+ onCancel: cancelWizard,
738
+ placeholder: "17:00"
739
+ }
740
+ )
741
+ ] }),
742
+ step === 4 && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
743
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "Select days" }),
744
+ /* @__PURE__ */ jsx7(
745
+ MultiSelect,
746
+ {
747
+ options: DAY_OPTIONS,
748
+ selected: [],
749
+ onConfirm: (vals) => submitWizard(vals.map(Number)),
750
+ onCancel: cancelWizard
751
+ }
752
+ )
753
+ ] })
754
+ ] })
755
+ ] });
756
+ }
757
+ function SettingsScreen({ config, lock, onRefresh, onFormActiveChange }) {
758
+ const [selectedIdx, setSelectedIdx] = useState7(0);
759
+ const [pending, setPending] = useState7(false);
760
+ const [error, setError] = useState7(null);
761
+ const [editing, setEditing] = useState7(false);
762
+ const [editValue, setEditValue] = useState7("");
763
+ const isLocked = lock?.status === "locked" || lock?.status === "grace";
764
+ const rows = config ? [
765
+ { kind: "bool", key: "chmodGuard", label: "chmod guard", value: config.chmodGuard },
766
+ {
767
+ kind: "bool",
768
+ key: "challengeBypassEnabled",
769
+ label: "Allow bypass",
770
+ value: config.challengeBypassEnabled ?? true
771
+ },
772
+ {
773
+ kind: "bool",
774
+ key: "paymentBypassEnabled",
775
+ label: "Payment bypass",
776
+ value: config.paymentBypassEnabled ?? false
777
+ },
778
+ {
779
+ kind: "bool",
780
+ key: "killSessionsOnLock",
781
+ label: "Kill on lock",
782
+ value: config.killSessionsOnLock ?? false
783
+ },
784
+ {
785
+ kind: "number",
786
+ key: "graceMinutes",
787
+ label: "Grace period",
788
+ value: config.graceMinutes,
789
+ unit: "min"
790
+ }
791
+ ] : [];
792
+ const save = (key, value) => {
793
+ setPending(true);
794
+ setError(null);
795
+ sendRequest({ type: "config-set", key, value }).then((res) => {
796
+ const r = res;
797
+ if (!r.ok) setError(r.error ?? "Failed to update setting");
798
+ else onRefresh();
799
+ }).catch((err) => {
800
+ setError(err instanceof Error ? err.message : "Unknown error");
801
+ }).finally(() => setPending(false));
802
+ };
803
+ const exitEdit = () => {
804
+ setEditing(false);
805
+ setEditValue("");
806
+ onFormActiveChange(false);
807
+ };
808
+ useInput7(
809
+ (input, key) => {
810
+ if (!config || pending || isLocked) return;
811
+ if (key.upArrow) {
812
+ setSelectedIdx((i) => Math.max(0, i - 1));
813
+ setError(null);
814
+ return;
815
+ }
816
+ if (key.downArrow) {
817
+ setSelectedIdx((i) => Math.min(rows.length - 1, i + 1));
818
+ setError(null);
819
+ return;
820
+ }
821
+ const row = rows[selectedIdx];
822
+ if (!row) return;
823
+ if (row.kind === "bool" && (input === " " || key.return)) {
824
+ save(row.key, !row.value);
825
+ return;
826
+ }
827
+ if (row.kind === "number" && key.return) {
828
+ setEditValue(String(row.value));
829
+ setEditing(true);
830
+ onFormActiveChange(true);
831
+ return;
832
+ }
833
+ },
834
+ { isActive: !!config && !editing }
835
+ );
836
+ if (!config) {
837
+ return /* @__PURE__ */ jsx8(Box7, { padding: 1, children: /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: "Loading config..." }) });
838
+ }
839
+ const infoRows = [
840
+ ["Installation", config.installationType],
841
+ ["Binary path", config.claudeBinaryPath],
842
+ ["Shim path", config.claudeShimPath]
843
+ ];
844
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", padding: 1, children: [
845
+ /* @__PURE__ */ jsxs8(Box7, { marginBottom: 1, children: [
846
+ /* @__PURE__ */ jsx8(Text9, { bold: true, children: "Settings" }),
847
+ isLocked && /* @__PURE__ */ jsxs8(Text9, { color: "yellow", children: [
848
+ " ",
849
+ "\u{1F512} locked \u2014 unlock to change settings"
850
+ ] })
851
+ ] }),
852
+ infoRows.map(([label, value]) => /* @__PURE__ */ jsxs8(Box7, { children: [
853
+ /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: label.padEnd(16) }),
854
+ /* @__PURE__ */ jsx8(Text9, { children: value })
855
+ ] }, label)),
856
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, flexDirection: "column", children: rows.map((row, i) => {
857
+ const isSelected = i === selectedIdx && !isLocked;
858
+ const isEditingThis = isSelected && editing && row.kind === "number";
859
+ return /* @__PURE__ */ jsxs8(Box7, { children: [
860
+ /* @__PURE__ */ jsx8(Text9, { color: isSelected ? "green" : void 0, children: isSelected ? "\u25B6 " : " " }),
861
+ /* @__PURE__ */ jsx8(Text9, { dimColor: isLocked, children: row.label.padEnd(14) }),
862
+ row.kind === "bool" && /* @__PURE__ */ jsx8(
863
+ Text9,
864
+ {
865
+ color: isLocked ? void 0 : row.value ? "green" : "red",
866
+ dimColor: isLocked,
867
+ children: row.value ? "[\u2713] enabled " : "[\u25CB] disabled"
868
+ }
869
+ ),
870
+ row.kind === "number" && !isEditingThis && /* @__PURE__ */ jsxs8(Text9, { dimColor: isLocked, children: [
871
+ row.value,
872
+ " ",
873
+ /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: row.unit })
874
+ ] }),
875
+ row.kind === "number" && isEditingThis && /* @__PURE__ */ jsx8(
876
+ TextInput,
877
+ {
878
+ value: editValue,
879
+ onChange: setEditValue,
880
+ onSubmit: (v) => {
881
+ const n = parseInt(v, 10);
882
+ if (!v.trim() || isNaN(n) || n < 1) {
883
+ setError("Enter a whole number of minutes (\u2265 1)");
884
+ return;
885
+ }
886
+ exitEdit();
887
+ save(row.key, n);
888
+ },
889
+ onCancel: exitEdit,
890
+ placeholder: "minutes"
891
+ }
892
+ )
893
+ ] }, row.key);
894
+ }) }),
895
+ error && /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { color: "red", children: error }) }),
896
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, borderStyle: "single", paddingX: 1, children: /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: isLocked ? "Settings are read-only while locked" : pending ? "Saving..." : editing ? "[Enter] Save [Esc] Cancel" : "[\u2191/\u2193] Navigate [Space/Enter] Toggle / Edit" }) })
897
+ ] });
898
+ }
899
+ var TABS = [
900
+ { key: "d", id: "dashboard", label: "Dashboard" },
901
+ { key: "s", id: "stats", label: "Stats" },
902
+ { key: "c", id: "schedules", label: "Schedules" },
903
+ { key: "t", id: "settings", label: "Settings" }
904
+ ];
905
+ var ADDICT_BANNER = [
906
+ " \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
907
+ " \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
908
+ " \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 ",
909
+ " \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D ",
910
+ " \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
911
+ " \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
912
+ "",
913
+ " \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
914
+ " \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
915
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
916
+ " \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
917
+ " \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
918
+ " \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D "
919
+ ];
920
+ var LOGO_BODY = [
921
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
922
+ " \u2502 \u2502",
923
+ " \u2554\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2557",
924
+ " \u2551 \u2551",
925
+ " \u2551 cc \xB7 lock \u2551",
926
+ " \u2551 \u2551",
927
+ " \u2551 \u25CB \u25CB \u25CB \u25CB \u2551",
928
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
929
+ ];
930
+ var LOGO_LOCKED_BODY = [
931
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
932
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
933
+ " \u2554\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2557",
934
+ " \u2551 \u2551",
935
+ " \u2551 cc \xB7 lock \u2551",
936
+ " \u2551 \u2551",
937
+ " \u2551 \u25CB \u25CB \u25CB \u25CB \u2551",
938
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
939
+ ];
940
+ function App() {
941
+ const [tab, setTab] = useState8("dashboard");
942
+ const [formActive, setFormActive] = useState8(false);
943
+ const { lock, config, todayUsage, connected, refresh } = useDaemonStatus();
944
+ const { exit } = useApp();
945
+ useInput8((input, key) => {
946
+ if (input === "q" || key.ctrl && input === "c") {
947
+ exit();
948
+ return;
949
+ }
950
+ for (const t of TABS) {
951
+ if (input === t.key && tab !== t.id) {
952
+ setTab(t.id);
953
+ return;
954
+ }
955
+ }
956
+ }, { isActive: !formActive });
957
+ const lockColor = !connected ? "gray" : lock?.status === "locked" ? "red" : lock?.status === "grace" ? "yellow" : "green";
958
+ const tagline = !connected ? " connecting..." : lock?.status === "locked" ? " you locked yourself. good." : lock?.status === "grace" ? " enjoy your grace period." : " I can stop whenever I want.";
959
+ return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
960
+ /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", paddingX: 1, children: [
961
+ (lock?.status === "locked" ? LOGO_LOCKED_BODY : LOGO_BODY).map((line, i) => /* @__PURE__ */ jsx9(Text10, { color: lockColor, children: line }, i)),
962
+ /* @__PURE__ */ jsx9(Text10, { color: lockColor, dimColor: true, children: tagline })
963
+ ] }),
964
+ /* @__PURE__ */ jsxs9(Box8, { borderStyle: "single", paddingX: 1, children: [
965
+ TABS.map((t, i) => /* @__PURE__ */ jsxs9(React7.Fragment, { children: [
966
+ i > 0 && /* @__PURE__ */ jsx9(Text10, { children: " | " }),
967
+ /* @__PURE__ */ jsxs9(
968
+ Text10,
969
+ {
970
+ bold: tab === t.id,
971
+ color: tab === t.id ? "green" : void 0,
972
+ dimColor: tab !== t.id,
973
+ children: [
974
+ "[",
975
+ t.key.toUpperCase(),
976
+ "] ",
977
+ t.label
978
+ ]
979
+ }
980
+ )
981
+ ] }, t.id)),
982
+ /* @__PURE__ */ jsx9(Text10, { children: " | " }),
983
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "[Q] Quit" })
984
+ ] }),
985
+ tab === "dashboard" && /* @__PURE__ */ jsx9(
986
+ Dashboard,
987
+ {
988
+ lock,
989
+ todayUsage,
990
+ connected,
991
+ onRefresh: refresh
992
+ }
993
+ ),
994
+ tab === "stats" && /* @__PURE__ */ jsx9(StatsScreen, {}),
995
+ tab === "schedules" && /* @__PURE__ */ jsx9(SchedulesScreen, { onFormActiveChange: setFormActive }),
996
+ tab === "settings" && /* @__PURE__ */ jsx9(SettingsScreen, { config, lock, onRefresh: refresh, onFormActiveChange: setFormActive })
997
+ ] });
998
+ }
999
+ async function launchTui() {
1000
+ process.stdout.write(ADDICT_BANNER.join("\n") + "\n\n");
1001
+ const { waitUntilExit } = render(React8.createElement(App));
1002
+ await waitUntilExit();
1003
+ }
1004
+ export {
1005
+ launchTui
1006
+ };
1007
+ //# sourceMappingURL=dist-56C5YEZV.js.map