@t0u9h/agent-cron 0.2.1 → 0.3.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.
- package/README.md +112 -77
- package/dist/cli.js +16 -1
- package/dist/cli.js.map +1 -1
- package/dist/queue.d.ts +17 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +49 -0
- package/dist/queue.js.map +1 -0
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/scheduler.d.ts +2 -0
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +3 -1
- package/dist/scheduler.js.map +1 -1
- package/dist/status.d.ts +13 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +139 -0
- package/dist/status.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
> 中文文档:[README.zh.md](./README.zh.md)
|
|
4
4
|
|
|
5
|
-
Run Claude Agent SDK tasks on a cron schedule. Tasks are plain `.md` files — write a prompt, set a cron expression
|
|
5
|
+
Run Claude Agent SDK tasks on a cron schedule. Tasks are plain `.md` files — write a prompt, set a cron expression. That's it.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
tasks/
|
|
9
|
-
daily-ai-news.md
|
|
10
|
-
|
|
9
|
+
daily-ai-news.md ← runs at 9am daily
|
|
10
|
+
claude-code-changelog.md ← runs at 10am daily, sends Feishu notification
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Install
|
|
@@ -32,8 +32,6 @@ Once installed:
|
|
|
32
32
|
- "立即运行 daily-ai-news"
|
|
33
33
|
- "帮我配置 agent-cron 开机自启"
|
|
34
34
|
|
|
35
|
-
The skill automatically configures macOS auto-start (launchd) when you create your first task.
|
|
36
|
-
|
|
37
35
|
## Quick Start
|
|
38
36
|
|
|
39
37
|
**1. Create a task file**
|
|
@@ -44,8 +42,6 @@ cat > tasks/daily-news.md << 'EOF'
|
|
|
44
42
|
---
|
|
45
43
|
name: Daily AI News
|
|
46
44
|
cron: "0 9 * * *"
|
|
47
|
-
output: file
|
|
48
|
-
outputDir: ./output
|
|
49
45
|
---
|
|
50
46
|
|
|
51
47
|
Today is {date}. Search for the latest AI coding news and summarize in 5 bullet points.
|
|
@@ -57,8 +53,7 @@ EOF
|
|
|
57
53
|
**2. Set your API key**
|
|
58
54
|
|
|
59
55
|
```bash
|
|
60
|
-
|
|
61
|
-
# edit .env and set ANTHROPIC_API_KEY
|
|
56
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
62
57
|
```
|
|
63
58
|
|
|
64
59
|
**3. Run once to test**
|
|
@@ -81,9 +76,46 @@ agent-cron start ./my-tasks # use a different tasks directory
|
|
|
81
76
|
agent-cron run # run all tasks immediately (one-off)
|
|
82
77
|
agent-cron run daily-news # run one task by slug immediately
|
|
83
78
|
agent-cron list # list all registered tasks with cron expressions
|
|
79
|
+
agent-cron status # show last run status for all tasks
|
|
80
|
+
agent-cron logs <slug> # show today's log for a task
|
|
81
|
+
agent-cron logs <slug> <date> # show log for a specific date (YYYY-MM-DD)
|
|
84
82
|
```
|
|
85
83
|
|
|
86
|
-
`start` is a long-running process. Use launchd (macOS), systemd (Linux), or pm2 to keep it running.
|
|
84
|
+
`start` is a long-running process. Use [launchd](#auto-start-on-macos) (macOS), systemd (Linux), or pm2 to keep it running.
|
|
85
|
+
|
|
86
|
+
### status
|
|
87
|
+
|
|
88
|
+
Shows a quick overview of all task runs, including live queue state:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
$ agent-cron status
|
|
92
|
+
|
|
93
|
+
TASK LAST RUN STATUS DURATION
|
|
94
|
+
--------------------------------------------------------------
|
|
95
|
+
ai-news-agent-reach → running - -
|
|
96
|
+
github-ai-projects → queued (#1) - -
|
|
97
|
+
claude-code-changelog 今天 00:00 ok 45s
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### logs
|
|
101
|
+
|
|
102
|
+
Prints the structured log for a task. Falls back to the most recent log if today has none.
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
$ agent-cron logs daily-ai-news
|
|
106
|
+
|
|
107
|
+
[2026-03-07 09:00:01.123] [START] task=daily-ai-news
|
|
108
|
+
[2026-03-07 09:00:03.456] [TOOL] name=web_search input={"query":"AI coding news"}
|
|
109
|
+
[2026-03-07 09:00:10.000] [END] status=ok duration=8877ms
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Task Queue
|
|
113
|
+
|
|
114
|
+
Tasks triggered at the same cron time are executed **serially** in filename (slug) order — not concurrently. This prevents silent failures from parallel execution.
|
|
115
|
+
|
|
116
|
+
- If the same task is already running or queued, duplicate triggers are skipped
|
|
117
|
+
- `agent-cron status` shows `→ running` and `→ queued (#N)` for live queue state
|
|
118
|
+
- `agent-cron run` bypasses the queue and runs tasks directly (for manual one-off use)
|
|
87
119
|
|
|
88
120
|
## Task File Format
|
|
89
121
|
|
|
@@ -93,8 +125,8 @@ Each task is a `.md` file in your tasks directory:
|
|
|
93
125
|
---
|
|
94
126
|
name: Daily AI News # display name (default: filename slug)
|
|
95
127
|
cron: "0 9 * * *" # cron expression (Asia/Shanghai timezone)
|
|
96
|
-
|
|
97
|
-
|
|
128
|
+
agent: claude # agent runner (default: claude)
|
|
129
|
+
skills: true # load ~/.claude/ skills (set false to isolate)
|
|
98
130
|
---
|
|
99
131
|
|
|
100
132
|
Today is {date}. Search for the latest AI news...
|
|
@@ -108,58 +140,54 @@ If nothing to report, output exactly: HEARTBEAT_OK
|
|
|
108
140
|
|-------|----------|---------|-------|
|
|
109
141
|
| `name` | no | filename slug | display name |
|
|
110
142
|
| `cron` | **yes** | — | standard 5-field cron expression |
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `skills` | no | `true` | load `~/.claude/` skills (set `false` to isolate) |
|
|
114
|
-
|
|
115
|
-
### Output channel fields
|
|
116
|
-
|
|
117
|
-
**`output: file`**
|
|
118
|
-
|
|
119
|
-
| Field | Required | Default |
|
|
120
|
-
|-------|----------|---------|
|
|
121
|
-
| `outputDir` | no | `./output` |
|
|
143
|
+
| `agent` | no | `claude` | agent runner (`claude` or `shell`) |
|
|
144
|
+
| `skills` | no | `true` | load `~/.claude/` skills, or `["skill-name"]` for specific ones |
|
|
122
145
|
|
|
123
|
-
|
|
146
|
+
### Template variables
|
|
124
147
|
|
|
125
|
-
|
|
148
|
+
| Variable | Value |
|
|
149
|
+
|----------|-------|
|
|
150
|
+
| `{date}` | today's date in `YYYY-MM-DD` format (e.g. `2026-03-07`) |
|
|
126
151
|
|
|
127
|
-
|
|
128
|
-
|-------|----------|---------|
|
|
129
|
-
| `feishuWebhook` | no | `FEISHU_WEBHOOK` env |
|
|
152
|
+
### Prompt-driven output
|
|
130
153
|
|
|
131
|
-
|
|
154
|
+
There is no built-in output channel system. Instead, write the output logic directly in your prompt — Claude will execute it. This is more flexible than any abstraction:
|
|
132
155
|
|
|
133
|
-
|
|
156
|
+
````markdown
|
|
157
|
+
---
|
|
158
|
+
name: Claude Code Changelog
|
|
159
|
+
cron: "0 10 * * *"
|
|
160
|
+
---
|
|
134
161
|
|
|
135
|
-
|
|
136
|
-
|-------|----------|---------|
|
|
137
|
-
| `githubRepo` | **yes** | — |
|
|
138
|
-
| `githubBranch` | no | `main` |
|
|
139
|
-
| `githubDir` | no | repo root |
|
|
140
|
-
| `githubToken` | no | `GITHUB_TOKEN` env |
|
|
162
|
+
Search for Claude Code release notes...
|
|
141
163
|
|
|
142
|
-
|
|
164
|
+
If new version found:
|
|
143
165
|
|
|
144
|
-
|
|
166
|
+
1. Write summary to `~/workspace/memory/changelog/{date}.md`
|
|
167
|
+
2. Git commit and push
|
|
168
|
+
3. Send Feishu notification:
|
|
169
|
+
```bash
|
|
170
|
+
curl -X POST "$FEISHU_WEBHOOK" \
|
|
171
|
+
-H "Content-Type: application/json" \
|
|
172
|
+
-d '{"msg_type":"text","content":{"text":"New version found!"}}'
|
|
173
|
+
```
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
| `{date}` | today's date, locale `zh-CN` (e.g. `2026/3/3`) |
|
|
175
|
+
If nothing new, output: HEARTBEAT_OK
|
|
176
|
+
````
|
|
149
177
|
|
|
150
178
|
### HEARTBEAT_OK protocol
|
|
151
179
|
|
|
152
|
-
If the agent returns exactly `HEARTBEAT_OK` (trimmed), the
|
|
153
|
-
|
|
154
|
-
```
|
|
155
|
-
If nothing new to report, output exactly: HEARTBEAT_OK
|
|
156
|
-
```
|
|
180
|
+
If the agent returns exactly `HEARTBEAT_OK` (trimmed), the task is considered a no-op — logged as `heartbeat` status. Use this when a task should only act when there is genuinely new content.
|
|
157
181
|
|
|
158
182
|
## Local Skills
|
|
159
183
|
|
|
160
|
-
By default, all tasks load your locally installed Claude Code skills (`~/.claude/plugins/`, `~/.claude/skills/`)
|
|
184
|
+
By default, all tasks load your locally installed Claude Code skills (`~/.claude/plugins/`, `~/.claude/skills/`). To load specific skills only:
|
|
161
185
|
|
|
162
|
-
|
|
186
|
+
```yaml
|
|
187
|
+
skills: ["agent-reach", "my-skill"]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
To disable for a specific task (isolation):
|
|
163
191
|
|
|
164
192
|
```yaml
|
|
165
193
|
skills: false
|
|
@@ -169,22 +197,41 @@ skills: false
|
|
|
169
197
|
|
|
170
198
|
```
|
|
171
199
|
ANTHROPIC_API_KEY= # required
|
|
172
|
-
GITHUB_TOKEN= # required for output: github (unless set per-task)
|
|
173
|
-
FEISHU_WEBHOOK= # required for output: feishu (unless set per-task)
|
|
174
200
|
```
|
|
175
201
|
|
|
176
|
-
|
|
202
|
+
A `.env` file in the current working directory is loaded automatically at startup.
|
|
177
203
|
|
|
178
204
|
## Timezone
|
|
179
205
|
|
|
180
|
-
All cron expressions run in `Asia/Shanghai` timezone.
|
|
206
|
+
All cron expressions run in `Asia/Shanghai` timezone.
|
|
207
|
+
|
|
208
|
+
## Logging
|
|
209
|
+
|
|
210
|
+
Each task run writes structured events to `~/.agent-cron/logs/<slug>/YYYY-MM-DD.log`:
|
|
211
|
+
|
|
212
|
+
- `[START]` — task begins
|
|
213
|
+
- `[TOOL]` — each agent tool call (name, input, truncated output)
|
|
214
|
+
- `[END]` — task ends with status (`ok`, `heartbeat`, or `error`) and duration
|
|
215
|
+
|
|
216
|
+
Use `agent-cron status` and `agent-cron logs <slug>` to inspect.
|
|
181
217
|
|
|
182
218
|
## Auto-start on macOS
|
|
183
219
|
|
|
184
|
-
|
|
220
|
+
Create a shell wrapper that loads your environment:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
cat > /path/to/agent-cron/start.sh << 'EOF'
|
|
224
|
+
#!/bin/zsh
|
|
225
|
+
source ~/.zshrc 2>/dev/null
|
|
226
|
+
exec node /path/to/agent-cron/node_modules/.bin/tsx \
|
|
227
|
+
/path/to/agent-cron/src/cli.ts start /path/to/tasks
|
|
228
|
+
EOF
|
|
229
|
+
chmod +x /path/to/agent-cron/start.sh
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Then create the LaunchAgent:
|
|
185
233
|
|
|
186
234
|
```bash
|
|
187
|
-
# Create LaunchAgent plist
|
|
188
235
|
cat > ~/Library/LaunchAgents/com.agent-cron.plist << 'EOF'
|
|
189
236
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
190
237
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -193,16 +240,10 @@ cat > ~/Library/LaunchAgents/com.agent-cron.plist << 'EOF'
|
|
|
193
240
|
<key>Label</key><string>com.agent-cron</string>
|
|
194
241
|
<key>ProgramArguments</key>
|
|
195
242
|
<array>
|
|
196
|
-
<string>/
|
|
197
|
-
<string
|
|
198
|
-
<string>start</string>
|
|
199
|
-
<string>/path/to/your/tasks</string>
|
|
243
|
+
<string>/bin/zsh</string>
|
|
244
|
+
<string>/path/to/agent-cron/start.sh</string>
|
|
200
245
|
</array>
|
|
201
|
-
<key>
|
|
202
|
-
<dict>
|
|
203
|
-
<key>ANTHROPIC_API_KEY</key><string>sk-ant-...</string>
|
|
204
|
-
</dict>
|
|
205
|
-
<key>WorkingDirectory</key><string>/path/to/your/project</string>
|
|
246
|
+
<key>WorkingDirectory</key><string>/path/to/agent-cron</string>
|
|
206
247
|
<key>RunAtLoad</key><true/>
|
|
207
248
|
<key>KeepAlive</key><true/>
|
|
208
249
|
<key>StandardOutPath</key><string>/tmp/agent-cron.log</string>
|
|
@@ -211,35 +252,29 @@ cat > ~/Library/LaunchAgents/com.agent-cron.plist << 'EOF'
|
|
|
211
252
|
</plist>
|
|
212
253
|
EOF
|
|
213
254
|
|
|
214
|
-
# Load immediately
|
|
255
|
+
# Load immediately
|
|
215
256
|
launchctl load ~/Library/LaunchAgents/com.agent-cron.plist
|
|
216
257
|
|
|
217
|
-
# Verify
|
|
258
|
+
# Verify
|
|
218
259
|
launchctl list | grep agent-cron
|
|
219
260
|
```
|
|
220
261
|
|
|
262
|
+
The shell wrapper (`start.sh`) ensures environment variables like `ANTHROPIC_API_KEY` are available — launchd does not inherit your shell environment.
|
|
263
|
+
|
|
221
264
|
Or use the Claude Code skill — it handles all of this automatically.
|
|
222
265
|
|
|
223
266
|
## Architecture
|
|
224
267
|
|
|
225
268
|
```
|
|
226
|
-
tasks/*.md
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
**Pluggable output channels** — implement `OutputChannel` and register in `src/outputs/index.ts`:
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
interface OutputChannel {
|
|
234
|
-
send(result: string, task: Task): Promise<void>
|
|
235
|
-
}
|
|
269
|
+
tasks/*.md → loader.ts → scheduler.ts → queue.ts → runner.ts → AgentRunner → Logger
|
|
270
|
+
(serial) agents/ ~/.agent-cron/logs/
|
|
236
271
|
```
|
|
237
272
|
|
|
238
273
|
**Pluggable agent runners** — implement `AgentRunner` and register in `src/agents/index.ts`:
|
|
239
274
|
|
|
240
275
|
```typescript
|
|
241
276
|
interface AgentRunner {
|
|
242
|
-
run(prompt: string, task: Task): Promise<string>
|
|
277
|
+
run(prompt: string, task: Task, logger?: Logger): Promise<string>
|
|
243
278
|
}
|
|
244
279
|
```
|
|
245
280
|
|
|
@@ -249,6 +284,6 @@ interface AgentRunner {
|
|
|
249
284
|
git clone https://github.com/T0UGH/agent-cron
|
|
250
285
|
cd agent-cron
|
|
251
286
|
npm install
|
|
252
|
-
npm test # run tests (
|
|
287
|
+
npm test # run tests (node:test)
|
|
253
288
|
npm run build # compile TypeScript → dist/
|
|
254
289
|
```
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import 'dotenv/config';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { loadTasks } from './loader.js';
|
|
5
|
-
import { startScheduler, runNow, listTasks } from './scheduler.js';
|
|
5
|
+
import { startScheduler, runNow, listTasks, taskQueue } from './scheduler.js';
|
|
6
|
+
import { statusAll, logsFor } from './status.js';
|
|
6
7
|
const args = process.argv.slice(2);
|
|
7
8
|
const command = args[0];
|
|
8
9
|
// Resolve tasks directory: default ./tasks, or explicit argument after command
|
|
@@ -23,6 +24,18 @@ switch (command) {
|
|
|
23
24
|
case 'list':
|
|
24
25
|
listTasks(tasks);
|
|
25
26
|
break;
|
|
27
|
+
case 'status':
|
|
28
|
+
statusAll(tasks, taskQueue.getState());
|
|
29
|
+
break;
|
|
30
|
+
case 'logs': {
|
|
31
|
+
const slug = args[1];
|
|
32
|
+
if (!slug) {
|
|
33
|
+
console.error('Usage: agent-cron logs <slug> [date]');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
logsFor(slug, args[2]);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
26
39
|
default:
|
|
27
40
|
console.log(`
|
|
28
41
|
agent-cron — run Claude Agent SDK tasks on a cron schedule
|
|
@@ -32,6 +45,8 @@ Usage:
|
|
|
32
45
|
agent-cron start ./my-tasks Specify tasks directory
|
|
33
46
|
agent-cron run [slug] Run all tasks or one by slug immediately
|
|
34
47
|
agent-cron list List all registered tasks
|
|
48
|
+
agent-cron status Show last run status for all tasks
|
|
49
|
+
agent-cron logs <slug> [date] Show logs for a task (date: YYYY-MM-DD, default: today)
|
|
35
50
|
|
|
36
51
|
Options:
|
|
37
52
|
dir Path to tasks directory (default: ./tasks)
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,+EAA+E;AAC/E,oCAAoC;AACpC,uDAAuD;AACvD,MAAM,MAAM,GACV,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;AAEnD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAElC,QAAQ,OAAO,EAAE,CAAC;IAChB,KAAK,OAAO;QACV,cAAc,CAAC,KAAK,CAAC,CAAC;QACtB,MAAM;IAER,KAAK,KAAK,CAAC,CAAC,CAAC;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1B,MAAM;IACR,CAAC;IAED,KAAK,MAAM;QACT,SAAS,CAAC,KAAK,CAAC,CAAC;QACjB,MAAM;IAER,KAAK,QAAQ;QACX,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvC,MAAM;IAER,KAAK,MAAM,CAAC,CAAC,CAAC;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;YACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM;IACR,CAAC;IAED;QACE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;CAmBf,CAAC,CAAC;QACC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Task } from './types.js';
|
|
2
|
+
export interface QueueState {
|
|
3
|
+
running: string | null;
|
|
4
|
+
queued: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare class TaskQueue {
|
|
7
|
+
private pending;
|
|
8
|
+
private processing;
|
|
9
|
+
private runningSlug;
|
|
10
|
+
private emptyResolvers;
|
|
11
|
+
enqueue(task: Task): void;
|
|
12
|
+
private process;
|
|
13
|
+
getState(): QueueState;
|
|
14
|
+
/** Resolves when the queue is empty and nothing is running. For testing. */
|
|
15
|
+
waitUntilEmpty(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAsB;IAE5C,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;YAaX,OAAO;IAiBrB,QAAQ,IAAI,UAAU;IAOtB,4EAA4E;IAC5E,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAIhC"}
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { runTask } from './runner.js';
|
|
2
|
+
export class TaskQueue {
|
|
3
|
+
pending = [];
|
|
4
|
+
processing = false;
|
|
5
|
+
runningSlug = null;
|
|
6
|
+
emptyResolvers = [];
|
|
7
|
+
enqueue(task) {
|
|
8
|
+
// Dedup: skip if this slug is currently running
|
|
9
|
+
if (this.runningSlug === task.slug)
|
|
10
|
+
return;
|
|
11
|
+
// Dedup: skip if this slug is already pending
|
|
12
|
+
if (this.pending.some(t => t.slug === task.slug))
|
|
13
|
+
return;
|
|
14
|
+
this.pending.push(task);
|
|
15
|
+
// Sort pending by slug for deterministic order
|
|
16
|
+
this.pending.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
17
|
+
// Defer to microtask so synchronous enqueues batch together before processing
|
|
18
|
+
queueMicrotask(() => this.process());
|
|
19
|
+
}
|
|
20
|
+
async process() {
|
|
21
|
+
if (this.processing || this.pending.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
this.processing = true;
|
|
24
|
+
while (this.pending.length > 0) {
|
|
25
|
+
const task = this.pending.shift();
|
|
26
|
+
this.runningSlug = task.slug;
|
|
27
|
+
await runTask(task);
|
|
28
|
+
this.runningSlug = null;
|
|
29
|
+
}
|
|
30
|
+
this.processing = false;
|
|
31
|
+
// Notify waiters
|
|
32
|
+
for (const resolve of this.emptyResolvers)
|
|
33
|
+
resolve();
|
|
34
|
+
this.emptyResolvers = [];
|
|
35
|
+
}
|
|
36
|
+
getState() {
|
|
37
|
+
return {
|
|
38
|
+
running: this.runningSlug,
|
|
39
|
+
queued: this.pending.map(t => t.slug),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Resolves when the queue is empty and nothing is running. For testing. */
|
|
43
|
+
waitUntilEmpty() {
|
|
44
|
+
if (!this.processing && this.pending.length === 0)
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
return new Promise(resolve => this.emptyResolvers.push(resolve));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAOtC,MAAM,OAAO,SAAS;IACZ,OAAO,GAAW,EAAE,CAAC;IACrB,UAAU,GAAY,KAAK,CAAC;IAC5B,WAAW,GAAkB,IAAI,CAAC;IAClC,cAAc,GAAmB,EAAE,CAAC;IAE5C,OAAO,CAAC,IAAU;QAChB,gDAAgD;QAChD,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,IAAI;YAAE,OAAO;QAC3C,8CAA8C;QAC9C,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO;QAEzD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,+CAA+C;QAC/C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1D,8EAA8E;QAC9E,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACvC,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACzD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAG,CAAC;YACnC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC;YAC7B,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,iBAAiB;QACjB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO,EAAE,CAAC;QACrD,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED,QAAQ;QACN,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,WAAW;YACzB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SACtC,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,cAAc;QACZ,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5E,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,CAAC;CACF"}
|
package/dist/runner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runners } from './agents/index.js';
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
3
|
function buildPrompt(template) {
|
|
4
|
-
const date = new Date().
|
|
4
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
5
5
|
return template.replace(/\{date\}/g, date);
|
|
6
6
|
}
|
|
7
7
|
export async function runTask(task) {
|
package/dist/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAU;IACtC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,CAAC,KAAK,EAAE,CAAC;IAEf,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,mBAAmB,SAAS,GAAG,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,gBAAgB,GAAG,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAE3D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,oBAAoB,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,gBAAgB,GAAG,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;YACpD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,cAAc,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;YAC/D,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,6BAA6B,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/D,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;AACH,CAAC"}
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Task } from './types.js';
|
|
2
|
+
import { TaskQueue } from './queue.js';
|
|
3
|
+
export declare const taskQueue: TaskQueue;
|
|
2
4
|
export declare function startScheduler(tasks: Task[]): void;
|
|
3
5
|
export declare function runNow(tasks: Task[], slug?: string): Promise<void>;
|
|
4
6
|
export declare function listTasks(tasks: Task[]): void;
|
package/dist/scheduler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,eAAO,MAAM,SAAS,WAAkB,CAAC;AAIzC,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAoBlD;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAaxE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAY7C"}
|
package/dist/scheduler.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import cron from 'node-cron';
|
|
2
2
|
import { runTask } from './runner.js';
|
|
3
|
+
import { TaskQueue } from './queue.js';
|
|
4
|
+
export const taskQueue = new TaskQueue();
|
|
3
5
|
const TIMEZONE = 'Asia/Shanghai';
|
|
4
6
|
export function startScheduler(tasks) {
|
|
5
7
|
if (tasks.length === 0) {
|
|
@@ -11,7 +13,7 @@ export function startScheduler(tasks) {
|
|
|
11
13
|
console.warn(`[agent-cron] invalid cron expression "${task.cron}" in task "${task.name}", skipping`);
|
|
12
14
|
continue;
|
|
13
15
|
}
|
|
14
|
-
cron.schedule(task.cron, () => {
|
|
16
|
+
cron.schedule(task.cron, () => { taskQueue.enqueue(task); }, { timezone: TIMEZONE });
|
|
15
17
|
console.log(`[agent-cron] scheduled: ${task.name} → ${task.cron} (${TIMEZONE})`);
|
|
16
18
|
}
|
|
17
19
|
console.log(`[agent-cron] scheduler running with ${tasks.length} task(s). Press Ctrl+C to stop.`);
|
package/dist/scheduler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC;AAEzC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAEjC,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QACjE,OAAO;IACT,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CACV,yCAAyC,IAAI,CAAC,IAAI,cAAc,IAAI,CAAC,IAAI,aAAa,CACvF,CAAC;YACF,SAAS;QACX,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrF,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,KAAK,QAAQ,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,OAAO,CAAC,GAAG,CACT,uCAAuC,KAAK,CAAC,MAAM,iCAAiC,CACrF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,KAAa,EAAE,IAAa;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CACX,IAAI,CAAC,CAAC,CAAC,iCAAiC,IAAI,GAAG,CAAC,CAAC,CAAC,8BAA8B,CACjF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Task } from './types.js';
|
|
2
|
+
import type { QueueState } from './queue.js';
|
|
3
|
+
export interface TaskStatus {
|
|
4
|
+
slug: string;
|
|
5
|
+
name: string;
|
|
6
|
+
lastRun: string | null;
|
|
7
|
+
status: 'ok' | 'error' | 'heartbeat' | 'never';
|
|
8
|
+
duration: string | null;
|
|
9
|
+
error: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function statusAll(tasks: Task[], queueState?: QueueState): void;
|
|
12
|
+
export declare function logsFor(slug: string, date?: string): void;
|
|
13
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AA8DD,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAwEtE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAsBzD"}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
function logsDir() {
|
|
5
|
+
return path.join(os.homedir(), '.agent-cron', 'logs');
|
|
6
|
+
}
|
|
7
|
+
/** Parse the last [END] line from a log file. */
|
|
8
|
+
function parseLastEnd(logFile) {
|
|
9
|
+
let content;
|
|
10
|
+
try {
|
|
11
|
+
content = fs.readFileSync(logFile, 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
// Find all END lines, take the last one
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
let lastEnd = null;
|
|
19
|
+
let lastStart = null;
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
if (line.includes('[END]'))
|
|
22
|
+
lastEnd = line;
|
|
23
|
+
if (line.includes('[START]'))
|
|
24
|
+
lastStart = line;
|
|
25
|
+
}
|
|
26
|
+
if (!lastEnd)
|
|
27
|
+
return null;
|
|
28
|
+
// Extract timestamp from "[2026-03-07 10:00:01.123] [END] status=ok duration=12345ms"
|
|
29
|
+
const tsMatch = lastEnd.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/);
|
|
30
|
+
const statusMatch = lastEnd.match(/status=(\w+)/);
|
|
31
|
+
const durationMatch = lastEnd.match(/duration=(\S+)/);
|
|
32
|
+
const errorMatch = lastEnd.match(/error="([^"]+)"/);
|
|
33
|
+
if (!statusMatch)
|
|
34
|
+
return null;
|
|
35
|
+
const rawStatus = statusMatch[1];
|
|
36
|
+
const status = (rawStatus === 'ok' || rawStatus === 'error' || rawStatus === 'heartbeat')
|
|
37
|
+
? rawStatus : 'error';
|
|
38
|
+
return {
|
|
39
|
+
status,
|
|
40
|
+
duration: durationMatch?.[1] ?? '?',
|
|
41
|
+
error: errorMatch?.[1] ?? null,
|
|
42
|
+
time: tsMatch?.[1] ?? '',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Find the most recent log file for a slug. */
|
|
46
|
+
function latestLogFile(slug) {
|
|
47
|
+
const slugDir = path.join(logsDir(), slug);
|
|
48
|
+
if (!fs.existsSync(slugDir))
|
|
49
|
+
return null;
|
|
50
|
+
const files = fs.readdirSync(slugDir)
|
|
51
|
+
.filter(f => f.endsWith('.log'))
|
|
52
|
+
.sort()
|
|
53
|
+
.reverse();
|
|
54
|
+
if (files.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
return { file: path.join(slugDir, files[0]), date: files[0].replace('.log', '') };
|
|
57
|
+
}
|
|
58
|
+
/** Format a log date for display: "today HH:MM" or "YYYY-MM-DD HH:MM". */
|
|
59
|
+
function formatRunTime(logDate, time) {
|
|
60
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
61
|
+
const hhmm = time.slice(11, 16); // "HH:MM"
|
|
62
|
+
return logDate === today ? `今天 ${hhmm}` : `${logDate} ${hhmm}`;
|
|
63
|
+
}
|
|
64
|
+
export function statusAll(tasks, queueState) {
|
|
65
|
+
const COL = { slug: 26, run: 14, status: 11, dur: 10 };
|
|
66
|
+
const header = 'TASK'.padEnd(COL.slug) +
|
|
67
|
+
'LAST RUN'.padEnd(COL.run) +
|
|
68
|
+
'STATUS'.padEnd(COL.status) +
|
|
69
|
+
'DURATION';
|
|
70
|
+
console.log(header);
|
|
71
|
+
console.log('-'.repeat(header.length + 8));
|
|
72
|
+
for (const task of tasks) {
|
|
73
|
+
if (queueState?.running === task.slug) {
|
|
74
|
+
console.log(task.slug.padEnd(COL.slug) +
|
|
75
|
+
'-'.padEnd(COL.run) +
|
|
76
|
+
'→ running'.padEnd(COL.status) +
|
|
77
|
+
'-');
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const queuedIndex = queueState?.queued.indexOf(task.slug) ?? -1;
|
|
81
|
+
if (queuedIndex >= 0) {
|
|
82
|
+
console.log(task.slug.padEnd(COL.slug) +
|
|
83
|
+
'-'.padEnd(COL.run) +
|
|
84
|
+
`→ queued (#${queuedIndex + 1})`.padEnd(COL.status) +
|
|
85
|
+
'-');
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const latest = latestLogFile(task.slug);
|
|
89
|
+
if (!latest) {
|
|
90
|
+
console.log(task.slug.padEnd(COL.slug) +
|
|
91
|
+
'never'.padEnd(COL.run) +
|
|
92
|
+
'-'.padEnd(COL.status) +
|
|
93
|
+
'-');
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const parsed = parseLastEnd(latest.file);
|
|
97
|
+
if (!parsed) {
|
|
98
|
+
console.log(task.slug.padEnd(COL.slug) +
|
|
99
|
+
latest.date.padEnd(COL.run) +
|
|
100
|
+
'running?'.padEnd(COL.status) +
|
|
101
|
+
'-');
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const runTime = formatRunTime(latest.date, parsed.time);
|
|
105
|
+
const statusLabel = parsed.status === 'ok' ? 'ok' :
|
|
106
|
+
parsed.status === 'heartbeat' ? 'heartbeat' :
|
|
107
|
+
`error ⚠`;
|
|
108
|
+
const line = task.slug.padEnd(COL.slug) +
|
|
109
|
+
runTime.padEnd(COL.run) +
|
|
110
|
+
statusLabel.padEnd(COL.status) +
|
|
111
|
+
parsed.duration;
|
|
112
|
+
console.log(line);
|
|
113
|
+
if (parsed.error) {
|
|
114
|
+
console.log(' '.repeat(COL.slug) + `↳ ${parsed.error}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export function logsFor(slug, date) {
|
|
119
|
+
const targetDate = date ?? new Date().toISOString().slice(0, 10);
|
|
120
|
+
const logFile = path.join(logsDir(), slug, `${targetDate}.log`);
|
|
121
|
+
if (!fs.existsSync(logFile)) {
|
|
122
|
+
// Try most recent if today not found
|
|
123
|
+
const latest = latestLogFile(slug);
|
|
124
|
+
if (!latest) {
|
|
125
|
+
console.error(`No logs found for task: ${slug}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
if (!date) {
|
|
129
|
+
// Fall back to latest
|
|
130
|
+
console.log(`(no log for today, showing ${latest.date})\n`);
|
|
131
|
+
console.log(fs.readFileSync(latest.file, 'utf-8'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.error(`No log found: ${logFile}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
console.log(fs.readFileSync(logFile, 'utf-8'));
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
|