@netnodeag/kraftwerk 0.64.0 → 0.66.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 +126 -138
- package/dist/acp.js +3 -1
- package/dist/cli/daemon.d.ts +14 -0
- package/dist/cli/daemon.js +60 -0
- package/dist/cli/devices.d.ts +14 -0
- package/dist/cli/devices.js +53 -0
- package/dist/cli/doctor-legacy.js +9 -1
- package/dist/cli/doctor.js +17 -30
- package/dist/cli/init.js +1 -6
- package/dist/cli/kraftwerk.js +8 -3
- package/dist/cli/ui.d.ts +9 -0
- package/dist/cli/ui.js +47 -23
- package/dist/cli/workspaces.js +5 -3
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +10 -2
- package/dist/client/live.d.ts +1 -2
- package/dist/client/live.js +4 -2
- package/dist/client/routes.d.ts +8 -0
- package/dist/client/routes.js +8 -0
- package/dist/config.d.ts +18 -55
- package/dist/config.js +39 -92
- package/dist/core/chat/context.js +1 -1
- package/dist/core/chat/pi.js +2 -1
- package/dist/core/chat/sessions.d.ts +1 -1
- package/dist/core/chat/sessions.js +1 -1
- package/dist/core/chat/state.d.ts +2 -0
- package/dist/core/chat/state.js +5 -0
- package/dist/core/daemon.d.ts +27 -0
- package/dist/core/daemon.js +61 -0
- package/dist/core/devices.d.ts +35 -0
- package/dist/core/devices.js +156 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +45 -0
- package/dist/core/git.d.ts +2 -0
- package/dist/core/git.js +20 -4
- package/dist/core/instances.d.ts +32 -12
- package/dist/core/instances.js +136 -64
- package/dist/core/routines.d.ts +6 -0
- package/dist/core/routines.js +11 -0
- package/dist/core/runner.js +7 -3
- package/dist/core/vibeables.d.ts +2 -0
- package/dist/core/vibeables.js +17 -5
- package/dist/core/workspace.d.ts +13 -0
- package/dist/core/workspace.js +25 -0
- package/dist/dotenv.d.ts +2 -2
- package/dist/dotenv.js +2 -2
- package/dist/server/api/devices.d.ts +20 -0
- package/dist/server/api/devices.js +55 -0
- package/dist/server/api/hub.d.ts +25 -0
- package/dist/server/api/hub.js +32 -0
- package/dist/server/api/index.d.ts +42 -2
- package/dist/server/api/index.js +5 -1
- package/dist/server/api/router.d.ts +21 -6
- package/dist/server/api/router.js +37 -9
- package/dist/server/api/socket.d.ts +15 -1
- package/dist/server/api/socket.js +59 -8
- package/dist/server/api/workspace.d.ts +2 -0
- package/dist/server/api/workspace.js +4 -2
- package/dist/server/bind.d.ts +16 -0
- package/dist/server/bind.js +25 -0
- package/dist/server/cloud.d.ts +5 -1
- package/dist/server/cloud.js +89 -56
- package/dist/server/hub-context.d.ts +7 -0
- package/dist/server/hub-context.js +18 -0
- package/dist/server/live.d.ts +2 -0
- package/dist/server/live.js +9 -0
- package/dist/server/preview.js +2 -1
- package/dist/server/server.d.ts +58 -1
- package/dist/server/server.js +267 -103
- package/dist/server/target.d.ts +20 -0
- package/dist/server/target.js +18 -0
- package/dist/server/trust.d.ts +41 -0
- package/dist/server/trust.js +42 -0
- package/package.json +1 -1
- package/schema/kraftwerk.schema.json +7 -46
- package/web/dist/assets/{dist-Cgcwvf4j.js → dist-1bNkaWWA.js} +1 -1
- package/web/dist/assets/dist-BU1SOB5S.js +1 -0
- package/web/dist/assets/{dist-CboP0-T1.js → dist-Bk3f-J5U.js} +1 -1
- package/web/dist/assets/{dist-2kswkIH2.js → dist-Bx99Puvv.js} +1 -1
- package/web/dist/assets/{dist-D2PvEzo7.js → dist-C0M2aITo.js} +1 -1
- package/web/dist/assets/{dist-D_BcTzz-.js → dist-C0z19Zge.js} +1 -1
- package/web/dist/assets/{dist-36MjITWa.js → dist-COEgBKG6.js} +1 -1
- package/web/dist/assets/{dist-Jdx6DRrm.js → dist-CYRrdlae.js} +1 -1
- package/web/dist/assets/{dist-DDkd_zrP.js → dist-CbEFnSCD.js} +1 -1
- package/web/dist/assets/{dist-BsuA2pgO.js → dist-DCYVKU-H.js} +1 -1
- package/web/dist/assets/{dist-B8gPjMPP.js → dist-XyTHWtoN.js} +1 -1
- package/web/dist/assets/{editor-ByRUOfrX.js → editor-DPcGvSPy.js} +3 -3
- package/web/dist/assets/index-BAYs-2WC.css +2 -0
- package/web/dist/assets/index-DKLJdwYE.js +229 -0
- package/web/dist/index.html +2 -2
- package/dist/cli/tunnel.d.ts +0 -10
- package/dist/cli/tunnel.js +0 -280
- package/dist/server/access.d.ts +0 -32
- package/dist/server/access.js +0 -121
- package/web/dist/assets/dist-CVqmJrYd.js +0 -1
- package/web/dist/assets/index-BR7HvDzD.js +0 -229
- package/web/dist/assets/index-Bra74Nxe.css +0 -2
package/dist/cli/kraftwerk.js
CHANGED
|
@@ -17,8 +17,9 @@ import { registerWorkspaceCommands } from "./workspaces.js";
|
|
|
17
17
|
import { registerRepoCommands } from "./repos.js";
|
|
18
18
|
import { registerVibeableCommands } from "./vibeables.js";
|
|
19
19
|
import { registerTrashCommands } from "./trash.js";
|
|
20
|
+
import { registerDevicesCommands } from "./devices.js";
|
|
21
|
+
import { registerDaemonCommand } from "./daemon.js";
|
|
20
22
|
import { registerJournalCommands } from "./journal.js";
|
|
21
|
-
import { registerTunnelCommands } from "./tunnel.js";
|
|
22
23
|
import { applyDotenv } from "../dotenv.js";
|
|
23
24
|
import { resolveWorkspace } from "../config.js";
|
|
24
25
|
import { registerRoutineCommands } from "./routines.js";
|
|
@@ -40,7 +41,8 @@ import { runUi } from "./ui.js";
|
|
|
40
41
|
* kraftwerk repos ... repositories the agents work on (list/add/update/remove)
|
|
41
42
|
* kraftwerk journal <agent> [entry] an agent's memory across sessions: print it, add a line
|
|
42
43
|
* kraftwerk trash ... what was deleted: list, restore, purge, empty
|
|
43
|
-
* kraftwerk
|
|
44
|
+
* kraftwerk devices ... paired devices: list, pair one, revoke one
|
|
45
|
+
* kraftwerk daemon the machine's kraftwerk: every open workspace at <slug>.localhost
|
|
44
46
|
* kraftwerk doctor preflight: harness CLIs, docker, workflows, env
|
|
45
47
|
* kraftwerk validate [paths...] validate without executing
|
|
46
48
|
*
|
|
@@ -291,13 +293,16 @@ registerProjectCommands(program);
|
|
|
291
293
|
registerRepoCommands(program);
|
|
292
294
|
registerVibeableCommands(program);
|
|
293
295
|
registerTrashCommands(program);
|
|
296
|
+
registerDevicesCommands(program);
|
|
297
|
+
registerDaemonCommand(program);
|
|
294
298
|
registerJournalCommands(program);
|
|
295
|
-
registerTunnelCommands(program);
|
|
296
299
|
program
|
|
297
300
|
.command("ui")
|
|
298
301
|
.description("Start the inspector web UI for this project's runs and workflows")
|
|
299
302
|
.option("--port <port>", "Port for the web UI (default: kraftwerk.yml `port`, else 1981)")
|
|
300
303
|
.option("--output <dir>", "Output directory to inspect (default: the project's output dir)")
|
|
304
|
+
.option("--lan", "Listen on the network too, so paired devices (a phone) reach it — others get a pairing screen")
|
|
305
|
+
.option("--standalone", "Run a server of its own even when the kraftwerk daemon runs")
|
|
301
306
|
.action(async (opts) => {
|
|
302
307
|
await runUi(process.cwd(), opts);
|
|
303
308
|
});
|
package/dist/cli/ui.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Published packages ship web/dist. A dev checkout builds it on
|
|
3
|
+
* first use (and after frontend edits: `npm run build` in web/).
|
|
4
|
+
*/
|
|
5
|
+
export declare function ensureBuilt(): string;
|
|
1
6
|
export declare function runUi(cwd: string, opts: {
|
|
2
7
|
port?: string;
|
|
3
8
|
output?: string;
|
|
9
|
+
lan?: boolean;
|
|
10
|
+
standalone?: boolean;
|
|
4
11
|
}): Promise<void>;
|
|
12
|
+
/** The respawn loop for any long-running kraftwerk process (`ui`, `daemon`): see superviseUi. */
|
|
13
|
+
export declare function supervise(argv: string[], what: string): Promise<void>;
|
package/dist/cli/ui.js
CHANGED
|
@@ -4,10 +4,12 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { spawn, spawnSync } from "node:child_process";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import { absolutePath,
|
|
8
|
-
import {
|
|
7
|
+
import { absolutePath, resolveWorkspace, workspaceSlug } from "../config.js";
|
|
8
|
+
import { slugClashes } from "../core/instances.js";
|
|
9
|
+
import { daemonCall, findDaemon } from "../core/daemon.js";
|
|
9
10
|
import { selfCommand } from "../core/self-command.js";
|
|
10
11
|
import { RESTART_EXIT_CODE, startInspector } from "../server/server.js";
|
|
12
|
+
import { networkUrls } from "../server/bind.js";
|
|
11
13
|
/**
|
|
12
14
|
* `kraftwerk ui` — start the inspector web UI pointed at the current
|
|
13
15
|
* project's output dir. The server is dependency-free node:http (part of
|
|
@@ -26,7 +28,10 @@ const packageRoot = () => path.join(path.dirname(fileURLToPath(import.meta.url))
|
|
|
26
28
|
* Published packages ship web/dist. A dev checkout builds it on
|
|
27
29
|
* first use (and after frontend edits: `npm run build` in web/).
|
|
28
30
|
*/
|
|
29
|
-
function ensureBuilt() {
|
|
31
|
+
export function ensureBuilt() {
|
|
32
|
+
// Another build of the web UI (tests serve a stub; a dev setup may point at its own).
|
|
33
|
+
if (process.env.KRAFTWERK_WEB_DIR)
|
|
34
|
+
return path.resolve(process.env.KRAFTWERK_WEB_DIR);
|
|
30
35
|
const web = path.join(packageRoot(), "web");
|
|
31
36
|
const dist = path.join(web, "dist");
|
|
32
37
|
if (existsSync(path.join(dist, "index.html")))
|
|
@@ -49,19 +54,46 @@ function ensureBuilt() {
|
|
|
49
54
|
return dist;
|
|
50
55
|
}
|
|
51
56
|
export async function runUi(cwd, opts) {
|
|
57
|
+
// The machine runs a daemon: hand it this workspace, it serves it next to the others.
|
|
58
|
+
if (!opts.standalone && !opts.port && !opts.output && !opts.lan && process.env.KRAFTWERK_UI_SUPERVISED !== "1") {
|
|
59
|
+
const daemon = await findDaemon();
|
|
60
|
+
if (daemon) {
|
|
61
|
+
const project = await resolveWorkspace(cwd);
|
|
62
|
+
const r = await daemonCall(daemon.port, "open", project.root);
|
|
63
|
+
if (!r.ok) {
|
|
64
|
+
console.error(chalk.red(`✖ the kraftwerk daemon refused: ${r.data.error ?? r.status}`));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
console.log(`${chalk.green("✔")} Kraftwerk UI: ${chalk.cyan(r.data.url)} ${chalk.dim(`(served by the kraftwerk daemon, pid ${daemon.pid})`)}`);
|
|
68
|
+
if (r.data.aliasPort)
|
|
69
|
+
console.log(chalk.dim(` also at http://localhost:${r.data.aliasPort}`));
|
|
70
|
+
console.log(chalk.dim(" `kraftwerk workspaces stop .` closes it there; `kraftwerk ui --standalone` runs a server of its own instead."));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// The server reads its bind address when it starts — in the supervised child, which inherits this.
|
|
75
|
+
if (opts.lan)
|
|
76
|
+
process.env.KRAFTWERK_UI_HOST ||= "0.0.0.0";
|
|
52
77
|
if (process.env.KRAFTWERK_UI_SUPERVISED !== "1")
|
|
53
|
-
return superviseUi(
|
|
78
|
+
return superviseUi(opts);
|
|
54
79
|
const staticDir = ensureBuilt();
|
|
55
80
|
const project = await resolveWorkspace(cwd);
|
|
56
81
|
const outputDir = opts.output ? absolutePath(opts.output, cwd) : project.outputDir;
|
|
57
82
|
// Port precedence: --port flag > kraftwerk.yml `port` > 1981.
|
|
58
83
|
const port = opts.port ? Number(opts.port) : (project.config.port ?? 1981);
|
|
59
84
|
await startInspector({ outputDir, staticDir, port, root: project.root });
|
|
85
|
+
// Two workspaces with one slug would share an address once one kraftwerk serves them all.
|
|
86
|
+
const slug = workspaceSlug(project);
|
|
87
|
+
const clash = await slugClashes(slug, project.root);
|
|
88
|
+
if (clash.length)
|
|
89
|
+
console.log(`${chalk.yellow("⚠")} slug "${slug}" is also used by ${clash.map((r) => r.root).join(", ")} — set \`slug:\` in one kraftwerk.yml so each workspace keeps its own address`);
|
|
60
90
|
console.log(`${chalk.green("✔")} Kraftwerk UI: ${chalk.cyan(`http://localhost:${port}`)} ` +
|
|
61
91
|
chalk.dim(`(output: ${outputDir})`));
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
console.log(`${chalk.green("✔")}
|
|
92
|
+
const lan = networkUrls(port);
|
|
93
|
+
if (lan.length) {
|
|
94
|
+
console.log(`${chalk.green("✔")} On your network: ${lan.map((u) => chalk.cyan(u)).join(", ")}`);
|
|
95
|
+
console.log(chalk.dim(" Devices there get a pairing screen — `kraftwerk devices pair` (or settings → devices) makes the code."));
|
|
96
|
+
}
|
|
65
97
|
}
|
|
66
98
|
/**
|
|
67
99
|
* Respawn loop around the real server. Spawns this same bin script via the
|
|
@@ -72,25 +104,18 @@ export async function runUi(cwd, opts) {
|
|
|
72
104
|
* workspaces start` reports) takes the whole UI down; Ctrl-C signals the
|
|
73
105
|
* foreground group and reaches both anyway.
|
|
74
106
|
*/
|
|
75
|
-
async function superviseUi(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// The tunnel lives with the supervisor, not the server: a self-restart
|
|
82
|
-
// (new version) swaps the server process and the tunnel stays up.
|
|
83
|
-
const project = await resolveWorkspace(cwd).catch(() => null);
|
|
84
|
-
const tunnel = project ? startTunnel(project, opts.port ? Number(opts.port) : (project.config.port ?? 1981)) : undefined;
|
|
107
|
+
async function superviseUi(opts) {
|
|
108
|
+
return supervise(["ui", "--standalone", ...(opts.port ? ["--port", opts.port] : []), ...(opts.output ? ["--output", opts.output] : [])], "the UI");
|
|
109
|
+
}
|
|
110
|
+
/** The respawn loop for any long-running kraftwerk process (`ui`, `daemon`): see superviseUi. */
|
|
111
|
+
export async function supervise(argv, what) {
|
|
112
|
+
const { cmd, args } = selfCommand(argv);
|
|
85
113
|
for (;;) {
|
|
86
114
|
const child = spawn(cmd, args, {
|
|
87
115
|
stdio: "inherit",
|
|
88
116
|
env: { ...process.env, KRAFTWERK_UI_SUPERVISED: "1" },
|
|
89
117
|
});
|
|
90
|
-
const forward = (sig) => () =>
|
|
91
|
-
tunnel?.stop();
|
|
92
|
-
child.kill(sig);
|
|
93
|
-
};
|
|
118
|
+
const forward = (sig) => () => child.kill(sig);
|
|
94
119
|
const handlers = { SIGTERM: forward("SIGTERM"), SIGINT: forward("SIGINT") };
|
|
95
120
|
process.on("SIGTERM", handlers.SIGTERM);
|
|
96
121
|
process.on("SIGINT", handlers.SIGINT);
|
|
@@ -100,13 +125,12 @@ async function superviseUi(cwd, opts) {
|
|
|
100
125
|
process.off("SIGTERM", handlers.SIGTERM);
|
|
101
126
|
process.off("SIGINT", handlers.SIGINT);
|
|
102
127
|
if (result.code !== RESTART_EXIT_CODE) {
|
|
103
|
-
tunnel?.stop();
|
|
104
128
|
// A signal-killed server is not a clean exit — say so in the exit code
|
|
105
129
|
// (128 + signal, the shell convention) so callers can tell.
|
|
106
130
|
if (result.signal)
|
|
107
131
|
process.exit(128 + (os.constants.signals[result.signal] ?? 1));
|
|
108
132
|
process.exit(result.code ?? 1);
|
|
109
133
|
}
|
|
110
|
-
console.log(chalk.dim(
|
|
134
|
+
console.log(chalk.dim(`↻ relaunching ${what} with the current install ...`));
|
|
111
135
|
}
|
|
112
136
|
}
|
package/dist/cli/workspaces.js
CHANGED
|
@@ -50,7 +50,9 @@ async function resolveRef(ref, needRoot = true) {
|
|
|
50
50
|
e.root === ref ||
|
|
51
51
|
e.name === ref ||
|
|
52
52
|
(e.root && path.basename(e.root) === ref) ||
|
|
53
|
-
e.url === port
|
|
53
|
+
e.url === port ||
|
|
54
|
+
// A daemon's workspace by its slug: <slug>.localhost.
|
|
55
|
+
e.url.startsWith(`http://${ref}.localhost:`));
|
|
54
56
|
if (hits.length === 1)
|
|
55
57
|
return hits[0];
|
|
56
58
|
if (hits.length === 0) {
|
|
@@ -117,8 +119,8 @@ export function registerWorkspaceCommands(program) {
|
|
|
117
119
|
});
|
|
118
120
|
workspaces
|
|
119
121
|
.command("stop")
|
|
120
|
-
.description("Stop a running UI (SIGTERM to
|
|
121
|
-
.argument("<ref>", "Workspace name, folder name, root path, or port")
|
|
122
|
+
.description("Stop a running UI (closes it in the kraftwerk daemon, or SIGTERM to a server of its own)")
|
|
123
|
+
.argument("<ref>", "Workspace name, slug, folder name, root path, or port")
|
|
122
124
|
.action(async (ref) => {
|
|
123
125
|
const entry = await resolveRef(ref, false);
|
|
124
126
|
if (!entry.live) {
|
package/dist/client/index.d.ts
CHANGED
|
@@ -63,7 +63,20 @@ export declare function errorOf(body: unknown): string | undefined;
|
|
|
63
63
|
export interface ClientOptions {
|
|
64
64
|
/** Where the server is; "" (the default) is the page's own origin. */
|
|
65
65
|
baseUrl?: string;
|
|
66
|
+
/**
|
|
67
|
+
* The workspace (its slug) to talk to, through the `/w/<slug>` path form.
|
|
68
|
+
* Leave it out when the address already names one (`<slug>.localhost`, or
|
|
69
|
+
* a server's own port).
|
|
70
|
+
*/
|
|
71
|
+
workspace?: string;
|
|
66
72
|
fetch?: typeof fetch;
|
|
73
|
+
/**
|
|
74
|
+
* A paired device's token (from `devices.pair`), sent as `Authorization:
|
|
75
|
+
* Bearer`. A browser needs none: pairing sets an HttpOnly cookie.
|
|
76
|
+
*/
|
|
77
|
+
token?: string;
|
|
78
|
+
/** Called when the server says this device is not paired (401 `{pair: true}`) — show the pairing screen. */
|
|
79
|
+
onUnauthorized?: () => void;
|
|
67
80
|
}
|
|
68
81
|
export interface Client {
|
|
69
82
|
call<N extends ApiName>(name: N, ...input: InputArg<N>): Promise<ApiResult<N>>;
|
|
@@ -72,5 +85,9 @@ export interface Client {
|
|
|
72
85
|
url<N extends ApiName>(name: N, ...input: InputArg<N>): string;
|
|
73
86
|
/** Called after every change this client made (a non-GET route that went through). */
|
|
74
87
|
onChange(fn: () => void): () => void;
|
|
88
|
+
/** The token the client was created with, if any (the socket sends it too). */
|
|
89
|
+
readonly token?: string;
|
|
90
|
+
/** Where routes live: baseUrl plus the workspace's `/w/<slug>` prefix. */
|
|
91
|
+
readonly baseUrl: string;
|
|
75
92
|
}
|
|
76
93
|
export declare function createClient(opts?: ClientOptions): Client;
|
package/dist/client/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export function errorOf(body) {
|
|
|
17
17
|
return typeof error === "string" && error ? error : undefined;
|
|
18
18
|
}
|
|
19
19
|
export function createClient(opts = {}) {
|
|
20
|
-
const base = (opts.baseUrl ?? "").replace(/\/+$/, "");
|
|
20
|
+
const base = (opts.baseUrl ?? "").replace(/\/+$/, "") + (opts.workspace ? `/w/${encodeURIComponent(opts.workspace)}` : "");
|
|
21
21
|
const doFetch = opts.fetch ?? ((...a) => fetch(...a));
|
|
22
22
|
const changeFns = new Set();
|
|
23
23
|
const url = (name, input = {}) => {
|
|
@@ -44,7 +44,11 @@ export function createClient(opts = {}) {
|
|
|
44
44
|
cache: "no-store",
|
|
45
45
|
signal,
|
|
46
46
|
keepalive,
|
|
47
|
-
headers: {
|
|
47
|
+
headers: {
|
|
48
|
+
...(body !== undefined && !raw ? { "content-type": "application/json" } : {}),
|
|
49
|
+
...(opts.token ? { authorization: `Bearer ${opts.token}` } : {}),
|
|
50
|
+
...headers,
|
|
51
|
+
},
|
|
48
52
|
body: body === undefined ? undefined : raw ? body : JSON.stringify(body),
|
|
49
53
|
});
|
|
50
54
|
const text = await res.text();
|
|
@@ -56,9 +60,13 @@ export function createClient(opts = {}) {
|
|
|
56
60
|
if (res.ok && method !== "GET")
|
|
57
61
|
for (const fn of changeFns)
|
|
58
62
|
fn();
|
|
63
|
+
if (res.status === 401 && data?.pair === true)
|
|
64
|
+
opts.onUnauthorized?.();
|
|
59
65
|
return res.ok ? { ok: true, status: res.status, data } : { ok: false, status: res.status, data, error: errorOf(data) };
|
|
60
66
|
};
|
|
61
67
|
return {
|
|
68
|
+
token: opts.token,
|
|
69
|
+
baseUrl: base,
|
|
62
70
|
onChange(fn) {
|
|
63
71
|
changeFns.add(fn);
|
|
64
72
|
return () => changeFns.delete(fn);
|
package/dist/client/live.d.ts
CHANGED
|
@@ -25,9 +25,8 @@ export interface Live {
|
|
|
25
25
|
close(): void;
|
|
26
26
|
}
|
|
27
27
|
export interface LiveOptions {
|
|
28
|
-
/** The HTTP client
|
|
28
|
+
/** The HTTP client: its baseUrl (workspace prefix included) is where the socket connects too. */
|
|
29
29
|
client: Client;
|
|
30
|
-
baseUrl?: string;
|
|
31
30
|
WebSocket?: typeof WebSocket;
|
|
32
31
|
EventSource?: typeof EventSource;
|
|
33
32
|
}
|
package/dist/client/live.js
CHANGED
|
@@ -12,8 +12,10 @@ export function createLive(opts) {
|
|
|
12
12
|
let retry;
|
|
13
13
|
let counter = 0;
|
|
14
14
|
const socketUrl = () => {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// The client's base carries the workspace prefix; a relative one is on this page's origin.
|
|
16
|
+
const base = /^https?:/.test(client.baseUrl) ? client.baseUrl : (typeof location === "undefined" ? "" : location.origin) + client.baseUrl;
|
|
17
|
+
// A browser's socket carries the device cookie; other runtimes cannot send headers on a WebSocket, so the token rides in the query.
|
|
18
|
+
return `${base.replace(/^http/, "ws").replace(/\/+$/, "")}/api/ws${client.token ? `?token=${encodeURIComponent(client.token)}` : ""}`;
|
|
17
19
|
};
|
|
18
20
|
const send = (msg) => {
|
|
19
21
|
if (connected)
|
package/dist/client/routes.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export declare const ROUTES: {
|
|
|
43
43
|
readonly "chats.setVibeable": readonly ["POST", "/api/chats/:id/vibeable"];
|
|
44
44
|
readonly "chats.steer": readonly ["POST", "/api/chats/:id/steer"];
|
|
45
45
|
readonly "chats.stopTask": readonly ["POST", "/api/chats/:id/task-stop"];
|
|
46
|
+
readonly "devices.code": readonly ["POST", "/api/devices/code"];
|
|
47
|
+
readonly "devices.list": readonly ["GET", "/api/devices"];
|
|
48
|
+
readonly "devices.pair": readonly ["POST", "/api/pair"];
|
|
49
|
+
readonly "devices.revoke": readonly ["DELETE", "/api/devices/:id"];
|
|
50
|
+
readonly "devices.self": readonly ["GET", "/api/devices/self"];
|
|
46
51
|
readonly "files.delete": readonly ["DELETE", "/api/files"];
|
|
47
52
|
readonly "files.list": readonly ["GET", "/api/files/list"];
|
|
48
53
|
readonly "files.mkdir": readonly ["POST", "/api/files/folder"];
|
|
@@ -57,6 +62,9 @@ export declare const ROUTES: {
|
|
|
57
62
|
readonly "git.pull": readonly ["POST", "/api/git/pull"];
|
|
58
63
|
readonly "git.push": readonly ["POST", "/api/git/push"];
|
|
59
64
|
readonly "git.status": readonly ["GET", "/api/git"];
|
|
65
|
+
readonly "hub.close": readonly ["POST", "/api/hub/close"];
|
|
66
|
+
readonly "hub.list": readonly ["GET", "/api/hub"];
|
|
67
|
+
readonly "hub.open": readonly ["POST", "/api/hub/open"];
|
|
60
68
|
readonly "knowledge.concept": readonly ["GET", "/api/knowledge/:bundle/concept"];
|
|
61
69
|
readonly "knowledge.create": readonly ["POST", "/api/knowledge"];
|
|
62
70
|
readonly "knowledge.delete": readonly ["DELETE", "/api/knowledge/:bundle"];
|
package/dist/client/routes.js
CHANGED
|
@@ -44,6 +44,11 @@ export const ROUTES = {
|
|
|
44
44
|
"chats.setVibeable": ["POST", "/api/chats/:id/vibeable"],
|
|
45
45
|
"chats.steer": ["POST", "/api/chats/:id/steer"],
|
|
46
46
|
"chats.stopTask": ["POST", "/api/chats/:id/task-stop"],
|
|
47
|
+
"devices.code": ["POST", "/api/devices/code"],
|
|
48
|
+
"devices.list": ["GET", "/api/devices"],
|
|
49
|
+
"devices.pair": ["POST", "/api/pair"],
|
|
50
|
+
"devices.revoke": ["DELETE", "/api/devices/:id"],
|
|
51
|
+
"devices.self": ["GET", "/api/devices/self"],
|
|
47
52
|
"files.delete": ["DELETE", "/api/files"],
|
|
48
53
|
"files.list": ["GET", "/api/files/list"],
|
|
49
54
|
"files.mkdir": ["POST", "/api/files/folder"],
|
|
@@ -58,6 +63,9 @@ export const ROUTES = {
|
|
|
58
63
|
"git.pull": ["POST", "/api/git/pull"],
|
|
59
64
|
"git.push": ["POST", "/api/git/push"],
|
|
60
65
|
"git.status": ["GET", "/api/git"],
|
|
66
|
+
"hub.close": ["POST", "/api/hub/close"],
|
|
67
|
+
"hub.list": ["GET", "/api/hub"],
|
|
68
|
+
"hub.open": ["POST", "/api/hub/open"],
|
|
61
69
|
"knowledge.concept": ["GET", "/api/knowledge/:bundle/concept"],
|
|
62
70
|
"knowledge.create": ["POST", "/api/knowledge"],
|
|
63
71
|
"knowledge.delete": ["DELETE", "/api/knowledge/:bundle"],
|
package/dist/config.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* name: my-project # display name (inspector header, "environment")
|
|
13
13
|
* icon: "⚡" # emoji shown as inspector favicon
|
|
14
14
|
* color: "#c2410c" # accent for this workspace in the switcher (default: derived from the root)
|
|
15
|
+
* slug: team-blau # this workspace's address (team-blau.localhost); default: the folder name
|
|
15
16
|
* port: 1981 # port `kraftwerk ui` listens on
|
|
16
17
|
* workflows: src/workflows # workflows root, relative to the file
|
|
17
18
|
* output: output # run-artifact directory, relative to the file
|
|
@@ -32,12 +33,6 @@
|
|
|
32
33
|
* root: apps # one folder per app, part of the workspace. Default: kraftwerk-data/vibeables
|
|
33
34
|
* projects: # goal-scoped folders (brief, systems of record, links) that chats work in (absent = off, bare key = on)
|
|
34
35
|
* root: kraftwerk-data/projects # one folder per project, part of the workspace. Default: kraftwerk-data/projects
|
|
35
|
-
* public: https://kw.example.com # hostname the inspector is reached at through a tunnel or reverse proxy
|
|
36
|
-
* tunnel: # Cloudflare Tunnel run by `kraftwerk ui` (absent = off, bare key = on)
|
|
37
|
-
* name: kraftwerk # locally-managed tunnel (cloudflared tunnel create); absent: TUNNEL_TOKEN env, dashboard-managed
|
|
38
|
-
* access: # verify the Cloudflare Access login on every request that arrives via `public`
|
|
39
|
-
* team: acme # Zero Trust team name (https://<team>.cloudflareaccess.com)
|
|
40
|
-
* aud: 4714c135… # Application Audience tag of the Access application
|
|
41
36
|
* cloud: # kraftwerk cloud manager — ON BY DEFAULT, `enabled: false` opts out
|
|
42
37
|
* url: https://srv.kraftwerk-cloud.netnode.cloud # where the cloud answers (default). KRAFTWERK_CLOUD_URL env wins; "off" disables
|
|
43
38
|
* interval: 60 # seconds between heartbeats. Default: 60
|
|
@@ -126,39 +121,6 @@ export interface ProjectsConfig {
|
|
|
126
121
|
export declare const PROJECTS_DEFAULT_ROOT = "kraftwerk-data/projects";
|
|
127
122
|
/** Absolute projects root when the feature is on, undefined otherwise. */
|
|
128
123
|
export declare function projectsRootFor(project: ResolvedWorkspace): string | undefined;
|
|
129
|
-
/**
|
|
130
|
-
* Cloudflare Access in front of the public hostname. Access puts a login
|
|
131
|
-
* page on the hostname at Cloudflare's edge and hands the origin a signed
|
|
132
|
-
* JWT per request (Cf-Access-Jwt-Assertion). With this block the inspector
|
|
133
|
-
* verifies that token itself, so a removed or misconfigured Access policy
|
|
134
|
-
* fails closed instead of exposing the UI, which has no login of its own.
|
|
135
|
-
*/
|
|
136
|
-
export interface AccessConfig {
|
|
137
|
-
/** Zero Trust team name: the subdomain of https://<team>.cloudflareaccess.com */
|
|
138
|
-
team: string;
|
|
139
|
-
/** Application Audience (AUD) tag of the Access application, from its overview page. */
|
|
140
|
-
aud: string;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Cloudflare Tunnel: `kraftwerk ui` runs cloudflared next to the inspector
|
|
144
|
-
* so the loopback bind is reachable at `public` without an open port. The
|
|
145
|
-
* tunnel itself is created once with cloudflared (or in the Zero Trust
|
|
146
|
-
* dashboard); this block only says which one to run.
|
|
147
|
-
*/
|
|
148
|
-
export interface TunnelConfig {
|
|
149
|
-
/** false keeps the block but turns the feature off. Default: true. */
|
|
150
|
-
enabled?: boolean;
|
|
151
|
-
/**
|
|
152
|
-
* Name of a locally-managed tunnel (`cloudflared tunnel create <name>`,
|
|
153
|
-
* `cloudflared tunnel route dns <name> <public host>`); cloudflared routes
|
|
154
|
-
* everything to the inspector port. Absent: a dashboard-managed tunnel,
|
|
155
|
-
* whose token comes from the TUNNEL_TOKEN environment variable and whose
|
|
156
|
-
* route to http://localhost:<port> is configured in the dashboard.
|
|
157
|
-
*/
|
|
158
|
-
name?: string;
|
|
159
|
-
/** Verify the Cloudflare Access token on every request that arrives via `public`. */
|
|
160
|
-
access?: AccessConfig;
|
|
161
|
-
}
|
|
162
124
|
/**
|
|
163
125
|
* Cloud manager: the kraftwerk cloud is ~/.kraftwerk made reachable over
|
|
164
126
|
* the internet — a registry of instances across machines. Every inspector
|
|
@@ -189,14 +151,6 @@ export interface CloudSettings {
|
|
|
189
151
|
}
|
|
190
152
|
/** The effective cloud settings when the feature is on; undefined when kraftwerk.yml or the environment turned it off. */
|
|
191
153
|
export declare function cloudFor(project: ResolvedWorkspace): CloudSettings | undefined;
|
|
192
|
-
/** The public hostname (lowercase, no port) when `public` is set, undefined otherwise. */
|
|
193
|
-
export declare function publicHostFor(project: ResolvedWorkspace): string | undefined;
|
|
194
|
-
/** The public origin ("https://kw.example.com") when `public` is set, undefined otherwise. */
|
|
195
|
-
export declare function publicUrlFor(project: ResolvedWorkspace): string | undefined;
|
|
196
|
-
/** The tunnel block when the feature is on, undefined otherwise. */
|
|
197
|
-
export declare function tunnelFor(project: ResolvedWorkspace): TunnelConfig | undefined;
|
|
198
|
-
/** "kw.example.com" or "https://kw.example.com[:port]" → its URL; undefined when it is neither. */
|
|
199
|
-
export declare function parsePublic(value: string): URL | undefined;
|
|
200
154
|
/** A directory as a .gitignore entry: relative, forward slashes, no trailing slash; undefined outside the root. */
|
|
201
155
|
export declare function ignoreEntryFor(projectRoot: string, dir: string): string | undefined;
|
|
202
156
|
/** True when .gitignore text already covers the entry (with or without a leading or trailing slash). */
|
|
@@ -213,6 +167,12 @@ export interface SwitcherEntry {
|
|
|
213
167
|
export interface WorkspaceConfig {
|
|
214
168
|
/** Display name of the project ("environment"), shown in the inspector header. */
|
|
215
169
|
name?: string;
|
|
170
|
+
/**
|
|
171
|
+
* The workspace's address on this machine: `<slug>.localhost`, `/w/<slug>/`.
|
|
172
|
+
* Unique per machine. Default: the folder name (see workspaceSlug). The
|
|
173
|
+
* name above may change freely; the slug only when you mean to move it.
|
|
174
|
+
*/
|
|
175
|
+
slug?: string;
|
|
216
176
|
/** Emoji used as the inspector favicon (browser-tab icon). */
|
|
217
177
|
icon?: string;
|
|
218
178
|
/** Accent colour (#rgb / #rrggbb) for this workspace in the switcher. Default: derived from the root path. */
|
|
@@ -239,14 +199,6 @@ export interface WorkspaceConfig {
|
|
|
239
199
|
vibeables?: VibeablesConfig;
|
|
240
200
|
/** Projects: goal-scoped folders the chats work in. Absent = off. */
|
|
241
201
|
projects?: ProjectsConfig;
|
|
242
|
-
/**
|
|
243
|
-
* Hostname the inspector is reached at through a tunnel or reverse proxy,
|
|
244
|
-
* e.g. "https://kw.example.com". The loopback bind then answers requests
|
|
245
|
-
* carrying that Host even without X-Forwarded-Host (cloudflared sends none).
|
|
246
|
-
*/
|
|
247
|
-
public?: string;
|
|
248
|
-
/** Cloudflare Tunnel run alongside the inspector. Absent = off. */
|
|
249
|
-
tunnel?: TunnelConfig;
|
|
250
202
|
/** kraftwerk cloud manager this instance registers with. Absent = on with defaults; `enabled: false` opts out. */
|
|
251
203
|
cloud?: CloudConfig;
|
|
252
204
|
}
|
|
@@ -261,6 +213,8 @@ export interface ResolvedWorkspace {
|
|
|
261
213
|
workflowsRoot?: string;
|
|
262
214
|
/** Absolute run-artifact directory (may not exist yet). */
|
|
263
215
|
outputDir: string;
|
|
216
|
+
/** Keys in kraftwerk.yml that an older kraftwerk read and this one ignores (`public`, `tunnel`), for doctor. Absent when none. */
|
|
217
|
+
legacyKeys?: string[];
|
|
264
218
|
}
|
|
265
219
|
/** True when the path exists and is a directory. */
|
|
266
220
|
/**
|
|
@@ -287,3 +241,12 @@ export declare const resolveProject: typeof resolveWorkspace;
|
|
|
287
241
|
export type Project = ResolvedWorkspace;
|
|
288
242
|
/** @deprecated Use `WorkspaceConfig`. */
|
|
289
243
|
export type ProjectConfig = WorkspaceConfig;
|
|
244
|
+
/** A folder name as a slug: "Team Blau (2024)" → "team-blau-2024". */
|
|
245
|
+
export declare function slugFromFolder(folder: string): string;
|
|
246
|
+
/** The workspace's slug: kraftwerk.yml `slug`, else its folder name made into one. */
|
|
247
|
+
export declare function workspaceSlug(ws: {
|
|
248
|
+
root: string;
|
|
249
|
+
config: {
|
|
250
|
+
slug?: string;
|
|
251
|
+
};
|
|
252
|
+
}): string;
|