@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,115 @@
1
+ import {
2
+ execCommand
3
+ } from "./chunk-3NEANSUS.js";
4
+ import {
5
+ createScheduler
6
+ } from "./chunk-QLRCFKLU.js";
7
+ import {
8
+ describeSchedule,
9
+ getNextRuns,
10
+ validateCronExpression
11
+ } from "./chunk-D4MBOIYQ.js";
12
+ import {
13
+ createJob
14
+ } from "./chunk-24PS2XSV.js";
15
+
16
+ // src/cli/commands/create.ts
17
+ import { readFile, stat } from "fs/promises";
18
+ async function ensureRepoExists(repoPath, githubRepo) {
19
+ try {
20
+ const s = await stat(repoPath);
21
+ if (!s.isDirectory()) {
22
+ throw new Error(`${repoPath} exists but is not a directory`);
23
+ }
24
+ await execCommand("git", ["-C", repoPath, "rev-parse", "--git-dir"]);
25
+ } catch (err) {
26
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
27
+ if (!githubRepo) {
28
+ throw new Error(
29
+ `Repository path ${repoPath} does not exist. Provide --github-repo <owner/repo> to clone automatically.`
30
+ );
31
+ }
32
+ console.log(`Cloning ${githubRepo} to ${repoPath}...`);
33
+ await execCommand("gh", ["repo", "clone", githubRepo, repoPath]);
34
+ return;
35
+ }
36
+ throw err;
37
+ }
38
+ }
39
+ async function createCommand(args) {
40
+ const name = args.name;
41
+ const repoPath = args.repo;
42
+ const schedule = args.schedule;
43
+ if (!name || !repoPath || !schedule) {
44
+ console.error(
45
+ "Usage: claude-auto create --name <name> --repo <path> --schedule <cron> [options]"
46
+ );
47
+ throw new Error("Missing required arguments: --name, --repo, and --schedule are required");
48
+ }
49
+ const branch = args.branch ?? "main";
50
+ const timezone = args.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
51
+ const focusStr = args.focus ?? "open-issues,bug-discovery";
52
+ validateCronExpression(schedule);
53
+ await ensureRepoExists(repoPath, args.githubRepo);
54
+ let systemPrompt;
55
+ if (args.systemPromptFile) {
56
+ systemPrompt = await readFile(args.systemPromptFile, "utf-8");
57
+ }
58
+ const focus = focusStr.split(",").map((s) => s.trim());
59
+ const maxTurns = args.maxTurns ? Number(args.maxTurns) : 50;
60
+ const maxBudgetUsd = args.maxBudget ? Number(args.maxBudget) : 5;
61
+ const noNewDependencies = Boolean(args.noNewDeps);
62
+ const noArchitectureChanges = Boolean(args.noArchChanges);
63
+ const bugFixOnly = Boolean(args.bugFixOnly);
64
+ const restrictToPaths = args.restrictPaths ? String(args.restrictPaths).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
65
+ const defaultTriggers = { onSuccess: true, onFailure: true, onNoChanges: false, onLocked: false };
66
+ const notifications = {};
67
+ if (args.notifyDiscord) {
68
+ notifications.discord = { webhookUrl: args.notifyDiscord, ...defaultTriggers };
69
+ }
70
+ if (args.notifySlack) {
71
+ notifications.slack = { webhookUrl: args.notifySlack, ...defaultTriggers };
72
+ }
73
+ if (args.notifyTelegram) {
74
+ const telegramStr = args.notifyTelegram;
75
+ const colonIndex = telegramStr.indexOf(":");
76
+ if (colonIndex > 0) {
77
+ notifications.telegram = {
78
+ botToken: telegramStr.slice(0, colonIndex),
79
+ chatId: telegramStr.slice(colonIndex + 1),
80
+ ...defaultTriggers
81
+ };
82
+ }
83
+ }
84
+ const input = {
85
+ name,
86
+ repo: { path: repoPath, branch, remote: "origin" },
87
+ schedule: { cron: schedule, timezone },
88
+ focus,
89
+ systemPrompt,
90
+ guardrails: {
91
+ maxTurns,
92
+ maxBudgetUsd,
93
+ noNewDependencies,
94
+ noArchitectureChanges,
95
+ bugFixOnly,
96
+ restrictToPaths
97
+ },
98
+ notifications,
99
+ enabled: true
100
+ };
101
+ const config = await createJob(input);
102
+ const scheduler = await createScheduler();
103
+ await scheduler.register(config);
104
+ const description = describeSchedule(schedule);
105
+ const nextRuns = getNextRuns(schedule, timezone, 3).map((d) => d.toLocaleString()).join(", ");
106
+ console.log(`Job created: ${config.id}`);
107
+ console.log(`Schedule: ${description}`);
108
+ console.log(`Next runs: ${nextRuns}`);
109
+ console.log(`Config: ~/.claude-auto/jobs/${config.id}/config.yaml`);
110
+ }
111
+
112
+ export {
113
+ createCommand
114
+ };
115
+ //# sourceMappingURL=chunk-M53MPY3U.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,146 @@
1
+ import {
2
+ listRunLogs
3
+ } from "./chunk-SMZYA6CY.js";
4
+ import {
5
+ formatDuration
6
+ } from "./chunk-ORBF5IW3.js";
7
+ import {
8
+ listJobs,
9
+ readJob
10
+ } from "./chunk-24PS2XSV.js";
11
+
12
+ // src/cli/commands/report.ts
13
+ function aggregateLogs(logs) {
14
+ let successCount = 0;
15
+ let noChangesCount = 0;
16
+ let errorCount = 0;
17
+ let gitErrorCount = 0;
18
+ let lockedCount = 0;
19
+ let totalCost = 0;
20
+ let totalDuration = 0;
21
+ let prsCreated = 0;
22
+ let lastPrUrl;
23
+ let earliest;
24
+ let latest;
25
+ for (const log of logs) {
26
+ switch (log.status) {
27
+ case "success":
28
+ successCount++;
29
+ break;
30
+ case "no-changes":
31
+ noChangesCount++;
32
+ break;
33
+ case "error":
34
+ errorCount++;
35
+ break;
36
+ case "git-error":
37
+ gitErrorCount++;
38
+ break;
39
+ case "locked":
40
+ lockedCount++;
41
+ break;
42
+ }
43
+ totalCost += log.costUsd ?? 0;
44
+ totalDuration += log.durationMs;
45
+ if (log.prUrl) {
46
+ prsCreated++;
47
+ if (!lastPrUrl) {
48
+ lastPrUrl = log.prUrl;
49
+ }
50
+ }
51
+ if (!earliest || log.startedAt < earliest) {
52
+ earliest = log.startedAt;
53
+ }
54
+ if (!latest || log.startedAt > latest) {
55
+ latest = log.startedAt;
56
+ }
57
+ }
58
+ return {
59
+ totalRuns: logs.length,
60
+ successCount,
61
+ noChangesCount,
62
+ errorCount,
63
+ gitErrorCount,
64
+ lockedCount,
65
+ totalCost,
66
+ avgDurationMs: logs.length > 0 ? Math.round(totalDuration / logs.length) : 0,
67
+ prsCreated,
68
+ lastPrUrl,
69
+ earliest,
70
+ latest
71
+ };
72
+ }
73
+ function formatReport(report) {
74
+ const lines = [];
75
+ lines.push(`Run Report: ${report.title}`);
76
+ if (report.repoPath) {
77
+ lines.push(`Repository: ${report.repoPath}`);
78
+ }
79
+ if (report.earliest && report.latest) {
80
+ const fmtDate = (iso) => new Date(iso).toLocaleDateString("en-US", {
81
+ year: "numeric",
82
+ month: "short",
83
+ day: "numeric"
84
+ });
85
+ lines.push(`Period: ${fmtDate(report.earliest)} to ${fmtDate(report.latest)}`);
86
+ }
87
+ lines.push("");
88
+ lines.push(`Runs: ${report.totalRuns}`);
89
+ const pct = (n) => report.totalRuns > 0 ? Math.round(n / report.totalRuns * 100) : 0;
90
+ lines.push(` Success: ${report.successCount} (${pct(report.successCount)}%)`);
91
+ lines.push(` No changes: ${report.noChangesCount} (${pct(report.noChangesCount)}%)`);
92
+ lines.push(
93
+ ` Errors: ${report.errorCount + report.gitErrorCount} (${pct(report.errorCount + report.gitErrorCount)}%)`
94
+ );
95
+ lines.push(` Locked: ${report.lockedCount} (${pct(report.lockedCount)}%)`);
96
+ lines.push("");
97
+ lines.push(`PRs created: ${report.prsCreated}`);
98
+ lines.push(`Total cost: $${report.totalCost.toFixed(2)}`);
99
+ lines.push(`Avg duration: ${formatDuration(report.avgDurationMs)}`);
100
+ lines.push(`Last PR: ${report.lastPrUrl || "none"}`);
101
+ return lines.join("\n");
102
+ }
103
+ async function reportCommand(args) {
104
+ const jobId = args.jobId;
105
+ if (jobId) {
106
+ const job = await readJob(jobId);
107
+ const logs = await listRunLogs(jobId);
108
+ if (logs.length === 0) {
109
+ console.log(`No runs found for job ${jobId}.`);
110
+ return;
111
+ }
112
+ const agg = aggregateLogs(logs);
113
+ const report = {
114
+ ...agg,
115
+ title: `${job.name} (${job.id})`,
116
+ repoPath: job.repo.path
117
+ };
118
+ console.log(formatReport(report));
119
+ } else {
120
+ const jobs = await listJobs();
121
+ if (jobs.length === 0) {
122
+ console.log("No jobs configured.");
123
+ return;
124
+ }
125
+ const allLogs = [];
126
+ for (const job of jobs) {
127
+ const logs = await listRunLogs(job.id);
128
+ allLogs.push(...logs);
129
+ }
130
+ if (allLogs.length === 0) {
131
+ console.log("No runs found across any jobs.");
132
+ return;
133
+ }
134
+ const agg = aggregateLogs(allLogs);
135
+ const report = {
136
+ ...agg,
137
+ title: `All Jobs (${jobs.length} jobs)`
138
+ };
139
+ console.log(formatReport(report));
140
+ }
141
+ }
142
+
143
+ export {
144
+ reportCommand
145
+ };
146
+ //# sourceMappingURL=chunk-MI7OZ5XD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/report.ts"],"sourcesContent":["import { listJobs, readJob } from \"../../core/job-manager.js\";\nimport { listRunLogs } from \"../../runner/logger.js\";\nimport type { RunLogEntry } from \"../../runner/types.js\";\nimport { formatDuration } from \"../format.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\ninterface AggregateReport {\n\ttitle: string;\n\trepoPath?: string;\n\ttotalRuns: number;\n\tsuccessCount: number;\n\tnoChangesCount: number;\n\terrorCount: number;\n\tgitErrorCount: number;\n\tlockedCount: number;\n\ttotalCost: number;\n\tavgDurationMs: number;\n\tprsCreated: number;\n\tlastPrUrl: string | undefined;\n\tearliest: string | undefined;\n\tlatest: string | undefined;\n}\n\nfunction aggregateLogs(logs: RunLogEntry[]): Omit<AggregateReport, \"title\" | \"repoPath\"> {\n\tlet successCount = 0;\n\tlet noChangesCount = 0;\n\tlet errorCount = 0;\n\tlet gitErrorCount = 0;\n\tlet lockedCount = 0;\n\tlet totalCost = 0;\n\tlet totalDuration = 0;\n\tlet prsCreated = 0;\n\tlet lastPrUrl: string | undefined;\n\tlet earliest: string | undefined;\n\tlet latest: string | undefined;\n\n\tfor (const log of logs) {\n\t\tswitch (log.status) {\n\t\t\tcase \"success\":\n\t\t\t\tsuccessCount++;\n\t\t\t\tbreak;\n\t\t\tcase \"no-changes\":\n\t\t\t\tnoChangesCount++;\n\t\t\t\tbreak;\n\t\t\tcase \"error\":\n\t\t\t\terrorCount++;\n\t\t\t\tbreak;\n\t\t\tcase \"git-error\":\n\t\t\t\tgitErrorCount++;\n\t\t\t\tbreak;\n\t\t\tcase \"locked\":\n\t\t\t\tlockedCount++;\n\t\t\t\tbreak;\n\t\t}\n\n\t\ttotalCost += log.costUsd ?? 0;\n\t\ttotalDuration += log.durationMs;\n\n\t\tif (log.prUrl) {\n\t\t\tprsCreated++;\n\t\t\tif (!lastPrUrl) {\n\t\t\t\tlastPrUrl = log.prUrl; // logs are sorted newest-first\n\t\t\t}\n\t\t}\n\n\t\tif (!earliest || log.startedAt < earliest) {\n\t\t\tearliest = log.startedAt;\n\t\t}\n\t\tif (!latest || log.startedAt > latest) {\n\t\t\tlatest = log.startedAt;\n\t\t}\n\t}\n\n\treturn {\n\t\ttotalRuns: logs.length,\n\t\tsuccessCount,\n\t\tnoChangesCount,\n\t\terrorCount,\n\t\tgitErrorCount,\n\t\tlockedCount,\n\t\ttotalCost,\n\t\tavgDurationMs: logs.length > 0 ? Math.round(totalDuration / logs.length) : 0,\n\t\tprsCreated,\n\t\tlastPrUrl,\n\t\tearliest,\n\t\tlatest,\n\t};\n}\n\nfunction formatReport(report: AggregateReport): string {\n\tconst lines: string[] = [];\n\tlines.push(`Run Report: ${report.title}`);\n\n\tif (report.repoPath) {\n\t\tlines.push(`Repository: ${report.repoPath}`);\n\t}\n\n\tif (report.earliest && report.latest) {\n\t\tconst fmtDate = (iso: string) =>\n\t\t\tnew Date(iso).toLocaleDateString(\"en-US\", {\n\t\t\t\tyear: \"numeric\",\n\t\t\t\tmonth: \"short\",\n\t\t\t\tday: \"numeric\",\n\t\t\t});\n\t\tlines.push(`Period: ${fmtDate(report.earliest)} to ${fmtDate(report.latest)}`);\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Runs: ${report.totalRuns}`);\n\n\tconst pct = (n: number) => (report.totalRuns > 0 ? Math.round((n / report.totalRuns) * 100) : 0);\n\tlines.push(` Success: ${report.successCount} (${pct(report.successCount)}%)`);\n\tlines.push(` No changes: ${report.noChangesCount} (${pct(report.noChangesCount)}%)`);\n\tlines.push(\n\t\t` Errors: ${report.errorCount + report.gitErrorCount} (${pct(report.errorCount + report.gitErrorCount)}%)`,\n\t);\n\tlines.push(` Locked: ${report.lockedCount} (${pct(report.lockedCount)}%)`);\n\n\tlines.push(\"\");\n\tlines.push(`PRs created: ${report.prsCreated}`);\n\tlines.push(`Total cost: $${report.totalCost.toFixed(2)}`);\n\tlines.push(`Avg duration: ${formatDuration(report.avgDurationMs)}`);\n\tlines.push(`Last PR: ${report.lastPrUrl || \"none\"}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Show aggregate run summary for a single job or across all jobs.\n * Satisfies REPT-02 (aggregate run summary reports).\n */\nexport async function reportCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\n\tif (jobId) {\n\t\t// Single job report\n\t\tconst job = await readJob(jobId);\n\t\tconst logs = await listRunLogs(jobId);\n\n\t\tif (logs.length === 0) {\n\t\t\tconsole.log(`No runs found for job ${jobId}.`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst agg = aggregateLogs(logs);\n\t\tconst report: AggregateReport = {\n\t\t\t...agg,\n\t\t\ttitle: `${job.name} (${job.id})`,\n\t\t\trepoPath: job.repo.path,\n\t\t};\n\n\t\tconsole.log(formatReport(report));\n\t} else {\n\t\t// All-jobs report\n\t\tconst jobs = await listJobs();\n\n\t\tif (jobs.length === 0) {\n\t\t\tconsole.log(\"No jobs configured.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst allLogs: RunLogEntry[] = [];\n\t\tfor (const job of jobs) {\n\t\t\tconst logs = await listRunLogs(job.id);\n\t\t\tallLogs.push(...logs);\n\t\t}\n\n\t\tif (allLogs.length === 0) {\n\t\t\tconsole.log(\"No runs found across any jobs.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst agg = aggregateLogs(allLogs);\n\t\tconst report: AggregateReport = {\n\t\t\t...agg,\n\t\t\ttitle: `All Jobs (${jobs.length} jobs)`,\n\t\t};\n\n\t\tconsole.log(formatReport(report));\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,177 @@
1
+ import {
2
+ paths
3
+ } from "./chunk-H2MUDYMW.js";
4
+ import {
5
+ ConfigParseError,
6
+ ConfigValidationError
7
+ } from "./chunk-6RYMWH5M.js";
8
+
9
+ // src/core/job-manager.ts
10
+ import { readdir, readFile as readFile2, rm } from "fs/promises";
11
+ import { nanoid } from "nanoid";
12
+ import { parseDocument as parseDocument2 } from "yaml";
13
+ import { z as z3 } from "zod";
14
+
15
+ // src/util/fs.ts
16
+ import { mkdir } from "fs/promises";
17
+ import { dirname } from "path";
18
+ import writeFileAtomic from "write-file-atomic";
19
+ async function writeFileSafe(filePath, content) {
20
+ await mkdir(dirname(filePath), { recursive: true });
21
+ await writeFileAtomic(filePath, content, "utf-8");
22
+ }
23
+
24
+ // src/core/config.ts
25
+ import { readFile } from "fs/promises";
26
+ import { Document, parseDocument } from "yaml";
27
+ import { z as z2 } from "zod";
28
+
29
+ // src/core/types.ts
30
+ import { z } from "zod";
31
+ var ModelSchema = z.union([
32
+ z.enum(["sonnet", "opus", "haiku", "opusplan", "default"]),
33
+ z.string().regex(/^claude-/)
34
+ ]);
35
+ var PipelineConfigSchema = z.object({
36
+ enabled: z.boolean().default(false),
37
+ planModel: ModelSchema.default("haiku"),
38
+ implementModel: ModelSchema.default("opus"),
39
+ reviewModel: ModelSchema.default("sonnet"),
40
+ maxReviewRounds: z.number().int().positive().default(1)
41
+ });
42
+ var JobConfigSchema = z.object({
43
+ id: z.string().min(1),
44
+ name: z.string().min(1),
45
+ repo: z.object({
46
+ path: z.string().min(1),
47
+ branch: z.string().default("main"),
48
+ remote: z.string().default("origin")
49
+ }),
50
+ schedule: z.object({
51
+ cron: z.string().min(1),
52
+ timezone: z.string().default("UTC")
53
+ }),
54
+ focus: z.array(z.enum(["open-issues", "bug-discovery", "features", "documentation"])).default(["open-issues", "bug-discovery"]),
55
+ systemPrompt: z.string().optional(),
56
+ guardrails: z.object({
57
+ maxTurns: z.number().int().positive().default(50),
58
+ maxBudgetUsd: z.number().positive().default(5),
59
+ noNewDependencies: z.boolean().default(false),
60
+ noArchitectureChanges: z.boolean().default(false),
61
+ bugFixOnly: z.boolean().default(false),
62
+ restrictToPaths: z.array(z.string()).optional()
63
+ }).default({
64
+ maxTurns: 50,
65
+ maxBudgetUsd: 5,
66
+ noNewDependencies: false,
67
+ noArchitectureChanges: false,
68
+ bugFixOnly: false
69
+ }),
70
+ notifications: z.object({
71
+ discord: z.object({
72
+ webhookUrl: z.string().url(),
73
+ onSuccess: z.boolean().default(true),
74
+ onFailure: z.boolean().default(true),
75
+ onNoChanges: z.boolean().default(false),
76
+ onLocked: z.boolean().default(false)
77
+ }).optional(),
78
+ slack: z.object({
79
+ webhookUrl: z.string().url(),
80
+ onSuccess: z.boolean().default(true),
81
+ onFailure: z.boolean().default(true),
82
+ onNoChanges: z.boolean().default(false),
83
+ onLocked: z.boolean().default(false)
84
+ }).optional(),
85
+ telegram: z.object({
86
+ botToken: z.string(),
87
+ chatId: z.string(),
88
+ onSuccess: z.boolean().default(true),
89
+ onFailure: z.boolean().default(true),
90
+ onNoChanges: z.boolean().default(false),
91
+ onLocked: z.boolean().default(false)
92
+ }).optional()
93
+ }).default({}),
94
+ enabled: z.boolean().default(true),
95
+ model: ModelSchema.optional(),
96
+ budget: z.object({
97
+ dailyUsd: z.number().positive().optional(),
98
+ weeklyUsd: z.number().positive().optional(),
99
+ monthlyUsd: z.number().positive().optional()
100
+ }).optional(),
101
+ maxFeedbackRounds: z.number().int().positive().default(3).optional(),
102
+ pipeline: PipelineConfigSchema.optional()
103
+ });
104
+
105
+ // src/core/config.ts
106
+ async function readConfigDocument(filePath) {
107
+ const content = await readFile(filePath, "utf-8");
108
+ const doc = parseDocument(content);
109
+ if (doc.errors.length > 0) {
110
+ throw new ConfigParseError(
111
+ filePath,
112
+ doc.errors.map((e) => ({ message: e.message }))
113
+ );
114
+ }
115
+ return doc;
116
+ }
117
+ function validateConfig(filePath, doc) {
118
+ const raw = doc.toJS();
119
+ const result = JobConfigSchema.safeParse(raw);
120
+ if (!result.success) {
121
+ throw new ConfigValidationError(filePath, z2.prettifyError(result.error));
122
+ }
123
+ return result.data;
124
+ }
125
+ async function loadJobConfig(filePath) {
126
+ const doc = await readConfigDocument(filePath);
127
+ return validateConfig(filePath, doc);
128
+ }
129
+
130
+ // src/core/job-manager.ts
131
+ async function readJob(jobId) {
132
+ return loadJobConfig(paths.jobConfig(jobId));
133
+ }
134
+ async function updateJob(jobId, updates) {
135
+ const configPath = paths.jobConfig(jobId);
136
+ const content = await readFile2(configPath, "utf-8");
137
+ const doc = parseDocument2(content);
138
+ for (const [key, value] of Object.entries(updates)) {
139
+ if (key === "id") continue;
140
+ doc.set(key, doc.createNode(value));
141
+ }
142
+ const raw = doc.toJS();
143
+ const result = JobConfigSchema.safeParse(raw);
144
+ if (!result.success) {
145
+ throw new Error(`Invalid config after update: ${z3.prettifyError(result.error)}`);
146
+ }
147
+ await writeFileSafe(configPath, doc.toString({ indent: 2 }));
148
+ return result.data;
149
+ }
150
+ async function listJobs() {
151
+ let entries;
152
+ try {
153
+ entries = await readdir(paths.jobs, { withFileTypes: true, encoding: "utf-8" });
154
+ } catch (error) {
155
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
156
+ return [];
157
+ }
158
+ throw error;
159
+ }
160
+ const results = [];
161
+ for (const entry of entries) {
162
+ if (!entry.isDirectory()) continue;
163
+ try {
164
+ const config = await readJob(entry.name);
165
+ results.push(config);
166
+ } catch {
167
+ }
168
+ }
169
+ return results;
170
+ }
171
+
172
+ export {
173
+ readJob,
174
+ updateJob,
175
+ listJobs
176
+ };
177
+ //# sourceMappingURL=chunk-NB46PEG2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/job-manager.ts","../src/util/fs.ts","../src/core/config.ts","../src/core/types.ts"],"sourcesContent":["import { readdir, readFile, rm } from \"node:fs/promises\";\nimport { nanoid } from \"nanoid\";\nimport { parseDocument } from \"yaml\";\nimport { z } from \"zod\";\nimport { writeFileSafe } from \"../util/fs.js\";\nimport { paths } from \"../util/paths.js\";\nimport { loadJobConfig, saveJobConfig } from \"./config.js\";\nimport { type JobConfig, JobConfigSchema } from \"./types.js\";\n\n/**\n * Create a new job with a generated nanoid(12) identifier.\n * Writes the config to disk and returns the complete JobConfig.\n */\nexport async function createJob(input: Omit<JobConfig, \"id\">): Promise<JobConfig> {\n\tconst id = nanoid(12);\n\tconst config: JobConfig = { ...input, id };\n\tawait saveJobConfig(paths.jobConfig(id), config);\n\treturn config;\n}\n\n/**\n * Read and validate a job config by its ID.\n * Throws if the job doesn't exist (ENOENT) or config is invalid.\n */\nexport async function readJob(jobId: string): Promise<JobConfig> {\n\treturn loadJobConfig(paths.jobConfig(jobId));\n}\n\n/**\n * Update specified fields of a job config while preserving YAML comments.\n * Validates the result before writing. Rejects invalid updates.\n */\nexport async function updateJob(\n\tjobId: string,\n\tupdates: Partial<Omit<JobConfig, \"id\">>,\n): Promise<JobConfig> {\n\tconst configPath = paths.jobConfig(jobId);\n\tconst content = await readFile(configPath, \"utf-8\");\n\tconst doc = parseDocument(content);\n\n\t// Apply each update key to the Document, preserving comments\n\tfor (const [key, value] of Object.entries(updates)) {\n\t\tif (key === \"id\") continue; // Never update the id\n\t\tdoc.set(key, doc.createNode(value));\n\t}\n\n\t// Validate the modified document\n\tconst raw = doc.toJS();\n\tconst result = JobConfigSchema.safeParse(raw);\n\n\tif (!result.success) {\n\t\tthrow new Error(`Invalid config after update: ${z.prettifyError(result.error)}`);\n\t}\n\n\tawait writeFileSafe(configPath, doc.toString({ indent: 2 }));\n\treturn result.data;\n}\n\n/**\n * Delete a job and its entire directory.\n * Idempotent: does not throw if the job doesn't exist.\n */\nexport async function deleteJob(jobId: string): Promise<void> {\n\tawait rm(paths.jobDir(jobId), { recursive: true, force: true });\n}\n\n/**\n * List all valid job configs.\n * Skips invalid directories/configs. Returns empty array if no jobs directory exists.\n */\nexport async function listJobs(): Promise<JobConfig[]> {\n\tlet entries: import(\"node:fs\").Dirent<string>[];\n\ttry {\n\t\tentries = await readdir(paths.jobs, { withFileTypes: true, encoding: \"utf-8\" });\n\t} catch (error: unknown) {\n\t\tif (\n\t\t\terror instanceof Error &&\n\t\t\t\"code\" in error &&\n\t\t\t(error as NodeJS.ErrnoException).code === \"ENOENT\"\n\t\t) {\n\t\t\treturn [];\n\t\t}\n\t\tthrow error;\n\t}\n\n\tconst results: JobConfig[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry.isDirectory()) continue;\n\t\ttry {\n\t\t\tconst config = await readJob(entry.name);\n\t\t\tresults.push(config);\n\t\t} catch {\n\t\t\t// Skip invalid directories/configs\n\t\t}\n\t}\n\n\treturn results;\n}\n","import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport writeFileAtomic from \"write-file-atomic\";\n\nexport async function writeFileSafe(filePath: string, content: string): Promise<void> {\n\tawait mkdir(dirname(filePath), { recursive: true });\n\tawait writeFileAtomic(filePath, content, \"utf-8\");\n}\n","import { readFile } from \"node:fs/promises\";\nimport { Document, parseDocument } from \"yaml\";\nimport { z } from \"zod\";\nimport { ConfigParseError, ConfigValidationError } from \"../util/errors.js\";\nimport { writeFileSafe } from \"../util/fs.js\";\nimport { type JobConfig, JobConfigSchema } from \"./types.js\";\n\n/**\n * Read a YAML config file and return the parsed Document (preserves comments).\n * Throws ConfigParseError if the YAML syntax is invalid.\n */\nexport async function readConfigDocument(filePath: string): Promise<Document> {\n\tconst content = await readFile(filePath, \"utf-8\");\n\tconst doc = parseDocument(content);\n\n\tif (doc.errors.length > 0) {\n\t\tthrow new ConfigParseError(\n\t\t\tfilePath,\n\t\t\tdoc.errors.map((e) => ({ message: e.message })),\n\t\t);\n\t}\n\n\treturn doc;\n}\n\n/**\n * Validate a parsed YAML Document against the JobConfigSchema.\n * Returns the validated and typed JobConfig.\n * Throws ConfigValidationError if validation fails.\n */\nexport function validateConfig(filePath: string, doc: Document): JobConfig {\n\tconst raw = doc.toJS();\n\tconst result = JobConfigSchema.safeParse(raw);\n\n\tif (!result.success) {\n\t\tthrow new ConfigValidationError(filePath, z.prettifyError(result.error));\n\t}\n\n\treturn result.data;\n}\n\n/**\n * Load a job config from a YAML file, validating it against the schema.\n * Combines readConfigDocument + validateConfig.\n */\nexport async function loadJobConfig(filePath: string): Promise<JobConfig> {\n\tconst doc = await readConfigDocument(filePath);\n\treturn validateConfig(filePath, doc);\n}\n\n/**\n * Save a JobConfig to a YAML file using atomic writes.\n * Multiline systemPrompt values use YAML block literal style (|).\n */\nexport async function saveJobConfig(filePath: string, config: JobConfig): Promise<void> {\n\tconst doc = new Document(config);\n\n\t// For multiline systemPrompt, force YAML block literal style (|)\n\tif (config.systemPrompt?.includes(\"\\n\")) {\n\t\tconst node = doc.getIn([\"systemPrompt\"]);\n\t\tif (node && typeof node === \"object\" && \"type\" in node) {\n\t\t\t(node as { type: string }).type = \"BLOCK_LITERAL\";\n\t\t}\n\t}\n\n\tconst yamlContent = doc.toString({ indent: 2 });\n\tawait writeFileSafe(filePath, yamlContent);\n}\n\n/**\n * Update a field in a YAML Document by path, preserving comments.\n */\nexport function updateConfigField(doc: Document, path: (string | number)[], value: unknown): void {\n\tdoc.setIn(path, value);\n}\n\n/**\n * Write a YAML Document to a file using atomic writes, preserving comments.\n */\nexport async function writeConfigDocument(filePath: string, doc: Document): Promise<void> {\n\tconst yamlContent = doc.toString({ indent: 2 });\n\tawait writeFileSafe(filePath, yamlContent);\n}\n","import { z } from \"zod\";\n\nconst ModelSchema = z.union([\n\tz.enum([\"sonnet\", \"opus\", \"haiku\", \"opusplan\", \"default\"]),\n\tz.string().regex(/^claude-/),\n]);\n\nexport const PipelineConfigSchema = z.object({\n\tenabled: z.boolean().default(false),\n\tplanModel: ModelSchema.default(\"haiku\"),\n\timplementModel: ModelSchema.default(\"opus\"),\n\treviewModel: ModelSchema.default(\"sonnet\"),\n\tmaxReviewRounds: z.number().int().positive().default(1),\n});\n\nexport type PipelineConfig = z.infer<typeof PipelineConfigSchema>;\n\nexport const JobConfigSchema = z.object({\n\tid: z.string().min(1),\n\tname: z.string().min(1),\n\trepo: z.object({\n\t\tpath: z.string().min(1),\n\t\tbranch: z.string().default(\"main\"),\n\t\tremote: z.string().default(\"origin\"),\n\t}),\n\tschedule: z.object({\n\t\tcron: z.string().min(1),\n\t\ttimezone: z.string().default(\"UTC\"),\n\t}),\n\tfocus: z\n\t\t.array(z.enum([\"open-issues\", \"bug-discovery\", \"features\", \"documentation\"]))\n\t\t.default([\"open-issues\", \"bug-discovery\"]),\n\tsystemPrompt: z.string().optional(),\n\tguardrails: z\n\t\t.object({\n\t\t\tmaxTurns: z.number().int().positive().default(50),\n\t\t\tmaxBudgetUsd: z.number().positive().default(5.0),\n\t\t\tnoNewDependencies: z.boolean().default(false),\n\t\t\tnoArchitectureChanges: z.boolean().default(false),\n\t\t\tbugFixOnly: z.boolean().default(false),\n\t\t\trestrictToPaths: z.array(z.string()).optional(),\n\t\t})\n\t\t.default({\n\t\t\tmaxTurns: 50,\n\t\t\tmaxBudgetUsd: 5.0,\n\t\t\tnoNewDependencies: false,\n\t\t\tnoArchitectureChanges: false,\n\t\t\tbugFixOnly: false,\n\t\t}),\n\tnotifications: z\n\t\t.object({\n\t\t\tdiscord: z\n\t\t\t\t.object({\n\t\t\t\t\twebhookUrl: z.string().url(),\n\t\t\t\t\tonSuccess: z.boolean().default(true),\n\t\t\t\t\tonFailure: z.boolean().default(true),\n\t\t\t\t\tonNoChanges: z.boolean().default(false),\n\t\t\t\t\tonLocked: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tslack: z\n\t\t\t\t.object({\n\t\t\t\t\twebhookUrl: z.string().url(),\n\t\t\t\t\tonSuccess: z.boolean().default(true),\n\t\t\t\t\tonFailure: z.boolean().default(true),\n\t\t\t\t\tonNoChanges: z.boolean().default(false),\n\t\t\t\t\tonLocked: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\ttelegram: z\n\t\t\t\t.object({\n\t\t\t\t\tbotToken: z.string(),\n\t\t\t\t\tchatId: z.string(),\n\t\t\t\t\tonSuccess: z.boolean().default(true),\n\t\t\t\t\tonFailure: z.boolean().default(true),\n\t\t\t\t\tonNoChanges: z.boolean().default(false),\n\t\t\t\t\tonLocked: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.default({}),\n\tenabled: z.boolean().default(true),\n\tmodel: ModelSchema.optional(),\n\tbudget: z\n\t\t.object({\n\t\t\tdailyUsd: z.number().positive().optional(),\n\t\t\tweeklyUsd: z.number().positive().optional(),\n\t\t\tmonthlyUsd: z.number().positive().optional(),\n\t\t})\n\t\t.optional(),\n\tmaxFeedbackRounds: z.number().int().positive().default(3).optional(),\n\tpipeline: PipelineConfigSchema.optional(),\n});\n\nexport type JobConfig = z.infer<typeof JobConfigSchema>;\n\nexport interface ScheduleInfo {\n\tcron: string;\n\ttimezone: string;\n\thumanReadable: string;\n\tnextRuns: Date[];\n}\n"],"mappings":"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","names":["readFile","parseDocument","z","z","z","readFile","parseDocument","z"]}
@@ -0,0 +1,60 @@
1
+ // src/cli/format.ts
2
+ function formatDuration(ms) {
3
+ const totalSeconds = Math.floor(ms / 1e3);
4
+ const hours = Math.floor(totalSeconds / 3600);
5
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
6
+ const seconds = totalSeconds % 60;
7
+ if (hours > 0) {
8
+ return `${hours}h ${minutes}m`;
9
+ }
10
+ if (minutes > 0) {
11
+ return `${minutes}m ${seconds}s`;
12
+ }
13
+ return `${seconds}s`;
14
+ }
15
+ function formatRelativeTime(date) {
16
+ const now = Date.now();
17
+ const diffMs = now - date.getTime();
18
+ const diffSeconds = Math.floor(diffMs / 1e3);
19
+ const diffMinutes = Math.floor(diffSeconds / 60);
20
+ const diffHours = Math.floor(diffMinutes / 60);
21
+ const diffDays = Math.floor(diffHours / 24);
22
+ if (diffSeconds < 60) {
23
+ return "just now";
24
+ }
25
+ if (diffMinutes < 60) {
26
+ return diffMinutes === 1 ? "1 minute ago" : `${diffMinutes} minutes ago`;
27
+ }
28
+ if (diffHours < 24) {
29
+ return diffHours === 1 ? "1 hour ago" : `${diffHours} hours ago`;
30
+ }
31
+ return diffDays === 1 ? "1 day ago" : `${diffDays} days ago`;
32
+ }
33
+ function formatTable(headers, rows) {
34
+ if (rows.length === 0) {
35
+ return "";
36
+ }
37
+ const colWidths = headers.map((h, i) => {
38
+ const maxDataWidth = rows.reduce((max, row) => Math.max(max, (row[i] || "").length), 0);
39
+ return Math.max(h.length, maxDataWidth);
40
+ });
41
+ const pad = (str, width) => str.padEnd(width);
42
+ const sep = " ";
43
+ const headerLine = headers.map((h, i) => pad(h, colWidths[i])).join(sep);
44
+ const separatorLine = colWidths.map((w) => "-".repeat(w)).join(sep);
45
+ const dataLines = rows.map(
46
+ (row) => row.map((cell, i) => pad(cell || "", colWidths[i])).join(sep)
47
+ );
48
+ return [headerLine, separatorLine, ...dataLines].join("\n");
49
+ }
50
+ function statusBadge(status) {
51
+ return `[${status}]`;
52
+ }
53
+
54
+ export {
55
+ formatDuration,
56
+ formatRelativeTime,
57
+ formatTable,
58
+ statusBadge
59
+ };
60
+ //# sourceMappingURL=chunk-ORBF5IW3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/format.ts"],"sourcesContent":["/**\n * Convert milliseconds to human-readable duration.\n * Omits zero-value leading parts. For hours+, shows \"Xh Ym\" (omits seconds).\n * For minutes+, shows \"Xm Ys\". For <1m, shows \"Xs\".\n */\nexport function formatDuration(ms: number): string {\n\tconst totalSeconds = Math.floor(ms / 1000);\n\tconst hours = Math.floor(totalSeconds / 3600);\n\tconst minutes = Math.floor((totalSeconds % 3600) / 60);\n\tconst seconds = totalSeconds % 60;\n\n\tif (hours > 0) {\n\t\treturn `${hours}h ${minutes}m`;\n\t}\n\tif (minutes > 0) {\n\t\treturn `${minutes}m ${seconds}s`;\n\t}\n\treturn `${seconds}s`;\n}\n\n/**\n * Format a date as a relative time string: \"just now\", \"5 minutes ago\", \"2 hours ago\", etc.\n * Pure math, no library dependencies.\n */\nexport function formatRelativeTime(date: Date): string {\n\tconst now = Date.now();\n\tconst diffMs = now - date.getTime();\n\tconst diffSeconds = Math.floor(diffMs / 1000);\n\tconst diffMinutes = Math.floor(diffSeconds / 60);\n\tconst diffHours = Math.floor(diffMinutes / 60);\n\tconst diffDays = Math.floor(diffHours / 24);\n\n\tif (diffSeconds < 60) {\n\t\treturn \"just now\";\n\t}\n\tif (diffMinutes < 60) {\n\t\treturn diffMinutes === 1 ? \"1 minute ago\" : `${diffMinutes} minutes ago`;\n\t}\n\tif (diffHours < 24) {\n\t\treturn diffHours === 1 ? \"1 hour ago\" : `${diffHours} hours ago`;\n\t}\n\treturn diffDays === 1 ? \"1 day ago\" : `${diffDays} days ago`;\n}\n\n/**\n * Format data as an aligned text table.\n * Returns an empty string when there are no rows.\n */\nexport function formatTable(headers: string[], rows: string[][]): string {\n\tif (rows.length === 0) {\n\t\treturn \"\";\n\t}\n\n\t// Compute column widths\n\tconst colWidths = headers.map((h, i) => {\n\t\tconst maxDataWidth = rows.reduce((max, row) => Math.max(max, (row[i] || \"\").length), 0);\n\t\treturn Math.max(h.length, maxDataWidth);\n\t});\n\n\tconst pad = (str: string, width: number) => str.padEnd(width);\n\tconst sep = \" \";\n\n\tconst headerLine = headers.map((h, i) => pad(h, colWidths[i])).join(sep);\n\tconst separatorLine = colWidths.map((w) => \"-\".repeat(w)).join(sep);\n\tconst dataLines = rows.map((row) =>\n\t\trow.map((cell, i) => pad(cell || \"\", colWidths[i])).join(sep),\n\t);\n\n\treturn [headerLine, separatorLine, ...dataLines].join(\"\\n\");\n}\n\n/**\n * Returns a text badge for a status string: \"active\" -> \"[active]\".\n */\nexport function statusBadge(status: string): string {\n\treturn `[${status}]`;\n}\n"],"mappings":";AAKO,SAAS,eAAe,IAAoB;AAClD,QAAM,eAAe,KAAK,MAAM,KAAK,GAAI;AACzC,QAAM,QAAQ,KAAK,MAAM,eAAe,IAAI;AAC5C,QAAM,UAAU,KAAK,MAAO,eAAe,OAAQ,EAAE;AACrD,QAAM,UAAU,eAAe;AAE/B,MAAI,QAAQ,GAAG;AACd,WAAO,GAAG,KAAK,KAAK,OAAO;AAAA,EAC5B;AACA,MAAI,UAAU,GAAG;AAChB,WAAO,GAAG,OAAO,KAAK,OAAO;AAAA,EAC9B;AACA,SAAO,GAAG,OAAO;AAClB;AAMO,SAAS,mBAAmB,MAAoB;AACtD,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,SAAS,MAAM,KAAK,QAAQ;AAClC,QAAM,cAAc,KAAK,MAAM,SAAS,GAAI;AAC5C,QAAM,cAAc,KAAK,MAAM,cAAc,EAAE;AAC/C,QAAM,YAAY,KAAK,MAAM,cAAc,EAAE;AAC7C,QAAM,WAAW,KAAK,MAAM,YAAY,EAAE;AAE1C,MAAI,cAAc,IAAI;AACrB,WAAO;AAAA,EACR;AACA,MAAI,cAAc,IAAI;AACrB,WAAO,gBAAgB,IAAI,iBAAiB,GAAG,WAAW;AAAA,EAC3D;AACA,MAAI,YAAY,IAAI;AACnB,WAAO,cAAc,IAAI,eAAe,GAAG,SAAS;AAAA,EACrD;AACA,SAAO,aAAa,IAAI,cAAc,GAAG,QAAQ;AAClD;AAMO,SAAS,YAAY,SAAmB,MAA0B;AACxE,MAAI,KAAK,WAAW,GAAG;AACtB,WAAO;AAAA,EACR;AAGA,QAAM,YAAY,QAAQ,IAAI,CAAC,GAAG,MAAM;AACvC,UAAM,eAAe,KAAK,OAAO,CAAC,KAAK,QAAQ,KAAK,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI,MAAM,GAAG,CAAC;AACtF,WAAO,KAAK,IAAI,EAAE,QAAQ,YAAY;AAAA,EACvC,CAAC;AAED,QAAM,MAAM,CAAC,KAAa,UAAkB,IAAI,OAAO,KAAK;AAC5D,QAAM,MAAM;AAEZ,QAAM,aAAa,QAAQ,IAAI,CAAC,GAAG,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG;AACvE,QAAM,gBAAgB,UAAU,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,GAAG;AAClE,QAAM,YAAY,KAAK;AAAA,IAAI,CAAC,QAC3B,IAAI,IAAI,CAAC,MAAM,MAAM,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG;AAAA,EAC7D;AAEA,SAAO,CAAC,YAAY,eAAe,GAAG,SAAS,EAAE,KAAK,IAAI;AAC3D;AAKO,SAAS,YAAY,QAAwB;AACnD,SAAO,IAAI,MAAM;AAClB;","names":[]}