@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.
Files changed (46) hide show
  1. package/README.md +98 -0
  2. package/dist/agents/claude.d.ts +5 -0
  3. package/dist/agents/claude.d.ts.map +1 -0
  4. package/dist/agents/claude.js +22 -0
  5. package/dist/agents/claude.js.map +1 -0
  6. package/dist/agents/index.d.ts +3 -0
  7. package/dist/agents/index.d.ts.map +1 -0
  8. package/dist/agents/index.js +6 -0
  9. package/dist/agents/index.js.map +1 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +47 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/loader.d.ts +3 -0
  15. package/dist/loader.d.ts.map +1 -0
  16. package/dist/loader.js +38 -0
  17. package/dist/loader.js.map +1 -0
  18. package/dist/outputs/feishu.d.ts +20 -0
  19. package/dist/outputs/feishu.d.ts.map +1 -0
  20. package/dist/outputs/feishu.js +93 -0
  21. package/dist/outputs/feishu.js.map +1 -0
  22. package/dist/outputs/file.d.ts +5 -0
  23. package/dist/outputs/file.d.ts.map +1 -0
  24. package/dist/outputs/file.js +13 -0
  25. package/dist/outputs/file.js.map +1 -0
  26. package/dist/outputs/github.d.ts +5 -0
  27. package/dist/outputs/github.d.ts.map +1 -0
  28. package/dist/outputs/github.js +51 -0
  29. package/dist/outputs/github.js.map +1 -0
  30. package/dist/outputs/index.d.ts +3 -0
  31. package/dist/outputs/index.d.ts.map +1 -0
  32. package/dist/outputs/index.js +10 -0
  33. package/dist/outputs/index.js.map +1 -0
  34. package/dist/runner.d.ts +3 -0
  35. package/dist/runner.d.ts.map +1 -0
  36. package/dist/runner.js +44 -0
  37. package/dist/runner.js.map +1 -0
  38. package/dist/scheduler.d.ts +5 -0
  39. package/dist/scheduler.d.ts.map +1 -0
  40. package/dist/scheduler.js +43 -0
  41. package/dist/scheduler.js.map +1 -0
  42. package/dist/types.d.ts +17 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. 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,5 @@
1
+ import type { AgentRunner, Task } from '../types.js';
2
+ export declare class ClaudeRunner implements AgentRunner {
3
+ run(prompt: string, task: Task): Promise<string>;
4
+ }
5
+ //# sourceMappingURL=claude.d.ts.map
@@ -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,3 @@
1
+ import type { AgentRunner } from '../types.js';
2
+ export declare const runners: Record<string, AgentRunner>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,6 @@
1
+ import { ClaudeRunner } from './claude.js';
2
+ export const runners = {
3
+ claude: new ClaudeRunner(),
4
+ // future: codex, opencode, copilot, ...
5
+ };
6
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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
@@ -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"}
@@ -0,0 +1,3 @@
1
+ import type { Task } from './types.js';
2
+ export declare function loadTasks(dir: string): Task[];
3
+ //# sourceMappingURL=loader.d.ts.map
@@ -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,5 @@
1
+ import type { OutputChannel, Task } from '../types.js';
2
+ export declare class FileChannel implements OutputChannel {
3
+ send(result: string, task: Task): Promise<void>;
4
+ }
5
+ //# sourceMappingURL=file.d.ts.map
@@ -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,5 @@
1
+ import type { OutputChannel, Task } from '../types.js';
2
+ export declare class GithubChannel implements OutputChannel {
3
+ send(result: string, task: Task): Promise<void>;
4
+ }
5
+ //# sourceMappingURL=github.d.ts.map
@@ -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,3 @@
1
+ import type { OutputChannel } from '../types.js';
2
+ export declare const channels: Record<string, OutputChannel>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -0,0 +1,3 @@
1
+ import type { Task } from './types.js';
2
+ export declare function runTask(task: Task): Promise<void>;
3
+ //# sourceMappingURL=runner.d.ts.map
@@ -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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }