@netnodeag/kraftwerk 0.35.0 → 0.37.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 +65 -20
- package/dist/cli/doctor.js +18 -1
- package/dist/cli/init.js +19 -12
- package/dist/cli/kraftwerk.js +3 -0
- package/dist/cli/projects.d.ts +14 -0
- package/dist/cli/projects.js +1 -1
- package/dist/cli/repos.d.ts +2 -0
- package/dist/cli/repos.js +115 -0
- package/dist/cli/routines.d.ts +10 -0
- package/dist/cli/routines.js +10 -10
- package/dist/config.d.ts +29 -2
- package/dist/config.js +56 -2
- package/dist/inspector/{team.d.ts → agents.d.ts} +21 -12
- package/dist/inspector/{team.js → agents.js} +34 -19
- package/dist/inspector/chat/backend.d.ts +1 -1
- package/dist/inspector/chat/sessions.js +72 -45
- package/dist/inspector/chat/store.js +13 -1
- package/dist/inspector/chat/types.d.ts +2 -2
- package/dist/inspector/git.d.ts +14 -0
- package/dist/inspector/git.js +11 -9
- package/dist/inspector/instances.d.ts +12 -0
- package/dist/inspector/instances.js +28 -0
- package/dist/inspector/repos.d.ts +118 -0
- package/dist/inspector/repos.js +249 -0
- package/dist/inspector/routines.d.ts +4 -4
- package/dist/inspector/routines.js +11 -11
- package/dist/inspector/search.d.ts +25 -0
- package/dist/inspector/search.js +31 -0
- package/dist/inspector/server.js +77 -30
- package/dist/inspector/settings.d.ts +7 -1
- package/dist/inspector/settings.js +32 -1
- package/dist/inspector/skills.d.ts +6 -6
- package/dist/inspector/skills.js +8 -8
- package/inspector/dist/assets/{editor-BABv2LmA.js → editor-vM6JSj5h.js} +3 -3
- package/inspector/dist/assets/{index-CCACy6uF.js → index-BAAjszPI.js} +1 -1
- package/inspector/dist/assets/{index-DuwmzZAr.js → index-BKcOp35r.js} +1 -1
- package/inspector/dist/assets/index-Ba8VU6Wz.js +217 -0
- package/inspector/dist/assets/{index-Ca9Qs5LZ.js → index-Bb08wK-l.js} +1 -1
- package/inspector/dist/assets/{index-ByWpqzAI.js → index-BrCwDkJZ.js} +1 -1
- package/inspector/dist/assets/{index-hkVov8T4.js → index-Bs1U0rWW.js} +1 -1
- package/inspector/dist/assets/{index-CdwTmXTV.js → index-C1UXlW32.js} +1 -1
- package/inspector/dist/assets/{index-HBdsdJhD.js → index-CQ2XKqmD.js} +1 -1
- package/inspector/dist/assets/{index-DCR7u4Xa.js → index-CReepirH.js} +1 -1
- package/inspector/dist/assets/{index-CzPQygR7.js → index-CUGfxoYt.js} +1 -1
- package/inspector/dist/assets/{index-sLxulPOm.js → index-CaKmxcD6.js} +1 -1
- package/inspector/dist/assets/{index-Bmm4zJNU.js → index-CtMGH62C.js} +1 -1
- package/inspector/dist/assets/{index-BXspF2KR.css → index-D4_mLTSH.css} +1 -1
- package/inspector/dist/assets/{index-BbTer62d.js → index-D_11IZBV.js} +1 -1
- package/inspector/dist/assets/{index-wkYwz0Tl.js → index-DfZT0IoM.js} +1 -1
- package/inspector/dist/assets/{index-Gx93brFq.js → index-DykDNrYm.js} +1 -1
- package/inspector/dist/assets/{index-DuPp3MUc.js → index-MWv5H1oP.js} +1 -1
- package/inspector/dist/assets/{index-CjlJeW9z.js → index-oIQqfbwm.js} +1 -1
- package/inspector/dist/assets/{index-DbWUNbt-.js → index-yGZsomcy.js} +1 -1
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/schema/kraftwerk.schema.json +15 -0
- package/inspector/dist/assets/index-uEig4--d.js +0 -217
package/README.md
CHANGED
|
@@ -179,8 +179,8 @@ different host.
|
|
|
179
179
|
Optional, at the project root, and also the root marker for the walk-up. All
|
|
180
180
|
fields are optional. `workflows:` sets the workflows root, `output:` the
|
|
181
181
|
run-artifact directory (default `output/`), `knowledge:` the OKF bundle root
|
|
182
|
-
(default `knowledge/`), and `agents:` the
|
|
183
|
-
`agents/`).
|
|
182
|
+
(default `knowledge/`), and `agents:` the agent-definition root (default
|
|
183
|
+
`agents/`). `repos:` turns on the [repositories](#repositories) folder.
|
|
184
184
|
|
|
185
185
|
`switcher:` links other kraftwerk workspaces from the inspector header. The
|
|
186
186
|
workspace name becomes a dropdown listing them:
|
|
@@ -192,6 +192,13 @@ switcher:
|
|
|
192
192
|
icon: "🛰" # optional
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
**⌘K** (Ctrl K) anywhere in the inspector opens the agent palette: type a
|
|
196
|
+
few letters of an agent's name, description or workspace and hit enter to
|
|
197
|
+
jump to it. It lists the active agents of every workspace on this machine,
|
|
198
|
+
running or not (a stopped one is started on the way), read from the
|
|
199
|
+
project registry under `~/.kraftwerk/projects`, which every inspector keeps
|
|
200
|
+
current with its roster.
|
|
201
|
+
|
|
195
202
|
### Triggering from CI, cron, or webhooks
|
|
196
203
|
|
|
197
204
|
`run --json` is the machine mode. It runs non-interactively, prints one JSON
|
|
@@ -249,15 +256,15 @@ repo. Agent logins made inside the container persist in the `agent-home`
|
|
|
249
256
|
volume. This is a different image from the `kraftwerk-runner` sandbox
|
|
250
257
|
(`runner/Dockerfile`) used by `run --sandbox`.
|
|
251
258
|
|
|
252
|
-
## Persistent
|
|
259
|
+
## Persistent agents
|
|
253
260
|
|
|
254
|
-
The inspector's "
|
|
255
|
-
|
|
261
|
+
The inspector's "agents" screen turns chat agents into persistent teammates. An
|
|
262
|
+
agent is one folder under the project's `agents/` root:
|
|
256
263
|
|
|
257
264
|
```
|
|
258
265
|
agents/max/
|
|
259
266
|
agent.yml # name, emoji, description, harness, model, effort, workflows
|
|
260
|
-
system.md # the
|
|
267
|
+
system.md # the agent's system prompt (its role)
|
|
261
268
|
```
|
|
262
269
|
|
|
263
270
|
```yaml
|
|
@@ -269,43 +276,81 @@ harness: claude # claude | codex | pi (which chat backend runs it)
|
|
|
269
276
|
model: sonnet # optional; harness default when omitted
|
|
270
277
|
effort: medium # optional: low | medium | high | xhigh | max
|
|
271
278
|
workflows: [tagline, website-check]
|
|
272
|
-
knowledge: [customer-support] # OKF bundles the
|
|
279
|
+
knowledge: [customer-support] # OKF bundles the agent consults & maintains
|
|
273
280
|
skills: [report-html] # optional allowlist; omit = all skills, [] = none
|
|
274
281
|
```
|
|
275
282
|
|
|
276
|
-
Sessions with
|
|
277
|
-
listed per
|
|
283
|
+
Sessions with an agent are ordinary chats scoped `{ kind: "agent", slug }`,
|
|
284
|
+
listed per agent in a second sidebar. On the first message the agent gets
|
|
278
285
|
its role plus its connected workflows and knowledge bundles injected as
|
|
279
286
|
context. That context includes how to run workflows
|
|
280
287
|
(`KRAFTWERK_YES=1 npx kraftwerk run <workflow> "<request>"`) and how to read
|
|
281
288
|
and write knowledge through `kraftwerk knowledge`, with writes stamped with
|
|
282
|
-
the
|
|
289
|
+
the agent's own actor, `<slug>/<harness>`. So the agent triggers its own
|
|
283
290
|
workflows when a request matches, and keeps its bundles current.
|
|
284
291
|
|
|
285
292
|
Model and effort ride on backend-specific channels. The claude adapter takes
|
|
286
293
|
the model via ACP session options and the thinking budget via
|
|
287
294
|
`MAX_THINKING_TOKENS`. Codex gets a `CODEX_CONFIG` env override (`model`,
|
|
288
|
-
`model_reasoning_effort`). Pi gets `--model` and `--thinking` flags.
|
|
295
|
+
`model_reasoning_effort`). Pi gets `--model` and `--thinking` flags. Agents
|
|
289
296
|
are created and edited in the UI, or by editing the files, since the
|
|
290
297
|
definition is read fresh for each new session.
|
|
291
298
|
|
|
299
|
+
### Repositories
|
|
300
|
+
|
|
301
|
+
Turn on `repos:` in `kraftwerk.yml` (or the checkbox in settings) and the
|
|
302
|
+
workspace gets one folder for the git repositories its agents work on:
|
|
303
|
+
|
|
304
|
+
```yaml
|
|
305
|
+
repos:
|
|
306
|
+
root: kraftwerk-data/repos # default: repos/ — git-ignored, never synced
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The folder is the registry. Whatever has a `.git` directly under the root is
|
|
310
|
+
a repository, whether the "repositories" screen cloned it, `kraftwerk repos
|
|
311
|
+
add <url>` did, or an agent ran `git clone` there. Every entry is read live
|
|
312
|
+
from git: origin, branch, head, uncommitted changes, ahead/behind. The
|
|
313
|
+
screen clones by url, fetches and fast-forwards clean clones, and removes
|
|
314
|
+
them (refusing while they hold unpushed or uncommitted work). Cloning uses
|
|
315
|
+
your own git credentials and never prompts, so a private remote has to work
|
|
316
|
+
from a terminal first.
|
|
317
|
+
|
|
318
|
+
Every agent, and the inspector assistant, gets the list as context: where
|
|
319
|
+
the root is, what is cloned, and how to add more with the CLI. So "look at
|
|
320
|
+
the widgets repo" works as soon as it is cloned, and "clone
|
|
321
|
+
github:org/widgets and look at it" works before.
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
kraftwerk repos # name, branch, head, state
|
|
325
|
+
kraftwerk repos add github:org/widgets # or any https / ssh url
|
|
326
|
+
kraftwerk repos add <url> --name tools --branch dev
|
|
327
|
+
kraftwerk repos add <url> --depth 1 # shallow clone of a large repository
|
|
328
|
+
kraftwerk repos update widgets # fetch, fast-forward when clean
|
|
329
|
+
kraftwerk repos remove widgets [--force]
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`GET /api/repos` returns the same list for automation. The root must be a
|
|
333
|
+
folder inside the project (never the project itself): it is excluded from the
|
|
334
|
+
workspace git sync, the first clone adds it to `.gitignore`, and `kraftwerk
|
|
335
|
+
doctor` warns when git does not ignore it.
|
|
336
|
+
|
|
292
337
|
### Skills in chat
|
|
293
338
|
|
|
294
|
-
Chats, both general ones and
|
|
339
|
+
Chats, both general ones and agent sessions, can use skills. These are
|
|
295
340
|
Claude-style instruction packages, one folder per skill with a `SKILL.md`
|
|
296
341
|
holding YAML frontmatter (`name`, `description`) and then the instructions.
|
|
297
342
|
Four roots are discovered, each shadowing same-named skills in the roots below
|
|
298
343
|
it:
|
|
299
344
|
|
|
300
345
|
```
|
|
301
|
-
<project>/agents/<slug>/skills/<name>/SKILL.md # private to that one
|
|
346
|
+
<project>/agents/<slug>/skills/<name>/SKILL.md # private to that one agent
|
|
302
347
|
<project>/<skills root>/<name>/SKILL.md # workspace, git-tracked (kraftwerk.yml `skills`, default skills/)
|
|
303
348
|
<project>/.claude/skills/<name>/SKILL.md # git-tracked, per project
|
|
304
349
|
~/.claude/skills/<name>/SKILL.md # personal, per user
|
|
305
350
|
```
|
|
306
351
|
|
|
307
352
|
Agent skills are visible only to that agent's sessions and always apply. A
|
|
308
|
-
|
|
353
|
+
agent's `skills:` allowlist narrows the shared roots only. Manage them in the
|
|
309
354
|
agent profile under "own skills", or by editing the files.
|
|
310
355
|
|
|
311
356
|
Every chat lists its visible skills as context under "## Your skills", so the
|
|
@@ -313,16 +358,16 @@ agent reaches for one when the request matches. Typing `/` in the composer
|
|
|
313
358
|
opens an autocomplete over them. Sending `/<name> <args>` expands the skill's
|
|
314
359
|
SKILL.md into the prompt, which is what makes this work identically on claude,
|
|
315
360
|
codex, and pi. On top of that, claude discovers `.claude/skills` natively (a
|
|
316
|
-
|
|
317
|
-
loads each visible skill folder via `--skill`.
|
|
361
|
+
agent's `skills:` allowlist narrows that via ACP session options) and pi
|
|
362
|
+
loads each visible skill folder via `--skill`. Agents take an optional
|
|
318
363
|
`skills:` list in `agent.yml`. Omitted means all discovered skills, an empty
|
|
319
364
|
list means none, otherwise it is the allowlist. `GET /api/skills` returns
|
|
320
365
|
what's discovered.
|
|
321
366
|
|
|
322
367
|
### Routines, or scheduled prompts
|
|
323
368
|
|
|
324
|
-
|
|
325
|
-
orders for an employee. Definitions live next to the
|
|
369
|
+
An agent can have routines, cron-scheduled prompts that work like standing
|
|
370
|
+
orders for an employee. Definitions live next to the agent in
|
|
326
371
|
`agents/<slug>/routines.yml` and are git-tracked. Run state (last run, last
|
|
327
372
|
session, errors) lives in `<output>/routines-state.json`.
|
|
328
373
|
|
|
@@ -338,12 +383,12 @@ session, errors) lives in `<output>/routines-state.json`.
|
|
|
338
383
|
```
|
|
339
384
|
|
|
340
385
|
The inspector server runs the scheduler in-process, so there is no external
|
|
341
|
-
cron to set up. Every due routine opens a fresh session for the
|
|
386
|
+
cron to set up. Every due routine opens a fresh session for the agent, posts
|
|
342
387
|
the prompt, and shows up in the sessions sidebar titled "⏰ <name>". Routine
|
|
343
388
|
sessions run unattended, so tool-permission requests are auto-approved, with
|
|
344
389
|
the request and approval pair left in the thread as an audit trail. Give
|
|
345
390
|
routine prompts the same trust you would give a `KRAFTWERK_YES=1` workflow
|
|
346
|
-
run. Manage routines on the
|
|
391
|
+
run. Manage routines on the agent page, where you can create, edit and
|
|
347
392
|
delete them, toggle enabled, hit "run now", and jump to the last run's
|
|
348
393
|
session. Schedules missed while the server is down are skipped, not replayed.
|
|
349
394
|
|
package/dist/cli/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { isDir, resolveProject } from "../config.js";
|
|
5
|
+
import { ignoreEntryFor, isDir, reposRootFor, resolveProject } from "../config.js";
|
|
6
6
|
import { discoverWorkflows } from "../discover.js";
|
|
7
7
|
import { missingEnv } from "../yaml.js";
|
|
8
8
|
const ICONS = {
|
|
@@ -106,6 +106,23 @@ export async function runDoctor(cwd) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
// Repositories: the clones root must stay out of the workspace git. git
|
|
110
|
+
// itself decides — that covers worktrees (.git is a file), a workspace
|
|
111
|
+
// nested in a larger repo, a .gitignore at the toplevel, global excludes.
|
|
112
|
+
const reposRoot = reposRootFor(project);
|
|
113
|
+
if (reposRoot) {
|
|
114
|
+
const entry = ignoreEntryFor(project.root, reposRoot) ?? path.relative(project.root, reposRoot);
|
|
115
|
+
const label = `repos: ${path.relative(cwd, reposRoot) || "."}`;
|
|
116
|
+
const check = spawnSync("git", ["check-ignore", "-q", "--", reposRoot], { cwd: project.root, encoding: "utf8" });
|
|
117
|
+
if (check.error)
|
|
118
|
+
report("info", label, "git not found — cannot check .gitignore");
|
|
119
|
+
else if (check.status === 0)
|
|
120
|
+
report("ok", label, "git-ignored");
|
|
121
|
+
else if (check.status === 1)
|
|
122
|
+
report("warn", label, `${entry}/ is not git-ignored — clones would show up as untracked gitlinks; add it to .gitignore`);
|
|
123
|
+
else
|
|
124
|
+
report("ok", label, "not inside a git repository");
|
|
125
|
+
}
|
|
109
126
|
const found = project.workflowsRoot ? await discoverWorkflows(cwd) : [];
|
|
110
127
|
if (!project.workflowsRoot) {
|
|
111
128
|
report("warn", "no workflows root", "expected src/workflows/ or workflows/ — `kraftwerk init` scaffolds one");
|
package/dist/cli/init.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { CONFIG_SCHEMA_URL, SCHEMA_URL } from "../config.js";
|
|
4
|
+
import { CONFIG_SCHEMA_URL, gitignoreHas, SCHEMA_URL } from "../config.js";
|
|
5
5
|
import { initBundle, writeConcept } from "../okf.js";
|
|
6
6
|
/**
|
|
7
7
|
* `kraftwerk init` — make any repository a kraftwerk consumer in one
|
|
8
8
|
* command: kraftwerk.yml (root marker + config), a kraftwerk-data/ tree with
|
|
9
|
-
* a runnable example workflow, a starter
|
|
9
|
+
* a runnable example workflow, a starter agent ("max"), a demo
|
|
10
10
|
* knowledge bundle, and an output ignore entry. Everything is idempotent:
|
|
11
11
|
* existing files are left untouched and reported.
|
|
12
12
|
*/
|
|
@@ -20,8 +20,10 @@ port: 1981 # port \`kraftwerk ui\` listens on
|
|
|
20
20
|
workflows: ${DATA_DIR}/workflows # where workflows live
|
|
21
21
|
output: ${DATA_DIR}/output # where run artifacts land (git-ignored)
|
|
22
22
|
knowledge: ${DATA_DIR}/knowledge # OKF knowledge bundles
|
|
23
|
-
agents: ${DATA_DIR}/agents #
|
|
23
|
+
agents: ${DATA_DIR}/agents # agent definitions
|
|
24
24
|
skills: ${DATA_DIR}/skills # workspace skills (shared instruction packages)
|
|
25
|
+
# repos: # git repositories the agents work on (uncomment both lines to enable)
|
|
26
|
+
# root: ${DATA_DIR}/repos # clones land here (git-ignored); a bare \`repos:\` uses repos/ instead
|
|
25
27
|
`;
|
|
26
28
|
const WORKFLOW_TEMPLATE = `# yaml-language-server: $schema=${SCHEMA_URL}
|
|
27
29
|
name: hello
|
|
@@ -107,7 +109,7 @@ export async function runInit(cwd) {
|
|
|
107
109
|
await put(`${DATA_DIR}/workflows/hello/workflow.yml`, WORKFLOW_TEMPLATE);
|
|
108
110
|
await put(`${DATA_DIR}/workflows/hello/prompts/assistant.md`, PERSONA_TEMPLATE);
|
|
109
111
|
await put(`${DATA_DIR}/workflows/hello/prompts/answer.md`, PROMPT_TEMPLATE);
|
|
110
|
-
//
|
|
112
|
+
// Agent "max".
|
|
111
113
|
await put(`${DATA_DIR}/agents/max/agent.yml`, AGENT_YML_TEMPLATE);
|
|
112
114
|
await put(`${DATA_DIR}/agents/max/system.md`, AGENT_SYSTEM_TEMPLATE);
|
|
113
115
|
// Example workspace skill.
|
|
@@ -124,20 +126,25 @@ export async function runInit(cwd) {
|
|
|
124
126
|
await writeConcept(knowledgeRoot, DEMO_BUNDLE, "playbooks/refunds", DEMO_CONCEPT, "kraftwerk-init");
|
|
125
127
|
created.push(bundleRel);
|
|
126
128
|
}
|
|
127
|
-
// .gitignore:
|
|
128
|
-
|
|
129
|
+
// .gitignore: the output dir and the repos root (clones must never become
|
|
130
|
+
// gitlinks of the workspace repo). A missing file gets both in one write;
|
|
131
|
+
// an existing one only the entries it lacks.
|
|
129
132
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
130
133
|
const gitignore = (await readFile(gitignorePath, "utf8").catch(() => null)) ?? null;
|
|
134
|
+
const entries = [`${DATA_DIR}/output`, `${DATA_DIR}/repos`];
|
|
131
135
|
if (gitignore === null) {
|
|
132
|
-
await writeFile(gitignorePath, `${
|
|
136
|
+
await writeFile(gitignorePath, entries.map((e) => `${e}/\n`).join(""));
|
|
133
137
|
created.push(".gitignore");
|
|
134
138
|
}
|
|
135
|
-
else if (!gitignore.split("\n").some((l) => l.trim().replace(/\/$/, "") === outputEntry)) {
|
|
136
|
-
await appendFile(gitignorePath, `${gitignore.endsWith("\n") ? "" : "\n"}${outputEntry}/\n`);
|
|
137
|
-
created.push(`.gitignore (${outputEntry}/ added)`);
|
|
138
|
-
}
|
|
139
139
|
else {
|
|
140
|
-
|
|
140
|
+
const missing = entries.filter((e) => !gitignoreHas(gitignore, e));
|
|
141
|
+
if (missing.length === 0) {
|
|
142
|
+
skipped.push(".gitignore");
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
await appendFile(gitignorePath, `${gitignore.endsWith("\n") ? "" : "\n"}${missing.map((e) => `${e}/\n`).join("")}`);
|
|
146
|
+
created.push(`.gitignore (${missing.map((e) => `${e}/`).join(", ")} added)`);
|
|
147
|
+
}
|
|
141
148
|
}
|
|
142
149
|
for (const f of created)
|
|
143
150
|
console.log(`${chalk.green("✔")} ${f}`);
|
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 { registerRepoCommands } from "./repos.js";
|
|
16
17
|
import { registerRoutineCommands } from "./routines.js";
|
|
17
18
|
import { listRuns, showRun } from "./runs.js";
|
|
18
19
|
import { runUi } from "./ui.js";
|
|
@@ -27,6 +28,7 @@ import { runUi } from "./ui.js";
|
|
|
27
28
|
* kraftwerk routines ... per-agent scheduled prompts (list/add/remove/enable/run)
|
|
28
29
|
* kraftwerk ui start the inspector web UI (localhost:1981)
|
|
29
30
|
* kraftwerk projects ... known projects on this machine (list/start/forget)
|
|
31
|
+
* kraftwerk repos ... repositories the agents work on (list/add/update/remove)
|
|
30
32
|
* kraftwerk doctor preflight: harness CLIs, docker, workflows, env
|
|
31
33
|
* kraftwerk validate [paths...] validate without executing
|
|
32
34
|
*
|
|
@@ -259,6 +261,7 @@ runs
|
|
|
259
261
|
registerKnowledgeCommands(program);
|
|
260
262
|
registerRoutineCommands(program);
|
|
261
263
|
registerProjectCommands(program);
|
|
264
|
+
registerRepoCommands(program);
|
|
262
265
|
program
|
|
263
266
|
.command("ui")
|
|
264
267
|
.description("Start the inspector web UI for this project's runs and workflows")
|
package/dist/cli/projects.d.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `kraftwerk projects` — every project that ever ran the inspector on this
|
|
4
|
+
* machine (~/.kraftwerk/projects), joined with what is running right now.
|
|
5
|
+
* The answer to "where did I start that UI, and how do I get it back":
|
|
6
|
+
*
|
|
7
|
+
* kraftwerk projects list: name, root, running/stopped
|
|
8
|
+
* kraftwerk projects start <ref> relaunch `kraftwerk ui` in that root, detached
|
|
9
|
+
* kraftwerk projects stop <ref> SIGTERM a running UI (also ones started in a terminal)
|
|
10
|
+
* kraftwerk projects forget <ref> drop the record (the folder stays)
|
|
11
|
+
*
|
|
12
|
+
* <ref> is a project name, the root's folder name, the root path, or a
|
|
13
|
+
* port / localhost:port for running instances that recorded no root.
|
|
14
|
+
*/
|
|
15
|
+
export declare const fmtAgo: (iso?: string) => string;
|
|
2
16
|
export declare function registerProjectCommands(program: Command): void;
|
package/dist/cli/projects.js
CHANGED
|
@@ -15,7 +15,7 @@ import { discoverWorkspaces, forgetProject, startProject, stopProject, tildify,
|
|
|
15
15
|
* <ref> is a project name, the root's folder name, the root path, or a
|
|
16
16
|
* port / localhost:port for running instances that recorded no root.
|
|
17
17
|
*/
|
|
18
|
-
const fmtAgo = (iso) => {
|
|
18
|
+
export const fmtAgo = (iso) => {
|
|
19
19
|
if (!iso)
|
|
20
20
|
return "—";
|
|
21
21
|
const s = Math.max(0, (Date.now() - Date.parse(iso)) / 1000);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { addRepo, listRepos, removeRepo, updateRepo } from "../inspector/repos.js";
|
|
4
|
+
import { fmtAgo } from "./projects.js";
|
|
5
|
+
import { initContext as prepare } from "./routines.js";
|
|
6
|
+
/**
|
|
7
|
+
* `kraftwerk repos` — the git repositories the agents work on, kept under
|
|
8
|
+
* the project's repos root (`repos.root` in kraftwerk.yml). The same module
|
|
9
|
+
* the inspector uses, so a clone made here shows up on the repositories
|
|
10
|
+
* screen and in every agent's context at once:
|
|
11
|
+
*
|
|
12
|
+
* kraftwerk repos list: name, branch, head, state
|
|
13
|
+
* kraftwerk repos add <url> [--name x] [--branch b] [--depth n]
|
|
14
|
+
* kraftwerk repos update <name> fetch, fast-forward when clean
|
|
15
|
+
* kraftwerk repos remove <name> [--force]
|
|
16
|
+
*/
|
|
17
|
+
const die = (msg) => {
|
|
18
|
+
console.error(chalk.red(msg));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
};
|
|
21
|
+
export function registerRepoCommands(program) {
|
|
22
|
+
const repos = program
|
|
23
|
+
.command("repos")
|
|
24
|
+
.description("Repositories the agents work on: list, clone one into the repos root, update, remove");
|
|
25
|
+
repos
|
|
26
|
+
.command("list", { isDefault: true })
|
|
27
|
+
.description("List the clones under the repos root with branch, head and state")
|
|
28
|
+
.option("--json", "Machine-readable output")
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
await prepare();
|
|
31
|
+
const view = await listRepos();
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
console.log(JSON.stringify(view, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!view.enabled)
|
|
37
|
+
die(view.error ?? "repositories are off");
|
|
38
|
+
if (view.repos.length === 0) {
|
|
39
|
+
console.log(chalk.dim(`No repositories under ${view.root} yet — \`kraftwerk repos add <url>\` clones one.`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const table = new Table({
|
|
43
|
+
head: ["name", "branch", "head", "state", "url"].map((h) => chalk.bold(h)),
|
|
44
|
+
wordWrap: true,
|
|
45
|
+
colWidths: [22, 16, 30, 18, 40],
|
|
46
|
+
});
|
|
47
|
+
for (const r of view.repos) {
|
|
48
|
+
const state = r.error
|
|
49
|
+
? chalk.red(r.error)
|
|
50
|
+
: [
|
|
51
|
+
r.dirty ? chalk.yellow(`${r.dirty} changed`) : "",
|
|
52
|
+
r.ahead ? chalk.green(`${r.ahead}↑`) : "",
|
|
53
|
+
r.behind ? chalk.cyan(`${r.behind}↓`) : "",
|
|
54
|
+
]
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join(" ") || chalk.dim("clean");
|
|
57
|
+
table.push([
|
|
58
|
+
chalk.cyan(r.slug),
|
|
59
|
+
r.branch ?? "—",
|
|
60
|
+
r.head ? `${r.head} ${chalk.dim(r.subject ?? "")}\n${chalk.dim(fmtAgo(r.committedAt))}` : "—",
|
|
61
|
+
state,
|
|
62
|
+
r.url ?? chalk.dim("(no origin)"),
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
console.log(table.toString());
|
|
66
|
+
console.log(chalk.dim(view.root ?? ""));
|
|
67
|
+
});
|
|
68
|
+
repos
|
|
69
|
+
.command("add")
|
|
70
|
+
.description("Clone a repository into the repos root")
|
|
71
|
+
.argument("<url>", "git url, or github:org/repo")
|
|
72
|
+
.option("--name <name>", "Folder name under the root (default: last url segment)")
|
|
73
|
+
.option("--branch <branch>", "Branch to check out")
|
|
74
|
+
.option("--depth <n>", "Shallow clone with n commits of history (large repositories)")
|
|
75
|
+
.option("--json", "Print the new entry as JSON")
|
|
76
|
+
.action(async (url, opts) => {
|
|
77
|
+
await prepare();
|
|
78
|
+
try {
|
|
79
|
+
const depth = opts.depth === undefined ? undefined : Number(opts.depth);
|
|
80
|
+
const repo = await addRepo({ url, name: opts.name, branch: opts.branch, depth });
|
|
81
|
+
if (opts.json)
|
|
82
|
+
console.log(JSON.stringify(repo, null, 2));
|
|
83
|
+
else
|
|
84
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(repo.slug)} → ${repo.path}${repo.branch ? chalk.dim(` (${repo.branch})`) : ""}`);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
die(err.message);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
repos
|
|
91
|
+
.command("update")
|
|
92
|
+
.description("Fetch a repository and fast-forward it when it is clean")
|
|
93
|
+
.argument("<name>", "Folder name under the root")
|
|
94
|
+
.action(async (name) => {
|
|
95
|
+
await prepare();
|
|
96
|
+
const r = await updateRepo(name).catch((err) => ({ ok: false, error: err.message, repo: undefined }));
|
|
97
|
+
if (!r.ok)
|
|
98
|
+
die(r.error ?? "update failed");
|
|
99
|
+
const repo = r.repo;
|
|
100
|
+
const tail = r.error ? chalk.yellow(` — ${r.error}`) : "";
|
|
101
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(name)} at ${repo.head} ${chalk.dim(repo.subject ?? "")}${tail}`);
|
|
102
|
+
});
|
|
103
|
+
repos
|
|
104
|
+
.command("remove")
|
|
105
|
+
.description("Delete a clone from the repos root (refused while it holds unpushed or uncommitted work)")
|
|
106
|
+
.argument("<name>", "Folder name under the root")
|
|
107
|
+
.option("--force", "Delete even with local changes")
|
|
108
|
+
.action(async (name, opts) => {
|
|
109
|
+
await prepare();
|
|
110
|
+
const r = await removeRepo(name, !!opts.force).catch((err) => ({ ok: false, error: err.message }));
|
|
111
|
+
if (!r.ok)
|
|
112
|
+
die(r.error ?? "remove failed");
|
|
113
|
+
console.log(`${chalk.green("✔")} removed ${chalk.cyan(name)}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
package/dist/cli/routines.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `kraftwerk routines` — CLI surface over per-agent scheduled prompts
|
|
4
|
+
* (agents/<slug>/routines.yml). Definition CRUD works directly on the
|
|
5
|
+
* files; `run` fires through the running inspector server, because the
|
|
6
|
+
* session (chat) and the scheduler live there.
|
|
7
|
+
*/
|
|
8
|
+
/** Point the inspector context at this project so agent/routine paths resolve. */
|
|
9
|
+
export declare function initContext(): Promise<{
|
|
10
|
+
port: number;
|
|
11
|
+
}>;
|
|
2
12
|
export declare function registerRoutineCommands(program: Command): void;
|
package/dist/cli/routines.js
CHANGED
|
@@ -4,22 +4,22 @@ import chalk from "chalk";
|
|
|
4
4
|
import { resolveProject } from "../config.js";
|
|
5
5
|
import { setOutputDir, setProjectRoot } from "../inspector/context.js";
|
|
6
6
|
import { deleteRoutine, listRoutines, routineStatuses, saveRoutine, } from "../inspector/routines.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getAgent, listAgents } from "../inspector/agents.js";
|
|
8
8
|
/**
|
|
9
9
|
* `kraftwerk routines` — CLI surface over per-agent scheduled prompts
|
|
10
10
|
* (agents/<slug>/routines.yml). Definition CRUD works directly on the
|
|
11
11
|
* files; `run` fires through the running inspector server, because the
|
|
12
12
|
* session (chat) and the scheduler live there.
|
|
13
13
|
*/
|
|
14
|
-
/** Point the inspector context at this project so
|
|
15
|
-
async function initContext() {
|
|
14
|
+
/** Point the inspector context at this project so agent/routine paths resolve. */
|
|
15
|
+
export async function initContext() {
|
|
16
16
|
const project = await resolveProject(process.cwd());
|
|
17
17
|
setProjectRoot(project.root);
|
|
18
18
|
setOutputDir(project.outputDir);
|
|
19
19
|
return { port: project.config.port ?? 1981 };
|
|
20
20
|
}
|
|
21
|
-
async function
|
|
22
|
-
if (!(await
|
|
21
|
+
async function requireAgent(slug) {
|
|
22
|
+
if (!(await getAgent(slug))) {
|
|
23
23
|
console.error(chalk.red(`Agent "${slug}" not found (expected agents/${slug}/agent.yml).`));
|
|
24
24
|
process.exit(2);
|
|
25
25
|
}
|
|
@@ -56,9 +56,9 @@ export function registerRoutineCommands(program) {
|
|
|
56
56
|
.option("--json", "Machine-readable output")
|
|
57
57
|
.action(async (agent, opts) => {
|
|
58
58
|
await initContext();
|
|
59
|
-
const slugs = agent ? [agent] : (await
|
|
59
|
+
const slugs = agent ? [agent] : (await listAgents()).map((m) => m.slug);
|
|
60
60
|
if (agent)
|
|
61
|
-
await
|
|
61
|
+
await requireAgent(agent);
|
|
62
62
|
const all = {};
|
|
63
63
|
for (const slug of slugs)
|
|
64
64
|
all[slug] = await routineStatuses(slug);
|
|
@@ -89,7 +89,7 @@ export function registerRoutineCommands(program) {
|
|
|
89
89
|
.option("--disabled", "Create it switched off")
|
|
90
90
|
.action(async (agent, opts) => {
|
|
91
91
|
await initContext();
|
|
92
|
-
await
|
|
92
|
+
await requireAgent(agent);
|
|
93
93
|
const prompt = opts.prompt ??
|
|
94
94
|
(opts.file ? await readFile(path.resolve(opts.file), "utf8") : process.stdin.isTTY ? "" : await readStdin());
|
|
95
95
|
if (!prompt.trim()) {
|
|
@@ -124,7 +124,7 @@ export function registerRoutineCommands(program) {
|
|
|
124
124
|
.argument("<id>", "Routine id (see `kraftwerk routines <agent>`)")
|
|
125
125
|
.action(async (agent, id) => {
|
|
126
126
|
await initContext();
|
|
127
|
-
await
|
|
127
|
+
await requireAgent(agent);
|
|
128
128
|
if (!(await listRoutines(agent)).some((r) => r.id === id)) {
|
|
129
129
|
console.error(chalk.red(`Routine "${agent}/${id}" not found.`));
|
|
130
130
|
process.exit(2);
|
|
@@ -158,7 +158,7 @@ export function registerRoutineCommands(program) {
|
|
|
158
158
|
.action(async (agent, id, opts) => {
|
|
159
159
|
const { port } = await initContext();
|
|
160
160
|
const p = opts.port ? Number(opts.port) : port;
|
|
161
|
-
const url = `http://localhost:${p}/api/
|
|
161
|
+
const url = `http://localhost:${p}/api/agents/${encodeURIComponent(agent)}/routines/${encodeURIComponent(id)}/run`;
|
|
162
162
|
let body;
|
|
163
163
|
try {
|
|
164
164
|
body = (await (await fetch(url, { method: "POST" })).json());
|
package/dist/config.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* workflows: src/workflows # workflows root, relative to the file
|
|
16
16
|
* output: output # run-artifact directory, relative to the file
|
|
17
17
|
* knowledge: knowledge # OKF knowledge-bundle root, relative to the file
|
|
18
|
-
* agents: agents #
|
|
18
|
+
* agents: agents # agent-definition root, relative to the file
|
|
19
19
|
* skills: skills # workspace skill root, relative to the file
|
|
20
20
|
* switcher: # other kraftwerk workspaces, linked from the header dropdown
|
|
21
21
|
* - name: other space
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* remote: origin
|
|
26
26
|
* interval: 300 # seconds between background fetches
|
|
27
27
|
* autosync: pull # off | pull
|
|
28
|
+
* repos: # git repositories the agents work on (absent = off, bare key = on)
|
|
29
|
+
* root: kraftwerk-data/repos # where clones land, relative to the file. Default: repos
|
|
28
30
|
*/
|
|
29
31
|
/** Stable, versionless URL of the workflow JSON schema (editor validation). */
|
|
30
32
|
export declare const SCHEMA_URL = "https://raw.githubusercontent.com/NETNODEAG/kraftwerk/main/kraftwerk/schema/workflow.schema.json";
|
|
@@ -49,6 +51,29 @@ export interface GitConfig {
|
|
|
49
51
|
/** off = fetch only. pull = also fast-forward when behind and clean. Default: pull. */
|
|
50
52
|
autosync?: "off" | "pull";
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Repositories: git clones the agents work on, kept under one root inside
|
|
56
|
+
* the project so every agent finds them at a known path. The root is never
|
|
57
|
+
* synced by the workspace git and should be git-ignored.
|
|
58
|
+
*/
|
|
59
|
+
export interface ReposConfig {
|
|
60
|
+
/** false keeps the block but turns the feature off. Default: true. */
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
/** Where clones land, relative to the project root. Default: repos */
|
|
63
|
+
root?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Where clones land when `repos.root` is not set. */
|
|
66
|
+
export declare const REPOS_DEFAULT_ROOT = "repos";
|
|
67
|
+
/**
|
|
68
|
+
* Absolute repos root when the feature is on, undefined otherwise. The one
|
|
69
|
+
* place that reads the block, so the sync exclude, the doctor check, the
|
|
70
|
+
* settings save and the module itself cannot disagree.
|
|
71
|
+
*/
|
|
72
|
+
export declare function reposRootFor(project: Project): string | undefined;
|
|
73
|
+
/** A directory as a .gitignore entry: relative, forward slashes, no trailing slash; undefined outside the root. */
|
|
74
|
+
export declare function ignoreEntryFor(projectRoot: string, dir: string): string | undefined;
|
|
75
|
+
/** True when .gitignore text already covers the entry (with or without a leading or trailing slash). */
|
|
76
|
+
export declare function gitignoreHas(text: string, entry: string): boolean;
|
|
52
77
|
/** One entry of the workspace switcher: another kraftwerk instance to link to. */
|
|
53
78
|
export interface SwitcherEntry {
|
|
54
79
|
/** Display name of the other workspace. */
|
|
@@ -71,7 +96,7 @@ export interface ProjectConfig {
|
|
|
71
96
|
output?: string;
|
|
72
97
|
/** OKF knowledge-bundle root relative to the project root. Default: knowledge */
|
|
73
98
|
knowledge?: string;
|
|
74
|
-
/**
|
|
99
|
+
/** Agent-definition root relative to the project root. Default: agents */
|
|
75
100
|
agents?: string;
|
|
76
101
|
/** Workspace skill root relative to the project root. Default: skills */
|
|
77
102
|
skills?: string;
|
|
@@ -79,6 +104,8 @@ export interface ProjectConfig {
|
|
|
79
104
|
switcher?: SwitcherEntry[];
|
|
80
105
|
/** Workspace git sync. Absent = off. */
|
|
81
106
|
git?: GitConfig;
|
|
107
|
+
/** Repositories the agents work on. Absent = off. */
|
|
108
|
+
repos?: ReposConfig;
|
|
82
109
|
}
|
|
83
110
|
export interface Project {
|
|
84
111
|
/** Absolute project root the CLI operates on. */
|