@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,118 @@
1
+ import {
2
+ createScheduler
3
+ } from "./chunk-DVZC42TL.js";
4
+ import {
5
+ execCommand
6
+ } from "./chunk-3NEANSUS.js";
7
+ import {
8
+ describeSchedule,
9
+ getNextRuns,
10
+ validateCronExpression
11
+ } from "./chunk-TORYFKPK.js";
12
+ import {
13
+ createJob
14
+ } from "./chunk-BY5YEOVG.js";
15
+ import "./chunk-2D5E23XA.js";
16
+ import "./chunk-E3XVLTT4.js";
17
+ import "./chunk-H2MUDYMW.js";
18
+ import "./chunk-AWLSYOVF.js";
19
+
20
+ // src/cli/commands/create.ts
21
+ import { readFile, stat } from "fs/promises";
22
+ async function ensureRepoExists(repoPath, githubRepo) {
23
+ try {
24
+ const s = await stat(repoPath);
25
+ if (!s.isDirectory()) {
26
+ throw new Error(`${repoPath} exists but is not a directory`);
27
+ }
28
+ await execCommand("git", ["-C", repoPath, "rev-parse", "--git-dir"]);
29
+ } catch (err) {
30
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
31
+ if (!githubRepo) {
32
+ throw new Error(
33
+ `Repository path ${repoPath} does not exist. Provide --github-repo <owner/repo> to clone automatically.`
34
+ );
35
+ }
36
+ console.log(`Cloning ${githubRepo} to ${repoPath}...`);
37
+ await execCommand("gh", ["repo", "clone", githubRepo, repoPath]);
38
+ return;
39
+ }
40
+ throw err;
41
+ }
42
+ }
43
+ async function createCommand(args) {
44
+ const name = args.name;
45
+ const repoPath = args.repo;
46
+ const schedule = args.schedule;
47
+ if (!name || !repoPath || !schedule) {
48
+ console.error(
49
+ "Usage: claude-auto create --name <name> --repo <path> --schedule <cron> [options]"
50
+ );
51
+ throw new Error("Missing required arguments: --name, --repo, and --schedule are required");
52
+ }
53
+ const branch = args.branch ?? "main";
54
+ const timezone = args.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
55
+ const focusStr = args.focus ?? "open-issues,bug-discovery";
56
+ validateCronExpression(schedule);
57
+ await ensureRepoExists(repoPath, args.githubRepo);
58
+ let systemPrompt;
59
+ if (args.systemPromptFile) {
60
+ systemPrompt = await readFile(args.systemPromptFile, "utf-8");
61
+ }
62
+ const focus = focusStr.split(",").map((s) => s.trim());
63
+ const maxTurns = args.maxTurns ? Number(args.maxTurns) : 50;
64
+ const maxBudgetUsd = args.maxBudget ? Number(args.maxBudget) : 5;
65
+ const noNewDependencies = Boolean(args.noNewDeps);
66
+ const noArchitectureChanges = Boolean(args.noArchChanges);
67
+ const bugFixOnly = Boolean(args.bugFixOnly);
68
+ const restrictToPaths = args.restrictPaths ? String(args.restrictPaths).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
69
+ const defaultTriggers = { onSuccess: true, onFailure: true, onNoChanges: false, onLocked: false };
70
+ const notifications = {};
71
+ if (args.notifyDiscord) {
72
+ notifications.discord = { webhookUrl: args.notifyDiscord, ...defaultTriggers };
73
+ }
74
+ if (args.notifySlack) {
75
+ notifications.slack = { webhookUrl: args.notifySlack, ...defaultTriggers };
76
+ }
77
+ if (args.notifyTelegram) {
78
+ const telegramStr = args.notifyTelegram;
79
+ const colonIndex = telegramStr.indexOf(":");
80
+ if (colonIndex > 0) {
81
+ notifications.telegram = {
82
+ botToken: telegramStr.slice(0, colonIndex),
83
+ chatId: telegramStr.slice(colonIndex + 1),
84
+ ...defaultTriggers
85
+ };
86
+ }
87
+ }
88
+ const input = {
89
+ name,
90
+ repo: { path: repoPath, branch, remote: "origin" },
91
+ schedule: { cron: schedule, timezone },
92
+ focus,
93
+ systemPrompt,
94
+ guardrails: {
95
+ maxTurns,
96
+ maxBudgetUsd,
97
+ noNewDependencies,
98
+ noArchitectureChanges,
99
+ bugFixOnly,
100
+ restrictToPaths
101
+ },
102
+ notifications,
103
+ enabled: true
104
+ };
105
+ const config = await createJob(input);
106
+ const scheduler = await createScheduler();
107
+ await scheduler.register(config);
108
+ const description = describeSchedule(schedule);
109
+ const nextRuns = getNextRuns(schedule, timezone, 3).map((d) => d.toLocaleString()).join(", ");
110
+ console.log(`Job created: ${config.id}`);
111
+ console.log(`Schedule: ${description}`);
112
+ console.log(`Next runs: ${nextRuns}`);
113
+ console.log(`Config: ~/.claude-auto/jobs/${config.id}/config.yaml`);
114
+ }
115
+ export {
116
+ createCommand
117
+ };
118
+ //# sourceMappingURL=create-U5WYKTD4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/create.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport { createJob } 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 { execCommand } from \"../../util/exec.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Ensure that the repository path exists and is a git repo.\n * If the path does not exist and a GitHub repo is provided, clone it via gh.\n */\nasync function ensureRepoExists(repoPath: string, githubRepo?: string): Promise<void> {\n\ttry {\n\t\tconst s = await stat(repoPath);\n\t\tif (!s.isDirectory()) {\n\t\t\tthrow new Error(`${repoPath} exists but is not a directory`);\n\t\t}\n\t\t// Verify it's a git repo\n\t\tawait execCommand(\"git\", [\"-C\", repoPath, \"rev-parse\", \"--git-dir\"]);\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tif (!githubRepo) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Repository path ${repoPath} does not exist. Provide --github-repo <owner/repo> to clone automatically.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconsole.log(`Cloning ${githubRepo} to ${repoPath}...`);\n\t\t\tawait execCommand(\"gh\", [\"repo\", \"clone\", githubRepo, repoPath]);\n\t\t\treturn;\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/**\n * Create a new autonomous cron job. Validates all inputs, optionally clones the repo,\n * creates the job config, and registers with the system scheduler.\n */\nexport async function createCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst name = args.name as string | undefined;\n\tconst repoPath = args.repo as string | undefined;\n\tconst schedule = args.schedule as string | undefined;\n\n\tif (!name || !repoPath || !schedule) {\n\t\tconsole.error(\n\t\t\t\"Usage: claude-auto create --name <name> --repo <path> --schedule <cron> [options]\",\n\t\t);\n\t\tthrow new Error(\"Missing required arguments: --name, --repo, and --schedule are required\");\n\t}\n\n\t// Extract optional args with defaults\n\tconst branch = (args.branch as string) ?? \"main\";\n\tconst timezone = (args.timezone as string) ?? Intl.DateTimeFormat().resolvedOptions().timeZone;\n\tconst focusStr = (args.focus as string) ?? \"open-issues,bug-discovery\";\n\n\t// Validate cron expression (throws CronValidationError if invalid)\n\tvalidateCronExpression(schedule);\n\n\t// Ensure repo exists (optionally clone)\n\tawait ensureRepoExists(repoPath, args.githubRepo as string | undefined);\n\n\t// Read system prompt from file if provided\n\tlet systemPrompt: string | undefined;\n\tif (args.systemPromptFile) {\n\t\tsystemPrompt = await readFile(args.systemPromptFile as string, \"utf-8\");\n\t}\n\n\t// Parse focus areas\n\tconst focus = focusStr.split(\",\").map((s) => s.trim()) as JobConfig[\"focus\"];\n\n\t// Parse guardrails\n\tconst maxTurns = args.maxTurns ? Number(args.maxTurns) : 50;\n\tconst maxBudgetUsd = args.maxBudget ? Number(args.maxBudget) : 5.0;\n\tconst noNewDependencies = Boolean(args.noNewDeps);\n\tconst noArchitectureChanges = Boolean(args.noArchChanges);\n\tconst bugFixOnly = Boolean(args.bugFixOnly);\n\tconst restrictToPaths = args.restrictPaths\n\t\t? String(args.restrictPaths)\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter(Boolean)\n\t\t: undefined;\n\n\t// Parse notifications (provide default trigger values matching Zod schema defaults)\n\tconst defaultTriggers = { onSuccess: true, onFailure: true, onNoChanges: false, onLocked: false };\n\tconst notifications: JobConfig[\"notifications\"] = {};\n\tif (args.notifyDiscord) {\n\t\tnotifications.discord = { webhookUrl: args.notifyDiscord as string, ...defaultTriggers };\n\t}\n\tif (args.notifySlack) {\n\t\tnotifications.slack = { webhookUrl: args.notifySlack as string, ...defaultTriggers };\n\t}\n\tif (args.notifyTelegram) {\n\t\tconst telegramStr = args.notifyTelegram as string;\n\t\tconst colonIndex = telegramStr.indexOf(\":\");\n\t\tif (colonIndex > 0) {\n\t\t\tnotifications.telegram = {\n\t\t\t\tbotToken: telegramStr.slice(0, colonIndex),\n\t\t\t\tchatId: telegramStr.slice(colonIndex + 1),\n\t\t\t\t...defaultTriggers,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Build job input\n\tconst input: Omit<JobConfig, \"id\"> = {\n\t\tname,\n\t\trepo: { path: repoPath, branch, remote: \"origin\" },\n\t\tschedule: { cron: schedule, timezone },\n\t\tfocus,\n\t\tsystemPrompt,\n\t\tguardrails: {\n\t\t\tmaxTurns,\n\t\t\tmaxBudgetUsd,\n\t\t\tnoNewDependencies,\n\t\t\tnoArchitectureChanges,\n\t\t\tbugFixOnly,\n\t\t\trestrictToPaths,\n\t\t},\n\t\tnotifications,\n\t\tenabled: true,\n\t};\n\n\t// Create job config on disk\n\tconst config = await createJob(input);\n\n\t// Register with system scheduler\n\tconst scheduler = await createScheduler();\n\tawait scheduler.register(config);\n\n\t// Print confirmation\n\tconst description = describeSchedule(schedule);\n\tconst nextRuns = getNextRuns(schedule, timezone, 3)\n\t\t.map((d) => d.toLocaleString())\n\t\t.join(\", \");\n\n\tconsole.log(`Job created: ${config.id}`);\n\tconsole.log(`Schedule: ${description}`);\n\tconsole.log(`Next runs: ${nextRuns}`);\n\tconsole.log(`Config: ~/.claude-auto/jobs/${config.id}/config.yaml`);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,118 @@
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-AWLSYOVF.js";
10
+
11
+ // src/platform/crontab.ts
12
+ import { dirname, join } from "path";
13
+ import { fileURLToPath } from "url";
14
+ var MARKER_PREFIX = "# claude-auto:";
15
+ function buildEntryBlock(jobId, cronExpr, command, timezone) {
16
+ const logPath = `${paths.jobLogs(jobId)}/cron.log`;
17
+ const lines = [`${MARKER_PREFIX}${jobId}`];
18
+ if (timezone && timezone !== "UTC") {
19
+ lines.push(`CRON_TZ=${timezone}`);
20
+ }
21
+ lines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);
22
+ return lines.join("\n");
23
+ }
24
+ async function readCrontab() {
25
+ try {
26
+ const { stdout } = await execCommand("crontab", ["-l"]);
27
+ return stdout;
28
+ } catch (error) {
29
+ const msg = error instanceof Error ? error.message : String(error);
30
+ if (msg.includes("no crontab")) {
31
+ return "";
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ async function writeCrontab(content) {
37
+ await execCommand("crontab", ["-"], { stdin: content });
38
+ }
39
+ function getRunnerPath() {
40
+ try {
41
+ const currentDir = dirname(fileURLToPath(import.meta.url));
42
+ return join(currentDir, "..", "..", "dist", "claude-auto-run.js");
43
+ } catch {
44
+ return join(process.cwd(), "dist", "claude-auto-run.js");
45
+ }
46
+ }
47
+ var CrontabScheduler = class {
48
+ async register(job, _env) {
49
+ const registered = await this.isRegistered(job.id);
50
+ if (registered) {
51
+ throw new SchedulerError("crontab", `Job "${job.id}" is already registered`);
52
+ }
53
+ const current = await readCrontab();
54
+ const runnerPath = getRunnerPath();
55
+ const command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;
56
+ const block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);
57
+ const updated = `${current.trimEnd()}
58
+ ${block}
59
+ `;
60
+ await writeCrontab(updated);
61
+ }
62
+ async unregister(jobId) {
63
+ const current = await readCrontab();
64
+ const lines = current.split("\n");
65
+ const marker = `${MARKER_PREFIX}${jobId}`;
66
+ const filtered = [];
67
+ let skipping = false;
68
+ for (const line of lines) {
69
+ if (line.trim() === marker) {
70
+ skipping = true;
71
+ continue;
72
+ }
73
+ if (skipping && (line.startsWith("CRON_TZ=") || !line.startsWith("#") && line.trim() !== "")) {
74
+ continue;
75
+ }
76
+ skipping = false;
77
+ filtered.push(line);
78
+ }
79
+ await writeCrontab(filtered.join("\n"));
80
+ }
81
+ async isRegistered(jobId) {
82
+ const content = await readCrontab();
83
+ return content.includes(`${MARKER_PREFIX}${jobId}`);
84
+ }
85
+ async list() {
86
+ const content = await readCrontab();
87
+ const lines = content.split("\n");
88
+ const jobs = [];
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const line = lines[i];
91
+ if (line.startsWith(MARKER_PREFIX)) {
92
+ const jobId = line.slice(MARKER_PREFIX.length).trim();
93
+ let entryLine = "";
94
+ let nextIdx = i + 1;
95
+ if (nextIdx < lines.length && lines[nextIdx].startsWith("CRON_TZ=")) {
96
+ nextIdx++;
97
+ }
98
+ if (nextIdx < lines.length) {
99
+ entryLine = lines[nextIdx];
100
+ }
101
+ if (entryLine.trim()) {
102
+ const parts = entryLine.trim().split(/\s+/);
103
+ if (parts.length >= 6) {
104
+ const schedule = parts.slice(0, 5).join(" ");
105
+ const command = parts.slice(5).join(" ");
106
+ jobs.push({ jobId, schedule, command });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return jobs;
112
+ }
113
+ };
114
+ export {
115
+ CrontabScheduler,
116
+ buildEntryBlock
117
+ };
118
+ //# sourceMappingURL=crontab-CDMC2FDT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/crontab.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\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\nconst MARKER_PREFIX = \"# claude-auto:\";\n\n/**\n * Build a crontab entry block for a claude-auto job.\n *\n * Format:\n * # claude-auto:{jobId}\n * CRON_TZ={timezone} (only if timezone !== \"UTC\")\n * {cronExpr} {command} >> {logPath} 2>&1\n */\nexport function buildEntryBlock(\n\tjobId: string,\n\tcronExpr: string,\n\tcommand: string,\n\ttimezone?: string,\n): string {\n\tconst logPath = `${paths.jobLogs(jobId)}/cron.log`;\n\tconst lines: string[] = [`${MARKER_PREFIX}${jobId}`];\n\tif (timezone && timezone !== \"UTC\") {\n\t\tlines.push(`CRON_TZ=${timezone}`);\n\t}\n\tlines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Read the current user's crontab. Returns empty string if none exists.\n */\nasync function readCrontab(): Promise<string> {\n\ttry {\n\t\tconst { stdout } = await execCommand(\"crontab\", [\"-l\"]);\n\t\treturn stdout;\n\t} catch (error: unknown) {\n\t\t// \"no crontab for user\" is not an error for our purposes\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tif (msg.includes(\"no crontab\")) {\n\t\t\treturn \"\";\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Write the full crontab content via stdin to `crontab -`.\n */\nasync function writeCrontab(content: string): Promise<void> {\n\tawait execCommand(\"crontab\", [\"-\"], { stdin: content });\n}\n\n/**\n * Resolve the runner script path. For now, compute 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 * CrontabScheduler implements the Scheduler interface for Linux systems.\n * Uses comment-tagged crontab entries for identification and CRUD.\n */\nexport class CrontabScheduler 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(\"crontab\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst current = await readCrontab();\n\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;\n\t\tconst block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);\n\n\t\tconst updated = `${current.trimEnd()}\\n${block}\\n`;\n\t\tawait writeCrontab(updated);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst current = await readCrontab();\n\t\tconst lines = current.split(\"\\n\");\n\t\tconst marker = `${MARKER_PREFIX}${jobId}`;\n\n\t\tconst filtered: string[] = [];\n\t\tlet skipping = false;\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.trim() === marker) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tskipping &&\n\t\t\t\t(line.startsWith(\"CRON_TZ=\") || (!line.startsWith(\"#\") && line.trim() !== \"\"))\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t\tfiltered.push(line);\n\t\t}\n\n\t\tawait writeCrontab(filtered.join(\"\\n\"));\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst content = await readCrontab();\n\t\treturn content.includes(`${MARKER_PREFIX}${jobId}`);\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst content = await readCrontab();\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst jobs: RegisteredJob[] = [];\n\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i];\n\t\t\tif (line.startsWith(MARKER_PREFIX)) {\n\t\t\t\tconst jobId = line.slice(MARKER_PREFIX.length).trim();\n\n\t\t\t\t// Look ahead: skip optional CRON_TZ line, then find the cron entry\n\t\t\t\tlet entryLine = \"\";\n\t\t\t\tlet nextIdx = i + 1;\n\n\t\t\t\tif (nextIdx < lines.length && lines[nextIdx].startsWith(\"CRON_TZ=\")) {\n\t\t\t\t\tnextIdx++;\n\t\t\t\t}\n\n\t\t\t\tif (nextIdx < lines.length) {\n\t\t\t\t\tentryLine = lines[nextIdx];\n\t\t\t\t}\n\n\t\t\t\tif (entryLine.trim()) {\n\t\t\t\t\t// Parse the cron fields (first 5 space-separated fields) and the command (rest)\n\t\t\t\t\tconst parts = entryLine.trim().split(/\\s+/);\n\t\t\t\t\tif (parts.length >= 6) {\n\t\t\t\t\t\tconst schedule = parts.slice(0, 5).join(\" \");\n\t\t\t\t\t\tconst command = parts.slice(5).join(\" \");\n\t\t\t\t\t\tjobs.push({ jobId, schedule, command });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,118 @@
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-6RYMWH5M.js";
10
+
11
+ // src/platform/crontab.ts
12
+ import { dirname, join } from "path";
13
+ import { fileURLToPath } from "url";
14
+ var MARKER_PREFIX = "# claude-auto:";
15
+ function buildEntryBlock(jobId, cronExpr, command, timezone) {
16
+ const logPath = `${paths.jobLogs(jobId)}/cron.log`;
17
+ const lines = [`${MARKER_PREFIX}${jobId}`];
18
+ if (timezone && timezone !== "UTC") {
19
+ lines.push(`CRON_TZ=${timezone}`);
20
+ }
21
+ lines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);
22
+ return lines.join("\n");
23
+ }
24
+ async function readCrontab() {
25
+ try {
26
+ const { stdout } = await execCommand("crontab", ["-l"]);
27
+ return stdout;
28
+ } catch (error) {
29
+ const msg = error instanceof Error ? error.message : String(error);
30
+ if (msg.includes("no crontab")) {
31
+ return "";
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ async function writeCrontab(content) {
37
+ await execCommand("crontab", ["-"], { stdin: content });
38
+ }
39
+ function getRunnerPath() {
40
+ try {
41
+ const currentDir = dirname(fileURLToPath(import.meta.url));
42
+ return join(currentDir, "..", "..", "dist", "claude-auto-run.js");
43
+ } catch {
44
+ return join(process.cwd(), "dist", "claude-auto-run.js");
45
+ }
46
+ }
47
+ var CrontabScheduler = class {
48
+ async register(job, _env) {
49
+ const registered = await this.isRegistered(job.id);
50
+ if (registered) {
51
+ throw new SchedulerError("crontab", `Job "${job.id}" is already registered`);
52
+ }
53
+ const current = await readCrontab();
54
+ const runnerPath = getRunnerPath();
55
+ const command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;
56
+ const block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);
57
+ const updated = `${current.trimEnd()}
58
+ ${block}
59
+ `;
60
+ await writeCrontab(updated);
61
+ }
62
+ async unregister(jobId) {
63
+ const current = await readCrontab();
64
+ const lines = current.split("\n");
65
+ const marker = `${MARKER_PREFIX}${jobId}`;
66
+ const filtered = [];
67
+ let skipping = false;
68
+ for (const line of lines) {
69
+ if (line.trim() === marker) {
70
+ skipping = true;
71
+ continue;
72
+ }
73
+ if (skipping && (line.startsWith("CRON_TZ=") || !line.startsWith("#") && line.trim() !== "")) {
74
+ continue;
75
+ }
76
+ skipping = false;
77
+ filtered.push(line);
78
+ }
79
+ await writeCrontab(filtered.join("\n"));
80
+ }
81
+ async isRegistered(jobId) {
82
+ const content = await readCrontab();
83
+ return content.includes(`${MARKER_PREFIX}${jobId}`);
84
+ }
85
+ async list() {
86
+ const content = await readCrontab();
87
+ const lines = content.split("\n");
88
+ const jobs = [];
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const line = lines[i];
91
+ if (line.startsWith(MARKER_PREFIX)) {
92
+ const jobId = line.slice(MARKER_PREFIX.length).trim();
93
+ let entryLine = "";
94
+ let nextIdx = i + 1;
95
+ if (nextIdx < lines.length && lines[nextIdx].startsWith("CRON_TZ=")) {
96
+ nextIdx++;
97
+ }
98
+ if (nextIdx < lines.length) {
99
+ entryLine = lines[nextIdx];
100
+ }
101
+ if (entryLine.trim()) {
102
+ const parts = entryLine.trim().split(/\s+/);
103
+ if (parts.length >= 6) {
104
+ const schedule = parts.slice(0, 5).join(" ");
105
+ const command = parts.slice(5).join(" ");
106
+ jobs.push({ jobId, schedule, command });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return jobs;
112
+ }
113
+ };
114
+ export {
115
+ CrontabScheduler,
116
+ buildEntryBlock
117
+ };
118
+ //# sourceMappingURL=crontab-MAJ52FOK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/crontab.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\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\nconst MARKER_PREFIX = \"# claude-auto:\";\n\n/**\n * Build a crontab entry block for a claude-auto job.\n *\n * Format:\n * # claude-auto:{jobId}\n * CRON_TZ={timezone} (only if timezone !== \"UTC\")\n * {cronExpr} {command} >> {logPath} 2>&1\n */\nexport function buildEntryBlock(\n\tjobId: string,\n\tcronExpr: string,\n\tcommand: string,\n\ttimezone?: string,\n): string {\n\tconst logPath = `${paths.jobLogs(jobId)}/cron.log`;\n\tconst lines: string[] = [`${MARKER_PREFIX}${jobId}`];\n\tif (timezone && timezone !== \"UTC\") {\n\t\tlines.push(`CRON_TZ=${timezone}`);\n\t}\n\tlines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Read the current user's crontab. Returns empty string if none exists.\n */\nasync function readCrontab(): Promise<string> {\n\ttry {\n\t\tconst { stdout } = await execCommand(\"crontab\", [\"-l\"]);\n\t\treturn stdout;\n\t} catch (error: unknown) {\n\t\t// \"no crontab for user\" is not an error for our purposes\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tif (msg.includes(\"no crontab\")) {\n\t\t\treturn \"\";\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Write the full crontab content via stdin to `crontab -`.\n */\nasync function writeCrontab(content: string): Promise<void> {\n\tawait execCommand(\"crontab\", [\"-\"], { stdin: content });\n}\n\n/**\n * Resolve the runner script path. For now, compute 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 * CrontabScheduler implements the Scheduler interface for Linux systems.\n * Uses comment-tagged crontab entries for identification and CRUD.\n */\nexport class CrontabScheduler 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(\"crontab\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst current = await readCrontab();\n\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;\n\t\tconst block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);\n\n\t\tconst updated = `${current.trimEnd()}\\n${block}\\n`;\n\t\tawait writeCrontab(updated);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst current = await readCrontab();\n\t\tconst lines = current.split(\"\\n\");\n\t\tconst marker = `${MARKER_PREFIX}${jobId}`;\n\n\t\tconst filtered: string[] = [];\n\t\tlet skipping = false;\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.trim() === marker) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tskipping &&\n\t\t\t\t(line.startsWith(\"CRON_TZ=\") || (!line.startsWith(\"#\") && line.trim() !== \"\"))\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t\tfiltered.push(line);\n\t\t}\n\n\t\tawait writeCrontab(filtered.join(\"\\n\"));\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst content = await readCrontab();\n\t\treturn content.includes(`${MARKER_PREFIX}${jobId}`);\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst content = await readCrontab();\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst jobs: RegisteredJob[] = [];\n\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i];\n\t\t\tif (line.startsWith(MARKER_PREFIX)) {\n\t\t\t\tconst jobId = line.slice(MARKER_PREFIX.length).trim();\n\n\t\t\t\t// Look ahead: skip optional CRON_TZ line, then find the cron entry\n\t\t\t\tlet entryLine = \"\";\n\t\t\t\tlet nextIdx = i + 1;\n\n\t\t\t\tif (nextIdx < lines.length && lines[nextIdx].startsWith(\"CRON_TZ=\")) {\n\t\t\t\t\tnextIdx++;\n\t\t\t\t}\n\n\t\t\t\tif (nextIdx < lines.length) {\n\t\t\t\t\tentryLine = lines[nextIdx];\n\t\t\t\t}\n\n\t\t\t\tif (entryLine.trim()) {\n\t\t\t\t\t// Parse the cron fields (first 5 space-separated fields) and the command (rest)\n\t\t\t\t\tconst parts = entryLine.trim().split(/\\s+/);\n\t\t\t\t\tif (parts.length >= 6) {\n\t\t\t\t\t\tconst schedule = parts.slice(0, 5).join(\" \");\n\t\t\t\t\t\tconst command = parts.slice(5).join(\" \");\n\t\t\t\t\t\tjobs.push({ jobId, schedule, command });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,12 @@
1
+ import {
2
+ CrontabScheduler,
3
+ buildEntryBlock
4
+ } from "./chunk-WYU476R2.js";
5
+ import "./chunk-3NEANSUS.js";
6
+ import "./chunk-H2MUDYMW.js";
7
+ import "./chunk-YMO45Z6G.js";
8
+ export {
9
+ CrontabScheduler,
10
+ buildEntryBlock
11
+ };
12
+ //# sourceMappingURL=crontab-PNEWANLW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,134 @@
1
+ import {
2
+ createScheduler
3
+ } from "./chunk-DVZC42TL.js";
4
+ import {
5
+ describeSchedule,
6
+ getNextRuns,
7
+ validateCronExpression
8
+ } from "./chunk-TORYFKPK.js";
9
+ import {
10
+ readJob,
11
+ updateJob
12
+ } from "./chunk-BY5YEOVG.js";
13
+ import "./chunk-2D5E23XA.js";
14
+ import "./chunk-E3XVLTT4.js";
15
+ import "./chunk-H2MUDYMW.js";
16
+ import "./chunk-AWLSYOVF.js";
17
+
18
+ // src/cli/commands/edit.ts
19
+ async function editCommand(args) {
20
+ const jobId = args.jobId;
21
+ if (!jobId) {
22
+ console.error(
23
+ "Usage: claude-auto edit <job-id> --name <value> [--schedule <cron>] [--timezone <tz>] [--branch <branch>] [--max-turns <n>] [--max-budget <n>] [--focus <csv>]"
24
+ );
25
+ throw new Error("Missing required argument: job-id");
26
+ }
27
+ let config;
28
+ try {
29
+ config = await readJob(jobId);
30
+ } catch (err) {
31
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
32
+ console.error(`Job ${jobId} not found.`);
33
+ throw err;
34
+ }
35
+ throw err;
36
+ }
37
+ const updates = {};
38
+ let scheduleChanged = false;
39
+ const confirmations = [];
40
+ if (args.name !== void 0) {
41
+ updates.name = args.name;
42
+ confirmations.push(`name -> ${args.name}`);
43
+ }
44
+ if (args.schedule !== void 0) {
45
+ const cronExpr = String(args.schedule);
46
+ try {
47
+ validateCronExpression(cronExpr);
48
+ } catch (err) {
49
+ console.error(`Invalid schedule: ${err instanceof Error ? err.message : String(err)}`);
50
+ return;
51
+ }
52
+ updates.schedule = { ...config.schedule, cron: cronExpr };
53
+ scheduleChanged = true;
54
+ confirmations.push(`schedule -> ${cronExpr}`);
55
+ }
56
+ if (args.timezone !== void 0) {
57
+ const currentSchedule = updates.schedule ?? { ...config.schedule };
58
+ updates.schedule = { ...currentSchedule, timezone: String(args.timezone) };
59
+ scheduleChanged = true;
60
+ confirmations.push(`timezone -> ${args.timezone}`);
61
+ }
62
+ if (args.branch !== void 0) {
63
+ updates.repo = { ...config.repo, branch: String(args.branch) };
64
+ confirmations.push(`branch -> ${args.branch}`);
65
+ }
66
+ if (args.maxTurns !== void 0) {
67
+ const n = Number.parseInt(String(args.maxTurns), 10);
68
+ if (Number.isNaN(n) || n <= 0) {
69
+ console.error("Invalid --max-turns: must be a positive integer");
70
+ return;
71
+ }
72
+ updates.guardrails = { ...config.guardrails, maxTurns: n };
73
+ confirmations.push(`maxTurns -> ${n}`);
74
+ }
75
+ if (args.maxBudget !== void 0) {
76
+ const n = Number.parseFloat(String(args.maxBudget));
77
+ if (Number.isNaN(n) || n <= 0) {
78
+ console.error("Invalid --max-budget: must be a positive number");
79
+ return;
80
+ }
81
+ updates.guardrails = {
82
+ ...updates.guardrails ?? config.guardrails,
83
+ maxBudgetUsd: n
84
+ };
85
+ confirmations.push(`maxBudgetUsd -> ${n}`);
86
+ }
87
+ if (args.focus !== void 0) {
88
+ const validFocusAreas = /* @__PURE__ */ new Set(["open-issues", "bug-discovery", "features", "documentation"]);
89
+ const focusItems = String(args.focus).split(",").map((s) => s.trim());
90
+ const invalid = focusItems.filter((f) => !validFocusAreas.has(f));
91
+ if (invalid.length > 0) {
92
+ console.error(
93
+ `Invalid focus area(s): ${invalid.join(", ")}. Valid: ${[...validFocusAreas].join(", ")}`
94
+ );
95
+ return;
96
+ }
97
+ updates.focus = focusItems;
98
+ confirmations.push(`focus -> ${focusItems.join(", ")}`);
99
+ }
100
+ if (confirmations.length === 0) {
101
+ console.log(`Job ${jobId} - ${config.name}`);
102
+ console.log(` Schedule: ${config.schedule.cron} (${config.schedule.timezone})`);
103
+ console.log(` Repo: ${config.repo.path} (${config.repo.branch})`);
104
+ console.log(` Focus: ${config.focus.join(", ")}`);
105
+ console.log(` Max turns: ${config.guardrails.maxTurns}`);
106
+ console.log(` Max budget: $${config.guardrails.maxBudgetUsd}`);
107
+ console.log(` Status: ${config.enabled ? "active" : "paused"}`);
108
+ console.log(
109
+ "\nUsage: claude-auto edit <job-id> --name <value> [--schedule <cron>] [--timezone <tz>] [--branch <branch>] [--max-turns <n>] [--max-budget <n>] [--focus <csv>]"
110
+ );
111
+ return;
112
+ }
113
+ const updatedConfig = await updateJob(jobId, updates);
114
+ if (scheduleChanged && config.enabled) {
115
+ const scheduler = await createScheduler();
116
+ try {
117
+ await scheduler.unregister(jobId);
118
+ } catch {
119
+ }
120
+ await scheduler.register(updatedConfig);
121
+ const scheduleObj = updatedConfig.schedule;
122
+ const description = describeSchedule(scheduleObj.cron);
123
+ const nextRuns = getNextRuns(scheduleObj.cron, scheduleObj.timezone, 1);
124
+ const nextRunStr = nextRuns.length > 0 ? nextRuns[0].toLocaleString() : "unknown";
125
+ console.log(`Updated job ${jobId}: ${confirmations.join(", ")}`);
126
+ console.log(`Schedule updated to: ${description}. Next run: ${nextRunStr}`);
127
+ } else {
128
+ console.log(`Updated job ${jobId}: ${confirmations.join(", ")}`);
129
+ }
130
+ }
131
+ export {
132
+ editCommand
133
+ };
134
+ //# sourceMappingURL=edit-77E3ZQHM.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,13 @@
1
+ import {
2
+ editCommand
3
+ } from "./chunk-PFU5YLRH.js";
4
+ import "./chunk-QLRCFKLU.js";
5
+ import "./chunk-D4MBOIYQ.js";
6
+ import "./chunk-24PS2XSV.js";
7
+ import "./chunk-E3XVLTT4.js";
8
+ import "./chunk-H2MUDYMW.js";
9
+ import "./chunk-YMO45Z6G.js";
10
+ export {
11
+ editCommand
12
+ };
13
+ //# sourceMappingURL=edit-RVPRAAQ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}