@netnodeag/kraftwerk 0.81.0 → 0.82.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 +52 -0
- package/dist/cli/boards.d.ts +2 -0
- package/dist/cli/boards.js +486 -0
- package/dist/cli/init.js +1 -0
- package/dist/cli/kraftwerk.js +3 -0
- package/dist/client/routes.d.ts +14 -0
- package/dist/client/routes.js +14 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +24 -1
- package/dist/core/attention.d.ts +2 -2
- package/dist/core/attention.js +22 -2
- package/dist/core/board-wake.d.ts +21 -0
- package/dist/core/board-wake.js +221 -0
- package/dist/core/boards.d.ts +256 -0
- package/dist/core/boards.js +936 -0
- package/dist/core/chat/acp.d.ts +1 -1
- package/dist/core/chat/acp.js +2 -1
- package/dist/core/chat/backend.d.ts +11 -0
- package/dist/core/chat/backend.js +9 -1
- package/dist/core/chat/context.js +16 -4
- package/dist/core/chat/pi.d.ts +1 -1
- package/dist/core/chat/pi.js +2 -1
- package/dist/core/chat/seats.js +3 -2
- package/dist/core/chat/sessions.d.ts +2 -0
- package/dist/core/chat/sessions.js +1 -0
- package/dist/core/chat/state.js +1 -1
- package/dist/core/chat/types.d.ts +11 -1
- package/dist/core/files.d.ts +1 -1
- package/dist/core/files.js +19 -1
- package/dist/core/git.js +3 -1
- package/dist/core/notifications.d.ts +1 -1
- package/dist/core/refs.d.ts +1 -1
- package/dist/core/refs.js +25 -1
- package/dist/core/search.d.ts +12 -0
- package/dist/core/search.js +19 -0
- package/dist/core/settings.d.ts +2 -0
- package/dist/core/settings.js +13 -1
- package/dist/core/trash.d.ts +1 -1
- package/dist/server/api/boards.d.ts +105 -0
- package/dist/server/api/boards.js +160 -0
- package/dist/server/api/chats.d.ts +2 -0
- package/dist/server/api/chats.js +9 -3
- package/dist/server/api/index.d.ts +105 -2
- package/dist/server/api/index.js +2 -0
- package/dist/server/api/workspace.d.ts +5 -0
- package/dist/server/api/workspace.js +2 -0
- package/dist/server/push.js +1 -1
- package/dist/server/server.js +3 -0
- package/package.json +1 -1
- package/schema/kraftwerk.schema.json +39 -0
- package/web/dist/assets/{dist-Dx3aajX1.js → dist-BBFUIftm.js} +1 -1
- package/web/dist/assets/{dist-ENZYo2jZ.js → dist-Bie1ebP9.js} +1 -1
- package/web/dist/assets/{dist-BiU4F98O.js → dist-BuwdjX6z.js} +1 -1
- package/web/dist/assets/{dist-8IOFzZx-.js → dist-C0ve4Tka.js} +1 -1
- package/web/dist/assets/{dist-FfKYtQxT.js → dist-C16bXf8c.js} +1 -1
- package/web/dist/assets/{dist-cewmjxFi.js → dist-CDUZdAAS.js} +1 -1
- package/web/dist/assets/{dist-BK0VhAPQ.js → dist-CH73jsgO.js} +1 -1
- package/web/dist/assets/{dist-Dy1SvhKC.js → dist-C_rJjRj7.js} +1 -1
- package/web/dist/assets/dist-DYICNp09.js +1 -0
- package/web/dist/assets/{dist-AhnSw4hy.js → dist-DtUB2Uu2.js} +1 -1
- package/web/dist/assets/{dist-Ba6WSVOB.js → dist-DvkrCSGL.js} +1 -1
- package/web/dist/assets/{editor-CaRa1NMC.js → editor-3z92i0tV.js} +3 -3
- package/web/dist/assets/{editor-CZMrhTTL.css → editor-C0_Snnzk.css} +1 -1
- package/web/dist/assets/index-DRhNELK_.js +232 -0
- package/web/dist/assets/index-unGEux3Y.css +2 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/dist-lgUK2Fjt.js +0 -1
- package/web/dist/assets/index-Dpzux3OC.js +0 -231
- package/web/dist/assets/index-Dvt-vE1h.css +0 -2
package/README.md
CHANGED
|
@@ -259,6 +259,7 @@ kraftwerk workspaces # every workspace on this machine; works
|
|
|
259
259
|
kraftwerk devices # paired devices (a phone, another computer); devices pair|revoke
|
|
260
260
|
kraftwerk remote on|off|pair # reach this machine from anywhere through the relay, end to end encrypted
|
|
261
261
|
kraftwerk projects # goal-scoped project folders; projects create|show|link|log|remove
|
|
262
|
+
kraftwerk boards|tasks # task boards per project or workspace; tasks create|show|move|assign|log|pick|remove
|
|
262
263
|
kraftwerk contacts|places|events # who, where and when, one Markdown file each; … show|create|set|remove, events --from/--to
|
|
263
264
|
kraftwerk ref <kind:slug> # resolve a reference (contact:anna-meier, project:relaunch, …); --file <md> resolves every one in a file
|
|
264
265
|
kraftwerk trash # what was deleted; trash restore|purge <id>, trash empty
|
|
@@ -634,6 +635,57 @@ kraftwerk projects remove relaunch-netnode-ch
|
|
|
634
635
|
The registry of workspaces on this machine, which answered to
|
|
635
636
|
`kraftwerk projects` until 0.48, is `kraftwerk workspaces` now.
|
|
636
637
|
|
|
638
|
+
## Boards and tasks
|
|
639
|
+
|
|
640
|
+
Turn on `boards:` in `kraftwerk.yml` (or the checkbox in settings) and every
|
|
641
|
+
project can have task boards, and the workspace its own:
|
|
642
|
+
|
|
643
|
+
```yaml
|
|
644
|
+
boards:
|
|
645
|
+
root: kraftwerk-data/boards # default; the workspace's boards (a project's live in projects/<slug>/boards/)
|
|
646
|
+
columns: [draft, todo, progress, review, done] # default for boards that name none
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
```
|
|
650
|
+
projects/<slug>/boards/<board>/board.md # title, columns, default agent, stale_after + the board's rules
|
|
651
|
+
projects/<slug>/boards/<board>/tasks/<n>-<slug>/ # one task: task.md, log.md, its files
|
|
652
|
+
kraftwerk-data/boards/<board>/… # workspace boards, same shape
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
A column is a slug or `{slug, label, limit, done, review}`: `review: true`
|
|
656
|
+
means tasks there wait for a human (they count as attention), `done: true`
|
|
657
|
+
means finished. The column is a field of `task.md`, so a move is a one-line
|
|
658
|
+
diff and the task's folder never moves. Task folders are numbered per board
|
|
659
|
+
(`50-write-a-press-release`); the number names the task in the CLI, the
|
|
660
|
+
folder name is its reference (`task:50-write-a-press-release`). The log is in
|
|
661
|
+
the journal format: `## 2026-11-05` days, `* 16:40 Moved Todo → In progress:
|
|
662
|
+
starting (lisa/claude)` lines, newest first; kraftwerk writes it on every
|
|
663
|
+
create, move, assign, pick and file change, and agents and humans add lines.
|
|
664
|
+
|
|
665
|
+
```
|
|
666
|
+
kraftwerk boards [--project <p>] [--all] # the boards; boards show|create|set|remove
|
|
667
|
+
kraftwerk tasks [--project <p>] [--board <b>] # the tasks of a board (default: the project's `tasks` board)
|
|
668
|
+
kraftwerk tasks create "<title>" --status todo --assign lisa --file $KRAFTWERK_TEMP/task.md -m "…"
|
|
669
|
+
kraftwerk tasks show 50 | move 50 review -m "…" | assign 50 max -m "…" | assign 50 human | log 50 "…" | pick --as lisa | remove 50
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**Agents and the board.** A project chat and every agent see the boards
|
|
673
|
+
in their context: columns with counts, the open tasks, the rules written
|
|
674
|
+
in `board.md`, and the commands. Inside a session the CLI knows who it is
|
|
675
|
+
(`KRAFTWERK_AGENT`, `KRAFTWERK_ACTOR`), so `tasks pick` and `tasks log` need
|
|
676
|
+
no name. Waking is event-based: whenever a task has an agent as assignee
|
|
677
|
+
and sits in a work column — created there, moved there, assigned, handed
|
|
678
|
+
over — that agent gets a session with the task as its scope (the task
|
|
679
|
+
block: `task.md`, the files, the whole log, so a hand-over reads like a
|
|
680
|
+
message from the previous agent), or the message is posted into its open
|
|
681
|
+
session for that task. Draft, Review and Done never wake anyone; an agent
|
|
682
|
+
acting on its own task does not wake itself; a board's `agent:` is the
|
|
683
|
+
default assignee for tasks entering Todo unassigned. The events are lines in
|
|
684
|
+
`<output>/boards/events.jsonl`, local to the machine, so a clone of the
|
|
685
|
+
workspace never starts the same work twice. Only a human moves a task into a
|
|
686
|
+
done column; `tasks assign <n> human` clears the assignee and puts the task
|
|
687
|
+
into Review.
|
|
688
|
+
|
|
637
689
|
## Contacts, places and events
|
|
638
690
|
|
|
639
691
|
Who, where and when, kept by the workspace itself so people and agents can
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { appendTaskLog, assignTask, createBoard, createTask, DEFAULT_BOARD, deleteBoard, deleteTask, ensureDefaultBoard, getBoard, getTask, listAllBoards, listBoards, moveTask, pickTask, saveBoard, saveTask, } from "../core/boards.js";
|
|
5
|
+
import { taskSessions } from "../core/board-wake.js";
|
|
6
|
+
import { initContext as prepare } from "./routines.js";
|
|
7
|
+
import { fmtAgo } from "./workspaces.js";
|
|
8
|
+
/**
|
|
9
|
+
* `kraftwerk boards` and `kraftwerk tasks` — the boards (columns of tasks)
|
|
10
|
+
* of a project or of the workspace, and the tasks on them. The same module
|
|
11
|
+
* the inspector uses, so a task created here is on the board at once, and
|
|
12
|
+
* the agent that is assigned is woken by the server. An agent inside a
|
|
13
|
+
* session needs no --as: KRAFTWERK_AGENT names it, KRAFTWERK_ACTOR signs
|
|
14
|
+
* its log lines.
|
|
15
|
+
*
|
|
16
|
+
* kraftwerk boards [--project <p>] [--all] list boards (the workspace's, a project's, every one)
|
|
17
|
+
* kraftwerk boards show <board> [--project <p>] columns, rules, the tasks per column
|
|
18
|
+
* kraftwerk boards create <title> [--project <p>] [--agent] new board with the default columns
|
|
19
|
+
* kraftwerk boards set <board> --agent <slug> | --notes-file change default agent, stale_after, rules
|
|
20
|
+
* kraftwerk boards remove <board> move the board to the trash
|
|
21
|
+
*
|
|
22
|
+
* kraftwerk tasks [--board <b>] [--project <p>] [--status] [--assignee] [--mine]
|
|
23
|
+
* kraftwerk tasks show <n> task.md, folder path, files, log
|
|
24
|
+
* kraftwerk tasks create <title> [--status todo] [--assign <agent>] [--file task.md] [-m]
|
|
25
|
+
* kraftwerk tasks set <n> [--title] [--due] [--tags] [--refs] [--notes-file]
|
|
26
|
+
* kraftwerk tasks move <n> <column> [-m "<why>"]
|
|
27
|
+
* kraftwerk tasks assign <n> <agent> [-m "<hand-over note>"] | --to human
|
|
28
|
+
* kraftwerk tasks log <n> "<what you did>" [--tail <k>]
|
|
29
|
+
* kraftwerk tasks pick [--as <agent>] [--column todo]
|
|
30
|
+
* kraftwerk tasks remove <n>
|
|
31
|
+
*
|
|
32
|
+
* --board defaults to the project's `tasks` board (or the only board); the
|
|
33
|
+
* number alone names a task.
|
|
34
|
+
*/
|
|
35
|
+
const die = (msg) => {
|
|
36
|
+
console.error(chalk.red(msg));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
};
|
|
39
|
+
/** Who writes: --actor, else the session's KRAFTWERK_ACTOR, else human:user. */
|
|
40
|
+
const actorOf = (opt) => (opt ?? process.env.KRAFTWERK_ACTOR ?? "").trim() || "human:user";
|
|
41
|
+
/** Which agent acts: --as, else the session's KRAFTWERK_AGENT. */
|
|
42
|
+
const agentOf = (opt) => (opt ?? process.env.KRAFTWERK_AGENT ?? "").trim();
|
|
43
|
+
/** The board a command means: --board, else the project's default board when it exists, else the only board. */
|
|
44
|
+
async function whereOf(w, create = false) {
|
|
45
|
+
const project = w.project?.trim() || undefined;
|
|
46
|
+
if (w.board)
|
|
47
|
+
return { ...(project ? { project } : {}), board: w.board.trim() };
|
|
48
|
+
const view = await listBoards(project);
|
|
49
|
+
if (!view.enabled)
|
|
50
|
+
die(view.error ?? "boards are off");
|
|
51
|
+
if (view.error)
|
|
52
|
+
die(view.error);
|
|
53
|
+
const dflt = view.boards.find((b) => b.board === DEFAULT_BOARD);
|
|
54
|
+
if (dflt)
|
|
55
|
+
return { ...(project ? { project } : {}), board: DEFAULT_BOARD };
|
|
56
|
+
if (view.boards.length === 1)
|
|
57
|
+
return { ...(project ? { project } : {}), board: view.boards[0].board };
|
|
58
|
+
if (create && view.boards.length === 0)
|
|
59
|
+
return ensureDefaultBoard(project);
|
|
60
|
+
if (view.boards.length === 0)
|
|
61
|
+
die(`no board ${project ? `in project ${project}` : "in the workspace"} yet — \`kraftwerk boards create "<title>"${project ? ` --project ${project}` : ""}\` makes one`);
|
|
62
|
+
return die(`several boards here (${view.boards.map((b) => b.board).join(", ")}) — say which with --board`);
|
|
63
|
+
}
|
|
64
|
+
const flags = (ref) => (ref.project ? `--project ${ref.project} --board ${ref.board}` : `--board ${ref.board}`);
|
|
65
|
+
function taskRow(t) {
|
|
66
|
+
return [
|
|
67
|
+
chalk.cyan(String(t.number)),
|
|
68
|
+
t.configError ? chalk.red(t.configError) : t.title,
|
|
69
|
+
t.status,
|
|
70
|
+
t.assignee ?? chalk.dim("—"),
|
|
71
|
+
t.due ?? chalk.dim("—"),
|
|
72
|
+
chalk.dim(t.lastLog ?? ""),
|
|
73
|
+
chalk.dim(fmtAgo(t.updatedAt)),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
function printBoard(b) {
|
|
77
|
+
console.log(`${chalk.bold(b.title)} ${chalk.dim(`(${b.project ? `${b.project}/` : ""}${b.board}${b.agent ? `, default agent ${b.agent}` : ""})`)}`);
|
|
78
|
+
if (b.configError)
|
|
79
|
+
console.log(chalk.red(`board.md: ${b.configError}`));
|
|
80
|
+
if (b.notes.trim())
|
|
81
|
+
console.log(b.notes.trim());
|
|
82
|
+
console.log(chalk.dim(b.path));
|
|
83
|
+
for (const c of b.columns) {
|
|
84
|
+
const items = b.items.filter((t) => t.status === c.slug);
|
|
85
|
+
console.log(`\n${chalk.bold(c.label)} ${chalk.dim(`(${items.length}${c.limit ? `/${c.limit}` : ""}${c.review ? ", review" : c.done ? ", done" : ""})`)}`);
|
|
86
|
+
for (const t of items)
|
|
87
|
+
console.log(` ${chalk.cyan(String(t.number).padStart(3))} ${t.title}${t.assignee ? chalk.dim(` · ${t.assignee}`) : ""}${t.due ? chalk.dim(` · due ${t.due}`) : ""}${t.stale ? chalk.yellow(" · stale") : ""}`);
|
|
88
|
+
}
|
|
89
|
+
const lost = b.items.filter((t) => !b.columns.some((c) => c.slug === t.status));
|
|
90
|
+
if (lost.length) {
|
|
91
|
+
console.log(`\n${chalk.bold("no column")}`);
|
|
92
|
+
for (const t of lost)
|
|
93
|
+
console.log(` ${chalk.cyan(String(t.number).padStart(3))} ${t.title} ${chalk.dim(`(status "${t.status}")`)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function printTask(t) {
|
|
97
|
+
console.log(`${chalk.cyan(String(t.number))} ${chalk.bold(t.title)} ${chalk.dim(`(${t.ref})`)}`);
|
|
98
|
+
console.log(`${t.status}${t.assignee ? ` · ${t.assignee}` : " · unassigned"}${t.due ? ` · due ${t.due}` : ""}${t.tags.length ? ` · ${t.tags.join(", ")}` : ""}${t.stale ? chalk.yellow(" · stale") : ""}`);
|
|
99
|
+
if (t.refs.length)
|
|
100
|
+
console.log(chalk.dim(`refs: ${t.refs.join(", ")}`));
|
|
101
|
+
console.log(chalk.dim(`folder: ${t.path}`));
|
|
102
|
+
const sessions = t.sessions ?? [];
|
|
103
|
+
if (sessions.length)
|
|
104
|
+
console.log(chalk.dim(`sessions: ${sessions.map((s) => `${s.emoji ? `${s.emoji} ` : ""}${s.name ?? s.agent} ${s.id}${s.busy ? " (working)" : ""}`).join(", ")}`));
|
|
105
|
+
if (t.configError)
|
|
106
|
+
console.log(chalk.red(`task.md: ${t.configError}`));
|
|
107
|
+
if (t.notes.trim())
|
|
108
|
+
console.log(`\n${t.notes.trim()}`);
|
|
109
|
+
console.log(`\n${chalk.bold("files")}${t.fileList.length ? "" : chalk.dim(" (none)")}`);
|
|
110
|
+
for (const f of t.fileList)
|
|
111
|
+
console.log(` ${f}`);
|
|
112
|
+
if (t.log.trim())
|
|
113
|
+
console.log(`\n${chalk.bold("log")}\n${t.log.trim()}`);
|
|
114
|
+
}
|
|
115
|
+
export function registerBoardCommands(program) {
|
|
116
|
+
const boards = program.command("boards").description("Task boards of a project or the workspace: list, show, create, set, remove");
|
|
117
|
+
boards
|
|
118
|
+
.command("list", { isDefault: true })
|
|
119
|
+
.description("List the boards: the workspace's, a project's (--project), or every one (--all)")
|
|
120
|
+
.option("--project <slug>", "A project's boards")
|
|
121
|
+
.option("--all", "Every board of the workspace and its projects")
|
|
122
|
+
.option("--json", "Machine-readable output")
|
|
123
|
+
.action(async (opts) => {
|
|
124
|
+
await prepare();
|
|
125
|
+
const view = opts.all ? { enabled: true, boards: await listAllBoards() } : await listBoards(opts.project);
|
|
126
|
+
if (opts.json) {
|
|
127
|
+
console.log(JSON.stringify(view, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!view.enabled)
|
|
131
|
+
die(("error" in view && view.error) || "boards are off");
|
|
132
|
+
if ("error" in view && view.error)
|
|
133
|
+
die(view.error);
|
|
134
|
+
if (view.boards.length === 0) {
|
|
135
|
+
console.log(chalk.dim(`No boards ${opts.project ? `in project ${opts.project}` : opts.all ? "anywhere" : "in the workspace"} yet — \`kraftwerk boards create "<title>"${opts.project ? ` --project ${opts.project}` : ""}\` starts one.`));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const table = new Table({ head: ["board", "where", "tasks", "review", "columns", "changed"].map((h) => chalk.bold(h)) });
|
|
139
|
+
for (const b of view.boards) {
|
|
140
|
+
table.push([
|
|
141
|
+
`${chalk.cyan(b.title)}\n${chalk.dim(b.board)}`,
|
|
142
|
+
b.project ? `project ${b.project}` : chalk.dim("workspace"),
|
|
143
|
+
String(b.tasks),
|
|
144
|
+
b.review ? chalk.yellow(String(b.review)) : chalk.dim("0"),
|
|
145
|
+
chalk.dim(b.configError ? chalk.red(b.configError) : b.columns.map((c) => `${c.label} ${b.counts[c.slug] ?? 0}`).join(" · ")),
|
|
146
|
+
chalk.dim(fmtAgo(b.updatedAt)),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
console.log(table.toString());
|
|
150
|
+
});
|
|
151
|
+
boards
|
|
152
|
+
.command("show")
|
|
153
|
+
.description("One board: columns, rules, every task per column")
|
|
154
|
+
.argument("<board>", "Board slug")
|
|
155
|
+
.option("--project <slug>", "The project the board belongs to")
|
|
156
|
+
.option("--json", "Machine-readable output")
|
|
157
|
+
.action(async (board, opts) => {
|
|
158
|
+
await prepare();
|
|
159
|
+
try {
|
|
160
|
+
const b = await getBoard({ ...(opts.project ? { project: opts.project } : {}), board });
|
|
161
|
+
if (!b)
|
|
162
|
+
die(`no board "${board}"`);
|
|
163
|
+
if (opts.json)
|
|
164
|
+
console.log(JSON.stringify(b, null, 2));
|
|
165
|
+
else
|
|
166
|
+
printBoard(b);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
die(err.message);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
boards
|
|
173
|
+
.command("create")
|
|
174
|
+
.description("Create a board with the default columns (draft, todo, progress, review, done) unless --columns is given")
|
|
175
|
+
.argument("<title>", "Display title; the folder name is derived from it unless --slug is given")
|
|
176
|
+
.option("--project <slug>", "Create it inside this project (projects/<slug>/boards/)")
|
|
177
|
+
.option("--slug <slug>", "Folder name (lowercase, digits, dashes)")
|
|
178
|
+
.option("--columns <list>", "Comma-separated column slugs")
|
|
179
|
+
.option("--agent <slug>", "Default agent: tasks entering todo unassigned go to it")
|
|
180
|
+
.option("--notes <text>", "The board's rules (the body of board.md)")
|
|
181
|
+
.option("--json", "Print the board as JSON")
|
|
182
|
+
.action(async (title, opts) => {
|
|
183
|
+
await prepare();
|
|
184
|
+
try {
|
|
185
|
+
const b = await createBoard({ project: opts.project, title, slug: opts.slug, columns: opts.columns ? opts.columns.split(",").map((s) => s.trim()).filter(Boolean) : undefined, agent: opts.agent, notes: opts.notes });
|
|
186
|
+
if (opts.json)
|
|
187
|
+
console.log(JSON.stringify(b, null, 2));
|
|
188
|
+
else
|
|
189
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(b.board)} → ${b.path} ${chalk.dim(`(columns: ${b.columns.map((c) => c.slug).join(", ")})`)}`);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
die(err.message);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
boards
|
|
196
|
+
.command("set")
|
|
197
|
+
.description("Change a board's title, default agent, stale_after or rules")
|
|
198
|
+
.argument("<board>", "Board slug")
|
|
199
|
+
.option("--project <slug>", "The project the board belongs to")
|
|
200
|
+
.option("--title <text>", "Display title")
|
|
201
|
+
.option("--agent <slug>", 'Default agent; "" clears it')
|
|
202
|
+
.option("--stale-after <duration>", 'Mark a worked task stale after e.g. 12h, 2d; "" for the default (1d)')
|
|
203
|
+
.option("--columns <list>", "Comma-separated column slugs (replaces the list)")
|
|
204
|
+
.option("--notes-file <path>", "Replace the rules with this file's text")
|
|
205
|
+
.option("--json", "Print the board as JSON")
|
|
206
|
+
.action(async (board, opts) => {
|
|
207
|
+
await prepare();
|
|
208
|
+
try {
|
|
209
|
+
const input = {
|
|
210
|
+
...(opts.title !== undefined ? { title: opts.title } : {}),
|
|
211
|
+
...(opts.agent !== undefined ? { agent: opts.agent || null } : {}),
|
|
212
|
+
...(opts.staleAfter !== undefined ? { staleAfter: opts.staleAfter || null } : {}),
|
|
213
|
+
...(opts.columns !== undefined ? { columns: opts.columns.split(",").map((s) => s.trim()).filter(Boolean) } : {}),
|
|
214
|
+
...(opts.notesFile ? { notes: await readFile(opts.notesFile, "utf8") } : {}),
|
|
215
|
+
};
|
|
216
|
+
if (Object.keys(input).length === 0)
|
|
217
|
+
die("nothing to set — pass --title, --agent, --stale-after, --columns or --notes-file");
|
|
218
|
+
const b = await saveBoard({ ...(opts.project ? { project: opts.project } : {}), board }, input);
|
|
219
|
+
if (opts.json)
|
|
220
|
+
console.log(JSON.stringify(b, null, 2));
|
|
221
|
+
else
|
|
222
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(b.board)} saved`);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
die(err.message);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
boards
|
|
229
|
+
.command("remove")
|
|
230
|
+
.description("Move a board, tasks and all, to the trash")
|
|
231
|
+
.argument("<board>", "Board slug")
|
|
232
|
+
.option("--project <slug>", "The project the board belongs to")
|
|
233
|
+
.action(async (board, opts) => {
|
|
234
|
+
await prepare();
|
|
235
|
+
try {
|
|
236
|
+
await deleteBoard({ ...(opts.project ? { project: opts.project } : {}), board });
|
|
237
|
+
console.log(`${chalk.green("✔")} moved ${chalk.cyan(board)} to the trash`);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
die(err.message);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
const tasks = program.command("tasks").description("Tasks on a board: list, show, create, set, move, assign, log, pick, remove");
|
|
244
|
+
const where = (cmd) => cmd.option("--board <slug>", "The board (default: the project's `tasks` board, or the only one)").option("--project <slug>", "The project the board belongs to");
|
|
245
|
+
where(tasks.command("list", { isDefault: true }))
|
|
246
|
+
.description("List the tasks of a board, column by column")
|
|
247
|
+
.option("--status <column>", "Only this column")
|
|
248
|
+
.option("--assignee <agent>", 'Only this agent\'s ("" for unassigned)')
|
|
249
|
+
.option("--mine", "Only the tasks of the agent this session runs as (KRAFTWERK_AGENT)")
|
|
250
|
+
.option("--as <agent>", "With --mine: which agent")
|
|
251
|
+
.option("--json", "Machine-readable output")
|
|
252
|
+
.action(async (opts) => {
|
|
253
|
+
await prepare();
|
|
254
|
+
try {
|
|
255
|
+
const ref = await whereOf(opts);
|
|
256
|
+
const b = await getBoard(ref);
|
|
257
|
+
if (!b)
|
|
258
|
+
die(`no board "${ref.board}"`);
|
|
259
|
+
const me = opts.mine ? agentOf(opts.as) || die("--mine needs --as <agent> outside an agent session") : undefined;
|
|
260
|
+
const items = b.items.filter((t) => (opts.status === undefined || t.status === opts.status) && (opts.assignee === undefined || (t.assignee ?? "") === opts.assignee) && (me === undefined || t.assignee === me));
|
|
261
|
+
if (opts.json) {
|
|
262
|
+
console.log(JSON.stringify({ board: ref, columns: b.columns, items }, null, 2));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (items.length === 0) {
|
|
266
|
+
console.log(chalk.dim(`No tasks on ${b.title}${opts.status ? ` in ${opts.status}` : ""}.`));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const table = new Table({ head: ["#", "task", "column", "assignee", "due", "last", "changed"].map((h) => chalk.bold(h)), wordWrap: true, colWidths: [5, 36, 11, 12, 12, 30, 12] });
|
|
270
|
+
for (const t of items)
|
|
271
|
+
table.push(taskRow(t));
|
|
272
|
+
console.log(table.toString());
|
|
273
|
+
console.log(chalk.dim(`${b.title} (${flags(ref)})`));
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
die(err.message);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
where(tasks.command("show"))
|
|
280
|
+
.description("One task: fields, markdown, folder path, files and log")
|
|
281
|
+
.argument("<task>", "Task number or folder name")
|
|
282
|
+
.option("--json", "Machine-readable output")
|
|
283
|
+
.action(async (task, opts) => {
|
|
284
|
+
await prepare();
|
|
285
|
+
try {
|
|
286
|
+
const found = await getTask(await whereOf(opts), task);
|
|
287
|
+
if (!found)
|
|
288
|
+
die(`no task "${task}"`);
|
|
289
|
+
const t = { ...found, sessions: await taskSessions(found.ref).catch(() => []) };
|
|
290
|
+
if (opts.json)
|
|
291
|
+
console.log(JSON.stringify(t, null, 2));
|
|
292
|
+
else
|
|
293
|
+
printTask(t);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
die(err.message);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
where(tasks.command("create"))
|
|
300
|
+
.description("Create a task (a numbered folder with task.md and log.md); --assign with --status todo wakes the agent")
|
|
301
|
+
.argument("<title>", "The task's title")
|
|
302
|
+
.option("--status <column>", "The column it starts in (default: the first, draft)")
|
|
303
|
+
.option("--assign <agent>", "Assignee (an agent slug)")
|
|
304
|
+
.option("--due <date>", "Due date, 2026-11-20")
|
|
305
|
+
.option("--tags <list>", "Comma-separated tags")
|
|
306
|
+
.option("--refs <list>", "Comma-separated kind:slug references (contact:anna-meier, knowledge:brand-voice)")
|
|
307
|
+
.option("--file <path>", "The task's markdown (what, done-when, notes) from a file")
|
|
308
|
+
.option("--notes <text>", "The task's markdown inline")
|
|
309
|
+
.option("-m, --message <text>", "A first log line")
|
|
310
|
+
.option("--actor <actor>", "Who writes (default: KRAFTWERK_ACTOR, else human:user)")
|
|
311
|
+
.option("--json", "Print the task as JSON")
|
|
312
|
+
.action(async (title, opts) => {
|
|
313
|
+
await prepare();
|
|
314
|
+
try {
|
|
315
|
+
const ref = await whereOf(opts, true);
|
|
316
|
+
const t = await createTask(ref, {
|
|
317
|
+
title,
|
|
318
|
+
status: opts.status,
|
|
319
|
+
assignee: opts.assign,
|
|
320
|
+
due: opts.due,
|
|
321
|
+
tags: opts.tags,
|
|
322
|
+
refs: opts.refs,
|
|
323
|
+
notes: opts.file ? await readFile(opts.file, "utf8") : opts.notes,
|
|
324
|
+
actor: actorOf(opts.actor),
|
|
325
|
+
message: opts.message,
|
|
326
|
+
});
|
|
327
|
+
if (opts.json)
|
|
328
|
+
console.log(JSON.stringify(t, null, 2));
|
|
329
|
+
else
|
|
330
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(String(t.number))} ${t.title} ${chalk.dim(`(${t.status}${t.assignee ? `, ${t.assignee}` : ""}) → ${t.path}`)}`);
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
die(err.message);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
where(tasks.command("set"))
|
|
337
|
+
.description("Change a task's title, due date, tags, refs or markdown")
|
|
338
|
+
.argument("<task>", "Task number or folder name")
|
|
339
|
+
.option("--title <text>", "New title (the folder keeps its name)")
|
|
340
|
+
.option("--due <date>", 'Due date; "" clears it')
|
|
341
|
+
.option("--tags <list>", "Comma-separated tags (replaces)")
|
|
342
|
+
.option("--refs <list>", "Comma-separated kind:slug references (replaces)")
|
|
343
|
+
.option("--notes-file <path>", "Replace the markdown with this file's text")
|
|
344
|
+
.option("--actor <actor>", "Who writes")
|
|
345
|
+
.option("--json", "Print the task as JSON")
|
|
346
|
+
.action(async (task, opts) => {
|
|
347
|
+
await prepare();
|
|
348
|
+
try {
|
|
349
|
+
const input = {
|
|
350
|
+
...(opts.title !== undefined ? { title: opts.title } : {}),
|
|
351
|
+
...(opts.due !== undefined ? { due: opts.due || null } : {}),
|
|
352
|
+
...(opts.tags !== undefined ? { tags: opts.tags } : {}),
|
|
353
|
+
...(opts.refs !== undefined ? { refs: opts.refs } : {}),
|
|
354
|
+
...(opts.notesFile ? { notes: await readFile(opts.notesFile, "utf8") } : {}),
|
|
355
|
+
};
|
|
356
|
+
if (Object.keys(input).length === 0)
|
|
357
|
+
die("nothing to set — pass --title, --due, --tags, --refs or --notes-file");
|
|
358
|
+
const t = await saveTask(await whereOf(opts), task, input, { actor: actorOf(opts.actor) });
|
|
359
|
+
if (opts.json)
|
|
360
|
+
console.log(JSON.stringify(t, null, 2));
|
|
361
|
+
else
|
|
362
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(String(t.number))} ${t.title} saved`);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
die(err.message);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
where(tasks.command("move"))
|
|
369
|
+
.description("Put a task into a column; the move is logged with -m, and an assigned agent entering a work column is woken")
|
|
370
|
+
.argument("<task>", "Task number or folder name")
|
|
371
|
+
.argument("<column>", "Column slug")
|
|
372
|
+
.option("-m, --message <text>", "Why, for the log")
|
|
373
|
+
.option("--actor <actor>", "Who writes")
|
|
374
|
+
.option("--json", "Print the task as JSON")
|
|
375
|
+
.action(async (task, column, opts) => {
|
|
376
|
+
await prepare();
|
|
377
|
+
try {
|
|
378
|
+
const t = await moveTask(await whereOf(opts), task, column, { message: opts.message, actor: actorOf(opts.actor) });
|
|
379
|
+
if (opts.json)
|
|
380
|
+
console.log(JSON.stringify(t, null, 2));
|
|
381
|
+
else
|
|
382
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(String(t.number))} ${t.title} → ${t.status}`);
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
die(err.message);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
where(tasks.command("assign"))
|
|
389
|
+
.description('Hand a task to an agent (woken when the task is in a work column), or back to the humans with "human" (unassigned, into review)')
|
|
390
|
+
.argument("<task>", "Task number or folder name")
|
|
391
|
+
.argument("[agent]", 'Agent slug, or "human"; omit with --to')
|
|
392
|
+
.option("--to <agent>", 'Same as the argument: an agent slug or "human"')
|
|
393
|
+
.option("-m, --message <text>", "The hand-over note, for the log and the next agent")
|
|
394
|
+
.option("--actor <actor>", "Who writes")
|
|
395
|
+
.option("--json", "Print the task as JSON")
|
|
396
|
+
.action(async (task, agent, opts) => {
|
|
397
|
+
await prepare();
|
|
398
|
+
const to = (opts.to ?? agent ?? "").trim();
|
|
399
|
+
if (!to)
|
|
400
|
+
die('say who: `tasks assign <n> <agent>` or `--to human`');
|
|
401
|
+
try {
|
|
402
|
+
const t = await assignTask(await whereOf(opts), task, to === "human" ? null : to, { message: opts.message, actor: actorOf(opts.actor) });
|
|
403
|
+
if (opts.json)
|
|
404
|
+
console.log(JSON.stringify(t, null, 2));
|
|
405
|
+
else
|
|
406
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(String(t.number))} ${t.title} → ${t.assignee ?? `humans (${t.status})`}`);
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
die(err.message);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
where(tasks.command("log"))
|
|
413
|
+
.description("Append a stamped line to the task's log (what you did, a question, a decision), or print the last lines with --tail")
|
|
414
|
+
.argument("<task>", "Task number or folder name")
|
|
415
|
+
.argument("[entry]", "The line to record")
|
|
416
|
+
.option("--file <path>", "Read the entry from a file")
|
|
417
|
+
.option("--tail <n>", "Print the newest n lines instead of writing")
|
|
418
|
+
.option("--actor <actor>", "Who writes (default: KRAFTWERK_ACTOR, else human:user)")
|
|
419
|
+
.action(async (task, entry, opts) => {
|
|
420
|
+
await prepare();
|
|
421
|
+
try {
|
|
422
|
+
const ref = await whereOf(opts);
|
|
423
|
+
if (opts.tail !== undefined) {
|
|
424
|
+
const t = await getTask(ref, task);
|
|
425
|
+
if (!t)
|
|
426
|
+
die(`no task "${task}"`);
|
|
427
|
+
const n = Number(opts.tail) || 5;
|
|
428
|
+
console.log(t.log
|
|
429
|
+
.split("\n")
|
|
430
|
+
.filter((l) => l.startsWith("* ") || l.startsWith("## "))
|
|
431
|
+
.slice(0, n * 2)
|
|
432
|
+
.join("\n"));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const text = opts.file ? await readFile(opts.file, "utf8") : entry ?? "";
|
|
436
|
+
const t = await appendTaskLog(ref, task, text, actorOf(opts.actor));
|
|
437
|
+
console.log(`${chalk.green("✔")} logged on ${chalk.cyan(String(t.number))} ${chalk.dim(`(${actorOf(opts.actor)})`)}`);
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
die(err.message);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
where(tasks.command("pick"))
|
|
444
|
+
.description("Take the next task of the pick column (todo): it becomes yours and moves to the work column; prints nothing and exits 3 when there is none")
|
|
445
|
+
.option("--as <agent>", "Which agent picks (default: KRAFTWERK_AGENT)")
|
|
446
|
+
.option("--column <slug>", "Pick from this column instead of todo")
|
|
447
|
+
.option("--json", "Print the task as JSON")
|
|
448
|
+
.action(async (opts) => {
|
|
449
|
+
await prepare();
|
|
450
|
+
const agent = agentOf(opts.as);
|
|
451
|
+
if (!agent)
|
|
452
|
+
die("who picks? pass --as <agent> (inside an agent session KRAFTWERK_AGENT is set)");
|
|
453
|
+
try {
|
|
454
|
+
const t = await pickTask(await whereOf(opts), agent, { column: opts.column, actor: process.env.KRAFTWERK_ACTOR || agent });
|
|
455
|
+
if (!t) {
|
|
456
|
+
if (opts.json)
|
|
457
|
+
console.log("null");
|
|
458
|
+
else
|
|
459
|
+
console.log(chalk.dim("nothing to pick"));
|
|
460
|
+
process.exit(3);
|
|
461
|
+
}
|
|
462
|
+
if (opts.json)
|
|
463
|
+
console.log(JSON.stringify(t, null, 2));
|
|
464
|
+
else {
|
|
465
|
+
console.log(`${chalk.green("✔")} picked ${chalk.cyan(String(t.number))} ${t.title} → ${t.status}`);
|
|
466
|
+
printTask(t);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
die(err.message);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
where(tasks.command("remove"))
|
|
474
|
+
.description("Move a task folder, files and log included, to the trash")
|
|
475
|
+
.argument("<task>", "Task number or folder name")
|
|
476
|
+
.action(async (task, opts) => {
|
|
477
|
+
await prepare();
|
|
478
|
+
try {
|
|
479
|
+
await deleteTask(await whereOf(opts), task);
|
|
480
|
+
console.log(`${chalk.green("✔")} moved task ${chalk.cyan(task)} to the trash`);
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
die(err.message);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
package/dist/cli/init.js
CHANGED
|
@@ -34,6 +34,7 @@ skills: ${DATA_DIR}/skills # workspace skills (shared instruction packag
|
|
|
34
34
|
# contacts: # people and organisations, one Markdown file each under ${DATA_DIR}/contacts — referenced as contact:<slug> (uncomment to enable)
|
|
35
35
|
# places: # addresses and points of interest, one Markdown file each under ${DATA_DIR}/places — referenced as place:<slug> (uncomment to enable)
|
|
36
36
|
# events: # dated one-off entries with place, contacts and project, one Markdown file each under ${DATA_DIR}/events — referenced as event:<slug> (uncomment to enable)
|
|
37
|
+
# boards: # tasks as folders in columns (Draft, Todo, In progress, Review, Done) per project and under ${DATA_DIR}/boards — agents are woken when a task is assigned to them (uncomment to enable)
|
|
37
38
|
# cloud: # the daemon registers this MACHINE with the kraftwerk cloud and lists this workspace (\`kraftwerk login\` claims the machine)
|
|
38
39
|
# enabled: false # uncomment both lines to leave this workspace out (\`kraftwerk cloud off\` turns the cloud off for the whole machine)
|
|
39
40
|
`;
|
package/dist/cli/kraftwerk.js
CHANGED
|
@@ -13,6 +13,7 @@ import { runDoctor } from "./doctor.js";
|
|
|
13
13
|
import { runInit } from "./init.js";
|
|
14
14
|
import { registerKnowledgeCommands } from "./knowledge.js";
|
|
15
15
|
import { registerProjectCommands } from "./projects.js";
|
|
16
|
+
import { registerBoardCommands } from "./boards.js";
|
|
16
17
|
import { registerContactCommands, registerEventCommands, registerPlaceCommands, registerRefCommand } from "./entities.js";
|
|
17
18
|
import { registerWorkspaceCommands } from "./workspaces.js";
|
|
18
19
|
import { registerRepoCommands } from "./repos.js";
|
|
@@ -44,6 +45,7 @@ import { runUi } from "./ui.js";
|
|
|
44
45
|
* kraftwerk ui start the inspector web UI (localhost:1981)
|
|
45
46
|
* kraftwerk workspaces ... known workspaces on this machine (list/start/stop/forget)
|
|
46
47
|
* kraftwerk projects ... goal-scoped project folders (list/create/show/link/log/remove)
|
|
48
|
+
* kraftwerk boards|tasks ... task boards per project or workspace: list/show/create; tasks list/show/create/move/assign/log/pick/remove
|
|
47
49
|
* kraftwerk contacts|places|events who, where and when: one Markdown file each (list/show/create/set/remove)
|
|
48
50
|
* kraftwerk ref <kind:slug> resolve a reference (contact:anna-meier) or every one in a file
|
|
49
51
|
* kraftwerk vibeables ... small apps built live in a chat (list/create/remove)
|
|
@@ -304,6 +306,7 @@ registerKnowledgeCommands(program);
|
|
|
304
306
|
registerRoutineCommands(program);
|
|
305
307
|
registerWorkspaceCommands(program);
|
|
306
308
|
registerProjectCommands(program);
|
|
309
|
+
registerBoardCommands(program);
|
|
307
310
|
registerContactCommands(program);
|
|
308
311
|
registerPlaceCommands(program);
|
|
309
312
|
registerEventCommands(program);
|
package/dist/client/routes.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export declare const ROUTES: {
|
|
|
20
20
|
readonly "agents.skills": readonly ["GET", "/api/agents/:slug/skills"];
|
|
21
21
|
readonly "agents.status": readonly ["GET", "/api/agent-status"];
|
|
22
22
|
readonly "attention.list": readonly ["GET", "/api/attention"];
|
|
23
|
+
readonly "boards.create": readonly ["POST", "/api/boards"];
|
|
24
|
+
readonly "boards.delete": readonly ["DELETE", "/api/boards/:board"];
|
|
25
|
+
readonly "boards.ensure": readonly ["POST", "/api/boards/default"];
|
|
26
|
+
readonly "boards.get": readonly ["GET", "/api/boards/:board"];
|
|
27
|
+
readonly "boards.list": readonly ["GET", "/api/boards"];
|
|
28
|
+
readonly "boards.save": readonly ["PUT", "/api/boards/:board"];
|
|
23
29
|
readonly "channels.create": readonly ["POST", "/api/channels"];
|
|
24
30
|
readonly "channels.delete": readonly ["DELETE", "/api/channels/:slug"];
|
|
25
31
|
readonly "channels.fromChat": readonly ["POST", "/api/channels/from-chat"];
|
|
@@ -136,6 +142,14 @@ export declare const ROUTES: {
|
|
|
136
142
|
readonly "settings.save": readonly ["PUT", "/api/settings"];
|
|
137
143
|
readonly "skills.get": readonly ["GET", "/api/skills/:name"];
|
|
138
144
|
readonly "skills.list": readonly ["GET", "/api/skills"];
|
|
145
|
+
readonly "tasks.assign": readonly ["POST", "/api/boards/:board/tasks/:task/assign"];
|
|
146
|
+
readonly "tasks.create": readonly ["POST", "/api/boards/:board/tasks"];
|
|
147
|
+
readonly "tasks.delete": readonly ["DELETE", "/api/boards/:board/tasks/:task"];
|
|
148
|
+
readonly "tasks.get": readonly ["GET", "/api/boards/:board/tasks/:task"];
|
|
149
|
+
readonly "tasks.log": readonly ["POST", "/api/boards/:board/tasks/:task/log"];
|
|
150
|
+
readonly "tasks.move": readonly ["POST", "/api/boards/:board/tasks/:task/move"];
|
|
151
|
+
readonly "tasks.pick": readonly ["POST", "/api/boards/:board/pick"];
|
|
152
|
+
readonly "tasks.save": readonly ["PUT", "/api/boards/:board/tasks/:task"];
|
|
139
153
|
readonly "temp.clean": readonly ["DELETE", "/api/temp"];
|
|
140
154
|
readonly "temp.list": readonly ["GET", "/api/temp"];
|
|
141
155
|
readonly "trash.empty": readonly ["DELETE", "/api/trash"];
|
package/dist/client/routes.js
CHANGED
|
@@ -21,6 +21,12 @@ export const ROUTES = {
|
|
|
21
21
|
"agents.skills": ["GET", "/api/agents/:slug/skills"],
|
|
22
22
|
"agents.status": ["GET", "/api/agent-status"],
|
|
23
23
|
"attention.list": ["GET", "/api/attention"],
|
|
24
|
+
"boards.create": ["POST", "/api/boards"],
|
|
25
|
+
"boards.delete": ["DELETE", "/api/boards/:board"],
|
|
26
|
+
"boards.ensure": ["POST", "/api/boards/default"],
|
|
27
|
+
"boards.get": ["GET", "/api/boards/:board"],
|
|
28
|
+
"boards.list": ["GET", "/api/boards"],
|
|
29
|
+
"boards.save": ["PUT", "/api/boards/:board"],
|
|
24
30
|
"channels.create": ["POST", "/api/channels"],
|
|
25
31
|
"channels.delete": ["DELETE", "/api/channels/:slug"],
|
|
26
32
|
"channels.fromChat": ["POST", "/api/channels/from-chat"],
|
|
@@ -137,6 +143,14 @@ export const ROUTES = {
|
|
|
137
143
|
"settings.save": ["PUT", "/api/settings"],
|
|
138
144
|
"skills.get": ["GET", "/api/skills/:name"],
|
|
139
145
|
"skills.list": ["GET", "/api/skills"],
|
|
146
|
+
"tasks.assign": ["POST", "/api/boards/:board/tasks/:task/assign"],
|
|
147
|
+
"tasks.create": ["POST", "/api/boards/:board/tasks"],
|
|
148
|
+
"tasks.delete": ["DELETE", "/api/boards/:board/tasks/:task"],
|
|
149
|
+
"tasks.get": ["GET", "/api/boards/:board/tasks/:task"],
|
|
150
|
+
"tasks.log": ["POST", "/api/boards/:board/tasks/:task/log"],
|
|
151
|
+
"tasks.move": ["POST", "/api/boards/:board/tasks/:task/move"],
|
|
152
|
+
"tasks.pick": ["POST", "/api/boards/:board/pick"],
|
|
153
|
+
"tasks.save": ["PUT", "/api/boards/:board/tasks/:task"],
|
|
140
154
|
"temp.clean": ["DELETE", "/api/temp"],
|
|
141
155
|
"temp.list": ["GET", "/api/temp"],
|
|
142
156
|
"trash.empty": ["DELETE", "/api/trash"],
|