cron-claude 2.0.0 → 2.0.2
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.
- package/README.md +85 -116
- package/dist/cli.js +0 -0
- package/dist/mcp-server.js +22 -6
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/dist/daemon.d.ts +0 -13
- package/dist/daemon.js +0 -104
- package/dist/daemon.js.map +0 -1
- package/dist/scheduler.d.ts +0 -20
- package/dist/scheduler.js +0 -143
- package/dist/scheduler.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,22 +1,53 @@
|
|
|
1
1
|
# cron-claude
|
|
2
2
|
|
|
3
|
-
> Schedule tasks
|
|
3
|
+
> **Claude is the cron daemon.** Schedule tasks → Claude executes them on a loop with full tool access (Slack, bash, files, APIs).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## TL;DR
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g cron-claude
|
|
9
|
+
cron-claude mcp install # wire up MCP in Claude Code
|
|
10
|
+
|
|
11
|
+
cron-claude add \
|
|
12
|
+
--id daily-standup \
|
|
13
|
+
--schedule "0 9 * * 1-5" \
|
|
14
|
+
--prompt "Post a standup summary to #dev-team on Slack"
|
|
15
|
+
|
|
16
|
+
# Then in Claude Code:
|
|
17
|
+
# "Call start_loop to begin."
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Claude starts looping: every 60s it checks for due jobs and spawns subagents to run them.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## How It Works
|
|
8
25
|
|
|
9
|
-
|
|
26
|
+
**Claude is the executor — not a background daemon.**
|
|
10
27
|
|
|
11
|
-
|
|
28
|
+
When you say `"Call start_loop"` in Claude Code, the MCP returns Claude's operating instructions. From that point:
|
|
12
29
|
|
|
13
|
-
1. Every 60s
|
|
14
|
-
2. For each due job
|
|
15
|
-
3.
|
|
16
|
-
4.
|
|
17
|
-
5.
|
|
30
|
+
1. **Every 60s** — Claude calls `get_due_jobs`
|
|
31
|
+
2. **For each due job** — Claude spawns a **subagent** with the job's prompt
|
|
32
|
+
3. **The subagent** — runs with full tool access, does the task, calls `mark_job_run` when done
|
|
33
|
+
4. **Disk state** — `get_due_jobs` atomically marks jobs as `running` before returning, preventing any job from being picked up twice even if loop iterations overlap
|
|
34
|
+
5. **Repeat** — forever, until told to stop
|
|
18
35
|
|
|
19
|
-
No
|
|
36
|
+
No LaunchAgent. No `--print` flag. No `--dangerously-skip-permissions`. Subagents run inside your existing Claude session with all your approved MCP servers already connected.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## For Claude (reading this as an agent)
|
|
41
|
+
|
|
42
|
+
If you have cron-claude MCP loaded, your role is to be the executor loop:
|
|
43
|
+
|
|
44
|
+
1. Call `start_loop` → get your operating instructions and job list
|
|
45
|
+
2. Every 60 seconds: call `get_due_jobs` → for each job, spawn a subagent
|
|
46
|
+
3. Each subagent: execute the job prompt, then call `mark_job_run(id, output)`
|
|
47
|
+
4. Never execute jobs yourself — always delegate to subagents
|
|
48
|
+
5. Loop indefinitely
|
|
49
|
+
|
|
50
|
+
The MCP server description and tool descriptions contain full instructions. You do not need to read this README.
|
|
20
51
|
|
|
21
52
|
---
|
|
22
53
|
|
|
@@ -24,158 +55,96 @@ No background daemon. No LaunchAgent. No permission dialogs. Survives Claude ses
|
|
|
24
55
|
|
|
25
56
|
```bash
|
|
26
57
|
npm install -g cron-claude
|
|
58
|
+
cron-claude mcp install # adds cron-claude-mcp to ~/.claude/settings.json
|
|
27
59
|
```
|
|
28
60
|
|
|
29
|
-
##
|
|
61
|
+
## Managing Jobs
|
|
30
62
|
|
|
31
|
-
**1. Install the MCP server into Claude Code:**
|
|
32
63
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
64
|
+
# Add a job
|
|
65
|
+
cron-claude add --id <id> --schedule "<cron>" --prompt "<what to do>"
|
|
35
66
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cron-claude add \
|
|
39
|
-
--id morning-standup \
|
|
40
|
-
--schedule "0 9 * * 1-5" \
|
|
41
|
-
--prompt "Post a morning standup summary to the #standup Slack channel. Include what's in my calendar today."
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**3. Start the loop in Claude Code:**
|
|
67
|
+
# List all jobs (shows status, last run, schedule)
|
|
68
|
+
cron-claude list
|
|
45
69
|
|
|
46
|
-
|
|
70
|
+
# Remove / enable / disable
|
|
71
|
+
cron-claude remove <id>
|
|
72
|
+
cron-claude enable <id>
|
|
73
|
+
cron-claude disable <id>
|
|
47
74
|
|
|
75
|
+
# Show agent loop setup instructions
|
|
76
|
+
cron-claude agent-loop
|
|
48
77
|
```
|
|
49
|
-
Call start_loop to begin.
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
That's it. Claude will read its operating instructions from `start_loop` and begin polling every 60 seconds.
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## CLI Commands
|
|
57
|
-
|
|
58
|
-
| Command | Description |
|
|
59
|
-
|---------|-------------|
|
|
60
|
-
| `cron-claude list` | List all jobs with status, last run, schedule |
|
|
61
|
-
| `cron-claude add --id <id> --schedule <cron> --prompt <text>` | Add a new job |
|
|
62
|
-
| `cron-claude remove <id>` | Remove a job |
|
|
63
|
-
| `cron-claude enable <id>` | Enable a job |
|
|
64
|
-
| `cron-claude disable <id>` | Disable a job |
|
|
65
|
-
| `cron-claude status` | Summary of job counts |
|
|
66
|
-
| `cron-claude agent-loop` | Print setup instructions |
|
|
67
|
-
| `cron-claude mcp install` | Install MCP into Claude Code |
|
|
68
|
-
| `cron-claude stop` | Stop and remove the legacy v1 LaunchAgent daemon |
|
|
69
78
|
|
|
70
|
-
### `add`
|
|
79
|
+
### `add` flags
|
|
71
80
|
|
|
72
81
|
| Flag | Default | Description |
|
|
73
82
|
|------|---------|-------------|
|
|
74
|
-
| `--id` | required | Unique job
|
|
75
|
-
| `--schedule` | required | Cron expression (e.g. `"0 9 * * 1-5"`) |
|
|
76
|
-
| `--prompt` | required | What
|
|
77
|
-
| `--model` | `sonnet` | Claude model for
|
|
78
|
-
| `--disabled` | — | Create
|
|
83
|
+
| `--id` | required | Unique job name |
|
|
84
|
+
| `--schedule` | required | Cron expression (e.g. `"0 9 * * 1-5"` = 9am weekdays) |
|
|
85
|
+
| `--prompt` | required | What Claude should do |
|
|
86
|
+
| `--model` | `sonnet` | Claude model for subagents |
|
|
87
|
+
| `--disabled` | — | Create in disabled state |
|
|
79
88
|
|
|
80
89
|
---
|
|
81
90
|
|
|
82
91
|
## MCP Tools
|
|
83
92
|
|
|
84
|
-
|
|
93
|
+
| Tool | When to call | What it does |
|
|
94
|
+
|------|-------------|--------------|
|
|
95
|
+
| `start_loop` | Once, to begin | Returns full operating instructions + job list |
|
|
96
|
+
| `get_due_jobs` | Every 60s in the loop | Returns due jobs; atomically marks them `running` |
|
|
97
|
+
| `mark_job_run` | After each job (from subagent) | Clears running state, writes log |
|
|
98
|
+
| `add_cron` | To add a job | Adds a job to the registry |
|
|
99
|
+
| `list_crons` | To see jobs | Returns all jobs as JSON |
|
|
100
|
+
| `remove_cron` | To remove a job | Deletes from registry |
|
|
101
|
+
| `enable_cron` / `disable_cron` | To toggle | Enables or disables |
|
|
102
|
+
| `get_status` | Any time | Job count summary |
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|------|-------------|
|
|
88
|
-
| `start_loop` | Call once to get operating instructions (entry point) |
|
|
89
|
-
| `get_due_jobs` | Returns due jobs, atomically marks them `running` |
|
|
90
|
-
| `mark_job_run` | Clears running state, writes log. Call after each job. |
|
|
91
|
-
| `add_cron` | Add a new job |
|
|
92
|
-
| `list_crons` | List all jobs |
|
|
93
|
-
| `remove_cron` | Remove a job by ID |
|
|
94
|
-
| `enable_cron` | Enable a job |
|
|
95
|
-
| `disable_cron` | Disable a job |
|
|
96
|
-
| `get_status` | Get job counts (total, enabled, disabled, running) |
|
|
104
|
+
### MCP Prompt
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| Prompt | Description |
|
|
101
|
-
|--------|-------------|
|
|
102
|
-
| `cron-loop` | Tells Claude to call `start_loop` — use from the prompt picker |
|
|
106
|
+
Use `cron-loop` from the Claude Code prompt picker — it sends `"Call start_loop to begin."` automatically.
|
|
103
107
|
|
|
104
108
|
---
|
|
105
109
|
|
|
106
|
-
##
|
|
107
|
-
|
|
108
|
-
`get_due_jobs` atomically claims jobs before returning them:
|
|
110
|
+
## Job State & Duplicate Prevention
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
2. Sets `status=running`, `startedAt=now` on disk **before** returning the list
|
|
112
|
-
3. A second call to `get_due_jobs` sees `status=running` and skips those jobs
|
|
112
|
+
`get_due_jobs` uses **atomic locking** to prevent duplicate execution:
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
- Sets `status: "running"` on disk **before** returning the job list
|
|
115
|
+
- Any concurrent call sees `status: "running"` and skips that job
|
|
116
|
+
- `mark_job_run` resets `status: "idle"` and records `lastRunAt`
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
If a job is stuck `running` for >10 minutes (subagent crashed), call:
|
|
118
|
+
If a subagent crashes without calling `mark_job_run`, the job stays locked. Reset it:
|
|
119
119
|
```
|
|
120
|
-
mark_job_run with id="<job-id>", error="timeout"
|
|
120
|
+
mark_job_run with id="<job-id>", error="timeout — manually reset"
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
---
|
|
124
124
|
|
|
125
125
|
## Logs
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
`~/.cron-claude/logs/<job-id>_<timestamp>.log`
|
|
128
128
|
|
|
129
129
|
```
|
|
130
|
-
Job:
|
|
131
|
-
Time: 2025-01-15T09:00:
|
|
130
|
+
Job: daily-standup
|
|
131
|
+
Time: 2025-01-15T09:00:03Z
|
|
132
132
|
Status: success
|
|
133
133
|
---
|
|
134
|
-
Posted standup to #
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## Why Not `--print` Mode?
|
|
140
|
-
|
|
141
|
-
`claude --print` disables all MCP tools. You can't use Slack MCP, bash, file access, or any external integrations. The only way around it was `--dangerously-skip-permissions`, which bypasses all permission checks.
|
|
142
|
-
|
|
143
|
-
In agent loop mode, subagents run inside your existing Claude session with all your approved MCP servers already connected. No flags needed.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Configuration
|
|
148
|
-
|
|
149
|
-
Jobs are stored in `~/.cron-claude/jobs.json`:
|
|
150
|
-
|
|
151
|
-
```json
|
|
152
|
-
{
|
|
153
|
-
"jobs": [
|
|
154
|
-
{
|
|
155
|
-
"id": "morning-standup",
|
|
156
|
-
"schedule": "0 9 * * 1-5",
|
|
157
|
-
"prompt": "Post a morning standup to #standup",
|
|
158
|
-
"model": "sonnet",
|
|
159
|
-
"enabled": true,
|
|
160
|
-
"status": "idle",
|
|
161
|
-
"lastRunAt": 1736931603241,
|
|
162
|
-
"startedAt": null
|
|
163
|
-
}
|
|
164
|
-
]
|
|
165
|
-
}
|
|
134
|
+
Posted standup to #dev-team. Calendar: 10am design review, 2pm 1:1 with Alex.
|
|
166
135
|
```
|
|
167
136
|
|
|
168
137
|
---
|
|
169
138
|
|
|
170
139
|
## Upgrading from v1
|
|
171
140
|
|
|
172
|
-
|
|
141
|
+
v1 used a LaunchAgent + `claude --print` subprocesses (which disabled MCP tools). Stop it:
|
|
173
142
|
|
|
174
143
|
```bash
|
|
175
144
|
cron-claude stop
|
|
176
145
|
```
|
|
177
146
|
|
|
178
|
-
|
|
147
|
+
Existing jobs in `~/.cron-claude/jobs.json` work as-is. Old fields (`headless`, `dangerouslySkipPermissions`) are ignored.
|
|
179
148
|
|
|
180
149
|
---
|
|
181
150
|
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/mcp-server.js
CHANGED
|
@@ -6,9 +6,25 @@ import { createRequire } from 'module';
|
|
|
6
6
|
import { addJob, removeJob, listJobs, toggleJob, updateJob, isJobDue, logExecution, JobSchema, } from './config.js';
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
8
|
const { version } = require('../package.json');
|
|
9
|
-
export const server = new McpServer({
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export const server = new McpServer({ name: 'cron-claude', version }, {
|
|
10
|
+
instructions: `cron-claude: You are the cron executor.
|
|
11
|
+
|
|
12
|
+
YOUR ROLE: You run a continuous agent loop that executes scheduled Claude jobs on behalf of the user.
|
|
13
|
+
|
|
14
|
+
HOW TO START: Call start_loop — it returns your full operating instructions and the list of configured jobs.
|
|
15
|
+
|
|
16
|
+
THE LOOP (runs forever until user says stop):
|
|
17
|
+
Every 60 seconds:
|
|
18
|
+
1. Call get_due_jobs → returns jobs due NOW, atomically marks them "running"
|
|
19
|
+
2. For each returned job → spawn a subagent with the job prompt
|
|
20
|
+
The subagent must call mark_job_run(id, output) when it finishes
|
|
21
|
+
3. Subagents run IN PARALLEL — do not wait for one before starting the next
|
|
22
|
+
4. Wait 60 seconds, repeat
|
|
23
|
+
|
|
24
|
+
WHY SUBAGENTS: Each job runs in its own agent context with full tool access (Slack, bash, files, APIs).
|
|
25
|
+
The disk state (status: "running") prevents any job from executing twice.
|
|
26
|
+
|
|
27
|
+
To begin: call start_loop.`,
|
|
12
28
|
});
|
|
13
29
|
// add_cron
|
|
14
30
|
server.tool('add_cron', 'Add a new scheduled Claude job (agent loop mode)', {
|
|
@@ -113,7 +129,7 @@ server.tool('get_status', 'Get status of all cron-claude jobs (counts)', {}, (_a
|
|
|
113
129
|
};
|
|
114
130
|
});
|
|
115
131
|
// get_due_jobs — atomically claims due jobs to prevent duplicate execution
|
|
116
|
-
server.tool('get_due_jobs', 'Returns jobs
|
|
132
|
+
server.tool('get_due_jobs', 'AGENT LOOP STEP 1: Call this every 60 seconds. Returns jobs whose cron schedule matches right now. Atomically marks each returned job as "running" on disk — so a second call will never return the same job while it is still executing. For each returned job: spawn a subagent with the job\'s prompt and instruct it to call mark_job_run(id, output) when done. Run all subagents in parallel.', {}, (_args) => {
|
|
117
133
|
const now = Date.now();
|
|
118
134
|
// Find jobs that are due and not already claimed
|
|
119
135
|
const dueJobs = listJobs().filter((job) => {
|
|
@@ -135,7 +151,7 @@ server.tool('get_due_jobs', 'Returns jobs that are due to run right now. Atomica
|
|
|
135
151
|
};
|
|
136
152
|
});
|
|
137
153
|
// mark_job_run — releases the running lock and writes a log
|
|
138
|
-
server.tool('mark_job_run', 'Records that a job
|
|
154
|
+
server.tool('mark_job_run', 'AGENT LOOP STEP 2 (call from subagent): Records that a job finished. Clears its "running" status on disk (allowing it to run again next cycle) and writes a log entry. Every subagent spawned by the loop MUST call this when done, even on failure — otherwise the job stays locked as "running" forever.', {
|
|
139
155
|
id: z.string().min(1).describe('Job ID that was run'),
|
|
140
156
|
output: z.string().optional().describe('Output or summary of what was done'),
|
|
141
157
|
error: z.string().optional().describe('Error message if the job failed'),
|
|
@@ -159,7 +175,7 @@ server.tool('mark_job_run', 'Records that a job was executed. Clears the running
|
|
|
159
175
|
}
|
|
160
176
|
});
|
|
161
177
|
// start_loop — returns operating instructions for the agent
|
|
162
|
-
server.tool('start_loop', '
|
|
178
|
+
server.tool('start_loop', 'CALL THIS FIRST. Returns your complete operating instructions as the cron-claude executor, plus the current list of configured jobs. After calling this, begin the loop: every 60 seconds call get_due_jobs, spawn subagents for each returned job, each subagent calls mark_job_run when done.', {}, (_args) => {
|
|
163
179
|
const jobs = listJobs().filter((j) => j.enabled);
|
|
164
180
|
const jobList = jobs.length === 0
|
|
165
181
|
? '(no enabled jobs — add some with cron-claude add or the add_cron tool)'
|
package/dist/mcp-server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EACL,MAAM,EACN,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAEtE,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,SAAS,
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EACL,MAAM,EACN,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAEtE,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,SAAS,CACjC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,EAChC;IACE,YAAY,EAAE;;;;;;;;;;;;;;;;;2BAiBS;CACxB,CACF,CAAC;AAEF,WAAW;AACX,MAAM,CAAC,IAAI,CACT,UAAU,EACV,kDAAkD,EAClD;IACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC;IACvD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,8CAA8C,CAAC;IACpF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC;IAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IAC9E,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;CAC1E,EACD,CAAC,IAAI,EAAE,EAAE;IACP,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC;YAC1B,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,QAAQ;YAC7B,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC;QACZ,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,GAAG,CAAC,EAAE,uBAAuB,EAAE,CAAC;SAClF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,aAAa;AACb,MAAM,CAAC,IAAI,CACT,YAAY,EACZ,gCAAgC,EAChC,EAAE,EACF,CAAC,KAAK,EAAE,EAAE;IACR,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;aACpC;SACF;KACF,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,cAAc;AACd,MAAM,CAAC,IAAI,CACT,aAAa,EACb,qCAAqC,EACrC;IACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC;CACnD,EACD,CAAC,IAAI,EAAE,EAAE;IACP,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,IAAI,CAAC,EAAE,YAAY,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,cAAc;AACd,MAAM,CAAC,IAAI,CACT,aAAa,EACb,qCAAqC,EACrC;IACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC;CACnD,EACD,CAAC,IAAI,EAAE,EAAE;IACP,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,IAAI,CAAC,EAAE,YAAY,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,eAAe;AACf,MAAM,CAAC,IAAI,CACT,cAAc,EACd,sCAAsC,EACtC;IACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC;CACpD,EACD,CAAC,IAAI,EAAE,EAAE;IACP,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC;SACzE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,aAAa;AACb,MAAM,CAAC,IAAI,CACT,YAAY,EACZ,6CAA6C,EAC7C,EAAE,EACF,CAAC,KAAK,EAAE,EAAE;IACR,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IAClE,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAClE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAC5E,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,2EAA2E;AAC3E,MAAM,CAAC,IAAI,CACT,cAAc,EACd,qYAAqY,EACrY,EAAE,EACF,CAAC,KAAK,EAAE,EAAE;IACR,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,iDAAiD;IACjD,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,IAAI,CAAC,GAAG,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC/B,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC,CAAC,kBAAkB;QAC9D,IAAI,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC,SAAS,GAAG,KAAK;YAAE,OAAO,KAAK,CAAC;QACvE,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,oDAAoD;IACpD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,8EAA8E;IAC9E,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,4DAA4D;AAC5D,MAAM,CAAC,IAAI,CACT,cAAc,EACd,4SAA4S,EAC5S;IACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IACrD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IAC5E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;CACzE,EACD,CAAC,IAAI,EAAE,EAAE;IACP,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE;YACjB,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QACH,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC;SACzE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAW,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,4DAA4D;AAC5D,MAAM,CAAC,IAAI,CACT,YAAY,EACZ,iSAAiS,EACjS,EAAE,EACF,CAAC,KAAK,EAAE,EAAE;IACR,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC;QAC/B,CAAC,CAAC,wEAAwE;QAC1E,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAExF,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;EAgBvB,IAAI,CAAC,MAAM;EACX,OAAO;;cAEK,CAAC;IAEX,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;KACzD,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,uCAAuC;AACvC,MAAM,CAAC,MAAM,CACX,WAAW,EACX,sCAAsC,EACtC,GAAG,EAAE,CAAC,CAAC;IACL,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,MAAe;YACrB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,2BAA2B,EAAE;SACtE;KACF;CACF,CAAC,CACH,CAAC;AAEF,4EAA4E;AAC5E,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAC5B,CAAC"}
|
package/package.json
CHANGED
package/dist/daemon.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Scheduler } from './scheduler.js';
|
|
2
|
-
export declare class Daemon {
|
|
3
|
-
private scheduler;
|
|
4
|
-
private watcher;
|
|
5
|
-
private configPath;
|
|
6
|
-
constructor(configPath?: string);
|
|
7
|
-
start(): void;
|
|
8
|
-
stop(): void;
|
|
9
|
-
getScheduler(): Scheduler;
|
|
10
|
-
private loadAndSchedule;
|
|
11
|
-
private watchConfig;
|
|
12
|
-
}
|
|
13
|
-
export declare function main(): void;
|
package/dist/daemon.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { loadConfig, getConfigPath, ensureConfigDir } from './config.js';
|
|
3
|
-
import { Scheduler } from './scheduler.js';
|
|
4
|
-
export class Daemon {
|
|
5
|
-
scheduler;
|
|
6
|
-
watcher = null;
|
|
7
|
-
configPath;
|
|
8
|
-
constructor(configPath) {
|
|
9
|
-
this.configPath = configPath ?? getConfigPath();
|
|
10
|
-
this.scheduler = new Scheduler({
|
|
11
|
-
onJobStart: (job) => {
|
|
12
|
-
console.log(`[cron-claude] Running job: ${job.id}`);
|
|
13
|
-
},
|
|
14
|
-
onJobComplete: (job, output) => {
|
|
15
|
-
console.log(`[cron-claude] Job ${job.id} completed. Output: ${output.slice(0, 200)}`);
|
|
16
|
-
},
|
|
17
|
-
onJobError: (job, error) => {
|
|
18
|
-
console.error(`[cron-claude] Job ${job.id} failed: ${error.message}`);
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
start() {
|
|
23
|
-
ensureConfigDir();
|
|
24
|
-
console.log('[cron-claude] Daemon starting...');
|
|
25
|
-
this.loadAndSchedule();
|
|
26
|
-
this.watchConfig();
|
|
27
|
-
console.log('[cron-claude] Daemon running. Watching for config changes.');
|
|
28
|
-
}
|
|
29
|
-
stop() {
|
|
30
|
-
console.log('[cron-claude] Daemon stopping...');
|
|
31
|
-
this.scheduler.unscheduleAll();
|
|
32
|
-
if (this.watcher) {
|
|
33
|
-
this.watcher.close();
|
|
34
|
-
this.watcher = null;
|
|
35
|
-
}
|
|
36
|
-
console.log('[cron-claude] Daemon stopped.');
|
|
37
|
-
}
|
|
38
|
-
getScheduler() {
|
|
39
|
-
return this.scheduler;
|
|
40
|
-
}
|
|
41
|
-
loadAndSchedule() {
|
|
42
|
-
try {
|
|
43
|
-
const config = loadConfig(this.configPath);
|
|
44
|
-
this.scheduler.unscheduleAll();
|
|
45
|
-
for (const job of config.jobs) {
|
|
46
|
-
try {
|
|
47
|
-
this.scheduler.schedule(job);
|
|
48
|
-
console.log(`[cron-claude] Scheduled: ${job.id} (${job.schedule})`);
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
console.error(`[cron-claude] Failed to schedule ${job.id}: ${err.message}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
console.log(`[cron-claude] ${this.scheduler.getScheduledIds().length} jobs scheduled.`);
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
console.error(`[cron-claude] Failed to load config: ${err.message}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
watchConfig() {
|
|
61
|
-
let debounce = null;
|
|
62
|
-
try {
|
|
63
|
-
this.watcher = fs.watch(this.configPath, () => {
|
|
64
|
-
if (debounce)
|
|
65
|
-
clearTimeout(debounce);
|
|
66
|
-
debounce = setTimeout(() => {
|
|
67
|
-
console.log('[cron-claude] Config changed, reloading...');
|
|
68
|
-
this.loadAndSchedule();
|
|
69
|
-
}, 500);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
console.warn('[cron-claude] Could not watch config file. Changes require restart.');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/* c8 ignore start */
|
|
78
|
-
export function main() {
|
|
79
|
-
const daemon = new Daemon();
|
|
80
|
-
daemon.start();
|
|
81
|
-
process.on('SIGINT', () => {
|
|
82
|
-
daemon.stop();
|
|
83
|
-
process.exit(0);
|
|
84
|
-
});
|
|
85
|
-
process.on('SIGTERM', () => {
|
|
86
|
-
daemon.stop();
|
|
87
|
-
process.exit(0);
|
|
88
|
-
});
|
|
89
|
-
// Safety net: log unhandled rejections instead of crashing the daemon.
|
|
90
|
-
// Cron callbacks already catch their own rejections, so this should be rare.
|
|
91
|
-
process.on('unhandledRejection', (reason) => {
|
|
92
|
-
console.error('[cron-claude] Unhandled rejection (bug — please report):', reason);
|
|
93
|
-
});
|
|
94
|
-
process.on('uncaughtException', (err) => {
|
|
95
|
-
console.error('[cron-claude] Uncaught exception (bug — please report):', err);
|
|
96
|
-
// Don't exit — keep the daemon alive so cron jobs continue running
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
const isMain = process.argv[1]?.endsWith('daemon.ts') || process.argv[1]?.endsWith('daemon.js');
|
|
100
|
-
if (isMain) {
|
|
101
|
-
main();
|
|
102
|
-
}
|
|
103
|
-
/* c8 ignore stop */
|
|
104
|
-
//# sourceMappingURL=daemon.js.map
|
package/dist/daemon.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/scheduler.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Job } from './config.js';
|
|
2
|
-
export interface SchedulerOptions {
|
|
3
|
-
claudePath?: string;
|
|
4
|
-
onJobStart?: (job: Job) => void;
|
|
5
|
-
onJobComplete?: (job: Job, output: string) => void;
|
|
6
|
-
onJobError?: (job: Job, error: Error) => void;
|
|
7
|
-
}
|
|
8
|
-
export declare class Scheduler {
|
|
9
|
-
private tasks;
|
|
10
|
-
private options;
|
|
11
|
-
constructor(options?: SchedulerOptions);
|
|
12
|
-
getClaudePath(): string;
|
|
13
|
-
schedule(job: Job): void;
|
|
14
|
-
unschedule(jobId: string): void;
|
|
15
|
-
unscheduleAll(): void;
|
|
16
|
-
getScheduledIds(): string[];
|
|
17
|
-
executeJob(job: Job): Promise<string>;
|
|
18
|
-
private openTerminal;
|
|
19
|
-
private logJobOutput;
|
|
20
|
-
}
|
package/dist/scheduler.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import cron from 'node-cron';
|
|
2
|
-
import { execFile, spawn } from 'node:child_process';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { getLogsDir } from './config.js';
|
|
6
|
-
const CLAUDE_PATH = path.join(process.env.HOME || '~', '.local', 'bin', 'claude');
|
|
7
|
-
export class Scheduler {
|
|
8
|
-
tasks = new Map();
|
|
9
|
-
options;
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.options = options;
|
|
12
|
-
}
|
|
13
|
-
getClaudePath() {
|
|
14
|
-
return this.options.claudePath ?? CLAUDE_PATH;
|
|
15
|
-
}
|
|
16
|
-
schedule(job) {
|
|
17
|
-
if (!job.enabled)
|
|
18
|
-
return;
|
|
19
|
-
if (!cron.validate(job.schedule)) {
|
|
20
|
-
throw new Error(`Invalid cron schedule for job "${job.id}": ${job.schedule}`);
|
|
21
|
-
}
|
|
22
|
-
// Stop existing task if re-scheduling
|
|
23
|
-
this.unschedule(job.id);
|
|
24
|
-
const task = cron.schedule(job.schedule, () => {
|
|
25
|
-
this.executeJob(job).catch(() => {
|
|
26
|
-
// Error already handled via onJobError callback; suppress unhandled rejection
|
|
27
|
-
// to prevent Node.js from crashing the daemon on job failures
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
this.tasks.set(job.id, task);
|
|
31
|
-
}
|
|
32
|
-
unschedule(jobId) {
|
|
33
|
-
const existing = this.tasks.get(jobId);
|
|
34
|
-
if (existing) {
|
|
35
|
-
existing.stop();
|
|
36
|
-
this.tasks.delete(jobId);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
unscheduleAll() {
|
|
40
|
-
for (const [id] of this.tasks) {
|
|
41
|
-
this.unschedule(id);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
getScheduledIds() {
|
|
45
|
-
return Array.from(this.tasks.keys());
|
|
46
|
-
}
|
|
47
|
-
executeJob(job) {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
this.options.onJobStart?.(job);
|
|
50
|
-
if (job.headless) {
|
|
51
|
-
// Headless: silent background execution, capture output to log file.
|
|
52
|
-
// Use spawn with stdin='ignore' so claude doesn't wait for TTY input
|
|
53
|
-
// (execFile leaves stdin open which causes silent hangs on rate limit errors).
|
|
54
|
-
const args = [];
|
|
55
|
-
if (job.dangerouslySkipPermissions)
|
|
56
|
-
args.push('--dangerously-skip-permissions');
|
|
57
|
-
args.push('--print', '--model', job.model, job.prompt);
|
|
58
|
-
const claudePath = this.getClaudePath();
|
|
59
|
-
const proc = spawn(claudePath, args, {
|
|
60
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
61
|
-
});
|
|
62
|
-
let stdout = '';
|
|
63
|
-
let stderr = '';
|
|
64
|
-
proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
65
|
-
proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
66
|
-
const timer = setTimeout(() => {
|
|
67
|
-
proc.kill();
|
|
68
|
-
}, 300000);
|
|
69
|
-
proc.on('close', (code) => {
|
|
70
|
-
clearTimeout(timer);
|
|
71
|
-
const output = stdout || stderr || '';
|
|
72
|
-
const error = code !== 0 ? new Error(`Command failed with code ${code}: ${output.trim()}`) : undefined;
|
|
73
|
-
this.logJobOutput(job, output, error);
|
|
74
|
-
if (error) {
|
|
75
|
-
this.options.onJobError?.(job, error);
|
|
76
|
-
reject(error);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
this.options.onJobComplete?.(job, output);
|
|
80
|
-
resolve(output);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// Non-headless: open a real Terminal window with an interactive claude session
|
|
86
|
-
this.openTerminal(job, resolve, reject);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
openTerminal(job, resolve, reject) {
|
|
91
|
-
const claudePath = this.getClaudePath();
|
|
92
|
-
const flags = job.dangerouslySkipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
93
|
-
const promptEscaped = job.prompt.replace(/'/g, "'\\''");
|
|
94
|
-
const claudeCmd = `${claudePath} ${flags}--model ${job.model} '${promptEscaped}'`;
|
|
95
|
-
// Write a temp script so we can open it cleanly in Terminal
|
|
96
|
-
const tmpScript = path.join(process.env.HOME || '~', '.cron-claude', `run-${job.id}-${Date.now()}.sh`);
|
|
97
|
-
try {
|
|
98
|
-
fs.writeFileSync(tmpScript, `#!/bin/bash\necho "cron-claude: running job ${job.id}"\n${claudeCmd}\necho "\\ncron-claude: job ${job.id} finished. Press enter to close."\nread\nrm -f '${tmpScript}'\n`, { mode: 0o755 });
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
const error = err;
|
|
102
|
-
this.options.onJobError?.(job, error);
|
|
103
|
-
reject(error);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// open -a Terminal <script> opens the script in a new Terminal window
|
|
107
|
-
execFile('open', ['-a', 'Terminal', tmpScript], { timeout: 10000 }, (error) => {
|
|
108
|
-
if (error) {
|
|
109
|
-
fs.unlink(tmpScript, () => { });
|
|
110
|
-
this.options.onJobError?.(job, error);
|
|
111
|
-
reject(error);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
this.options.onJobComplete?.(job, 'Terminal window opened');
|
|
115
|
-
resolve('Terminal window opened');
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
logJobOutput(job, output, error) {
|
|
120
|
-
const logsDir = getLogsDir();
|
|
121
|
-
try {
|
|
122
|
-
if (!fs.existsSync(logsDir)) {
|
|
123
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
124
|
-
}
|
|
125
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
126
|
-
const logFile = path.join(logsDir, `${job.id}_${timestamp}.log`);
|
|
127
|
-
const content = [
|
|
128
|
-
`Job: ${job.id}`,
|
|
129
|
-
`Time: ${new Date().toISOString()}`,
|
|
130
|
-
`Schedule: ${job.schedule}`,
|
|
131
|
-
`Model: ${job.model}`,
|
|
132
|
-
error ? `Error: ${error.message}` : 'Status: success',
|
|
133
|
-
`---`,
|
|
134
|
-
output,
|
|
135
|
-
].join('\n');
|
|
136
|
-
fs.writeFileSync(logFile, content, 'utf-8');
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
// Logging failure shouldn't crash the daemon
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=scheduler.js.map
|
package/dist/scheduler.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,IAAuB,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAO,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AASlF,MAAM,OAAO,SAAS;IACZ,KAAK,GAA+B,IAAI,GAAG,EAAE,CAAC;IAC9C,OAAO,CAAmB;IAElC,YAAY,UAA4B,EAAE;QACxC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC;IAChD,CAAC;IAED,QAAQ,CAAC,GAAQ;QACf,IAAI,CAAC,GAAG,CAAC,OAAO;YAAE,OAAO;QAEzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,sCAAsC;QACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAExB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE;YAC5C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC9B,8EAA8E;gBAC9E,8DAA8D;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,UAAU,CAAC,KAAa;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,aAAa;QACX,KAAK,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,eAAe;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,UAAU,CAAC,GAAQ;QACjB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;YAE/B,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;gBACjB,qEAAqE;gBACrE,qEAAqE;gBACrE,+EAA+E;gBAC/E,MAAM,IAAI,GAAa,EAAE,CAAC;gBAC1B,IAAI,GAAG,CAAC,0BAA0B;oBAAE,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;gBAChF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;gBACvD,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;gBAExC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE;oBACnC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;iBAClC,CAAC,CAAC;gBAEH,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3E,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE3E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,CAAC,EAAE,MAAM,CAAC,CAAC;gBAEX,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;oBACvC,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,MAAM,GAAG,MAAM,IAAI,MAAM,IAAI,EAAE,CAAC;oBACtC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,4BAA4B,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;oBACvG,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;oBAEtC,IAAI,KAAK,EAAE,CAAC;wBACV,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;wBACtC,MAAM,CAAC,KAAK,CAAC,CAAC;oBAChB,CAAC;yBAAM,CAAC;wBACN,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;wBAC1C,OAAO,CAAC,MAAM,CAAC,CAAC;oBAClB,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,+EAA+E;gBAC/E,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,YAAY,CAClB,GAAQ,EACR,OAAgC,EAChC,MAA+B;QAE/B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,GAAG,UAAU,IAAI,KAAK,WAAW,GAAG,CAAC,KAAK,KAAK,aAAa,GAAG,CAAC;QAElF,4DAA4D;QAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CACzB,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EACvB,cAAc,EACd,OAAO,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,KAAK,CACjC,CAAC;QACF,IAAI,CAAC;YACH,EAAE,CAAC,aAAa,CACd,SAAS,EACT,+CAA+C,GAAG,CAAC,EAAE,MAAM,SAAS,+BAA+B,GAAG,CAAC,EAAE,mDAAmD,SAAS,KAAK,EAC1K,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,GAAY,CAAC;YAC3B,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QAED,sEAAsE;QACtE,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE;YAC5E,IAAI,KAAK,EAAE,CAAC;gBACV,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBAC/B,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACtC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAC;gBAC5D,OAAO,CAAC,wBAAwB,CAAC,CAAC;YACpC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,GAAQ,EAAE,MAAc,EAAE,KAAa;QAC1D,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7C,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACjE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,SAAS,MAAM,CAAC,CAAC;YACjE,MAAM,OAAO,GAAG;gBACd,QAAQ,GAAG,CAAC,EAAE,EAAE;gBAChB,SAAS,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE;gBACnC,aAAa,GAAG,CAAC,QAAQ,EAAE;gBAC3B,UAAU,GAAG,CAAC,KAAK,EAAE;gBACrB,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,iBAAiB;gBACrD,KAAK;gBACL,MAAM;aACP,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACb,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,6CAA6C;QAC/C,CAAC;IACH,CAAC;CACF"}
|