@karmaniverous/jeeves-runner 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/LICENSE +28 -0
- package/README.md +401 -0
- package/dist/cli/jeeves-runner/index.js +880 -0
- package/dist/db/migrations/001-initial.sql +61 -0
- package/dist/index.d.ts +270 -0
- package/dist/mjs/index.js +917 -0
- package/package.json +141 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Jason Williscroft
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# jeeves-runner
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@karmaniverous/jeeves-runner)
|
|
4
|
+

|
|
5
|
+
[](https://github.com/karmaniverous/jeeves-runner/tree/main/LICENSE.md)
|
|
6
|
+
|
|
7
|
+
Graph-aware job execution engine with SQLite state. Part of the [Jeeves platform](#the-jeeves-platform).
|
|
8
|
+
|
|
9
|
+
## What It Does
|
|
10
|
+
|
|
11
|
+
jeeves-runner schedules and executes jobs, tracks their state in SQLite, and exposes status via a REST API. It replaces both n8n and Windows Task Scheduler as the substrate for data flow automation.
|
|
12
|
+
|
|
13
|
+
**Key properties:**
|
|
14
|
+
|
|
15
|
+
- **Domain-agnostic.** The runner knows graph primitives (source, sink, datastore, queue, process, auth), not business concepts. "Email polling" and "meeting extraction" are just jobs with scripts.
|
|
16
|
+
- **SQLite-native.** Job definitions, run history, cursors, and queues live in a single SQLite file. No external database, no Redis.
|
|
17
|
+
- **Zero new infrastructure.** One Node.js process, one SQLite file. Runs as a system service via NSSM (Windows) or systemd (Linux).
|
|
18
|
+
- **Scripts as config.** Job scripts live outside the runner repo at configurable absolute paths. The runner is generic; the scripts are instance-specific.
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────────────────────┐
|
|
24
|
+
│ jeeves-runner │
|
|
25
|
+
│ │
|
|
26
|
+
│ ┌───────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
27
|
+
│ │ Scheduler │──│ Executor │──│ Notifier │ │
|
|
28
|
+
│ │ (croner) │ │ (spawn) │ │ (Slack) │ │
|
|
29
|
+
│ └───────────┘ └──────────┘ └──────────────┘ │
|
|
30
|
+
│ │
|
|
31
|
+
│ ┌───────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
32
|
+
│ │ SQLite │ │ REST API │ │ Maintenance │ │
|
|
33
|
+
│ │ (DB) │ │(Fastify) │ │ (pruning) │ │
|
|
34
|
+
│ └───────────┘ └──────────┘ └──────────────┘ │
|
|
35
|
+
└─────────────────────────────────────────────────┘
|
|
36
|
+
│ │
|
|
37
|
+
▼ ▼
|
|
38
|
+
runner.sqlite localhost:3100
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Stack
|
|
42
|
+
|
|
43
|
+
| Component | Technology |
|
|
44
|
+
|-----------|-----------|
|
|
45
|
+
| Runtime | Node.js v24+ (uses built-in `node:sqlite`) |
|
|
46
|
+
| Scheduler | [croner](https://www.npmjs.com/package/croner) |
|
|
47
|
+
| Database | SQLite via `node:sqlite` |
|
|
48
|
+
| Process isolation | `child_process.spawn` |
|
|
49
|
+
| HTTP API | [Fastify](https://fastify.dev/) |
|
|
50
|
+
| Logging | [pino](https://getpino.io/) |
|
|
51
|
+
| Config validation | [Zod](https://zod.dev/) |
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @karmaniverous/jeeves-runner
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requires Node.js 24+ for `node:sqlite` support.
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### 1. Create a config file
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"port": 3100,
|
|
68
|
+
"dbPath": "./data/runner.sqlite",
|
|
69
|
+
"maxConcurrency": 4,
|
|
70
|
+
"runRetentionDays": 30,
|
|
71
|
+
"cursorCleanupIntervalMs": 3600000,
|
|
72
|
+
"shutdownGraceMs": 30000,
|
|
73
|
+
"notifications": {
|
|
74
|
+
"slackTokenPath": "./credentials/slack-bot-token",
|
|
75
|
+
"defaultOnFailure": "YOUR_SLACK_CHANNEL_ID",
|
|
76
|
+
"defaultOnSuccess": null
|
|
77
|
+
},
|
|
78
|
+
"log": {
|
|
79
|
+
"level": "info",
|
|
80
|
+
"file": "./data/runner.log"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Start the runner
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx jeeves-runner start --config ./config.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. Add a job
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx jeeves-runner add-job \
|
|
95
|
+
--id my-job \
|
|
96
|
+
--name "My Job" \
|
|
97
|
+
--schedule "*/5 * * * *" \
|
|
98
|
+
--script /absolute/path/to/script.js \
|
|
99
|
+
--config ./config.json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 4. Check status
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx jeeves-runner status --config ./config.json
|
|
106
|
+
npx jeeves-runner list-jobs --config ./config.json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CLI Commands
|
|
110
|
+
|
|
111
|
+
| Command | Description |
|
|
112
|
+
|---------|-------------|
|
|
113
|
+
| `start` | Start the runner daemon |
|
|
114
|
+
| `status` | Show runner stats (queries the HTTP API) |
|
|
115
|
+
| `list-jobs` | List all configured jobs |
|
|
116
|
+
| `add-job` | Add a new job to the database |
|
|
117
|
+
| `trigger` | Manually trigger a job run (queries the HTTP API) |
|
|
118
|
+
|
|
119
|
+
All commands accept `--config <path>` to specify the config file.
|
|
120
|
+
|
|
121
|
+
## HTTP API
|
|
122
|
+
|
|
123
|
+
The runner exposes a REST API on `localhost` (not externally accessible by default).
|
|
124
|
+
|
|
125
|
+
| Method | Path | Description |
|
|
126
|
+
|--------|------|-------------|
|
|
127
|
+
| `GET` | `/health` | Health check |
|
|
128
|
+
| `GET` | `/jobs` | List all jobs with last run status |
|
|
129
|
+
| `GET` | `/jobs/:id` | Single job detail |
|
|
130
|
+
| `GET` | `/jobs/:id/runs` | Run history (paginated via `?limit=N`) |
|
|
131
|
+
| `POST` | `/jobs/:id/run` | Trigger manual run |
|
|
132
|
+
| `POST` | `/jobs/:id/enable` | Enable a job |
|
|
133
|
+
| `POST` | `/jobs/:id/disable` | Disable a job |
|
|
134
|
+
| `GET` | `/stats` | Aggregate stats (jobs ok/error/running counts) |
|
|
135
|
+
|
|
136
|
+
### Example response
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
// GET /jobs
|
|
140
|
+
{
|
|
141
|
+
"jobs": [
|
|
142
|
+
{
|
|
143
|
+
"id": "email-poll",
|
|
144
|
+
"name": "Poll Email",
|
|
145
|
+
"schedule": "*/11 * * * *",
|
|
146
|
+
"enabled": 1,
|
|
147
|
+
"last_status": "ok",
|
|
148
|
+
"last_run": "2026-02-24T10:30:00"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## SQLite Schema
|
|
155
|
+
|
|
156
|
+
Four tables manage all runner state:
|
|
157
|
+
|
|
158
|
+
### `jobs` — Job Definitions
|
|
159
|
+
|
|
160
|
+
Each job has an ID, name, cron schedule, script path, and behavioral configuration.
|
|
161
|
+
|
|
162
|
+
| Column | Type | Description |
|
|
163
|
+
|--------|------|-------------|
|
|
164
|
+
| `id` | TEXT PK | Job identifier (e.g. `email-poll`) |
|
|
165
|
+
| `name` | TEXT | Human-readable name |
|
|
166
|
+
| `schedule` | TEXT | Cron expression |
|
|
167
|
+
| `script` | TEXT | Absolute path to script |
|
|
168
|
+
| `type` | TEXT | `script` or `session` (LLM dispatcher) |
|
|
169
|
+
| `enabled` | INTEGER | 1 = active, 0 = paused |
|
|
170
|
+
| `timeout_ms` | INTEGER | Kill after this duration (null = no limit) |
|
|
171
|
+
| `overlap_policy` | TEXT | `skip` (default), `queue`, or `allow` |
|
|
172
|
+
| `on_failure` | TEXT | Slack channel ID for failure alerts |
|
|
173
|
+
| `on_success` | TEXT | Slack channel ID for success alerts |
|
|
174
|
+
|
|
175
|
+
### `runs` — Run History
|
|
176
|
+
|
|
177
|
+
Every execution is recorded with status, timing, output capture, and optional token tracking.
|
|
178
|
+
|
|
179
|
+
| Column | Type | Description |
|
|
180
|
+
|--------|------|-------------|
|
|
181
|
+
| `id` | INTEGER PK | Auto-incrementing run ID |
|
|
182
|
+
| `job_id` | TEXT FK | References `jobs.id` |
|
|
183
|
+
| `status` | TEXT | `pending`, `running`, `ok`, `error`, `timeout`, `skipped` |
|
|
184
|
+
| `duration_ms` | INTEGER | Wall-clock execution time |
|
|
185
|
+
| `exit_code` | INTEGER | Process exit code |
|
|
186
|
+
| `tokens` | INTEGER | LLM token count (session jobs only) |
|
|
187
|
+
| `result_meta` | TEXT | JSON from `JR_RESULT:{json}` stdout lines |
|
|
188
|
+
| `stdout_tail` | TEXT | Last 100 lines of stdout |
|
|
189
|
+
| `stderr_tail` | TEXT | Last 100 lines of stderr |
|
|
190
|
+
| `trigger` | TEXT | `schedule`, `manual`, or `retry` |
|
|
191
|
+
|
|
192
|
+
Runs older than `runRetentionDays` are automatically pruned.
|
|
193
|
+
|
|
194
|
+
### `cursors` — Key-Value State
|
|
195
|
+
|
|
196
|
+
General-purpose key-value store with optional TTL. Replaces JSONL registry files.
|
|
197
|
+
|
|
198
|
+
| Column | Type | Description |
|
|
199
|
+
|--------|------|-------------|
|
|
200
|
+
| `namespace` | TEXT | Logical grouping (typically job ID) |
|
|
201
|
+
| `key` | TEXT | State key |
|
|
202
|
+
| `value` | TEXT | State value (string or JSON) |
|
|
203
|
+
| `expires_at` | TEXT | Optional TTL (ISO timestamp, auto-cleaned) |
|
|
204
|
+
|
|
205
|
+
### `queues` — Work Queues
|
|
206
|
+
|
|
207
|
+
Priority-ordered work queues with claim semantics. SQLite's serialized writes prevent double-claims.
|
|
208
|
+
|
|
209
|
+
| Column | Type | Description |
|
|
210
|
+
|--------|------|-------------|
|
|
211
|
+
| `id` | INTEGER PK | Auto-incrementing item ID |
|
|
212
|
+
| `queue` | TEXT | Queue name |
|
|
213
|
+
| `payload` | TEXT | JSON blob |
|
|
214
|
+
| `status` | TEXT | `pending`, `claimed`, `done`, `error` |
|
|
215
|
+
| `priority` | INTEGER | Higher = more urgent |
|
|
216
|
+
| `attempts` | INTEGER | Delivery attempt count |
|
|
217
|
+
| `max_attempts` | INTEGER | Maximum retries |
|
|
218
|
+
|
|
219
|
+
## Job Scripts
|
|
220
|
+
|
|
221
|
+
Jobs are plain Node.js scripts executed as child processes. The runner passes context via environment variables:
|
|
222
|
+
|
|
223
|
+
| Variable | Description |
|
|
224
|
+
|----------|-------------|
|
|
225
|
+
| `JR_DB_PATH` | Path to the runner SQLite database |
|
|
226
|
+
| `JR_JOB_ID` | ID of the current job |
|
|
227
|
+
| `JR_RUN_ID` | ID of the current run |
|
|
228
|
+
|
|
229
|
+
### Structured output
|
|
230
|
+
|
|
231
|
+
Scripts can emit structured results by writing a line to stdout:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
JR_RESULT:{"tokens":1500,"meta":"processed 42 items"}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The runner parses this and stores the data in the `runs` table.
|
|
238
|
+
|
|
239
|
+
### Client library
|
|
240
|
+
|
|
241
|
+
Job scripts can import the runner client for cursor and queue operations:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { createClient } from '@karmaniverous/jeeves-runner';
|
|
245
|
+
|
|
246
|
+
const jr = createClient(); // reads JR_DB_PATH from env
|
|
247
|
+
|
|
248
|
+
// Cursors (key-value state)
|
|
249
|
+
const lastId = jr.getCursor('email-poll', 'last_history_id');
|
|
250
|
+
jr.setCursor('email-poll', 'last_history_id', newId);
|
|
251
|
+
jr.setCursor('email-poll', `seen:${threadId}`, '1', { ttl: '30d' });
|
|
252
|
+
jr.deleteCursor('email-poll', 'old_key');
|
|
253
|
+
|
|
254
|
+
// Queues
|
|
255
|
+
jr.enqueue('email-updates', { threadId, action: 'label' });
|
|
256
|
+
const items = jr.dequeue('email-updates', 10); // claim up to 10
|
|
257
|
+
jr.done(items[0].id);
|
|
258
|
+
jr.fail(items[1].id, 'API error');
|
|
259
|
+
|
|
260
|
+
jr.close();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Job Lifecycle
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
Cron fires
|
|
267
|
+
→ Check overlap policy (skip if running & policy = 'skip')
|
|
268
|
+
→ INSERT run (status = 'running')
|
|
269
|
+
→ spawn('node', [script], { env: JR_* })
|
|
270
|
+
→ Capture stdout/stderr (ring buffer, last 100 lines)
|
|
271
|
+
→ Parse JR_RESULT lines → extract tokens + result_meta
|
|
272
|
+
→ On timeout: kill process, status = 'timeout', notify
|
|
273
|
+
→ On exit 0: status = 'ok', notify if on_success configured
|
|
274
|
+
→ On exit ≠ 0: status = 'error', notify if on_failure configured
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Overlap policies
|
|
278
|
+
|
|
279
|
+
| Policy | Behavior |
|
|
280
|
+
|--------|----------|
|
|
281
|
+
| `skip` | Don't start if already running (default) |
|
|
282
|
+
| `queue` | Wait for current run to finish, then start |
|
|
283
|
+
| `allow` | Run concurrently |
|
|
284
|
+
|
|
285
|
+
### Concurrency
|
|
286
|
+
|
|
287
|
+
A global semaphore limits concurrent jobs (default: 4, configurable via `maxConcurrency`). When the limit is hit, behavior follows the job's overlap policy.
|
|
288
|
+
|
|
289
|
+
### Notifications
|
|
290
|
+
|
|
291
|
+
Slack notifications are sent via direct HTTP POST to `chat.postMessage` (no SDK dependency):
|
|
292
|
+
|
|
293
|
+
- **Failure:** `⚠️ *Job Name* failed (12.3s): error message`
|
|
294
|
+
- **Success:** `✅ *Job Name* completed (3.4s)`
|
|
295
|
+
|
|
296
|
+
Notifications require a Slack bot token (file path in config). Each job can override the default notification channels.
|
|
297
|
+
|
|
298
|
+
## Maintenance
|
|
299
|
+
|
|
300
|
+
The runner automatically performs periodic maintenance:
|
|
301
|
+
|
|
302
|
+
- **Run pruning:** Deletes run records older than `runRetentionDays` (default: 30).
|
|
303
|
+
- **Cursor cleanup:** Deletes expired cursor entries (runs every `cursorCleanupIntervalMs`, default: 1 hour).
|
|
304
|
+
|
|
305
|
+
Both tasks run on startup and at the configured interval.
|
|
306
|
+
|
|
307
|
+
## Programmatic Usage
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { createRunner, runnerConfigSchema } from '@karmaniverous/jeeves-runner';
|
|
311
|
+
|
|
312
|
+
const config = runnerConfigSchema.parse({
|
|
313
|
+
port: 3100,
|
|
314
|
+
dbPath: './data/runner.sqlite',
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const runner = createRunner(config);
|
|
318
|
+
await runner.start();
|
|
319
|
+
|
|
320
|
+
// Graceful shutdown
|
|
321
|
+
process.on('SIGTERM', () => runner.stop());
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Configuration Reference
|
|
325
|
+
|
|
326
|
+
| Key | Type | Default | Description |
|
|
327
|
+
|-----|------|---------|-------------|
|
|
328
|
+
| `port` | number | `3100` | HTTP API port |
|
|
329
|
+
| `dbPath` | string | `./data/runner.sqlite` | SQLite database path |
|
|
330
|
+
| `maxConcurrency` | number | `4` | Max concurrent jobs |
|
|
331
|
+
| `runRetentionDays` | number | `30` | Days to keep run history |
|
|
332
|
+
| `cursorCleanupIntervalMs` | number | `3600000` | Cursor cleanup interval (ms) |
|
|
333
|
+
| `shutdownGraceMs` | number | `30000` | Grace period for running jobs on shutdown |
|
|
334
|
+
| `notifications.slackTokenPath` | string | — | Path to Slack bot token file |
|
|
335
|
+
| `notifications.defaultOnFailure` | string \| null | `null` | Default Slack channel for failures |
|
|
336
|
+
| `notifications.defaultOnSuccess` | string \| null | `null` | Default Slack channel for successes |
|
|
337
|
+
| `log.level` | string | `info` | Log level (trace/debug/info/warn/error/fatal) |
|
|
338
|
+
| `log.file` | string | — | Log file path (stdout if omitted) |
|
|
339
|
+
|
|
340
|
+
## The Jeeves Platform
|
|
341
|
+
|
|
342
|
+
jeeves-runner is one component of a four-part platform:
|
|
343
|
+
|
|
344
|
+
| Component | Role | Status |
|
|
345
|
+
|-----------|------|--------|
|
|
346
|
+
| **jeeves-runner** | Execute: run processes, move data through the graph | This package |
|
|
347
|
+
| **[jeeves-watcher](https://github.com/karmaniverous/jeeves-watcher)** | Index: observe file-backed datastores, embed in Qdrant | Shipped |
|
|
348
|
+
| **jeeves-server** | Present: UI, API, file serving, search, dashboards | Shipped |
|
|
349
|
+
| **Jeeves skill** | Converse: configure, operate, and query via chat | Planned |
|
|
350
|
+
|
|
351
|
+
## Project Status
|
|
352
|
+
|
|
353
|
+
**Phase 1** (current): Replicate existing job scheduling and status reporting. Replace n8n and the Notion Process Dashboard.
|
|
354
|
+
|
|
355
|
+
### What's built
|
|
356
|
+
|
|
357
|
+
- ✅ SQLite schema (jobs, runs, cursors, queues)
|
|
358
|
+
- ✅ Cron scheduler with overlap policies and concurrency limits
|
|
359
|
+
- ✅ Job executor with output capture, timeout enforcement, and `JR_RESULT` parsing
|
|
360
|
+
- ✅ Client library for cursor/queue operations from job scripts
|
|
361
|
+
- ✅ Slack notifications for job success/failure
|
|
362
|
+
- ✅ REST API (Fastify) for job management and monitoring
|
|
363
|
+
- ✅ CLI for daemon management and job operations
|
|
364
|
+
- ✅ Maintenance tasks (run pruning, cursor cleanup)
|
|
365
|
+
- ✅ Zod-validated configuration
|
|
366
|
+
- ✅ Seed script for 27 existing n8n workflows
|
|
367
|
+
- ✅ 75 passing tests
|
|
368
|
+
|
|
369
|
+
### What's next (Phase 1 remaining)
|
|
370
|
+
|
|
371
|
+
- [ ] NSSM service setup
|
|
372
|
+
- [ ] jeeves-server dashboard page (`/runner`)
|
|
373
|
+
- [ ] Migrate jobs from n8n one by one
|
|
374
|
+
- [ ] Retire n8n
|
|
375
|
+
|
|
376
|
+
### Future phases
|
|
377
|
+
|
|
378
|
+
| Feature | Phase |
|
|
379
|
+
|---------|-------|
|
|
380
|
+
| Graph topology (nodes/edges schema) | 2 |
|
|
381
|
+
| Credential/auth management | 2 |
|
|
382
|
+
| REST API for graph mutations | 2 |
|
|
383
|
+
| OpenClaw plugin & Jeeves skill | 3 |
|
|
384
|
+
| Container packaging | 3 |
|
|
385
|
+
|
|
386
|
+
## Development
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
npm install
|
|
390
|
+
npx lefthook install
|
|
391
|
+
|
|
392
|
+
npm run lint # ESLint + Prettier
|
|
393
|
+
npm run test # Vitest
|
|
394
|
+
npm run knip # Unused code detection
|
|
395
|
+
npm run build # Rollup (ESM + types + CLI)
|
|
396
|
+
npm run typecheck # TypeScript (noEmit)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
BSD-3-Clause
|