@cjvana/claude-auto 0.1.1 → 0.2.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 (121) hide show
  1. package/dist/{chunk-24PS2XSV.js → chunk-27NCPABY.js} +4 -67
  2. package/dist/chunk-27NCPABY.js.map +1 -0
  3. package/dist/chunk-2CLZERVS.js +1703 -0
  4. package/dist/chunk-2CLZERVS.js.map +1 -0
  5. package/dist/{chunk-2D5E23XA.js → chunk-2G2KQJ2Q.js} +3 -3
  6. package/dist/chunk-2G2KQJ2Q.js.map +1 -0
  7. package/dist/{chunk-BY5YEOVG.js → chunk-3VNP3RVY.js} +2 -2
  8. package/dist/{chunk-M53MPY3U.js → chunk-4VUPUHND.js} +3 -3
  9. package/dist/chunk-76PDFLLR.js +1744 -0
  10. package/dist/chunk-76PDFLLR.js.map +1 -0
  11. package/dist/{chunk-WYU476R2.js → chunk-CFD6OA3U.js} +6 -1
  12. package/dist/chunk-CFD6OA3U.js.map +1 -0
  13. package/dist/{chunk-PFU5YLRH.js → chunk-DE5AVSQ4.js} +3 -3
  14. package/dist/{chunk-S6W4SURF.js → chunk-ETRY7XUT.js} +4 -4
  15. package/dist/{chunk-W2HBRERV.js → chunk-FXPJY6FJ.js} +3 -3
  16. package/dist/chunk-I4RM5O56.js +75 -0
  17. package/dist/chunk-I4RM5O56.js.map +1 -0
  18. package/dist/{chunk-QLRCFKLU.js → chunk-JKZUC53H.js} +4 -4
  19. package/dist/{chunk-SZRIZBWI.js → chunk-LW7XOWL2.js} +3 -3
  20. package/dist/{chunk-U35GRLBD.js → chunk-O3NXIT5A.js} +6 -1
  21. package/dist/chunk-O3NXIT5A.js.map +1 -0
  22. package/dist/{chunk-MI7OZ5XD.js → chunk-PDL7SDI3.js} +2 -2
  23. package/dist/{chunk-HF7PGQI3.js → chunk-QJIXXBMI.js} +2 -2
  24. package/dist/{chunk-LBH6SLHH.js → chunk-RWA7YWPE.js} +4 -4
  25. package/dist/{chunk-TAGHPCFT.js → chunk-SZA5UZBI.js} +3 -3
  26. package/dist/{chunk-QQTIJN3S.js → chunk-TKUHYD6T.js} +6 -1
  27. package/dist/chunk-TKUHYD6T.js.map +1 -0
  28. package/dist/{chunk-DVZC42TL.js → chunk-XCXFSLSS.js} +4 -4
  29. package/dist/{chunk-NB46PEG2.js → chunk-Y7ACM23C.js} +3 -3
  30. package/dist/{chunk-NB46PEG2.js.map → chunk-Y7ACM23C.js.map} +1 -1
  31. package/dist/claude-auto-run.js +9 -1683
  32. package/dist/claude-auto-run.js.map +1 -1
  33. package/dist/claude-auto.js +14 -8
  34. package/dist/claude-auto.js.map +1 -1
  35. package/dist/{create-U5WYKTD4.js → create-DC63FYWP.js} +4 -4
  36. package/dist/{create-T3BDDS6G.js → create-Z2OVC5JK.js} +5 -4
  37. package/dist/{crontab-CDMC2FDT.js → crontab-OMTYKJC7.js} +6 -1
  38. package/dist/crontab-OMTYKJC7.js.map +1 -0
  39. package/dist/{crontab-MAJ52FOK.js → crontab-YKFQ3AQA.js} +6 -1
  40. package/dist/crontab-YKFQ3AQA.js.map +1 -0
  41. package/dist/{crontab-PNEWANLW.js → crontab-ZZHAZ5OJ.js} +2 -2
  42. package/dist/{edit-77E3ZQHM.js → edit-O5T3UXM4.js} +4 -4
  43. package/dist/{edit-RVPRAAQ2.js → edit-ZGUTPFHQ.js} +5 -4
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.js +78 -1682
  46. package/dist/index.js.map +1 -1
  47. package/dist/{launchd-HNZIWLNC.js → launchd-5COTJSRK.js} +6 -1
  48. package/dist/launchd-5COTJSRK.js.map +1 -0
  49. package/dist/{launchd-7F27BIZB.js → launchd-6NKJDIYC.js} +6 -1
  50. package/dist/launchd-6NKJDIYC.js.map +1 -0
  51. package/dist/{launchd-LZGDP7BM.js → launchd-MZNYUY3Y.js} +2 -2
  52. package/dist/{list-OIGERGYJ.js → list-55ABTJVW.js} +4 -3
  53. package/dist/{list-T35RSQVU.js → list-AS7F4LFU.js} +3 -3
  54. package/dist/{pause-OJNUYBCJ.js → pause-72P4ERIU.js} +4 -4
  55. package/dist/{pause-JB42JGTB.js → pause-JCPZFBVZ.js} +3 -3
  56. package/dist/pause-TAS6BCYR.js +13 -0
  57. package/dist/{remove-UASXZCOR.js → remove-RMTSKUYN.js} +4 -4
  58. package/dist/remove-VBIVYREY.js +13 -0
  59. package/dist/{report-IYGK5HTC.js → report-53DC5DL2.js} +4 -3
  60. package/dist/{report-CHAJH2SA.js → report-6O43ICNG.js} +3 -3
  61. package/dist/{resume-3ATNZP6D.js → resume-3TB76AUU.js} +5 -4
  62. package/dist/{resume-JVTR7OEX.js → resume-4XSHJ4SN.js} +4 -4
  63. package/dist/{resume-6WVGU6XW.js → resume-A2PWKRLE.js} +3 -3
  64. package/dist/run-CXRPDX2S.js +34 -0
  65. package/dist/run-CXRPDX2S.js.map +1 -0
  66. package/dist/run-H3IF4QXQ.js +34 -0
  67. package/dist/run-H3IF4QXQ.js.map +1 -0
  68. package/dist/{schtasks-4V2IFD3A.js → schtasks-5IZCEIPB.js} +6 -1
  69. package/dist/schtasks-5IZCEIPB.js.map +1 -0
  70. package/dist/{schtasks-JGEPEKQS.js → schtasks-6GQ27GI2.js} +6 -1
  71. package/dist/schtasks-6GQ27GI2.js.map +1 -0
  72. package/dist/{schtasks-2EQAD3ES.js → schtasks-ZJ5NAIU6.js} +2 -2
  73. package/dist/{tui-6LOGPILA.js → tui-NVOPC5SD.js} +5 -5
  74. package/dist/{tui-2DUPCX3Q.js → tui-VKT4UBXQ.js} +4 -3
  75. package/package.json +1 -1
  76. package/dist/chunk-24PS2XSV.js.map +0 -1
  77. package/dist/chunk-2D5E23XA.js.map +0 -1
  78. package/dist/chunk-QQTIJN3S.js.map +0 -1
  79. package/dist/chunk-U35GRLBD.js.map +0 -1
  80. package/dist/chunk-WYU476R2.js.map +0 -1
  81. package/dist/crontab-CDMC2FDT.js.map +0 -1
  82. package/dist/crontab-MAJ52FOK.js.map +0 -1
  83. package/dist/launchd-7F27BIZB.js.map +0 -1
  84. package/dist/launchd-HNZIWLNC.js.map +0 -1
  85. package/dist/pause-2YOLFMAR.js +0 -12
  86. package/dist/remove-RXYKFYBI.js +0 -12
  87. package/dist/schtasks-4V2IFD3A.js.map +0 -1
  88. package/dist/schtasks-JGEPEKQS.js.map +0 -1
  89. /package/dist/{chunk-BY5YEOVG.js.map → chunk-3VNP3RVY.js.map} +0 -0
  90. /package/dist/{chunk-M53MPY3U.js.map → chunk-4VUPUHND.js.map} +0 -0
  91. /package/dist/{chunk-PFU5YLRH.js.map → chunk-DE5AVSQ4.js.map} +0 -0
  92. /package/dist/{chunk-DVZC42TL.js.map → chunk-ETRY7XUT.js.map} +0 -0
  93. /package/dist/{chunk-W2HBRERV.js.map → chunk-FXPJY6FJ.js.map} +0 -0
  94. /package/dist/{chunk-QLRCFKLU.js.map → chunk-JKZUC53H.js.map} +0 -0
  95. /package/dist/{chunk-SZRIZBWI.js.map → chunk-LW7XOWL2.js.map} +0 -0
  96. /package/dist/{chunk-MI7OZ5XD.js.map → chunk-PDL7SDI3.js.map} +0 -0
  97. /package/dist/{chunk-HF7PGQI3.js.map → chunk-QJIXXBMI.js.map} +0 -0
  98. /package/dist/{chunk-LBH6SLHH.js.map → chunk-RWA7YWPE.js.map} +0 -0
  99. /package/dist/{chunk-TAGHPCFT.js.map → chunk-SZA5UZBI.js.map} +0 -0
  100. /package/dist/{chunk-S6W4SURF.js.map → chunk-XCXFSLSS.js.map} +0 -0
  101. /package/dist/{create-U5WYKTD4.js.map → create-DC63FYWP.js.map} +0 -0
  102. /package/dist/{create-T3BDDS6G.js.map → create-Z2OVC5JK.js.map} +0 -0
  103. /package/dist/{crontab-PNEWANLW.js.map → crontab-ZZHAZ5OJ.js.map} +0 -0
  104. /package/dist/{edit-77E3ZQHM.js.map → edit-O5T3UXM4.js.map} +0 -0
  105. /package/dist/{edit-RVPRAAQ2.js.map → edit-ZGUTPFHQ.js.map} +0 -0
  106. /package/dist/{launchd-LZGDP7BM.js.map → launchd-MZNYUY3Y.js.map} +0 -0
  107. /package/dist/{list-OIGERGYJ.js.map → list-55ABTJVW.js.map} +0 -0
  108. /package/dist/{list-T35RSQVU.js.map → list-AS7F4LFU.js.map} +0 -0
  109. /package/dist/{pause-OJNUYBCJ.js.map → pause-72P4ERIU.js.map} +0 -0
  110. /package/dist/{pause-JB42JGTB.js.map → pause-JCPZFBVZ.js.map} +0 -0
  111. /package/dist/{pause-2YOLFMAR.js.map → pause-TAS6BCYR.js.map} +0 -0
  112. /package/dist/{remove-UASXZCOR.js.map → remove-RMTSKUYN.js.map} +0 -0
  113. /package/dist/{remove-RXYKFYBI.js.map → remove-VBIVYREY.js.map} +0 -0
  114. /package/dist/{report-IYGK5HTC.js.map → report-53DC5DL2.js.map} +0 -0
  115. /package/dist/{report-CHAJH2SA.js.map → report-6O43ICNG.js.map} +0 -0
  116. /package/dist/{resume-3ATNZP6D.js.map → resume-3TB76AUU.js.map} +0 -0
  117. /package/dist/{resume-JVTR7OEX.js.map → resume-4XSHJ4SN.js.map} +0 -0
  118. /package/dist/{resume-6WVGU6XW.js.map → resume-A2PWKRLE.js.map} +0 -0
  119. /package/dist/{schtasks-2EQAD3ES.js.map → schtasks-ZJ5NAIU6.js.map} +0 -0
  120. /package/dist/{tui-6LOGPILA.js.map → tui-NVOPC5SD.js.map} +0 -0
  121. /package/dist/{tui-2DUPCX3Q.js.map → tui-VKT4UBXQ.js.map} +0 -0
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  reportCommand
3
- } from "./chunk-MI7OZ5XD.js";
3
+ } from "./chunk-PDL7SDI3.js";
4
4
  import "./chunk-SMZYA6CY.js";
