opencode-cron-job 0.1.0 → 0.1.1

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.
@@ -0,0 +1,2 @@
1
+ import { type Plugin } from "@opencode-ai/plugin";
2
+ export declare const CronPlugin: Plugin;
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ import cron from "node-cron";
4
+ import { tool } from "@opencode-ai/plugin";
5
+ function parseTasks(content) {
6
+ const tasks = [];
7
+ const sections = content.split(/^## /m).filter((s) => s.trim());
8
+ for (const section of sections) {
9
+ const lines = section.split("\n");
10
+ const name = lines[0]?.trim();
11
+ if (!name)
12
+ continue;
13
+ let schedule = "", prompt = "";
14
+ for (const line of lines.slice(1)) {
15
+ const t = line.trim();
16
+ if (t.startsWith("- cron:"))
17
+ schedule = t.slice("- cron:".length).trim();
18
+ else if (t.startsWith("- prompt:"))
19
+ prompt = t.slice("- prompt:".length).trim();
20
+ }
21
+ if (schedule && prompt)
22
+ tasks.push({ name, schedule, prompt });
23
+ }
24
+ return tasks;
25
+ }
26
+ let jobs = [];
27
+ let nextId = 1;
28
+ export const CronPlugin = async ({ client, directory }) => {
29
+ const tasksFile = join(directory, ".cron-job", "tasks.md");
30
+ function fire(job) {
31
+ client.tui.appendPrompt({ body: { text: job.prompt } })
32
+ .then(() => client.tui.submitPrompt())
33
+ .catch(() => { });
34
+ }
35
+ function schedule(j) {
36
+ if (cron.validate(j.schedule)) {
37
+ j.task = cron.schedule(j.schedule, () => fire(j));
38
+ }
39
+ }
40
+ // Load from file on startup
41
+ if (existsSync(tasksFile)) {
42
+ const items = parseTasks(readFileSync(tasksFile, "utf-8"));
43
+ for (const item of items) {
44
+ const job = { ...item, id: String(nextId++), task: null };
45
+ schedule(job);
46
+ jobs.push(job);
47
+ }
48
+ }
49
+ return {
50
+ tool: {
51
+ cron_create: tool({
52
+ description: "Create a new cron job. The job fires on schedule by injecting the prompt into the user's session. Jobs are ephemeral (in-memory) and lost when OpenCode restarts. To persist a job permanently, also add it to .cron-job/tasks.md so it auto-loads on startup.",
53
+ args: {
54
+ name: tool.schema.string().describe("Unique name for this job"),
55
+ schedule: tool.schema.string().describe("Cron expression. 5-field format (min hour dom mon dow), e.g. '0 9 * * *' for daily at 9am. Supports 6-field with seconds: '*/30 * * * * *' for every 30s."),
56
+ prompt: tool.schema.string().describe("The prompt text that gets injected into the session when the job fires. The AI will receive this as a user message and act on it."),
57
+ },
58
+ async execute(args) {
59
+ const job = { id: String(nextId++), name: args.name, schedule: args.schedule, prompt: args.prompt, task: null };
60
+ schedule(job);
61
+ jobs.push(job);
62
+ return `Created job: ${job.name} (ID: ${job.id}, cron: ${job.schedule})`;
63
+ },
64
+ }),
65
+ cron_list: tool({
66
+ description: "List all scheduled cron jobs",
67
+ args: {},
68
+ async execute() {
69
+ if (jobs.length === 0)
70
+ return "No jobs scheduled.";
71
+ return jobs.map((j) => `${j.id} ${j.schedule} ${j.name}`).join("\n");
72
+ },
73
+ }),
74
+ cron_run: tool({
75
+ description: "Run a job immediately (fire-and-forget). The job fires once right now regardless of its cron schedule. Useful for testing or one-off execution. The job remains scheduled and will continue firing on its normal cron schedule afterwards.",
76
+ args: {
77
+ jobId: tool.schema.string().describe("ID of the job to run. Get it from cron_list."),
78
+ },
79
+ async execute(args) {
80
+ const job = jobs.find((j) => j.id === args.jobId);
81
+ if (!job)
82
+ return `Job ${args.jobId} not found.`;
83
+ fire(job);
84
+ return `${job.name}: triggered`;
85
+ },
86
+ }),
87
+ cron_delete: tool({
88
+ description: "Delete a cron job. Stops the timer and removes it from memory. This does not modify .cron-job/tasks.md — if the job was defined there, it will reappear after OpenCode restarts.",
89
+ args: {
90
+ jobId: tool.schema.string().describe("ID of the job to delete. Get it from cron_list."),
91
+ },
92
+ async execute(args) {
93
+ const idx = jobs.findIndex((j) => j.id === args.jobId);
94
+ if (idx === -1)
95
+ return `Job ${args.jobId} not found.`;
96
+ const [job] = jobs.splice(idx, 1);
97
+ job.task?.stop();
98
+ return `${job.name}: deleted`;
99
+ },
100
+ }),
101
+ },
102
+ };
103
+ };
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "opencode-cron-job",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenCode plugin - schedule recurring prompts via markdown files",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
+ "files": [
8
+ "dist/"
9
+ ],
7
10
  "exports": {
8
11
  ".": "./dist/index.js"
9
12
  },
@@ -1,5 +0,0 @@
1
- # 周期任务
2
-
3
- ## 喝水提醒
4
- - cron: 0 */2 * * *
5
- - prompt: 提醒用户喝水,保持健康
@@ -1,19 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- build:
11
- runs-on: ubuntu-latest
12
- steps:
13
- - uses: actions/checkout@v4
14
- - uses: actions/setup-node@v4
15
- with:
16
- node-version: lts/*
17
- cache: npm
18
- - run: npm ci
19
- - run: npm run build
@@ -1,42 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- permissions:
9
- contents: write
10
- packages: write
11
-
12
- jobs:
13
- publish-npm:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
- - uses: actions/setup-node@v4
18
- with:
19
- node-version: 20
20
- registry-url: 'https://registry.npmjs.org'
21
- - run: npm ci
22
- - run: npm run build
23
- - run: npm publish
24
- env:
25
- NODE_AUTH_TOKEN: ${{ secrets.NPM }}
26
-
27
- release:
28
- needs: publish-npm
29
- runs-on: ubuntu-latest
30
- steps:
31
- - uses: actions/checkout@v4
32
- - name: Extract release notes from CHANGELOG.md
33
- id: changelog
34
- uses: release-flow/keep-a-changelog-action@v3
35
- with:
36
- command: query
37
- version: ${{ github.ref_name }}
38
- - name: Create GitHub Release
39
- uses: softprops/action-gh-release@v2
40
- with:
41
- body: ${{ steps.changelog.outputs.release-notes }}
42
- draft: false
@@ -1,3 +0,0 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json"
3
- }
package/AGENTS.md DELETED
@@ -1,50 +0,0 @@
1
- # opencode-cron-job
2
-
3
- ## Build
4
-
5
- ```bash
6
- npm run build # tsc → dist/index.js
7
- npm publish # builds + publishes to npm
8
- ```
9
-
10
- After build, copy to plugins dir for local testing:
11
- ```bash
12
- cp dist/index.js .opencode/plugins/
13
- ```
14
-
15
- ## Architecture
16
-
17
- Single-file TypeScript OpenCode plugin (`src/index.ts`). Exports `CronPlugin` which is auto-discovered when installed as an npm plugin or placed in `.opencode/plugins/`.
18
-
19
- ### Flow
20
-
21
- 1. Plugin initializes → reads `.cron-job/tasks.md` from `directory` (project root)
22
- 2. Parses `##` sections → extracts `- cron:` and `- prompt:` fields
23
- 3. Schedules each valid cron expression via `node-cron`
24
- 4. On timer fire → `client.tui.appendPrompt()` → `client.tui.submitPrompt()`
25
- 5. Registers 4 tools: `cron_create`, `cron_list`, `cron_run`, `cron_delete`
26
-
27
- ### Key constraints
28
-
29
- - NOT an MCP server — runs inside OpenCode process (no HTTP API, no sidecar)
30
- - Tools are defined with `tool()` helper from `@opencode-ai/plugin`, not raw objects
31
- - Prompt injection uses `client.tui.*` methods (TUI mode only)
32
- - Cron uses 5-field expressions by default, 6-field if seconds are specified (`*/30 * * * * *`)
33
- - Jobs are in-memory and volatile — reload via `cron_reload` after editing `.cron-job/tasks.md`
34
- - `@opencode-ai/plugin` is a peer dependency provided by OpenCode runtime
35
- - Plugin auto-updates via npm `@latest` tag are unreliable — bump pinned version in config or clear cache manually
36
-
37
- ### Published API
38
-
39
- ```json
40
- {
41
- "plugin": ["opencode-cron-job@latest"]
42
- }
43
- ```
44
-
45
- ## Gotchas
46
-
47
- - `node-cron` v3 accepts 6-field format (seconds included)
48
- - Plugin runs inside Bun/OpenCode runtime — use `import` syntax, not `require`
49
- - Build artifact `.opencode/plugins/index.js` is copied from `dist/` — both gitignored
50
- - `.opencode/package.json` has its own `node_modules/` for local plugin dev — separate from root
package/CHANGELOG.md DELETED
@@ -1,17 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [0.1.0] - 2026-06-04
9
-
10
- ### Added
11
-
12
- - Initial release
13
- - OpenCode plugin that schedules recurring prompts via `.cron-job/tasks.md`
14
- - Tools: `cron_create`, `cron_list`, `cron_run`, `cron_delete`
15
- - Auto-load tasks on plugin initialization
16
- - Prompt injection via `client.tui.appendPrompt()` + `client.tui.submitPrompt()`
17
-
package/PUBLISH.md DELETED
@@ -1,77 +0,0 @@
1
- # 发布指南
2
-
3
- ## 发布前检查
4
-
5
- ### 检查 `package.json` 版本号
6
-
7
- ```json
8
- {
9
- "version": "0.1.0"
10
- }
11
- ```
12
-
13
- 确保版本号符合语义化版本规范。
14
-
15
- ### 检查 CHANGELOG.md 是否需要同步
16
-
17
- CHANGELOG.md 是 GitHub Release Notes 的数据源:
18
-
19
- - **每次发版前**:在 `[Unreleased]` 下方写好本次版本的变更说明
20
- - **格式**:遵循 [Keep a Changelog](https://keepachangelog.com/) 规范,分 `Added` / `Changed` / `Fixed` / `Removed` 等分类
21
- - **日期**:格式为 `YYYY-MM-DD`
22
-
23
- ### 检查 README.md 是否需要同步
24
-
25
- - **功能增减**或**行为变化**时需要同步更新中英文说明
26
- - 纯 bug 修复不需要更新
27
-
28
- ### 检查 AGENTS.md 是否需要同步
29
-
30
- - **架构变更**、**工具变更**(新增/删除工具)时需要更新
31
- - Bug 修复或内部重构不需要更新
32
-
33
- ---
34
-
35
- ## 发布流程
36
-
37
- ### 1. 更新 CHANGELOG.md(必做)
38
-
39
- 将 `[Unreleased]` 改为版本号和日期:
40
-
41
- ```markdown
42
- ## [0.1.0] - 2026-06-04
43
-
44
- ### Added
45
- - 新增功能...
46
- ```
47
-
48
- ### 2. 更新版本号
49
-
50
- 编辑 `package.json` 中的 `version` 字段。
51
-
52
- ### 3. 提交代码并推送 Tag
53
-
54
- > ⚠️ 执行前先向用户发送消息确认(已完成以上检查和修改),获得确认后才能执行。
55
-
56
- ```bash
57
- git add -A
58
- git commit -m "release: v0.1.0"
59
- git tag v0.1.0
60
- git push origin main --tags
61
- ```
62
-
63
- > push 不会自动推送 tags,必须执行 `git push origin --tags` 或 `git push origin v0.1.0`。
64
-
65
- ### 4. 自动构建与发布
66
-
67
- 推送 tag 后,GitHub Actions 自动触发 `.github/workflows/release.yml`:
68
-
69
- 1. **`publish-npm`** — 安装依赖 → 构建 → 发布到 npm
70
- 2. **`release`** — 自动创建带 Release Notes 的 GitHub Release
71
-
72
- > **前置条件**:在 GitHub 仓库 Settings → Secrets and variables → Actions 中配置 `NPM` secret,值为 npm Automation Token。
73
-
74
- ### 5. 检查结果
75
-
76
- - 确认 npm 包已更新:`npm view opencode-cron-job`
77
- - 确认 GitHub Release 已创建
package/src/index.ts DELETED
@@ -1,114 +0,0 @@
1
- import { readFileSync, existsSync } from "fs"
2
- import { join } from "path"
3
- import cron from "node-cron"
4
- import { tool, type Plugin } from "@opencode-ai/plugin"
5
-
6
- interface Job {
7
- id: string
8
- name: string
9
- schedule: string
10
- prompt: string
11
- task: cron.ScheduledTask | null
12
- }
13
-
14
- function parseTasks(content: string): { name: string; schedule: string; prompt: string }[] {
15
- const tasks: { name: string; schedule: string; prompt: string }[] = []
16
- const sections = content.split(/^## /m).filter((s) => s.trim())
17
- for (const section of sections) {
18
- const lines = section.split("\n")
19
- const name = lines[0]?.trim()
20
- if (!name) continue
21
- let schedule = "", prompt = ""
22
- for (const line of lines.slice(1)) {
23
- const t = line.trim()
24
- if (t.startsWith("- cron:")) schedule = t.slice("- cron:".length).trim()
25
- else if (t.startsWith("- prompt:")) prompt = t.slice("- prompt:".length).trim()
26
- }
27
- if (schedule && prompt) tasks.push({ name, schedule, prompt })
28
- }
29
- return tasks
30
- }
31
-
32
- let jobs: Job[] = []
33
- let nextId = 1
34
-
35
- export const CronPlugin: Plugin = async ({ client, directory }) => {
36
- const tasksFile = join(directory, ".cron-job", "tasks.md")
37
-
38
- function fire(job: Job) {
39
- client.tui.appendPrompt({ body: { text: job.prompt } })
40
- .then(() => client.tui.submitPrompt())
41
- .catch(() => {})
42
- }
43
-
44
- function schedule(j: Job) {
45
- if (cron.validate(j.schedule)) {
46
- j.task = cron.schedule(j.schedule, () => fire(j))
47
- }
48
- }
49
-
50
- // Load from file on startup
51
- if (existsSync(tasksFile)) {
52
- const items = parseTasks(readFileSync(tasksFile, "utf-8"))
53
- for (const item of items) {
54
- const job: Job = { ...item, id: String(nextId++), task: null }
55
- schedule(job)
56
- jobs.push(job)
57
- }
58
- }
59
-
60
- return {
61
- tool: {
62
- cron_create: tool({
63
- description: "Create a new cron job. The job fires on schedule by injecting the prompt into the user's session. Jobs are ephemeral (in-memory) and lost when OpenCode restarts. To persist a job permanently, also add it to .cron-job/tasks.md so it auto-loads on startup.",
64
- args: {
65
- name: tool.schema.string().describe("Unique name for this job"),
66
- schedule: tool.schema.string().describe("Cron expression. 5-field format (min hour dom mon dow), e.g. '0 9 * * *' for daily at 9am. Supports 6-field with seconds: '*/30 * * * * *' for every 30s."),
67
- prompt: tool.schema.string().describe("The prompt text that gets injected into the session when the job fires. The AI will receive this as a user message and act on it."),
68
- },
69
- async execute(args) {
70
- const job: Job = { id: String(nextId++), name: args.name, schedule: args.schedule, prompt: args.prompt, task: null }
71
- schedule(job)
72
- jobs.push(job)
73
- return `Created job: ${job.name} (ID: ${job.id}, cron: ${job.schedule})`
74
- },
75
- }),
76
-
77
- cron_list: tool({
78
- description: "List all scheduled cron jobs",
79
- args: {},
80
- async execute() {
81
- if (jobs.length === 0) return "No jobs scheduled."
82
- return jobs.map((j) => `${j.id} ${j.schedule} ${j.name}`).join("\n")
83
- },
84
- }),
85
-
86
- cron_run: tool({
87
- description: "Run a job immediately (fire-and-forget). The job fires once right now regardless of its cron schedule. Useful for testing or one-off execution. The job remains scheduled and will continue firing on its normal cron schedule afterwards.",
88
- args: {
89
- jobId: tool.schema.string().describe("ID of the job to run. Get it from cron_list."),
90
- },
91
- async execute(args) {
92
- const job = jobs.find((j) => j.id === args.jobId)
93
- if (!job) return `Job ${args.jobId} not found.`
94
- fire(job)
95
- return `${job.name}: triggered`
96
- },
97
- }),
98
-
99
- cron_delete: tool({
100
- description: "Delete a cron job. Stops the timer and removes it from memory. This does not modify .cron-job/tasks.md — if the job was defined there, it will reappear after OpenCode restarts.",
101
- args: {
102
- jobId: tool.schema.string().describe("ID of the job to delete. Get it from cron_list."),
103
- },
104
- async execute(args) {
105
- const idx = jobs.findIndex((j) => j.id === args.jobId)
106
- if (idx === -1) return `Job ${args.jobId} not found.`
107
- const [job] = jobs.splice(idx, 1)
108
- job.task?.stop()
109
- return `${job.name}: deleted`
110
- },
111
- }),
112
- },
113
- }
114
- }
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ES2022",
5
- "moduleResolution": "bundler",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "declaration": true,
13
- "sourceMap": true
14
- },
15
- "include": ["src/**/*"],
16
- "exclude": ["node_modules", "dist"]
17
- }
18
-