@netnodeag/kraftwerk 0.80.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 +57 -2
- package/dist/cli/boards.d.ts +2 -0
- package/dist/cli/boards.js +486 -0
- package/dist/cli/init.js +4 -2
- package/dist/cli/kraftwerk.js +6 -0
- package/dist/cli/temp.d.ts +2 -0
- package/dist/cli/temp.js +62 -0
- package/dist/client/routes.d.ts +16 -0
- package/dist/client/routes.js +16 -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 +3 -1
- package/dist/core/chat/backend.d.ts +13 -0
- package/dist/core/chat/backend.js +9 -1
- package/dist/core/chat/context.js +26 -9
- package/dist/core/chat/pi.d.ts +1 -1
- package/dist/core/chat/pi.js +3 -1
- package/dist/core/chat/seats.js +6 -2
- package/dist/core/chat/sessions.d.ts +2 -0
- package/dist/core/chat/sessions.js +4 -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 +20 -1
- package/dist/core/git.js +3 -1
- package/dist/core/notifications.d.ts +1 -1
- package/dist/core/projects.js +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/temp.d.ts +73 -0
- package/dist/core/temp.js +154 -0
- package/dist/core/trash.d.ts +1 -1
- package/dist/harness.d.ts +2 -0
- package/dist/harnesses/acp.js +1 -1
- package/dist/harnesses/claude.js +1 -0
- package/dist/harnesses/codex.js +1 -0
- package/dist/harnesses/pi.js +1 -0
- package/dist/run.d.ts +7 -0
- package/dist/run.js +13 -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/files.d.ts +9 -1
- package/dist/server/api/files.js +13 -0
- package/dist/server/api/index.d.ts +113 -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/dist/yaml.js +1 -0
- package/package.json +1 -1
- package/schema/kraftwerk.schema.json +39 -0
- package/web/dist/assets/{dist-DlZPeneT.js → dist-BBFUIftm.js} +1 -1
- package/web/dist/assets/{dist-u_oAKIhZ.js → dist-Bie1ebP9.js} +1 -1
- package/web/dist/assets/{dist-NX4_yvho.js → dist-BuwdjX6z.js} +1 -1
- package/web/dist/assets/{dist-1PajaYPg.js → dist-C0ve4Tka.js} +1 -1
- package/web/dist/assets/{dist-DgKgeYFJ.js → dist-C16bXf8c.js} +1 -1
- package/web/dist/assets/{dist-DKduisGW.js → dist-CDUZdAAS.js} +1 -1
- package/web/dist/assets/{dist-B4dqwo7_.js → dist-CH73jsgO.js} +1 -1
- package/web/dist/assets/{dist-D-f0nszo.js → dist-C_rJjRj7.js} +1 -1
- package/web/dist/assets/dist-DYICNp09.js +1 -0
- package/web/dist/assets/{dist-K_3Y5Nbl.js → dist-DtUB2Uu2.js} +1 -1
- package/web/dist/assets/{dist-CK2u1OXb.js → dist-DvkrCSGL.js} +1 -1
- package/web/dist/assets/{editor-CE9TlKkJ.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-Bp1iMuQ7.js +0 -1
- package/web/dist/assets/index-Dvt-vE1h.css +0 -2
- package/web/dist/assets/index-IAuwD22j.js +0 -231
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
|
|
@@ -300,7 +301,9 @@ Optional, at the project root, and also the root marker for the walk-up. All
|
|
|
300
301
|
fields are optional. `workflows:` sets the workflows root, `output:` the
|
|
301
302
|
run-artifact directory (default `output/`), `knowledge:` the OKF bundle root
|
|
302
303
|
(default `knowledge/`), and `agents:` the agent-definition root (default
|
|
303
|
-
`agents/`). `repos:` turns on the [repositories](#repositories) folder
|
|
304
|
+
`agents/`). `repos:` turns on the [repositories](#repositories) folder,
|
|
305
|
+
`projects:` the [projects](#projects), and `contacts:`, `places:` and
|
|
306
|
+
`events:` each one of the [contacts, places and events](#contacts-places-and-events).
|
|
304
307
|
`cloud: { enabled: false }` leaves the workspace out of this machine's
|
|
305
308
|
record in the [kraftwerk cloud](#this-machine-in-the-kraftwerk-cloud).
|
|
306
309
|
|
|
@@ -342,7 +345,8 @@ projects (all but archived ones) and the channels of every workspace on this
|
|
|
342
345
|
machine, grouped by workspace and running or not (a stopped one is started
|
|
343
346
|
on the way), read from the workspace registry under `~/.kraftwerk/workspaces`,
|
|
344
347
|
which every inspector keeps current with its roster, project and channel
|
|
345
|
-
lists
|
|
348
|
+
lists — and this workspace's contacts, places and events, by title, role,
|
|
349
|
+
organisation, address or date.
|
|
346
350
|
|
|
347
351
|
### Triggering from CI, cron, or webhooks
|
|
348
352
|
|
|
@@ -631,6 +635,57 @@ kraftwerk projects remove relaunch-netnode-ch
|
|
|
631
635
|
The registry of workspaces on this machine, which answered to
|
|
632
636
|
`kraftwerk projects` until 0.48, is `kraftwerk workspaces` now.
|
|
633
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
|
+
|
|
634
689
|
## Contacts, places and events
|
|
635
690
|
|
|
636
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
|
`;
|
|
@@ -139,11 +140,12 @@ export async function runInit(cwd) {
|
|
|
139
140
|
created.push(bundleRel);
|
|
140
141
|
}
|
|
141
142
|
// .gitignore: the output dir, the repos root (clones must never become
|
|
142
|
-
// gitlinks of the workspace repo)
|
|
143
|
+
// gitlinks of the workspace repo), the temp root (session scratch, which
|
|
144
|
+
// also carries a .gitignore of its own) and .env (secrets). A missing file gets
|
|
143
145
|
// all in one write; an existing one only the entries it lacks.
|
|
144
146
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
145
147
|
const gitignore = (await readFile(gitignorePath, "utf8").catch(() => null)) ?? null;
|
|
146
|
-
const entries = [`${DATA_DIR}/output/`, `${DATA_DIR}/repos/`, DOTENV_FILE];
|
|
148
|
+
const entries = [`${DATA_DIR}/output/`, `${DATA_DIR}/repos/`, `${DATA_DIR}/temp/`, DOTENV_FILE];
|
|
147
149
|
if (gitignore === null) {
|
|
148
150
|
await writeFile(gitignorePath, entries.map((e) => `${e}\n`).join(""));
|
|
149
151
|
created.push(".gitignore");
|
package/dist/cli/kraftwerk.js
CHANGED
|
@@ -13,11 +13,13 @@ 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";
|
|
19
20
|
import { registerVibeableCommands } from "./vibeables.js";
|
|
20
21
|
import { registerTrashCommands } from "./trash.js";
|
|
22
|
+
import { registerTempCommands } from "./temp.js";
|
|
21
23
|
import { registerDevicesCommands } from "./devices.js";
|
|
22
24
|
import { registerDaemonCommand } from "./daemon.js";
|
|
23
25
|
import { registerRemoteCommand } from "./remote.js";
|
|
@@ -43,12 +45,14 @@ import { runUi } from "./ui.js";
|
|
|
43
45
|
* kraftwerk ui start the inspector web UI (localhost:1981)
|
|
44
46
|
* kraftwerk workspaces ... known workspaces on this machine (list/start/stop/forget)
|
|
45
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
|
|
46
49
|
* kraftwerk contacts|places|events who, where and when: one Markdown file each (list/show/create/set/remove)
|
|
47
50
|
* kraftwerk ref <kind:slug> resolve a reference (contact:anna-meier) or every one in a file
|
|
48
51
|
* kraftwerk vibeables ... small apps built live in a chat (list/create/remove)
|
|
49
52
|
* kraftwerk repos ... repositories the agents work on (list/add/update/remove)
|
|
50
53
|
* kraftwerk journal <agent> [entry] an agent's memory across sessions: print it, add a line
|
|
51
54
|
* kraftwerk trash ... what was deleted: list, restore, purge, empty
|
|
55
|
+
* kraftwerk temp [clean] the sessions' scratch folders (TMPDIR of every agent): list, clean
|
|
52
56
|
* kraftwerk devices ... paired devices: list, pair one, revoke one
|
|
53
57
|
* kraftwerk daemon the machine's kraftwerk: every open workspace at <slug>.localhost
|
|
54
58
|
* kraftwerk cloud [on|off|status] this machine in the kraftwerk cloud (the daemon registers it)
|
|
@@ -302,6 +306,7 @@ registerKnowledgeCommands(program);
|
|
|
302
306
|
registerRoutineCommands(program);
|
|
303
307
|
registerWorkspaceCommands(program);
|
|
304
308
|
registerProjectCommands(program);
|
|
309
|
+
registerBoardCommands(program);
|
|
305
310
|
registerContactCommands(program);
|
|
306
311
|
registerPlaceCommands(program);
|
|
307
312
|
registerEventCommands(program);
|
|
@@ -309,6 +314,7 @@ registerRefCommand(program);
|
|
|
309
314
|
registerRepoCommands(program);
|
|
310
315
|
registerVibeableCommands(program);
|
|
311
316
|
registerTrashCommands(program);
|
|
317
|
+
registerTempCommands(program);
|
|
312
318
|
registerDevicesCommands(program);
|
|
313
319
|
registerDaemonCommand(program);
|
|
314
320
|
registerRemoteCommand(program);
|