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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/package.json +4 -1
- package/.cron-job/tasks.md +0 -5
- package/.github/workflows/ci.yml +0 -19
- package/.github/workflows/release.yml +0 -42
- package/.opencode/opencode.json +0 -3
- package/AGENTS.md +0 -50
- package/CHANGELOG.md +0 -17
- package/PUBLISH.md +0 -77
- package/src/index.ts +0 -114
- package/tsconfig.json +0 -18
package/dist/index.d.ts
ADDED
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.
|
|
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
|
},
|
package/.cron-job/tasks.md
DELETED
package/.github/workflows/ci.yml
DELETED
|
@@ -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
|
package/.opencode/opencode.json
DELETED
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
|
-
|