@geekbeer/minion 2.5.0 → 2.6.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/rules/minion.md CHANGED
@@ -3,6 +3,23 @@
3
3
  You are running on a Minion VPS managed by the @geekbeer/minion package.
4
4
  A local Agent API server runs at `http://localhost:3001` and a CLI tool `minion-cli` is available.
5
5
 
6
+ ## Architecture: Project / Workflow / Routine
7
+
8
+ ```
9
+ Project (組織・課金単位)
10
+ ├── Context (markdown, PMが更新)
11
+ ├── Members (minion + role)
12
+ └── Workflows (何をするか, スケジュールなし)
13
+
14
+ Minion
15
+ └── Routines (いつ・何を, cron付き)
16
+ ```
17
+
18
+ - **Workflow** はプロジェクトスコープ。「何をするか」を定義。スケジュールは持たない。
19
+ - **Routine** はミニオンスコープ。「いつ・何を」を定義。cron_expression を持ち、自律トリガーする。
20
+ - ミニオンは複数プロジェクトに `pm` または `engineer` として参加できる。
21
+ - ワークフローは直接トリガーされず、ルーティン経由で実行される(`project-workflow-check` スキル)。
22
+
6
23
  ## CLI Commands
7
24
 
8
25
  ```bash
@@ -35,7 +52,7 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
35
52
  | Method | Endpoint | Description |
36
53
  |--------|----------|-------------|
37
54
  | GET | `/api/list-skills` | List local deployed skills |
38
- | DELETE | `/api/skills/:name` | Delete local skill |
55
+ | DELETE | `/api/skills/:name` | Delete a local skill |
39
56
  | POST | `/api/skills/push/:name` | Push local skill to HQ |
40
57
  | POST | `/api/skills/fetch/:name` | Fetch skill from HQ and deploy locally |
41
58
  | GET | `/api/skills/remote` | List skills on HQ |
@@ -44,23 +61,26 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
44
61
 
45
62
  | Method | Endpoint | Description |
46
63
  |--------|----------|-------------|
47
- | GET | `/api/workflows` | List all workflows with next_run |
48
- | POST | `/api/workflows` | Receive/upsert workflows from HQ |
64
+ | GET | `/api/workflows` | List all local workflows with next_run |
65
+ | POST | `/api/workflows` | Receive/upsert workflows (from HQ or local) |
49
66
  | POST | `/api/workflows/push/:name` | Push local workflow to HQ |
50
67
  | POST | `/api/workflows/fetch/:name` | Fetch workflow from HQ and deploy locally (+ pipeline skills) |
51
68
  | GET | `/api/workflows/remote` | List workflows on HQ |
52
- | PUT | `/api/workflows/:id/schedule` | Update workflow schedule (cron_expression, is_active) |
53
- | DELETE | `/api/workflows/:id` | Remove workflow |
69
+ | PUT | `/api/workflows/:id/schedule` | **Legacy.** Update cron_expression/is_active. 新アーキではRoutine側で管理 |
70
+ | DELETE | `/api/workflows/:id` | Remove a local workflow |
54
71
  | POST | `/api/workflows/trigger` | Manual trigger. Body: `{workflow_id}` |
55
72
 
73
+ > **Note:** 新アーキテクチャでは Workflow にスケジュールがなく、Routine の cron で自律トリガーする設計。
74
+ > `PUT /api/workflows/:id/schedule` はレガシー互換で残っているが、新規利用は非推奨。
75
+
56
76
  ### Executions
57
77
 
58
78
  | Method | Endpoint | Description |
59
79
  |--------|----------|-------------|
60
- | GET | `/api/executions` | List execution history (?limit=50) |
80
+ | GET | `/api/executions` | List execution history (`?limit=50`, `?workflow_id=`) |
61
81
  | GET | `/api/executions/:id` | Get single execution |
62
- | GET | `/api/executions/:id/log` | Get execution log content |
63
- | POST | `/api/executions/:id/outcome` | Report outcome (no auth). Body: `{outcome}` |
82
+ | GET | `/api/executions/:id/log` | Get execution log content (`?tail=N`) |
83
+ | POST | `/api/executions/:id/outcome` | Report outcome (no auth). Body: `{outcome, summary?, details?}` |
64
84
 
65
85
  Outcome values: `success`, `failure`, `partial`
66
86
 
@@ -69,13 +89,25 @@ Outcome values: `success`, `failure`, `partial`
69
89
  | Method | Endpoint | Description |
70
90
  |--------|----------|-------------|
71
91
  | GET | `/api/terminal/sessions` | List tmux sessions |
72
- | POST | `/api/terminal/send` | Send keys. Body: `{session, keys}` |
73
- | POST | `/api/terminal/create` | Create session. Body: `{name, command?}` |
92
+ | POST | `/api/terminal/send` | Send keys. Body: `{session, input?, enter?, special?}` |
93
+ | POST | `/api/terminal/create` | Create session. Body: `{name?, command?}` |
74
94
  | POST | `/api/terminal/kill` | Kill session. Body: `{session}` |
75
- | GET | `/api/terminal/capture` | Capture pane content. Query: `?session=` |
95
+ | GET | `/api/terminal/capture` | Capture pane content. Query: `?session=&lines=100` |
76
96
  | GET | `/api/terminal/ttyd/status` | ttyd process status |
77
- | POST | `/api/terminal/ttyd/start` | Start ttyd. Body: `{session}` |
78
- | POST | `/api/terminal/ttyd/stop` | Stop ttyd. Body: `{session}` |
97
+ | POST | `/api/terminal/ttyd/start` | Start ttyd for session. Body: `{session}` |
98
+ | POST | `/api/terminal/ttyd/stop` | Stop ttyd for session. Body: `{session}` |
99
+ | POST | `/api/terminal/ttyd/stop-all` | Stop all ttyd processes |
100
+
101
+ ### Files
102
+
103
+ Files are stored in `~/files/`. Max upload size: 50MB.
104
+
105
+ | Method | Endpoint | Description |
106
+ |--------|----------|-------------|
107
+ | GET | `/api/files` | List files in directory. Query: `?path=subdir` |
108
+ | GET | `/api/files/*` | Download a file |
109
+ | POST | `/api/files/*` | Upload a file (Content-Type: `application/octet-stream`) |
110
+ | DELETE | `/api/files/*` | Delete a file or directory |
79
111
 
80
112
  ### Commands
81
113
 
@@ -86,6 +118,103 @@ Outcome values: `success`, `failure`, `partial`
86
118
 
87
119
  Available commands: `restart-agent`, `update-agent`, `restart-display`, `status-services`
88
120
 
121
+ ## HQ API Endpoints (https://<HQ_URL>)
122
+
123
+ Authentication: `Authorization: Bearer $API_TOKEN` header.
124
+ Base path: All endpoints are prefixed with `/api/minion` by the agent's API client.
125
+
126
+ ### Projects
127
+
128
+ | Method | Endpoint | Description |
129
+ |--------|----------|-------------|
130
+ | GET | `/api/minion/me/projects` | 参加プロジェクト一覧(role 含む) |
131
+
132
+ Response:
133
+ ```json
134
+ [
135
+ {
136
+ "id": "uuid",
137
+ "name": "project-name",
138
+ "description": "...",
139
+ "github_owner": "org",
140
+ "github_repo": "repo",
141
+ "role": "pm",
142
+ "created_at": "...",
143
+ "updated_at": "..."
144
+ }
145
+ ]
146
+ ```
147
+
148
+ `role` is either `"pm"` (project manager) or `"engineer"`.
149
+
150
+ ### Project Context
151
+
152
+ | Method | Endpoint | Description |
153
+ |--------|----------|-------------|
154
+ | GET | `/api/minion/me/project/[id]/context` | プロジェクトコンテキスト取得 |
155
+ | PUT | `/api/minion/me/project/[id]/context` | コンテキスト更新(PM のみ) |
156
+
157
+ PUT body: `{ "content": "markdown string" }`
158
+
159
+ ### Workflows (project-scoped)
160
+
161
+ | Method | Endpoint | Description |
162
+ |--------|----------|-------------|
163
+ | GET | `/api/minion/workflows` | 参加プロジェクトのワークフロー一覧 |
164
+
165
+ Response:
166
+ ```json
167
+ {
168
+ "workflows": [
169
+ {
170
+ "name": "workflow-name",
171
+ "pipeline_skill_names": ["skill-1", "skill-2"],
172
+ "content": "...",
173
+ "project_id": "uuid",
174
+ "created_at": "..."
175
+ }
176
+ ]
177
+ }
178
+ ```
179
+
180
+ ### Routines (minion-scoped)
181
+
182
+ | Method | Endpoint | Description |
183
+ |--------|----------|-------------|
184
+ | GET | `/api/minion/routines` | 自分のルーティン一覧 |
185
+
186
+ Response:
187
+ ```json
188
+ [
189
+ {
190
+ "id": "uuid",
191
+ "name": "routine-name",
192
+ "pipeline_skill_names": ["skill-1", "execution-report"],
193
+ "content": "...",
194
+ "is_active": true,
195
+ "cron_expression": "0 9 * * 1-5",
196
+ "last_run": "...",
197
+ "next_run": "...",
198
+ "created_at": "...",
199
+ "updated_at": "..."
200
+ }
201
+ ]
202
+ ```
203
+
204
+ ### Skills
205
+
206
+ | Method | Endpoint | Description |
207
+ |--------|----------|-------------|
208
+ | GET | `/api/minion/skills` | HQ に登録されたスキル一覧 |
209
+ | GET | `/api/minion/skills/[name]` | スキル詳細取得(content, references 含む) |
210
+ | POST | `/api/minion/skills` | スキル登録/更新 |
211
+
212
+ ### Execution Reporting
213
+
214
+ | Method | Endpoint | Description |
215
+ |--------|----------|-------------|
216
+ | POST | `/api/minion/execution` | 実行状況を HQ に報告 |
217
+
89
218
  ## Environment Variables
90
219
 
91
220
  | Variable | Description |
@@ -125,7 +254,8 @@ When not configured, the agent runs in standalone mode and HQ-dependent features
125
254
 
126
255
  ## Workflow Structure
127
256
 
128
- Workflows are stored locally in `workflows.json`. Each workflow object:
257
+ Workflows are project-scoped and fetched from HQ via `GET /api/minion/workflows`.
258
+ They are also stored locally in `workflows.json`. Each workflow object:
129
259
 
130
260
  ```json
131
261
  {
@@ -133,9 +263,7 @@ Workflows are stored locally in `workflows.json`. Each workflow object:
133
263
  "name": "my-workflow",
134
264
  "pipeline_skill_names": ["skill-1", "skill-2", "execution-report"],
135
265
  "content": "Markdown description of the workflow",
136
- "cron_expression": "0 9 * * 1-5",
137
- "is_active": true,
138
- "last_run": "2026-02-10T00:00:00.000Z"
266
+ "project_id": "uuid"
139
267
  }
140
268
  ```
141
269
 
@@ -145,38 +273,7 @@ Workflows are stored locally in `workflows.json`. Each workflow object:
145
273
  | `name` | string | Slug identifier (`/^[a-z0-9-]+$/`) |
146
274
  | `pipeline_skill_names` | string[] | Ordered skill names to execute |
147
275
  | `content` | string | Markdown body describing the workflow |
148
- | `cron_expression` | string | Cron schedule (empty = manual only) |
149
- | `is_active` | boolean | Whether cron scheduling is enabled |
150
- | `last_run` | string\|null | ISO timestamp of last execution |
151
-
152
- ### Creating a workflow locally
153
-
154
- Use `POST /api/workflows` to create/update workflows:
155
-
156
- ```bash
157
- curl -X POST http://localhost:3001/api/workflows \
158
- -H "Authorization: Bearer $API_TOKEN" \
159
- -H "Content-Type: application/json" \
160
- -d '{
161
- "workflows": [{
162
- "id": "'$(uuidgen)'",
163
- "name": "my-workflow",
164
- "pipeline_skill_names": ["my-skill", "execution-report"],
165
- "content": "Description of what this workflow does",
166
- "cron_expression": "",
167
- "is_active": false
168
- }]
169
- }'
170
- ```
171
-
172
- Then activate with a schedule:
173
-
174
- ```bash
175
- curl -X PUT http://localhost:3001/api/workflows/<id>/schedule \
176
- -H "Authorization: Bearer $API_TOKEN" \
177
- -H "Content-Type: application/json" \
178
- -d '{"cron_expression": "0 9 * * 1-5", "is_active": true}'
179
- ```
276
+ | `project_id` | string | UUID of the parent project |
180
277
 
181
278
  ### Syncing workflows with HQ
182
279
 
@@ -186,10 +283,55 @@ curl -X PUT http://localhost:3001/api/workflows/<id>/schedule \
186
283
 
187
284
  Pipeline skills must be deployed locally (in `~/.claude/skills/`) before pushing a workflow.
188
285
 
189
- ## Workflow Execution
286
+ ## Routine Structure
190
287
 
191
- Workflows run on cron schedules. Each execution:
288
+ Routines are minion-scoped and fetched from HQ via `GET /api/minion/routines`.
289
+ They are also stored locally in `routines.json`. Each routine object:
290
+
291
+ ```json
292
+ {
293
+ "id": "uuid",
294
+ "name": "morning-work",
295
+ "pipeline_skill_names": ["project-workflow-check", "execution-report"],
296
+ "content": "Markdown description of the routine",
297
+ "cron_expression": "0 9 * * 1-5",
298
+ "is_active": true,
299
+ "last_run": "2026-02-10T09:00:00.000Z",
300
+ "next_run": "2026-02-11T09:00:00.000Z"
301
+ }
302
+ ```
303
+
304
+ | Field | Type | Description |
305
+ |-------|------|-------------|
306
+ | `id` | string | UUID (auto-generated) |
307
+ | `name` | string | Slug identifier (`/^[a-z0-9-]+$/`) |
308
+ | `pipeline_skill_names` | string[] | Ordered skill names to execute |
309
+ | `content` | string | Markdown body describing the routine |
310
+ | `cron_expression` | string | Cron schedule (empty = manual only) |
311
+ | `is_active` | boolean | Whether cron scheduling is enabled |
312
+ | `last_run` | string\|null | ISO timestamp of last execution |
313
+ | `next_run` | string\|null | ISO timestamp of next scheduled run |
314
+
315
+ ## Routine Execution
316
+
317
+ Routines run on cron schedules. Each execution:
192
318
  1. Creates a tmux session
193
319
  2. Runs skills in pipeline order via `claude --dangerously-skip-permissions`
194
320
  3. Appends `execution-report` skill to report outcome
195
- 4. Environment variables `MINION_EXECUTION_ID`, `MINION_WORKFLOW_ID`, `MINION_WORKFLOW_NAME` are available during execution
321
+ 4. Environment variables `MINION_EXECUTION_ID`, `MINION_ROUTINE_ID`, `MINION_ROUTINE_NAME` are available during execution
322
+
323
+ ### Workflow Execution via Routine
324
+
325
+ ワークフローは `project-workflow-check` システムスキルを通じてルーティンから実行される:
326
+
327
+ ```
328
+ ルーティン: "morning-work" (cron: 0 9 * * 1-5)
329
+ pipeline: [project-workflow-check, execution-report]
330
+
331
+ project-workflow-check:
332
+ 1. GET /api/minion/me/projects → 参加プロジェクト一覧
333
+ 2. GET /api/minion/workflows → active なワークフロー一覧
334
+ 3. 各ワークフローの pipeline を順に実行
335
+
336
+ execution-report → HQ に結果報告
337
+ ```
package/server.js CHANGED
@@ -13,6 +13,8 @@ const fastify = require('fastify')({ logger: true })
13
13
  const { config, validate, isHqConfigured } = require('./config')
14
14
  const workflowRunner = require('./workflow-runner')
15
15
  const workflowStore = require('./workflow-store')
16
+ const routineRunner = require('./routine-runner')
17
+ const routineStore = require('./routine-store')
16
18
 
17
19
  const { registerRoutes, setOffline, getProcessManager, getAllowedCommands } = require('./routes')
18
20
  const { cleanupTtyd, killStaleTtydProcesses } = require('./routes/terminal')
@@ -31,8 +33,9 @@ async function shutdown(signal) {
31
33
 
32
34
  setOffline()
33
35
 
34
- // Stop workflow runner
36
+ // Stop workflow and routine runners
35
37
  workflowRunner.stopAll()
38
+ routineRunner.stopAll()
36
39
 
37
40
  // Stop terminal proxy and all ttyd processes
38
41
  stopTerminalProxy()
@@ -164,6 +167,17 @@ async function start() {
164
167
  console.error('[Server] Failed to load cached workflows:', err.message)
165
168
  }
166
169
 
170
+ // Load cached routines and start routine runner
171
+ try {
172
+ const cachedRoutines = await routineStore.load()
173
+ if (cachedRoutines.length > 0) {
174
+ console.log(`[Server] Loading ${cachedRoutines.length} cached routines`)
175
+ routineRunner.loadRoutines(cachedRoutines)
176
+ }
177
+ } catch (err) {
178
+ console.error('[Server] Failed to load cached routines:', err.message)
179
+ }
180
+
167
181
  if (isHqConfigured()) {
168
182
  console.log(`[Server] HQ URL: ${config.HQ_URL}`)
169
183
  } else {
@@ -100,6 +100,7 @@ async function executeWorkflowSession(workflow, executionId, skillNames) {
100
100
 
101
101
  // Extend PATH to include common CLI installation locations
102
102
  const additionalPaths = [
103
+ path.join(homeDir, 'bin'),
103
104
  path.join(homeDir, '.npm-global', 'bin'),
104
105
  path.join(homeDir, '.local', 'bin'),
105
106
  path.join(homeDir, '.claude', 'bin'),
package/workflow-store.js CHANGED
@@ -82,7 +82,7 @@ async function findByName(name) {
82
82
  * Upsert a workflow by name.
83
83
  * If exists: updates definition only (preserves schedule/local state).
84
84
  * If new: creates with inactive schedule.
85
- * @param {object} workflowData - { name, pipeline_skill_names, content }
85
+ * @param {object} workflowData - { name, pipeline_skill_names, content, project_id }
86
86
  * @returns {Promise<Array>} Updated workflows array
87
87
  */
88
88
  async function upsertByName(workflowData) {
@@ -96,12 +96,16 @@ async function upsertByName(workflowData) {
96
96
  if (workflowData.content !== undefined) {
97
97
  workflows[index].content = workflowData.content
98
98
  }
99
+ if (workflowData.project_id !== undefined) {
100
+ workflows[index].project_id = workflowData.project_id
101
+ }
99
102
  } else {
100
103
  workflows.push({
101
104
  id: crypto.randomUUID(),
102
105
  name: workflowData.name,
103
106
  pipeline_skill_names: workflowData.pipeline_skill_names,
104
107
  content: workflowData.content || '',
108
+ project_id: workflowData.project_id || null,
105
109
  cron_expression: '',
106
110
  is_active: false,
107
111
  last_run: null,