@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.
Files changed (57) hide show
  1. package/README.md +65 -20
  2. package/dist/cli/doctor.js +18 -1
  3. package/dist/cli/init.js +19 -12
  4. package/dist/cli/kraftwerk.js +3 -0
  5. package/dist/cli/projects.d.ts +14 -0
  6. package/dist/cli/projects.js +1 -1
  7. package/dist/cli/repos.d.ts +2 -0
  8. package/dist/cli/repos.js +115 -0
  9. package/dist/cli/routines.d.ts +10 -0
  10. package/dist/cli/routines.js +10 -10
  11. package/dist/config.d.ts +29 -2
  12. package/dist/config.js +56 -2
  13. package/dist/inspector/{team.d.ts → agents.d.ts} +21 -12
  14. package/dist/inspector/{team.js → agents.js} +34 -19
  15. package/dist/inspector/chat/backend.d.ts +1 -1
  16. package/dist/inspector/chat/sessions.js +72 -45
  17. package/dist/inspector/chat/store.js +13 -1
  18. package/dist/inspector/chat/types.d.ts +2 -2
  19. package/dist/inspector/git.d.ts +14 -0
  20. package/dist/inspector/git.js +11 -9
  21. package/dist/inspector/instances.d.ts +12 -0
  22. package/dist/inspector/instances.js +28 -0
  23. package/dist/inspector/repos.d.ts +118 -0
  24. package/dist/inspector/repos.js +249 -0
  25. package/dist/inspector/routines.d.ts +4 -4
  26. package/dist/inspector/routines.js +11 -11
  27. package/dist/inspector/search.d.ts +25 -0
  28. package/dist/inspector/search.js +31 -0
  29. package/dist/inspector/server.js +77 -30
  30. package/dist/inspector/settings.d.ts +7 -1
  31. package/dist/inspector/settings.js +32 -1
  32. package/dist/inspector/skills.d.ts +6 -6
  33. package/dist/inspector/skills.js +8 -8
  34. package/inspector/dist/assets/{editor-BABv2LmA.js → editor-vM6JSj5h.js} +3 -3
  35. package/inspector/dist/assets/{index-CCACy6uF.js → index-BAAjszPI.js} +1 -1
  36. package/inspector/dist/assets/{index-DuwmzZAr.js → index-BKcOp35r.js} +1 -1
  37. package/inspector/dist/assets/index-Ba8VU6Wz.js +217 -0
  38. package/inspector/dist/assets/{index-Ca9Qs5LZ.js → index-Bb08wK-l.js} +1 -1
  39. package/inspector/dist/assets/{index-ByWpqzAI.js → index-BrCwDkJZ.js} +1 -1
  40. package/inspector/dist/assets/{index-hkVov8T4.js → index-Bs1U0rWW.js} +1 -1
  41. package/inspector/dist/assets/{index-CdwTmXTV.js → index-C1UXlW32.js} +1 -1
  42. package/inspector/dist/assets/{index-HBdsdJhD.js → index-CQ2XKqmD.js} +1 -1
  43. package/inspector/dist/assets/{index-DCR7u4Xa.js → index-CReepirH.js} +1 -1
  44. package/inspector/dist/assets/{index-CzPQygR7.js → index-CUGfxoYt.js} +1 -1
  45. package/inspector/dist/assets/{index-sLxulPOm.js → index-CaKmxcD6.js} +1 -1
  46. package/inspector/dist/assets/{index-Bmm4zJNU.js → index-CtMGH62C.js} +1 -1
  47. package/inspector/dist/assets/{index-BXspF2KR.css → index-D4_mLTSH.css} +1 -1
  48. package/inspector/dist/assets/{index-BbTer62d.js → index-D_11IZBV.js} +1 -1
  49. package/inspector/dist/assets/{index-wkYwz0Tl.js → index-DfZT0IoM.js} +1 -1
  50. package/inspector/dist/assets/{index-Gx93brFq.js → index-DykDNrYm.js} +1 -1
  51. package/inspector/dist/assets/{index-DuPp3MUc.js → index-MWv5H1oP.js} +1 -1
  52. package/inspector/dist/assets/{index-CjlJeW9z.js → index-oIQqfbwm.js} +1 -1
  53. package/inspector/dist/assets/{index-DbWUNbt-.js → index-yGZsomcy.js} +1 -1
  54. package/inspector/dist/index.html +2 -2
  55. package/package.json +1 -1
  56. package/schema/kraftwerk.schema.json +15 -0
  57. 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 team agent-definition root (default
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 team agents
259
+ ## Persistent agents
253
260
 
254
- The inspector's "team" screen turns chat agents into persistent teammates. A
255
- team member is one folder under the project's `agents/` root:
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 member's system prompt (its role)
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 member consults & maintains
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 a member are ordinary chats scoped `{ kind: "team", member }`,
277
- listed per member in a second sidebar. On the first message the member gets
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 member's own actor, `<slug>/<harness>`. So the member triggers its own
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. Members
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 team sessions, can use skills. These are
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 team agent
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
- member's `skills:` allowlist narrows the shared roots only. Manage them in the
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
- team member's `skills:` allowlist narrows that via ACP session options) and pi
317
- loads each visible skill folder via `--skill`. Team members take an optional
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
- A member can have routines, cron-scheduled prompts that work like standing
325
- orders for an employee. Definitions live next to the member in
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 member, posts
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 member page, where you can create, edit and
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
 
@@ -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 team agent ("max"), a demo
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 # team agent definitions
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
- // Team agent "max".
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: append the output dir if it's not covered yet.
128
- const outputEntry = `${DATA_DIR}/output`;
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, `${outputEntry}/\n`);
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
- skipped.push(".gitignore");
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}`);
@@ -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")
@@ -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;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerRepoCommands(program: Command): void;
@@ -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
+ }
@@ -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;
@@ -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 { getMember, listMembers } from "../inspector/team.js";
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 team/routine paths resolve. */
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 requireMember(slug) {
22
- if (!(await getMember(slug))) {
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 listMembers()).map((m) => m.slug);
59
+ const slugs = agent ? [agent] : (await listAgents()).map((m) => m.slug);
60
60
  if (agent)
61
- await requireMember(agent);
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 requireMember(agent);
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 requireMember(agent);
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/team/${encodeURIComponent(agent)}/routines/${encodeURIComponent(id)}/run`;
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 # team agent-definition root, relative to the file
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
- /** Team agent-definition root relative to the project root. Default: agents */
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. */