kanbango 2.0.0 → 2.4.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/AGENTS.md +7 -7
- package/API.md +20 -12
- package/CHANGELOG.md +40 -0
- package/LLM_AGENTS.md +141 -175
- package/README.md +78 -266
- package/bin/kanban.js +101 -26
- package/index.html +162 -85
- package/index.js +2 -0
- package/kan2.md +76 -0
- package/kanban.js +284 -35
- package/mcp-server.js +393 -184
- package/package.json +1 -1
- package/plan.js +159 -0
- package/planv2.md +17 -7
- package/tests/run.js +1 -0
package/README.md
CHANGED
|
@@ -1,267 +1,92 @@
|
|
|
1
1
|
# kanbango
|
|
2
2
|
|
|
3
|
-
JSON-first local Kanban board
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- 📊 Local Kanban board stored in JSON task files
|
|
8
|
-
- 🎨 Modern web GUI with drag-and-drop interface
|
|
9
|
-
- 🖥️ Full CLI support for automation and CI/CD
|
|
10
|
-
- 🤖 AI-friendly API (JSON output)
|
|
11
|
-
- 📁 Four columns: Active, Planned, Icebox, Done
|
|
12
|
-
- ✅ Subtasks with progress tracking
|
|
13
|
-
- 🏷️ Epic grouping
|
|
3
|
+
JSON-first local Kanban board for developers and AI agents — CLI, web GUI, and MCP server in one lightweight package.
|
|
14
4
|
|
|
15
5
|
## Installation
|
|
16
6
|
|
|
17
|
-
### Global (recommended)
|
|
7
|
+
### Global (recommended for CLI)
|
|
18
8
|
```bash
|
|
19
9
|
npm install -g kanbango
|
|
20
10
|
```
|
|
21
11
|
|
|
22
|
-
### Local
|
|
12
|
+
### Local per project (recommended for MCP)
|
|
23
13
|
```bash
|
|
24
14
|
npm install -D kanbango
|
|
25
15
|
```
|
|
26
16
|
|
|
27
|
-
###
|
|
17
|
+
### No install
|
|
28
18
|
```bash
|
|
29
19
|
npx kanbango --help
|
|
30
20
|
```
|
|
31
21
|
|
|
32
|
-
## NPM Package
|
|
33
|
-
|
|
34
|
-
- Package name: `kanbango`
|
|
35
|
-
- Binaries: `kanban`, `kanban-cmd`
|
|
36
|
-
- MCP server entrypoint: `mcp-server.js` (run via `npx kanbango mcp`)
|
|
37
|
-
|
|
38
|
-
## Requirements
|
|
39
|
-
|
|
40
|
-
- Node.js 16+
|
|
41
|
-
|
|
42
22
|
## Quick Start
|
|
43
23
|
|
|
44
24
|
```bash
|
|
45
|
-
# Initialize backlog
|
|
25
|
+
# Initialize backlog directories
|
|
46
26
|
kanban init
|
|
47
27
|
|
|
48
|
-
# Start web GUI (
|
|
28
|
+
# Start web GUI (stable project port; prints the real URL)
|
|
49
29
|
kanban serve
|
|
50
30
|
|
|
51
|
-
# List all tasks
|
|
31
|
+
# List all tasks
|
|
52
32
|
kanban list --json
|
|
53
33
|
|
|
54
|
-
# Add a
|
|
55
|
-
kanban add "
|
|
34
|
+
# Add a task
|
|
35
|
+
kanban add "My task" --col planned --epic "Phase 1"
|
|
56
36
|
|
|
57
|
-
# Show
|
|
58
|
-
kanban show
|
|
37
|
+
# Show details
|
|
38
|
+
kanban show 001
|
|
59
39
|
|
|
60
|
-
# Move
|
|
61
|
-
kanban move
|
|
40
|
+
# Move between columns (active | planned | icebox | done)
|
|
41
|
+
kanban move 001 active
|
|
62
42
|
|
|
63
|
-
#
|
|
64
|
-
kanban
|
|
43
|
+
# Update subtasks in one call
|
|
44
|
+
kanban update 001 '{"subtasks":[{"done":true,"text":"Research"},{"done":false,"text":"Implementation"}]}'
|
|
65
45
|
```
|
|
66
46
|
|
|
67
|
-
|
|
47
|
+
### Columns
|
|
68
48
|
|
|
69
|
-
|
|
|
70
|
-
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `kanban show <ID>` | Show task details |
|
|
76
|
-
| `kanban add <TITLE>` | Add new task |
|
|
77
|
-
| `kanban move <ID> <COL>` | Move task to column |
|
|
78
|
-
| `kanban toggle <ID> <IDX>` | Toggle subtask |
|
|
79
|
-
|
|
80
|
-
## Columns
|
|
81
|
-
|
|
82
|
-
- `active` — In progress (max 1-2 tasks)
|
|
83
|
-
- `planned` — Planned for implementation
|
|
84
|
-
- `icebox` — Frozen / nice-to-have
|
|
85
|
-
- `done` — Completed
|
|
49
|
+
| Column | Purpose |
|
|
50
|
+
|--------|---------|
|
|
51
|
+
| `active` | In progress (keep to 1–2 tasks) |
|
|
52
|
+
| `planned` | Ready to implement |
|
|
53
|
+
| `icebox` | Nice-to-have / frozen |
|
|
54
|
+
| `done` | Completed |
|
|
86
55
|
|
|
87
56
|
## Data Structure
|
|
88
57
|
|
|
89
|
-
Tasks are
|
|
58
|
+
Tasks are JSON files in `backlog/<column>/`:
|
|
90
59
|
|
|
91
60
|
```json
|
|
92
61
|
{
|
|
93
|
-
"id": "
|
|
94
|
-
"title": "Feature
|
|
62
|
+
"id": "001",
|
|
63
|
+
"title": "My Feature",
|
|
95
64
|
"column": "planned",
|
|
96
65
|
"epic_group": "Phase 1",
|
|
97
|
-
"created": "2026-07-
|
|
98
|
-
"description": "High-level context
|
|
99
|
-
"specs": "Technical details
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
],
|
|
66
|
+
"created": "2026-07-08",
|
|
67
|
+
"description": "High-level context.",
|
|
68
|
+
"specs": "Technical details.",
|
|
69
|
+
"in_scope": ["What this task covers"],
|
|
70
|
+
"out_of_scope": ["What is explicitly excluded"],
|
|
71
|
+
"acceptance_criteria": ["Works as expected"],
|
|
72
|
+
"test_cases": ["Verify the happy path"],
|
|
104
73
|
"subtasks": [
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
"text": "First subtask",
|
|
108
|
-
"done": false,
|
|
109
|
-
"description": "Execution details for the subtask"
|
|
110
|
-
}
|
|
111
|
-
],
|
|
112
|
-
"notes": "Optional freeform notes"
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## AI Integration
|
|
117
|
-
|
|
118
|
-
The `kanban-cmd` command provides simplified JSON output for AI agents:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
# For AI: always use JSON output
|
|
122
|
-
kanban-cmd list
|
|
123
|
-
|
|
124
|
-
# Filter by column
|
|
125
|
-
kanban-cmd list --col active
|
|
126
|
-
|
|
127
|
-
# Show task details
|
|
128
|
-
kanban-cmd show PI-001
|
|
129
|
-
|
|
130
|
-
# Add task (AI-friendly)
|
|
131
|
-
kanban-cmd add "New task" --col planned --epic "Phase 1"
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### JSON Output Format
|
|
135
|
-
|
|
136
|
-
```json
|
|
137
|
-
[
|
|
138
|
-
{
|
|
139
|
-
"id": "PI-001-feature-title",
|
|
140
|
-
"title": "Feature Title",
|
|
141
|
-
"column": "planned",
|
|
142
|
-
"epic_group": "Phase 1",
|
|
143
|
-
"created": "2026-07-07",
|
|
144
|
-
"description": "High-level context and implementation plan.",
|
|
145
|
-
"specs": "Technical details and constraints.",
|
|
146
|
-
"acceptance_criteria": [
|
|
147
|
-
"First condition"
|
|
148
|
-
],
|
|
149
|
-
"tasks": [
|
|
150
|
-
{
|
|
151
|
-
"id": "st-1",
|
|
152
|
-
"done": false,
|
|
153
|
-
"text": "First subtask",
|
|
154
|
-
"description": "Execution details for the subtask"
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
}
|
|
158
|
-
]
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## MCP Task Views
|
|
162
|
-
|
|
163
|
-
`kanban_read` supports compact response shaping so agents can avoid reading the whole task every time.
|
|
164
|
-
|
|
165
|
-
```json
|
|
166
|
-
{
|
|
167
|
-
"operation": "show",
|
|
168
|
-
"task_id": "PI-001-feature-title",
|
|
169
|
-
"view": "planning"
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Supported views:
|
|
174
|
-
|
|
175
|
-
- `summary` - `id`, `title`, `column`, `epic_group`, `created`, `progress`
|
|
176
|
-
- `planning` - summary + `description`, `specs`, `acceptance_criteria`
|
|
177
|
-
- `execution` - planning + `subtasks`
|
|
178
|
-
- `full` - execution + `notes`
|
|
179
|
-
|
|
180
|
-
You can also request explicit fields:
|
|
181
|
-
|
|
182
|
-
```json
|
|
183
|
-
{
|
|
184
|
-
"operation": "show",
|
|
185
|
-
"task_id": "PI-001-feature-title",
|
|
186
|
-
"fields": ["title", "description", "acceptance_criteria"]
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
`kanban_update` supports patch-style updates and compact responses:
|
|
191
|
-
|
|
192
|
-
```json
|
|
193
|
-
{
|
|
194
|
-
"operation": "update",
|
|
195
|
-
"task_id": "PI-001-feature-title",
|
|
196
|
-
"patch": {
|
|
197
|
-
"description": "Updated plan",
|
|
198
|
-
"acceptance_criteria": ["OAuth works"]
|
|
199
|
-
},
|
|
200
|
-
"return": "summary"
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
Errors are returned as structured JSON:
|
|
205
|
-
|
|
206
|
-
```json
|
|
207
|
-
{
|
|
208
|
-
"error": {
|
|
209
|
-
"code": "TASK_NOT_FOUND",
|
|
210
|
-
"message": "Task PI-999-missing was not found",
|
|
211
|
-
"hint": "Call kanban_read with operation=list to discover valid task ids",
|
|
212
|
-
"details": {
|
|
213
|
-
"task_id": "PI-999-missing"
|
|
214
|
-
},
|
|
215
|
-
"retryable": false
|
|
216
|
-
}
|
|
74
|
+
{ "id": "st-1", "text": "First step", "done": false }
|
|
75
|
+
]
|
|
217
76
|
}
|
|
218
77
|
```
|
|
219
78
|
|
|
220
|
-
##
|
|
221
|
-
|
|
222
|
-
Start the web interface:
|
|
223
|
-
```bash
|
|
224
|
-
kanban serve 5500
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Features:
|
|
228
|
-
- Swimlanes grouped by epic
|
|
229
|
-
- Drag-and-drop between columns
|
|
230
|
-
- Inline editing
|
|
231
|
-
- Real-time subtask checkboxes
|
|
232
|
-
- Progress tracking
|
|
233
|
-
|
|
234
|
-
## Directory Structure
|
|
235
|
-
|
|
236
|
-
```
|
|
237
|
-
backlog/
|
|
238
|
-
├── active/ # Tasks in progress
|
|
239
|
-
├── planned/ # Planned tasks
|
|
240
|
-
├── icebox/ # Frozen tasks
|
|
241
|
-
└── done/ # Completed tasks
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## API for AI Agents
|
|
245
|
-
|
|
246
|
-
See [API.md](API.md) for detailed API function definitions.
|
|
247
|
-
|
|
248
|
-
## MCP Server
|
|
79
|
+
## MCP Server (for Claude Code, OpenCode, Cursor)
|
|
249
80
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
### Using the MCP server
|
|
81
|
+
Run the MCP server to let AI agents read / create / update your board:
|
|
253
82
|
|
|
254
83
|
```bash
|
|
255
|
-
# Run the MCP server directly
|
|
256
|
-
npm run mcp
|
|
257
|
-
|
|
258
|
-
# Or using npx
|
|
259
84
|
npx kanbango mcp
|
|
260
85
|
```
|
|
261
86
|
|
|
262
|
-
###
|
|
87
|
+
### Give it to your agent
|
|
263
88
|
|
|
264
|
-
|
|
89
|
+
Add this to your MCP client config (`.mcp.json`, `opencode.json`, or Claude Desktop config):
|
|
265
90
|
|
|
266
91
|
```json
|
|
267
92
|
{
|
|
@@ -274,87 +99,74 @@ For MCP clients, add this to your configuration:
|
|
|
274
99
|
}
|
|
275
100
|
```
|
|
276
101
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Install locally in the project so each repo controls its own MCP version:
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
npm install -D kanbango
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Then point MCP to the local package:
|
|
286
|
-
|
|
287
|
-
```json
|
|
288
|
-
{
|
|
289
|
-
"mcpServers": {
|
|
290
|
-
"kanbango": {
|
|
291
|
-
"command": "node",
|
|
292
|
-
"args": ["./node_modules/kanbango/mcp-server.js"]
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
If you prefer `npx`, you can still use it, but versioning is less explicit:
|
|
102
|
+
Auto-start the web GUI with MCP (opt-in):
|
|
299
103
|
|
|
300
104
|
```json
|
|
301
105
|
{
|
|
302
106
|
"mcpServers": {
|
|
303
107
|
"kanbango": {
|
|
304
108
|
"command": "npx",
|
|
305
|
-
"args": ["kanbango", "mcp"]
|
|
109
|
+
"args": ["kanbango", "mcp"],
|
|
110
|
+
"env": {
|
|
111
|
+
"KANBANGO_AUTO_GUI": "1"
|
|
112
|
+
}
|
|
306
113
|
}
|
|
307
114
|
}
|
|
308
115
|
}
|
|
309
116
|
```
|
|
310
117
|
|
|
311
|
-
|
|
118
|
+
Optional: pin the port with `KANBANGO_GUI_PORT` (e.g. `"5821"`). Without it, each project gets a stable port in `5510–5999` derived from the project path. The real URL is always available via `kanban_gui` → `status` (and written to `backlog/.kanbango-gui.json` while the GUI runs).
|
|
312
119
|
|
|
313
|
-
|
|
120
|
+
Or generate the config files automatically:
|
|
314
121
|
|
|
315
122
|
```bash
|
|
316
|
-
# Local install (recommended)
|
|
317
123
|
npx kanbango mcp-init
|
|
124
|
+
```
|
|
318
125
|
|
|
319
|
-
|
|
320
|
-
npx kanbango mcp-init --npx
|
|
126
|
+
### What the agent can do
|
|
321
127
|
|
|
322
|
-
|
|
323
|
-
npx kanbango mcp-init --claude
|
|
128
|
+
Once connected, your agent gets access to these tools:
|
|
324
129
|
|
|
325
|
-
|
|
326
|
-
|
|
130
|
+
| Tool | What it does |
|
|
131
|
+
|------|-------------|
|
|
132
|
+
| `kanban_read` | List tasks, filter by column/epic, show details |
|
|
133
|
+
| `kanban_manage` | Create, move, patch-update tasks |
|
|
134
|
+
| `kanban_gui` | Start, stop, or check web GUI status (returns the real URL/port) |
|
|
327
135
|
|
|
328
|
-
|
|
329
|
-
npx kanbango mcp-init --force
|
|
330
|
-
```
|
|
136
|
+
Your agent stays in sync with your real board — every change is persisted as JSON files.
|
|
331
137
|
|
|
332
|
-
|
|
138
|
+
## CLI Reference
|
|
333
139
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
140
|
+
| Command | Description |
|
|
141
|
+
|---------|-------------|
|
|
142
|
+
| `kanban init` | Create backlog directory structure |
|
|
143
|
+
| `kanban serve [PORT]` | Start web GUI (stable project port, or PORT / KANBANGO_GUI_PORT) |
|
|
144
|
+
| `kanban list [--col <col>] [--json]` | List tasks |
|
|
145
|
+
| `kanban show <ID>` | Show task details |
|
|
146
|
+
| `kanban add <TITLE>` | Add a new task |
|
|
147
|
+
| `kanban move <ID> <COL>` | Move task |
|
|
148
|
+
| `kanban mcp-init` | Generate MCP config files |
|
|
340
149
|
|
|
341
|
-
##
|
|
150
|
+
## Web GUI
|
|
342
151
|
|
|
343
152
|
```bash
|
|
344
|
-
|
|
345
|
-
|
|
153
|
+
kanban serve
|
|
154
|
+
```
|
|
155
|
+
- Swimlanes grouped by epic
|
|
156
|
+
- Drag-and-drop between columns
|
|
157
|
+
- Inline editing and subtask checkboxes
|
|
346
158
|
|
|
347
|
-
|
|
348
|
-
npm test
|
|
159
|
+
## Using as a Node.js module
|
|
349
160
|
|
|
350
|
-
|
|
351
|
-
|
|
161
|
+
```js
|
|
162
|
+
const kanban = require('kanbango');
|
|
163
|
+
const tasks = await kanban.allEpics();
|
|
352
164
|
```
|
|
353
165
|
|
|
354
|
-
##
|
|
166
|
+
## Requirements
|
|
355
167
|
|
|
356
|
-
|
|
168
|
+
Node.js 16+
|
|
357
169
|
|
|
358
|
-
##
|
|
170
|
+
## License
|
|
359
171
|
|
|
360
|
-
|
|
172
|
+
MIT
|
package/bin/kanban.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const kanban = require('../kanban.js');
|
|
4
|
+
const plan = require('../plan.js');
|
|
4
5
|
const http = require('http');
|
|
5
6
|
const fs = require('fs');
|
|
6
7
|
const path = require('path');
|
|
@@ -9,7 +10,7 @@ const BACKLOG = path.join(process.cwd(), 'backlog');
|
|
|
9
10
|
const COLS = kanban.COLS;
|
|
10
11
|
|
|
11
12
|
function shortId(taskId) {
|
|
12
|
-
const match = taskId.match(/^(
|
|
13
|
+
const match = taskId.match(/^(?:[A-Z]+-)?(\d+)/);
|
|
13
14
|
return match ? match[1] : taskId;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -222,21 +223,6 @@ async function cliAdd(title, column, epicGroup) {
|
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
async function cliToggle(taskId, idx) {
|
|
226
|
-
const success = await kanban.doToggle(taskId, idx);
|
|
227
|
-
if (!success) {
|
|
228
|
-
console.error(`✗ Nie znaleziono: ${taskId} subtask ${idx}`);
|
|
229
|
-
process.exit(1);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const task = await kanban.getTask(taskId);
|
|
233
|
-
if (idx < task.subtasks.length) {
|
|
234
|
-
const subtask = task.subtasks[idx];
|
|
235
|
-
const mark = subtask.done ? '✓' : '○';
|
|
236
|
-
console.log(` [${idx}] ${mark} ${subtask.text}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
226
|
async function serveWeb(port) {
|
|
241
227
|
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf-8');
|
|
242
228
|
|
|
@@ -262,7 +248,10 @@ async function serveWeb(port) {
|
|
|
262
248
|
const task = await kanban.doCreate(body.title || '', body.column || 'planned', body.epic_group || '—', {
|
|
263
249
|
description: body.description,
|
|
264
250
|
specs: body.specs,
|
|
251
|
+
in_scope: body.in_scope,
|
|
252
|
+
out_of_scope: body.out_of_scope,
|
|
265
253
|
acceptance_criteria: body.acceptance_criteria,
|
|
254
|
+
test_cases: body.test_cases,
|
|
266
255
|
subtasks: body.subtasks,
|
|
267
256
|
notes: body.notes
|
|
268
257
|
});
|
|
@@ -312,10 +301,12 @@ async function serveWeb(port) {
|
|
|
312
301
|
const patch = body.patch ? { ...body.patch } : {};
|
|
313
302
|
|
|
314
303
|
if (body.title !== undefined) patch.title = body.title;
|
|
315
|
-
if (body.tasks !== undefined) patch.subtasks = body.tasks;
|
|
316
304
|
if (body.description !== undefined) patch.description = body.description;
|
|
317
305
|
if (body.specs !== undefined) patch.specs = body.specs;
|
|
306
|
+
if (body.in_scope !== undefined) patch.in_scope = body.in_scope;
|
|
307
|
+
if (body.out_of_scope !== undefined) patch.out_of_scope = body.out_of_scope;
|
|
318
308
|
if (body.acceptance_criteria !== undefined) patch.acceptance_criteria = body.acceptance_criteria;
|
|
309
|
+
if (body.test_cases !== undefined) patch.test_cases = body.test_cases;
|
|
319
310
|
if (body.subtasks !== undefined) patch.subtasks = body.subtasks;
|
|
320
311
|
if (body.notes !== undefined) patch.notes = body.notes;
|
|
321
312
|
if (body.epic_group !== undefined) patch.epic_group = body.epic_group;
|
|
@@ -332,11 +323,66 @@ async function serveWeb(port) {
|
|
|
332
323
|
}
|
|
333
324
|
});
|
|
334
325
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
326
|
+
const MAX_ATTEMPTS = 10;
|
|
327
|
+
|
|
328
|
+
function listenOnce(server, port) {
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
function onError(err) {
|
|
331
|
+
server.removeListener('listening', onListening);
|
|
332
|
+
reject(err);
|
|
333
|
+
}
|
|
334
|
+
function onListening() {
|
|
335
|
+
server.removeListener('error', onError);
|
|
336
|
+
resolve();
|
|
337
|
+
}
|
|
338
|
+
server.once('error', onError);
|
|
339
|
+
server.once('listening', onListening);
|
|
340
|
+
server.listen(port, 'localhost');
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
345
|
+
try {
|
|
346
|
+
await listenOnce(server, port + attempt);
|
|
347
|
+
break;
|
|
348
|
+
} catch (err) {
|
|
349
|
+
if (err.code !== 'EADDRINUSE') throw err;
|
|
350
|
+
console.log(`Port ${port + attempt} zajęty, próbuję ${port + attempt + 1}…`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!server.listening) {
|
|
355
|
+
console.log(`Porty ${port}–${port + MAX_ATTEMPTS - 1} zajęte, próbuję losowy port…`);
|
|
356
|
+
await listenOnce(server, 0);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const actualPort = server.address().port;
|
|
360
|
+
const portInfo = await kanban.writeGuiPortFile({ port: actualPort, pid: process.pid });
|
|
361
|
+
|
|
362
|
+
async function cleanupGuiPortFile() {
|
|
363
|
+
try {
|
|
364
|
+
await kanban.clearGuiPortFile({ pid: process.pid });
|
|
365
|
+
} catch {
|
|
366
|
+
// best-effort cleanup
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
server.on('close', () => {
|
|
371
|
+
cleanupGuiPortFile();
|
|
339
372
|
});
|
|
373
|
+
|
|
374
|
+
process.once('SIGINT', async () => {
|
|
375
|
+
await cleanupGuiPortFile();
|
|
376
|
+
server.close(() => process.exit(0));
|
|
377
|
+
});
|
|
378
|
+
process.once('SIGTERM', async () => {
|
|
379
|
+
await cleanupGuiPortFile();
|
|
380
|
+
server.close(() => process.exit(0));
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
console.log(`\x1b[1;32m→ Kanban GUI: ${portInfo.url}\x1b[0m`);
|
|
384
|
+
console.log(` Backlog: ${BACKLOG}`);
|
|
385
|
+
console.log(' Ctrl+C żeby zamknąć');
|
|
340
386
|
}
|
|
341
387
|
|
|
342
388
|
function readBody(req) {
|
|
@@ -379,13 +425,42 @@ async function cliMigrate(dryRun) {
|
|
|
379
425
|
}
|
|
380
426
|
}
|
|
381
427
|
|
|
428
|
+
function parseJsonPayload(value) {
|
|
429
|
+
try {
|
|
430
|
+
return JSON.parse(value || '{}');
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw kanban.createKanbanError('INVALID_JSON', 'Payload is not valid JSON',
|
|
433
|
+
'Pass a JSON object after --json', { reason: error.message }, false, 400);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function cliPlan(action, payload) {
|
|
438
|
+
try {
|
|
439
|
+
const handlers = {
|
|
440
|
+
create: plan.create,
|
|
441
|
+
advance: plan.advance,
|
|
442
|
+
evidence: plan.evidence,
|
|
443
|
+
done: plan.done,
|
|
444
|
+
status: (input) => plan.status(input.task_id)
|
|
445
|
+
};
|
|
446
|
+
if (!handlers[action]) throw kanban.createKanbanError('INVALID_PLAN_ACTION', `Unknown plan action: ${action}`,
|
|
447
|
+
'Use create, advance, evidence, done, or status', { action }, false, 400);
|
|
448
|
+
console.log(JSON.stringify(await handlers[action](payload)));
|
|
449
|
+
} catch (error) {
|
|
450
|
+
console.log(JSON.stringify({ ok: false, task_id: payload.task_id || null, subtasks: [], error: {
|
|
451
|
+
code: error.code || 'INTERNAL_ERROR', message: error.message, hint: error.hint || '', details: error.details || {}
|
|
452
|
+
} }));
|
|
453
|
+
process.exitCode = 1;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
382
457
|
async function main() {
|
|
383
458
|
const args = process.argv.slice(2);
|
|
384
459
|
const cmd = args[0];
|
|
385
460
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
461
|
+
if (!cmd || cmd === 'serve') {
|
|
462
|
+
const port = kanban.resolvePreferredGuiPort(args[1]);
|
|
463
|
+
await serveWeb(port);
|
|
389
464
|
} else if (cmd === 'init') {
|
|
390
465
|
await cliInit();
|
|
391
466
|
} else if (cmd === 'mcp-init') {
|
|
@@ -448,8 +523,8 @@ async function main() {
|
|
|
448
523
|
}
|
|
449
524
|
|
|
450
525
|
await cliAdd(args[1], column, epicGroup);
|
|
451
|
-
} else if (cmd === '
|
|
452
|
-
await
|
|
526
|
+
} else if (cmd === 'plan' && args[1] && args[2] === '--json') {
|
|
527
|
+
await cliPlan(args[1], parseJsonPayload(args[3]));
|
|
453
528
|
} else {
|
|
454
529
|
console.error('Unknown command:', cmd);
|
|
455
530
|
process.exit(1);
|