5
5
  import "./chunk-ORBF5IW3.js";
6
6
  import "./chunk-4I5UIASZ.js";
7
- import "./chunk-24PS2XSV.js";
7
+ import "./chunk-3VNP3RVY.js";
8
+ import "./chunk-27NCPABY.js";
8
9
  import "./chunk-E3XVLTT4.js";
9
10
  import "./chunk-H2MUDYMW.js";
10
11
  import "./chunk-YMO45Z6G.js";
11
12
  export {
12
13
  reportCommand
13
14
  };
14
- //# sourceMappingURL=report-IYGK5HTC.js.map
15
+ //# sourceMappingURL=report-53DC5DL2.js.map
@@ -8,8 +8,8 @@ import "./chunk-4I5UIASZ.js";
8
8
  import {
9
9
  listJobs,
10
10
  readJob
11
- } from "./chunk-BY5YEOVG.js";
12
- import "./chunk-2D5E23XA.js";
11
+ } from "./chunk-I4RM5O56.js";
12
+ import "./chunk-2G2KQJ2Q.js";
13
13
  import "./chunk-E3XVLTT4.js";
14
14
  import "./chunk-H2MUDYMW.js";
15
15
  import "./chunk-AWLSYOVF.js";
@@ -147,4 +147,4 @@ async function reportCommand(args) {
147
147
  export {
148
148
  reportCommand
149
149
  };
150
- //# sourceMappingURL=report-CHAJH2SA.js.map
150
+ //# sourceMappingURL=report-6O43ICNG.js.map
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  resumeCommand
3
- } from "./chunk-TAGHPCFT.js";
4
- import "./chunk-QLRCFKLU.js";
3
+ } from "./chunk-SZA5UZBI.js";
4
+ import "./chunk-JKZUC53H.js";
5
5
  import "./chunk-D4MBOIYQ.js";
6
- import "./chunk-24PS2XSV.js";
6
+ import "./chunk-3VNP3RVY.js";
7
+ import "./chunk-27NCPABY.js";
7
8
  import "./chunk-E3XVLTT4.js";
8
9
  import "./chunk-H2MUDYMW.js";
9
10
  import "./chunk-YMO45Z6G.js";
10
11
  export {
11
12
  resumeCommand
12
13
  };
13
- //# sourceMappingURL=resume-3ATNZP6D.js.map
14
+ //# sourceMappingURL=resume-3TB76AUU.js.map
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  createScheduler
3
- } from "./chunk-DVZC42TL.js";
3
+ } from "./chunk-XCXFSLSS.js";
4
4
  import {
5
5
  getNextRuns
6
6
  } from "./chunk-TORYFKPK.js";
7
7
  import {
8
8
  readJob,
9
9
  updateJob
10
- } from "./chunk-BY5YEOVG.js";
11
- import "./chunk-2D5E23XA.js";
10
+ } from "./chunk-I4RM5O56.js";
11
+ import "./chunk-2G2KQJ2Q.js";
12
12
  import "./chunk-E3XVLTT4.js";
13
13
  import "./chunk-H2MUDYMW.js";
14
14
  import "./chunk-AWLSYOVF.js";
@@ -47,4 +47,4 @@ async function resumeCommand(args) {
47
47
  export {
48
48
  resumeCommand
49
49
  };
50
- //# sourceMappingURL=resume-JVTR7OEX.js.map
50
+ //# sourceMappingURL=resume-4XSHJ4SN.js.map
@@ -3,11 +3,11 @@ import {
3
3
  } from "./chunk-SNOA575X.js";
4
4
  import {
5
5
  createScheduler
6
- } from "./chunk-S6W4SURF.js";
6
+ } from "./chunk-ETRY7XUT.js";
7
7
  import {
8
8
  readJob,
9
9
  updateJob
10
- } from "./chunk-NB46PEG2.js";
10
+ } from "./chunk-Y7ACM23C.js";
11
11
  import "./chunk-H2MUDYMW.js";
12
12
  import "./chunk-6RYMWH5M.js";
13
13
 
