@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,203 @@
1
+ import {
2
+ writeFileSafe
3
+ } from "./chunk-E3XVLTT4.js";
4
+ import {
5
+ paths
6
+ } from "./chunk-H2MUDYMW.js";
7
+ import {
8
+ ConfigParseError,
9
+ ConfigValidationError
10
+ } from "./chunk-YMO45Z6G.js";
11
+
12
+ // src/core/types.ts
13
+ import { z } from "zod";
14
+ var ModelSchema = z.union([
15
+ z.enum(["sonnet", "opus", "haiku", "opusplan", "default"]),
16
+ z.string().regex(/^claude-/)
17
+ ]);
18
+ var PipelineConfigSchema = z.object({
19
+ enabled: z.boolean().default(false),
20
+ planModel: ModelSchema.default("haiku"),
21
+ implementModel: ModelSchema.default("opus"),
22
+ reviewModel: ModelSchema.default("sonnet"),
23
+ maxReviewRounds: z.number().int().positive().default(1)
24
+ });
25
+ var JobConfigSchema = z.object({
26
+ id: z.string().min(1),
27
+ name: z.string().min(1),
28
+ repo: z.object({
29
+ path: z.string().min(1),
30
+ branch: z.string().default("main"),
31
+ remote: z.string().default("origin")
32
+ }),
33
+ schedule: z.object({
34
+ cron: z.string().min(1),
35
+ timezone: z.string().default("UTC")
36
+ }),
37
+ focus: z.array(z.enum(["open-issues", "bug-discovery", "features", "documentation"])).default(["open-issues", "bug-discovery"]),
38
+ systemPrompt: z.string().optional(),
39
+ guardrails: z.object({
40
+ maxTurns: z.number().int().positive().default(50),
41
+ maxBudgetUsd: z.number().positive().default(5),
42
+ noNewDependencies: z.boolean().default(false),
43
+ noArchitectureChanges: z.boolean().default(false),
44
+ bugFixOnly: z.boolean().default(false),
45
+ restrictToPaths: z.array(z.string()).optional()
46
+ }).default({
47
+ maxTurns: 50,
48
+ maxBudgetUsd: 5,
49
+ noNewDependencies: false,
50
+ noArchitectureChanges: false,
51
+ bugFixOnly: false
52
+ }),
53
+ notifications: z.object({
54
+ discord: z.object({
55
+ webhookUrl: z.string().url(),
56
+ onSuccess: z.boolean().default(true),
57
+ onFailure: z.boolean().default(true),
58
+ onNoChanges: z.boolean().default(false),
59
+ onLocked: z.boolean().default(false)
60
+ }).optional(),
61
+ slack: z.object({
62
+ webhookUrl: z.string().url(),
63
+ onSuccess: z.boolean().default(true),
64
+ onFailure: z.boolean().default(true),
65
+ onNoChanges: z.boolean().default(false),
66
+ onLocked: z.boolean().default(false)
67
+ }).optional(),
68
+ telegram: z.object({
69
+ botToken: z.string(),
70
+ chatId: z.string(),
71
+ onSuccess: z.boolean().default(true),
72
+ onFailure: z.boolean().default(true),
73
+ onNoChanges: z.boolean().default(false),
74
+ onLocked: z.boolean().default(false)
75
+ }).optional()
76
+ }).default({}),
77
+ enabled: z.boolean().default(true),
78
+ model: ModelSchema.optional(),
79
+ budget: z.object({
80
+ dailyUsd: z.number().positive().optional(),
81
+ weeklyUsd: z.number().positive().optional(),
82
+ monthlyUsd: z.number().positive().optional()
83
+ }).optional(),
84
+ maxFeedbackRounds: z.number().int().positive().default(3).optional(),
85
+ pipeline: PipelineConfigSchema.optional()
86
+ });
87
+
88
+ // src/core/config.ts
89
+ import { readFile } from "fs/promises";
90
+ import { Document, parseDocument } from "yaml";
91
+ import { z as z2 } from "zod";
92
+ async function readConfigDocument(filePath) {
93
+ const content = await readFile(filePath, "utf-8");
94
+ const doc = parseDocument(content);
95
+ if (doc.errors.length > 0) {
96
+ throw new ConfigParseError(
97
+ filePath,
98
+ doc.errors.map((e) => ({ message: e.message }))
99
+ );
100
+ }
101
+ return doc;
102
+ }
103
+ function validateConfig(filePath, doc) {
104
+ const raw = doc.toJS();
105
+ const result = JobConfigSchema.safeParse(raw);
106
+ if (!result.success) {
107
+ throw new ConfigValidationError(filePath, z2.prettifyError(result.error));
108
+ }
109
+ return result.data;
110
+ }
111
+ async function loadJobConfig(filePath) {
112
+ const doc = await readConfigDocument(filePath);
113
+ return validateConfig(filePath, doc);
114
+ }
115
+ async function saveJobConfig(filePath, config) {
116
+ const doc = new Document(config);
117
+ if (config.systemPrompt?.includes("\n")) {
118
+ const node = doc.getIn(["systemPrompt"]);
119
+ if (node && typeof node === "object" && "type" in node) {
120
+ node.type = "BLOCK_LITERAL";
121
+ }
122
+ }
123
+ const yamlContent = doc.toString({ indent: 2 });
124
+ await writeFileSafe(filePath, yamlContent);
125
+ }
126
+ function updateConfigField(doc, path, value) {
127
+ doc.setIn(path, value);
128
+ }
129
+ async function writeConfigDocument(filePath, doc) {
130
+ const yamlContent = doc.toString({ indent: 2 });
131
+ await writeFileSafe(filePath, yamlContent);
132
+ }
133
+
134
+ // src/core/job-manager.ts
135
+ import { readdir, readFile as readFile2, rm } from "fs/promises";
136
+ import { nanoid } from "nanoid";
137
+ import { parseDocument as parseDocument2 } from "yaml";
138
+ import { z as z3 } from "zod";
139
+ async function createJob(input) {
140
+ const id = nanoid(12);
141
+ const config = { ...input, id };
142
+ await saveJobConfig(paths.jobConfig(id), config);
143
+ return config;
144
+ }
145
+ async function readJob(jobId) {
146
+ return loadJobConfig(paths.jobConfig(jobId));
147
+ }
148
+ async function updateJob(jobId, updates) {
149
+ const configPath = paths.jobConfig(jobId);
150
+ const content = await readFile2(configPath, "utf-8");
151
+ const doc = parseDocument2(content);
152
+ for (const [key, value] of Object.entries(updates)) {
153
+ if (key === "id") continue;
154
+ doc.set(key, doc.createNode(value));
155
+ }
156
+ const raw = doc.toJS();
157
+ const result = JobConfigSchema.safeParse(raw);
158
+ if (!result.success) {
159
+ throw new Error(`Invalid config after update: ${z3.prettifyError(result.error)}`);
160
+ }
161
+ await writeFileSafe(configPath, doc.toString({ indent: 2 }));
162
+ return result.data;
163
+ }
164
+ async function deleteJob(jobId) {
165
+ await rm(paths.jobDir(jobId), { recursive: true, force: true });
166
+ }
167
+ async function listJobs() {
168
+ let entries;
169
+ try {
170
+ entries = await readdir(paths.jobs, { withFileTypes: true, encoding: "utf-8" });
171
+ } catch (error) {
172
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
173
+ return [];
174
+ }
175
+ throw error;
176
+ }
177
+ const results = [];
178
+ for (const entry of entries) {
179
+ if (!entry.isDirectory()) continue;
180
+ try {
181
+ const config = await readJob(entry.name);
182
+ results.push(config);
183
+ } catch {
184
+ }
185
+ }
186
+ return results;
187
+ }
188
+
189
+ export {
190
+ JobConfigSchema,
191
+ readConfigDocument,
192
+ validateConfig,
193
+ loadJobConfig,
194
+ saveJobConfig,
195
+ updateConfigField,
196
+ writeConfigDocument,
197
+ createJob,
198
+ readJob,
199
+ updateJob,
200
+ deleteJob,
201
+ listJobs
202
+ };
203
+ //# sourceMappingURL=chunk-24PS2XSV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/types.ts","../src/core/config.ts","../src/core/job-manager.ts"],"sourcesContent":["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","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 { 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"],"mappings":"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","names":["z","z","readFile","parseDocument","z","readFile","parseDocument","z"]}
@@ -0,0 +1,129 @@
1
+ import {
2
+ writeFileSafe
3
+ } from "./chunk-E3XVLTT4.js";
4
+ import {
5
+ ConfigParseError,
6
+ ConfigValidationError
7
+ } from "./chunk-AWLSYOVF.js";
8
+
9
+ // src/core/types.ts
10
+ import { z } from "zod";
11
+ var ModelSchema = z.union([
12
+ z.enum(["sonnet", "opus", "haiku", "opusplan", "default"]),
13
+ z.string().regex(/^claude-/)
14
+ ]);
15
+ var PipelineConfigSchema = z.object({
16
+ enabled: z.boolean().default(false),
17
+ planModel: ModelSchema.default("haiku"),
18
+ implementModel: ModelSchema.default("opus"),
19
+ reviewModel: ModelSchema.default("sonnet"),
20
+ maxReviewRounds: z.number().int().positive().default(1)
21
+ });
22
+ var JobConfigSchema = z.object({
23
+ id: z.string().min(1),
24
+ name: z.string().min(1),
25
+ repo: z.object({
26
+ path: z.string().min(1),
27
+ branch: z.string().default("main"),
28
+ remote: z.string().default("origin")
29
+ }),
30
+ schedule: z.object({
31
+ cron: z.string().min(1),
32
+ timezone: z.string().default("UTC")
33
+ }),
34
+ focus: z.array(z.enum(["open-issues", "bug-discovery", "features", "documentation"])).default(["open-issues", "bug-discovery"]),
35
+ systemPrompt: z.string().optional(),
36
+ guardrails: z.object({
37
+ maxTurns: z.number().int().positive().default(50),
38
+ maxBudgetUsd: z.number().positive().default(5),
39
+ noNewDependencies: z.boolean().default(false),
40
+ noArchitectureChanges: z.boolean().default(false),
41
+ bugFixOnly: z.boolean().default(false),
42
+ restrictToPaths: z.array(z.string()).optional()
43
+ }).default({
44
+ maxTurns: 50,
45
+ maxBudgetUsd: 5,
46
+ noNewDependencies: false,
47
+ noArchitectureChanges: false,
48
+ bugFixOnly: false
49
+ }),
50
+ notifications: z.object({
51
+ discord: z.object({
52
+ webhookUrl: z.string().url(),
53
+ onSuccess: z.boolean().default(true),
54
+ onFailure: z.boolean().default(true),
55
+ onNoChanges: z.boolean().default(false),
56
+ onLocked: z.boolean().default(false)
57
+ }).optional(),
58
+ slack: z.object({
59
+ webhookUrl: z.string().url(),
60
+ onSuccess: z.boolean().default(true),
61
+ onFailure: z.boolean().default(true),
62
+ onNoChanges: z.boolean().default(false),
63
+ onLocked: z.boolean().default(false)
64
+ }).optional(),
65
+ telegram: z.object({
66
+ botToken: z.string(),
67
+ chatId: z.string(),
68
+ onSuccess: z.boolean().default(true),
69
+ onFailure: z.boolean().default(true),
70
+ onNoChanges: z.boolean().default(false),
71
+ onLocked: z.boolean().default(false)
72
+ }).optional()
73
+ }).default({}),
74
+ enabled: z.boolean().default(true),
75
+ model: ModelSchema.optional(),
76
+ budget: z.object({
77
+ dailyUsd: z.number().positive().optional(),
78
+ weeklyUsd: z.number().positive().optional(),
79
+ monthlyUsd: z.number().positive().optional()
80
+ }).optional(),
81
+ maxFeedbackRounds: z.number().int().positive().default(3).optional(),
82
+ pipeline: PipelineConfigSchema.optional()
83
+ });
84
+
85
+ // src/core/config.ts
86
+ import { readFile } from "fs/promises";
87
+ import { Document, parseDocument } from "yaml";
88
+ import { z as z2 } from "zod";
89
+ async function readConfigDocument(filePath) {
90
+ const content = await readFile(filePath, "utf-8");
91
+ const doc = parseDocument(content);
92
+ if (doc.errors.length > 0) {
93
+ throw new ConfigParseError(
94
+ filePath,
95
+ doc.errors.map((e) => ({ message: e.message }))
96
+ );
97
+ }
98
+ return doc;
99
+ }
100
+ function validateConfig(filePath, doc) {
101
+ const raw = doc.toJS();
102
+ const result = JobConfigSchema.safeParse(raw);
103
+ if (!result.success) {
104
+ throw new ConfigValidationError(filePath, z2.prettifyError(result.error));
105
+ }
106
+ return result.data;
107
+ }
108
+ async function loadJobConfig(filePath) {
109
+ const doc = await readConfigDocument(filePath);
110
+ return validateConfig(filePath, doc);
111
+ }
112
+ async function saveJobConfig(filePath, config) {
113
+ const doc = new Document(config);
114
+ if (config.systemPrompt?.includes("\n")) {
115
+ const node = doc.getIn(["systemPrompt"]);
116
+ if (node && typeof node === "object" && "type" in node) {
117
+ node.type = "BLOCK_LITERAL";
118
+ }
119
+ }
120
+ const yamlContent = doc.toString({ indent: 2 });
121
+ await writeFileSafe(filePath, yamlContent);
122
+ }
123
+
124
+ export {
125
+ JobConfigSchema,
126
+ loadJobConfig,
127
+ saveJobConfig
128
+ };
129
+ //# sourceMappingURL=chunk-2D5E23XA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/types.ts","../src/core/config.ts"],"sourcesContent":["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","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"],"mappings":"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","names":["z","z"]}
@@ -0,0 +1,26 @@
1
+ // src/util/exec.ts
2
+ import { execFile } from "child_process";
3
+ import { promisify } from "util";
4
+ var execFileAsync = promisify(execFile);
5
+ async function execCommand(command, args, options) {
6
+ if (options?.stdin !== void 0) {
7
+ return new Promise((resolve, reject) => {
8
+ const child = execFile(command, args, { cwd: options?.cwd }, (err, stdout2, stderr2) => {
9
+ if (err) {
10
+ reject(err);
11
+ return;
12
+ }
13
+ resolve({ stdout: stdout2.toString(), stderr: stderr2.toString() });
14
+ });
15
+ child.stdin?.write(options.stdin);
16
+ child.stdin?.end();
17
+ });
18
+ }
19
+ const { stdout, stderr } = await execFileAsync(command, args, { cwd: options?.cwd });
20
+ return { stdout, stderr };
21
+ }
22
+
23
+ export {
24
+ execCommand
25
+ };
26
+ //# sourceMappingURL=chunk-3NEANSUS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/exec.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n}\n\n/**\n * Execute a command and return stdout/stderr.\n * Throws if the command exits with non-zero status.\n */\nexport async function execCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { stdin?: string; cwd?: string },\n): Promise<ExecResult> {\n\tif (options?.stdin !== undefined) {\n\t\treturn new Promise<ExecResult>((resolve, reject) => {\n\t\t\tconst child = execFile(command, args, { cwd: options?.cwd }, (err, stdout, stderr) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolve({ stdout: stdout.toString(), stderr: stderr.toString() });\n\t\t\t});\n\t\t\tchild.stdin?.write(options.stdin);\n\t\t\tchild.stdin?.end();\n\t\t});\n\t}\n\tconst { stdout, stderr } = await execFileAsync(command, args, { cwd: options?.cwd });\n\treturn { stdout, stderr };\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAE1B,IAAM,gBAAgB,UAAU,QAAQ;AAWxC,eAAsB,YACrB,SACA,MACA,SACsB;AACtB,MAAI,SAAS,UAAU,QAAW;AACjC,WAAO,IAAI,QAAoB,CAAC,SAAS,WAAW;AACnD,YAAM,QAAQ,SAAS,SAAS,MAAM,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,KAAKA,SAAQC,YAAW;AACrF,YAAI,KAAK;AACR,iBAAO,GAAG;AACV;AAAA,QACD;AACA,gBAAQ,EAAE,QAAQD,QAAO,SAAS,GAAG,QAAQC,QAAO,SAAS,EAAE,CAAC;AAAA,MACjE,CAAC;AACD,YAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,YAAM,OAAO,IAAI;AAAA,IAClB,CAAC;AAAA,EACF;AACA,QAAM,EAAE,QAAQ,OAAO,IAAI,MAAM,cAAc,SAAS,MAAM,EAAE,KAAK,SAAS,IAAI,CAAC;AACnF,SAAO,EAAE,QAAQ,OAAO;AACzB;","names":["stdout","stderr"]}
@@ -0,0 +1,71 @@
1
+ import {
2
+ paths
3
+ } from "./chunk-H2MUDYMW.js";
4
+
5
+ // src/core/database.ts
6
+ import Database from "better-sqlite3";
7
+ var db = null;
8
+ function getDatabase(dbPath) {
9
+ if (!db) {
10
+ db = new Database(dbPath ?? paths.database);
11
+ db.pragma("journal_mode = WAL");
12
+ db.pragma("synchronous = normal");
13
+ db.pragma("foreign_keys = ON");
14
+ migrateSchema(db);
15
+ }
16
+ return db;
17
+ }
18
+ function migrateSchema(database) {
19
+ const version = database.pragma("user_version", { simple: true });
20
+ if (version < 1) {
21
+ database.exec(`
22
+ CREATE TABLE IF NOT EXISTS runs (
23
+ id TEXT PRIMARY KEY,
24
+ job_id TEXT NOT NULL,
25
+ status TEXT NOT NULL,
26
+ started_at TEXT NOT NULL,
27
+ completed_at TEXT NOT NULL,
28
+ duration_ms INTEGER NOT NULL DEFAULT 0,
29
+ cost_usd REAL,
30
+ num_turns INTEGER,
31
+ session_id TEXT,
32
+ model TEXT,
33
+ pr_url TEXT,
34
+ branch_name TEXT,
35
+ issue_number INTEGER,
36
+ summary TEXT,
37
+ error TEXT,
38
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
39
+ );
40
+ CREATE INDEX IF NOT EXISTS idx_runs_job_id ON runs(job_id);
41
+ CREATE INDEX IF NOT EXISTS idx_runs_started_at ON runs(started_at);
42
+ CREATE INDEX IF NOT EXISTS idx_runs_job_started ON runs(job_id, started_at);
43
+ `);
44
+ database.pragma("user_version = 1");
45
+ }
46
+ if (version < 2) {
47
+ database.exec(`
48
+ ALTER TABLE runs ADD COLUMN feedback_round INTEGER;
49
+ ALTER TABLE runs ADD COLUMN pr_number INTEGER;
50
+ `);
51
+ database.pragma("user_version = 2");
52
+ }
53
+ if (version < 3) {
54
+ database.exec(`
55
+ ALTER TABLE runs ADD COLUMN pipeline_stages TEXT;
56
+ `);
57
+ database.pragma("user_version = 3");
58
+ }
59
+ }
60
+ function closeDatabase() {
61
+ if (db) {
62
+ db.close();
63
+ db = null;
64
+ }
65
+ }
66
+
67
+ export {
68
+ getDatabase,
69
+ closeDatabase
70
+ };
71
+ //# sourceMappingURL=chunk-4I5UIASZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/database.ts"],"sourcesContent":["import Database from \"better-sqlite3\";\nimport { paths } from \"../util/paths.js\";\n\nlet db: Database.Database | null = null;\n\n/**\n * Get or create the SQLite database singleton.\n * On first call, opens the database, sets pragmas (WAL mode, synchronous=normal,\n * foreign_keys=ON), and runs schema migrations.\n *\n * @param dbPath - Optional path override for testing (e.g., \":memory:\")\n * @returns The singleton Database instance\n */\nexport function getDatabase(dbPath?: string): Database.Database {\n\tif (!db) {\n\t\tdb = new Database(dbPath ?? paths.database);\n\t\tdb.pragma(\"journal_mode = WAL\");\n\t\tdb.pragma(\"synchronous = normal\");\n\t\tdb.pragma(\"foreign_keys = ON\");\n\t\tmigrateSchema(db);\n\t}\n\treturn db;\n}\n\n/**\n * Run schema migrations based on the user_version pragma.\n * Each migration block increments user_version atomically.\n */\nfunction migrateSchema(database: Database.Database): void {\n\tconst version = database.pragma(\"user_version\", { simple: true }) as number;\n\n\tif (version < 1) {\n\t\tdatabase.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS runs (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tjob_id TEXT NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\tstarted_at TEXT NOT NULL,\n\t\t\t\tcompleted_at TEXT NOT NULL,\n\t\t\t\tduration_ms INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcost_usd REAL,\n\t\t\t\tnum_turns INTEGER,\n\t\t\t\tsession_id TEXT,\n\t\t\t\tmodel TEXT,\n\t\t\t\tpr_url TEXT,\n\t\t\t\tbranch_name TEXT,\n\t\t\t\tissue_number INTEGER,\n\t\t\t\tsummary TEXT,\n\t\t\t\terror TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_runs_job_id ON runs(job_id);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_runs_started_at ON runs(started_at);\n\t\t\tCREATE INDEX IF NOT EXISTS idx_runs_job_started ON runs(job_id, started_at);\n\t\t`);\n\t\tdatabase.pragma(\"user_version = 1\");\n\t}\n\n\tif (version < 2) {\n\t\tdatabase.exec(`\n\t\t\tALTER TABLE runs ADD COLUMN feedback_round INTEGER;\n\t\t\tALTER TABLE runs ADD COLUMN pr_number INTEGER;\n\t\t`);\n\t\tdatabase.pragma(\"user_version = 2\");\n\t}\n\n\tif (version < 3) {\n\t\tdatabase.exec(`\n\t\t\tALTER TABLE runs ADD COLUMN pipeline_stages TEXT;\n\t\t`);\n\t\tdatabase.pragma(\"user_version = 3\");\n\t}\n}\n\n/**\n * Close the database connection and reset the singleton.\n * Safe to call multiple times. After calling, the next getDatabase()\n * call will open a fresh connection.\n */\nexport function closeDatabase(): void {\n\tif (db) {\n\t\tdb.close();\n\t\tdb = null;\n\t}\n}\n"],"mappings":";;;;;AAAA,OAAO,cAAc;AAGrB,IAAI,KAA+B;AAU5B,SAAS,YAAY,QAAoC;AAC/D,MAAI,CAAC,IAAI;AACR,SAAK,IAAI,SAAS,UAAU,MAAM,QAAQ;AAC1C,OAAG,OAAO,oBAAoB;AAC9B,OAAG,OAAO,sBAAsB;AAChC,OAAG,OAAO,mBAAmB;AAC7B,kBAAc,EAAE;AAAA,EACjB;AACA,SAAO;AACR;AAMA,SAAS,cAAc,UAAmC;AACzD,QAAM,UAAU,SAAS,OAAO,gBAAgB,EAAE,QAAQ,KAAK,CAAC;AAEhE,MAAI,UAAU,GAAG;AAChB,aAAS,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAsBb;AACD,aAAS,OAAO,kBAAkB;AAAA,EACnC;AAEA,MAAI,UAAU,GAAG;AAChB,aAAS,KAAK;AAAA;AAAA;AAAA,GAGb;AACD,aAAS,OAAO,kBAAkB;AAAA,EACnC;AAEA,MAAI,UAAU,GAAG;AAChB,aAAS,KAAK;AAAA;AAAA,GAEb;AACD,aAAS,OAAO,kBAAkB;AAAA,EACnC;AACD;AAOO,SAAS,gBAAsB;AACrC,MAAI,IAAI;AACP,OAAG,MAAM;AACT,SAAK;AAAA,EACN;AACD;","names":[]}
@@ -0,0 +1,38 @@
1
+ import {
2
+ listRunLogs
3
+ } from "./chunk-SMZYA6CY.js";
4
+ import {
5
+ formatDuration,
6
+ formatRelativeTime,
7
+ formatTable
8
+ } from "./chunk-ORBF5IW3.js";
9
+
10
+ // src/cli/commands/logs.ts
11
+ async function logsCommand(args) {
12
+ const jobId = args.jobId;
13
+ if (!jobId) {
14
+ console.error("Usage: claude-auto logs <job-id> [--limit N]");
15
+ return;
16
+ }
17
+ const limit = typeof args.limit === "number" ? args.limit : 10;
18
+ const logs = await listRunLogs(jobId);
19
+ const sliced = logs.slice(0, limit);
20
+ if (sliced.length === 0) {
21
+ console.log(`No runs found for job ${jobId}.`);
22
+ return;
23
+ }
24
+ const rows = sliced.map((entry) => {
25
+ const started = formatRelativeTime(new Date(entry.startedAt));
26
+ const duration = formatDuration(entry.durationMs);
27
+ const prUrl = entry.prUrl || "--";
28
+ const error = entry.error ? entry.error.length > 50 ? `${entry.error.slice(0, 47)}...` : entry.error : "--";
29
+ return [entry.runId, entry.status, started, duration, prUrl, error];
30
+ });
31
+ const headers = ["Run ID", "Status", "Started", "Duration", "PR URL", "Error"];
32
+ console.log(formatTable(headers, rows));
33
+ }
34
+
35
+ export {
36
+ logsCommand
37
+ };
38
+ //# sourceMappingURL=chunk-5LGOK52J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/logs.ts"],"sourcesContent":["import { listRunLogs } from \"../../runner/logger.js\";\nimport { formatDuration, formatRelativeTime, formatTable } from \"../format.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Show recent run history for a specific job.\n * Displays a table of: Run ID, Status, Started, Duration, PR URL, Error.\n */\nexport async function logsCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\n\tif (!jobId) {\n\t\tconsole.error(\"Usage: claude-auto logs <job-id> [--limit N]\");\n\t\treturn;\n\t}\n\n\tconst limit = typeof args.limit === \"number\" ? args.limit : 10;\n\tconst logs = await listRunLogs(jobId);\n\tconst sliced = logs.slice(0, limit);\n\n\tif (sliced.length === 0) {\n\t\tconsole.log(`No runs found for job ${jobId}.`);\n\t\treturn;\n\t}\n\n\tconst rows: string[][] = sliced.map((entry) => {\n\t\tconst started = formatRelativeTime(new Date(entry.startedAt));\n\t\tconst duration = formatDuration(entry.durationMs);\n\t\tconst prUrl = entry.prUrl || \"--\";\n\t\tconst error = entry.error\n\t\t\t? entry.error.length > 50\n\t\t\t\t? `${entry.error.slice(0, 47)}...`\n\t\t\t\t: entry.error\n\t\t\t: \"--\";\n\t\treturn [entry.runId, entry.status, started, duration, prUrl, error];\n\t});\n\n\tconst headers = [\"Run ID\", \"Status\", \"Started\", \"Duration\", \"PR URL\", \"Error\"];\n\tconsole.log(formatTable(headers, rows));\n}\n"],"mappings":";;;;;;;;;;AAQA,eAAsB,YAAY,MAA4C;AAC7E,QAAM,QAAQ,KAAK;AAEnB,MAAI,CAAC,OAAO;AACX,YAAQ,MAAM,8CAA8C;AAC5D;AAAA,EACD;AAEA,QAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAC5D,QAAM,OAAO,MAAM,YAAY,KAAK;AACpC,QAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAElC,MAAI,OAAO,WAAW,GAAG;AACxB,YAAQ,IAAI,yBAAyB,KAAK,GAAG;AAC7C;AAAA,EACD;AAEA,QAAM,OAAmB,OAAO,IAAI,CAAC,UAAU;AAC9C,UAAM,UAAU,mBAAmB,IAAI,KAAK,MAAM,SAAS,CAAC;AAC5D,UAAM,WAAW,eAAe,MAAM,UAAU;AAChD,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,QAAQ,MAAM,QACjB,MAAM,MAAM,SAAS,KACpB,GAAG,MAAM,MAAM,MAAM,GAAG,EAAE,CAAC,QAC3B,MAAM,QACP;AACH,WAAO,CAAC,MAAM,OAAO,MAAM,QAAQ,SAAS,UAAU,OAAO,KAAK;AAAA,EACnE,CAAC;AAED,QAAM,UAAU,CAAC,UAAU,UAAU,WAAW,YAAY,UAAU,OAAO;AAC7E,UAAQ,IAAI,YAAY,SAAS,IAAI,CAAC;AACvC;","names":[]}
@@ -0,0 +1,35 @@
1
+ // src/util/errors.ts
2
+ var ConfigParseError = class extends Error {
3
+ constructor(filePath, parseErrors) {
4
+ const errorMessages = parseErrors.map((e) => ` - ${e.message}`).join("\n");
5
+ super(`YAML syntax error in ${filePath}:
6
+ ${errorMessages}`);
7
+ this.filePath = filePath;
8
+ this.parseErrors = parseErrors;
9
+ }
10
+ name = "ConfigParseError";
11
+ };
12
+ var ConfigValidationError = class extends Error {
13
+ constructor(filePath, validationMessage) {
14
+ super(`Invalid config in ${filePath}:
15
+ ${validationMessage}`);
16
+ this.filePath = filePath;
17
+ this.validationMessage = validationMessage;
18
+ }
19
+ name = "ConfigValidationError";
20
+ };
21
+ var SchedulerError = class extends Error {
22
+ constructor(platform, message, cause) {
23
+ super(`Scheduler error (${platform}): ${message}`);
24
+ this.platform = platform;
25
+ this.cause = cause;
26
+ }
27
+ name = "SchedulerError";
28
+ };
29
+
30
+ export {
31
+ ConfigParseError,
32
+ ConfigValidationError,
33
+ SchedulerError
34
+ };
35
+ //# sourceMappingURL=chunk-6RYMWH5M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/errors.ts"],"sourcesContent":["export class ConfigParseError extends Error {\n\toverride name = \"ConfigParseError\" as const;\n\tconstructor(\n\t\tpublic readonly filePath: string,\n\t\tpublic readonly parseErrors: Array<{ message: string }>,\n\t) {\n\t\tconst errorMessages = parseErrors.map((e) => ` - ${e.message}`).join(\"\\n\");\n\t\tsuper(`YAML syntax error in ${filePath}:\\n${errorMessages}`);\n\t}\n}\n\nexport class ConfigValidationError extends Error {\n\toverride name = \"ConfigValidationError\" as const;\n\tconstructor(\n\t\tpublic readonly filePath: string,\n\t\tpublic readonly validationMessage: string,\n\t) {\n\t\tsuper(`Invalid config in ${filePath}:\\n${validationMessage}`);\n\t}\n}\n\nexport class SchedulerError extends Error {\n\toverride name = \"SchedulerError\" as const;\n\tconstructor(\n\t\tpublic readonly platform: string,\n\t\tmessage: string,\n\t\tpublic readonly cause?: Error,\n\t) {\n\t\tsuper(`Scheduler error (${platform}): ${message}`);\n\t}\n}\n\nexport class CronValidationError extends Error {\n\toverride name = \"CronValidationError\" as const;\n\tconstructor(\n\t\tpublic readonly expression: string,\n\t\tmessage: string,\n\t) {\n\t\tsuper(`Invalid cron expression \"${expression}\": ${message}`);\n\t}\n}\n\nexport class GitOpsError extends Error {\n\toverride name = \"GitOpsError\" as const;\n\tconstructor(\n\t\tpublic readonly operation: string,\n\t\tpublic readonly repoPath: string,\n\t\tmessage: string,\n\t\tpublic override readonly cause?: Error,\n\t) {\n\t\tsuper(`Git operation '${operation}' failed in ${repoPath}: ${message}`);\n\t}\n}\n\nexport class LockError extends Error {\n\toverride name = \"LockError\" as const;\n\tconstructor(\n\t\tpublic readonly jobId: string,\n\t\tmessage: string,\n\t) {\n\t\tsuper(`Lock error for job ${jobId}: ${message}`);\n\t}\n}\n\nexport class SpawnError extends Error {\n\toverride name = \"SpawnError\" as const;\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly exitCode?: number,\n\t) {\n\t\tsuper(`Claude spawn error: ${message}`);\n\t}\n}\n"],"mappings":";AAAO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAE3C,YACiB,UACA,aACf;AACD,UAAM,gBAAgB,YAAY,IAAI,CAAC,MAAM,OAAO,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAC1E,UAAM,wBAAwB,QAAQ;AAAA,EAAM,aAAa,EAAE;AAJ3C;AACA;AAAA,EAIjB;AAAA,EAPS,OAAO;AAQjB;AAEO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAEhD,YACiB,UACA,mBACf;AACD,UAAM,qBAAqB,QAAQ;AAAA,EAAM,iBAAiB,EAAE;AAH5C;AACA;AAAA,EAGjB;AAAA,EANS,OAAO;AAOjB;AAEO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EAEzC,YACiB,UAChB,SACgB,OACf;AACD,UAAM,oBAAoB,QAAQ,MAAM,OAAO,EAAE;AAJjC;AAEA;AAAA,EAGjB;AAAA,EAPS,OAAO;AAQjB;","names":[]}
@@ -0,0 +1,56 @@
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
+
51
+ export {
52
+ formatDuration,
53
+ formatRelativeTime,
54
+ formatTable
55
+ };
56
+ //# sourceMappingURL=chunk-A6XWZPLY.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;","names":[]}