@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.
Files changed (159) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +435 -0
  4. package/dist/check-repo-6C4QI2M2.js +33 -0
  5. package/dist/check-repo-6C4QI2M2.js.map +1 -0
  6. package/dist/check-repo-SXWFIVO5.js +8 -0
  7. package/dist/check-repo-SXWFIVO5.js.map +1 -0
  8. package/dist/chunk-24PS2XSV.js +203 -0
  9. package/dist/chunk-24PS2XSV.js.map +1 -0
  10. package/dist/chunk-2D5E23XA.js +129 -0
  11. package/dist/chunk-2D5E23XA.js.map +1 -0
  12. package/dist/chunk-3NEANSUS.js +26 -0
  13. package/dist/chunk-3NEANSUS.js.map +1 -0
  14. package/dist/chunk-4I5UIASZ.js +71 -0
  15. package/dist/chunk-4I5UIASZ.js.map +1 -0
  16. package/dist/chunk-5LGOK52J.js +38 -0
  17. package/dist/chunk-5LGOK52J.js.map +1 -0
  18. package/dist/chunk-6RYMWH5M.js +35 -0
  19. package/dist/chunk-6RYMWH5M.js.map +1 -0
  20. package/dist/chunk-A6XWZPLY.js +56 -0
  21. package/dist/chunk-A6XWZPLY.js.map +1 -0
  22. package/dist/chunk-AWLSYOVF.js +61 -0
  23. package/dist/chunk-AWLSYOVF.js.map +1 -0
  24. package/dist/chunk-BY5YEOVG.js +75 -0
  25. package/dist/chunk-BY5YEOVG.js.map +1 -0
  26. package/dist/chunk-D4MBOIYQ.js +46 -0
  27. package/dist/chunk-D4MBOIYQ.js.map +1 -0
  28. package/dist/chunk-DVZC42TL.js +33 -0
  29. package/dist/chunk-DVZC42TL.js.map +1 -0
  30. package/dist/chunk-E3XVLTT4.js +13 -0
  31. package/dist/chunk-E3XVLTT4.js.map +1 -0
  32. package/dist/chunk-GLW7T4QE.js +116 -0
  33. package/dist/chunk-GLW7T4QE.js.map +1 -0
  34. package/dist/chunk-H2MUDYMW.js +23 -0
  35. package/dist/chunk-H2MUDYMW.js.map +1 -0
  36. package/dist/chunk-HF7PGQI3.js +69 -0
  37. package/dist/chunk-HF7PGQI3.js.map +1 -0
  38. package/dist/chunk-LBH6SLHH.js +543 -0
  39. package/dist/chunk-LBH6SLHH.js.map +1 -0
  40. package/dist/chunk-M53MPY3U.js +115 -0
  41. package/dist/chunk-M53MPY3U.js.map +1 -0
  42. package/dist/chunk-MI7OZ5XD.js +146 -0
  43. package/dist/chunk-MI7OZ5XD.js.map +1 -0
  44. package/dist/chunk-NB46PEG2.js +177 -0
  45. package/dist/chunk-NB46PEG2.js.map +1 -0
  46. package/dist/chunk-ORBF5IW3.js +60 -0
  47. package/dist/chunk-ORBF5IW3.js.map +1 -0
  48. package/dist/chunk-PFU5YLRH.js +131 -0
  49. package/dist/chunk-PFU5YLRH.js.map +1 -0
  50. package/dist/chunk-QLRCFKLU.js +34 -0
  51. package/dist/chunk-QLRCFKLU.js.map +1 -0
  52. package/dist/chunk-QQTIJN3S.js +167 -0
  53. package/dist/chunk-QQTIJN3S.js.map +1 -0
  54. package/dist/chunk-QRYCNVLT.js +72 -0
  55. package/dist/chunk-QRYCNVLT.js.map +1 -0
  56. package/dist/chunk-S6E67XMR.js +52 -0
  57. package/dist/chunk-S6E67XMR.js.map +1 -0
  58. package/dist/chunk-S6W4SURF.js +33 -0
  59. package/dist/chunk-S6W4SURF.js.map +1 -0
  60. package/dist/chunk-SMZYA6CY.js +121 -0
  61. package/dist/chunk-SMZYA6CY.js.map +1 -0
  62. package/dist/chunk-SNOA575X.js +12 -0
  63. package/dist/chunk-SNOA575X.js.map +1 -0
  64. package/dist/chunk-SZRIZBWI.js +44 -0
  65. package/dist/chunk-SZRIZBWI.js.map +1 -0
  66. package/dist/chunk-TAGHPCFT.js +47 -0
  67. package/dist/chunk-TAGHPCFT.js.map +1 -0
  68. package/dist/chunk-TGKCHHXT.js +34 -0
  69. package/dist/chunk-TGKCHHXT.js.map +1 -0
  70. package/dist/chunk-TORYFKPK.js +39 -0
  71. package/dist/chunk-TORYFKPK.js.map +1 -0
  72. package/dist/chunk-U35GRLBD.js +143 -0
  73. package/dist/chunk-U35GRLBD.js.map +1 -0
  74. package/dist/chunk-W2HBRERV.js +57 -0
  75. package/dist/chunk-W2HBRERV.js.map +1 -0
  76. package/dist/chunk-WYU476R2.js +119 -0
  77. package/dist/chunk-WYU476R2.js.map +1 -0
  78. package/dist/chunk-YMO45Z6G.js +69 -0
  79. package/dist/chunk-YMO45Z6G.js.map +1 -0
  80. package/dist/claude-auto-run.js +1717 -0
  81. package/dist/claude-auto-run.js.map +1 -0
  82. package/dist/claude-auto.js +186 -0
  83. package/dist/claude-auto.js.map +1 -0
  84. package/dist/cost-QGM3D4QW.js +72 -0
  85. package/dist/cost-QGM3D4QW.js.map +1 -0
  86. package/dist/cost-QKN3U7AG.js +11 -0
  87. package/dist/cost-QKN3U7AG.js.map +1 -0
  88. package/dist/create-T3BDDS6G.js +14 -0
  89. package/dist/create-T3BDDS6G.js.map +1 -0
  90. package/dist/create-U5WYKTD4.js +118 -0
  91. package/dist/create-U5WYKTD4.js.map +1 -0
  92. package/dist/crontab-CDMC2FDT.js +118 -0
  93. package/dist/crontab-CDMC2FDT.js.map +1 -0
  94. package/dist/crontab-MAJ52FOK.js +118 -0
  95. package/dist/crontab-MAJ52FOK.js.map +1 -0
  96. package/dist/crontab-PNEWANLW.js +12 -0
  97. package/dist/crontab-PNEWANLW.js.map +1 -0
  98. package/dist/edit-77E3ZQHM.js +134 -0
  99. package/dist/edit-77E3ZQHM.js.map +1 -0
  100. package/dist/edit-RVPRAAQ2.js +13 -0
  101. package/dist/edit-RVPRAAQ2.js.map +1 -0
  102. package/dist/index.d.ts +1137 -0
  103. package/dist/index.js +2049 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/launchd-7F27BIZB.js +166 -0
  106. package/dist/launchd-7F27BIZB.js.map +1 -0
  107. package/dist/launchd-HNZIWLNC.js +166 -0
  108. package/dist/launchd-HNZIWLNC.js.map +1 -0
  109. package/dist/launchd-LZGDP7BM.js +12 -0
  110. package/dist/launchd-LZGDP7BM.js.map +1 -0
  111. package/dist/list-OIGERGYJ.js +15 -0
  112. package/dist/list-OIGERGYJ.js.map +1 -0
  113. package/dist/list-T35RSQVU.js +73 -0
  114. package/dist/list-T35RSQVU.js.map +1 -0
  115. package/dist/logs-D5FNSCXE.js +12 -0
  116. package/dist/logs-D5FNSCXE.js.map +1 -0
  117. package/dist/logs-YVSFXBSB.js +40 -0
  118. package/dist/logs-YVSFXBSB.js.map +1 -0
  119. package/dist/pause-2YOLFMAR.js +12 -0
  120. package/dist/pause-2YOLFMAR.js.map +1 -0
  121. package/dist/pause-JB42JGTB.js +45 -0
  122. package/dist/pause-JB42JGTB.js.map +1 -0
  123. package/dist/pause-OJNUYBCJ.js +47 -0
  124. package/dist/pause-OJNUYBCJ.js.map +1 -0
  125. package/dist/remove-RXYKFYBI.js +12 -0
  126. package/dist/remove-RXYKFYBI.js.map +1 -0
  127. package/dist/remove-UASXZCOR.js +59 -0
  128. package/dist/remove-UASXZCOR.js.map +1 -0
  129. package/dist/report-CHAJH2SA.js +150 -0
  130. package/dist/report-CHAJH2SA.js.map +1 -0
  131. package/dist/report-IYGK5HTC.js +14 -0
  132. package/dist/report-IYGK5HTC.js.map +1 -0
  133. package/dist/resume-3ATNZP6D.js +13 -0
  134. package/dist/resume-3ATNZP6D.js.map +1 -0
  135. package/dist/resume-6WVGU6XW.js +48 -0
  136. package/dist/resume-6WVGU6XW.js.map +1 -0
  137. package/dist/resume-JVTR7OEX.js +50 -0
  138. package/dist/resume-JVTR7OEX.js.map +1 -0
  139. package/dist/schtasks-2EQAD3ES.js +11 -0
  140. package/dist/schtasks-2EQAD3ES.js.map +1 -0
  141. package/dist/schtasks-4V2IFD3A.js +142 -0
  142. package/dist/schtasks-4V2IFD3A.js.map +1 -0
  143. package/dist/schtasks-JGEPEKQS.js +142 -0
  144. package/dist/schtasks-JGEPEKQS.js.map +1 -0
  145. package/dist/tui-2DUPCX3Q.js +15 -0
  146. package/dist/tui-2DUPCX3Q.js.map +1 -0
  147. package/dist/tui-6LOGPILA.js +547 -0
  148. package/dist/tui-6LOGPILA.js.map +1 -0
  149. package/package.json +81 -0
  150. package/scripts/postinstall.mjs +65 -0
  151. package/scripts/preuninstall.mjs +33 -0
  152. package/skills/edit/SKILL.md +25 -0
  153. package/skills/list/SKILL.md +26 -0
  154. package/skills/logs/SKILL.md +33 -0
  155. package/skills/pause/SKILL.md +21 -0
  156. package/skills/remove/SKILL.md +22 -0
  157. package/skills/resume/SKILL.md +21 -0
  158. package/skills/setup/SKILL.md +195 -0
  159. package/skills/status/SKILL.md +27 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/schtasks.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CronExpressionParser } from \"cron-parser\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nconst TASK_PREFIX = \"claude-auto-\";\n\nexport interface SchtasksSchedule {\n\targs: string[];\n\tdescription: string;\n}\n\nconst dayMap = [\"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\"];\n\n/**\n * Translate a 5-field cron expression into schtasks /SC schedule parameters.\n *\n * Supports:\n * - Every N minutes (e.g., *\\/30 * * * *)\n * - Every N hours at minute M (e.g., 0 *\\/6 * * *)\n * - Daily at specific time (e.g., 0 9 * * *)\n * - Specific days of week (e.g., 0 9 * * 1-5)\n * - Monthly on a single day (e.g., 0 9 15 * *)\n *\n * Throws SchedulerError for patterns that cannot be represented as a single\n * Windows Task Scheduler entry.\n */\nexport function cronToSchtasks(cronExpr: string): SchtasksSchedule {\n\tconst interval = CronExpressionParser.parse(cronExpr);\n\tconst fields = interval.fields;\n\n\tconst minutes = [...fields.minute.values].map(Number);\n\tconst hours = [...fields.hour.values].map(Number);\n\tconst daysOfMonth = [...fields.dayOfMonth.values].map(Number);\n\tconst months = [...fields.month.values].map(Number);\n\tconst daysOfWeek = [...fields.dayOfWeek.values].map(Number);\n\n\tconst isAllHours = hours.length === 24;\n\tconst isAllDays = daysOfMonth.length === 31;\n\tconst isAllMonths = months.length === 12;\n\t// cron-parser returns 0-7 for dayOfWeek (0 and 7 both mean Sunday)\n\tconst isAllDow = daysOfWeek.length === 8;\n\n\t// Pattern: every N minutes (e.g., */30 * * * *)\n\tif (isAllHours && isAllDays && isAllMonths && isAllDow && minutes.length > 1) {\n\t\tconst step = minutes[1] - minutes[0];\n\t\tconst isEvenStep = minutes.every((m, i) => i === 0 || m - minutes[i - 1] === step);\n\t\tif (isEvenStep) {\n\t\t\treturn {\n\t\t\t\targs: [\"/sc\", \"MINUTE\", \"/mo\", String(step)],\n\t\t\t\tdescription: `every ${step} minutes`,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Pattern: every N hours at minute M (e.g., 0 */6 * * *)\n\tif (isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length > 1) {\n\t\tconst step = hours[1] - hours[0];\n\t\tconst isEvenStep = hours.every((h, i) => i === 0 || h - hours[i - 1] === step);\n\t\tif (isEvenStep) {\n\t\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\t\treturn {\n\t\t\t\targs: [\"/sc\", \"HOURLY\", \"/mo\", String(step), \"/st\", st],\n\t\t\t\tdescription: `every ${step} hours starting at ${st}`,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Pattern: daily at specific time (e.g., 0 9 * * *)\n\tif (isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length === 1) {\n\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\treturn {\n\t\t\targs: [\"/sc\", \"DAILY\", \"/st\", st],\n\t\t\tdescription: `daily at ${st}`,\n\t\t};\n\t}\n\n\t// Pattern: specific days of week (e.g., 0 9 * * 1-5)\n\tif (isAllDays && isAllMonths && !isAllDow && minutes.length === 1 && hours.length === 1) {\n\t\tconst days = daysOfWeek.filter((d) => d <= 6).map((d) => dayMap[d]);\n\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\treturn {\n\t\t\targs: [\"/sc\", \"WEEKLY\", \"/d\", days.join(\",\"), \"/st\", st],\n\t\t\tdescription: `weekly on ${days.join(\",\")} at ${st}`,\n\t\t};\n\t}\n\n\t// Pattern: specific day of month (e.g., 0 9 15 * *)\n\tif (\n\t\t!isAllDays &&\n\t\tisAllMonths &&\n\t\tisAllDow &&\n\t\tminutes.length === 1 &&\n\t\thours.length === 1 &&\n\t\tdaysOfMonth.length === 1\n\t) {\n\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\treturn {\n\t\t\targs: [\"/sc\", \"MONTHLY\", \"/d\", String(daysOfMonth[0]), \"/st\", st],\n\t\t\tdescription: `monthly on day ${daysOfMonth[0]} at ${st}`,\n\t\t};\n\t}\n\n\t// Unsupported pattern\n\tthrow new SchedulerError(\n\t\t\"win32\",\n\t\t`Cron expression \"${cronExpr}\" cannot be represented as a single Windows Task Scheduler entry. ` +\n\t\t\t\"Simplify the schedule (e.g., daily at a specific time, every N minutes, or specific weekdays).\",\n\t);\n}\n\n/**\n * Resolve the runner script path relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\t// Fallback for test/bundle environments\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * SchtasksScheduler implements the Scheduler interface for Windows systems.\n * Uses `schtasks.exe` to create, query, and delete tasks in Windows Task Scheduler.\n *\n * Task name format: `claude-auto-{jobId}`\n */\nexport class SchtasksScheduler implements Scheduler {\n\tasync register(job: JobConfig, _env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"win32\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst schedule = cronToSchtasks(job.schedule.cron);\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `\"${process.execPath}\" \"${runnerPath}\" --job-id ${job.id}`;\n\t\tconst taskName = `${TASK_PREFIX}${job.id}`;\n\n\t\tconst args = [\"/create\", \"/tn\", taskName, \"/tr\", command, ...schedule.args, \"/f\"];\n\n\t\tawait execCommand(\"schtasks\", args);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst taskName = `${TASK_PREFIX}${jobId}`;\n\t\ttry {\n\t\t\tawait execCommand(\"schtasks\", [\"/delete\", \"/tn\", taskName, \"/f\"]);\n\t\t} catch {\n\t\t\t// Task may not exist -- continue gracefully (same pattern as launchd bootout)\n\t\t}\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst taskName = `${TASK_PREFIX}${jobId}`;\n\t\ttry {\n\t\t\tawait execCommand(\"schtasks\", [\"/query\", \"/tn\", taskName, \"/fo\", \"CSV\", \"/nh\"]);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst jobs: RegisteredJob[] = [];\n\t\ttry {\n\t\t\tconst { stdout } = await execCommand(\"schtasks\", [\"/query\", \"/fo\", \"CSV\", \"/nh\"]);\n\t\t\tconst lines = stdout.split(\"\\n\").filter((line) => line.includes(TASK_PREFIX));\n\n\t\t\tfor (const line of lines) {\n\t\t\t\ttry {\n\t\t\t\t\t// CSV format: \"TaskName\",\"Next Run Time\",\"Status\"\n\t\t\t\t\t// Task names may include path prefix like \\claude-auto-jobId\n\t\t\t\t\tconst match = line.match(/claude-auto-([^\"]+)/);\n\t\t\t\t\tif (match) {\n\t\t\t\t\t\tconst jobId = match[1];\n\t\t\t\t\t\t// Extract schedule and status from CSV fields\n\t\t\t\t\t\tconst fields = line.split('\",\"');\n\t\t\t\t\t\tconst schedule = fields.length > 1 ? fields[1] : \"\";\n\t\t\t\t\t\tconst command = fields.length > 2 ? fields[2]?.replace(/\"/g, \"\") : \"\";\n\n\t\t\t\t\t\tjobs.push({\n\t\t\t\t\t\t\tjobId,\n\t\t\t\t\t\t\tschedule,\n\t\t\t\t\t\t\tcommand,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Skip malformed lines\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// No tasks found or schtasks not available\n\t\t}\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import {
2
+ launchDashboard
3
+ } from "./chunk-LBH6SLHH.js";
4
+ import "./chunk-SMZYA6CY.js";
5
+ import "./chunk-S6E67XMR.js";
6
+ import "./chunk-4I5UIASZ.js";
7
+ import "./chunk-D4MBOIYQ.js";
8
+ import "./chunk-24PS2XSV.js";
9
+ import "./chunk-E3XVLTT4.js";
10
+ import "./chunk-H2MUDYMW.js";
11
+ import "./chunk-YMO45Z6G.js";
12
+ export {
13
+ launchDashboard
14
+ };
15
+ //# sourceMappingURL=tui-2DUPCX3Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,547 @@
1
+ import {
2
+ getCostSummary
3
+ } from "./chunk-S6E67XMR.js";
4
+ import {
5
+ listRunLogs
6
+ } from "./chunk-GLW7T4QE.js";
7
+ import "./chunk-4I5UIASZ.js";
8
+ import {
9
+ getNextRuns
10
+ } from "./chunk-TORYFKPK.js";
11
+ import {
12
+ listJobs
13
+ } from "./chunk-BY5YEOVG.js";
14
+ import "./chunk-2D5E23XA.js";
15
+ import "./chunk-E3XVLTT4.js";
16
+ import "./chunk-H2MUDYMW.js";
17
+ import "./chunk-AWLSYOVF.js";
18
+
19
+ // src/tui/index.tsx
20
+ import "react";
21
+ import { render } from "ink";
22
+
23
+ // src/tui/app.tsx
24
+ import { useState as useState4 } from "react";
25
+ import { Box as Box5, Text as Text5 } from "ink";
26
+
27
+ // src/tui/components/job-list.tsx
28
+ import { Box, Text } from "ink";
29
+ import { Spinner } from "@inkjs/ui";
30
+ import cronstrue from "cronstrue";
31
+ import { jsx, jsxs } from "react/jsx-runtime";
32
+ function formatRelativeTime(date) {
33
+ const now = Date.now();
34
+ const diffMs = date.getTime() - now;
35
+ if (diffMs < 0) return "overdue";
36
+ const diffMin = Math.floor(diffMs / 6e4);
37
+ if (diffMin < 60) return `in ${diffMin}m`;
38
+ const diffHr = Math.floor(diffMin / 60);
39
+ if (diffHr < 24) return `in ${diffHr}h`;
40
+ const diffDay = Math.floor(diffHr / 24);
41
+ return `in ${diffDay}d`;
42
+ }
43
+ function formatCost(cost) {
44
+ return `$${cost.toFixed(2)}`;
45
+ }
46
+ function truncate(str, max) {
47
+ if (str.length <= max) return str;
48
+ return str.slice(0, max - 1) + "\u2026";
49
+ }
50
+ function describeScheduleSafe(cron) {
51
+ try {
52
+ return cronstrue.toString(cron, { use24HourTimeFormat: false });
53
+ } catch {
54
+ return cron;
55
+ }
56
+ }
57
+ function JobList({ jobs, selectedIdx, loading }) {
58
+ if (loading && jobs.length === 0) {
59
+ return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Spinner, { label: "Loading jobs..." }) });
60
+ }
61
+ if (jobs.length === 0) {
62
+ return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No jobs configured. Run 'claude-auto create' to get started." }) });
63
+ }
64
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [
65
+ /* @__PURE__ */ jsxs(Box, { children: [
66
+ /* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Name" }) }),
67
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Status" }) }),
68
+ /* @__PURE__ */ jsx(Box, { width: 27, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Schedule" }) }),
69
+ /* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Next Run" }) }),
70
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Cost" }) })
71
+ ] }),
72
+ jobs.map((job, idx) => {
73
+ const isSelected = idx === selectedIdx;
74
+ const statusText = job.enabled ? "active" : "paused";
75
+ const statusColor2 = job.enabled ? "green" : "yellow";
76
+ const schedule = truncate(describeScheduleSafe(job.cron), 25);
77
+ const nextRun = job.nextRun ? formatRelativeTime(job.nextRun) : "paused";
78
+ const cost = formatCost(job.totalCost);
79
+ return /* @__PURE__ */ jsxs(Box, { children: [
80
+ /* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, bold: isSelected, children: truncate(job.name, 20) }) }),
81
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, color: statusColor2, children: statusText }) }),
82
+ /* @__PURE__ */ jsx(Box, { width: 27, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, dimColor: !isSelected, children: schedule }) }),
83
+ /* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, dimColor: !isSelected, children: nextRun }) }),
84
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { inverse: isSelected, dimColor: !isSelected, children: cost }) })
85
+ ] }, job.id);
86
+ })
87
+ ] });
88
+ }
89
+
90
+ // src/tui/components/job-detail.tsx
91
+ import { Box as Box2, Text as Text2 } from "ink";
92
+ import cronstrue2 from "cronstrue";
93
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
94
+ function describeScheduleSafe2(cron) {
95
+ try {
96
+ return cronstrue2.toString(cron, { use24HourTimeFormat: false });
97
+ } catch {
98
+ return cron;
99
+ }
100
+ }
101
+ function formatDuration(ms) {
102
+ const sec = Math.floor(ms / 1e3);
103
+ if (sec < 60) return `${sec}s`;
104
+ const min = Math.floor(sec / 60);
105
+ if (min < 60) return `${min}m ${sec % 60}s`;
106
+ const hr = Math.floor(min / 60);
107
+ return `${hr}h ${min % 60}m`;
108
+ }
109
+ function JobDetail({ job }) {
110
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingTop: 1, paddingLeft: 1, children: [
111
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: job.name }),
112
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
113
+ /* @__PURE__ */ jsxs2(Box2, { children: [
114
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Repository:" }) }),
115
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.repoPath })
116
+ ] }),
117
+ /* @__PURE__ */ jsxs2(Box2, { children: [
118
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Branch:" }) }),
119
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.branch })
120
+ ] }),
121
+ /* @__PURE__ */ jsxs2(Box2, { children: [
122
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Schedule:" }) }),
123
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
124
+ job.cron,
125
+ " (",
126
+ describeScheduleSafe2(job.cron),
127
+ ")"
128
+ ] })
129
+ ] }),
130
+ /* @__PURE__ */ jsxs2(Box2, { children: [
131
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Timezone:" }) }),
132
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.timezone })
133
+ ] }),
134
+ /* @__PURE__ */ jsxs2(Box2, { children: [
135
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Focus:" }) }),
136
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.focus.join(", ") })
137
+ ] }),
138
+ job.model && /* @__PURE__ */ jsxs2(Box2, { children: [
139
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Model:" }) }),
140
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.model })
141
+ ] }),
142
+ /* @__PURE__ */ jsxs2(Box2, { children: [
143
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Status:" }) }),
144
+ /* @__PURE__ */ jsx2(Text2, { color: job.enabled ? "green" : "yellow", children: job.enabled ? "active" : "paused" })
145
+ ] }),
146
+ /* @__PURE__ */ jsxs2(Box2, { children: [
147
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Budget:" }) }),
148
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
149
+ "$",
150
+ job.maxBudgetUsd.toFixed(2),
151
+ "/run"
152
+ ] })
153
+ ] }),
154
+ /* @__PURE__ */ jsxs2(Box2, { children: [
155
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Total Cost:" }) }),
156
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
157
+ "$",
158
+ job.totalCost.toFixed(2)
159
+ ] })
160
+ ] }),
161
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
162
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Last Run:" }),
163
+ job.lastRun ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
164
+ /* @__PURE__ */ jsxs2(Box2, { children: [
165
+ /* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Date:" }) }),
166
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: new Date(job.lastRun.startedAt).toLocaleString() })
167
+ ] }),
168
+ /* @__PURE__ */ jsxs2(Box2, { children: [
169
+ /* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Status:" }) }),
170
+ /* @__PURE__ */ jsx2(Text2, { color: job.lastRun.status === "success" ? "green" : "red", children: job.lastRun.status })
171
+ ] }),
172
+ /* @__PURE__ */ jsxs2(Box2, { children: [
173
+ /* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Duration:" }) }),
174
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: formatDuration(job.lastRun.durationMs) })
175
+ ] }),
176
+ job.lastRun.costUsd !== void 0 && /* @__PURE__ */ jsxs2(Box2, { children: [
177
+ /* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Cost:" }) }),
178
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
179
+ "$",
180
+ job.lastRun.costUsd.toFixed(4)
181
+ ] })
182
+ ] }),
183
+ job.lastRun.summary && /* @__PURE__ */ jsxs2(Box2, { children: [
184
+ /* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Summary:" }) }),
185
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.lastRun.summary })
186
+ ] }),
187
+ job.lastRun.prUrl && /* @__PURE__ */ jsxs2(Box2, { children: [
188
+ /* @__PURE__ */ jsx2(Box2, { width: 14, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "PR:" }) }),
189
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.lastRun.prUrl })
190
+ ] })
191
+ ] }) : /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No runs yet" }) }),
192
+ job.nextRun && /* @__PURE__ */ jsxs2(Fragment, { children: [
193
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
194
+ /* @__PURE__ */ jsxs2(Box2, { children: [
195
+ /* @__PURE__ */ jsx2(Box2, { width: 16, children: /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Next Run:" }) }),
196
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: job.nextRun.toLocaleString() })
197
+ ] })
198
+ ] })
199
+ ] });
200
+ }
201
+
202
+ // src/tui/components/run-log.tsx
203
+ import { useState, useEffect } from "react";
204
+ import { Box as Box3, Text as Text3 } from "ink";
205
+ import { Spinner as Spinner2 } from "@inkjs/ui";
206
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
207
+ function formatDuration2(ms) {
208
+ const sec = Math.floor(ms / 1e3);
209
+ if (sec < 60) return `${sec}s`;
210
+ const min = Math.floor(sec / 60);
211
+ if (min < 60) return `${min}m ${sec % 60}s`;
212
+ const hr = Math.floor(min / 60);
213
+ return `${hr}h ${min % 60}m`;
214
+ }
215
+ function statusColor(status) {
216
+ switch (status) {
217
+ case "success":
218
+ return "green";
219
+ case "no-changes":
220
+ return "yellow";
221
+ case "error":
222
+ case "git-error":
223
+ return "red";
224
+ case "locked":
225
+ case "paused":
226
+ return "gray";
227
+ case "budget-exceeded":
228
+ return "magenta";
229
+ case "merge-conflict":
230
+ return "red";
231
+ case "needs-human-review":
232
+ return "cyan";
233
+ default:
234
+ return "white";
235
+ }
236
+ }
237
+ var VISIBLE_ENTRIES = 10;
238
+ function RunLog({ jobId, scrollOffset = 0 }) {
239
+ const [logs, setLogs] = useState([]);
240
+ const [loading, setLoading] = useState(true);
241
+ const [error, setError] = useState(null);
242
+ useEffect(() => {
243
+ let cancelled = false;
244
+ const load = async () => {
245
+ try {
246
+ const entries = await listRunLogs(jobId);
247
+ if (!cancelled) {
248
+ setLogs(entries);
249
+ setLoading(false);
250
+ }
251
+ } catch (err) {
252
+ if (!cancelled) {
253
+ setError(err instanceof Error ? err.message : String(err));
254
+ setLoading(false);
255
+ }
256
+ }
257
+ };
258
+ load();
259
+ return () => {
260
+ cancelled = true;
261
+ };
262
+ }, [jobId]);
263
+ if (loading) {
264
+ return /* @__PURE__ */ jsx3(Box3, { padding: 1, children: /* @__PURE__ */ jsx3(Spinner2, { label: "Loading run logs..." }) });
265
+ }
266
+ if (error) {
267
+ return /* @__PURE__ */ jsx3(Box3, { padding: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
268
+ "Error loading logs: ",
269
+ error
270
+ ] }) });
271
+ }
272
+ if (logs.length === 0) {
273
+ return /* @__PURE__ */ jsx3(Box3, { padding: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No run history for this job." }) });
274
+ }
275
+ const clampedOffset = Math.min(scrollOffset, Math.max(0, logs.length - VISIBLE_ENTRIES));
276
+ const visibleLogs = logs.slice(clampedOffset, clampedOffset + VISIBLE_ENTRIES);
277
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingTop: 1, paddingLeft: 1, children: [
278
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
279
+ "Run History (",
280
+ logs.length,
281
+ " runs, showing ",
282
+ clampedOffset + 1,
283
+ "-",
284
+ clampedOffset + visibleLogs.length,
285
+ ")"
286
+ ] }),
287
+ /* @__PURE__ */ jsx3(Text3, { children: " " }),
288
+ visibleLogs.map((log) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
289
+ /* @__PURE__ */ jsxs3(Box3, { children: [
290
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: new Date(log.startedAt).toLocaleString() }),
291
+ /* @__PURE__ */ jsx3(Text3, { children: " " }),
292
+ /* @__PURE__ */ jsx3(Text3, { color: statusColor(log.status), bold: true, children: log.status }),
293
+ /* @__PURE__ */ jsx3(Text3, { children: " " }),
294
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: formatDuration2(log.durationMs) }),
295
+ log.costUsd !== void 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
296
+ /* @__PURE__ */ jsx3(Text3, { children: " " }),
297
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
298
+ "$",
299
+ log.costUsd.toFixed(4)
300
+ ] })
301
+ ] })
302
+ ] }),
303
+ log.summary && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: log.summary }) }),
304
+ log.error && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { color: "red", children: log.error }) }),
305
+ log.prUrl && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
306
+ "PR: ",
307
+ log.prUrl
308
+ ] }) })
309
+ ] }, log.runId)),
310
+ logs.length > VISIBLE_ENTRIES && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Use j/k or up/down to scroll" })
311
+ ] });
312
+ }
313
+
314
+ // src/tui/components/status-bar.tsx
315
+ import { Box as Box4, Text as Text4 } from "ink";
316
+ import { jsx as jsx4 } from "react/jsx-runtime";
317
+ var HINTS = {
318
+ list: "up/down navigate | Enter detail | p pause/resume | l logs | q quit",
319
+ detail: "Esc back | p pause/resume | l logs | q quit",
320
+ logs: "Esc back | j/k scroll | q quit"
321
+ };
322
+ function StatusBar({ view }) {
323
+ 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] }) });
324
+ }
325
+
326
+ // src/tui/hooks/use-jobs.ts
327
+ import { useEffect as useEffect2, useState as useState2 } from "react";
328
+ async function loadJobsWithMeta() {
329
+ const jobs = await listJobs();
330
+ const costMap = /* @__PURE__ */ new Map();
331
+ try {
332
+ const costRows = getCostSummary();
333
+ for (const row of costRows) {
334
+ costMap.set(row.job_id, row.total_cost);
335
+ }
336
+ } catch {
337
+ }
338
+ const results = [];
339
+ for (const job of jobs) {
340
+ let lastRun = null;
341
+ try {
342
+ const logs = await listRunLogs(job.id);
343
+ if (logs.length > 0) {
344
+ lastRun = logs[0];
345
+ }
346
+ } catch {
347
+ }
348
+ let nextRun = null;
349
+ try {
350
+ if (job.enabled) {
351
+ const runs = getNextRuns(job.schedule.cron, job.schedule.timezone, 1);
352
+ if (runs.length > 0) {
353
+ nextRun = runs[0];
354
+ }
355
+ }
356
+ } catch {
357
+ }
358
+ results.push({
359
+ id: job.id,
360
+ name: job.name,
361
+ repoPath: job.repo.path,
362
+ branch: job.repo.branch,
363
+ cron: job.schedule.cron,
364
+ timezone: job.schedule.timezone,
365
+ focus: job.focus,
366
+ enabled: job.enabled,
367
+ model: job.model,
368
+ maxBudgetUsd: job.guardrails.maxBudgetUsd,
369
+ lastRun,
370
+ nextRun,
371
+ totalCost: costMap.get(job.id) ?? 0
372
+ });
373
+ }
374
+ return results;
375
+ }
376
+ function useJobs(pollIntervalMs = 3e3) {
377
+ const [jobs, setJobs] = useState2([]);
378
+ const [loading, setLoading] = useState2(true);
379
+ const [error, setError] = useState2(null);
380
+ const [_refreshTick, setRefreshTick] = useState2(0);
381
+ const refresh = () => setRefreshTick((t) => t + 1);
382
+ useEffect2(() => {
383
+ let cancelled = false;
384
+ const load = async () => {
385
+ try {
386
+ const data = await loadJobsWithMeta();
387
+ if (!cancelled) {
388
+ setJobs(data);
389
+ setLoading(false);
390
+ setError(null);
391
+ }
392
+ } catch (err) {
393
+ if (!cancelled) {
394
+ setError(err instanceof Error ? err.message : String(err));
395
+ setLoading(false);
396
+ }
397
+ }
398
+ };
399
+ load();
400
+ const timer = setInterval(load, pollIntervalMs);
401
+ return () => {
402
+ cancelled = true;
403
+ clearInterval(timer);
404
+ };
405
+ }, [pollIntervalMs]);
406
+ return { jobs, loading, error, refresh };
407
+ }
408
+
409
+ // src/tui/hooks/use-keyboard.ts
410
+ import { useApp, useInput } from "ink";
411
+ import { useCallback, useState as useState3 } from "react";
412
+ function useKeyboard(jobCount) {
413
+ const { exit } = useApp();
414
+ const [view, setView] = useState3("list");
415
+ const [selectedIdx, setSelectedIdx] = useState3(0);
416
+ const [scrollOffset, setScrollOffset] = useState3(0);
417
+ const [pendingPauseResume, setPendingPauseResume] = useState3(false);
418
+ useInput((input, key) => {
419
+ if (input === "q") {
420
+ exit();
421
+ return;
422
+ }
423
+ if (key.upArrow) {
424
+ if (view === "logs") {
425
+ setScrollOffset((o) => Math.max(0, o - 1));
426
+ } else {
427
+ setSelectedIdx((i) => Math.max(0, i - 1));
428
+ }
429
+ return;
430
+ }
431
+ if (key.downArrow) {
432
+ if (view === "logs") {
433
+ setScrollOffset((o) => o + 1);
434
+ } else {
435
+ setSelectedIdx((i) => Math.min(Math.max(0, jobCount - 1), i + 1));
436
+ }
437
+ return;
438
+ }
439
+ if (key.return && view === "list") {
440
+ setView("detail");
441
+ return;
442
+ }
443
+ if (key.escape && view !== "list") {
444
+ setView("list");
445
+ setScrollOffset(0);
446
+ return;
447
+ }
448
+ if (input === "p" && view !== "logs") {
449
+ setPendingPauseResume(true);
450
+ return;
451
+ }
452
+ if (input === "l" && view !== "logs") {
453
+ setView("logs");
454
+ setScrollOffset(0);
455
+ return;
456
+ }
457
+ if (view === "logs") {
458
+ if (input === "j") {
459
+ setScrollOffset((o) => o + 1);
460
+ } else if (input === "k") {
461
+ setScrollOffset((o) => Math.max(0, o - 1));
462
+ }
463
+ }
464
+ });
465
+ const getSelectedJobId = useCallback(
466
+ (jobs) => {
467
+ if (jobs.length === 0 || selectedIdx >= jobs.length) return null;
468
+ return jobs[selectedIdx].id;
469
+ },
470
+ [selectedIdx]
471
+ );
472
+ const pauseResume = useCallback(
473
+ (jobs) => {
474
+ if (!pendingPauseResume) return null;
475
+ setPendingPauseResume(false);
476
+ return getSelectedJobId(jobs);
477
+ },
478
+ [pendingPauseResume, getSelectedJobId]
479
+ );
480
+ return {
481
+ view,
482
+ selectedIdx,
483
+ scrollOffset,
484
+ actions: {
485
+ pauseResume,
486
+ getSelectedJobId
487
+ }
488
+ };
489
+ }
490
+
491
+ // src/tui/app.tsx
492
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
493
+ function App() {
494
+ const { jobs, loading, error, refresh } = useJobs();
495
+ const { view, selectedIdx, scrollOffset, actions } = useKeyboard(jobs.length);
496
+ const [statusMessage, setStatusMessage] = useState4(null);
497
+ const jobIdToPauseResume = actions.pauseResume(jobs);
498
+ if (jobIdToPauseResume) {
499
+ handlePauseResume(jobIdToPauseResume);
500
+ }
501
+ async function handlePauseResume(jobId) {
502
+ const job = jobs.find((j) => j.id === jobId);
503
+ if (!job) return;
504
+ try {
505
+ if (job.enabled) {
506
+ const { pauseCommand } = await import("./pause-OJNUYBCJ.js");
507
+ await pauseCommand({ jobId });
508
+ setStatusMessage(`Paused: ${job.name}`);
509
+ } else {
510
+ const { resumeCommand } = await import("./resume-JVTR7OEX.js");
511
+ await resumeCommand({ jobId });
512
+ setStatusMessage(`Resumed: ${job.name}`);
513
+ }
514
+ refresh();
515
+ } catch (err) {
516
+ setStatusMessage(`Error: ${err instanceof Error ? err.message : String(err)}`);
517
+ }
518
+ setTimeout(() => setStatusMessage(null), 3e3);
519
+ }
520
+ const selectedJob = jobs.length > 0 && selectedIdx < jobs.length ? jobs[selectedIdx] : null;
521
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
522
+ /* @__PURE__ */ jsxs4(Box5, { paddingX: 1, children: [
523
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "claude-auto dashboard" }),
524
+ loading && jobs.length > 0 && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " (refreshing...)" })
525
+ ] }),
526
+ error && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text5, { color: "red", children: [
527
+ "Error: ",
528
+ error
529
+ ] }) }),
530
+ statusMessage && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: statusMessage }) }),
531
+ view === "list" && /* @__PURE__ */ jsx5(JobList, { jobs, selectedIdx, loading }),
532
+ view === "detail" && selectedJob && /* @__PURE__ */ jsx5(JobDetail, { job: selectedJob }),
533
+ view === "logs" && selectedJob && /* @__PURE__ */ jsx5(RunLog, { jobId: selectedJob.id, scrollOffset }),
534
+ /* @__PURE__ */ jsx5(StatusBar, { view })
535
+ ] });
536
+ }
537
+
538
+ // src/tui/index.tsx
539
+ import { jsx as jsx6 } from "react/jsx-runtime";
540
+ async function launchDashboard() {
541
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx6(App, {}));
542
+ await waitUntilExit();
543
+ }
544
+ export {
545
+ launchDashboard
546
+ };
547
+ //# sourceMappingURL=tui-6LOGPILA.js.map