@bosun-sh/logbook 1.0.0 → 1.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/README.md +53 -3
- package/package.json +3 -2
- package/src/cli/cli.ts +489 -0
- package/src/cli/init.ts +157 -17
- package/src/infra/layer.ts +34 -0
- package/src/mcp/server.ts +6 -23
package/README.md
CHANGED
|
@@ -34,7 +34,44 @@ logbook is a file-system based kanban board that uses jsonl files to enter one t
|
|
|
34
34
|
- the agent can call `create_task(input)` to open a new task in `backlog`, passing `predictedKTokens` so the server derives a Fibonacci estimation automatically
|
|
35
35
|
- the agent can call `edit_task(id, updates)` to change mutable fields without altering status
|
|
36
36
|
|
|
37
|
-
each one of these tools has the sole purpose of removing overload from the agent context, handling the _"heavy load"_ programmatically on the MCP server.
|
|
37
|
+
each one of these tools has the sole purpose of removing overload from the agent context, handling the _"heavy load"_ programmatically on the MCP server or CLI.
|
|
38
|
+
|
|
39
|
+
### cli
|
|
40
|
+
|
|
41
|
+
in addition to the MCP server, logbook provides a CLI for direct command-line usage:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# create a task
|
|
45
|
+
logbook create-task --project myproject --milestone v1 --title "Fix bug" \
|
|
46
|
+
--definition-of-done "Bug fixed and tested" --description "Details..." \
|
|
47
|
+
--predicted-k-tokens 3
|
|
48
|
+
|
|
49
|
+
# list tasks
|
|
50
|
+
logbook list-tasks --status in_progress
|
|
51
|
+
logbook list-tasks --status "*"
|
|
52
|
+
|
|
53
|
+
# get current task
|
|
54
|
+
logbook current-task
|
|
55
|
+
|
|
56
|
+
# update task status
|
|
57
|
+
logbook update-task --id <uuid> --new-status in_progress
|
|
58
|
+
|
|
59
|
+
# edit task
|
|
60
|
+
logbook edit-task --id <uuid> --title "New title"
|
|
61
|
+
|
|
62
|
+
# initialize project
|
|
63
|
+
logbook init
|
|
64
|
+
logbook init --force # ensures both AGENTS.md and CLAUDE.md exist
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
all commands output JSON to stdout for easy parsing:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{"ok": true, "task": {...}}
|
|
71
|
+
{"ok": false, "error": {"code": -32001, "message": "Task not found", ...}}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
see `logbook --help` for full documentation.
|
|
38
75
|
|
|
39
76
|
## walkthrough
|
|
40
77
|
|
|
@@ -278,6 +315,8 @@ requires **bun ≥ 1.0.0** as the runtime ([install bun](https://bun.sh)).
|
|
|
278
315
|
verify the installation:
|
|
279
316
|
|
|
280
317
|
```bash
|
|
318
|
+
logbook --version
|
|
319
|
+
# or
|
|
281
320
|
logbook-mcp --version
|
|
282
321
|
```
|
|
283
322
|
|
|
@@ -287,7 +326,17 @@ for a full onboarding walkthrough see [quickstart.md](quickstart.md).
|
|
|
287
326
|
|
|
288
327
|
### quick setup
|
|
289
328
|
|
|
290
|
-
run `logbook
|
|
329
|
+
run `logbook init` in your project directory to scaffold:
|
|
330
|
+
- `tasks.jsonl` — the task store
|
|
331
|
+
- `hooks/` — directory for custom hooks
|
|
332
|
+
- `AGENTS.md` and `CLAUDE.md` — documentation for AI agents
|
|
333
|
+
|
|
334
|
+
by default:
|
|
335
|
+
- if neither AGENTS.md nor CLAUDE.md exists → creates AGENTS.md and symlinks CLAUDE.md to it
|
|
336
|
+
- if only AGENTS.md exists → appends logbook docs to AGENTS.md
|
|
337
|
+
- if only CLAUDE.md exists → appends logbook docs to CLAUDE.md
|
|
338
|
+
|
|
339
|
+
use `logbook init --force` to ensure both files exist (appends to existing, creates/symlinks missing).
|
|
291
340
|
|
|
292
341
|
### environment variables
|
|
293
342
|
|
|
@@ -299,11 +348,12 @@ run `logbook-mcp init` in your project directory to scaffold `tasks.jsonl`, `hoo
|
|
|
299
348
|
|
|
300
349
|
### gitignore
|
|
301
350
|
|
|
302
|
-
`tasks.jsonl
|
|
351
|
+
`tasks.jsonl`, `sessions.json`, and `.logbook-session` are runtime files generated by logbook — they should not be committed to version control:
|
|
303
352
|
|
|
304
353
|
```gitignore
|
|
305
354
|
tasks.jsonl
|
|
306
355
|
sessions.json
|
|
356
|
+
.logbook-session
|
|
307
357
|
```
|
|
308
358
|
|
|
309
359
|
> note: the logbook repo itself intentionally commits these files for dogfooding — that is the exception, not the rule.
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bosun-sh/logbook",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "File-system kanban board MCP server for AI agents",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "File-system kanban board CLI and MCP server for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
+
"logbook": "src/cli/cli.ts",
|
|
7
8
|
"logbook-mcp": "src/mcp/server.ts"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
package/src/cli/cli.ts
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
4
|
+
import { Effect } from "effect"
|
|
5
|
+
import { createLayer, type LayerConfig } from "../infra/layer.js"
|
|
6
|
+
import { taskErrorToMcpError } from "../mcp/error-codes.js"
|
|
7
|
+
import { newSessionId } from "../mcp/session.js"
|
|
8
|
+
import { toolCreateTask } from "../mcp/tool-create-task.js"
|
|
9
|
+
import { toolCurrentTask } from "../mcp/tool-current-task.js"
|
|
10
|
+
import { toolEditTask } from "../mcp/tool-edit-task.js"
|
|
11
|
+
import { toolListTasks } from "../mcp/tool-list-tasks.js"
|
|
12
|
+
import { toolUpdateTask } from "../mcp/tool-update-task.js"
|
|
13
|
+
import { runInit } from "./init.js"
|
|
14
|
+
|
|
15
|
+
const DEFAULT_TASKS_FILE = "./tasks.jsonl"
|
|
16
|
+
const DEFAULT_HOOKS_DIR = "./hooks"
|
|
17
|
+
const SESSION_FILE = ".logbook-session"
|
|
18
|
+
|
|
19
|
+
const helpText = `# logbook - File-system kanban board CLI
|
|
20
|
+
|
|
21
|
+
## SYNOPSIS
|
|
22
|
+
|
|
23
|
+
logbook <command> [options]
|
|
24
|
+
|
|
25
|
+
## DESCRIPTION
|
|
26
|
+
|
|
27
|
+
Logbook is a file-system kanban board for AI agents. It tracks tasks across a structured
|
|
28
|
+
lifecycle so agents and humans share a single source of truth without context bloat.
|
|
29
|
+
|
|
30
|
+
## COMMANDS
|
|
31
|
+
|
|
32
|
+
### logbook create-task
|
|
33
|
+
|
|
34
|
+
Create a new task in backlog. The task is assigned a unique ID and estimated Fibonacci
|
|
35
|
+
number based on the predicted context size (predictedKTokens).
|
|
36
|
+
|
|
37
|
+
Required arguments:
|
|
38
|
+
--project <name> Project name (e.g., "myproject")
|
|
39
|
+
--milestone <name> Milestone name (e.g., "v1")
|
|
40
|
+
--title <text> Task title
|
|
41
|
+
--definition-of-done <text> What "done" means for this task
|
|
42
|
+
--description <text> Detailed description
|
|
43
|
+
--predicted-k-tokens <n> Estimated context size in thousands of tokens (drives model selection)
|
|
44
|
+
|
|
45
|
+
Optional arguments:
|
|
46
|
+
--priority <n> Priority (higher = more urgent, default: 0)
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
logbook create-task --project myproject --milestone v1 --title "Fix bug" \\
|
|
50
|
+
--definition-of-done "Bug fixed and tested" --description "Details..." \\
|
|
51
|
+
--predicted-k-tokens 3
|
|
52
|
+
|
|
53
|
+
### logbook list-tasks
|
|
54
|
+
|
|
55
|
+
List tasks, optionally filtered by status, project, or milestone.
|
|
56
|
+
|
|
57
|
+
Optional arguments:
|
|
58
|
+
--status <status> Filter by status (default: "in_progress")
|
|
59
|
+
Valid values: backlog, todo, need_info, blocked, in_progress,
|
|
60
|
+
pending_review, done, or "*" for all
|
|
61
|
+
--project <name> Filter by project name
|
|
62
|
+
--milestone <name> Filter by milestone name
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
logbook list-tasks
|
|
66
|
+
logbook list-tasks --status "*"
|
|
67
|
+
logbook list-tasks --status todo --project myproject
|
|
68
|
+
|
|
69
|
+
### logbook current-task
|
|
70
|
+
|
|
71
|
+
Return the highest-priority in_progress task for this session. If no task is assigned
|
|
72
|
+
to this session, it will claim an unassigned task or transition a todo task to in_progress.
|
|
73
|
+
|
|
74
|
+
This command automatically claims a task if none is currently assigned to the session.
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
logbook current-task
|
|
78
|
+
|
|
79
|
+
### logbook update-task
|
|
80
|
+
|
|
81
|
+
Transition a task's status. Some transitions require a comment.
|
|
82
|
+
|
|
83
|
+
Required arguments:
|
|
84
|
+
--id <uuid> Task ID
|
|
85
|
+
--new-status <status> Target status
|
|
86
|
+
|
|
87
|
+
Optional arguments:
|
|
88
|
+
--comment-title <text> Comment title
|
|
89
|
+
--comment-content <text> Comment body
|
|
90
|
+
--comment-kind <kind> Comment type: "regular" or "need_info" (default: regular)
|
|
91
|
+
|
|
92
|
+
Status transitions require a comment in these cases:
|
|
93
|
+
- Transitioning to need_info (must document what info is needed)
|
|
94
|
+
- Transitioning to blocked (must document why blocked)
|
|
95
|
+
- Moving a second task to in_progress (must explain priority)
|
|
96
|
+
- Transitioning to pending_review (should document review request)
|
|
97
|
+
|
|
98
|
+
Use --comment-title and --comment-content together. To reply to a need_info comment,
|
|
99
|
+
include the original comment's ID via the MCP interface.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
logbook update-task --id <uuid> --new-status in_progress
|
|
103
|
+
logbook update-task --id <uuid> --new-status pending_review \\
|
|
104
|
+
--comment-title "Review please" --comment-content "Done!"
|
|
105
|
+
logbook update-task --id <uuid> --new-status need_info \\
|
|
106
|
+
--comment-title "Need info" --comment-content "What does X mean?"
|
|
107
|
+
|
|
108
|
+
### logbook edit-task
|
|
109
|
+
|
|
110
|
+
Edit mutable fields of a task without changing its status.
|
|
111
|
+
|
|
112
|
+
Required arguments:
|
|
113
|
+
--id <uuid> Task ID
|
|
114
|
+
|
|
115
|
+
Optional arguments:
|
|
116
|
+
--title <text> New title
|
|
117
|
+
--description <text> New description
|
|
118
|
+
--definition-of-done <text> New definition of done
|
|
119
|
+
--predicted-k-tokens <n> New predicted context size (will recalculate estimation)
|
|
120
|
+
--priority <n> New priority
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
logbook edit-task --id <uuid> --title "New title"
|
|
124
|
+
|
|
125
|
+
### logbook init
|
|
126
|
+
|
|
127
|
+
Initialize the project: create tasks.jsonl, hooks/, AGENTS.md, and CLAUDE.md if they
|
|
128
|
+
don't exist. If they exist, append logbook documentation to them.
|
|
129
|
+
|
|
130
|
+
By default:
|
|
131
|
+
- If neither AGENTS.md nor CLAUDE.md exists → creates AGENTS.md and symlinks CLAUDE.md to it
|
|
132
|
+
- If only AGENTS.md exists → appends to AGENTS.md
|
|
133
|
+
- If only CLAUDE.md exists → appends to CLAUDE.md
|
|
134
|
+
|
|
135
|
+
Optional arguments:
|
|
136
|
+
--force Force creation/sync of both files. Appends to existing,
|
|
137
|
+
creates/symlinks missing. Ensures both files exist.
|
|
138
|
+
|
|
139
|
+
This command scaffolds the basic structure needed to use logbook.
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
logbook init
|
|
143
|
+
logbook init --force
|
|
144
|
+
|
|
145
|
+
## TASK LIFECYCLE
|
|
146
|
+
|
|
147
|
+
backlog → todo → in_progress → pending_review → done
|
|
148
|
+
|
|
149
|
+
Side-exits from in_progress:
|
|
150
|
+
- need_info: task needs clarification (return to in_progress once resolved)
|
|
151
|
+
- blocked: task is blocked by external dependency (return to in_progress once resolved)
|
|
152
|
+
|
|
153
|
+
## ENVIRONMENT
|
|
154
|
+
|
|
155
|
+
LOGBOOK_TASKS_FILE Path to JSONL task store (default: ./tasks.jsonl)
|
|
156
|
+
LOGBOOK_HOOKS_DIR Directory for hook definitions (default: ./hooks)
|
|
157
|
+
LOGBOOK_SESSION_ID Session ID to use (auto-generated if not provided)
|
|
158
|
+
|
|
159
|
+
## GLOBAL OPTIONS
|
|
160
|
+
|
|
161
|
+
--tasks-file <path> Path to JSONL task store
|
|
162
|
+
--hooks-dir <path> Directory for hook definitions
|
|
163
|
+
--session <id> Session ID to use
|
|
164
|
+
--version, -v Print version
|
|
165
|
+
--help, -h Show this help
|
|
166
|
+
|
|
167
|
+
## OUTPUT FORMAT
|
|
168
|
+
|
|
169
|
+
All commands output JSON to stdout:
|
|
170
|
+
|
|
171
|
+
Success: { "ok": true, ...result }
|
|
172
|
+
Error: { "ok": false, "error": { "code": <n>, "message": <text>, ... } }
|
|
173
|
+
|
|
174
|
+
Exit code: 0 on success, 1 on error.
|
|
175
|
+
`
|
|
176
|
+
|
|
177
|
+
interface CliArgs {
|
|
178
|
+
tasksFile: string
|
|
179
|
+
hooksDir: string
|
|
180
|
+
sessionId: string | null
|
|
181
|
+
command: string | null
|
|
182
|
+
commandArgs: Record<string, string>
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const parseArgs = (): CliArgs => {
|
|
186
|
+
const args = process.argv.slice(2)
|
|
187
|
+
const result: CliArgs = {
|
|
188
|
+
tasksFile: process.env.LOGBOOK_TASKS_FILE ?? DEFAULT_TASKS_FILE,
|
|
189
|
+
hooksDir: process.env.LOGBOOK_HOOKS_DIR ?? DEFAULT_HOOKS_DIR,
|
|
190
|
+
sessionId: process.env.LOGBOOK_SESSION_ID ?? null,
|
|
191
|
+
command: null,
|
|
192
|
+
commandArgs: {},
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let i = 0
|
|
196
|
+
while (i < args.length) {
|
|
197
|
+
const arg = args[i]
|
|
198
|
+
if (arg === "--tasks-file" && i + 1 < args.length) {
|
|
199
|
+
result.tasksFile = args[i + 1] ?? ""
|
|
200
|
+
i += 2
|
|
201
|
+
} else if (arg === "--hooks-dir" && i + 1 < args.length) {
|
|
202
|
+
result.hooksDir = args[i + 1] ?? ""
|
|
203
|
+
i += 2
|
|
204
|
+
} else if (arg === "--session" && i + 1 < args.length) {
|
|
205
|
+
result.sessionId = args[i + 1] ?? null
|
|
206
|
+
i += 2
|
|
207
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
208
|
+
printVersion()
|
|
209
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
210
|
+
printHelp()
|
|
211
|
+
} else if (arg && !arg.startsWith("-")) {
|
|
212
|
+
result.command = arg
|
|
213
|
+
i++
|
|
214
|
+
while (i < args.length) {
|
|
215
|
+
const cmdArg = args[i]
|
|
216
|
+
if (cmdArg?.startsWith("-") && cmdArg.includes("=")) {
|
|
217
|
+
const [key, ...valueParts] = cmdArg.slice(2).split("=")
|
|
218
|
+
if (key) {
|
|
219
|
+
result.commandArgs[key] = valueParts.join("=")
|
|
220
|
+
}
|
|
221
|
+
i++
|
|
222
|
+
} else if (
|
|
223
|
+
cmdArg?.startsWith("-") &&
|
|
224
|
+
i + 1 < args.length &&
|
|
225
|
+
args[i + 1] &&
|
|
226
|
+
!args[i + 1]?.startsWith("-")
|
|
227
|
+
) {
|
|
228
|
+
result.commandArgs[cmdArg.slice(2)] = args[i + 1] ?? ""
|
|
229
|
+
i += 2
|
|
230
|
+
} else if (
|
|
231
|
+
cmdArg?.startsWith("-") &&
|
|
232
|
+
(i + 1 >= args.length || args[i + 1]?.startsWith("-"))
|
|
233
|
+
) {
|
|
234
|
+
result.commandArgs[cmdArg.slice(2)] = "true"
|
|
235
|
+
i++
|
|
236
|
+
} else {
|
|
237
|
+
i++
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
break
|
|
241
|
+
} else {
|
|
242
|
+
i++
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return result
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const printVersion = async (): Promise<void> => {
|
|
250
|
+
const pkg = await import("../../package.json", { with: { type: "json" } })
|
|
251
|
+
console.log(pkg.default.version)
|
|
252
|
+
process.exit(0)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const printHelp = (): void => {
|
|
256
|
+
console.log(helpText)
|
|
257
|
+
process.exit(0)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const getOrCreateSession = async (explicitSessionId: string | null): Promise<string> => {
|
|
261
|
+
if (explicitSessionId) {
|
|
262
|
+
return explicitSessionId
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
if (existsSync(SESSION_FILE)) {
|
|
266
|
+
const stored = await readFile(SESSION_FILE, "utf8")
|
|
267
|
+
const parsed = JSON.parse(stored)
|
|
268
|
+
if (parsed.sessionId && typeof parsed.sessionId === "string") {
|
|
269
|
+
return parsed.sessionId
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch {}
|
|
273
|
+
const newSession = newSessionId()
|
|
274
|
+
await writeFile(SESSION_FILE, JSON.stringify({ sessionId: newSession }), "utf8")
|
|
275
|
+
return newSession
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const _saveSession = async (sessionId: string): Promise<void> => {
|
|
279
|
+
await writeFile(SESSION_FILE, JSON.stringify({ sessionId }), "utf8")
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const output = (result: unknown): void => {
|
|
283
|
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const outputError = (error: unknown, _command?: string): void => {
|
|
287
|
+
let code = -32603
|
|
288
|
+
let message = "Internal error"
|
|
289
|
+
let hint = ""
|
|
290
|
+
let extra: Record<string, unknown> = {}
|
|
291
|
+
|
|
292
|
+
if (isTaskError(error)) {
|
|
293
|
+
const mcpErr = taskErrorToMcpError(error)
|
|
294
|
+
code = mcpErr.code
|
|
295
|
+
message = mcpErr.message
|
|
296
|
+
extra = mcpErr.data
|
|
297
|
+
|
|
298
|
+
const hints: string[] = []
|
|
299
|
+
|
|
300
|
+
switch (error._tag) {
|
|
301
|
+
case "not_found":
|
|
302
|
+
hints.push("Use: logbook list-tasks --status '*' to find available tasks")
|
|
303
|
+
hints.push("Use: logbook list-tasks --status <status> to list tasks by status")
|
|
304
|
+
break
|
|
305
|
+
case "transition_not_allowed":
|
|
306
|
+
if (error.from && error.to) {
|
|
307
|
+
hints.push(`Use: logbook update-task --id=${error.taskId} --new-status=<valid-status>`)
|
|
308
|
+
if (error.from === "backlog") {
|
|
309
|
+
hints.push("Note: Tasks must move: backlog → todo → in_progress")
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
break
|
|
313
|
+
case "validation_error":
|
|
314
|
+
if (error.message.includes("second task to in_progress")) {
|
|
315
|
+
hints.push(
|
|
316
|
+
"Use: logbook update-task --id=<uuid> --new-status=in_progress --comment-title='...' --comment-content='justification'"
|
|
317
|
+
)
|
|
318
|
+
} else if (error.message.includes("need_info") && error.message.includes("reply")) {
|
|
319
|
+
hints.push("Include a non-empty reply in your comment to proceed")
|
|
320
|
+
}
|
|
321
|
+
break
|
|
322
|
+
case "missing_comment":
|
|
323
|
+
hints.push(
|
|
324
|
+
"Use: logbook update-task --id=<uuid> --new-status=<status> --comment-title='...' --comment-content='...'"
|
|
325
|
+
)
|
|
326
|
+
break
|
|
327
|
+
case "no_current_task":
|
|
328
|
+
hints.push("Use: logbook list-tasks --status=todo to find available tasks")
|
|
329
|
+
hints.push("Use: logbook create-task ... to create a new task")
|
|
330
|
+
break
|
|
331
|
+
case "conflict":
|
|
332
|
+
hints.push(
|
|
333
|
+
"Use a different task ID or check existing tasks with: logbook list-tasks --status='*'"
|
|
334
|
+
)
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (hints.length > 0) {
|
|
339
|
+
hint = `\n\nHints:\n${hints.map((h) => ` - ${h}`).join("\n")}`
|
|
340
|
+
}
|
|
341
|
+
} else if (error instanceof Error) {
|
|
342
|
+
message = error.message
|
|
343
|
+
if (message.includes("Missing required")) {
|
|
344
|
+
hint = "\n\nRun: logbook <command> --help for usage information"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const fullMessage = message + hint
|
|
349
|
+
output({ ok: false, error: { code, message: fullMessage, ...extra } })
|
|
350
|
+
process.exit(1)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const isTaskError = (e: unknown): e is import("../domain/types.js").TaskError =>
|
|
354
|
+
typeof e === "object" &&
|
|
355
|
+
e !== null &&
|
|
356
|
+
typeof (e as { _tag?: unknown })._tag === "string" &&
|
|
357
|
+
[
|
|
358
|
+
"not_found",
|
|
359
|
+
"transition_not_allowed",
|
|
360
|
+
"validation_error",
|
|
361
|
+
"missing_comment",
|
|
362
|
+
"conflict",
|
|
363
|
+
"no_current_task",
|
|
364
|
+
].includes((e as { _tag: string })._tag)
|
|
365
|
+
|
|
366
|
+
const runCommand = async (args: CliArgs): Promise<void> => {
|
|
367
|
+
const sessionId = await getOrCreateSession(args.sessionId)
|
|
368
|
+
const config: LayerConfig = {
|
|
369
|
+
tasksFile: args.tasksFile,
|
|
370
|
+
hooksDir: args.hooksDir,
|
|
371
|
+
}
|
|
372
|
+
const layer = await createLayer(config)
|
|
373
|
+
|
|
374
|
+
const dispatch = async (): Promise<unknown> => {
|
|
375
|
+
switch (args.command) {
|
|
376
|
+
case "init": {
|
|
377
|
+
await runInit(process.cwd(), { force: !!args.commandArgs.force })
|
|
378
|
+
return { ok: true }
|
|
379
|
+
}
|
|
380
|
+
case "create-task": {
|
|
381
|
+
const input = {
|
|
382
|
+
project: args.commandArgs.project,
|
|
383
|
+
milestone: args.commandArgs.milestone,
|
|
384
|
+
title: args.commandArgs.title,
|
|
385
|
+
definition_of_done: args.commandArgs["definition-of-done"],
|
|
386
|
+
description: args.commandArgs.description,
|
|
387
|
+
predictedKTokens: parseInt(args.commandArgs["predicted-k-tokens"] ?? "0", 10),
|
|
388
|
+
priority: args.commandArgs.priority ? parseInt(args.commandArgs.priority, 10) : 0,
|
|
389
|
+
}
|
|
390
|
+
if (
|
|
391
|
+
!input.project ||
|
|
392
|
+
!input.milestone ||
|
|
393
|
+
!input.title ||
|
|
394
|
+
!input.definition_of_done ||
|
|
395
|
+
!input.description ||
|
|
396
|
+
!input.predictedKTokens
|
|
397
|
+
) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
"Missing required arguments: project, milestone, title, definition-of-done, description, predicted-k-tokens"
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
return toolCreateTask(input, sessionId, layer)
|
|
403
|
+
}
|
|
404
|
+
case "list-tasks": {
|
|
405
|
+
const input = {
|
|
406
|
+
status: args.commandArgs.status ?? "in_progress",
|
|
407
|
+
project: args.commandArgs.project,
|
|
408
|
+
milestone: args.commandArgs.milestone,
|
|
409
|
+
}
|
|
410
|
+
return toolListTasks(input, layer)
|
|
411
|
+
}
|
|
412
|
+
case "current-task": {
|
|
413
|
+
return toolCurrentTask(sessionId, layer)
|
|
414
|
+
}
|
|
415
|
+
case "update-task": {
|
|
416
|
+
const input: Record<string, unknown> = {
|
|
417
|
+
id: args.commandArgs.id,
|
|
418
|
+
new_status: args.commandArgs["new-status"],
|
|
419
|
+
}
|
|
420
|
+
if (args.commandArgs["comment-title"] || args.commandArgs["comment-content"]) {
|
|
421
|
+
input.comment = {
|
|
422
|
+
title: args.commandArgs["comment-title"] ?? "",
|
|
423
|
+
content: args.commandArgs["comment-content"] ?? "",
|
|
424
|
+
kind: args.commandArgs["comment-kind"] ?? "regular",
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!input.id || !input.new_status) {
|
|
428
|
+
throw new Error("Missing required arguments: id, new-status")
|
|
429
|
+
}
|
|
430
|
+
return toolUpdateTask(input, sessionId, layer)
|
|
431
|
+
}
|
|
432
|
+
case "edit-task": {
|
|
433
|
+
const input: Record<string, unknown> = { id: args.commandArgs.id }
|
|
434
|
+
if (args.commandArgs.title) input.title = args.commandArgs.title
|
|
435
|
+
if (args.commandArgs.description) input.description = args.commandArgs.description
|
|
436
|
+
if (args.commandArgs["definition-of-done"])
|
|
437
|
+
input.definition_of_done = args.commandArgs["definition-of-done"]
|
|
438
|
+
if (args.commandArgs["predicted-k-tokens"])
|
|
439
|
+
input.predictedKTokens = parseInt(args.commandArgs["predicted-k-tokens"], 10)
|
|
440
|
+
if (args.commandArgs.priority) input.priority = parseInt(args.commandArgs.priority, 10)
|
|
441
|
+
if (!input.id) {
|
|
442
|
+
throw new Error("Missing required argument: id")
|
|
443
|
+
}
|
|
444
|
+
return toolEditTask(input, layer)
|
|
445
|
+
}
|
|
446
|
+
default:
|
|
447
|
+
throw new Error(`Unknown command: ${args.command ?? "none"}`)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const result = await dispatch()
|
|
453
|
+
output({ ok: true, ...(result as object) })
|
|
454
|
+
} catch (err) {
|
|
455
|
+
outputError(err, args.command ?? undefined)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const cleanup = async (sessionId: string): Promise<void> => {
|
|
460
|
+
try {
|
|
461
|
+
const tasksFile = process.env.LOGBOOK_TASKS_FILE ?? DEFAULT_TASKS_FILE
|
|
462
|
+
const { PidSessionRegistry } = await import("../infra/pid-session-registry.js")
|
|
463
|
+
const registry = new PidSessionRegistry(tasksFile)
|
|
464
|
+
await Effect.runPromise(registry.deregister(sessionId))
|
|
465
|
+
} catch {}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const main = async (): Promise<void> => {
|
|
469
|
+
const args = parseArgs()
|
|
470
|
+
|
|
471
|
+
if (!args.command) {
|
|
472
|
+
printHelp()
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const sessionId = await getOrCreateSession(args.sessionId)
|
|
476
|
+
|
|
477
|
+
process.on("SIGINT", async () => {
|
|
478
|
+
await cleanup(sessionId)
|
|
479
|
+
process.exit(0)
|
|
480
|
+
})
|
|
481
|
+
process.on("SIGTERM", async () => {
|
|
482
|
+
await cleanup(sessionId)
|
|
483
|
+
process.exit(0)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
await runCommand(args)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
main()
|
package/src/cli/init.ts
CHANGED
|
@@ -1,9 +1,68 @@
|
|
|
1
|
-
import { access, mkdir, writeFile } from "node:fs/promises"
|
|
1
|
+
import { access, lstat, mkdir, readFile, symlink, writeFile } from "node:fs/promises"
|
|
2
2
|
import { join } from "node:path"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const CLI_DOC_CONTENT = `# Logbook CLI
|
|
5
|
+
|
|
6
|
+
File-system kanban board for AI agents.
|
|
7
|
+
|
|
8
|
+
## Commands
|
|
9
|
+
|
|
10
|
+
| Command | Description |
|
|
11
|
+
|---------|-------------|
|
|
12
|
+
| \`logbook create-task\` | Create a new task in backlog |
|
|
13
|
+
| \`logbook list-tasks\` | List tasks, optionally filtered by status |
|
|
14
|
+
| \`logbook current-task\` | Get current in-progress task for this session |
|
|
15
|
+
| \`logbook update-task\` | Transition task status |
|
|
16
|
+
| \`logbook edit-task\` | Edit task fields without changing status |
|
|
17
|
+
| \`logbook init\` | Initialize project |
|
|
18
|
+
|
|
19
|
+
## Task Lifecycle
|
|
20
|
+
|
|
21
|
+
\`backlog → todo → in_progress → pending_review → done\`
|
|
22
|
+
|
|
23
|
+
Side-exits: \`in_progress → need_info\`, \`blocked\` (return to \`in_progress\`)
|
|
24
|
+
|
|
25
|
+
## Usage Examples
|
|
26
|
+
|
|
27
|
+
### Create a task
|
|
28
|
+
\`\`\`bash
|
|
29
|
+
logbook create-task --project myproject --milestone v1 --title "Fix bug" \\
|
|
30
|
+
--definition-of-done "Bug fixed and tested" --description "Details..." \\
|
|
31
|
+
--predicted-k-tokens 3
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
### List tasks
|
|
35
|
+
\`\`\`bash
|
|
36
|
+
logbook list-tasks --status in_progress
|
|
37
|
+
logbook list-tasks --status "*"
|
|
38
|
+
logbook list-tasks --status todo --project myproject
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
### Get current task
|
|
42
|
+
\`\`\`bash
|
|
43
|
+
logbook current-task
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
### Update task status
|
|
47
|
+
\`\`\`bash
|
|
48
|
+
logbook update-task --id <uuid> --new-status in_progress
|
|
49
|
+
logbook update-task --id <uuid> --new-status need_info \\
|
|
50
|
+
--comment-title "Need info" --comment-content "What does X mean?"
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
### Edit task
|
|
54
|
+
\`\`\`bash
|
|
55
|
+
logbook edit-task --id <uuid> --title "New title"
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
## Environment Variables
|
|
59
|
+
|
|
60
|
+
| Variable | Default | Description |
|
|
61
|
+
|----------|---------|-------------|
|
|
62
|
+
| \`LOGBOOK_TASKS_FILE\` | \`./tasks.jsonl\` | Path to JSONL task store |
|
|
63
|
+
| \`LOGBOOK_HOOKS_DIR\` | \`./hooks\` | Directory for hook definitions |
|
|
64
|
+
| \`LOGBOOK_SESSION_ID\` | auto-generated | Session ID to use |
|
|
65
|
+
`
|
|
7
66
|
|
|
8
67
|
const CLAUDE_CODE_SNIPPET = `Claude Code — add to .claude/settings.json:
|
|
9
68
|
{
|
|
@@ -27,18 +86,15 @@ const OPENCODE_SNIPPET = `OpenCode — add to opencode.json:
|
|
|
27
86
|
|
|
28
87
|
const GITIGNORE_SNIPPET = `.gitignore — add these runtime files:
|
|
29
88
|
tasks.jsonl
|
|
30
|
-
sessions.json
|
|
89
|
+
sessions.json
|
|
90
|
+
.logbook-session`
|
|
31
91
|
|
|
32
92
|
const NEXT_STEPS = `Next steps:
|
|
33
|
-
1. Add the config snippet for your AI client
|
|
34
|
-
2. Run: LOGBOOK_TASKS_FILE=./tasks.jsonl logbook
|
|
35
|
-
3.
|
|
93
|
+
1. Add the config snippet for your AI client (MCP) or use CLI directly
|
|
94
|
+
2. Run: LOGBOOK_TASKS_FILE=./tasks.jsonl logbook init
|
|
95
|
+
3. See AGENTS.md for CLI commands reference
|
|
36
96
|
4. See quickstart.md for the full walkthrough`
|
|
37
97
|
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Side-effecting helpers (file I/O at boundary)
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
98
|
const fileExists = async (path: string): Promise<boolean> => {
|
|
43
99
|
try {
|
|
44
100
|
await access(path)
|
|
@@ -48,6 +104,15 @@ const fileExists = async (path: string): Promise<boolean> => {
|
|
|
48
104
|
}
|
|
49
105
|
}
|
|
50
106
|
|
|
107
|
+
const isSymlink = async (path: string): Promise<boolean> => {
|
|
108
|
+
try {
|
|
109
|
+
const stats = await lstat(path)
|
|
110
|
+
return stats.isSymbolicLink()
|
|
111
|
+
} catch {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
51
116
|
const scaffoldTasksFile = async (cwd: string): Promise<void> => {
|
|
52
117
|
const path = join(cwd, "tasks.jsonl")
|
|
53
118
|
if (await fileExists(path)) {
|
|
@@ -68,6 +133,81 @@ const scaffoldHooksDir = async (cwd: string): Promise<void> => {
|
|
|
68
133
|
console.log("✓ hooks/ created")
|
|
69
134
|
}
|
|
70
135
|
|
|
136
|
+
const appendLogbookDocs = async (path: string, isAgents: boolean): Promise<void> => {
|
|
137
|
+
const existing = await readFile(path, "utf8")
|
|
138
|
+
if (isAgents) {
|
|
139
|
+
const separator = "\n\n---\n\n"
|
|
140
|
+
await writeFile(path, `${existing}${separator}${CLI_DOC_CONTENT}`, "utf8")
|
|
141
|
+
} else {
|
|
142
|
+
const cliSection = `
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Logbook
|
|
147
|
+
|
|
148
|
+
${CLI_DOC_CONTENT}
|
|
149
|
+
`
|
|
150
|
+
await writeFile(path, `${existing}${cliSection}`, "utf8")
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const ensureBothDocs = async (cwd: string, force: boolean): Promise<void> => {
|
|
155
|
+
const agentsPath = join(cwd, "AGENTS.md")
|
|
156
|
+
const claudePath = join(cwd, "CLAUDE.md")
|
|
157
|
+
|
|
158
|
+
const agentsExists = await fileExists(agentsPath)
|
|
159
|
+
const claudeExists = await fileExists(claudePath)
|
|
160
|
+
const agentsIsSymlink = await isSymlink(agentsPath)
|
|
161
|
+
const claudeIsSymlink = await isSymlink(claudePath)
|
|
162
|
+
|
|
163
|
+
if (!force) {
|
|
164
|
+
if (agentsExists && !agentsIsSymlink) {
|
|
165
|
+
console.log("✓ AGENTS.md already exists, appending logbook documentation")
|
|
166
|
+
await appendLogbookDocs(agentsPath, true)
|
|
167
|
+
}
|
|
168
|
+
if (claudeExists && !claudeIsSymlink) {
|
|
169
|
+
console.log("✓ CLAUDE.md already exists, appending logbook documentation")
|
|
170
|
+
await appendLogbookDocs(claudePath, false)
|
|
171
|
+
}
|
|
172
|
+
if (!agentsExists && !claudeExists) {
|
|
173
|
+
await writeFile(agentsPath, CLI_DOC_CONTENT, "utf8")
|
|
174
|
+
console.log("✓ AGENTS.md created")
|
|
175
|
+
await symlink("AGENTS.md", claudePath)
|
|
176
|
+
console.log("✓ CLAUDE.md created (symlink to AGENTS.md)")
|
|
177
|
+
}
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (agentsExists && !agentsIsSymlink) {
|
|
182
|
+
await appendLogbookDocs(agentsPath, true)
|
|
183
|
+
} else if (!agentsExists) {
|
|
184
|
+
await writeFile(agentsPath, CLI_DOC_CONTENT, "utf8")
|
|
185
|
+
console.log("✓ AGENTS.md created")
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (claudeExists && !claudeIsSymlink) {
|
|
189
|
+
await appendLogbookDocs(claudePath, false)
|
|
190
|
+
} else if (!claudeExists) {
|
|
191
|
+
const targetExists = await fileExists(agentsPath)
|
|
192
|
+
if (targetExists) {
|
|
193
|
+
await symlink("AGENTS.md", claudePath)
|
|
194
|
+
console.log("✓ CLAUDE.md created (symlink to AGENTS.md)")
|
|
195
|
+
} else {
|
|
196
|
+
await writeFile(
|
|
197
|
+
claudePath,
|
|
198
|
+
`# Logbook
|
|
199
|
+
|
|
200
|
+
${CLI_DOC_CONTENT}
|
|
201
|
+
`,
|
|
202
|
+
"utf8"
|
|
203
|
+
)
|
|
204
|
+
console.log("✓ CLAUDE.md created")
|
|
205
|
+
}
|
|
206
|
+
} else if (claudeIsSymlink) {
|
|
207
|
+
console.log("✓ CLAUDE.md is already a symlink to AGENTS.md, skipping")
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
71
211
|
const printSnippets = (): void => {
|
|
72
212
|
console.log("")
|
|
73
213
|
console.log(CLAUDE_CODE_SNIPPET)
|
|
@@ -79,12 +219,12 @@ const printSnippets = (): void => {
|
|
|
79
219
|
console.log(NEXT_STEPS)
|
|
80
220
|
}
|
|
81
221
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
export const runInit = async (cwd: string = process.cwd()): Promise<void> => {
|
|
222
|
+
export const runInit = async (
|
|
223
|
+
cwd: string = process.cwd(),
|
|
224
|
+
options: { force?: boolean } = {}
|
|
225
|
+
): Promise<void> => {
|
|
87
226
|
await scaffoldTasksFile(cwd)
|
|
88
227
|
await scaffoldHooksDir(cwd)
|
|
228
|
+
await ensureBothDocs(cwd, options.force ?? false)
|
|
89
229
|
printSnippets()
|
|
90
230
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Layer } from "effect"
|
|
2
|
+
import { executeHooks } from "../hook/hook-executor.js"
|
|
3
|
+
import type { HookEvent } from "../hook/ports.js"
|
|
4
|
+
import { HookRunner } from "../hook/ports.js"
|
|
5
|
+
import { loadHookConfigs } from "../infra/hook-config-loader.js"
|
|
6
|
+
import { JsonlTaskRepository } from "../infra/jsonl-task-repository.js"
|
|
7
|
+
import { PidSessionRegistry } from "../infra/pid-session-registry.js"
|
|
8
|
+
import { TaskRepository } from "../task/ports.js"
|
|
9
|
+
import { SessionRegistry } from "../task/session-registry.js"
|
|
10
|
+
|
|
11
|
+
export interface LayerConfig {
|
|
12
|
+
tasksFile: string
|
|
13
|
+
hooksDir: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const createLayer = async (
|
|
17
|
+
config: LayerConfig
|
|
18
|
+
): Promise<Layer.Layer<TaskRepository | HookRunner | SessionRegistry>> => {
|
|
19
|
+
const configs = await loadHookConfigs(config.hooksDir)
|
|
20
|
+
const repo = new JsonlTaskRepository(config.tasksFile)
|
|
21
|
+
const registry = new PidSessionRegistry(config.tasksFile)
|
|
22
|
+
|
|
23
|
+
const hookRunnerImpl: HookRunner = {
|
|
24
|
+
run: (event: HookEvent) => executeHooks(event, configs),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const repoLayer: Layer.Layer<TaskRepository> = Layer.succeed(TaskRepository, repo)
|
|
28
|
+
const fullLayer: Layer.Layer<TaskRepository | HookRunner | SessionRegistry> = Layer.merge(
|
|
29
|
+
Layer.merge(repoLayer, Layer.succeed(HookRunner, hookRunnerImpl)),
|
|
30
|
+
Layer.succeed(SessionRegistry, registry)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return fullLayer
|
|
34
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { createInterface } from "node:readline"
|
|
3
|
-
import { Effect
|
|
3
|
+
import { Effect } from "effect"
|
|
4
4
|
import { runInit } from "../cli/init.js"
|
|
5
|
-
import {
|
|
6
|
-
import type { HookEvent } from "../hook/ports.js"
|
|
7
|
-
import { HookRunner } from "../hook/ports.js"
|
|
8
|
-
import { loadHookConfigs } from "../infra/hook-config-loader.js"
|
|
9
|
-
import { JsonlTaskRepository } from "../infra/jsonl-task-repository.js"
|
|
5
|
+
import { createLayer } from "../infra/layer.js"
|
|
10
6
|
import { PidSessionRegistry } from "../infra/pid-session-registry.js"
|
|
11
|
-
import { TaskRepository } from "../task/ports.js"
|
|
12
|
-
import { SessionRegistry } from "../task/session-registry.js"
|
|
13
7
|
import { taskErrorToMcpError } from "./error-codes.js"
|
|
14
8
|
import { newSessionId } from "./session.js"
|
|
15
9
|
import { toolCreateTask } from "./tool-create-task.js"
|
|
@@ -218,20 +212,9 @@ export const startServer = async (): Promise<void> => {
|
|
|
218
212
|
const tasksFile = process.env.LOGBOOK_TASKS_FILE ?? "./tasks.jsonl"
|
|
219
213
|
const hooksDir = process.env.LOGBOOK_HOOKS_DIR ?? "./hooks"
|
|
220
214
|
|
|
221
|
-
const
|
|
222
|
-
const repo = new JsonlTaskRepository(tasksFile)
|
|
215
|
+
const fullLayer = await createLayer({ tasksFile, hooksDir })
|
|
223
216
|
const registry = new PidSessionRegistry(tasksFile)
|
|
224
217
|
|
|
225
|
-
const hookRunnerImpl: HookRunner = {
|
|
226
|
-
run: (event: HookEvent) => executeHooks(event, configs),
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const repoLayer: Layer.Layer<TaskRepository> = Layer.succeed(TaskRepository, repo)
|
|
230
|
-
const fullLayer: Layer.Layer<TaskRepository | HookRunner | SessionRegistry> = Layer.merge(
|
|
231
|
-
Layer.merge(repoLayer, Layer.succeed(HookRunner, hookRunnerImpl)),
|
|
232
|
-
Layer.succeed(SessionRegistry, registry)
|
|
233
|
-
)
|
|
234
|
-
|
|
235
218
|
const sessionId = newSessionId()
|
|
236
219
|
await Effect.runPromise(registry.register(sessionId, process.pid))
|
|
237
220
|
|
|
@@ -256,15 +239,15 @@ export const startServer = async (): Promise<void> => {
|
|
|
256
239
|
return { content: [{ type: "text", text: JSON.stringify(result) }] }
|
|
257
240
|
}
|
|
258
241
|
case "list_tasks":
|
|
259
|
-
return toolListTasks(params,
|
|
242
|
+
return toolListTasks(params, fullLayer)
|
|
260
243
|
case "current_task":
|
|
261
244
|
return toolCurrentTask(sessionId, fullLayer)
|
|
262
245
|
case "update_task":
|
|
263
246
|
return toolUpdateTask(params, sessionId, fullLayer)
|
|
264
247
|
case "create_task":
|
|
265
|
-
return toolCreateTask(params, sessionId,
|
|
248
|
+
return toolCreateTask(params, sessionId, fullLayer)
|
|
266
249
|
case "edit_task":
|
|
267
|
-
return toolEditTask(params,
|
|
250
|
+
return toolEditTask(params, fullLayer)
|
|
268
251
|
default:
|
|
269
252
|
return Promise.reject(new MethodNotFoundError(method))
|
|
270
253
|
}
|