@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,121 @@
1
+ import {
2
+ getDatabase
3
+ } from "./chunk-4I5UIASZ.js";
4
+ import {
5
+ writeFileSafe
6
+ } from "./chunk-E3XVLTT4.js";
7
+ import {
8
+ paths
9
+ } from "./chunk-H2MUDYMW.js";
10
+
11
+ // src/runner/context-store.ts
12
+ function saveRunContext(entry) {
13
+ const db = getDatabase();
14
+ db.prepare(
15
+ `INSERT INTO runs (
16
+ id, job_id, status, started_at, completed_at, duration_ms,
17
+ cost_usd, num_turns, session_id, model, pr_url, branch_name,
18
+ issue_number, summary, error, feedback_round, pr_number, pipeline_stages
19
+ ) VALUES (
20
+ @id, @job_id, @status, @started_at, @completed_at, @duration_ms,
21
+ @cost_usd, @num_turns, @session_id, @model, @pr_url, @branch_name,
22
+ @issue_number, @summary, @error, @feedback_round, @pr_number, @pipeline_stages
23
+ )`
24
+ ).run({
25
+ id: entry.runId,
26
+ job_id: entry.jobId,
27
+ status: entry.status,
28
+ started_at: entry.startedAt,
29
+ completed_at: entry.completedAt,
30
+ duration_ms: entry.durationMs,
31
+ cost_usd: entry.costUsd ?? null,
32
+ num_turns: entry.numTurns ?? null,
33
+ session_id: entry.sessionId ?? null,
34
+ model: entry.model ?? null,
35
+ pr_url: entry.prUrl ?? null,
36
+ branch_name: entry.branchName ?? null,
37
+ issue_number: entry.issueNumber ?? null,
38
+ summary: entry.summary ?? null,
39
+ error: entry.error ?? null,
40
+ feedback_round: entry.feedbackRound ?? null,
41
+ pr_number: entry.prNumber ?? null,
42
+ pipeline_stages: entry.pipelineStages ? JSON.stringify(entry.pipelineStages) : null
43
+ });
44
+ }
45
+ function loadRunContext(jobId, limit = 5) {
46
+ const db = getDatabase();
47
+ return db.prepare(
48
+ `SELECT id, status, pr_url, branch_name, issue_number, summary, started_at
49
+ FROM runs
50
+ WHERE job_id = ? AND status IN ('success', 'no-changes')
51
+ ORDER BY started_at DESC
52
+ LIMIT ?`
53
+ ).all(jobId, limit);
54
+ }
55
+ function formatContextWindow(context) {
56
+ if (context.length === 0) return "";
57
+ const lines = context.map((c) => {
58
+ const parts = [`- Run ${c.id} (${c.started_at}): ${c.status}`];
59
+ if (c.issue_number) parts.push(` Issue: #${c.issue_number}`);
60
+ if (c.pr_url) parts.push(` PR: ${c.pr_url}`);
61
+ if (c.branch_name) parts.push(` Branch: ${c.branch_name}`);
62
+ return parts.join("\n");
63
+ });
64
+ return `## Previous Work (DO NOT duplicate)
65
+
66
+ ${lines.join("\n\n")}`;
67
+ }
68
+
69
+ // src/runner/logger.ts
70
+ import { readdir, readFile } from "fs/promises";
71
+ async function writeRunLog(jobId, entry) {
72
+ const logPath = paths.jobLog(jobId, entry.runId);
73
+ await writeFileSafe(logPath, JSON.stringify(entry, null, 2));
74
+ try {
75
+ saveRunContext(entry);
76
+ } catch {
77
+ }
78
+ }
79
+ async function readRunLog(jobId, runId) {
80
+ const logPath = paths.jobLog(jobId, runId);
81
+ const content = await readFile(logPath, "utf-8");
82
+ return JSON.parse(content);
83
+ }
84
+ async function listRunLogs(jobId) {
85
+ const logsDir = paths.jobLogs(jobId);
86
+ let files;
87
+ try {
88
+ files = await readdir(logsDir);
89
+ } catch (err) {
90
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
91
+ return [];
92
+ }
93
+ throw err;
94
+ }
95
+ const logFiles = files.filter((f) => f.endsWith(".log"));
96
+ const entries = [];
97
+ for (const file of logFiles) {
98
+ try {
99
+ const filePath = paths.jobLog(jobId, file.replace(/\.log$/, ""));
100
+ const content = await readFile(filePath, "utf-8");
101
+ entries.push(JSON.parse(content));
102
+ } catch {
103
+ }
104
+ }
105
+ entries.sort((a, b) => {
106
+ const aTime = new Date(a.startedAt).getTime();
107
+ const bTime = new Date(b.startedAt).getTime();
108
+ return bTime - aTime;
109
+ });
110
+ return entries;
111
+ }
112
+
113
+ export {
114
+ saveRunContext,
115
+ loadRunContext,
116
+ formatContextWindow,
117
+ writeRunLog,
118
+ readRunLog,
119
+ listRunLogs
120
+ };
121
+ //# sourceMappingURL=chunk-SMZYA6CY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runner/context-store.ts","../src/runner/logger.ts"],"sourcesContent":["import { getDatabase } from \"../core/database.js\";\nimport type { RunLogEntry } from \"./types.js\";\n\n/**\n * Structured context from a prior run, containing only verifiable facts\n * (issue numbers, PR URLs, branch names) -- never raw narrative summaries.\n */\nexport interface RunContext {\n\tid: string;\n\tstatus: string;\n\tpr_url: string | null;\n\tbranch_name: string | null;\n\tissue_number: number | null;\n\tsummary: string | null;\n\tstarted_at: string;\n}\n\n/**\n * Persist a run log entry to the SQLite database.\n * Maps RunLogEntry camelCase fields to snake_case columns.\n *\n * @param entry - The run log entry to save\n */\nexport function saveRunContext(entry: RunLogEntry): void {\n\tconst db = getDatabase();\n\tdb.prepare(\n\t\t`INSERT INTO runs (\n\t\t\tid, job_id, status, started_at, completed_at, duration_ms,\n\t\t\tcost_usd, num_turns, session_id, model, pr_url, branch_name,\n\t\t\tissue_number, summary, error, feedback_round, pr_number, pipeline_stages\n\t\t) VALUES (\n\t\t\t@id, @job_id, @status, @started_at, @completed_at, @duration_ms,\n\t\t\t@cost_usd, @num_turns, @session_id, @model, @pr_url, @branch_name,\n\t\t\t@issue_number, @summary, @error, @feedback_round, @pr_number, @pipeline_stages\n\t\t)`,\n\t).run({\n\t\tid: entry.runId,\n\t\tjob_id: entry.jobId,\n\t\tstatus: entry.status,\n\t\tstarted_at: entry.startedAt,\n\t\tcompleted_at: entry.completedAt,\n\t\tduration_ms: entry.durationMs,\n\t\tcost_usd: entry.costUsd ?? null,\n\t\tnum_turns: entry.numTurns ?? null,\n\t\tsession_id: entry.sessionId ?? null,\n\t\tmodel: entry.model ?? null,\n\t\tpr_url: entry.prUrl ?? null,\n\t\tbranch_name: entry.branchName ?? null,\n\t\tissue_number: entry.issueNumber ?? null,\n\t\tsummary: entry.summary ?? null,\n\t\terror: entry.error ?? null,\n\t\tfeedback_round: entry.feedbackRound ?? null,\n\t\tpr_number: entry.prNumber ?? null,\n\t\tpipeline_stages: entry.pipelineStages ? JSON.stringify(entry.pipelineStages) : null,\n\t});\n}\n\n/**\n * Load recent run context for a job from SQLite.\n * Only returns runs with status \"success\" or \"no-changes\" (not errors, locked, etc.)\n * to avoid injecting failure context into Claude's prompt.\n *\n * @param jobId - The job identifier\n * @param limit - Maximum number of recent runs to return (default: 5)\n * @returns Array of RunContext sorted by started_at descending\n */\nexport function loadRunContext(jobId: string, limit = 5): RunContext[] {\n\tconst db = getDatabase();\n\treturn db\n\t\t.prepare(\n\t\t\t`SELECT id, status, pr_url, branch_name, issue_number, summary, started_at\n\t\t\tFROM runs\n\t\t\tWHERE job_id = ? AND status IN ('success', 'no-changes')\n\t\t\tORDER BY started_at DESC\n\t\t\tLIMIT ?`,\n\t\t)\n\t\t.all(jobId, limit) as RunContext[];\n}\n\n/**\n * Format run context as a structured \"Previous Work\" prompt section.\n * Only includes verifiable facts (issue numbers, PR URLs, branch names)\n * to avoid hallucination amplification from raw narrative summaries.\n *\n * @param context - Array of RunContext from loadRunContext\n * @returns Formatted string for prompt injection, or empty string if no context\n */\nexport function formatContextWindow(context: RunContext[]): string {\n\tif (context.length === 0) return \"\";\n\n\tconst lines = context.map((c) => {\n\t\tconst parts = [`- Run ${c.id} (${c.started_at}): ${c.status}`];\n\t\tif (c.issue_number) parts.push(` Issue: #${c.issue_number}`);\n\t\tif (c.pr_url) parts.push(` PR: ${c.pr_url}`);\n\t\tif (c.branch_name) parts.push(` Branch: ${c.branch_name}`);\n\t\treturn parts.join(\"\\n\");\n\t});\n\n\treturn `## Previous Work (DO NOT duplicate)\\n\\n${lines.join(\"\\n\\n\")}`;\n}\n","import { readdir, readFile } from \"node:fs/promises\";\nimport { writeFileSafe } from \"../util/fs.js\";\nimport { paths } from \"../util/paths.js\";\nimport { saveRunContext } from \"./context-store.js\";\nimport type { RunLogEntry } from \"./types.js\";\n\n/**\n * Write a run log entry as a JSON file for a specific job run.\n * Creates the runs directory if it doesn't exist (via writeFileSafe).\n *\n * @param jobId - The job identifier\n * @param entry - The run log entry to write\n */\nexport async function writeRunLog(jobId: string, entry: RunLogEntry): Promise<void> {\n\tconst logPath = paths.jobLog(jobId, entry.runId);\n\tawait writeFileSafe(logPath, JSON.stringify(entry, null, 2));\n\n\t// Dual-write: persist to SQLite for cross-run context queries (CTXT-01, COST-01).\n\t// Best-effort -- never fail the run due to DB issues.\n\ttry {\n\t\tsaveRunContext(entry);\n\t} catch {\n\t\t// SQLite write is best-effort\n\t}\n}\n\n/**\n * Read and parse a specific run log entry.\n *\n * @param jobId - The job identifier\n * @param runId - The run identifier\n * @returns Parsed RunLogEntry\n * @throws When the log file doesn't exist (ENOENT)\n */\nexport async function readRunLog(jobId: string, runId: string): Promise<RunLogEntry> {\n\tconst logPath = paths.jobLog(jobId, runId);\n\tconst content = await readFile(logPath, \"utf-8\");\n\treturn JSON.parse(content) as RunLogEntry;\n}\n\n/**\n * List all run logs for a job, sorted by startedAt descending (newest first).\n * Returns an empty array when the runs directory doesn't exist.\n * Skips files that fail to parse (logs warning but doesn't throw).\n *\n * @param jobId - The job identifier\n * @returns Array of RunLogEntry sorted by startedAt descending\n */\nexport async function listRunLogs(jobId: string): Promise<RunLogEntry[]> {\n\tconst logsDir = paths.jobLogs(jobId);\n\n\tlet files: string[];\n\ttry {\n\t\tfiles = (await readdir(logsDir)) as string[];\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\treturn [];\n\t\t}\n\t\tthrow err;\n\t}\n\n\tconst logFiles = files.filter((f) => f.endsWith(\".log\"));\n\tconst entries: RunLogEntry[] = [];\n\n\tfor (const file of logFiles) {\n\t\ttry {\n\t\t\tconst filePath = paths.jobLog(jobId, file.replace(/\\.log$/, \"\"));\n\t\t\tconst content = await readFile(filePath, \"utf-8\");\n\t\t\tentries.push(JSON.parse(content) as RunLogEntry);\n\t\t} catch {\n\t\t\t// Skip files that fail to parse\n\t\t}\n\t}\n\n\t// Sort by startedAt descending (newest first)\n\tentries.sort((a, b) => {\n\t\tconst aTime = new Date(a.startedAt).getTime();\n\t\tconst bTime = new Date(b.startedAt).getTime();\n\t\treturn bTime - aTime;\n\t});\n\n\treturn entries;\n}\n"],"mappings":";;;;;;;;;;;AAuBO,SAAS,eAAe,OAA0B;AACxD,QAAM,KAAK,YAAY;AACvB,KAAG;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASD,EAAE,IAAI;AAAA,IACL,IAAI,MAAM;AAAA,IACV,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,YAAY,MAAM;AAAA,IAClB,cAAc,MAAM;AAAA,IACpB,aAAa,MAAM;AAAA,IACnB,UAAU,MAAM,WAAW;AAAA,IAC3B,WAAW,MAAM,YAAY;AAAA,IAC7B,YAAY,MAAM,aAAa;AAAA,IAC/B,OAAO,MAAM,SAAS;AAAA,IACtB,QAAQ,MAAM,SAAS;AAAA,IACvB,aAAa,MAAM,cAAc;AAAA,IACjC,cAAc,MAAM,eAAe;AAAA,IACnC,SAAS,MAAM,WAAW;AAAA,IAC1B,OAAO,MAAM,SAAS;AAAA,IACtB,gBAAgB,MAAM,iBAAiB;AAAA,IACvC,WAAW,MAAM,YAAY;AAAA,IAC7B,iBAAiB,MAAM,iBAAiB,KAAK,UAAU,MAAM,cAAc,IAAI;AAAA,EAChF,CAAC;AACF;AAWO,SAAS,eAAe,OAAe,QAAQ,GAAiB;AACtE,QAAM,KAAK,YAAY;AACvB,SAAO,GACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKD,EACC,IAAI,OAAO,KAAK;AACnB;AAUO,SAAS,oBAAoB,SAA+B;AAClE,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAChC,UAAM,QAAQ,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,MAAM,EAAE,MAAM,EAAE;AAC7D,QAAI,EAAE,aAAc,OAAM,KAAK,aAAa,EAAE,YAAY,EAAE;AAC5D,QAAI,EAAE,OAAQ,OAAM,KAAK,SAAS,EAAE,MAAM,EAAE;AAC5C,QAAI,EAAE,YAAa,OAAM,KAAK,aAAa,EAAE,WAAW,EAAE;AAC1D,WAAO,MAAM,KAAK,IAAI;AAAA,EACvB,CAAC;AAED,SAAO;AAAA;AAAA,EAA0C,MAAM,KAAK,MAAM,CAAC;AACpE;;;ACnGA,SAAS,SAAS,gBAAgB;AAalC,eAAsB,YAAY,OAAe,OAAmC;AACnF,QAAM,UAAU,MAAM,OAAO,OAAO,MAAM,KAAK;AAC/C,QAAM,cAAc,SAAS,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAI3D,MAAI;AACH,mBAAe,KAAK;AAAA,EACrB,QAAQ;AAAA,EAER;AACD;AAUA,eAAsB,WAAW,OAAe,OAAqC;AACpF,QAAM,UAAU,MAAM,OAAO,OAAO,KAAK;AACzC,QAAM,UAAU,MAAM,SAAS,SAAS,OAAO;AAC/C,SAAO,KAAK,MAAM,OAAO;AAC1B;AAUA,eAAsB,YAAY,OAAuC;AACxE,QAAM,UAAU,MAAM,QAAQ,KAAK;AAEnC,MAAI;AACJ,MAAI;AACH,YAAS,MAAM,QAAQ,OAAO;AAAA,EAC/B,SAAS,KAAc;AACtB,QAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,UAAU;AAC9F,aAAO,CAAC;AAAA,IACT;AACA,UAAM;AAAA,EACP;AAEA,QAAM,WAAW,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,CAAC;AACvD,QAAM,UAAyB,CAAC;AAEhC,aAAW,QAAQ,UAAU;AAC5B,QAAI;AACH,YAAM,WAAW,MAAM,OAAO,OAAO,KAAK,QAAQ,UAAU,EAAE,CAAC;AAC/D,YAAM,UAAU,MAAM,SAAS,UAAU,OAAO;AAChD,cAAQ,KAAK,KAAK,MAAM,OAAO,CAAgB;AAAA,IAChD,QAAQ;AAAA,IAER;AAAA,EACD;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM;AACtB,UAAM,QAAQ,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;AAC5C,UAAM,QAAQ,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;AAC5C,WAAO,QAAQ;AAAA,EAChB,CAAC;AAED,SAAO;AACR;","names":[]}
@@ -0,0 +1,12 @@
1
+ // src/core/schedule.ts
2
+ import { CronExpressionParser } from "cron-parser";
3
+ import cronstrue from "cronstrue";
4
+ function getNextRuns(cronExpr, timezone, count = 3) {
5
+ const interval = CronExpressionParser.parse(cronExpr, { tz: timezone });
6
+ return interval.take(count).map((d) => d.toDate());
7
+ }
8
+
9
+ export {
10
+ getNextRuns
11
+ };
12
+ //# sourceMappingURL=chunk-SNOA575X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/schedule.ts"],"sourcesContent":["import { CronExpressionParser } from \"cron-parser\";\nimport cronstrue from \"cronstrue\";\nimport { CronValidationError } from \"../util/errors.js\";\nimport type { ScheduleInfo } from \"./types.js\";\n\n/**\n * Validate a cron expression. Throws CronValidationError if invalid.\n * Only accepts standard 5-field cron expressions.\n */\nexport function validateCronExpression(cronExpr: string): void {\n\tconst fields = cronExpr.trim().split(/\\s+/);\n\tif (fields.length !== 5) {\n\t\tthrow new CronValidationError(\n\t\t\tcronExpr,\n\t\t\t`Expected 5 fields (minute hour day month weekday), got ${fields.length}`,\n\t\t);\n\t}\n\ttry {\n\t\tCronExpressionParser.parse(cronExpr);\n\t} catch (error: unknown) {\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tthrow new CronValidationError(cronExpr, msg);\n\t}\n}\n\n/**\n * Get a human-readable description of a cron expression.\n * Example: \"0 *​/6 * * *\" -> \"Every 6 hours\"\n */\nexport function describeSchedule(cronExpr: string): string {\n\treturn cronstrue.toString(cronExpr, {\n\t\tuse24HourTimeFormat: false,\n\t\tverbose: true,\n\t});\n}\n\n/**\n * Compute the next N run times for a cron expression in the given IANA timezone.\n */\nexport function getNextRuns(cronExpr: string, timezone: string, count = 3): Date[] {\n\tconst interval = CronExpressionParser.parse(cronExpr, { tz: timezone });\n\treturn interval.take(count).map((d) => d.toDate());\n}\n\n/**\n * Validate a cron expression and return complete schedule info\n * including human-readable description and next run times.\n */\nexport function validateAndDescribeSchedule(cronExpr: string, timezone: string): ScheduleInfo {\n\tvalidateCronExpression(cronExpr);\n\tconst humanReadable = describeSchedule(cronExpr);\n\tconst nextRuns = getNextRuns(cronExpr, timezone);\n\treturn { cron: cronExpr, timezone, humanReadable, nextRuns };\n}\n"],"mappings":";AAAA,SAAS,4BAA4B;AACrC,OAAO,eAAe;AAsCf,SAAS,YAAY,UAAkB,UAAkB,QAAQ,GAAW;AAClF,QAAM,WAAW,qBAAqB,MAAM,UAAU,EAAE,IAAI,SAAS,CAAC;AACtE,SAAO,SAAS,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AAClD;","names":[]}
@@ -0,0 +1,44 @@
1
+ import {
2
+ createScheduler
3
+ } from "./chunk-QLRCFKLU.js";
4
+ import {
5
+ readJob,
6
+ updateJob
7
+ } from "./chunk-24PS2XSV.js";
8
+
9
+ // src/cli/commands/pause.ts
10
+ async function pauseCommand(args) {
11
+ const jobId = args.jobId;
12
+ if (!jobId) {
13
+ console.error("Usage: claude-auto pause <job-id>");
14
+ throw new Error("Missing required argument: job-id");
15
+ }
16
+ let config;
17
+ try {
18
+ config = await readJob(jobId);
19
+ } catch (err) {
20
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
21
+ console.error(`Job ${jobId} not found.`);
22
+ throw err;
23
+ }
24
+ throw err;
25
+ }
26
+ if (!config.enabled) {
27
+ console.log(`Job ${jobId} is already paused.`);
28
+ return;
29
+ }
30
+ await updateJob(jobId, { enabled: false });
31
+ try {
32
+ const scheduler = await createScheduler();
33
+ await scheduler.unregister(jobId);
34
+ } catch {
35
+ }
36
+ console.log(
37
+ `Job ${jobId} paused. Configuration preserved -- resume with: claude-auto resume ${jobId}`
38
+ );
39
+ }
40
+
41
+ export {
42
+ pauseCommand
43
+ };
44
+ //# sourceMappingURL=chunk-SZRIZBWI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/pause.ts"],"sourcesContent":["import { readJob, updateJob } from \"../../core/job-manager.js\";\nimport type { JobConfig } from \"../../core/types.js\";\nimport { createScheduler } from \"../../platform/scheduler.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Pause a running job: sets enabled=false and unregisters from scheduler.\n * Idempotent: pausing an already-paused job prints a message and exits cleanly.\n */\nexport async function pauseCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tif (!jobId) {\n\t\tconsole.error(\"Usage: claude-auto pause <job-id>\");\n\t\tthrow new Error(\"Missing required argument: job-id\");\n\t}\n\n\tlet config: JobConfig;\n\ttry {\n\t\tconfig = await readJob(jobId);\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tconsole.error(`Job ${jobId} not found.`);\n\t\t\tthrow err;\n\t\t}\n\t\tthrow err;\n\t}\n\n\tif (!config.enabled) {\n\t\tconsole.log(`Job ${jobId} is already paused.`);\n\t\treturn;\n\t}\n\n\tawait updateJob(jobId, { enabled: false });\n\n\t// Best-effort unregister from scheduler\n\ttry {\n\t\tconst scheduler = await createScheduler();\n\t\tawait scheduler.unregister(jobId);\n\t} catch {\n\t\t// Scheduler entry may already be missing -- best-effort\n\t}\n\n\tconsole.log(\n\t\t`Job ${jobId} paused. Configuration preserved -- resume with: claude-auto resume ${jobId}`,\n\t);\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,aAAa,MAA4C;AAC9E,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,OAAO;AACX,YAAQ,MAAM,mCAAmC;AACjD,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACpD;AAEA,MAAI;AACJ,MAAI;AACH,aAAS,MAAM,QAAQ,KAAK;AAAA,EAC7B,SAAS,KAAc;AACtB,QAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,UAAU;AAC9F,cAAQ,MAAM,OAAO,KAAK,aAAa;AACvC,YAAM;AAAA,IACP;AACA,UAAM;AAAA,EACP;AAEA,MAAI,CAAC,OAAO,SAAS;AACpB,YAAQ,IAAI,OAAO,KAAK,qBAAqB;AAC7C;AAAA,EACD;AAEA,QAAM,UAAU,OAAO,EAAE,SAAS,MAAM,CAAC;AAGzC,MAAI;AACH,UAAM,YAAY,MAAM,gBAAgB;AACxC,UAAM,UAAU,WAAW,KAAK;AAAA,EACjC,QAAQ;AAAA,EAER;AAEA,UAAQ;AAAA,IACP,OAAO,KAAK,uEAAuE,KAAK;AAAA,EACzF;AACD;","names":[]}
@@ -0,0 +1,47 @@
1
+ import {
2
+ createScheduler
3
+ } from "./chunk-QLRCFKLU.js";
4
+ import {
5
+ getNextRuns
6
+ } from "./chunk-D4MBOIYQ.js";
7
+ import {
8
+ readJob,
9
+ updateJob
10
+ } from "./chunk-24PS2XSV.js";
11
+
12
+ // src/cli/commands/resume.ts
13
+ async function resumeCommand(args) {
14
+ const jobId = args.jobId;
15
+ if (!jobId) {
16
+ console.error("Usage: claude-auto resume <job-id>");
17
+ throw new Error("Missing required argument: job-id");
18
+ }
19
+ let config;
20
+ try {
21
+ config = await readJob(jobId);
22
+ } catch (err) {
23
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
24
+ console.error(`Job ${jobId} not found.`);
25
+ throw err;
26
+ }
27
+ throw err;
28
+ }
29
+ if (config.enabled) {
30
+ console.log(`Job ${jobId} is already active.`);
31
+ return;
32
+ }
33
+ const updatedConfig = await updateJob(jobId, { enabled: true });
34
+ const scheduler = await createScheduler();
35
+ if (await scheduler.isRegistered(jobId)) {
36
+ await scheduler.unregister(jobId);
37
+ }
38
+ await scheduler.register(updatedConfig);
39
+ const nextRuns = getNextRuns(config.schedule.cron, config.schedule.timezone, 1);
40
+ const nextRunStr = nextRuns.length > 0 ? nextRuns[0].toLocaleString() : "unknown";
41
+ console.log(`Job ${jobId} resumed. Next run: ${nextRunStr}`);
42
+ }
43
+
44
+ export {
45
+ resumeCommand
46
+ };
47
+ //# sourceMappingURL=chunk-TAGHPCFT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/resume.ts"],"sourcesContent":["import { readJob, updateJob } from \"../../core/job-manager.js\";\nimport { getNextRuns } from \"../../core/schedule.js\";\nimport type { JobConfig } from \"../../core/types.js\";\nimport { createScheduler } from \"../../platform/scheduler.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Resume a paused job: sets enabled=true and re-registers with scheduler.\n * Idempotent: resuming an already-active job prints a message and exits cleanly.\n */\nexport async function resumeCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tif (!jobId) {\n\t\tconsole.error(\"Usage: claude-auto resume <job-id>\");\n\t\tthrow new Error(\"Missing required argument: job-id\");\n\t}\n\n\tlet config: JobConfig;\n\ttry {\n\t\tconfig = await readJob(jobId);\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tconsole.error(`Job ${jobId} not found.`);\n\t\t\tthrow err;\n\t\t}\n\t\tthrow err;\n\t}\n\n\tif (config.enabled) {\n\t\tconsole.log(`Job ${jobId} is already active.`);\n\t\treturn;\n\t}\n\n\tconst updatedConfig = await updateJob(jobId, { enabled: true });\n\n\tconst scheduler = await createScheduler();\n\n\t// Defensive: unregister first if already registered (shouldn't happen, but safe)\n\tif (await scheduler.isRegistered(jobId)) {\n\t\tawait scheduler.unregister(jobId);\n\t}\n\n\tawait scheduler.register(updatedConfig);\n\n\tconst nextRuns = getNextRuns(config.schedule.cron, config.schedule.timezone, 1);\n\tconst nextRunStr = nextRuns.length > 0 ? nextRuns[0].toLocaleString() : \"unknown\";\n\n\tconsole.log(`Job ${jobId} resumed. Next run: ${nextRunStr}`);\n}\n"],"mappings":";;;;;;;;;;;;AAUA,eAAsB,cAAc,MAA4C;AAC/E,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,OAAO;AACX,YAAQ,MAAM,oCAAoC;AAClD,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACpD;AAEA,MAAI;AACJ,MAAI;AACH,aAAS,MAAM,QAAQ,KAAK;AAAA,EAC7B,SAAS,KAAc;AACtB,QAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,UAAU;AAC9F,cAAQ,MAAM,OAAO,KAAK,aAAa;AACvC,YAAM;AAAA,IACP;AACA,UAAM;AAAA,EACP;AAEA,MAAI,OAAO,SAAS;AACnB,YAAQ,IAAI,OAAO,KAAK,qBAAqB;AAC7C;AAAA,EACD;AAEA,QAAM,gBAAgB,MAAM,UAAU,OAAO,EAAE,SAAS,KAAK,CAAC;AAE9D,QAAM,YAAY,MAAM,gBAAgB;AAGxC,MAAI,MAAM,UAAU,aAAa,KAAK,GAAG;AACxC,UAAM,UAAU,WAAW,KAAK;AAAA,EACjC;AAEA,QAAM,UAAU,SAAS,aAAa;AAEtC,QAAM,WAAW,YAAY,OAAO,SAAS,MAAM,OAAO,SAAS,UAAU,CAAC;AAC9E,QAAM,aAAa,SAAS,SAAS,IAAI,SAAS,CAAC,EAAE,eAAe,IAAI;AAExE,UAAQ,IAAI,OAAO,KAAK,uBAAuB,UAAU,EAAE;AAC5D;","names":[]}
@@ -0,0 +1,34 @@
1
+ import {
2
+ execCommand
3
+ } from "./chunk-3NEANSUS.js";
4
+
5
+ // src/cli/commands/check-repo.ts
6
+ import { stat } from "fs/promises";
7
+ async function checkRepoCommand(args) {
8
+ const repoPath = args.path;
9
+ if (!repoPath) {
10
+ console.error("Usage: claude-auto check-repo --path <path>");
11
+ throw new Error("Missing --path argument");
12
+ }
13
+ try {
14
+ const s = await stat(repoPath);
15
+ if (!s.isDirectory()) {
16
+ console.log(JSON.stringify({ exists: false, error: "Not a directory" }));
17
+ return;
18
+ }
19
+ await execCommand("git", ["-C", repoPath, "rev-parse", "--git-dir"]);
20
+ const { stdout } = await execCommand("git", ["-C", repoPath, "remote", "get-url", "origin"]);
21
+ console.log(JSON.stringify({ exists: true, isGitRepo: true, remote: stdout.trim() }));
22
+ } catch (err) {
23
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
24
+ console.log(JSON.stringify({ exists: false }));
25
+ return;
26
+ }
27
+ console.log(JSON.stringify({ exists: false }));
28
+ }
29
+ }
30
+
31
+ export {
32
+ checkRepoCommand
33
+ };
34
+ //# sourceMappingURL=chunk-TGKCHHXT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/check-repo.ts"],"sourcesContent":["import { stat } from \"node:fs/promises\";\nimport { execCommand } from \"../../util/exec.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Check if a path is a valid git repository.\n * Outputs JSON for the setup wizard to consume.\n */\nexport async function checkRepoCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst repoPath = args.path as string | undefined;\n\tif (!repoPath) {\n\t\tconsole.error(\"Usage: claude-auto check-repo --path <path>\");\n\t\tthrow new Error(\"Missing --path argument\");\n\t}\n\n\ttry {\n\t\tconst s = await stat(repoPath);\n\t\tif (!s.isDirectory()) {\n\t\t\tconsole.log(JSON.stringify({ exists: false, error: \"Not a directory\" }));\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if it's a git repo\n\t\tawait execCommand(\"git\", [\"-C\", repoPath, \"rev-parse\", \"--git-dir\"]);\n\n\t\t// Get remote URL\n\t\tconst { stdout } = await execCommand(\"git\", [\"-C\", repoPath, \"remote\", \"get-url\", \"origin\"]);\n\t\tconsole.log(JSON.stringify({ exists: true, isGitRepo: true, remote: stdout.trim() }));\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tconsole.log(JSON.stringify({ exists: false }));\n\t\t\treturn;\n\t\t}\n\t\t// If stat succeeded but git commands failed, still report as not a git repo\n\t\tconsole.log(JSON.stringify({ exists: false }));\n\t}\n}\n"],"mappings":";;;;;AAAA,SAAS,YAAY;AAQrB,eAAsB,iBAAiB,MAA4C;AAClF,QAAM,WAAW,KAAK;AACtB,MAAI,CAAC,UAAU;AACd,YAAQ,MAAM,6CAA6C;AAC3D,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC1C;AAEA,MAAI;AACH,UAAM,IAAI,MAAM,KAAK,QAAQ;AAC7B,QAAI,CAAC,EAAE,YAAY,GAAG;AACrB,cAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,OAAO,kBAAkB,CAAC,CAAC;AACvE;AAAA,IACD;AAGA,UAAM,YAAY,OAAO,CAAC,MAAM,UAAU,aAAa,WAAW,CAAC;AAGnE,UAAM,EAAE,OAAO,IAAI,MAAM,YAAY,OAAO,CAAC,MAAM,UAAU,UAAU,WAAW,QAAQ,CAAC;AAC3F,YAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,MAAM,WAAW,MAAM,QAAQ,OAAO,KAAK,EAAE,CAAC,CAAC;AAAA,EACrF,SAAS,KAAc;AACtB,QAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,UAAU;AAC9F,cAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,MAAM,CAAC,CAAC;AAC7C;AAAA,IACD;AAEA,YAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC9C;AACD;","names":[]}
@@ -0,0 +1,39 @@
1
+ import {
2
+ CronValidationError
3
+ } from "./chunk-AWLSYOVF.js";
4
+
5
+ // src/core/schedule.ts
6
+ import { CronExpressionParser } from "cron-parser";
7
+ import cronstrue from "cronstrue";
8
+ function validateCronExpression(cronExpr) {
9
+ const fields = cronExpr.trim().split(/\s+/);
10
+ if (fields.length !== 5) {
11
+ throw new CronValidationError(
12
+ cronExpr,
13
+ `Expected 5 fields (minute hour day month weekday), got ${fields.length}`
14
+ );
15
+ }
16
+ try {
17
+ CronExpressionParser.parse(cronExpr);
18
+ } catch (error) {
19
+ const msg = error instanceof Error ? error.message : String(error);
20
+ throw new CronValidationError(cronExpr, msg);
21
+ }
22
+ }
23
+ function describeSchedule(cronExpr) {
24
+ return cronstrue.toString(cronExpr, {
25
+ use24HourTimeFormat: false,
26
+ verbose: true
27
+ });
28
+ }
29
+ function getNextRuns(cronExpr, timezone, count = 3) {
30
+ const interval = CronExpressionParser.parse(cronExpr, { tz: timezone });
31
+ return interval.take(count).map((d) => d.toDate());
32
+ }
33
+
34
+ export {
35
+ validateCronExpression,
36
+ describeSchedule,
37
+ getNextRuns
38
+ };
39
+ //# sourceMappingURL=chunk-TORYFKPK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/schedule.ts"],"sourcesContent":["import { CronExpressionParser } from \"cron-parser\";\nimport cronstrue from \"cronstrue\";\nimport { CronValidationError } from \"../util/errors.js\";\nimport type { ScheduleInfo } from \"./types.js\";\n\n/**\n * Validate a cron expression. Throws CronValidationError if invalid.\n * Only accepts standard 5-field cron expressions.\n */\nexport function validateCronExpression(cronExpr: string): void {\n\tconst fields = cronExpr.trim().split(/\\s+/);\n\tif (fields.length !== 5) {\n\t\tthrow new CronValidationError(\n\t\t\tcronExpr,\n\t\t\t`Expected 5 fields (minute hour day month weekday), got ${fields.length}`,\n\t\t);\n\t}\n\ttry {\n\t\tCronExpressionParser.parse(cronExpr);\n\t} catch (error: unknown) {\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tthrow new CronValidationError(cronExpr, msg);\n\t}\n}\n\n/**\n * Get a human-readable description of a cron expression.\n * Example: \"0 *​/6 * * *\" -> \"Every 6 hours\"\n */\nexport function describeSchedule(cronExpr: string): string {\n\treturn cronstrue.toString(cronExpr, {\n\t\tuse24HourTimeFormat: false,\n\t\tverbose: true,\n\t});\n}\n\n/**\n * Compute the next N run times for a cron expression in the given IANA timezone.\n */\nexport function getNextRuns(cronExpr: string, timezone: string, count = 3): Date[] {\n\tconst interval = CronExpressionParser.parse(cronExpr, { tz: timezone });\n\treturn interval.take(count).map((d) => d.toDate());\n}\n\n/**\n * Validate a cron expression and return complete schedule info\n * including human-readable description and next run times.\n */\nexport function validateAndDescribeSchedule(cronExpr: string, timezone: string): ScheduleInfo {\n\tvalidateCronExpression(cronExpr);\n\tconst humanReadable = describeSchedule(cronExpr);\n\tconst nextRuns = getNextRuns(cronExpr, timezone);\n\treturn { cron: cronExpr, timezone, humanReadable, nextRuns };\n}\n"],"mappings":";;;;;AAAA,SAAS,4BAA4B;AACrC,OAAO,eAAe;AAQf,SAAS,uBAAuB,UAAwB;AAC9D,QAAM,SAAS,SAAS,KAAK,EAAE,MAAM,KAAK;AAC1C,MAAI,OAAO,WAAW,GAAG;AACxB,UAAM,IAAI;AAAA,MACT;AAAA,MACA,0DAA0D,OAAO,MAAM;AAAA,IACxE;AAAA,EACD;AACA,MAAI;AACH,yBAAqB,MAAM,QAAQ;AAAA,EACpC,SAAS,OAAgB;AACxB,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,UAAM,IAAI,oBAAoB,UAAU,GAAG;AAAA,EAC5C;AACD;AAMO,SAAS,iBAAiB,UAA0B;AAC1D,SAAO,UAAU,SAAS,UAAU;AAAA,IACnC,qBAAqB;AAAA,IACrB,SAAS;AAAA,EACV,CAAC;AACF;AAKO,SAAS,YAAY,UAAkB,UAAkB,QAAQ,GAAW;AAClF,QAAM,WAAW,qBAAqB,MAAM,UAAU,EAAE,IAAI,SAAS,CAAC;AACtE,SAAO,SAAS,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AAClD;","names":[]}
@@ -0,0 +1,143 @@
1
+ import {
2
+ execCommand
3
+ } from "./chunk-3NEANSUS.js";
4
+ import {
5
+ SchedulerError
6
+ } from "./chunk-YMO45Z6G.js";
7
+
8
+ // src/platform/schtasks.ts
9
+ import { dirname, join } from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { CronExpressionParser } from "cron-parser";
12
+ var TASK_PREFIX = "claude-auto-";
13
+ var dayMap = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
14
+ function cronToSchtasks(cronExpr) {
15
+ const interval = CronExpressionParser.parse(cronExpr);
16
+ const fields = interval.fields;
17
+ const minutes = [...fields.minute.values].map(Number);
18
+ const hours = [...fields.hour.values].map(Number);
19
+ const daysOfMonth = [...fields.dayOfMonth.values].map(Number);
20
+ const months = [...fields.month.values].map(Number);
21
+ const daysOfWeek = [...fields.dayOfWeek.values].map(Number);
22
+ const isAllHours = hours.length === 24;
23
+ const isAllDays = daysOfMonth.length === 31;
24
+ const isAllMonths = months.length === 12;
25
+ const isAllDow = daysOfWeek.length === 8;
26
+ if (isAllHours && isAllDays && isAllMonths && isAllDow && minutes.length > 1) {
27
+ const step = minutes[1] - minutes[0];
28
+ const isEvenStep = minutes.every((m, i) => i === 0 || m - minutes[i - 1] === step);
29
+ if (isEvenStep) {
30
+ return {
31
+ args: ["/sc", "MINUTE", "/mo", String(step)],
32
+ description: `every ${step} minutes`
33
+ };
34
+ }
35
+ }
36
+ if (isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length > 1) {
37
+ const step = hours[1] - hours[0];
38
+ const isEvenStep = hours.every((h, i) => i === 0 || h - hours[i - 1] === step);
39
+ if (isEvenStep) {
40
+ const st = `${String(hours[0]).padStart(2, "0")}:${String(minutes[0]).padStart(2, "0")}`;
41
+ return {
42
+ args: ["/sc", "HOURLY", "/mo", String(step), "/st", st],
43
+ description: `every ${step} hours starting at ${st}`
44
+ };
45
+ }
46
+ }
47
+ if (isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length === 1) {
48
+ const st = `${String(hours[0]).padStart(2, "0")}:${String(minutes[0]).padStart(2, "0")}`;
49
+ return {
50
+ args: ["/sc", "DAILY", "/st", st],
51
+ description: `daily at ${st}`
52
+ };
53
+ }
54
+ if (isAllDays && isAllMonths && !isAllDow && minutes.length === 1 && hours.length === 1) {
55
+ const days = daysOfWeek.filter((d) => d <= 6).map((d) => dayMap[d]);
56
+ const st = `${String(hours[0]).padStart(2, "0")}:${String(minutes[0]).padStart(2, "0")}`;
57
+ return {
58
+ args: ["/sc", "WEEKLY", "/d", days.join(","), "/st", st],
59
+ description: `weekly on ${days.join(",")} at ${st}`
60
+ };
61
+ }
62
+ if (!isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length === 1 && daysOfMonth.length === 1) {
63
+ const st = `${String(hours[0]).padStart(2, "0")}:${String(minutes[0]).padStart(2, "0")}`;
64
+ return {
65
+ args: ["/sc", "MONTHLY", "/d", String(daysOfMonth[0]), "/st", st],
66
+ description: `monthly on day ${daysOfMonth[0]} at ${st}`
67
+ };
68
+ }
69
+ throw new SchedulerError(
70
+ "win32",
71
+ `Cron expression "${cronExpr}" cannot be represented as a single Windows Task Scheduler entry. Simplify the schedule (e.g., daily at a specific time, every N minutes, or specific weekdays).`
72
+ );
73
+ }
74
+ function getRunnerPath() {
75
+ try {
76
+ const currentDir = dirname(fileURLToPath(import.meta.url));
77
+ return join(currentDir, "..", "..", "dist", "claude-auto-run.js");
78
+ } catch {
79
+ return join(process.cwd(), "dist", "claude-auto-run.js");
80
+ }
81
+ }
82
+ var SchtasksScheduler = class {
83
+ async register(job, _env) {
84
+ const registered = await this.isRegistered(job.id);
85
+ if (registered) {
86
+ throw new SchedulerError("win32", `Job "${job.id}" is already registered`);
87
+ }
88
+ const schedule = cronToSchtasks(job.schedule.cron);
89
+ const runnerPath = getRunnerPath();
90
+ const command = `"${process.execPath}" "${runnerPath}" --job-id ${job.id}`;
91
+ const taskName = `${TASK_PREFIX}${job.id}`;
92
+ const args = ["/create", "/tn", taskName, "/tr", command, ...schedule.args, "/f"];
93
+ await execCommand("schtasks", args);
94
+ }
95
+ async unregister(jobId) {
96
+ const taskName = `${TASK_PREFIX}${jobId}`;
97
+ try {
98
+ await execCommand("schtasks", ["/delete", "/tn", taskName, "/f"]);
99
+ } catch {
100
+ }
101
+ }
102
+ async isRegistered(jobId) {
103
+ const taskName = `${TASK_PREFIX}${jobId}`;
104
+ try {
105
+ await execCommand("schtasks", ["/query", "/tn", taskName, "/fo", "CSV", "/nh"]);
106
+ return true;
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+ async list() {
112
+ const jobs = [];
113
+ try {
114
+ const { stdout } = await execCommand("schtasks", ["/query", "/fo", "CSV", "/nh"]);
115
+ const lines = stdout.split("\n").filter((line) => line.includes(TASK_PREFIX));
116
+ for (const line of lines) {
117
+ try {
118
+ const match = line.match(/claude-auto-([^"]+)/);
119
+ if (match) {
120
+ const jobId = match[1];
121
+ const fields = line.split('","');
122
+ const schedule = fields.length > 1 ? fields[1] : "";
123
+ const command = fields.length > 2 ? fields[2]?.replace(/"/g, "") : "";
124
+ jobs.push({
125
+ jobId,
126
+ schedule,
127
+ command
128
+ });
129
+ }
130
+ } catch {
131
+ }
132
+ }
133
+ } catch {
134
+ }
135
+ return jobs;
136
+ }
137
+ };
138
+
139
+ export {
140
+ cronToSchtasks,
141
+ SchtasksScheduler
142
+ };
143
+ //# sourceMappingURL=chunk-U35GRLBD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/schtasks.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CronExpressionParser } from \"cron-parser\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nconst TASK_PREFIX = \"claude-auto-\";\n\nexport interface SchtasksSchedule {\n\targs: string[];\n\tdescription: string;\n}\n\nconst dayMap = [\"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\"];\n\n/**\n * Translate a 5-field cron expression into schtasks /SC schedule parameters.\n *\n * Supports:\n * - Every N minutes (e.g., *\\/30 * * * *)\n * - Every N hours at minute M (e.g., 0 *\\/6 * * *)\n * - Daily at specific time (e.g., 0 9 * * *)\n * - Specific days of week (e.g., 0 9 * * 1-5)\n * - Monthly on a single day (e.g., 0 9 15 * *)\n *\n * Throws SchedulerError for patterns that cannot be represented as a single\n * Windows Task Scheduler entry.\n */\nexport function cronToSchtasks(cronExpr: string): SchtasksSchedule {\n\tconst interval = CronExpressionParser.parse(cronExpr);\n\tconst fields = interval.fields;\n\n\tconst minutes = [...fields.minute.values].map(Number);\n\tconst hours = [...fields.hour.values].map(Number);\n\tconst daysOfMonth = [...fields.dayOfMonth.values].map(Number);\n\tconst months = [...fields.month.values].map(Number);\n\tconst daysOfWeek = [...fields.dayOfWeek.values].map(Number);\n\n\tconst isAllHours = hours.length === 24;\n\tconst isAllDays = daysOfMonth.length === 31;\n\tconst isAllMonths = months.length === 12;\n\t// cron-parser returns 0-7 for dayOfWeek (0 and 7 both mean Sunday)\n\tconst isAllDow = daysOfWeek.length === 8;\n\n\t// Pattern: every N minutes (e.g., */30 * * * *)\n\tif (isAllHours && isAllDays && isAllMonths && isAllDow && minutes.length > 1) {\n\t\tconst step = minutes[1] - minutes[0];\n\t\tconst isEvenStep = minutes.every((m, i) => i === 0 || m - minutes[i - 1] === step);\n\t\tif (isEvenStep) {\n\t\t\treturn {\n\t\t\t\targs: [\"/sc\", \"MINUTE\", \"/mo\", String(step)],\n\t\t\t\tdescription: `every ${step} minutes`,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Pattern: every N hours at minute M (e.g., 0 */6 * * *)\n\tif (isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length > 1) {\n\t\tconst step = hours[1] - hours[0];\n\t\tconst isEvenStep = hours.every((h, i) => i === 0 || h - hours[i - 1] === step);\n\t\tif (isEvenStep) {\n\t\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\t\treturn {\n\t\t\t\targs: [\"/sc\", \"HOURLY\", \"/mo\", String(step), \"/st\", st],\n\t\t\t\tdescription: `every ${step} hours starting at ${st}`,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Pattern: daily at specific time (e.g., 0 9 * * *)\n\tif (isAllDays && isAllMonths && isAllDow && minutes.length === 1 && hours.length === 1) {\n\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\treturn {\n\t\t\targs: [\"/sc\", \"DAILY\", \"/st\", st],\n\t\t\tdescription: `daily at ${st}`,\n\t\t};\n\t}\n\n\t// Pattern: specific days of week (e.g., 0 9 * * 1-5)\n\tif (isAllDays && isAllMonths && !isAllDow && minutes.length === 1 && hours.length === 1) {\n\t\tconst days = daysOfWeek.filter((d) => d <= 6).map((d) => dayMap[d]);\n\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\treturn {\n\t\t\targs: [\"/sc\", \"WEEKLY\", \"/d\", days.join(\",\"), \"/st\", st],\n\t\t\tdescription: `weekly on ${days.join(\",\")} at ${st}`,\n\t\t};\n\t}\n\n\t// Pattern: specific day of month (e.g., 0 9 15 * *)\n\tif (\n\t\t!isAllDays &&\n\t\tisAllMonths &&\n\t\tisAllDow &&\n\t\tminutes.length === 1 &&\n\t\thours.length === 1 &&\n\t\tdaysOfMonth.length === 1\n\t) {\n\t\tconst st = `${String(hours[0]).padStart(2, \"0\")}:${String(minutes[0]).padStart(2, \"0\")}`;\n\t\treturn {\n\t\t\targs: [\"/sc\", \"MONTHLY\", \"/d\", String(daysOfMonth[0]), \"/st\", st],\n\t\t\tdescription: `monthly on day ${daysOfMonth[0]} at ${st}`,\n\t\t};\n\t}\n\n\t// Unsupported pattern\n\tthrow new SchedulerError(\n\t\t\"win32\",\n\t\t`Cron expression \"${cronExpr}\" cannot be represented as a single Windows Task Scheduler entry. ` +\n\t\t\t\"Simplify the schedule (e.g., daily at a specific time, every N minutes, or specific weekdays).\",\n\t);\n}\n\n/**\n * Resolve the runner script path relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\t// Fallback for test/bundle environments\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * SchtasksScheduler implements the Scheduler interface for Windows systems.\n * Uses `schtasks.exe` to create, query, and delete tasks in Windows Task Scheduler.\n *\n * Task name format: `claude-auto-{jobId}`\n */\nexport class SchtasksScheduler implements Scheduler {\n\tasync register(job: JobConfig, _env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"win32\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst schedule = cronToSchtasks(job.schedule.cron);\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `\"${process.execPath}\" \"${runnerPath}\" --job-id ${job.id}`;\n\t\tconst taskName = `${TASK_PREFIX}${job.id}`;\n\n\t\tconst args = [\"/create\", \"/tn\", taskName, \"/tr\", command, ...schedule.args, \"/f\"];\n\n\t\tawait execCommand(\"schtasks\", args);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst taskName = `${TASK_PREFIX}${jobId}`;\n\t\ttry {\n\t\t\tawait execCommand(\"schtasks\", [\"/delete\", \"/tn\", taskName, \"/f\"]);\n\t\t} catch {\n\t\t\t// Task may not exist -- continue gracefully (same pattern as launchd bootout)\n\t\t}\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst taskName = `${TASK_PREFIX}${jobId}`;\n\t\ttry {\n\t\t\tawait execCommand(\"schtasks\", [\"/query\", \"/tn\", taskName, \"/fo\", \"CSV\", \"/nh\"]);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst jobs: RegisteredJob[] = [];\n\t\ttry {\n\t\t\tconst { stdout } = await execCommand(\"schtasks\", [\"/query\", \"/fo\", \"CSV\", \"/nh\"]);\n\t\t\tconst lines = stdout.split(\"\\n\").filter((line) => line.includes(TASK_PREFIX));\n\n\t\t\tfor (const line of lines) {\n\t\t\t\ttry {\n\t\t\t\t\t// CSV format: \"TaskName\",\"Next Run Time\",\"Status\"\n\t\t\t\t\t// Task names may include path prefix like \\claude-auto-jobId\n\t\t\t\t\tconst match = line.match(/claude-auto-([^\"]+)/);\n\t\t\t\t\tif (match) {\n\t\t\t\t\t\tconst jobId = match[1];\n\t\t\t\t\t\t// Extract schedule and status from CSV fields\n\t\t\t\t\t\tconst fields = line.split('\",\"');\n\t\t\t\t\t\tconst schedule = fields.length > 1 ? fields[1] : \"\";\n\t\t\t\t\t\tconst command = fields.length > 2 ? fields[2]?.replace(/\"/g, \"\") : \"\";\n\n\t\t\t\t\t\tjobs.push({\n\t\t\t\t\t\t\tjobId,\n\t\t\t\t\t\t\tschedule,\n\t\t\t\t\t\t\tcommand,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Skip malformed lines\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// No tasks found or schtasks not available\n\t\t}\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,57 @@
1
+ import {
2
+ createScheduler
3
+ } from "./chunk-QLRCFKLU.js";
4
+ import {
5
+ deleteJob,
6
+ readJob
7
+ } from "./chunk-24PS2XSV.js";
8
+ import {
9
+ paths
10
+ } from "./chunk-H2MUDYMW.js";
11
+
12
+ // src/cli/commands/remove.ts
13
+ import { cp, mkdir } from "fs/promises";
14
+ import { join } from "path";
15
+ async function removeCommand(args) {
16
+ const jobId = args.jobId;
17
+ if (!jobId) {
18
+ console.error("Usage: claude-auto remove <job-id> [--keep-logs]");
19
+ throw new Error("Missing required argument: job-id");
20
+ }
21
+ try {
22
+ await readJob(jobId);
23
+ } catch (err) {
24
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
25
+ console.error(`Job ${jobId} not found.`);
26
+ throw err;
27
+ }
28
+ throw err;
29
+ }
30
+ try {
31
+ const scheduler = await createScheduler();
32
+ await scheduler.unregister(jobId);
33
+ } catch {
34
+ }
35
+ let archivePath;
36
+ if (args.keepLogs) {
37
+ const logsDir = paths.jobLogs(jobId);
38
+ archivePath = join(paths.base, "archived-logs", jobId);
39
+ try {
40
+ await mkdir(archivePath, { recursive: true });
41
+ await cp(logsDir, archivePath, { recursive: true });
42
+ } catch {
43
+ archivePath = void 0;
44
+ }
45
+ }
46
+ await deleteJob(jobId);
47
+ if (archivePath) {
48
+ console.log(`Job ${jobId} removed. Run logs archived to ${archivePath}.`);
49
+ } else {
50
+ console.log(`Job ${jobId} removed.`);
51
+ }
52
+ }
53
+
54
+ export {
55
+ removeCommand
56
+ };
57
+ //# sourceMappingURL=chunk-W2HBRERV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/remove.ts"],"sourcesContent":["import { cp, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { deleteJob, readJob } from \"../../core/job-manager.js\";\nimport { createScheduler } from \"../../platform/scheduler.js\";\nimport { paths } from \"../../util/paths.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Remove a job: unregisters from scheduler and deletes job directory.\n * With --keep-logs: archives run logs before deleting.\n */\nexport async function removeCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tif (!jobId) {\n\t\tconsole.error(\"Usage: claude-auto remove <job-id> [--keep-logs]\");\n\t\tthrow new Error(\"Missing required argument: job-id\");\n\t}\n\n\ttry {\n\t\tawait readJob(jobId);\n\t} catch (err: unknown) {\n\t\tif (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tconsole.error(`Job ${jobId} not found.`);\n\t\t\tthrow err;\n\t\t}\n\t\tthrow err;\n\t}\n\n\t// Best-effort unregister from scheduler\n\ttry {\n\t\tconst scheduler = await createScheduler();\n\t\tawait scheduler.unregister(jobId);\n\t} catch {\n\t\t// Scheduler entry may already be missing -- best-effort\n\t}\n\n\tlet archivePath: string | undefined;\n\tif (args.keepLogs) {\n\t\t// Archive run logs before deleting\n\t\tconst logsDir = paths.jobLogs(jobId);\n\t\tarchivePath = join(paths.base, \"archived-logs\", jobId);\n\t\ttry {\n\t\t\tawait mkdir(archivePath, { recursive: true });\n\t\t\tawait cp(logsDir, archivePath, { recursive: true });\n\t\t} catch {\n\t\t\t// If logs dir doesn't exist, nothing to archive\n\t\t\tarchivePath = undefined;\n\t\t}\n\t}\n\n\tawait deleteJob(jobId);\n\n\tif (archivePath) {\n\t\tconsole.log(`Job ${jobId} removed. Run logs archived to ${archivePath}.`);\n\t} else {\n\t\tconsole.log(`Job ${jobId} removed.`);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,IAAI,aAAa;AAC1B,SAAS,YAAY;AAUrB,eAAsB,cAAc,MAA4C;AAC/E,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,OAAO;AACX,YAAQ,MAAM,kDAAkD;AAChE,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACpD;AAEA,MAAI;AACH,UAAM,QAAQ,KAAK;AAAA,EACpB,SAAS,KAAc;AACtB,QAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,UAAU;AAC9F,cAAQ,MAAM,OAAO,KAAK,aAAa;AACvC,YAAM;AAAA,IACP;AACA,UAAM;AAAA,EACP;AAGA,MAAI;AACH,UAAM,YAAY,MAAM,gBAAgB;AACxC,UAAM,UAAU,WAAW,KAAK;AAAA,EACjC,QAAQ;AAAA,EAER;AAEA,MAAI;AACJ,MAAI,KAAK,UAAU;AAElB,UAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,kBAAc,KAAK,MAAM,MAAM,iBAAiB,KAAK;AACrD,QAAI;AACH,YAAM,MAAM,aAAa,EAAE,WAAW,KAAK,CAAC;AAC5C,YAAM,GAAG,SAAS,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,IACnD,QAAQ;AAEP,oBAAc;AAAA,IACf;AAAA,EACD;AAEA,QAAM,UAAU,KAAK;AAErB,MAAI,aAAa;AAChB,YAAQ,IAAI,OAAO,KAAK,kCAAkC,WAAW,GAAG;AAAA,EACzE,OAAO;AACN,YAAQ,IAAI,OAAO,KAAK,WAAW;AAAA,EACpC;AACD;","names":[]}