@t0u9h/agent-cron 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/dist/agents/claude.d.ts +5 -0
- package/dist/agents/claude.d.ts.map +1 -0
- package/dist/agents/claude.js +22 -0
- package/dist/agents/claude.js.map +1 -0
- package/dist/agents/index.d.ts +3 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +6 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +47 -0
- package/dist/cli.js.map +1 -0
- package/dist/loader.d.ts +3 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +38 -0
- package/dist/loader.js.map +1 -0
- package/dist/outputs/feishu.d.ts +20 -0
- package/dist/outputs/feishu.d.ts.map +1 -0
- package/dist/outputs/feishu.js +93 -0
- package/dist/outputs/feishu.js.map +1 -0
- package/dist/outputs/file.d.ts +5 -0
- package/dist/outputs/file.d.ts.map +1 -0
- package/dist/outputs/file.js +13 -0
- package/dist/outputs/file.js.map +1 -0
- package/dist/outputs/github.d.ts +5 -0
- package/dist/outputs/github.d.ts.map +1 -0
- package/dist/outputs/github.js +51 -0
- package/dist/outputs/github.js.map +1 -0
- package/dist/outputs/index.d.ts +3 -0
- package/dist/outputs/index.d.ts.map +1 -0
- package/dist/outputs/index.js +10 -0
- package/dist/outputs/index.js.map +1 -0
- package/dist/runner.d.ts +3 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +44 -0
- package/dist/runner.js.map +1 -0
- package/dist/scheduler.d.ts +5 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +43 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# agent-cron
|
|
2
|
+
|
|
3
|
+
Run Claude Agent SDK tasks on a cron schedule. Tasks are defined as `.md` files with YAML frontmatter.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g agent-cron
|
|
9
|
+
# or use npx
|
|
10
|
+
npx agent-cron list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
agent-cron start # start scheduler (reads ./tasks/)
|
|
17
|
+
agent-cron start ./my-tasks # specify tasks directory
|
|
18
|
+
agent-cron run # run all tasks now
|
|
19
|
+
agent-cron run daily-news # run one task by slug
|
|
20
|
+
agent-cron list # list all tasks
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Task File Format
|
|
24
|
+
|
|
25
|
+
Create `.md` files in your `tasks/` directory:
|
|
26
|
+
|
|
27
|
+
```markdown
|
|
28
|
+
---
|
|
29
|
+
name: Daily AI News
|
|
30
|
+
cron: "0 9 * * *"
|
|
31
|
+
output: feishu
|
|
32
|
+
feishuWebhook: https://open.feishu.cn/open-apis/bot/v2/hook/xxx
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
Today is {date}. Search for the latest AI news and summarize.
|
|
36
|
+
|
|
37
|
+
If nothing new, output exactly: HEARTBEAT_OK
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Frontmatter fields
|
|
41
|
+
|
|
42
|
+
| Field | Required | Default | Notes |
|
|
43
|
+
|-------|----------|---------|-------|
|
|
44
|
+
| `name` | no | filename slug | display name |
|
|
45
|
+
| `cron` | **yes** | — | standard cron expression |
|
|
46
|
+
| `output` | **yes** | — | `file`, `github`, or `feishu` |
|
|
47
|
+
| `agent` | no | `claude` | agent runner (currently only `claude`) |
|
|
48
|
+
| `skills` | no | `true` | load `~/.claude/` skills via `settingSources: ['user']` |
|
|
49
|
+
| `outputDir` | no | `./output` | for `output: file` |
|
|
50
|
+
| `githubRepo` | if github | — | `owner/repo` format |
|
|
51
|
+
| `githubBranch` | no | `main` | for `output: github` |
|
|
52
|
+
| `githubDir` | no | `` (root) | subdirectory in repo |
|
|
53
|
+
| `githubToken` | no | `GITHUB_TOKEN` env | for `output: github` |
|
|
54
|
+
| `feishuWebhook` | no | `FEISHU_WEBHOOK` env | for `output: feishu` |
|
|
55
|
+
|
|
56
|
+
### Template variables
|
|
57
|
+
|
|
58
|
+
- `{date}` — replaced with today's date (locale: zh-CN)
|
|
59
|
+
|
|
60
|
+
## Output Channels
|
|
61
|
+
|
|
62
|
+
| Channel | Required config |
|
|
63
|
+
|---------|----------------|
|
|
64
|
+
| `file` | `outputDir` (default `./output`) |
|
|
65
|
+
| `feishu` | `feishuWebhook` or `FEISHU_WEBHOOK` env |
|
|
66
|
+
| `github` | `githubRepo`, `githubToken` or `GITHUB_TOKEN` env |
|
|
67
|
+
|
|
68
|
+
### HEARTBEAT_OK protocol
|
|
69
|
+
|
|
70
|
+
If the agent returns exactly `HEARTBEAT_OK`, the output step is skipped silently. Use this for tasks that only push when there's new content.
|
|
71
|
+
|
|
72
|
+
## Local Skills
|
|
73
|
+
|
|
74
|
+
By default, all tasks load your locally installed Claude Code skills (`~/.claude/plugins/`, `~/.claude/skills/`). To disable for a specific task:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
skills: false
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Environment Variables
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
ANTHROPIC_API_KEY= # required
|
|
84
|
+
GITHUB_TOKEN= # required for output: github
|
|
85
|
+
FEISHU_WEBHOOK= # required for output: feishu (unless set per-task)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Copy `.env.example` to `.env` and fill in your values.
|
|
89
|
+
|
|
90
|
+
## Timezone
|
|
91
|
+
|
|
92
|
+
All cron expressions use `Asia/Shanghai` timezone.
|
|
93
|
+
|
|
94
|
+
## Extending
|
|
95
|
+
|
|
96
|
+
Adding a new output channel: implement `OutputChannel` interface and register it in `src/outputs/index.ts`.
|
|
97
|
+
|
|
98
|
+
Adding a new agent runner: implement `AgentRunner` interface and register it in `src/agents/index.ts`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAErD,qBAAa,YAAa,YAAW,WAAW;IACxC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;CAqBvD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
export class ClaudeRunner {
|
|
3
|
+
async run(prompt, task) {
|
|
4
|
+
const loadSkills = task.skills !== false;
|
|
5
|
+
let result = '';
|
|
6
|
+
const q = query({
|
|
7
|
+
prompt,
|
|
8
|
+
options: {
|
|
9
|
+
cwd: process.cwd(),
|
|
10
|
+
permissionMode: 'bypassPermissions',
|
|
11
|
+
...(loadSkills ? { settingSources: ['user'] } : {}),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
for await (const message of q) {
|
|
15
|
+
if (message.type === 'result' && 'result' in message && message.result) {
|
|
16
|
+
result = message.result;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAGvD,MAAM,OAAO,YAAY;IACvB,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,IAAU;QAClC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC;QACzC,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,CAAC,GAAG,KAAK,CAAC;YACd,MAAM;YACN,OAAO,EAAE;gBACP,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;gBAClB,cAAc,EAAE,mBAAmB;gBACnC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpD;SACF,CAAC,CAAC;QAEH,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,CAAC;YAC9B,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACvE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAG/C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,OAAO,GAAgC;IAClD,MAAM,EAAE,IAAI,YAAY,EAAE;IAC1B,wCAAwC;CACzC,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { loadTasks } from './loader.js';
|
|
5
|
+
import { startScheduler, runNow, listTasks } from './scheduler.js';
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
8
|
+
// Resolve tasks directory: default ./tasks, or explicit argument after command
|
|
9
|
+
// For 'start [dir]': dir is args[1]
|
|
10
|
+
// For 'run [slug]': args[1] is a slug, not a directory
|
|
11
|
+
const dirArg = command === 'start' && args[1] && !args[1].startsWith('-') ? args[1] : undefined;
|
|
12
|
+
const tasksDir = path.resolve(dirArg ?? './tasks');
|
|
13
|
+
const tasks = loadTasks(tasksDir);
|
|
14
|
+
switch (command) {
|
|
15
|
+
case 'start':
|
|
16
|
+
startScheduler(tasks);
|
|
17
|
+
break;
|
|
18
|
+
case 'run': {
|
|
19
|
+
const slug = args[1] && !args[1].startsWith('-') ? args[1] : undefined;
|
|
20
|
+
await runNow(tasks, slug);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case 'list':
|
|
24
|
+
listTasks(tasks);
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
console.log(`
|
|
28
|
+
agent-cron — run Claude Agent SDK tasks on a cron schedule
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
agent-cron start [dir] Start scheduler (default dir: ./tasks)
|
|
32
|
+
agent-cron start ./my-tasks Specify tasks directory
|
|
33
|
+
agent-cron run [slug] Run all tasks or one by slug immediately
|
|
34
|
+
agent-cron list List all registered tasks
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
dir Path to tasks directory (default: ./tasks)
|
|
38
|
+
slug Task filename without .md extension
|
|
39
|
+
|
|
40
|
+
Environment:
|
|
41
|
+
ANTHROPIC_API_KEY Required
|
|
42
|
+
GITHUB_TOKEN Required for output: github
|
|
43
|
+
FEISHU_WEBHOOK Required for output: feishu (unless set per-task)
|
|
44
|
+
`);
|
|
45
|
+
process.exit(command ? 1 : 0);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +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;AAEnE,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;QACE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;CAiBf,CAAC,CAAC;QACC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,CAwC7C"}
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
export function loadTasks(dir) {
|
|
5
|
+
if (!fs.existsSync(dir)) {
|
|
6
|
+
console.warn(`[agent-cron] tasks directory not found: ${dir}`);
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
return fs
|
|
10
|
+
.readdirSync(dir)
|
|
11
|
+
.filter((f) => f.endsWith('.md'))
|
|
12
|
+
.map((f) => {
|
|
13
|
+
const filePath = path.join(dir, f);
|
|
14
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
15
|
+
const { data, content } = matter(raw);
|
|
16
|
+
const slug = f.replace(/\.md$/, '');
|
|
17
|
+
if (!data.cron) {
|
|
18
|
+
console.warn(`[agent-cron] task "${slug}" missing required field: cron, skipping`);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (!data.output) {
|
|
22
|
+
console.warn(`[agent-cron] task "${slug}" missing required field: output, skipping`);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const task = {
|
|
26
|
+
slug,
|
|
27
|
+
name: String(data.name ?? slug),
|
|
28
|
+
cron: String(data.cron),
|
|
29
|
+
output: String(data.output),
|
|
30
|
+
prompt: content.trim(),
|
|
31
|
+
// spread all other frontmatter fields (channel-specific config)
|
|
32
|
+
...Object.fromEntries(Object.entries(data).filter(([k]) => !['name', 'cron', 'output'].includes(k))),
|
|
33
|
+
};
|
|
34
|
+
return task;
|
|
35
|
+
})
|
|
36
|
+
.filter((t) => t !== null);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,2CAA2C,GAAG,EAAE,CAAC,CAAC;QAC/D,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,EAAE;SACN,WAAW,CAAC,GAAG,CAAC;SAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAEpC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,sBAAsB,IAAI,0CAA0C,CAAC,CAAC;YACnF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,sBAAsB,IAAI,4CAA4C,CAAC,CAAC;YACrF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAS;YACjB,IAAI;YACJ,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;YAC/B,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE;YACtB,gEAAgE;YAChE,GAAG,MAAM,CAAC,WAAW,CACnB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAC9E;SACF,CAAC;QAEF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAa,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OutputChannel, Task } from '../types.js';
|
|
2
|
+
type FeishuInlineElement = {
|
|
3
|
+
tag: 'text';
|
|
4
|
+
text: string;
|
|
5
|
+
style?: string[];
|
|
6
|
+
} | {
|
|
7
|
+
tag: 'a';
|
|
8
|
+
text: string;
|
|
9
|
+
href: string;
|
|
10
|
+
};
|
|
11
|
+
type FeishuLine = FeishuInlineElement[];
|
|
12
|
+
export declare function markdownToFeishuPost(markdown: string): {
|
|
13
|
+
title: string;
|
|
14
|
+
content: FeishuLine[];
|
|
15
|
+
};
|
|
16
|
+
export declare class FeishuChannel implements OutputChannel {
|
|
17
|
+
send(result: string, task: Task): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=feishu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/outputs/feishu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD,KAAK,mBAAmB,GACpB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/C;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,KAAK,UAAU,GAAG,mBAAmB,EAAE,CAAC;AA4BxC,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,UAAU,EAAE,CAAA;CAAE,CA6C/F;AAED,qBAAa,aAAc,YAAW,aAAa;IAC3C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAsCtD"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function parseInline(raw) {
|
|
2
|
+
const elements = [];
|
|
3
|
+
// Match [text](url) links and **bold**
|
|
4
|
+
const re = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)|\*\*([^*]+)\*\*/g;
|
|
5
|
+
let last = 0;
|
|
6
|
+
let m;
|
|
7
|
+
while ((m = re.exec(raw)) !== null) {
|
|
8
|
+
if (m.index > last) {
|
|
9
|
+
elements.push({ tag: 'text', text: raw.slice(last, m.index) });
|
|
10
|
+
}
|
|
11
|
+
if (m[1] && m[2]) {
|
|
12
|
+
elements.push({ tag: 'a', text: m[1], href: m[2] });
|
|
13
|
+
}
|
|
14
|
+
else if (m[3]) {
|
|
15
|
+
elements.push({ tag: 'text', text: m[3], style: ['bold'] });
|
|
16
|
+
}
|
|
17
|
+
last = re.lastIndex;
|
|
18
|
+
}
|
|
19
|
+
if (last < raw.length) {
|
|
20
|
+
elements.push({ tag: 'text', text: raw.slice(last) });
|
|
21
|
+
}
|
|
22
|
+
return elements.length > 0 ? elements : [{ tag: 'text', text: raw }];
|
|
23
|
+
}
|
|
24
|
+
export function markdownToFeishuPost(markdown) {
|
|
25
|
+
const lines = markdown.split('\n');
|
|
26
|
+
let title = '';
|
|
27
|
+
const content = [];
|
|
28
|
+
for (const raw of lines) {
|
|
29
|
+
const line = raw.trimEnd();
|
|
30
|
+
// h1 → card title (first one wins)
|
|
31
|
+
if (/^#\s+/.test(line)) {
|
|
32
|
+
if (!title)
|
|
33
|
+
title = line.replace(/^#\s+/, '');
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// h2/h3 → bold line
|
|
37
|
+
if (/^#{2,3}\s+/.test(line)) {
|
|
38
|
+
const text = line.replace(/^#{2,3}\s+/, '');
|
|
39
|
+
content.push([{ tag: 'text', text, style: ['bold'] }]);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// horizontal rule → empty line
|
|
43
|
+
if (/^---+$/.test(line)) {
|
|
44
|
+
content.push([{ tag: 'text', text: '' }]);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// empty line → empty line
|
|
48
|
+
if (line === '') {
|
|
49
|
+
content.push([{ tag: 'text', text: '' }]);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// list items → bullet prefix
|
|
53
|
+
if (/^[-*]\s+/.test(line)) {
|
|
54
|
+
const text = line.replace(/^[-*]\s+/, '• ');
|
|
55
|
+
content.push(parseInline(text));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// regular paragraph
|
|
59
|
+
content.push(parseInline(line));
|
|
60
|
+
}
|
|
61
|
+
return { title: title || 'Agent Report', content };
|
|
62
|
+
}
|
|
63
|
+
export class FeishuChannel {
|
|
64
|
+
async send(result, task) {
|
|
65
|
+
const webhook = task.feishuWebhook ?? process.env.FEISHU_WEBHOOK;
|
|
66
|
+
if (!webhook) {
|
|
67
|
+
throw new Error(`[agent-cron] feishu: missing webhook for task "${task.name}". Set feishuWebhook in frontmatter or FEISHU_WEBHOOK env var.`);
|
|
68
|
+
}
|
|
69
|
+
const { title, content } = markdownToFeishuPost(result);
|
|
70
|
+
const body = {
|
|
71
|
+
msg_type: 'post',
|
|
72
|
+
content: {
|
|
73
|
+
post: {
|
|
74
|
+
zh_cn: { title, content },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const res = await fetch(webhook, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'Content-Type': 'application/json' },
|
|
81
|
+
body: JSON.stringify(body),
|
|
82
|
+
});
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
throw new Error(`[agent-cron] feishu: request failed: ${res.status} ${await res.text()}`);
|
|
85
|
+
}
|
|
86
|
+
const json = (await res.json());
|
|
87
|
+
if (json.code !== 0) {
|
|
88
|
+
throw new Error(`[agent-cron] feishu: error response: code=${json.code} msg=${json.msg}`);
|
|
89
|
+
}
|
|
90
|
+
console.log(`[agent-cron] pushed to Feishu (${task.name})`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=feishu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../src/outputs/feishu.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/outputs/file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD,qBAAa,WAAY,YAAW,aAAa;IACzC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAStD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export class FileChannel {
|
|
4
|
+
async send(result, task) {
|
|
5
|
+
const outputDir = String(task.outputDir ?? './output');
|
|
6
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
7
|
+
const date = new Date().toISOString().split('T')[0];
|
|
8
|
+
const filePath = path.join(outputDir, `${task.slug}-${date}.md`);
|
|
9
|
+
fs.writeFileSync(filePath, result, 'utf-8');
|
|
10
|
+
console.log(`[agent-cron] written to ${filePath} (${task.name})`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/outputs/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,OAAO,WAAW;IACtB,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,IAAU;QACnC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,UAAU,CAAC,CAAC;QACvD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7C,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC;QACjE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,2BAA2B,QAAQ,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IACpE,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/outputs/github.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEvD,qBAAa,aAAc,YAAW,aAAa;IAC3C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAyDtD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Octokit } from '@octokit/rest';
|
|
2
|
+
export class GithubChannel {
|
|
3
|
+
async send(result, task) {
|
|
4
|
+
const repo = task.githubRepo;
|
|
5
|
+
if (!repo) {
|
|
6
|
+
throw new Error(`[agent-cron] github: missing githubRepo for task "${task.name}"`);
|
|
7
|
+
}
|
|
8
|
+
const [owner, repoName] = repo.split('/');
|
|
9
|
+
if (!owner || !repoName) {
|
|
10
|
+
throw new Error(`[agent-cron] github: invalid githubRepo format "${repo}", expected "owner/repo"`);
|
|
11
|
+
}
|
|
12
|
+
const token = task.githubToken ?? process.env.GITHUB_TOKEN;
|
|
13
|
+
if (!token) {
|
|
14
|
+
throw new Error(`[agent-cron] github: missing token for task "${task.name}". Set githubToken in frontmatter or GITHUB_TOKEN env var.`);
|
|
15
|
+
}
|
|
16
|
+
const branch = String(task.githubBranch ?? 'main');
|
|
17
|
+
const dir = task.githubDir ? String(task.githubDir) : '';
|
|
18
|
+
const date = new Date().toISOString().split('T')[0];
|
|
19
|
+
const fileName = `${task.slug}-${date}.md`;
|
|
20
|
+
const filePath = dir ? `${dir}/${fileName}` : fileName;
|
|
21
|
+
const octokit = new Octokit({ auth: token });
|
|
22
|
+
const content = Buffer.from(result, 'utf-8').toString('base64');
|
|
23
|
+
const message = `chore: ${task.name} ${date}`;
|
|
24
|
+
// Check if file exists (to get sha for update)
|
|
25
|
+
let sha;
|
|
26
|
+
try {
|
|
27
|
+
const { data } = await octokit.repos.getContent({
|
|
28
|
+
owner,
|
|
29
|
+
repo: repoName,
|
|
30
|
+
path: filePath,
|
|
31
|
+
ref: branch,
|
|
32
|
+
});
|
|
33
|
+
if (!Array.isArray(data) && data.type === 'file')
|
|
34
|
+
sha = data.sha;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// file doesn't exist yet, sha stays undefined
|
|
38
|
+
}
|
|
39
|
+
await octokit.repos.createOrUpdateFileContents({
|
|
40
|
+
owner,
|
|
41
|
+
repo: repoName,
|
|
42
|
+
path: filePath,
|
|
43
|
+
message,
|
|
44
|
+
content,
|
|
45
|
+
branch,
|
|
46
|
+
...(sha ? { sha } : {}),
|
|
47
|
+
});
|
|
48
|
+
console.log(`[agent-cron] pushed to GitHub ${repo}/${filePath} (${task.name})`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/outputs/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,MAAM,OAAO,aAAa;IACxB,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,IAAU;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAgC,CAAC;QACnD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,qDAAqD,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACrF,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,mDAAmD,IAAI,0BAA0B,CAClF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GACR,IAAI,CAAC,WAAkC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QACvE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,gDAAgD,IAAI,CAAC,IAAI,4DAA4D,CACtH,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,CAAC;QAC3C,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QAEvD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,UAAU,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QAE9C,+CAA+C;QAC/C,IAAI,GAAuB,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;gBAC9C,KAAK;gBACL,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,MAAM;aACZ,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;gBAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACnE,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;QAED,MAAM,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC;YAC7C,KAAK;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,QAAQ;YACd,OAAO;YACP,OAAO;YACP,MAAM;YACN,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxB,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,iCAAiC,IAAI,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAClF,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/outputs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAKlD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FileChannel } from './file.js';
|
|
2
|
+
import { FeishuChannel } from './feishu.js';
|
|
3
|
+
import { GithubChannel } from './github.js';
|
|
4
|
+
export const channels = {
|
|
5
|
+
file: new FileChannel(),
|
|
6
|
+
feishu: new FeishuChannel(),
|
|
7
|
+
github: new GithubChannel(),
|
|
8
|
+
// future: slack, telegram, discord, webhook, ...
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/outputs/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,QAAQ,GAAkC;IACrD,IAAI,EAAI,IAAI,WAAW,EAAE;IACzB,MAAM,EAAE,IAAI,aAAa,EAAE;IAC3B,MAAM,EAAE,IAAI,aAAa,EAAE;IAC3B,iDAAiD;CAClD,CAAC"}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AASvC,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCvD"}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { runners } from './agents/index.js';
|
|
2
|
+
import { channels } from './outputs/index.js';
|
|
3
|
+
function buildPrompt(template) {
|
|
4
|
+
const date = new Date().toLocaleDateString('zh-CN');
|
|
5
|
+
return template.replace(/\{date\}/g, date);
|
|
6
|
+
}
|
|
7
|
+
export async function runTask(task) {
|
|
8
|
+
console.log(`[agent-cron] starting: ${task.name} (${new Date().toLocaleString('zh-CN')})`);
|
|
9
|
+
const agentName = String(task.agent ?? 'claude');
|
|
10
|
+
const agentRunner = runners[agentName];
|
|
11
|
+
if (!agentRunner) {
|
|
12
|
+
console.error(`[agent-cron] unknown agent: "${agentName}" (${task.name})`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const prompt = buildPrompt(task.prompt);
|
|
16
|
+
let result = '';
|
|
17
|
+
try {
|
|
18
|
+
result = await agentRunner.run(prompt, task);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error(`[agent-cron] agent error (${task.name}):`, err);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!result) {
|
|
25
|
+
console.error(`[agent-cron] no result returned (${task.name})`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (result.trim() === 'HEARTBEAT_OK') {
|
|
29
|
+
console.log(`[agent-cron] OK — no new content (${task.name})`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const channel = channels[task.output];
|
|
33
|
+
if (!channel) {
|
|
34
|
+
console.error(`[agent-cron] unknown output channel: "${task.output}" (${task.name})`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await channel.send(result, task);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`[agent-cron] output error (${task.name}):`, err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +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,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpD,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAU;IACtC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAE3F,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,OAAO,CAAC,KAAK,CAAC,gCAAgC,SAAS,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC3E,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,oCAAoC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,cAAc,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACtF,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;IAClE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Task } from './types.js';
|
|
2
|
+
export declare function startScheduler(tasks: Task[]): void;
|
|
3
|
+
export declare function runNow(tasks: Task[], slug?: string): Promise<void>;
|
|
4
|
+
export declare function listTasks(tasks: Task[]): void;
|
|
5
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,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,CAa7C"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import cron from 'node-cron';
|
|
2
|
+
import { runTask } from './runner.js';
|
|
3
|
+
const TIMEZONE = 'Asia/Shanghai';
|
|
4
|
+
export function startScheduler(tasks) {
|
|
5
|
+
if (tasks.length === 0) {
|
|
6
|
+
console.warn('[agent-cron] no tasks found, nothing to schedule');
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
for (const task of tasks) {
|
|
10
|
+
if (!cron.validate(task.cron)) {
|
|
11
|
+
console.warn(`[agent-cron] invalid cron expression "${task.cron}" in task "${task.name}", skipping`);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
cron.schedule(task.cron, () => { void runTask(task); }, { timezone: TIMEZONE });
|
|
15
|
+
console.log(`[agent-cron] scheduled: ${task.name} → ${task.cron} (${TIMEZONE})`);
|
|
16
|
+
}
|
|
17
|
+
console.log(`[agent-cron] scheduler running with ${tasks.length} task(s). Press Ctrl+C to stop.`);
|
|
18
|
+
}
|
|
19
|
+
export async function runNow(tasks, slug) {
|
|
20
|
+
const targets = slug ? tasks.filter((t) => t.slug === slug) : tasks;
|
|
21
|
+
if (targets.length === 0) {
|
|
22
|
+
console.error(slug ? `[agent-cron] task not found: "${slug}"` : '[agent-cron] no tasks to run');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
for (const task of targets) {
|
|
26
|
+
await runTask(task);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function listTasks(tasks) {
|
|
30
|
+
if (tasks.length === 0) {
|
|
31
|
+
console.log('[agent-cron] no tasks found');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log('\nRegistered tasks:\n');
|
|
35
|
+
for (const task of tasks) {
|
|
36
|
+
console.log(` ${task.slug}`);
|
|
37
|
+
console.log(` name: ${task.name}`);
|
|
38
|
+
console.log(` cron: ${task.cron}`);
|
|
39
|
+
console.log(` output: ${task.output}`);
|
|
40
|
+
console.log('');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +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;AAEtC,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,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChF,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,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Task {
|
|
2
|
+
slug: string;
|
|
3
|
+
name: string;
|
|
4
|
+
cron: string;
|
|
5
|
+
output: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
skills?: boolean;
|
|
8
|
+
prompt: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface OutputChannel {
|
|
12
|
+
send(result: string, task: Task): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentRunner {
|
|
15
|
+
run(prompt: string, task: Task): Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACjD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@t0u9h/agent-cron",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Run Claude Agent SDK tasks on a cron schedule. Tasks are defined as .md files.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"agent-cron": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "node --import tsx/esm src/cli.ts",
|
|
13
|
+
"list": "node --import tsx/esm src/cli.ts list",
|
|
14
|
+
"start": "node --import tsx/esm src/cli.ts start",
|
|
15
|
+
"test": "node --import tsx/esm --test tests/**/*.test.ts"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@anthropic-ai/claude-agent-sdk": "latest",
|
|
19
|
+
"@octokit/rest": "^21.0.0",
|
|
20
|
+
"dotenv": "^16.0.0",
|
|
21
|
+
"gray-matter": "^4.0.3",
|
|
22
|
+
"node-cron": "^3.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"@types/node-cron": "^3.0.0",
|
|
27
|
+
"tsx": "^4.0.0",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/"
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"agent",
|
|
38
|
+
"cron",
|
|
39
|
+
"claude",
|
|
40
|
+
"ai",
|
|
41
|
+
"scheduler"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT"
|
|
44
|
+
}
|