@cjvana/claude-auto 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/.claude-plugin/plugin.json +10 -0
- package/LICENSE +21 -0
- package/README.md +435 -0
- package/dist/check-repo-6C4QI2M2.js +33 -0
- package/dist/check-repo-6C4QI2M2.js.map +1 -0
- package/dist/check-repo-SXWFIVO5.js +8 -0
- package/dist/check-repo-SXWFIVO5.js.map +1 -0
- package/dist/chunk-24PS2XSV.js +203 -0
- package/dist/chunk-24PS2XSV.js.map +1 -0
- package/dist/chunk-2D5E23XA.js +129 -0
- package/dist/chunk-2D5E23XA.js.map +1 -0
- package/dist/chunk-3NEANSUS.js +26 -0
- package/dist/chunk-3NEANSUS.js.map +1 -0
- package/dist/chunk-4I5UIASZ.js +71 -0
- package/dist/chunk-4I5UIASZ.js.map +1 -0
- package/dist/chunk-5LGOK52J.js +38 -0
- package/dist/chunk-5LGOK52J.js.map +1 -0
- package/dist/chunk-6RYMWH5M.js +35 -0
- package/dist/chunk-6RYMWH5M.js.map +1 -0
- package/dist/chunk-A6XWZPLY.js +56 -0
- package/dist/chunk-A6XWZPLY.js.map +1 -0
- package/dist/chunk-AWLSYOVF.js +61 -0
- package/dist/chunk-AWLSYOVF.js.map +1 -0
- package/dist/chunk-BY5YEOVG.js +75 -0
- package/dist/chunk-BY5YEOVG.js.map +1 -0
- package/dist/chunk-D4MBOIYQ.js +46 -0
- package/dist/chunk-D4MBOIYQ.js.map +1 -0
- package/dist/chunk-DVZC42TL.js +33 -0
- package/dist/chunk-DVZC42TL.js.map +1 -0
- package/dist/chunk-E3XVLTT4.js +13 -0
- package/dist/chunk-E3XVLTT4.js.map +1 -0
- package/dist/chunk-GLW7T4QE.js +116 -0
- package/dist/chunk-GLW7T4QE.js.map +1 -0
- package/dist/chunk-H2MUDYMW.js +23 -0
- package/dist/chunk-H2MUDYMW.js.map +1 -0
- package/dist/chunk-HF7PGQI3.js +69 -0
- package/dist/chunk-HF7PGQI3.js.map +1 -0
- package/dist/chunk-LBH6SLHH.js +543 -0
- package/dist/chunk-LBH6SLHH.js.map +1 -0
- package/dist/chunk-M53MPY3U.js +115 -0
- package/dist/chunk-M53MPY3U.js.map +1 -0
- package/dist/chunk-MI7OZ5XD.js +146 -0
- package/dist/chunk-MI7OZ5XD.js.map +1 -0
- package/dist/chunk-NB46PEG2.js +177 -0
- package/dist/chunk-NB46PEG2.js.map +1 -0
- package/dist/chunk-ORBF5IW3.js +60 -0
- package/dist/chunk-ORBF5IW3.js.map +1 -0
- package/dist/chunk-PFU5YLRH.js +131 -0
- package/dist/chunk-PFU5YLRH.js.map +1 -0
- package/dist/chunk-QLRCFKLU.js +34 -0
- package/dist/chunk-QLRCFKLU.js.map +1 -0
- package/dist/chunk-QQTIJN3S.js +167 -0
- package/dist/chunk-QQTIJN3S.js.map +1 -0
- package/dist/chunk-QRYCNVLT.js +72 -0
- package/dist/chunk-QRYCNVLT.js.map +1 -0
- package/dist/chunk-S6E67XMR.js +52 -0
- package/dist/chunk-S6E67XMR.js.map +1 -0
- package/dist/chunk-S6W4SURF.js +33 -0
- package/dist/chunk-S6W4SURF.js.map +1 -0
- package/dist/chunk-SMZYA6CY.js +121 -0
- package/dist/chunk-SMZYA6CY.js.map +1 -0
- package/dist/chunk-SNOA575X.js +12 -0
- package/dist/chunk-SNOA575X.js.map +1 -0
- package/dist/chunk-SZRIZBWI.js +44 -0
- package/dist/chunk-SZRIZBWI.js.map +1 -0
- package/dist/chunk-TAGHPCFT.js +47 -0
- package/dist/chunk-TAGHPCFT.js.map +1 -0
- package/dist/chunk-TGKCHHXT.js +34 -0
- package/dist/chunk-TGKCHHXT.js.map +1 -0
- package/dist/chunk-TORYFKPK.js +39 -0
- package/dist/chunk-TORYFKPK.js.map +1 -0
- package/dist/chunk-U35GRLBD.js +143 -0
- package/dist/chunk-U35GRLBD.js.map +1 -0
- package/dist/chunk-W2HBRERV.js +57 -0
- package/dist/chunk-W2HBRERV.js.map +1 -0
- package/dist/chunk-WYU476R2.js +119 -0
- package/dist/chunk-WYU476R2.js.map +1 -0
- package/dist/chunk-YMO45Z6G.js +69 -0
- package/dist/chunk-YMO45Z6G.js.map +1 -0
- package/dist/claude-auto-run.js +1717 -0
- package/dist/claude-auto-run.js.map +1 -0
- package/dist/claude-auto.js +186 -0
- package/dist/claude-auto.js.map +1 -0
- package/dist/cost-QGM3D4QW.js +72 -0
- package/dist/cost-QGM3D4QW.js.map +1 -0
- package/dist/cost-QKN3U7AG.js +11 -0
- package/dist/cost-QKN3U7AG.js.map +1 -0
- package/dist/create-T3BDDS6G.js +14 -0
- package/dist/create-T3BDDS6G.js.map +1 -0
- package/dist/create-U5WYKTD4.js +118 -0
- package/dist/create-U5WYKTD4.js.map +1 -0
- package/dist/crontab-CDMC2FDT.js +118 -0
- package/dist/crontab-CDMC2FDT.js.map +1 -0
- package/dist/crontab-MAJ52FOK.js +118 -0
- package/dist/crontab-MAJ52FOK.js.map +1 -0
- package/dist/crontab-PNEWANLW.js +12 -0
- package/dist/crontab-PNEWANLW.js.map +1 -0
- package/dist/edit-77E3ZQHM.js +134 -0
- package/dist/edit-77E3ZQHM.js.map +1 -0
- package/dist/edit-RVPRAAQ2.js +13 -0
- package/dist/edit-RVPRAAQ2.js.map +1 -0
- package/dist/index.d.ts +1137 -0
- package/dist/index.js +2049 -0
- package/dist/index.js.map +1 -0
- package/dist/launchd-7F27BIZB.js +166 -0
- package/dist/launchd-7F27BIZB.js.map +1 -0
- package/dist/launchd-HNZIWLNC.js +166 -0
- package/dist/launchd-HNZIWLNC.js.map +1 -0
- package/dist/launchd-LZGDP7BM.js +12 -0
- package/dist/launchd-LZGDP7BM.js.map +1 -0
- package/dist/list-OIGERGYJ.js +15 -0
- package/dist/list-OIGERGYJ.js.map +1 -0
- package/dist/list-T35RSQVU.js +73 -0
- package/dist/list-T35RSQVU.js.map +1 -0
- package/dist/logs-D5FNSCXE.js +12 -0
- package/dist/logs-D5FNSCXE.js.map +1 -0
- package/dist/logs-YVSFXBSB.js +40 -0
- package/dist/logs-YVSFXBSB.js.map +1 -0
- package/dist/pause-2YOLFMAR.js +12 -0
- package/dist/pause-2YOLFMAR.js.map +1 -0
- package/dist/pause-JB42JGTB.js +45 -0
- package/dist/pause-JB42JGTB.js.map +1 -0
- package/dist/pause-OJNUYBCJ.js +47 -0
- package/dist/pause-OJNUYBCJ.js.map +1 -0
- package/dist/remove-RXYKFYBI.js +12 -0
- package/dist/remove-RXYKFYBI.js.map +1 -0
- package/dist/remove-UASXZCOR.js +59 -0
- package/dist/remove-UASXZCOR.js.map +1 -0
- package/dist/report-CHAJH2SA.js +150 -0
- package/dist/report-CHAJH2SA.js.map +1 -0
- package/dist/report-IYGK5HTC.js +14 -0
- package/dist/report-IYGK5HTC.js.map +1 -0
- package/dist/resume-3ATNZP6D.js +13 -0
- package/dist/resume-3ATNZP6D.js.map +1 -0
- package/dist/resume-6WVGU6XW.js +48 -0
- package/dist/resume-6WVGU6XW.js.map +1 -0
- package/dist/resume-JVTR7OEX.js +50 -0
- package/dist/resume-JVTR7OEX.js.map +1 -0
- package/dist/schtasks-2EQAD3ES.js +11 -0
- package/dist/schtasks-2EQAD3ES.js.map +1 -0
- package/dist/schtasks-4V2IFD3A.js +142 -0
- package/dist/schtasks-4V2IFD3A.js.map +1 -0
- package/dist/schtasks-JGEPEKQS.js +142 -0
- package/dist/schtasks-JGEPEKQS.js.map +1 -0
- package/dist/tui-2DUPCX3Q.js +15 -0
- package/dist/tui-2DUPCX3Q.js.map +1 -0
- package/dist/tui-6LOGPILA.js +547 -0
- package/dist/tui-6LOGPILA.js.map +1 -0
- package/package.json +81 -0
- package/scripts/postinstall.mjs +65 -0
- package/scripts/preuninstall.mjs +33 -0
- package/skills/edit/SKILL.md +25 -0
- package/skills/list/SKILL.md +26 -0
- package/skills/logs/SKILL.md +33 -0
- package/skills/pause/SKILL.md +21 -0
- package/skills/remove/SKILL.md +22 -0
- package/skills/resume/SKILL.md +21 -0
- package/skills/setup/SKILL.md +195 -0
- package/skills/status/SKILL.md +27 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listRunLogs
|
|
3
|
+
} from "./chunk-SMZYA6CY.js";
|
|
4
|
+
import {
|
|
5
|
+
getCostSummary
|
|
6
|
+
} from "./chunk-S6E67XMR.js";
|
|
7
|
+
import {
|
|
8
|
+
getNextRuns
|
|
9
|
+
} from "./chunk-D4MBOIYQ.js";
|
|
10
|
+
import {
|
|
11
|
+
listJobs
|
|
12
|
+
} from "./chunk-24PS2XSV.js";
|
|
13
|
+
|
|
14
|
+
// src/tui/index.tsx
|
|
15
|
+
import "react";
|
|
16
|
+
import { render } from "ink";
|
|
17
|
+
|
|
18
|
+
// src/tui/app.tsx
|
|
19
|
+
import { useState as useState4 } from "react";
|
|
20
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
21
|
+
|
|
22
|
+
// src/tui/components/job-list.tsx
|
|
23
|
+
import { Box, Text } from "ink";
|
|
24
|
+
import { Spinner } from "@inkjs/ui";
|
|
25
|
+
import cronstrue from "cronstrue";
|
|
26
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
27
|
+
function formatRelativeTime(date) {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const diffMs = date.getTime() - now;
|
|
30
|
+
if (diffMs < 0) return "overdue";
|
|
31
|
+
const diffMin = Math.floor(diffMs / 6e4);
|
|
32
|
+
if (diffMin < 60) return `in ${diffMin}m`;
|
|
33
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
34
|
+
if (diffHr < 24) return `in ${diffHr}h`;
|
|
35
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
36
|
+
return `in ${diffDay}d`;
|
|
37
|
+
}
|
|
38
|
+
function formatCost(cost) {
|
|
39
|
+
return `$${cost.toFixed(2)}`;
|
|
40
|
+
}
|
|
41
|
+
function truncate(str, max) {
|
|
42
|
+
if (str.length <= max) return str;
|
|
43
|
+
return str.slice(0, max - 1) + "\u2026";
|
|
44
|
+
}
|
|
45
|
+
function describeScheduleSafe(cron) {
|
|
46
|
+
try {
|
|
47
|
+
return cronstrue.toString(cron, { use24HourTimeFormat: false });
|
|
48
|
+
} catch {
|
|
49
|
+
return cron;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function JobList({ jobs, selectedIdx, loading }) {
|
|
53
|
+
if (loading && jobs.length === 0) {
|
|
54
|
+
return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Spinner, { label: "Loading jobs..." }) });
|
|
55
|
+
}
|
|
56
|
+
if (jobs.length === 0) {
|
|
57
|
+
return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No jobs configured. Run 'claude-auto create' to get started." }) });
|
|
58
|
+
}
|
|
59
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [
|
|
60
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
61
|
+
/* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Name" }) }),
|
|
62
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Status" }) }),
|
|
63
|
+
/* @__PURE__ */ jsx(Box, { width: 27, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Schedule" }) }),
|
|
64
|
+
/* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Next Run" }) }),
|
|
65
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Cost" }) })
|
|
66
|
+
] }),
|
|
67
|
+
jobs.map((job, idx) => {
|
|
68
|
+
const isSelected = idx === selectedIdx;
|
|
69
|
+
const statusText = job.enabled ? "active" : "paused";
|
|
70
|
+
const statusColor2 = job.enabled ? "green" : "yellow";
|
|
71
|
+
const schedule = truncate(describeScheduleSafe(job.cron), 25);
|
|
72
|
+
const nextRun = job.nextRun ? formatRelativeTime(job.nextRun) : "paused";
|
|
73
|
+
const cost = formatCost(job.totalCost);
|
|
74
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
75
|
+
/* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, bold: isSelected, children: truncate(job.name, 20) }) }),
|
|
76
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, color: statusColor2, children: statusText }) }),
|
|
77
|
+
/* @__PURE__ */ jsx(Box, { width: 27, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, dimColor: !isSelected, children: schedule }) }),
|
|
78
|
+
/* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, dimColor: !isSelected, children: nextRun }) }),
|
|
79
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, dimColor: !isSelected, children: cost }) })
|
|
80
|
+
] }, job.id);
|
|
81
|
+
})
|
|
82
|
+
] });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/tui/components/job-detail.tsx
|
|
86
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
87
|
+
import cronstrue2 from "cronstrue";
|
|
88
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
89
|
+
function describeScheduleSafe2(cron) {
|
|
90
|
+
try {
|
|
91
|
+
return cronstrue2.toString(cron, { use24HourTimeFormat: false });
|
|
92
|
+
} catch {
|
|
93
|
+
return cron;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function formatDuration(ms) {
|
|
97
|
+
const sec = Math.floor(ms / 1e3);
|
|
98
|
+
if (sec < 60) return `${sec}s`;
|
|
99
|
+
const min = Math.floor(sec / 60);
|
|
100
|
+
if (min < 60) return `${min}m ${sec % 60}s`;
|
|
101
|
+
const hr = Math.floor(min / 60);
|
|
102
|
+
return `${hr}h ${min % 60}m`;
|
|
103
|
+
}
|
|
104
|
+
function JobDetail({ job }) {
|
|
105
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingTop: 1, paddingLeft: 1, children: [
|
|
106
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: job.name }),
|
|
107
|
+
/* @__PURE__ */ jsx2(Text2, { children: " " }),
|
|
108
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
109
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Repository:" }) }),
|
|
110
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.repoPath })
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
113
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Branch:" }) }),
|
|
114
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.branch })
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
117
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Schedule:" }) }),
|
|
118
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
119
|
+
job.cron,
|
|
120
|
+
" (",
|
|
121
|
+
describeScheduleSafe2(job.cron),
|
|
122
|
+
")"
|
|
123
|
+
] })
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
126
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Timezone:" }) }),
|
|
127
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.timezone })
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
130
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Focus:" }) }),
|
|
131
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.focus.join(", ") })
|
|
132
|
+
] }),
|
|
133
|
+
job.model && /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
134
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Model:" }) }),
|
|
135
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.model })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
138
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Status:" }) }),
|
|
139
|
+
/* @__PURE__ */ jsx2(Text2, { color: job.enabled ? "green" : "yellow", children: job.enabled ? "active" : "paused" })
|
|
140
|
+
] }),
|
|
141
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
142
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Budget:" }) }),
|
|
143
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
144
|
+
"$",
|
|
145
|
+
job.maxBudgetUsd.toFixed(2),
|
|
146
|
+
"/run"
|
|
147
|
+
] })
|
|
148
|
+
] }),
|
|
149
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
150
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Total Cost:" }) }),
|
|
151
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
152
|
+
"$",
|
|
153
|
+
job.totalCost.toFixed(2)
|
|
154
|
+
] })
|
|
155
|
+
] }),
|
|
156
|
+
/* @__PURE__ */ jsx2(Text2, { children: " " }),
|
|
157
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "Last Run:" }),
|
|
158
|
+
job.lastRun ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
|
|
159
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
160
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Date:" }) }),
|
|
161
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: new Date(job.lastRun.startedAt).toLocaleString() })
|
|
162
|
+
] }),
|
|
163
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
164
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Status:" }) }),
|
|
165
|
+
/* @__PURE__ */ jsx2(Text2, { color: job.lastRun.status === "success" ? "green" : "red", children: job.lastRun.status })
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
168
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Duration:" }) }),
|
|
169
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: formatDuration(job.lastRun.durationMs) })
|
|
170
|
+
] }),
|
|
171
|
+
job.lastRun.costUsd !== void 0 && /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
172
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Cost:" }) }),
|
|
173
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
174
|
+
"$",
|
|
175
|
+
job.lastRun.costUsd.toFixed(4)
|
|
176
|
+
] })
|
|
177
|
+
] }),
|
|
178
|
+
job.lastRun.summary && /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
179
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Summary:" }) }),
|
|
180
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.lastRun.summary })
|
|
181
|
+
] }),
|
|
182
|
+
job.lastRun.prUrl && /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
183
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "PR:" }) }),
|
|
184
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.lastRun.prUrl })
|
|
185
|
+
] })
|
|
186
|
+
] }) : /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No runs yet" }) }),
|
|
187
|
+
job.nextRun && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
188
|
+
/* @__PURE__ */ jsx2(Text2, { children: " " }),
|
|
189
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
190
|
+
/* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Next Run:" }) }),
|
|
191
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.nextRun.toLocaleString() })
|
|
192
|
+
] })
|
|
193
|
+
] })
|
|
194
|
+
] });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/tui/components/run-log.tsx
|
|
198
|
+
import { useState, useEffect } from "react";
|
|
199
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
200
|
+
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
201
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
202
|
+
function formatDuration2(ms) {
|
|
203
|
+
const sec = Math.floor(ms / 1e3);
|
|
204
|
+
if (sec < 60) return `${sec}s`;
|
|
205
|
+
const min = Math.floor(sec / 60);
|
|
206
|
+
if (min < 60) return `${min}m ${sec % 60}s`;
|
|
207
|
+
const hr = Math.floor(min / 60);
|
|
208
|
+
return `${hr}h ${min % 60}m`;
|
|
209
|
+
}
|
|
210
|
+
function statusColor(status) {
|
|
211
|
+
switch (status) {
|
|
212
|
+
case "success":
|
|
213
|
+
return "green";
|
|
214
|
+
case "no-changes":
|
|
215
|
+
return "yellow";
|
|
216
|
+
case "error":
|
|
217
|
+
case "git-error":
|
|
218
|
+
return "red";
|
|
219
|
+
case "locked":
|
|
220
|
+
case "paused":
|
|
221
|
+
return "gray";
|
|
222
|
+
case "budget-exceeded":
|
|
223
|
+
return "magenta";
|
|
224
|
+
case "merge-conflict":
|
|
225
|
+
return "red";
|
|
226
|
+
case "needs-human-review":
|
|
227
|
+
return "cyan";
|
|
228
|
+
default:
|
|
229
|
+
return "white";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
var VISIBLE_ENTRIES = 10;
|
|
233
|
+
function RunLog({ jobId, scrollOffset = 0 }) {
|
|
234
|
+
const [logs, setLogs] = useState([]);
|
|
235
|
+
const [loading, setLoading] = useState(true);
|
|
236
|
+
const [error, setError] = useState(null);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
let cancelled = false;
|
|
239
|
+
const load = async () => {
|
|
240
|
+
try {
|
|
241
|
+
const entries = await listRunLogs(jobId);
|
|
242
|
+
if (!cancelled) {
|
|
243
|
+
setLogs(entries);
|
|
244
|
+
setLoading(false);
|
|
245
|
+
}
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (!cancelled) {
|
|
248
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
249
|
+
setLoading(false);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
load();
|
|
254
|
+
return () => {
|
|
255
|
+
cancelled = true;
|
|
256
|
+
};
|
|
257
|
+
}, [jobId]);
|
|
258
|
+
if (loading) {
|
|
259
|
+
return /* @__PURE__ */ jsx3(Box3, { padding: 1, children: /* @__PURE__ */ jsx3(Spinner2, { label: "Loading run logs..." }) });
|
|
260
|
+
}
|
|
261
|
+
if (error) {
|
|
262
|
+
return /* @__PURE__ */ jsx3(Box3, { padding: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
263
|
+
"Error loading logs: ",
|
|
264
|
+
error
|
|
265
|
+
] }) });
|
|
266
|
+
}
|
|
267
|
+
if (logs.length === 0) {
|
|
268
|
+
return /* @__PURE__ */ jsx3(Box3, { padding: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No run history for this job." }) });
|
|
269
|
+
}
|
|
270
|
+
const clampedOffset = Math.min(scrollOffset, Math.max(0, logs.length - VISIBLE_ENTRIES));
|
|
271
|
+
const visibleLogs = logs.slice(clampedOffset, clampedOffset + VISIBLE_ENTRIES);
|
|
272
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingTop: 1, paddingLeft: 1, children: [
|
|
273
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
|
|
274
|
+
"Run History (",
|
|
275
|
+
logs.length,
|
|
276
|
+
" runs, showing ",
|
|
277
|
+
clampedOffset + 1,
|
|
278
|
+
"-",
|
|
279
|
+
clampedOffset + visibleLogs.length,
|
|
280
|
+
")"
|
|
281
|
+
] }),
|
|
282
|
+
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
283
|
+
visibleLogs.map((log) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
|
|
284
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
285
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: new Date(log.startedAt).toLocaleString() }),
|
|
286
|
+
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
287
|
+
/* @__PURE__ */ jsx3(Text3, { color: statusColor(log.status), bold: true, children: log.status }),
|
|
288
|
+
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
289
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: formatDuration2(log.durationMs) }),
|
|
290
|
+
log.costUsd !== void 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
291
|
+
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
292
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
293
|
+
"$",
|
|
294
|
+
log.costUsd.toFixed(4)
|
|
295
|
+
] })
|
|
296
|
+
] })
|
|
297
|
+
] }),
|
|
298
|
+
log.summary && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: log.summary }) }),
|
|
299
|
+
log.error && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { color: "red", children: log.error }) }),
|
|
300
|
+
log.prUrl && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
301
|
+
"PR: ",
|
|
302
|
+
log.prUrl
|
|
303
|
+
] }) })
|
|
304
|
+
] }, log.runId)),
|
|
305
|
+
logs.length > VISIBLE_ENTRIES && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Use j/k or up/down to scroll" })
|
|
306
|
+
] });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/tui/components/status-bar.tsx
|
|
310
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
311
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
312
|
+
var HINTS = {
|
|
313
|
+
list: "up/down navigate | Enter detail | p pause/resume | l logs | q quit",
|
|
314
|
+
detail: "Esc back | p pause/resume | l logs | q quit",
|
|
315
|
+
logs: "Esc back | j/k scroll | q quit"
|
|
316
|
+
};
|
|
317
|
+
function StatusBar({ view }) {
|
|
318
|
+
return /* @__PURE__ */ jsx4(Box4, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: HINTS[view] }) });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/tui/hooks/use-jobs.ts
|
|
322
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
323
|
+
async function loadJobsWithMeta() {
|
|
324
|
+
const jobs = await listJobs();
|
|
325
|
+
const costMap = /* @__PURE__ */ new Map();
|
|
326
|
+
try {
|
|
327
|
+
const costRows = getCostSummary();
|
|
328
|
+
for (const row of costRows) {
|
|
329
|
+
costMap.set(row.job_id, row.total_cost);
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
const results = [];
|
|
334
|
+
for (const job of jobs) {
|
|
335
|
+
let lastRun = null;
|
|
336
|
+
try {
|
|
337
|
+
const logs = await listRunLogs(job.id);
|
|
338
|
+
if (logs.length > 0) {
|
|
339
|
+
lastRun = logs[0];
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
let nextRun = null;
|
|
344
|
+
try {
|
|
345
|
+
if (job.enabled) {
|
|
346
|
+
const runs = getNextRuns(job.schedule.cron, job.schedule.timezone, 1);
|
|
347
|
+
if (runs.length > 0) {
|
|
348
|
+
nextRun = runs[0];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
results.push({
|
|
354
|
+
id: job.id,
|
|
355
|
+
name: job.name,
|
|
356
|
+
repoPath: job.repo.path,
|
|
357
|
+
branch: job.repo.branch,
|
|
358
|
+
cron: job.schedule.cron,
|
|
359
|
+
timezone: job.schedule.timezone,
|
|
360
|
+
focus: job.focus,
|
|
361
|
+
enabled: job.enabled,
|
|
362
|
+
model: job.model,
|
|
363
|
+
maxBudgetUsd: job.guardrails.maxBudgetUsd,
|
|
364
|
+
lastRun,
|
|
365
|
+
nextRun,
|
|
366
|
+
totalCost: costMap.get(job.id) ?? 0
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return results;
|
|
370
|
+
}
|
|
371
|
+
function useJobs(pollIntervalMs = 3e3) {
|
|
372
|
+
const [jobs, setJobs] = useState2([]);
|
|
373
|
+
const [loading, setLoading] = useState2(true);
|
|
374
|
+
const [error, setError] = useState2(null);
|
|
375
|
+
const [_refreshTick, setRefreshTick] = useState2(0);
|
|
376
|
+
const refresh = () => setRefreshTick((t) => t + 1);
|
|
377
|
+
useEffect2(() => {
|
|
378
|
+
let cancelled = false;
|
|
379
|
+
const load = async () => {
|
|
380
|
+
try {
|
|
381
|
+
const data = await loadJobsWithMeta();
|
|
382
|
+
if (!cancelled) {
|
|
383
|
+
setJobs(data);
|
|
384
|
+
setLoading(false);
|
|
385
|
+
setError(null);
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
388
|
+
if (!cancelled) {
|
|
389
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
390
|
+
setLoading(false);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
load();
|
|
395
|
+
const timer = setInterval(load, pollIntervalMs);
|
|
396
|
+
return () => {
|
|
397
|
+
cancelled = true;
|
|
398
|
+
clearInterval(timer);
|
|
399
|
+
};
|
|
400
|
+
}, [pollIntervalMs]);
|
|
401
|
+
return { jobs, loading, error, refresh };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/tui/hooks/use-keyboard.ts
|
|
405
|
+
import { useApp, useInput } from "ink";
|
|
406
|
+
import { useCallback, useState as useState3 } from "react";
|
|
407
|
+
function useKeyboard(jobCount) {
|
|
408
|
+
const { exit } = useApp();
|
|
409
|
+
const [view, setView] = useState3("list");
|
|
410
|
+
const [selectedIdx, setSelectedIdx] = useState3(0);
|
|
411
|
+
const [scrollOffset, setScrollOffset] = useState3(0);
|
|
412
|
+
const [pendingPauseResume, setPendingPauseResume] = useState3(false);
|
|
413
|
+
useInput((input, key) => {
|
|
414
|
+
if (input === "q") {
|
|
415
|
+
exit();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (key.upArrow) {
|
|
419
|
+
if (view === "logs") {
|
|
420
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
421
|
+
} else {
|
|
422
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (key.downArrow) {
|
|
427
|
+
if (view === "logs") {
|
|
428
|
+
setScrollOffset((o) => o + 1);
|
|
429
|
+
} else {
|
|
430
|
+
setSelectedIdx((i) => Math.min(Math.max(0, jobCount - 1), i + 1));
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (key.return && view === "list") {
|
|
435
|
+
setView("detail");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (key.escape && view !== "list") {
|
|
439
|
+
setView("list");
|
|
440
|
+
setScrollOffset(0);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (input === "p" && view !== "logs") {
|
|
444
|
+
setPendingPauseResume(true);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (input === "l" && view !== "logs") {
|
|
448
|
+
setView("logs");
|
|
449
|
+
setScrollOffset(0);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (view === "logs") {
|
|
453
|
+
if (input === "j") {
|
|
454
|
+
setScrollOffset((o) => o + 1);
|
|
455
|
+
} else if (input === "k") {
|
|
456
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
const getSelectedJobId = useCallback(
|
|
461
|
+
(jobs) => {
|
|
462
|
+
if (jobs.length === 0 || selectedIdx >= jobs.length) return null;
|
|
463
|
+
return jobs[selectedIdx].id;
|
|
464
|
+
},
|
|
465
|
+
[selectedIdx]
|
|
466
|
+
);
|
|
467
|
+
const pauseResume = useCallback(
|
|
468
|
+
(jobs) => {
|
|
469
|
+
if (!pendingPauseResume) return null;
|
|
470
|
+
setPendingPauseResume(false);
|
|
471
|
+
return getSelectedJobId(jobs);
|
|
472
|
+
},
|
|
473
|
+
[pendingPauseResume, getSelectedJobId]
|
|
474
|
+
);
|
|
475
|
+
return {
|
|
476
|
+
view,
|
|
477
|
+
selectedIdx,
|
|
478
|
+
scrollOffset,
|
|
479
|
+
actions: {
|
|
480
|
+
pauseResume,
|
|
481
|
+
getSelectedJobId
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/tui/app.tsx
|
|
487
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
488
|
+
function App() {
|
|
489
|
+
const { jobs, loading, error, refresh } = useJobs();
|
|
490
|
+
const { view, selectedIdx, scrollOffset, actions } = useKeyboard(jobs.length);
|
|
491
|
+
const [statusMessage, setStatusMessage] = useState4(null);
|
|
492
|
+
const jobIdToPauseResume = actions.pauseResume(jobs);
|
|
493
|
+
if (jobIdToPauseResume) {
|
|
494
|
+
handlePauseResume(jobIdToPauseResume);
|
|
495
|
+
}
|
|
496
|
+
async function handlePauseResume(jobId) {
|
|
497
|
+
const job = jobs.find((j) => j.id === jobId);
|
|
498
|
+
if (!job) return;
|
|
499
|
+
try {
|
|
500
|
+
if (job.enabled) {
|
|
501
|
+
const { pauseCommand } = await import("./pause-2YOLFMAR.js");
|
|
502
|
+
await pauseCommand({ jobId });
|
|
503
|
+
setStatusMessage(`Paused: ${job.name}`);
|
|
504
|
+
} else {
|
|
505
|
+
const { resumeCommand } = await import("./resume-3ATNZP6D.js");
|
|
506
|
+
await resumeCommand({ jobId });
|
|
507
|
+
setStatusMessage(`Resumed: ${job.name}`);
|
|
508
|
+
}
|
|
509
|
+
refresh();
|
|
510
|
+
} catch (err) {
|
|
511
|
+
setStatusMessage(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
512
|
+
}
|
|
513
|
+
setTimeout(() => setStatusMessage(null), 3e3);
|
|
514
|
+
}
|
|
515
|
+
const selectedJob = jobs.length > 0 && selectedIdx < jobs.length ? jobs[selectedIdx] : null;
|
|
516
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
517
|
+
/* @__PURE__ */ jsxs4(Box5, { paddingX: 1, children: [
|
|
518
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "claude-auto dashboard" }),
|
|
519
|
+
loading && jobs.length > 0 && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " (refreshing...)" })
|
|
520
|
+
] }),
|
|
521
|
+
error && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text5, { color: "red", children: [
|
|
522
|
+
"Error: ",
|
|
523
|
+
error
|
|
524
|
+
] }) }),
|
|
525
|
+
statusMessage && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: statusMessage }) }),
|
|
526
|
+
view === "list" && /* @__PURE__ */ jsx5(JobList, { jobs, selectedIdx, loading }),
|
|
527
|
+
view === "detail" && selectedJob && /* @__PURE__ */ jsx5(JobDetail, { job: selectedJob }),
|
|
528
|
+
view === "logs" && selectedJob && /* @__PURE__ */ jsx5(RunLog, { jobId: selectedJob.id, scrollOffset }),
|
|
529
|
+
/* @__PURE__ */ jsx5(StatusBar, { view })
|
|
530
|
+
] });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/tui/index.tsx
|
|
534
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
535
|
+
async function launchDashboard() {
|
|
536
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx6(App, {}));
|
|
537
|
+
await waitUntilExit();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export {
|
|
541
|
+
launchDashboard
|
|
542
|
+
};
|
|
543
|
+
//# sourceMappingURL=chunk-LBH6SLHH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tui/index.tsx","../src/tui/app.tsx","../src/tui/components/job-list.tsx","../src/tui/components/job-detail.tsx","../src/tui/components/run-log.tsx","../src/tui/components/status-bar.tsx","../src/tui/hooks/use-jobs.ts","../src/tui/hooks/use-keyboard.ts"],"sourcesContent":["import React from \"react\";\nimport { render } from \"ink\";\nimport { App } from \"./app.js\";\n\n/**\n * Launch the interactive TUI dashboard.\n * Renders the App component via ink and blocks until the user quits.\n */\nexport async function launchDashboard(): Promise<void> {\n\tconst { waitUntilExit } = render(<App />);\n\tawait waitUntilExit();\n}\n","import { useState, useCallback } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { JobList } from \"./components/job-list.js\";\nimport { JobDetail } from \"./components/job-detail.js\";\nimport { RunLog } from \"./components/run-log.js\";\nimport { StatusBar } from \"./components/status-bar.js\";\nimport { useJobs } from \"./hooks/use-jobs.js\";\nimport { useKeyboard } from \"./hooks/use-keyboard.js\";\n\n/**\n * Root App component for the TUI dashboard.\n * Routes between list, detail, and log views.\n * Handles pause/resume via dynamic import of CLI commands.\n */\nexport function App() {\n\tconst { jobs, loading, error, refresh } = useJobs();\n\tconst { view, selectedIdx, scrollOffset, actions } = useKeyboard(jobs.length);\n\tconst [statusMessage, setStatusMessage] = useState<string | null>(null);\n\n\t// Handle pause/resume action from keyboard\n\tconst jobIdToPauseResume = actions.pauseResume(jobs);\n\tif (jobIdToPauseResume) {\n\t\thandlePauseResume(jobIdToPauseResume);\n\t}\n\n\tasync function handlePauseResume(jobId: string): Promise<void> {\n\t\tconst job = jobs.find((j) => j.id === jobId);\n\t\tif (!job) return;\n\n\t\ttry {\n\t\t\tif (job.enabled) {\n\t\t\t\tconst { pauseCommand } = await import(\"../cli/commands/pause.js\");\n\t\t\t\tawait pauseCommand({ jobId });\n\t\t\t\tsetStatusMessage(`Paused: ${job.name}`);\n\t\t\t} else {\n\t\t\t\tconst { resumeCommand } = await import(\"../cli/commands/resume.js\");\n\t\t\t\tawait resumeCommand({ jobId });\n\t\t\t\tsetStatusMessage(`Resumed: ${job.name}`);\n\t\t\t}\n\t\t\trefresh();\n\t\t} catch (err: unknown) {\n\t\t\tsetStatusMessage(`Error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\n\t\t// Clear status message after 3 seconds\n\t\tsetTimeout(() => setStatusMessage(null), 3000);\n\t}\n\n\tconst selectedJob = jobs.length > 0 && selectedIdx < jobs.length ? jobs[selectedIdx] : null;\n\n\treturn (\n\t\t<Box flexDirection=\"column\">\n\t\t\t{/* Header */}\n\t\t\t<Box paddingX={1}>\n\t\t\t\t<Text bold color=\"cyan\">claude-auto dashboard</Text>\n\t\t\t\t{loading && jobs.length > 0 && <Text dimColor> (refreshing...)</Text>}\n\t\t\t</Box>\n\n\t\t\t{/* Error display */}\n\t\t\t{error && (\n\t\t\t\t<Box paddingX={1}>\n\t\t\t\t\t<Text color=\"red\">Error: {error}</Text>\n\t\t\t\t</Box>\n\t\t\t)}\n\n\t\t\t{/* Status message */}\n\t\t\t{statusMessage && (\n\t\t\t\t<Box paddingX={1}>\n\t\t\t\t\t<Text color=\"yellow\">{statusMessage}</Text>\n\t\t\t\t</Box>\n\t\t\t)}\n\n\t\t\t{/* Main content area */}\n\t\t\t{view === \"list\" && (\n\t\t\t\t<JobList jobs={jobs} selectedIdx={selectedIdx} loading={loading} />\n\t\t\t)}\n\t\t\t{view === \"detail\" && selectedJob && (\n\t\t\t\t<JobDetail job={selectedJob} />\n\t\t\t)}\n\t\t\t{view === \"logs\" && selectedJob && (\n\t\t\t\t<RunLog jobId={selectedJob.id} scrollOffset={scrollOffset} />\n\t\t\t)}\n\n\t\t\t{/* Footer */}\n\t\t\t<StatusBar view={view} />\n\t\t</Box>\n\t);\n}\n","import { Box, Text } from \"ink\";\nimport { Spinner } from \"@inkjs/ui\";\nimport type { JobWithMeta } from \"../hooks/use-jobs.js\";\nimport cronstrue from \"cronstrue\";\n\ninterface JobListProps {\n\tjobs: JobWithMeta[];\n\tselectedIdx: number;\n\tloading: boolean;\n}\n\nfunction formatRelativeTime(date: Date): string {\n\tconst now = Date.now();\n\tconst diffMs = date.getTime() - now;\n\tif (diffMs < 0) return \"overdue\";\n\n\tconst diffMin = Math.floor(diffMs / 60000);\n\tif (diffMin < 60) return `in ${diffMin}m`;\n\n\tconst diffHr = Math.floor(diffMin / 60);\n\tif (diffHr < 24) return `in ${diffHr}h`;\n\n\tconst diffDay = Math.floor(diffHr / 24);\n\treturn `in ${diffDay}d`;\n}\n\nfunction formatCost(cost: number): string {\n\treturn `$${cost.toFixed(2)}`;\n}\n\nfunction truncate(str: string, max: number): string {\n\tif (str.length <= max) return str;\n\treturn str.slice(0, max - 1) + \"\\u2026\";\n}\n\nfunction describeScheduleSafe(cron: string): string {\n\ttry {\n\t\treturn cronstrue.toString(cron, { use24HourTimeFormat: false });\n\t} catch {\n\t\treturn cron;\n\t}\n}\n\n/**\n * Job list table with columns: Name, Status, Schedule, Next Run, Cost.\n * Highlights the selected row with inverse/bold styling.\n */\nexport function JobList({ jobs, selectedIdx, loading }: JobListProps) {\n\tif (loading && jobs.length === 0) {\n\t\treturn (\n\t\t\t<Box padding={1}>\n\t\t\t\t<Spinner label=\"Loading jobs...\" />\n\t\t\t</Box>\n\t\t);\n\t}\n\n\tif (jobs.length === 0) {\n\t\treturn (\n\t\t\t<Box padding={1}>\n\t\t\t\t<Text dimColor>No jobs configured. Run 'claude-auto create' to get started.</Text>\n\t\t\t</Box>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Box flexDirection=\"column\" paddingTop={1}>\n\t\t\t{/* Header */}\n\t\t\t<Box>\n\t\t\t\t<Box width={22}><Text bold underline>Name</Text></Box>\n\t\t\t\t<Box width={10}><Text bold underline>Status</Text></Box>\n\t\t\t\t<Box width={27}><Text bold underline>Schedule</Text></Box>\n\t\t\t\t<Box width={14}><Text bold underline>Next Run</Text></Box>\n\t\t\t\t<Box width={10}><Text bold underline>Cost</Text></Box>\n\t\t\t</Box>\n\n\t\t\t{/* Rows */}\n\t\t\t{jobs.map((job, idx) => {\n\t\t\t\tconst isSelected = idx === selectedIdx;\n\t\t\t\tconst statusText = job.enabled ? \"active\" : \"paused\";\n\t\t\t\tconst statusColor = job.enabled ? \"green\" : \"yellow\";\n\t\t\t\tconst schedule = truncate(describeScheduleSafe(job.cron), 25);\n\t\t\t\tconst nextRun = job.nextRun ? formatRelativeTime(job.nextRun) : \"paused\";\n\t\t\t\tconst cost = formatCost(job.totalCost);\n\n\t\t\t\treturn (\n\t\t\t\t\t<Box key={job.id}>\n\t\t\t\t\t\t<Box width={22}>\n\t\t\t\t\t\t\t<Text inverse={isSelected} bold={isSelected}>\n\t\t\t\t\t\t\t\t{truncate(job.name, 20)}\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t\t<Box width={10}>\n\t\t\t\t\t\t\t<Text inverse={isSelected} color={statusColor}>\n\t\t\t\t\t\t\t\t{statusText}\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t\t<Box width={27}>\n\t\t\t\t\t\t\t<Text inverse={isSelected} dimColor={!isSelected}>\n\t\t\t\t\t\t\t\t{schedule}\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t\t<Box width={14}>\n\t\t\t\t\t\t\t<Text inverse={isSelected} dimColor={!isSelected}>\n\t\t\t\t\t\t\t\t{nextRun}\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t\t<Box width={10}>\n\t\t\t\t\t\t\t<Text inverse={isSelected} dimColor={!isSelected}>\n\t\t\t\t\t\t\t\t{cost}\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t</Box>\n\t\t\t\t);\n\t\t\t})}\n\t\t</Box>\n\t);\n}\n","import { Box, Text } from \"ink\";\nimport type { JobWithMeta } from \"../hooks/use-jobs.js\";\nimport cronstrue from \"cronstrue\";\n\ninterface JobDetailProps {\n\tjob: JobWithMeta;\n}\n\nfunction describeScheduleSafe(cron: string): string {\n\ttry {\n\t\treturn cronstrue.toString(cron, { use24HourTimeFormat: false });\n\t} catch {\n\t\treturn cron;\n\t}\n}\n\nfunction formatDuration(ms: number): string {\n\tconst sec = Math.floor(ms / 1000);\n\tif (sec < 60) return `${sec}s`;\n\tconst min = Math.floor(sec / 60);\n\tif (min < 60) return `${min}m ${sec % 60}s`;\n\tconst hr = Math.floor(min / 60);\n\treturn `${hr}h ${min % 60}m`;\n}\n\n/**\n * Single job detail view showing config, recent run info, and cost.\n */\nexport function JobDetail({ job }: JobDetailProps) {\n\treturn (\n\t\t<Box flexDirection=\"column\" paddingTop={1} paddingLeft={1}>\n\t\t\t<Text bold color=\"cyan\">{job.name}</Text>\n\t\t\t<Text> </Text>\n\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Repository:</Text></Box>\n\t\t\t\t<Text dimColor>{job.repoPath}</Text>\n\t\t\t</Box>\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Branch:</Text></Box>\n\t\t\t\t<Text dimColor>{job.branch}</Text>\n\t\t\t</Box>\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Schedule:</Text></Box>\n\t\t\t\t<Text dimColor>{job.cron} ({describeScheduleSafe(job.cron)})</Text>\n\t\t\t</Box>\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Timezone:</Text></Box>\n\t\t\t\t<Text dimColor>{job.timezone}</Text>\n\t\t\t</Box>\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Focus:</Text></Box>\n\t\t\t\t<Text dimColor>{job.focus.join(\", \")}</Text>\n\t\t\t</Box>\n\t\t\t{job.model && (\n\t\t\t\t<Box>\n\t\t\t\t\t<Box width={16}><Text bold>Model:</Text></Box>\n\t\t\t\t\t<Text dimColor>{job.model}</Text>\n\t\t\t\t</Box>\n\t\t\t)}\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Status:</Text></Box>\n\t\t\t\t<Text color={job.enabled ? \"green\" : \"yellow\"}>\n\t\t\t\t\t{job.enabled ? \"active\" : \"paused\"}\n\t\t\t\t</Text>\n\t\t\t</Box>\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Budget:</Text></Box>\n\t\t\t\t<Text dimColor>${job.maxBudgetUsd.toFixed(2)}/run</Text>\n\t\t\t</Box>\n\t\t\t<Box>\n\t\t\t\t<Box width={16}><Text bold>Total Cost:</Text></Box>\n\t\t\t\t<Text dimColor>${job.totalCost.toFixed(2)}</Text>\n\t\t\t</Box>\n\n\t\t\t<Text> </Text>\n\t\t\t<Text bold>Last Run:</Text>\n\t\t\t{job.lastRun ? (\n\t\t\t\t<Box flexDirection=\"column\" paddingLeft={2}>\n\t\t\t\t\t<Box>\n\t\t\t\t\t\t<Box width={14}><Text bold>Date:</Text></Box>\n\t\t\t\t\t\t<Text dimColor>{new Date(job.lastRun.startedAt).toLocaleString()}</Text>\n\t\t\t\t\t</Box>\n\t\t\t\t\t<Box>\n\t\t\t\t\t\t<Box width={14}><Text bold>Status:</Text></Box>\n\t\t\t\t\t\t<Text color={job.lastRun.status === \"success\" ? \"green\" : \"red\"}>\n\t\t\t\t\t\t\t{job.lastRun.status}\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t</Box>\n\t\t\t\t\t<Box>\n\t\t\t\t\t\t<Box width={14}><Text bold>Duration:</Text></Box>\n\t\t\t\t\t\t<Text dimColor>{formatDuration(job.lastRun.durationMs)}</Text>\n\t\t\t\t\t</Box>\n\t\t\t\t\t{job.lastRun.costUsd !== undefined && (\n\t\t\t\t\t\t<Box>\n\t\t\t\t\t\t\t<Box width={14}><Text bold>Cost:</Text></Box>\n\t\t\t\t\t\t\t<Text dimColor>${job.lastRun.costUsd.toFixed(4)}</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t)}\n\t\t\t\t\t{job.lastRun.summary && (\n\t\t\t\t\t\t<Box>\n\t\t\t\t\t\t\t<Box width={14}><Text bold>Summary:</Text></Box>\n\t\t\t\t\t\t\t<Text dimColor>{job.lastRun.summary}</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t)}\n\t\t\t\t\t{job.lastRun.prUrl && (\n\t\t\t\t\t\t<Box>\n\t\t\t\t\t\t\t<Box width={14}><Text bold>PR:</Text></Box>\n\t\t\t\t\t\t\t<Text dimColor>{job.lastRun.prUrl}</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t)}\n\t\t\t\t</Box>\n\t\t\t) : (\n\t\t\t\t<Box paddingLeft={2}>\n\t\t\t\t\t<Text dimColor>No runs yet</Text>\n\t\t\t\t</Box>\n\t\t\t)}\n\n\t\t\t{job.nextRun && (\n\t\t\t\t<>\n\t\t\t\t\t<Text> </Text>\n\t\t\t\t\t<Box>\n\t\t\t\t\t\t<Box width={16}><Text bold>Next Run:</Text></Box>\n\t\t\t\t\t\t<Text dimColor>{job.nextRun.toLocaleString()}</Text>\n\t\t\t\t\t</Box>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</Box>\n\t);\n}\n","import { useState, useEffect } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { Spinner } from \"@inkjs/ui\";\nimport { listRunLogs } from \"../../runner/logger.js\";\nimport type { RunLogEntry } from \"../../runner/types.js\";\n\ninterface RunLogProps {\n\tjobId: string;\n\tscrollOffset?: number;\n}\n\nfunction formatDuration(ms: number): string {\n\tconst sec = Math.floor(ms / 1000);\n\tif (sec < 60) return `${sec}s`;\n\tconst min = Math.floor(sec / 60);\n\tif (min < 60) return `${min}m ${sec % 60}s`;\n\tconst hr = Math.floor(min / 60);\n\treturn `${hr}h ${min % 60}m`;\n}\n\nfunction statusColor(status: string): string {\n\tswitch (status) {\n\t\tcase \"success\":\n\t\t\treturn \"green\";\n\t\tcase \"no-changes\":\n\t\t\treturn \"yellow\";\n\t\tcase \"error\":\n\t\tcase \"git-error\":\n\t\t\treturn \"red\";\n\t\tcase \"locked\":\n\t\tcase \"paused\":\n\t\t\treturn \"gray\";\n\t\tcase \"budget-exceeded\":\n\t\t\treturn \"magenta\";\n\t\tcase \"merge-conflict\":\n\t\t\treturn \"red\";\n\t\tcase \"needs-human-review\":\n\t\t\treturn \"cyan\";\n\t\tdefault:\n\t\t\treturn \"white\";\n\t}\n}\n\nconst VISIBLE_ENTRIES = 10;\n\n/**\n * Scrollable run log history for a single job.\n * Displays date, status, duration, cost, and summary for each run.\n */\nexport function RunLog({ jobId, scrollOffset = 0 }: RunLogProps) {\n\tconst [logs, setLogs] = useState<RunLogEntry[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<string | null>(null);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\n\t\tconst load = async () => {\n\t\t\ttry {\n\t\t\t\tconst entries = await listRunLogs(jobId);\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetLogs(entries);\n\t\t\t\t\tsetLoading(false);\n\t\t\t\t}\n\t\t\t} catch (err: unknown) {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetError(err instanceof Error ? err.message : String(err));\n\t\t\t\t\tsetLoading(false);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tload();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, [jobId]);\n\n\tif (loading) {\n\t\treturn (\n\t\t\t<Box padding={1}>\n\t\t\t\t<Spinner label=\"Loading run logs...\" />\n\t\t\t</Box>\n\t\t);\n\t}\n\n\tif (error) {\n\t\treturn (\n\t\t\t<Box padding={1}>\n\t\t\t\t<Text color=\"red\">Error loading logs: {error}</Text>\n\t\t\t</Box>\n\t\t);\n\t}\n\n\tif (logs.length === 0) {\n\t\treturn (\n\t\t\t<Box padding={1}>\n\t\t\t\t<Text dimColor>No run history for this job.</Text>\n\t\t\t</Box>\n\t\t);\n\t}\n\n\tconst clampedOffset = Math.min(scrollOffset, Math.max(0, logs.length - VISIBLE_ENTRIES));\n\tconst visibleLogs = logs.slice(clampedOffset, clampedOffset + VISIBLE_ENTRIES);\n\n\treturn (\n\t\t<Box flexDirection=\"column\" paddingTop={1} paddingLeft={1}>\n\t\t\t<Text bold>Run History ({logs.length} runs, showing {clampedOffset + 1}-{clampedOffset + visibleLogs.length})</Text>\n\t\t\t<Text> </Text>\n\n\t\t\t{visibleLogs.map((log) => (\n\t\t\t\t<Box key={log.runId} flexDirection=\"column\" marginBottom={1}>\n\t\t\t\t\t<Box>\n\t\t\t\t\t\t<Text dimColor>{new Date(log.startedAt).toLocaleString()}</Text>\n\t\t\t\t\t\t<Text> </Text>\n\t\t\t\t\t\t<Text color={statusColor(log.status)} bold>{log.status}</Text>\n\t\t\t\t\t\t<Text> </Text>\n\t\t\t\t\t\t<Text dimColor>{formatDuration(log.durationMs)}</Text>\n\t\t\t\t\t\t{log.costUsd !== undefined && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<Text> </Text>\n\t\t\t\t\t\t\t\t<Text dimColor>${log.costUsd.toFixed(4)}</Text>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Box>\n\t\t\t\t\t{log.summary && (\n\t\t\t\t\t\t<Box paddingLeft={2}>\n\t\t\t\t\t\t\t<Text dimColor>{log.summary}</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t)}\n\t\t\t\t\t{log.error && (\n\t\t\t\t\t\t<Box paddingLeft={2}>\n\t\t\t\t\t\t\t<Text color=\"red\">{log.error}</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t)}\n\t\t\t\t\t{log.prUrl && (\n\t\t\t\t\t\t<Box paddingLeft={2}>\n\t\t\t\t\t\t\t<Text dimColor>PR: {log.prUrl}</Text>\n\t\t\t\t\t\t</Box>\n\t\t\t\t\t)}\n\t\t\t\t</Box>\n\t\t\t))}\n\n\t\t\t{logs.length > VISIBLE_ENTRIES && (\n\t\t\t\t<Text dimColor>Use j/k or up/down to scroll</Text>\n\t\t\t)}\n\t\t</Box>\n\t);\n}\n","import { Box, Text } from \"ink\";\nimport type { View } from \"../hooks/use-keyboard.js\";\n\ninterface StatusBarProps {\n\tview: View;\n}\n\nconst HINTS: Record<View, string> = {\n\tlist: \"up/down navigate | Enter detail | p pause/resume | l logs | q quit\",\n\tdetail: \"Esc back | p pause/resume | l logs | q quit\",\n\tlogs: \"Esc back | j/k scroll | q quit\",\n};\n\n/**\n * Bottom status bar with keybinding hints based on current view.\n */\nexport function StatusBar({ view }: StatusBarProps) {\n\treturn (\n\t\t<Box borderStyle=\"single\" borderTop borderBottom={false} borderLeft={false} borderRight={false} paddingX={1} marginTop={1}>\n\t\t\t<Text dimColor>{HINTS[view]}</Text>\n\t\t</Box>\n\t);\n}\n","import { useEffect, useState } from \"react\";\nimport { listJobs } from \"../../core/job-manager.js\";\nimport { getNextRuns } from \"../../core/schedule.js\";\nimport { type CostSummaryRow, getCostSummary } from \"../../runner/cost-tracker.js\";\nimport { listRunLogs } from \"../../runner/logger.js\";\nimport type { RunLogEntry } from \"../../runner/types.js\";\n\n/**\n * Extended job data combining config with runtime metadata.\n */\nexport interface JobWithMeta {\n\tid: string;\n\tname: string;\n\trepoPath: string;\n\tbranch: string;\n\tcron: string;\n\ttimezone: string;\n\tfocus: string[];\n\tenabled: boolean;\n\tmodel?: string;\n\tmaxBudgetUsd: number;\n\tlastRun: RunLogEntry | null;\n\tnextRun: Date | null;\n\ttotalCost: number;\n}\n\n/**\n * Load all jobs with combined metadata (cost, last run, next run).\n * Exported separately for direct testing without React hooks.\n */\nexport async function loadJobsWithMeta(): Promise<JobWithMeta[]> {\n\tconst jobs = await listJobs();\n\n\t// Load cost data (best-effort -- DB may not exist)\n\tconst costMap: Map<string, number> = new Map();\n\ttry {\n\t\tconst costRows = getCostSummary() as CostSummaryRow[];\n\t\tfor (const row of costRows) {\n\t\t\tcostMap.set(row.job_id, row.total_cost);\n\t\t}\n\t} catch {\n\t\t// Cost data unavailable -- default to 0\n\t}\n\n\tconst results: JobWithMeta[] = [];\n\n\tfor (const job of jobs) {\n\t\t// Get last run (best-effort)\n\t\tlet lastRun: RunLogEntry | null = null;\n\t\ttry {\n\t\t\tconst logs = await listRunLogs(job.id);\n\t\t\tif (logs.length > 0) {\n\t\t\t\tlastRun = logs[0]; // Already sorted newest first\n\t\t\t}\n\t\t} catch {\n\t\t\t// No logs available\n\t\t}\n\n\t\t// Get next run time (best-effort)\n\t\tlet nextRun: Date | null = null;\n\t\ttry {\n\t\t\tif (job.enabled) {\n\t\t\t\tconst runs = getNextRuns(job.schedule.cron, job.schedule.timezone, 1);\n\t\t\t\tif (runs.length > 0) {\n\t\t\t\t\tnextRun = runs[0];\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid cron or timezone\n\t\t}\n\n\t\tresults.push({\n\t\t\tid: job.id,\n\t\t\tname: job.name,\n\t\t\trepoPath: job.repo.path,\n\t\t\tbranch: job.repo.branch,\n\t\t\tcron: job.schedule.cron,\n\t\t\ttimezone: job.schedule.timezone,\n\t\t\tfocus: job.focus,\n\t\t\tenabled: job.enabled,\n\t\t\tmodel: job.model,\n\t\t\tmaxBudgetUsd: job.guardrails.maxBudgetUsd,\n\t\t\tlastRun,\n\t\t\tnextRun,\n\t\t\ttotalCost: costMap.get(job.id) ?? 0,\n\t\t});\n\t}\n\n\treturn results;\n}\n\n/**\n * React hook that polls job data at a configurable interval.\n *\n * @param pollIntervalMs - Polling interval in milliseconds (default 3000)\n * @returns Jobs with metadata, loading state, and any error message\n */\nexport function useJobs(pollIntervalMs = 3000): {\n\tjobs: JobWithMeta[];\n\tloading: boolean;\n\terror: string | null;\n\trefresh: () => void;\n} {\n\tconst [jobs, setJobs] = useState<JobWithMeta[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst [_refreshTick, setRefreshTick] = useState(0);\n\n\tconst refresh = () => setRefreshTick((t) => t + 1);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\n\t\tconst load = async () => {\n\t\t\ttry {\n\t\t\t\tconst data = await loadJobsWithMeta();\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetJobs(data);\n\t\t\t\t\tsetLoading(false);\n\t\t\t\t\tsetError(null);\n\t\t\t\t}\n\t\t\t} catch (err: unknown) {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetError(err instanceof Error ? err.message : String(err));\n\t\t\t\t\tsetLoading(false);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tload();\n\t\tconst timer = setInterval(load, pollIntervalMs);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [pollIntervalMs]);\n\n\treturn { jobs, loading, error, refresh };\n}\n","import { useApp, useInput } from \"ink\";\nimport { useCallback, useState } from \"react\";\nimport type { JobWithMeta } from \"./use-jobs.js\";\n\n/**\n * Dashboard view states.\n */\nexport type View = \"list\" | \"detail\" | \"logs\";\n\n/**\n * Keyboard navigation state for the TUI dashboard.\n */\nexport interface KeyboardState {\n\tview: View;\n\tselectedIdx: number;\n\tscrollOffset: number;\n}\n\n/**\n * Actions that require caller-side mutation (not pure state changes).\n */\nexport interface KeyboardActions {\n\t/** Returns the selected job ID for pause/resume, or null if no job selected */\n\tpauseResume: (jobs: JobWithMeta[]) => string | null;\n\t/** Returns the selected job ID, or null if no jobs */\n\tgetSelectedJobId: (jobs: JobWithMeta[]) => string | null;\n}\n\n/**\n * Hook for keyboard navigation in the TUI dashboard.\n *\n * Handles arrow keys, Enter, Escape, p (pause/resume), l (logs), q (quit),\n * and j/k for scrolling in log view.\n *\n * @param jobCount - Total number of jobs (for bounds checking)\n * @returns Current navigation state and action helpers\n */\nexport function useKeyboard(jobCount: number): KeyboardState & { actions: KeyboardActions } {\n\tconst { exit } = useApp();\n\tconst [view, setView] = useState<View>(\"list\");\n\tconst [selectedIdx, setSelectedIdx] = useState(0);\n\tconst [scrollOffset, setScrollOffset] = useState(0);\n\tconst [pendingPauseResume, setPendingPauseResume] = useState(false);\n\n\tuseInput((input, key) => {\n\t\tif (input === \"q\") {\n\t\t\texit();\n\t\t\treturn;\n\t\t}\n\n\t\tif (key.upArrow) {\n\t\t\tif (view === \"logs\") {\n\t\t\t\tsetScrollOffset((o) => Math.max(0, o - 1));\n\t\t\t} else {\n\t\t\t\tsetSelectedIdx((i) => Math.max(0, i - 1));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (key.downArrow) {\n\t\t\tif (view === \"logs\") {\n\t\t\t\tsetScrollOffset((o) => o + 1);\n\t\t\t} else {\n\t\t\t\tsetSelectedIdx((i) => Math.min(Math.max(0, jobCount - 1), i + 1));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (key.return && view === \"list\") {\n\t\t\tsetView(\"detail\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (key.escape && view !== \"list\") {\n\t\t\tsetView(\"list\");\n\t\t\tsetScrollOffset(0);\n\t\t\treturn;\n\t\t}\n\n\t\tif (input === \"p\" && view !== \"logs\") {\n\t\t\tsetPendingPauseResume(true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (input === \"l\" && view !== \"logs\") {\n\t\t\tsetView(\"logs\");\n\t\t\tsetScrollOffset(0);\n\t\t\treturn;\n\t\t}\n\n\t\t// j/k scrolling in log view\n\t\tif (view === \"logs\") {\n\t\t\tif (input === \"j\") {\n\t\t\t\tsetScrollOffset((o) => o + 1);\n\t\t\t} else if (input === \"k\") {\n\t\t\t\tsetScrollOffset((o) => Math.max(0, o - 1));\n\t\t\t}\n\t\t}\n\t});\n\n\tconst getSelectedJobId = useCallback(\n\t\t(jobs: JobWithMeta[]): string | null => {\n\t\t\tif (jobs.length === 0 || selectedIdx >= jobs.length) return null;\n\t\t\treturn jobs[selectedIdx].id;\n\t\t},\n\t\t[selectedIdx],\n\t);\n\n\tconst pauseResume = useCallback(\n\t\t(jobs: JobWithMeta[]): string | null => {\n\t\t\tif (!pendingPauseResume) return null;\n\t\t\tsetPendingPauseResume(false);\n\t\t\treturn getSelectedJobId(jobs);\n\t\t},\n\t\t[pendingPauseResume, getSelectedJobId],\n\t);\n\n\treturn {\n\t\tview,\n\t\tselectedIdx,\n\t\tscrollOffset,\n\t\tactions: {\n\t\t\tpauseResume,\n\t\t\tgetSelectedJobId,\n\t\t},\n\t};\n}\n"],"mappings":"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","names":["useState","Box","Text","statusColor","Box","Text","cronstrue","jsx","jsxs","describeScheduleSafe","Box","Text","Spinner","Fragment","jsx","jsxs","formatDuration","Box","Spinner","Text","Box","Text","jsx","useEffect","useState","useState","useEffect","useState","jsx","jsxs","useState","Box","Text","jsx"]}
|