@@ -45,4 +45,4 @@ async function resumeCommand(args) {
45
45
  export {
46
46
  resumeCommand
47
47
  };
48
- //# sourceMappingURL=resume-6WVGU6XW.js.map
48
+ //# sourceMappingURL=resume-A2PWKRLE.js.map
@@ -0,0 +1,34 @@
1
+ import {
2
+ executeRun
3
+ } from "./chunk-2CLZERVS.js";
4
+ import "./chunk-S6E67XMR.js";
5
+ import "./chunk-3NEANSUS.js";
6
+ import "./chunk-GLW7T4QE.js";
7
+ import "./chunk-4I5UIASZ.js";
8
+ import "./chunk-2G2KQJ2Q.js";
9
+ import "./chunk-E3XVLTT4.js";
10
+ import "./chunk-H2MUDYMW.js";
11
+ import "./chunk-AWLSYOVF.js";
12
+
13
+ // src/cli/commands/run.ts
14
+ async function runCommand(args) {
15
+ const jobId = args.jobId;
16
+ if (!jobId) {
17
+ console.error("Usage: claude-auto run <job-id>");
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ console.log(`Triggering run for job: ${jobId}`);
22
+ const result = await executeRun(jobId);
23
+ console.log(`Run complete: ${result.status}`);
24
+ if (result.prUrl) {
25
+ console.log(`PR: ${result.prUrl}`);
26
+ }
27
+ if (result.error) {
28
+ console.error(`Error: ${result.error}`);
29
+ }
30
+ }
31
+ export {
32
+ runCommand
33
+ };
34
+ //# sourceMappingURL=run-CXRPDX2S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/run.ts"],"sourcesContent":["import { executeRun } from \"../../runner/orchestrator.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Trigger an immediate run of a job.\n * Equivalent to what the cron entry point does, but invoked manually.\n */\nexport async function runCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tif (!jobId) {\n\t\tconsole.error(\"Usage: claude-auto run <job-id>\");\n\t\tprocess.exitCode = 1;\n\t\treturn;\n\t}\n\n\tconsole.log(`Triggering run for job: ${jobId}`);\n\tconst result = await executeRun(jobId);\n\n\tconsole.log(`Run complete: ${result.status}`);\n\tif (result.prUrl) {\n\t\tconsole.log(`PR: ${result.prUrl}`);\n\t}\n\tif (result.error) {\n\t\tconsole.error(`Error: ${result.error}`);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAOA,eAAsB,WAAW,MAA4C;AAC5E,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,OAAO;AACX,YAAQ,MAAM,iCAAiC;AAC/C,YAAQ,WAAW;AACnB;AAAA,EACD;AAEA,UAAQ,IAAI,2BAA2B,KAAK,EAAE;AAC9C,QAAM,SAAS,MAAM,WAAW,KAAK;AAErC,UAAQ,IAAI,iBAAiB,OAAO,MAAM,EAAE;AAC5C,MAAI,OAAO,OAAO;AACjB,YAAQ,IAAI,OAAO,OAAO,KAAK,EAAE;AAAA,EAClC;AACA,MAAI,OAAO,OAAO;AACjB,YAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,EACvC;AACD;","names":[]}
@@ -0,0 +1,34 @@
1
+ import {
2
+ executeRun
3
+ } from "./chunk-76PDFLLR.js";
4
+ import "./chunk-SMZYA6CY.js";
5
+ import "./chunk-S6E67XMR.js";
6
+ import "./chunk-4I5UIASZ.js";
7
+ import "./chunk-3NEANSUS.js";
8
+ import "./chunk-27NCPABY.js";
9
+ import "./chunk-E3XVLTT4.js";
10
+ import "./chunk-H2MUDYMW.js";
11
+ import "./chunk-YMO45Z6G.js";
12
+
13
+ // src/cli/commands/run.ts
14
+ async function runCommand(args) {
15
+ const jobId = args.jobId;
16
+ if (!jobId) {
17
+ console.error("Usage: claude-auto run <job-id>");
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ console.log(`Triggering run for job: ${jobId}`);
22
+ const result = await executeRun(jobId);
23
+ console.log(`Run complete: ${result.status}`);
24
+ if (result.prUrl) {
25
+ console.log(`PR: ${result.prUrl}`);
26
+ }
27
+ if (result.error) {
28
+ console.error(`Error: ${result.error}`);
29
+ }
30
+ }
31
+ export {
32
+ runCommand
33
+ };
34
+ //# sourceMappingURL=run-H3IF4QXQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/run.ts"],"sourcesContent":["import { executeRun } from \"../../runner/orchestrator.js\";\nimport type { ParsedCommand } from \"../types.js\";\n\n/**\n * Trigger an immediate run of a job.\n * Equivalent to what the cron entry point does, but invoked manually.\n */\nexport async function runCommand(args: ParsedCommand[\"args\"]): Promise<void> {\n\tconst jobId = args.jobId as string | undefined;\n\tif (!jobId) {\n\t\tconsole.error(\"Usage: claude-auto run <job-id>\");\n\t\tprocess.exitCode = 1;\n\t\treturn;\n\t}\n\n\tconsole.log(`Triggering run for job: ${jobId}`);\n\tconst result = await executeRun(jobId);\n\n\tconsole.log(`Run complete: ${result.status}`);\n\tif (result.prUrl) {\n\t\tconsole.log(`PR: ${result.prUrl}`);\n\t}\n\tif (result.error) {\n\t\tconsole.error(`Error: ${result.error}`);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAOA,eAAsB,WAAW,MAA4C;AAC5E,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,OAAO;AACX,YAAQ,MAAM,iCAAiC;AAC/C,YAAQ,WAAW;AACnB;AAAA,EACD;AAEA,UAAQ,IAAI,2BAA2B,KAAK,EAAE;AAC9C,QAAM,SAAS,MAAM,WAAW,KAAK;AAErC,UAAQ,IAAI,iBAAiB,OAAO,MAAM,EAAE;AAC5C,MAAI,OAAO,OAAO;AACjB,YAAQ,IAAI,OAAO,OAAO,KAAK,EAAE;AAAA,EAClC;AACA,MAAI,OAAO,OAAO;AACjB,YAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,EACvC;AACD;","names":[]}
@@ -6,6 +6,7 @@ import {
6
6
  } from "./chunk-6RYMWH5M.js";
7
7
 
8
8
  // src/platform/schtasks.ts
9
+ import { existsSync } from "fs";
9
10
  import { dirname, join } from "path";
10
11
  import { fileURLToPath } from "url";
11
12
  import { CronExpressionParser } from "cron-parser";
@@ -74,6 +75,10 @@ function cronToSchtasks(cronExpr) {
74
75
  function getRunnerPath() {
75
76
  try {
76
77
  const currentDir = dirname(fileURLToPath(import.meta.url));
78
+ const siblingPath = join(currentDir, "claude-auto-run.js");
79
+ if (existsSync(siblingPath)) {
80
+ return siblingPath;
81
+ }
77
82
  return join(currentDir, "..", "..", "dist", "claude-auto-run.js");
78
83
  } catch {
79
84
  return join(process.cwd(), "dist", "claude-auto-run.js");
@@ -139,4 +144,4 @@ export {
139
144
  SchtasksScheduler,
140
145
  cronToSchtasks
141
146
  };
142
- //# sourceMappingURL=schtasks-4V2IFD3A.js.map
147
+ //# sourceMappingURL=schtasks-5IZCEIPB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/schtasks.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { 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.\n * When bundled by tsup, this file lives in dist/ alongside claude-auto-run.js.\n * When running from source (src/platform/), navigate up to project root.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\t// Bundled: runner is a sibling in the same dist/ directory\n\t\tconst siblingPath = join(currentDir, \"claude-auto-run.js\");\n\t\tif (existsSync(siblingPath)) {\n\t\t\treturn siblingPath;\n\t\t}\n\t\t// Source: navigate from src/platform/ up to project root\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * 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":";;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AAMrC,IAAM,cAAc;AAOpB,IAAM,SAAS,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAexD,SAAS,eAAe,UAAoC;AAClE,QAAM,WAAW,qBAAqB,MAAM,QAAQ;AACpD,QAAM,SAAS,SAAS;AAExB,QAAM,UAAU,CAAC,GAAG,OAAO,OAAO,MAAM,EAAE,IAAI,MAAM;AACpD,QAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,MAAM,EAAE,IAAI,MAAM;AAChD,QAAM,cAAc,CAAC,GAAG,OAAO,WAAW,MAAM,EAAE,IAAI,MAAM;AAC5D,QAAM,SAAS,CAAC,GAAG,OAAO,MAAM,MAAM,EAAE,IAAI,MAAM;AAClD,QAAM,aAAa,CAAC,GAAG,OAAO,UAAU,MAAM,EAAE,IAAI,MAAM;AAE1D,QAAM,aAAa,MAAM,WAAW;AACpC,QAAM,YAAY,YAAY,WAAW;AACzC,QAAM,cAAc,OAAO,WAAW;AAEtC,QAAM,WAAW,WAAW,WAAW;AAGvC,MAAI,cAAc,aAAa,eAAe,YAAY,QAAQ,SAAS,GAAG;AAC7E,UAAM,OAAO,QAAQ,CAAC,IAAI,QAAQ,CAAC;AACnC,UAAM,aAAa,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,KAAK,IAAI,QAAQ,IAAI,CAAC,MAAM,IAAI;AACjF,QAAI,YAAY;AACf,aAAO;AAAA,QACN,MAAM,CAAC,OAAO,UAAU,OAAO,OAAO,IAAI,CAAC;AAAA,QAC3C,aAAa,SAAS,IAAI;AAAA,MAC3B;AAAA,IACD;AAAA,EACD;AAGA,MAAI,aAAa,eAAe,YAAY,QAAQ,WAAW,KAAK,MAAM,SAAS,GAAG;AACrF,UAAM,OAAO,MAAM,CAAC,IAAI,MAAM,CAAC;AAC/B,UAAM,aAAa,MAAM,MAAM,CAAC,GAAG,MAAM,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,MAAM,IAAI;AAC7E,QAAI,YAAY;AACf,YAAM,KAAK,GAAG,OAAO,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACtF,aAAO;AAAA,QACN,MAAM,CAAC,OAAO,UAAU,OAAO,OAAO,IAAI,GAAG,OAAO,EAAE;AAAA,QACtD,aAAa,SAAS,IAAI,sBAAsB,EAAE;AAAA,MACnD;AAAA,IACD;AAAA,EACD;AAGA,MAAI,aAAa,eAAe,YAAY,QAAQ,WAAW,KAAK,MAAM,WAAW,GAAG;AACvF,UAAM,KAAK,GAAG,OAAO,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACtF,WAAO;AAAA,MACN,MAAM,CAAC,OAAO,SAAS,OAAO,EAAE;AAAA,MAChC,aAAa,YAAY,EAAE;AAAA,IAC5B;AAAA,EACD;AAGA,MAAI,aAAa,eAAe,CAAC,YAAY,QAAQ,WAAW,KAAK,MAAM,WAAW,GAAG;AACxF,UAAM,OAAO,WAAW,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;AAClE,UAAM,KAAK,GAAG,OAAO,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACtF,WAAO;AAAA,MACN,MAAM,CAAC,OAAO,UAAU,MAAM,KAAK,KAAK,GAAG,GAAG,OAAO,EAAE;AAAA,MACvD,aAAa,aAAa,KAAK,KAAK,GAAG,CAAC,OAAO,EAAE;AAAA,IAClD;AAAA,EACD;AAGA,MACC,CAAC,aACD,eACA,YACA,QAAQ,WAAW,KACnB,MAAM,WAAW,KACjB,YAAY,WAAW,GACtB;AACD,UAAM,KAAK,GAAG,OAAO,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACtF,WAAO;AAAA,MACN,MAAM,CAAC,OAAO,WAAW,MAAM,OAAO,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE;AAAA,MAChE,aAAa,kBAAkB,YAAY,CAAC,CAAC,OAAO,EAAE;AAAA,IACvD;AAAA,EACD;AAGA,QAAM,IAAI;AAAA,IACT;AAAA,IACA,oBAAoB,QAAQ;AAAA,EAE7B;AACD;AAOA,SAAS,gBAAwB;AAChC,MAAI;AACH,UAAM,aAAa,QAAQ,cAAc,YAAY,GAAG,CAAC;AAEzD,UAAM,cAAc,KAAK,YAAY,oBAAoB;AACzD,QAAI,WAAW,WAAW,GAAG;AAC5B,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,YAAY,MAAM,MAAM,QAAQ,oBAAoB;AAAA,EACjE,QAAQ;AACP,WAAO,KAAK,QAAQ,IAAI,GAAG,QAAQ,oBAAoB;AAAA,EACxD;AACD;AAQO,IAAM,oBAAN,MAA6C;AAAA,EACnD,MAAM,SAAS,KAAgB,MAA8C;AAC5E,UAAM,aAAa,MAAM,KAAK,aAAa,IAAI,EAAE;AACjD,QAAI,YAAY;AACf,YAAM,IAAI,eAAe,SAAS,QAAQ,IAAI,EAAE,yBAAyB;AAAA,IAC1E;AAEA,UAAM,WAAW,eAAe,IAAI,SAAS,IAAI;AACjD,UAAM,aAAa,cAAc;AACjC,UAAM,UAAU,IAAI,QAAQ,QAAQ,MAAM,UAAU,cAAc,IAAI,EAAE;AACxE,UAAM,WAAW,GAAG,WAAW,GAAG,IAAI,EAAE;AAExC,UAAM,OAAO,CAAC,WAAW,OAAO,UAAU,OAAO,SAAS,GAAG,SAAS,MAAM,IAAI;AAEhF,UAAM,YAAY,YAAY,IAAI;AAAA,EACnC;AAAA,EAEA,MAAM,WAAW,OAA8B;AAC9C,UAAM,WAAW,GAAG,WAAW,GAAG,KAAK;AACvC,QAAI;AACH,YAAM,YAAY,YAAY,CAAC,WAAW,OAAO,UAAU,IAAI,CAAC;AAAA,IACjE,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,MAAM,aAAa,OAAiC;AACnD,UAAM,WAAW,GAAG,WAAW,GAAG,KAAK;AACvC,QAAI;AACH,YAAM,YAAY,YAAY,CAAC,UAAU,OAAO,UAAU,OAAO,OAAO,KAAK,CAAC;AAC9E,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,OAAiC;AACtC,UAAM,OAAwB,CAAC;AAC/B,QAAI;AACH,YAAM,EAAE,OAAO,IAAI,MAAM,YAAY,YAAY,CAAC,UAAU,OAAO,OAAO,KAAK,CAAC;AAChF,YAAM,QAAQ,OAAO,MAAM,IAAI,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,WAAW,CAAC;AAE5E,iBAAW,QAAQ,OAAO;AACzB,YAAI;AAGH,gBAAM,QAAQ,KAAK,MAAM,qBAAqB;AAC9C,cAAI,OAAO;AACV,kBAAM,QAAQ,MAAM,CAAC;AAErB,kBAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,kBAAM,WAAW,OAAO,SAAS,IAAI,OAAO,CAAC,IAAI;AACjD,kBAAM,UAAU,OAAO,SAAS,IAAI,OAAO,CAAC,GAAG,QAAQ,MAAM,EAAE,IAAI;AAEnE,iBAAK,KAAK;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YACD,CAAC;AAAA,UACF;AAAA,QACD,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACR;AACD;","names":[]}
@@ -6,6 +6,7 @@ import {
6
6
  } from "./chunk-AWLSYOVF.js";
7
7
 
8
8
  // src/platform/schtasks.ts
9
+ import { existsSync } from "fs";
9
10
  import { dirname, join } from "path";
10
11
  import { fileURLToPath } from "url";
11
12
  import { CronExpressionParser } from "cron-parser";
@@ -74,6 +75,10 @@ function cronToSchtasks(cronExpr) {
74
75
  function getRunnerPath() {
75
76
  try {
76
77
  const currentDir = dirname(fileURLToPath(import.meta.url));
78
+ const siblingPath = join(currentDir, "claude-auto-run.js");
79
+ if (existsSync(siblingPath)) {
80
+ return siblingPath;
81
+ }
77
82
  return join(currentDir, "..", "..", "dist", "claude-auto-run.js");
78
83
  } catch {
79
84
  return join(process.cwd(), "dist", "claude-auto-run.js");
@@ -139,4 +144,4 @@ export {
139
144
  SchtasksScheduler,
140
145
  cronToSchtasks
141
146
  };
142
- //# sourceMappingURL=schtasks-JGEPEKQS.js.map
147
+ //# sourceMappingURL=schtasks-6GQ27GI2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/schtasks.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { 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.\n * When bundled by tsup, this file lives in dist/ alongside claude-auto-run.js.\n * When running from source (src/platform/), navigate up to project root.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\t// Bundled: runner is a sibling in the same dist/ directory\n\t\tconst siblingPath = join(currentDir, \"claude-auto-run.js\");\n\t\tif (existsSync(siblingPath)) {\n\t\t\treturn siblingPath;\n\t\t}\n\t\t// Source: navigate from src/platform/ up to project root\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * 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":[]}
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  SchtasksScheduler,
3
3
  cronToSchtasks
4
- } from "./chunk-U35GRLBD.js";
4
+ } from "./chunk-O3NXIT5A.js";
5
5
  import "./chunk-3NEANSUS.js";
6
6
  import "./chunk-YMO45Z6G.js";
7
7
  export {
8
8
  SchtasksScheduler,
9
9
  cronToSchtasks
10
10
  };
11
- //# sourceMappingURL=schtasks-2EQAD3ES.js.map
11
+ //# sourceMappingURL=schtasks-ZJ5NAIU6.js.map
@@ -10,8 +10,8 @@ import {
10
10
  } from "./chunk-TORYFKPK.js";
11
11
  import {
12
12
  listJobs
13
- } from "./chunk-BY5YEOVG.js";
14
- import "./chunk-2D5E23XA.js";
13
+ } from "./chunk-I4RM5O56.js";
14
+ import "./chunk-2G2KQJ2Q.js";
15
15
  import "./chunk-E3XVLTT4.js";
16
16
  import "./chunk-H2MUDYMW.js";
17
17
  import "./chunk-AWLSYOVF.js";
@@ -503,11 +503,11 @@ function App() {
503
503
  if (!job) return;
504
504
  try {
505
505
  if (job.enabled) {
506
- const { pauseCommand } = await import("./pause-OJNUYBCJ.js");
506
+ const { pauseCommand } = await import("./pause-72P4ERIU.js");
507
507
  await pauseCommand({ jobId });
508
508
  setStatusMessage(`Paused: ${job.name}`);
509
509
  } else {
510
- const { resumeCommand } = await import("./resume-JVTR7OEX.js");
510
+ const { resumeCommand } = await import("./resume-4XSHJ4SN.js");
511
511
  await resumeCommand({ jobId });
512
512
  setStatusMessage(`Resumed: ${job.name}`);
513
513
  }
@@ -544,4 +544,4 @@ async function launchDashboard() {
544
544
  export {
545
545
  launchDashboard
546
546
  };
547
- //# sourceMappingURL=tui-6LOGPILA.js.map
547
+ //# sourceMappingURL=tui-NVOPC5SD.js.map
@@ -1,15 +1,16 @@
1
1
  import {
2
2
  launchDashboard
3
- } from "./chunk-LBH6SLHH.js";
3
+ } from "./chunk-RWA7YWPE.js";
4
4
  import "./chunk-SMZYA6CY.js";
5
5
  import "./chunk-S6E67XMR.js";
6
6
  import "./chunk-4I5UIASZ.js";
7
7
  import "./chunk-D4MBOIYQ.js";
8
- import "./chunk-24PS2XSV.js";
8
+ import "./chunk-3VNP3RVY.js";
9
+ import "./chunk-27NCPABY.js";
9
10
  import "./chunk-E3XVLTT4.js";
10
11
  import "./chunk-H2MUDYMW.js";
11
12
  import "./chunk-YMO45Z6G.js";
12
13
  export {
13
14
  launchDashboard
14
15
  };
15
- //# sourceMappingURL=tui-2DUPCX3Q.js.map
16
+ //# sourceMappingURL=tui-VKT4UBXQ.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjvana/claude-auto",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Autonomous Claude Code cron jobs for continuous codebase improvement",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/platform/launchd.ts"],"sourcesContent":["import { access, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CronExpressionParser } from \"cron-parser\";\nimport plist from \"plist\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport { paths } from \"../util/paths.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nexport interface CalendarInterval {\n\tMonth?: number;\n\tDay?: number;\n\tWeekday?: number;\n\tHour?: number;\n\tMinute?: number;\n}\n\nconst LABEL_PREFIX = \"com.claude-auto.\";\n\nfunction getLabel(jobId: string): string {\n\treturn `${LABEL_PREFIX}${jobId}`;\n}\n\nfunction getUid(): number {\n\treturn process.getuid?.() ?? 501;\n}\n\n/**\n * Resolve the runner script path relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * Convert a 5-field cron expression to launchd scheduling config.\n *\n * For high-frequency \"every N minutes\" patterns, returns { startInterval: seconds }.\n * For specific times, returns { calendarIntervals: CalendarInterval[] }.\n *\n * Throws if the expression would produce more than 50 calendar intervals.\n */\nexport function cronToCalendarIntervals(cronExpr: string): {\n\tcalendarIntervals?: CalendarInterval[];\n\tstartInterval?: number;\n} {\n\tconst interval = CronExpressionParser.parse(cronExpr);\n\tconst fields = interval.fields;\n\n\tconst minutes = [...fields.minute.values].map(Number);\n\tconst hours = [...fields.hour.values].map(Number);\n\tconst daysOfMonth = [...fields.dayOfMonth.values].map(Number);\n\tconst months = [...fields.month.values].map(Number);\n\tconst daysOfWeek = [...fields.dayOfWeek.values].map(Number);\n\n\tconst isAllHours = hours.length === 24;\n\tconst isAllDays = daysOfMonth.length === 31;\n\tconst isAllMonths = months.length === 12;\n\t// cron-parser returns 0-7 for dayOfWeek (8 values since 0 and 7 both mean Sunday)\n\tconst isAllDow = daysOfWeek.length === 8;\n\n\t// Detect \"every N minutes\" pattern: hours/days/months/dow all wildcard, minutes have even spacing\n\tif (isAllHours && isAllDays && isAllMonths && isAllDow && minutes.length > 1) {\n\t\tconst step = minutes[1] - minutes[0];\n\t\tconst isEvenStep = minutes.every((m, i) => i === 0 || m - minutes[i - 1] === step);\n\t\tif (isEvenStep) {\n\t\t\treturn { startInterval: step * 60 };\n\t\t}\n\t}\n\n\t// Build calendar interval combinations\n\tconst intervals: CalendarInterval[] = [];\n\tconst effectiveHours = isAllHours ? [undefined] : hours;\n\t// Filter out duplicate Sunday (7) -- launchd uses 0 for Sunday\n\tconst effectiveDow = isAllDow ? [undefined] : daysOfWeek.filter((d) => d <= 6);\n\tconst effectiveDom = isAllDays ? [undefined] : daysOfMonth;\n\n\tfor (const minute of minutes) {\n\t\tfor (const hour of effectiveHours) {\n\t\t\tfor (const dow of effectiveDow) {\n\t\t\t\tfor (const dom of effectiveDom) {\n\t\t\t\t\tconst entry: CalendarInterval = { Minute: minute };\n\t\t\t\t\tif (hour !== undefined) entry.Hour = hour;\n\t\t\t\t\tif (dow !== undefined) entry.Weekday = dow;\n\t\t\t\t\tif (dom !== undefined) entry.Day = dom;\n\t\t\t\t\tintervals.push(entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (intervals.length > 50) {\n\t\tthrow new Error(\n\t\t\t`Cron expression \"${cronExpr}\" would produce ${intervals.length} calendar intervals. ` +\n\t\t\t\t\"launchd StartCalendarInterval is not efficient for complex schedules. \" +\n\t\t\t\t\"Simplify the schedule or use a simpler pattern.\",\n\t\t);\n\t}\n\n\treturn { calendarIntervals: intervals };\n}\n\n/**\n * LaunchdScheduler implements the Scheduler interface for macOS.\n * Uses plist files in ~/Library/LaunchAgents/ and modern launchctl bootstrap/bootout.\n */\nexport class LaunchdScheduler implements Scheduler {\n\tasync register(job: JobConfig, env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"launchd\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst scheduling = cronToCalendarIntervals(job.schedule.cron);\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst logPath = `${paths.jobLogs(job.id)}/launchd.log`;\n\t\tconst plistPath = paths.plistPath(job.id);\n\n\t\tconst obj: Record<string, unknown> = {\n\t\t\tLabel: getLabel(job.id),\n\t\t\tProgramArguments: [process.execPath, runnerPath, \"--job-id\", job.id],\n\t\t\tStandardOutPath: logPath,\n\t\t\tStandardErrorPath: logPath,\n\t\t\tEnvironmentVariables: {\n\t\t\t\tPATH: env?.PATH ?? process.env.PATH ?? \"\",\n\t\t\t\tHOME: env?.HOME ?? homedir(),\n\t\t\t\t...(env\n\t\t\t\t\t? Object.fromEntries(Object.entries(env).filter(([k]) => k !== \"PATH\" && k !== \"HOME\"))\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\tRunAtLoad: false,\n\t\t\tKeepAlive: false,\n\t\t};\n\n\t\tif (scheduling.startInterval !== undefined) {\n\t\t\tobj.StartInterval = scheduling.startInterval;\n\t\t} else if (scheduling.calendarIntervals) {\n\t\t\tobj.StartCalendarInterval =\n\t\t\t\tscheduling.calendarIntervals.length === 1\n\t\t\t\t\t? scheduling.calendarIntervals[0]\n\t\t\t\t\t: scheduling.calendarIntervals;\n\t\t}\n\n\t\tconst xml = plist.build(obj as unknown as plist.PlistValue);\n\t\tawait writeFile(plistPath, xml, \"utf-8\");\n\n\t\tconst uid = getUid();\n\t\tawait execCommand(\"launchctl\", [\"bootstrap\", `gui/${uid}`, plistPath]);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst uid = getUid();\n\t\tconst label = getLabel(jobId);\n\t\tconst plistPath = paths.plistPath(jobId);\n\n\t\ttry {\n\t\t\tawait execCommand(\"launchctl\", [\"bootout\", `gui/${uid}/${label}`]);\n\t\t} catch {\n\t\t\t// Service may already be unloaded -- continue to delete plist\n\t\t}\n\n\t\ttry {\n\t\t\tawait unlink(plistPath);\n\t\t} catch {\n\t\t\t// Plist may already be deleted\n\t\t}\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(paths.plistPath(jobId));\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst jobs: RegisteredJob[] = [];\n\t\ttry {\n\t\t\tconst files = await readdir(paths.plistDir);\n\t\t\tconst plistFiles = files.filter((f) => f.startsWith(LABEL_PREFIX) && f.endsWith(\".plist\"));\n\n\t\t\tfor (const file of plistFiles) {\n\t\t\t\ttry {\n\t\t\t\t\tconst filePath = join(paths.plistDir, file);\n\t\t\t\t\tconst content = await readFile(filePath, \"utf-8\");\n\t\t\t\t\tconst parsed = plist.parse(content) as Record<string, unknown>;\n\t\t\t\t\tconst label = parsed.Label as string;\n\t\t\t\t\tconst jobId = label.replace(LABEL_PREFIX, \"\");\n\t\t\t\t\tconst progArgs = (parsed.ProgramArguments as string[]) ?? [];\n\n\t\t\t\t\tlet schedule = \"\";\n\t\t\t\t\tif (parsed.StartInterval) {\n\t\t\t\t\t\tschedule = `every ${parsed.StartInterval}s`;\n\t\t\t\t\t} else if (parsed.StartCalendarInterval) {\n\t\t\t\t\t\tschedule = \"calendar\";\n\t\t\t\t\t}\n\n\t\t\t\t\tjobs.push({\n\t\t\t\t\t\tjobId,\n\t\t\t\t\t\tschedule,\n\t\t\t\t\t\tcommand: progArgs.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Skip malformed plists\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Directory may not exist\n\t\t}\n\t\treturn jobs;\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,QAAQ,SAAS,UAAU,QAAQ,iBAAiB;AAC7D,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AACrC,OAAO,WAAW;AAelB,IAAM,eAAe;AAErB,SAAS,SAAS,OAAuB;AACxC,SAAO,GAAG,YAAY,GAAG,KAAK;AAC/B;AAEA,SAAS,SAAiB;AACzB,SAAO,QAAQ,SAAS,KAAK;AAC9B;AAKA,SAAS,gBAAwB;AAChC,MAAI;AACH,UAAM,aAAa,QAAQ,cAAc,YAAY,GAAG,CAAC;AACzD,WAAO,KAAK,YAAY,MAAM,MAAM,QAAQ,oBAAoB;AAAA,EACjE,QAAQ;AACP,WAAO,KAAK,QAAQ,IAAI,GAAG,QAAQ,oBAAoB;AAAA,EACxD;AACD;AAUO,SAAS,wBAAwB,UAGtC;AACD,QAAM,WAAW,qBAAqB,MAAM,QAAQ;AACpD,QAAM,SAAS,SAAS;AAExB,QAAM,UAAU,CAAC,GAAG,OAAO,OAAO,MAAM,EAAE,IAAI,MAAM;AACpD,QAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,MAAM,EAAE,IAAI,MAAM;AAChD,QAAM,cAAc,CAAC,GAAG,OAAO,WAAW,MAAM,EAAE,IAAI,MAAM;AAC5D,QAAM,SAAS,CAAC,GAAG,OAAO,MAAM,MAAM,EAAE,IAAI,MAAM;AAClD,QAAM,aAAa,CAAC,GAAG,OAAO,UAAU,MAAM,EAAE,IAAI,MAAM;AAE1D,QAAM,aAAa,MAAM,WAAW;AACpC,QAAM,YAAY,YAAY,WAAW;AACzC,QAAM,cAAc,OAAO,WAAW;AAEtC,QAAM,WAAW,WAAW,WAAW;AAGvC,MAAI,cAAc,aAAa,eAAe,YAAY,QAAQ,SAAS,GAAG;AAC7E,UAAM,OAAO,QAAQ,CAAC,IAAI,QAAQ,CAAC;AACnC,UAAM,aAAa,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,KAAK,IAAI,QAAQ,IAAI,CAAC,MAAM,IAAI;AACjF,QAAI,YAAY;AACf,aAAO,EAAE,eAAe,OAAO,GAAG;AAAA,IACnC;AAAA,EACD;AAGA,QAAM,YAAgC,CAAC;AACvC,QAAM,iBAAiB,aAAa,CAAC,MAAS,IAAI;AAElD,QAAM,eAAe,WAAW,CAAC,MAAS,IAAI,WAAW,OAAO,CAAC,MAAM,KAAK,CAAC;AAC7E,QAAM,eAAe,YAAY,CAAC,MAAS,IAAI;AAE/C,aAAW,UAAU,SAAS;AAC7B,eAAW,QAAQ,gBAAgB;AAClC,iBAAW,OAAO,cAAc;AAC/B,mBAAW,OAAO,cAAc;AAC/B,gBAAM,QAA0B,EAAE,QAAQ,OAAO;AACjD,cAAI,SAAS,OAAW,OAAM,OAAO;AACrC,cAAI,QAAQ,OAAW,OAAM,UAAU;AACvC,cAAI,QAAQ,OAAW,OAAM,MAAM;AACnC,oBAAU,KAAK,KAAK;AAAA,QACrB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,MAAI,UAAU,SAAS,IAAI;AAC1B,UAAM,IAAI;AAAA,MACT,oBAAoB,QAAQ,mBAAmB,UAAU,MAAM;AAAA,IAGhE;AAAA,EACD;AAEA,SAAO,EAAE,mBAAmB,UAAU;AACvC;AAMO,IAAM,mBAAN,MAA4C;AAAA,EAClD,MAAM,SAAS,KAAgB,KAA6C;AAC3E,UAAM,aAAa,MAAM,KAAK,aAAa,IAAI,EAAE;AACjD,QAAI,YAAY;AACf,YAAM,IAAI,eAAe,WAAW,QAAQ,IAAI,EAAE,yBAAyB;AAAA,IAC5E;AAEA,UAAM,aAAa,wBAAwB,IAAI,SAAS,IAAI;AAC5D,UAAM,aAAa,cAAc;AACjC,UAAM,UAAU,GAAG,MAAM,QAAQ,IAAI,EAAE,CAAC;AACxC,UAAM,YAAY,MAAM,UAAU,IAAI,EAAE;AAExC,UAAM,MAA+B;AAAA,MACpC,OAAO,SAAS,IAAI,EAAE;AAAA,MACtB,kBAAkB,CAAC,QAAQ,UAAU,YAAY,YAAY,IAAI,EAAE;AAAA,MACnE,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,sBAAsB;AAAA,QACrB,MAAM,KAAK,QAAQ,QAAQ,IAAI,QAAQ;AAAA,QACvC,MAAM,KAAK,QAAQ,QAAQ;AAAA,QAC3B,GAAI,MACD,OAAO,YAAY,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,UAAU,MAAM,MAAM,CAAC,IACpF,CAAC;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,IACZ;AAEA,QAAI,WAAW,kBAAkB,QAAW;AAC3C,UAAI,gBAAgB,WAAW;AAAA,IAChC,WAAW,WAAW,mBAAmB;AACxC,UAAI,wBACH,WAAW,kBAAkB,WAAW,IACrC,WAAW,kBAAkB,CAAC,IAC9B,WAAW;AAAA,IAChB;AAEA,UAAM,MAAM,MAAM,MAAM,GAAkC;AAC1D,UAAM,UAAU,WAAW,KAAK,OAAO;AAEvC,UAAM,MAAM,OAAO;AACnB,UAAM,YAAY,aAAa,CAAC,aAAa,OAAO,GAAG,IAAI,SAAS,CAAC;AAAA,EACtE;AAAA,EAEA,MAAM,WAAW,OAA8B;AAC9C,UAAM,MAAM,OAAO;AACnB,UAAM,QAAQ,SAAS,KAAK;AAC5B,UAAM,YAAY,MAAM,UAAU,KAAK;AAEvC,QAAI;AACH,YAAM,YAAY,aAAa,CAAC,WAAW,OAAO,GAAG,IAAI,KAAK,EAAE,CAAC;AAAA,IAClE,QAAQ;AAAA,IAER;AAEA,QAAI;AACH,YAAM,OAAO,SAAS;AAAA,IACvB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,MAAM,aAAa,OAAiC;AACnD,QAAI;AACH,YAAM,OAAO,MAAM,UAAU,KAAK,CAAC;AACnC,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,OAAiC;AACtC,UAAM,OAAwB,CAAC;AAC/B,QAAI;AACH,YAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ;AAC1C,YAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,KAAK,EAAE,SAAS,QAAQ,CAAC;AAEzF,iBAAW,QAAQ,YAAY;AAC9B,YAAI;AACH,gBAAM,WAAW,KAAK,MAAM,UAAU,IAAI;AAC1C,gBAAM,UAAU,MAAM,SAAS,UAAU,OAAO;AAChD,gBAAM,SAAS,MAAM,MAAM,OAAO;AAClC,gBAAM,QAAQ,OAAO;AACrB,gBAAM,QAAQ,MAAM,QAAQ,cAAc,EAAE;AAC5C,gBAAM,WAAY,OAAO,oBAAiC,CAAC;AAE3D,cAAI,WAAW;AACf,cAAI,OAAO,eAAe;AACzB,uBAAW,SAAS,OAAO,aAAa;AAAA,UACzC,WAAW,OAAO,uBAAuB;AACxC,uBAAW;AAAA,UACZ;AAEA,eAAK,KAAK;AAAA,YACT;AAAA,YACA;AAAA,YACA,SAAS,SAAS,KAAK,GAAG;AAAA,UAC3B,CAAC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACR;AACD;","names":[]}
@@ -1 +0,0 @@
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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/platform/crontab.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport { paths } from \"../util/paths.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nconst MARKER_PREFIX = \"# claude-auto:\";\n\n/**\n * Build a crontab entry block for a claude-auto job.\n *\n * Format:\n * # claude-auto:{jobId}\n * CRON_TZ={timezone} (only if timezone !== \"UTC\")\n * {cronExpr} {command} >> {logPath} 2>&1\n */\nexport function buildEntryBlock(\n\tjobId: string,\n\tcronExpr: string,\n\tcommand: string,\n\ttimezone?: string,\n): string {\n\tconst logPath = `${paths.jobLogs(jobId)}/cron.log`;\n\tconst lines: string[] = [`${MARKER_PREFIX}${jobId}`];\n\tif (timezone && timezone !== \"UTC\") {\n\t\tlines.push(`CRON_TZ=${timezone}`);\n\t}\n\tlines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Read the current user's crontab. Returns empty string if none exists.\n */\nasync function readCrontab(): Promise<string> {\n\ttry {\n\t\tconst { stdout } = await execCommand(\"crontab\", [\"-l\"]);\n\t\treturn stdout;\n\t} catch (error: unknown) {\n\t\t// \"no crontab for user\" is not an error for our purposes\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tif (msg.includes(\"no crontab\")) {\n\t\t\treturn \"\";\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Write the full crontab content via stdin to `crontab -`.\n */\nasync function writeCrontab(content: string): Promise<void> {\n\tawait execCommand(\"crontab\", [\"-\"], { stdin: content });\n}\n\n/**\n * Resolve the runner script path. For now, compute relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\t// Fallback for test/bundle environments\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * CrontabScheduler implements the Scheduler interface for Linux systems.\n * Uses comment-tagged crontab entries for identification and CRUD.\n */\nexport class CrontabScheduler implements Scheduler {\n\tasync register(job: JobConfig, _env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"crontab\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst current = await readCrontab();\n\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;\n\t\tconst block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);\n\n\t\tconst updated = `${current.trimEnd()}\\n${block}\\n`;\n\t\tawait writeCrontab(updated);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst current = await readCrontab();\n\t\tconst lines = current.split(\"\\n\");\n\t\tconst marker = `${MARKER_PREFIX}${jobId}`;\n\n\t\tconst filtered: string[] = [];\n\t\tlet skipping = false;\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.trim() === marker) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tskipping &&\n\t\t\t\t(line.startsWith(\"CRON_TZ=\") || (!line.startsWith(\"#\") && line.trim() !== \"\"))\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t\tfiltered.push(line);\n\t\t}\n\n\t\tawait writeCrontab(filtered.join(\"\\n\"));\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst content = await readCrontab();\n\t\treturn content.includes(`${MARKER_PREFIX}${jobId}`);\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst content = await readCrontab();\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst jobs: RegisteredJob[] = [];\n\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i];\n\t\t\tif (line.startsWith(MARKER_PREFIX)) {\n\t\t\t\tconst jobId = line.slice(MARKER_PREFIX.length).trim();\n\n\t\t\t\t// Look ahead: skip optional CRON_TZ line, then find the cron entry\n\t\t\t\tlet entryLine = \"\";\n\t\t\t\tlet nextIdx = i + 1;\n\n\t\t\t\tif (nextIdx < lines.length && lines[nextIdx].startsWith(\"CRON_TZ=\")) {\n\t\t\t\t\tnextIdx++;\n\t\t\t\t}\n\n\t\t\t\tif (nextIdx < lines.length) {\n\t\t\t\t\tentryLine = lines[nextIdx];\n\t\t\t\t}\n\n\t\t\t\tif (entryLine.trim()) {\n\t\t\t\t\t// Parse the cron fields (first 5 space-separated fields) and the command (rest)\n\t\t\t\t\tconst parts = entryLine.trim().split(/\\s+/);\n\t\t\t\t\tif (parts.length >= 6) {\n\t\t\t\t\t\tconst schedule = parts.slice(0, 5).join(\" \");\n\t\t\t\t\t\tconst command = parts.slice(5).join(\" \");\n\t\t\t\t\t\tjobs.push({ jobId, schedule, command });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/platform/crontab.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport { paths } from \"../util/paths.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nconst MARKER_PREFIX = \"# claude-auto:\";\n\n/**\n * Build a crontab entry block for a claude-auto job.\n *\n * Format:\n * # claude-auto:{jobId}\n * CRON_TZ={timezone} (only if timezone !== \"UTC\")\n * {cronExpr} {command} >> {logPath} 2>&1\n */\nexport function buildEntryBlock(\n\tjobId: string,\n\tcronExpr: string,\n\tcommand: string,\n\ttimezone?: string,\n): string {\n\tconst logPath = `${paths.jobLogs(jobId)}/cron.log`;\n\tconst lines: string[] = [`${MARKER_PREFIX}${jobId}`];\n\tif (timezone && timezone !== \"UTC\") {\n\t\tlines.push(`CRON_TZ=${timezone}`);\n\t}\n\tlines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Read the current user's crontab. Returns empty string if none exists.\n */\nasync function readCrontab(): Promise<string> {\n\ttry {\n\t\tconst { stdout } = await execCommand(\"crontab\", [\"-l\"]);\n\t\treturn stdout;\n\t} catch (error: unknown) {\n\t\t// \"no crontab for user\" is not an error for our purposes\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tif (msg.includes(\"no crontab\")) {\n\t\t\treturn \"\";\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Write the full crontab content via stdin to `crontab -`.\n */\nasync function writeCrontab(content: string): Promise<void> {\n\tawait execCommand(\"crontab\", [\"-\"], { stdin: content });\n}\n\n/**\n * Resolve the runner script path. For now, compute relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\t// Fallback for test/bundle environments\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * CrontabScheduler implements the Scheduler interface for Linux systems.\n * Uses comment-tagged crontab entries for identification and CRUD.\n */\nexport class CrontabScheduler implements Scheduler {\n\tasync register(job: JobConfig, _env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"crontab\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst current = await readCrontab();\n\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;\n\t\tconst block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);\n\n\t\tconst updated = `${current.trimEnd()}\\n${block}\\n`;\n\t\tawait writeCrontab(updated);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst current = await readCrontab();\n\t\tconst lines = current.split(\"\\n\");\n\t\tconst marker = `${MARKER_PREFIX}${jobId}`;\n\n\t\tconst filtered: string[] = [];\n\t\tlet skipping = false;\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.trim() === marker) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tskipping &&\n\t\t\t\t(line.startsWith(\"CRON_TZ=\") || (!line.startsWith(\"#\") && line.trim() !== \"\"))\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t\tfiltered.push(line);\n\t\t}\n\n\t\tawait writeCrontab(filtered.join(\"\\n\"));\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst content = await readCrontab();\n\t\treturn content.includes(`${MARKER_PREFIX}${jobId}`);\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst content = await readCrontab();\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst jobs: RegisteredJob[] = [];\n\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i];\n\t\t\tif (line.startsWith(MARKER_PREFIX)) {\n\t\t\t\tconst jobId = line.slice(MARKER_PREFIX.length).trim();\n\n\t\t\t\t// Look ahead: skip optional CRON_TZ line, then find the cron entry\n\t\t\t\tlet entryLine = \"\";\n\t\t\t\tlet nextIdx = i + 1;\n\n\t\t\t\tif (nextIdx < lines.length && lines[nextIdx].startsWith(\"CRON_TZ=\")) {\n\t\t\t\t\tnextIdx++;\n\t\t\t\t}\n\n\t\t\t\tif (nextIdx < lines.length) {\n\t\t\t\t\tentryLine = lines[nextIdx];\n\t\t\t\t}\n\n\t\t\t\tif (entryLine.trim()) {\n\t\t\t\t\t// Parse the cron fields (first 5 space-separated fields) and the command (rest)\n\t\t\t\t\tconst parts = entryLine.trim().split(/\\s+/);\n\t\t\t\t\tif (parts.length >= 6) {\n\t\t\t\t\t\tconst schedule = parts.slice(0, 5).join(\" \");\n\t\t\t\t\t\tconst command = parts.slice(5).join(\" \");\n\t\t\t\t\t\tjobs.push({ jobId, schedule, command });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/platform/crontab.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport { paths } from \"../util/paths.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nconst MARKER_PREFIX = \"# claude-auto:\";\n\n/**\n * Build a crontab entry block for a claude-auto job.\n *\n * Format:\n * # claude-auto:{jobId}\n * CRON_TZ={timezone} (only if timezone !== \"UTC\")\n * {cronExpr} {command} >> {logPath} 2>&1\n */\nexport function buildEntryBlock(\n\tjobId: string,\n\tcronExpr: string,\n\tcommand: string,\n\ttimezone?: string,\n): string {\n\tconst logPath = `${paths.jobLogs(jobId)}/cron.log`;\n\tconst lines: string[] = [`${MARKER_PREFIX}${jobId}`];\n\tif (timezone && timezone !== \"UTC\") {\n\t\tlines.push(`CRON_TZ=${timezone}`);\n\t}\n\tlines.push(`${cronExpr} ${command} >> ${logPath} 2>&1`);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Read the current user's crontab. Returns empty string if none exists.\n */\nasync function readCrontab(): Promise<string> {\n\ttry {\n\t\tconst { stdout } = await execCommand(\"crontab\", [\"-l\"]);\n\t\treturn stdout;\n\t} catch (error: unknown) {\n\t\t// \"no crontab for user\" is not an error for our purposes\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\tif (msg.includes(\"no crontab\")) {\n\t\t\treturn \"\";\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Write the full crontab content via stdin to `crontab -`.\n */\nasync function writeCrontab(content: string): Promise<void> {\n\tawait execCommand(\"crontab\", [\"-\"], { stdin: content });\n}\n\n/**\n * Resolve the runner script path. For now, compute relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\t// Fallback for test/bundle environments\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * CrontabScheduler implements the Scheduler interface for Linux systems.\n * Uses comment-tagged crontab entries for identification and CRUD.\n */\nexport class CrontabScheduler implements Scheduler {\n\tasync register(job: JobConfig, _env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"crontab\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst current = await readCrontab();\n\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst command = `${process.execPath} ${runnerPath} --job-id ${job.id}`;\n\t\tconst block = buildEntryBlock(job.id, job.schedule.cron, command, job.schedule.timezone);\n\n\t\tconst updated = `${current.trimEnd()}\\n${block}\\n`;\n\t\tawait writeCrontab(updated);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst current = await readCrontab();\n\t\tconst lines = current.split(\"\\n\");\n\t\tconst marker = `${MARKER_PREFIX}${jobId}`;\n\n\t\tconst filtered: string[] = [];\n\t\tlet skipping = false;\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.trim() === marker) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tskipping &&\n\t\t\t\t(line.startsWith(\"CRON_TZ=\") || (!line.startsWith(\"#\") && line.trim() !== \"\"))\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t\tfiltered.push(line);\n\t\t}\n\n\t\tawait writeCrontab(filtered.join(\"\\n\"));\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\tconst content = await readCrontab();\n\t\treturn content.includes(`${MARKER_PREFIX}${jobId}`);\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst content = await readCrontab();\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst jobs: RegisteredJob[] = [];\n\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i];\n\t\t\tif (line.startsWith(MARKER_PREFIX)) {\n\t\t\t\tconst jobId = line.slice(MARKER_PREFIX.length).trim();\n\n\t\t\t\t// Look ahead: skip optional CRON_TZ line, then find the cron entry\n\t\t\t\tlet entryLine = \"\";\n\t\t\t\tlet nextIdx = i + 1;\n\n\t\t\t\tif (nextIdx < lines.length && lines[nextIdx].startsWith(\"CRON_TZ=\")) {\n\t\t\t\t\tnextIdx++;\n\t\t\t\t}\n\n\t\t\t\tif (nextIdx < lines.length) {\n\t\t\t\t\tentryLine = lines[nextIdx];\n\t\t\t\t}\n\n\t\t\t\tif (entryLine.trim()) {\n\t\t\t\t\t// Parse the cron fields (first 5 space-separated fields) and the command (rest)\n\t\t\t\t\tconst parts = entryLine.trim().split(/\\s+/);\n\t\t\t\t\tif (parts.length >= 6) {\n\t\t\t\t\t\tconst schedule = parts.slice(0, 5).join(\" \");\n\t\t\t\t\t\tconst command = parts.slice(5).join(\" \");\n\t\t\t\t\t\tjobs.push({ jobId, schedule, command });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/platform/launchd.ts"],"sourcesContent":["import { access, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CronExpressionParser } from \"cron-parser\";\nimport plist from \"plist\";\nimport type { JobConfig } from \"../core/types.js\";\nimport { SchedulerError } from \"../util/errors.js\";\nimport { execCommand } from \"../util/exec.js\";\nimport { paths } from \"../util/paths.js\";\nimport type { RegisteredJob, Scheduler } from \"./scheduler.js\";\n\nexport interface CalendarInterval {\n\tMonth?: number;\n\tDay?: number;\n\tWeekday?: number;\n\tHour?: number;\n\tMinute?: number;\n}\n\nconst LABEL_PREFIX = \"com.claude-auto.\";\n\nfunction getLabel(jobId: string): string {\n\treturn `${LABEL_PREFIX}${jobId}`;\n}\n\nfunction getUid(): number {\n\treturn process.getuid?.() ?? 501;\n}\n\n/**\n * Resolve the runner script path relative to this module.\n */\nfunction getRunnerPath(): string {\n\ttry {\n\t\tconst currentDir = dirname(fileURLToPath(import.meta.url));\n\t\treturn join(currentDir, \"..\", \"..\", \"dist\", \"claude-auto-run.js\");\n\t} catch {\n\t\treturn join(process.cwd(), \"dist\", \"claude-auto-run.js\");\n\t}\n}\n\n/**\n * Convert a 5-field cron expression to launchd scheduling config.\n *\n * For high-frequency \"every N minutes\" patterns, returns { startInterval: seconds }.\n * For specific times, returns { calendarIntervals: CalendarInterval[] }.\n *\n * Throws if the expression would produce more than 50 calendar intervals.\n */\nexport function cronToCalendarIntervals(cronExpr: string): {\n\tcalendarIntervals?: CalendarInterval[];\n\tstartInterval?: number;\n} {\n\tconst interval = CronExpressionParser.parse(cronExpr);\n\tconst fields = interval.fields;\n\n\tconst minutes = [...fields.minute.values].map(Number);\n\tconst hours = [...fields.hour.values].map(Number);\n\tconst daysOfMonth = [...fields.dayOfMonth.values].map(Number);\n\tconst months = [...fields.month.values].map(Number);\n\tconst daysOfWeek = [...fields.dayOfWeek.values].map(Number);\n\n\tconst isAllHours = hours.length === 24;\n\tconst isAllDays = daysOfMonth.length === 31;\n\tconst isAllMonths = months.length === 12;\n\t// cron-parser returns 0-7 for dayOfWeek (8 values since 0 and 7 both mean Sunday)\n\tconst isAllDow = daysOfWeek.length === 8;\n\n\t// Detect \"every N minutes\" pattern: hours/days/months/dow all wildcard, minutes have even spacing\n\tif (isAllHours && isAllDays && isAllMonths && isAllDow && minutes.length > 1) {\n\t\tconst step = minutes[1] - minutes[0];\n\t\tconst isEvenStep = minutes.every((m, i) => i === 0 || m - minutes[i - 1] === step);\n\t\tif (isEvenStep) {\n\t\t\treturn { startInterval: step * 60 };\n\t\t}\n\t}\n\n\t// Build calendar interval combinations\n\tconst intervals: CalendarInterval[] = [];\n\tconst effectiveHours = isAllHours ? [undefined] : hours;\n\t// Filter out duplicate Sunday (7) -- launchd uses 0 for Sunday\n\tconst effectiveDow = isAllDow ? [undefined] : daysOfWeek.filter((d) => d <= 6);\n\tconst effectiveDom = isAllDays ? [undefined] : daysOfMonth;\n\n\tfor (const minute of minutes) {\n\t\tfor (const hour of effectiveHours) {\n\t\t\tfor (const dow of effectiveDow) {\n\t\t\t\tfor (const dom of effectiveDom) {\n\t\t\t\t\tconst entry: CalendarInterval = { Minute: minute };\n\t\t\t\t\tif (hour !== undefined) entry.Hour = hour;\n\t\t\t\t\tif (dow !== undefined) entry.Weekday = dow;\n\t\t\t\t\tif (dom !== undefined) entry.Day = dom;\n\t\t\t\t\tintervals.push(entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (intervals.length > 50) {\n\t\tthrow new Error(\n\t\t\t`Cron expression \"${cronExpr}\" would produce ${intervals.length} calendar intervals. ` +\n\t\t\t\t\"launchd StartCalendarInterval is not efficient for complex schedules. \" +\n\t\t\t\t\"Simplify the schedule or use a simpler pattern.\",\n\t\t);\n\t}\n\n\treturn { calendarIntervals: intervals };\n}\n\n/**\n * LaunchdScheduler implements the Scheduler interface for macOS.\n * Uses plist files in ~/Library/LaunchAgents/ and modern launchctl bootstrap/bootout.\n */\nexport class LaunchdScheduler implements Scheduler {\n\tasync register(job: JobConfig, env?: Record<string, string>): Promise<void> {\n\t\tconst registered = await this.isRegistered(job.id);\n\t\tif (registered) {\n\t\t\tthrow new SchedulerError(\"launchd\", `Job \"${job.id}\" is already registered`);\n\t\t}\n\n\t\tconst scheduling = cronToCalendarIntervals(job.schedule.cron);\n\t\tconst runnerPath = getRunnerPath();\n\t\tconst logPath = `${paths.jobLogs(job.id)}/launchd.log`;\n\t\tconst plistPath = paths.plistPath(job.id);\n\n\t\tconst obj: Record<string, unknown> = {\n\t\t\tLabel: getLabel(job.id),\n\t\t\tProgramArguments: [process.execPath, runnerPath, \"--job-id\", job.id],\n\t\t\tStandardOutPath: logPath,\n\t\t\tStandardErrorPath: logPath,\n\t\t\tEnvironmentVariables: {\n\t\t\t\tPATH: env?.PATH ?? process.env.PATH ?? \"\",\n\t\t\t\tHOME: env?.HOME ?? homedir(),\n\t\t\t\t...(env\n\t\t\t\t\t? Object.fromEntries(Object.entries(env).filter(([k]) => k !== \"PATH\" && k !== \"HOME\"))\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\tRunAtLoad: false,\n\t\t\tKeepAlive: false,\n\t\t};\n\n\t\tif (scheduling.startInterval !== undefined) {\n\t\t\tobj.StartInterval = scheduling.startInterval;\n\t\t} else if (scheduling.calendarIntervals) {\n\t\t\tobj.StartCalendarInterval =\n\t\t\t\tscheduling.calendarIntervals.length === 1\n\t\t\t\t\t? scheduling.calendarIntervals[0]\n\t\t\t\t\t: scheduling.calendarIntervals;\n\t\t}\n\n\t\tconst xml = plist.build(obj as unknown as plist.PlistValue);\n\t\tawait writeFile(plistPath, xml, \"utf-8\");\n\n\t\tconst uid = getUid();\n\t\tawait execCommand(\"launchctl\", [\"bootstrap\", `gui/${uid}`, plistPath]);\n\t}\n\n\tasync unregister(jobId: string): Promise<void> {\n\t\tconst uid = getUid();\n\t\tconst label = getLabel(jobId);\n\t\tconst plistPath = paths.plistPath(jobId);\n\n\t\ttry {\n\t\t\tawait execCommand(\"launchctl\", [\"bootout\", `gui/${uid}/${label}`]);\n\t\t} catch {\n\t\t\t// Service may already be unloaded -- continue to delete plist\n\t\t}\n\n\t\ttry {\n\t\t\tawait unlink(plistPath);\n\t\t} catch {\n\t\t\t// Plist may already be deleted\n\t\t}\n\t}\n\n\tasync isRegistered(jobId: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(paths.plistPath(jobId));\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(): Promise<RegisteredJob[]> {\n\t\tconst jobs: RegisteredJob[] = [];\n\t\ttry {\n\t\t\tconst files = await readdir(paths.plistDir);\n\t\t\tconst plistFiles = files.filter((f) => f.startsWith(LABEL_PREFIX) && f.endsWith(\".plist\"));\n\n\t\t\tfor (const file of plistFiles) {\n\t\t\t\ttry {\n\t\t\t\t\tconst filePath = join(paths.plistDir, file);\n\t\t\t\t\tconst content = await readFile(filePath, \"utf-8\");\n\t\t\t\t\tconst parsed = plist.parse(content) as Record<string, unknown>;\n\t\t\t\t\tconst label = parsed.Label as string;\n\t\t\t\t\tconst jobId = label.replace(LABEL_PREFIX, \"\");\n\t\t\t\t\tconst progArgs = (parsed.ProgramArguments as string[]) ?? [];\n\n\t\t\t\t\tlet schedule = \"\";\n\t\t\t\t\tif (parsed.StartInterval) {\n\t\t\t\t\t\tschedule = `every ${parsed.StartInterval}s`;\n\t\t\t\t\t} else if (parsed.StartCalendarInterval) {\n\t\t\t\t\t\tschedule = \"calendar\";\n\t\t\t\t\t}\n\n\t\t\t\t\tjobs.push({\n\t\t\t\t\t\tjobId,\n\t\t\t\t\t\tschedule,\n\t\t\t\t\t\tcommand: progArgs.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Skip malformed plists\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Directory may not exist\n\t\t}\n\t\treturn jobs;\n\t}\n}\n"],"mappings":"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","names":[]}