@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,131 @@
1
+ import {
2
+ createScheduler
3
+ } from "./chunk-QLRCFKLU.js";
4
+ import {
5
+ describeSchedule,
6
+ getNextRuns,
7
+ validateCronExpression
8
+ } from "./chunk-D4MBOIYQ.js";
9
+ import {
10
+ readJob,
11
+ updateJob
12
+ } from "./chunk-24PS2XSV.js";
13
+
14
+ // src/cli/commands/edit.ts
15
+ async function editCommand(args) {
16
+ const jobId = args.jobId;
17
+ if (!jobId) {
18
+ console.error(
19
+ "Usage: claude-auto edit <job-id> --name <value> [--schedule <cron>] [--timezone <tz>] [--branch <branch>] [--max-turns <n>] [--max-budget <n>] [--focus <csv>]"
20
+ );
21
+ throw new Error("Missing required argument: job-id");
22
+ }
23
+ let config;
24
+ try {
25
+ config = await readJob(jobId);
26
+ } catch (err) {
27
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
28
+ console.error(`Job ${jobId} not found.`);
29
+ throw err;
30
+ }
31
+ throw err;
32
+ }
33
+ const updates = {};
34
+ let scheduleChanged = false;
35
+ const confirmations = [];
36
+ if (args.name !== void 0) {
37
+ updates.name = args.name;
38
+ confirmations.push(`name -> ${args.name}`);
39
+ }
40
+ if (args.schedule !== void 0) {
41
+ const cronExpr = String(args.schedule);
42
+ try {
43
+ validateCronExpression(cronExpr);
44
+ } catch (err) {
45
+ console.error(`Invalid schedule: ${err instanceof Error ? err.message : String(err)}`);
46
+ return;
47
+ }
48
+ updates.schedule = { ...config.schedule, cron: cronExpr };
49
+ scheduleChanged = true;
50
+ confirmations.push(`schedule -> ${cronExpr}`);
51
+ }
52
+ if (args.timezone !== void 0) {
53
+ const currentSchedule = updates.schedule ?? { ...config.schedule };
54
+ updates.schedule = { ...currentSchedule, timezone: String(args.timezone) };
55
+ scheduleChanged = true;
56
+ confirmations.push(`timezone -> ${args.timezone}`);
57
+ }
58
+ if (args.branch !== void 0) {
59
+ updates.repo = { ...config.repo, branch: String(args.branch) };
60
+ confirmations.push(`branch -> ${args.branch}`);
61
+ }
62
+ if (args.maxTurns !== void 0) {
63
+ const n = Number.parseInt(String(args.maxTurns), 10);
64
+ if (Number.isNaN(n) || n <= 0) {
65
+ console.error("Invalid --max-turns: must be a positive integer");
66
+ return;
67
+ }
68
+ updates.guardrails = { ...config.guardrails, maxTurns: n };
69
+ confirmations.push(`maxTurns -> ${n}`);
70
+ }
71
+ if (args.maxBudget !== void 0) {
72
+ const n = Number.parseFloat(String(args.maxBudget));
73
+ if (Number.isNaN(n) || n <= 0) {
74
+ console.error("Invalid --max-budget: must be a positive number");
75
+ return;
76
+ }
77
+ updates.guardrails = {
78
+ ...updates.guardrails ?? config.guardrails,
79
+ maxBudgetUsd: n
80
+ };
81
+ confirmations.push(`maxBudgetUsd -> ${n}`);
82
+ }
83
+ if (args.focus !== void 0) {
84
+ const validFocusAreas = /* @__PURE__ */ new Set(["open-issues", "bug-discovery", "features", "documentation"]);
85
+ const focusItems = String(args.focus).split(",").map((s) => s.trim());
86
+ const invalid = focusItems.filter((f) => !validFocusAreas.has(f));
87
+ if (invalid.length > 0) {
88
+ console.error(
89
+ `Invalid focus area(s): ${invalid.join(", ")}. Valid: ${[...validFocusAreas].join(", ")}`
90
+ );
91
+ return;
92
+ }
93
+ updates.focus = focusItems;
94
+ confirmations.push(`focus -> ${focusItems.join(", ")}`);
95
+ }
96
+ if (confirmations.length === 0) {
97
+ console.log(`Job ${jobId} - ${config.name}`);
98
+ console.log(` Schedule: ${config.schedule.cron} (${config.schedule.timezone})`);
99
+ console.log(` Repo: ${config.repo.path} (${config.repo.branch})`);
100
+ console.log(` Focus: ${config.focus.join(", ")}`);
101
+ console.log(` Max turns: ${config.guardrails.maxTurns}`);
102
+ console.log(` Max budget: $${config.guardrails.maxBudgetUsd}`);
103
+ console.log(` Status: ${config.enabled ? "active" : "paused"}`);
104
+ console.log(
105
+ "\nUsage: claude-auto edit <job-id> --name <value> [--schedule <cron>] [--timezone <tz>] [--branch <branch>] [--max-turns <n>] [--max-budget <n>] [--focus <csv>]"
106
+ );
107
+ return;
108
+ }
109
+ const updatedConfig = await updateJob(jobId, updates);
110
+ if (scheduleChanged && config.enabled) {
111
+ const scheduler = await createScheduler();
112
+ try {
113
+ await scheduler.unregister(jobId);
114
+ } catch {
115
+ }
116
+ await scheduler.register(updatedConfig);
117
+ const scheduleObj = updatedConfig.schedule;
118
+ const description = describeSchedule(scheduleObj.cron);
119
+ const nextRuns = getNextRuns(scheduleObj.cron, scheduleObj.timezone, 1);
120
+ const nextRunStr = nextRuns.length > 0 ? nextRuns[0].toLocaleString() : "unknown";
121
+ console.log(`Updated job ${jobId}: ${confirmations.join(", ")}`);
122
+ console.log(`Schedule updated to: ${description}. Next run: ${nextRunStr}`);
123
+ } else {
124
+ console.log(`Updated job ${jobId}: ${confirmations.join(", ")}`);
125
+ }
126
+ }
127
+
128
+ export {
129
+ editCommand
130
+ };
131
+ //# sourceMappingURL=chunk-PFU5YLRH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/edit.ts"],"sourcesContent":["import { readJob, updateJob } from \"../../core/job-manager.js\";\nimport { describeSchedule, getNextRuns, validateCronExpression } from \"../../core/schedule.js\";\nimport type { JobConfig } from \"../../core/types.js\";\nimport { createScheduler } from \"../../platform/scheduler.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Edit a job's configuration fields. Re-validates and re-registers with scheduler\n * if schedule or timezone changes. Supports multiple flags in one invocation.\n */\nexport async function editCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tif (!jobId) {\n\t\tconsole.error(\n\t\t\t\"Usage: claude-auto edit <job-id> --name <value> [--schedule <cron>] [--timezone <tz>] [--branch <branch>] [--max-turns <n>] [--max-budget <n>] [--focus <csv>]\",\n\t\t);\n\t\tthrow new Error(\"Missing required argument: job-id\");\n\t}\n\n\tlet config: JobConfig;\n\ttry {\n\t\tconfig = await readJob(jobId);\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tconsole.error(`Job ${jobId} not found.`);\n\t\t\tthrow err;\n\t\t}\n\t\tthrow err;\n\t}\n\n\tconst updates: Record<string, unknown> = {};\n\tlet scheduleChanged = false;\n\tconst confirmations: string[] = [];\n\n\t// --name\n\tif (args.name !== undefined) {\n\t\tupdates.name = args.name;\n\t\tconfirmations.push(`name -> ${args.name}`);\n\t}\n\n\t// --schedule\n\tif (args.schedule !== undefined) {\n\t\tconst cronExpr = String(args.schedule);\n\t\ttry {\n\t\t\tvalidateCronExpression(cronExpr);\n\t\t} catch (err) {\n\t\t\tconsole.error(`Invalid schedule: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\treturn;\n\t\t}\n\t\tupdates.schedule = { ...config.schedule, cron: cronExpr };\n\t\tscheduleChanged = true;\n\t\tconfirmations.push(`schedule -> ${cronExpr}`);\n\t}\n\n\t// --timezone\n\tif (args.timezone !== undefined) {\n\t\tconst currentSchedule = (updates.schedule as typeof config.schedule) ?? { ...config.schedule };\n\t\tupdates.schedule = { ...currentSchedule, timezone: String(args.timezone) };\n\t\tscheduleChanged = true;\n\t\tconfirmations.push(`timezone -> ${args.timezone}`);\n\t}\n\n\t// --branch\n\tif (args.branch !== undefined) {\n\t\tupdates.repo = { ...config.repo, branch: String(args.branch) };\n\t\tconfirmations.push(`branch -> ${args.branch}`);\n\t}\n\n\t// --max-turns\n\tif (args.maxTurns !== undefined) {\n\t\tconst n = Number.parseInt(String(args.maxTurns), 10);\n\t\tif (Number.isNaN(n) || n <= 0) {\n\t\t\tconsole.error(\"Invalid --max-turns: must be a positive integer\");\n\t\t\treturn;\n\t\t}\n\t\tupdates.guardrails = { ...config.guardrails, maxTurns: n };\n\t\tconfirmations.push(`maxTurns -> ${n}`);\n\t}\n\n\t// --max-budget\n\tif (args.maxBudget !== undefined) {\n\t\tconst n = Number.parseFloat(String(args.maxBudget));\n\t\tif (Number.isNaN(n) || n <= 0) {\n\t\t\tconsole.error(\"Invalid --max-budget: must be a positive number\");\n\t\t\treturn;\n\t\t}\n\t\tupdates.guardrails = {\n\t\t\t...(updates.guardrails ?? config.guardrails),\n\t\t\tmaxBudgetUsd: n,\n\t\t};\n\t\tconfirmations.push(`maxBudgetUsd -> ${n}`);\n\t}\n\n\t// --focus\n\tif (args.focus !== undefined) {\n\t\tconst validFocusAreas = new Set([\"open-issues\", \"bug-discovery\", \"features\", \"documentation\"]);\n\t\tconst focusItems = String(args.focus)\n\t\t\t.split(\",\")\n\t\t\t.map((s) => s.trim());\n\t\tconst invalid = focusItems.filter((f) => !validFocusAreas.has(f));\n\t\tif (invalid.length > 0) {\n\t\t\tconsole.error(\n\t\t\t\t`Invalid focus area(s): ${invalid.join(\", \")}. Valid: ${[...validFocusAreas].join(\", \")}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tupdates.focus = focusItems;\n\t\tconfirmations.push(`focus -> ${focusItems.join(\", \")}`);\n\t}\n\n\t// If no edit flags provided, show current config\n\tif (confirmations.length === 0) {\n\t\tconsole.log(`Job ${jobId} - ${config.name}`);\n\t\tconsole.log(` Schedule: ${config.schedule.cron} (${config.schedule.timezone})`);\n\t\tconsole.log(` Repo: ${config.repo.path} (${config.repo.branch})`);\n\t\tconsole.log(` Focus: ${config.focus.join(\", \")}`);\n\t\tconsole.log(` Max turns: ${config.guardrails.maxTurns}`);\n\t\tconsole.log(` Max budget: $${config.guardrails.maxBudgetUsd}`);\n\t\tconsole.log(` Status: ${config.enabled ? \"active\" : \"paused\"}`);\n\t\tconsole.log(\n\t\t\t\"\\nUsage: claude-auto edit <job-id> --name <value> [--schedule <cron>] [--timezone <tz>] [--branch <branch>] [--max-turns <n>] [--max-budget <n>] [--focus <csv>]\",\n\t\t);\n\t\treturn;\n\t}\n\n\tconst updatedConfig = await updateJob(jobId, updates as Parameters<typeof updateJob>[1]);\n\n\t// Re-register with scheduler if schedule changed and job is enabled\n\tif (scheduleChanged && config.enabled) {\n\t\tconst scheduler = await createScheduler();\n\t\ttry {\n\t\t\tawait scheduler.unregister(jobId);\n\t\t} catch {\n\t\t\t// Best-effort unregister\n\t\t}\n\t\tawait scheduler.register(updatedConfig);\n\n\t\tconst scheduleObj = updatedConfig.schedule;\n\t\tconst description = describeSchedule(scheduleObj.cron);\n\t\tconst nextRuns = getNextRuns(scheduleObj.cron, scheduleObj.timezone, 1);\n\t\tconst nextRunStr = nextRuns.length > 0 ? nextRuns[0].toLocaleString() : \"unknown\";\n\t\tconsole.log(`Updated job ${jobId}: ${confirmations.join(\", \")}`);\n\t\tconsole.log(`Schedule updated to: ${description}. Next run: ${nextRunStr}`);\n\t} else {\n\t\tconsole.log(`Updated job ${jobId}: ${confirmations.join(\", \")}`);\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,34 @@
1
+ import {
2
+ SchedulerError
3
+ } from "./chunk-YMO45Z6G.js";
4
+
5
+ // src/platform/detect.ts
6
+ function detectPlatform() {
7
+ const p = process.platform;
8
+ if (p === "linux" || p === "darwin" || p === "win32") return p;
9
+ throw new SchedulerError(
10
+ p,
11
+ `Unsupported platform: ${p}. Only linux, darwin, and win32 are supported.`
12
+ );
13
+ }
14
+
15
+ // src/platform/scheduler.ts
16
+ async function createScheduler() {
17
+ const platform = detectPlatform();
18
+ if (platform === "linux") {
19
+ const { CrontabScheduler } = await import("./crontab-PNEWANLW.js");
20
+ return new CrontabScheduler();
21
+ }
22
+ if (platform === "win32") {
23
+ const { SchtasksScheduler } = await import("./schtasks-2EQAD3ES.js");
24
+ return new SchtasksScheduler();
25
+ }
26
+ const { LaunchdScheduler } = await import("./launchd-LZGDP7BM.js");
27
+ return new LaunchdScheduler();
28
+ }
29
+
30
+ export {
31
+ detectPlatform,
32
+ createScheduler
33
+ };
34
+ //# sourceMappingURL=chunk-QLRCFKLU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/detect.ts","../src/platform/scheduler.ts"],"sourcesContent":["import { SchedulerError } from \"../util/errors.js\";\n\nexport type Platform = \"linux\" | \"darwin\" | \"win32\";\n\n/**\n * Detect the current platform. Returns \"linux\", \"darwin\", or \"win32\".\n * Throws SchedulerError for unsupported platforms.\n */\nexport function detectPlatform(): Platform {\n\tconst p = process.platform;\n\tif (p === \"linux\" || p === \"darwin\" || p === \"win32\") return p;\n\tthrow new SchedulerError(\n\t\tp,\n\t\t`Unsupported platform: ${p}. Only linux, darwin, and win32 are supported.`,\n\t);\n}\n","import type { JobConfig } from \"../core/types.js\";\nimport { detectPlatform } from \"./detect.js\";\n\nexport interface RegisteredJob {\n\tjobId: string;\n\tschedule: string;\n\tcommand: string;\n}\n\nexport interface Scheduler {\n\tregister(job: JobConfig, env?: Record<string, string>): Promise<void>;\n\tunregister(jobId: string): Promise<void>;\n\tisRegistered(jobId: string): Promise<boolean>;\n\tlist(): Promise<RegisteredJob[]>;\n}\n\n/**\n * Factory function that returns the correct Scheduler implementation\n * for the current platform: CrontabScheduler on Linux, LaunchdScheduler on macOS,\n * SchtasksScheduler on Windows.\n */\nexport async function createScheduler(): Promise<Scheduler> {\n\tconst platform = detectPlatform();\n\tif (platform === \"linux\") {\n\t\tconst { CrontabScheduler } = await import(\"./crontab.js\");\n\t\treturn new CrontabScheduler();\n\t}\n\tif (platform === \"win32\") {\n\t\tconst { SchtasksScheduler } = await import(\"./schtasks.js\");\n\t\treturn new SchtasksScheduler();\n\t}\n\tconst { LaunchdScheduler } = await import(\"./launchd.js\");\n\treturn new LaunchdScheduler();\n}\n"],"mappings":";;;;;AAQO,SAAS,iBAA2B;AAC1C,QAAM,IAAI,QAAQ;AAClB,MAAI,MAAM,WAAW,MAAM,YAAY,MAAM,QAAS,QAAO;AAC7D,QAAM,IAAI;AAAA,IACT;AAAA,IACA,yBAAyB,CAAC;AAAA,EAC3B;AACD;;;ACMA,eAAsB,kBAAsC;AAC3D,QAAM,WAAW,eAAe;AAChC,MAAI,aAAa,SAAS;AACzB,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,uBAAc;AACxD,WAAO,IAAI,iBAAiB;AAAA,EAC7B;AACA,MAAI,aAAa,SAAS;AACzB,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,wBAAe;AAC1D,WAAO,IAAI,kBAAkB;AAAA,EAC9B;AACA,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,uBAAc;AACxD,SAAO,IAAI,iBAAiB;AAC7B;","names":[]}
@@ -0,0 +1,167 @@
1
+ import {
2
+ execCommand
3
+ } from "./chunk-3NEANSUS.js";
4
+ import {
5
+ paths
6
+ } from "./chunk-H2MUDYMW.js";
7
+ import {
8
+ SchedulerError
9
+ } from "./chunk-YMO45Z6G.js";
10
+
11
+ // src/platform/launchd.ts
12
+ import { access, readdir, readFile, unlink, writeFile } from "fs/promises";
13
+ import { homedir } from "os";
14
+ import { dirname, join } from "path";
15
+ import { fileURLToPath } from "url";
16
+ import { CronExpressionParser } from "cron-parser";
17
+ import plist from "plist";
18
+ var LABEL_PREFIX = "com.claude-auto.";
19
+ function getLabel(jobId) {
20
+ return `${LABEL_PREFIX}${jobId}`;
21
+ }
22
+ function getUid() {
23
+ return process.getuid?.() ?? 501;
24
+ }
25
+ function getRunnerPath() {
26
+ try {
27
+ const currentDir = dirname(fileURLToPath(import.meta.url));
28
+ return join(currentDir, "..", "..", "dist", "claude-auto-run.js");
29
+ } catch {
30
+ return join(process.cwd(), "dist", "claude-auto-run.js");
31
+ }
32
+ }
33
+ function cronToCalendarIntervals(cronExpr) {
34
+ const interval = CronExpressionParser.parse(cronExpr);
35
+ const fields = interval.fields;
36
+ const minutes = [...fields.minute.values].map(Number);
37
+ const hours = [...fields.hour.values].map(Number);
38
+ const daysOfMonth = [...fields.dayOfMonth.values].map(Number);
39
+ const months = [...fields.month.values].map(Number);
40
+ const daysOfWeek = [...fields.dayOfWeek.values].map(Number);
41
+ const isAllHours = hours.length === 24;
42
+ const isAllDays = daysOfMonth.length === 31;
43
+ const isAllMonths = months.length === 12;
44
+ const isAllDow = daysOfWeek.length === 8;
45
+ if (isAllHours && isAllDays && isAllMonths && isAllDow && minutes.length > 1) {
46
+ const step = minutes[1] - minutes[0];
47
+ const isEvenStep = minutes.every((m, i) => i === 0 || m - minutes[i - 1] === step);
48
+ if (isEvenStep) {
49
+ return { startInterval: step * 60 };
50
+ }
51
+ }
52
+ const intervals = [];
53
+ const effectiveHours = isAllHours ? [void 0] : hours;
54
+ const effectiveDow = isAllDow ? [void 0] : daysOfWeek.filter((d) => d <= 6);
55
+ const effectiveDom = isAllDays ? [void 0] : daysOfMonth;
56
+ for (const minute of minutes) {
57
+ for (const hour of effectiveHours) {
58
+ for (const dow of effectiveDow) {
59
+ for (const dom of effectiveDom) {
60
+ const entry = { Minute: minute };
61
+ if (hour !== void 0) entry.Hour = hour;
62
+ if (dow !== void 0) entry.Weekday = dow;
63
+ if (dom !== void 0) entry.Day = dom;
64
+ intervals.push(entry);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ if (intervals.length > 50) {
70
+ throw new Error(
71
+ `Cron expression "${cronExpr}" would produce ${intervals.length} calendar intervals. launchd StartCalendarInterval is not efficient for complex schedules. Simplify the schedule or use a simpler pattern.`
72
+ );
73
+ }
74
+ return { calendarIntervals: intervals };
75
+ }
76
+ var LaunchdScheduler = class {
77
+ async register(job, env) {
78
+ const registered = await this.isRegistered(job.id);
79
+ if (registered) {
80
+ throw new SchedulerError("launchd", `Job "${job.id}" is already registered`);
81
+ }
82
+ const scheduling = cronToCalendarIntervals(job.schedule.cron);
83
+ const runnerPath = getRunnerPath();
84
+ const logPath = `${paths.jobLogs(job.id)}/launchd.log`;
85
+ const plistPath = paths.plistPath(job.id);
86
+ const obj = {
87
+ Label: getLabel(job.id),
88
+ ProgramArguments: [process.execPath, runnerPath, "--job-id", job.id],
89
+ StandardOutPath: logPath,
90
+ StandardErrorPath: logPath,
91
+ EnvironmentVariables: {
92
+ PATH: env?.PATH ?? process.env.PATH ?? "",
93
+ HOME: env?.HOME ?? homedir(),
94
+ ...env ? Object.fromEntries(Object.entries(env).filter(([k]) => k !== "PATH" && k !== "HOME")) : {}
95
+ },
96
+ RunAtLoad: false,
97
+ KeepAlive: false
98
+ };
99
+ if (scheduling.startInterval !== void 0) {
100
+ obj.StartInterval = scheduling.startInterval;
101
+ } else if (scheduling.calendarIntervals) {
102
+ obj.StartCalendarInterval = scheduling.calendarIntervals.length === 1 ? scheduling.calendarIntervals[0] : scheduling.calendarIntervals;
103
+ }
104
+ const xml = plist.build(obj);
105
+ await writeFile(plistPath, xml, "utf-8");
106
+ const uid = getUid();
107
+ await execCommand("launchctl", ["bootstrap", `gui/${uid}`, plistPath]);
108
+ }
109
+ async unregister(jobId) {
110
+ const uid = getUid();
111
+ const label = getLabel(jobId);
112
+ const plistPath = paths.plistPath(jobId);
113
+ try {
114
+ await execCommand("launchctl", ["bootout", `gui/${uid}/${label}`]);
115
+ } catch {
116
+ }
117
+ try {
118
+ await unlink(plistPath);
119
+ } catch {
120
+ }
121
+ }
122
+ async isRegistered(jobId) {
123
+ try {
124
+ await access(paths.plistPath(jobId));
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+ async list() {
131
+ const jobs = [];
132
+ try {
133
+ const files = await readdir(paths.plistDir);
134
+ const plistFiles = files.filter((f) => f.startsWith(LABEL_PREFIX) && f.endsWith(".plist"));
135
+ for (const file of plistFiles) {
136
+ try {
137
+ const filePath = join(paths.plistDir, file);
138
+ const content = await readFile(filePath, "utf-8");
139
+ const parsed = plist.parse(content);
140
+ const label = parsed.Label;
141
+ const jobId = label.replace(LABEL_PREFIX, "");
142
+ const progArgs = parsed.ProgramArguments ?? [];
143
+ let schedule = "";
144
+ if (parsed.StartInterval) {
145
+ schedule = `every ${parsed.StartInterval}s`;
146
+ } else if (parsed.StartCalendarInterval) {
147
+ schedule = "calendar";
148
+ }
149
+ jobs.push({
150
+ jobId,
151
+ schedule,
152
+ command: progArgs.join(" ")
153
+ });
154
+ } catch {
155
+ }
156
+ }
157
+ } catch {
158
+ }
159
+ return jobs;
160
+ }
161
+ };
162
+
163
+ export {
164
+ cronToCalendarIntervals,
165
+ LaunchdScheduler
166
+ };
167
+ //# sourceMappingURL=chunk-QQTIJN3S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/launchd.ts"],"sourcesContent":["import { access, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CronExpressionParser } from \"cron-parser\";\nimport plist from \"plist\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport { paths } from \"../util/paths.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nexport interface CalendarInterval {\n\tMonth?: number;\n\tDay?: number;\n\tWeekday?: number;\n\tHour?: number;\n\tMinute?: number;\n}\n\nconst LABEL_PREFIX = \"com.claude-auto.\";\n\nfunction getLabel(jobId: string): string {\n\treturn `${LABEL_PREFIX}${jobId}`;\n}\n\nfunction getUid(): number {\n\treturn process.getuid?.() ?? 501;\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\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * Convert a 5-field cron expression to launchd scheduling config.\n *\n * For high-frequency \"every N minutes\" patterns, returns { startInterval: seconds }.\n * For specific times, returns { calendarIntervals: CalendarInterval[] }.\n *\n * Throws if the expression would produce more than 50 calendar intervals.\n */\nexport function cronToCalendarIntervals(cronExpr: string): {\n\tcalendarIntervals?: CalendarInterval[];\n\tstartInterval?: number;\n} {\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 (8 values since 0 and 7 both mean Sunday)\n\tconst isAllDow = daysOfWeek.length === 8;\n\n\t// Detect \"every N minutes\" pattern: hours/days/months/dow all wildcard, minutes have even spacing\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 { startInterval: step * 60 };\n\t\t}\n\t}\n\n\t// Build calendar interval combinations\n\tconst intervals: CalendarInterval[] = [];\n\tconst effectiveHours = isAllHours ? [undefined] : hours;\n\t// Filter out duplicate Sunday (7) -- launchd uses 0 for Sunday\n\tconst effectiveDow = isAllDow ? [undefined] : daysOfWeek.filter((d) => d <= 6);\n\tconst effectiveDom = isAllDays ? [undefined] : daysOfMonth;\n\n\tfor (const minute of minutes) {\n\t\tfor (const hour of effectiveHours) {\n\t\t\tfor (const dow of effectiveDow) {\n\t\t\t\tfor (const dom of effectiveDom) {\n\t\t\t\t\tconst entry: CalendarInterval = { Minute: minute };\n\t\t\t\t\tif (hour !== undefined) entry.Hour = hour;\n\t\t\t\t\tif (dow !== undefined) entry.Weekday = dow;\n\t\t\t\t\tif (dom !== undefined) entry.Day = dom;\n\t\t\t\t\tintervals.push(entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (intervals.length > 50) {\n\t\tthrow new Error(\n\t\t\t`Cron expression \"${cronExpr}\" would produce ${intervals.length} calendar intervals. ` +\n\t\t\t\t\"launchd StartCalendarInterval is not efficient for complex schedules. \" +\n\t\t\t\t\"Simplify the schedule or use a simpler pattern.\",\n\t\t);\n\t}\n\n\treturn { calendarIntervals: intervals };\n}\n\n/**\n * LaunchdScheduler implements the Scheduler interface for macOS.\n * Uses plist files in ~/Library/LaunchAgents/ and modern launchctl bootstrap/bootout.\n */\nexport class LaunchdScheduler 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(\"launchd\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst scheduling = cronToCalendarIntervals(job.schedule.cron);\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst logPath = `${paths.jobLogs(job.id)}/launchd.log`;\n\t\tconst plistPath = paths.plistPath(job.id);\n\n\t\tconst obj: Record<string, unknown> = {\n\t\t\tLabel: getLabel(job.id),\n\t\t\tProgramArguments: [process.execPath, runnerPath, \"--job-id\", job.id],\n\t\t\tStandardOutPath: logPath,\n\t\t\tStandardErrorPath: logPath,\n\t\t\tEnvironmentVariables: {\n\t\t\t\tPATH: env?.PATH ?? process.env.PATH ?? \"\",\n\t\t\t\tHOME: env?.HOME ?? homedir(),\n\t\t\t\t...(env\n\t\t\t\t\t? Object.fromEntries(Object.entries(env).filter(([k]) => k !== \"PATH\" && k !== \"HOME\"))\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\tRunAtLoad: false,\n\t\t\tKeepAlive: false,\n\t\t};\n\n\t\tif (scheduling.startInterval !== undefined) {\n\t\t\tobj.StartInterval = scheduling.startInterval;\n\t\t} else if (scheduling.calendarIntervals) {\n\t\t\tobj.StartCalendarInterval =\n\t\t\t\tscheduling.calendarIntervals.length === 1\n\t\t\t\t\t? scheduling.calendarIntervals[0]\n\t\t\t\t\t: scheduling.calendarIntervals;\n\t\t}\n\n\t\tconst xml = plist.build(obj as unknown as plist.PlistValue);\n\t\tawait writeFile(plistPath, xml, \"utf-8\");\n\n\t\tconst uid = getUid();\n\t\tawait execCommand(\"launchctl\", [\"bootstrap\", `gui/${uid}`, plistPath]);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst uid = getUid();\n\t\tconst label = getLabel(jobId);\n\t\tconst plistPath = paths.plistPath(jobId);\n\n\t\ttry {\n\t\t\tawait execCommand(\"launchctl\", [\"bootout\", `gui/${uid}/${label}`]);\n\t\t} catch {\n\t\t\t// Service may already be unloaded -- continue to delete plist\n\t\t}\n\n\t\ttry {\n\t\t\tawait unlink(plistPath);\n\t\t} catch {\n\t\t\t// Plist may already be deleted\n\t\t}\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(paths.plistPath(jobId));\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 files = await readdir(paths.plistDir);\n\t\t\tconst plistFiles = files.filter((f) => f.startsWith(LABEL_PREFIX) && f.endsWith(\".plist\"));\n\n\t\t\tfor (const file of plistFiles) {\n\t\t\t\ttry {\n\t\t\t\t\tconst filePath = join(paths.plistDir, file);\n\t\t\t\t\tconst content = await readFile(filePath, \"utf-8\");\n\t\t\t\t\tconst parsed = plist.parse(content) as Record<string, unknown>;\n\t\t\t\t\tconst label = parsed.Label as string;\n\t\t\t\t\tconst jobId = label.replace(LABEL_PREFIX, \"\");\n\t\t\t\t\tconst progArgs = (parsed.ProgramArguments as string[]) ?? [];\n\n\t\t\t\t\tlet schedule = \"\";\n\t\t\t\t\tif (parsed.StartInterval) {\n\t\t\t\t\t\tschedule = `every ${parsed.StartInterval}s`;\n\t\t\t\t\t} else if (parsed.StartCalendarInterval) {\n\t\t\t\t\t\tschedule = \"calendar\";\n\t\t\t\t\t}\n\n\t\t\t\t\tjobs.push({\n\t\t\t\t\t\tjobId,\n\t\t\t\t\t\tschedule,\n\t\t\t\t\t\tcommand: progArgs.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Skip malformed plists\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Directory may not exist\n\t\t}\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,72 @@
1
+ import {
2
+ formatTable
3
+ } from "./chunk-ORBF5IW3.js";
4
+ import {
5
+ getCostSummary
6
+ } from "./chunk-S6E67XMR.js";
7
+ import {
8
+ closeDatabase
9
+ } from "./chunk-4I5UIASZ.js";
10
+
11
+ // src/cli/commands/cost.ts
12
+ async function costCommand(args) {
13
+ const jobId = args.jobId;
14
+ const jsonOutput = args.json === true;
15
+ try {
16
+ if (jobId) {
17
+ const rows = getCostSummary(jobId);
18
+ if (rows.length === 0) {
19
+ console.log(`No cost data found for job: ${jobId}`);
20
+ return;
21
+ }
22
+ if (jsonOutput) {
23
+ console.log(JSON.stringify(rows, null, 2));
24
+ return;
25
+ }
26
+ const tableRows = rows.map((r) => [
27
+ r.day,
28
+ String(r.runs),
29
+ `$${r.total_cost.toFixed(4)}`,
30
+ String(r.total_turns)
31
+ ]);
32
+ console.log(`
33
+ Cost breakdown for job: ${jobId}
34
+ `);
35
+ console.log(formatTable(["Date", "Runs", "Cost (USD)", "Turns"], tableRows));
36
+ const totalCost = rows.reduce((sum, r) => sum + r.total_cost, 0);
37
+ console.log(`
38
+ Total: $${totalCost.toFixed(4)}`);
39
+ } else {
40
+ const rows = getCostSummary();
41
+ if (rows.length === 0) {
42
+ console.log("No cost data found.");
43
+ return;
44
+ }
45
+ if (jsonOutput) {
46
+ console.log(JSON.stringify(rows, null, 2));
47
+ return;
48
+ }
49
+ const tableRows = rows.map((r) => [
50
+ r.job_id,
51
+ String(r.runs),
52
+ `$${r.total_cost.toFixed(4)}`,
53
+ `$${r.avg_cost.toFixed(4)}`,
54
+ String(r.total_turns)
55
+ ]);
56
+ console.log("\nCost Summary\n");
57
+ console.log(
58
+ formatTable(["Job ID", "Runs", "Total Cost (USD)", "Avg Cost", "Turns"], tableRows)
59
+ );
60
+ const grandTotal = rows.reduce((sum, r) => sum + r.total_cost, 0);
61
+ console.log(`
62
+ Grand Total: $${grandTotal.toFixed(4)}`);
63
+ }
64
+ } finally {
65
+ closeDatabase();
66
+ }
67
+ }
68
+
69
+ export {
70
+ costCommand
71
+ };
72
+ //# sourceMappingURL=chunk-QRYCNVLT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/cost.ts"],"sourcesContent":["import { closeDatabase } from \"../../core/database.js\";\nimport {\n\ttype CostSummaryRow,\n\ttype DailyCostRow,\n\tgetCostSummary,\n} from \"../../runner/cost-tracker.js\";\nimport { formatTable } from \"../format.js\";\n\n/**\n * CLI command handler for `claude-auto cost`.\n *\n * Without a jobId: displays per-job cost summary table.\n * With a jobId: displays per-day cost breakdown for that job.\n * With --json flag: outputs JSON array instead of formatted table.\n *\n * @param args - Parsed CLI arguments (jobId?: string, json?: boolean)\n */\nexport async function costCommand(\n\targs: Record<string, string | number | boolean | undefined>,\n): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tconst jsonOutput = args.json === true;\n\n\ttry {\n\t\tif (jobId) {\n\t\t\tconst rows = getCostSummary(jobId) as DailyCostRow[];\n\n\t\t\tif (rows.length === 0) {\n\t\t\t\tconsole.log(`No cost data found for job: ${jobId}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (jsonOutput) {\n\t\t\t\tconsole.log(JSON.stringify(rows, null, 2));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst tableRows = rows.map((r) => [\n\t\t\t\tr.day,\n\t\t\t\tString(r.runs),\n\t\t\t\t`$${r.total_cost.toFixed(4)}`,\n\t\t\t\tString(r.total_turns),\n\t\t\t]);\n\n\t\t\tconsole.log(`\\nCost breakdown for job: ${jobId}\\n`);\n\t\t\tconsole.log(formatTable([\"Date\", \"Runs\", \"Cost (USD)\", \"Turns\"], tableRows));\n\n\t\t\tconst totalCost = rows.reduce((sum, r) => sum + r.total_cost, 0);\n\t\t\tconsole.log(`\\nTotal: $${totalCost.toFixed(4)}`);\n\t\t} else {\n\t\t\tconst rows = getCostSummary() as CostSummaryRow[];\n\n\t\t\tif (rows.length === 0) {\n\t\t\t\tconsole.log(\"No cost data found.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (jsonOutput) {\n\t\t\t\tconsole.log(JSON.stringify(rows, null, 2));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst tableRows = rows.map((r) => [\n\t\t\t\tr.job_id,\n\t\t\t\tString(r.runs),\n\t\t\t\t`$${r.total_cost.toFixed(4)}`,\n\t\t\t\t`$${r.avg_cost.toFixed(4)}`,\n\t\t\t\tString(r.total_turns),\n\t\t\t]);\n\n\t\t\tconsole.log(\"\\nCost Summary\\n\");\n\t\t\tconsole.log(\n\t\t\t\tformatTable([\"Job ID\", \"Runs\", \"Total Cost (USD)\", \"Avg Cost\", \"Turns\"], tableRows),\n\t\t\t);\n\n\t\t\tconst grandTotal = rows.reduce((sum, r) => sum + r.total_cost, 0);\n\t\t\tconsole.log(`\\nGrand Total: $${grandTotal.toFixed(4)}`);\n\t\t}\n\t} finally {\n\t\tcloseDatabase();\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAiBA,eAAsB,YACrB,MACgB;AAChB,QAAM,QAAQ,KAAK;AACnB,QAAM,aAAa,KAAK,SAAS;AAEjC,MAAI;AACH,QAAI,OAAO;AACV,YAAM,OAAO,eAAe,KAAK;AAEjC,UAAI,KAAK,WAAW,GAAG;AACtB,gBAAQ,IAAI,+BAA+B,KAAK,EAAE;AAClD;AAAA,MACD;AAEA,UAAI,YAAY;AACf,gBAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACzC;AAAA,MACD;AAEA,YAAM,YAAY,KAAK,IAAI,CAAC,MAAM;AAAA,QACjC,EAAE;AAAA,QACF,OAAO,EAAE,IAAI;AAAA,QACb,IAAI,EAAE,WAAW,QAAQ,CAAC,CAAC;AAAA,QAC3B,OAAO,EAAE,WAAW;AAAA,MACrB,CAAC;AAED,cAAQ,IAAI;AAAA,0BAA6B,KAAK;AAAA,CAAI;AAClD,cAAQ,IAAI,YAAY,CAAC,QAAQ,QAAQ,cAAc,OAAO,GAAG,SAAS,CAAC;AAE3E,YAAM,YAAY,KAAK,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAC/D,cAAQ,IAAI;AAAA,UAAa,UAAU,QAAQ,CAAC,CAAC,EAAE;AAAA,IAChD,OAAO;AACN,YAAM,OAAO,eAAe;AAE5B,UAAI,KAAK,WAAW,GAAG;AACtB,gBAAQ,IAAI,qBAAqB;AACjC;AAAA,MACD;AAEA,UAAI,YAAY;AACf,gBAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACzC;AAAA,MACD;AAEA,YAAM,YAAY,KAAK,IAAI,CAAC,MAAM;AAAA,QACjC,EAAE;AAAA,QACF,OAAO,EAAE,IAAI;AAAA,QACb,IAAI,EAAE,WAAW,QAAQ,CAAC,CAAC;AAAA,QAC3B,IAAI,EAAE,SAAS,QAAQ,CAAC,CAAC;AAAA,QACzB,OAAO,EAAE,WAAW;AAAA,MACrB,CAAC;AAED,cAAQ,IAAI,kBAAkB;AAC9B,cAAQ;AAAA,QACP,YAAY,CAAC,UAAU,QAAQ,oBAAoB,YAAY,OAAO,GAAG,SAAS;AAAA,MACnF;AAEA,YAAM,aAAa,KAAK,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAChE,cAAQ,IAAI;AAAA,gBAAmB,WAAW,QAAQ,CAAC,CAAC,EAAE;AAAA,IACvD;AAAA,EACD,UAAE;AACD,kBAAc;AAAA,EACf;AACD;","names":[]}
@@ -0,0 +1,52 @@
1
+ import {
2
+ getDatabase
3
+ } from "./chunk-4I5UIASZ.js";
4
+
5
+ // src/runner/cost-tracker.ts
6
+ var PERIOD_OFFSETS = [
7
+ { key: "dailyUsd", offset: "-1 day" },
8
+ { key: "weeklyUsd", offset: "-7 days" },
9
+ { key: "monthlyUsd", offset: "-30 days" }
10
+ ];
11
+ function checkBudget(jobId, budget, dbPath) {
12
+ const db = getDatabase(dbPath);
13
+ for (const { key, offset } of PERIOD_OFFSETS) {
14
+ const cap = budget[key];
15
+ if (cap === void 0) continue;
16
+ const row = db.prepare(
17
+ `SELECT COALESCE(SUM(cost_usd), 0) as total FROM runs
18
+ WHERE job_id = ? AND started_at >= datetime('now', ?)
19
+ AND status NOT IN ('locked', 'paused', 'budget-exceeded')`
20
+ ).get(jobId, offset);
21
+ if (row.total >= cap) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+ function getCostSummary(jobId, dbPath) {
28
+ const db = getDatabase(dbPath);
29
+ if (jobId) {
30
+ return db.prepare(
31
+ `SELECT date(started_at) as day, COUNT(*) as runs,
32
+ ROUND(COALESCE(SUM(cost_usd), 0), 4) as total_cost,
33
+ COALESCE(SUM(num_turns), 0) as total_turns
34
+ FROM runs WHERE job_id = ? AND cost_usd IS NOT NULL
35
+ GROUP BY date(started_at) ORDER BY day DESC LIMIT 30`
36
+ ).all(jobId);
37
+ }
38
+ return db.prepare(
39
+ `SELECT job_id, COUNT(*) as runs,
40
+ ROUND(COALESCE(SUM(cost_usd), 0), 4) as total_cost,
41
+ ROUND(COALESCE(AVG(cost_usd), 0), 4) as avg_cost,
42
+ COALESCE(SUM(num_turns), 0) as total_turns
43
+ FROM runs WHERE cost_usd IS NOT NULL
44
+ GROUP BY job_id ORDER BY total_cost DESC`
45
+ ).all();
46
+ }
47
+
48
+ export {
49
+ checkBudget,
50
+ getCostSummary
51
+ };
52
+ //# sourceMappingURL=chunk-S6E67XMR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runner/cost-tracker.ts"],"sourcesContent":["import { getDatabase } from \"../core/database.js\";\n\n/**\n * Budget configuration with optional caps per time period.\n * When a cap is set, cumulative cost within that period is checked\n * before spawning a new run.\n */\nexport interface BudgetConfig {\n\tdailyUsd?: number;\n\tweeklyUsd?: number;\n\tmonthlyUsd?: number;\n}\n\n/**\n * Per-job cost summary row returned by getCostSummary() when no jobId is specified.\n */\nexport interface CostSummaryRow {\n\tjob_id: string;\n\truns: number;\n\ttotal_cost: number;\n\tavg_cost: number;\n\ttotal_turns: number;\n}\n\n/**\n * Per-day cost breakdown row returned by getCostSummary(jobId).\n */\nexport interface DailyCostRow {\n\tday: string;\n\truns: number;\n\ttotal_cost: number;\n\ttotal_turns: number;\n}\n\n/**\n * Period definitions mapping budget config keys to SQLite datetime offsets.\n */\nconst PERIOD_OFFSETS: Array<{ key: keyof BudgetConfig; offset: string }> = [\n\t{ key: \"dailyUsd\", offset: \"-1 day\" },\n\t{ key: \"weeklyUsd\", offset: \"-7 days\" },\n\t{ key: \"monthlyUsd\", offset: \"-30 days\" },\n];\n\n/**\n * Check whether cumulative cost for a job exceeds any configured budget cap.\n *\n * Queries the runs table for cost accrued within each budget period (daily, weekly, monthly),\n * excluding non-spending statuses (locked, paused, budget-exceeded).\n *\n * @param jobId - The job identifier to check\n * @param budget - Budget configuration with optional period caps\n * @param dbPath - Optional database path override for testing\n * @returns true if any budget cap is met or exceeded, false otherwise\n */\nexport function checkBudget(jobId: string, budget: BudgetConfig, dbPath?: string): boolean {\n\tconst db = getDatabase(dbPath);\n\n\tfor (const { key, offset } of PERIOD_OFFSETS) {\n\t\tconst cap = budget[key];\n\t\tif (cap === undefined) continue;\n\n\t\tconst row = db\n\t\t\t.prepare(\n\t\t\t\t`SELECT COALESCE(SUM(cost_usd), 0) as total FROM runs\n\t\t\t\tWHERE job_id = ? AND started_at >= datetime('now', ?)\n\t\t\t\tAND status NOT IN ('locked', 'paused', 'budget-exceeded')`,\n\t\t\t)\n\t\t\t.get(jobId, offset) as { total: number };\n\n\t\tif (row.total >= cap) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get cost summary data from the runs table.\n *\n * When jobId is provided, returns per-day cost breakdown for that job (last 30 days).\n * When no jobId is provided, returns per-job aggregate cost totals across all jobs.\n *\n * @param jobId - Optional job identifier for per-day breakdown\n * @param dbPath - Optional database path override for testing\n * @returns Array of CostSummaryRow (no jobId) or DailyCostRow (with jobId)\n */\nexport function getCostSummary(jobId?: string, dbPath?: string): CostSummaryRow[] | DailyCostRow[] {\n\tconst db = getDatabase(dbPath);\n\n\tif (jobId) {\n\t\treturn db\n\t\t\t.prepare(\n\t\t\t\t`SELECT date(started_at) as day, COUNT(*) as runs,\n\t\t\t\t\tROUND(COALESCE(SUM(cost_usd), 0), 4) as total_cost,\n\t\t\t\t\tCOALESCE(SUM(num_turns), 0) as total_turns\n\t\t\t\tFROM runs WHERE job_id = ? AND cost_usd IS NOT NULL\n\t\t\t\tGROUP BY date(started_at) ORDER BY day DESC LIMIT 30`,\n\t\t\t)\n\t\t\t.all(jobId) as DailyCostRow[];\n\t}\n\n\treturn db\n\t\t.prepare(\n\t\t\t`SELECT job_id, COUNT(*) as runs,\n\t\t\t\tROUND(COALESCE(SUM(cost_usd), 0), 4) as total_cost,\n\t\t\t\tROUND(COALESCE(AVG(cost_usd), 0), 4) as avg_cost,\n\t\t\t\tCOALESCE(SUM(num_turns), 0) as total_turns\n\t\t\tFROM runs WHERE cost_usd IS NOT NULL\n\t\t\tGROUP BY job_id ORDER BY total_cost DESC`,\n\t\t)\n\t\t.all() as CostSummaryRow[];\n}\n"],"mappings":";;;;;AAqCA,IAAM,iBAAqE;AAAA,EAC1E,EAAE,KAAK,YAAY,QAAQ,SAAS;AAAA,EACpC,EAAE,KAAK,aAAa,QAAQ,UAAU;AAAA,EACtC,EAAE,KAAK,cAAc,QAAQ,WAAW;AACzC;AAaO,SAAS,YAAY,OAAe,QAAsB,QAA0B;AAC1F,QAAM,KAAK,YAAY,MAAM;AAE7B,aAAW,EAAE,KAAK,OAAO,KAAK,gBAAgB;AAC7C,UAAM,MAAM,OAAO,GAAG;AACtB,QAAI,QAAQ,OAAW;AAEvB,UAAM,MAAM,GACV;AAAA,MACA;AAAA;AAAA;AAAA,IAGD,EACC,IAAI,OAAO,MAAM;AAEnB,QAAI,IAAI,SAAS,KAAK;AACrB,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAYO,SAAS,eAAe,OAAgB,QAAoD;AAClG,QAAM,KAAK,YAAY,MAAM;AAE7B,MAAI,OAAO;AACV,WAAO,GACL;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKD,EACC,IAAI,KAAK;AAAA,EACZ;AAEA,SAAO,GACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,EACC,IAAI;AACP;","names":[]}
@@ -0,0 +1,33 @@
1
+ import {
2
+ SchedulerError
3
+ } from "./chunk-6RYMWH5M.js";
4
+
5
+ // src/platform/detect.ts
6
+ function detectPlatform() {
7
+ const p = process.platform;
8
+ if (p === "linux" || p === "darwin" || p === "win32") return p;
9
+ throw new SchedulerError(
10
+ p,
11
+ `Unsupported platform: ${p}. Only linux, darwin, and win32 are supported.`
12
+ );
13
+ }
14
+
15
+ // src/platform/scheduler.ts
16
+ async function createScheduler() {
17
+ const platform = detectPlatform();
18
+ if (platform === "linux") {
19
+ const { CrontabScheduler } = await import("./crontab-MAJ52FOK.js");
20
+ return new CrontabScheduler();
21
+ }
22
+ if (platform === "win32") {
23
+ const { SchtasksScheduler } = await import("./schtasks-4V2IFD3A.js");
24
+ return new SchtasksScheduler();
25
+ }
26
+ const { LaunchdScheduler } = await import("./launchd-7F27BIZB.js");
27
+ return new LaunchdScheduler();
28
+ }
29
+
30
+ export {
31
+ createScheduler
32
+ };
33
+ //# sourceMappingURL=chunk-S6W4SURF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/detect.ts","../src/platform/scheduler.ts"],"sourcesContent":["import { SchedulerError } from \"../util/errors.js\";\n\nexport type Platform = \"linux\" | \"darwin\" | \"win32\";\n\n/**\n * Detect the current platform. Returns \"linux\", \"darwin\", or \"win32\".\n * Throws SchedulerError for unsupported platforms.\n */\nexport function detectPlatform(): Platform {\n\tconst p = process.platform;\n\tif (p === \"linux\" || p === \"darwin\" || p === \"win32\") return p;\n\tthrow new SchedulerError(\n\t\tp,\n\t\t`Unsupported platform: ${p}. Only linux, darwin, and win32 are supported.`,\n\t);\n}\n","import type { JobConfig } from \"../core/types.js\";\nimport { detectPlatform } from \"./detect.js\";\n\nexport interface RegisteredJob {\n\tjobId: string;\n\tschedule: string;\n\tcommand: string;\n}\n\nexport interface Scheduler {\n\tregister(job: JobConfig, env?: Record<string, string>): Promise<void>;\n\tunregister(jobId: string): Promise<void>;\n\tisRegistered(jobId: string): Promise<boolean>;\n\tlist(): Promise<RegisteredJob[]>;\n}\n\n/**\n * Factory function that returns the correct Scheduler implementation\n * for the current platform: CrontabScheduler on Linux, LaunchdScheduler on macOS,\n * SchtasksScheduler on Windows.\n */\nexport async function createScheduler(): Promise<Scheduler> {\n\tconst platform = detectPlatform();\n\tif (platform === \"linux\") {\n\t\tconst { CrontabScheduler } = await import(\"./crontab.js\");\n\t\treturn new CrontabScheduler();\n\t}\n\tif (platform === \"win32\") {\n\t\tconst { SchtasksScheduler } = await import(\"./schtasks.js\");\n\t\treturn new SchtasksScheduler();\n\t}\n\tconst { LaunchdScheduler } = await import(\"./launchd.js\");\n\treturn new LaunchdScheduler();\n}\n"],"mappings":";;;;;AAQO,SAAS,iBAA2B;AAC1C,QAAM,IAAI,QAAQ;AAClB,MAAI,MAAM,WAAW,MAAM,YAAY,MAAM,QAAS,QAAO;AAC7D,QAAM,IAAI;AAAA,IACT;AAAA,IACA,yBAAyB,CAAC;AAAA,EAC3B;AACD;;;ACMA,eAAsB,kBAAsC;AAC3D,QAAM,WAAW,eAAe;AAChC,MAAI,aAAa,SAAS;AACzB,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,uBAAc;AACxD,WAAO,IAAI,iBAAiB;AAAA,EAC7B;AACA,MAAI,aAAa,SAAS;AACzB,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,wBAAe;AAC1D,WAAO,IAAI,kBAAkB;AAAA,EAC9B;AACA,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,uBAAc;AACxD,SAAO,IAAI,iBAAiB;AAC7B;","names":[]}