@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 @@
|
|
|
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
|