@netnodeag/kraftwerk 0.68.0 → 0.70.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 +21 -14
- package/dist/cli/daemon-login.d.ts +1 -0
- package/dist/cli/daemon-login.js +3 -1
- package/dist/cli/daemon.js +4 -2
- package/dist/server/server.js +26 -9
- package/package.json +1 -1
- package/web/dist/assets/dist-1EXSVa5k.js +1 -0
- package/web/dist/assets/{dist-D_nqqdyh.js → dist-5v0UmV-F.js} +1 -1
- package/web/dist/assets/{dist-D7BYSDIr.js → dist-B-eaMFbi.js} +1 -1
- package/web/dist/assets/{dist-Y0KLvK_g.js → dist-B5BBuAK8.js} +1 -1
- package/web/dist/assets/{dist-jyZ9dxfk.js → dist-BeHtCIB0.js} +1 -1
- package/web/dist/assets/{dist-aMqhK50K.js → dist-CkKJVLbX.js} +1 -1
- package/web/dist/assets/{dist-IO8GeoYh.js → dist-CxMl8Xeq.js} +1 -1
- package/web/dist/assets/{dist-CZjwFXw-.js → dist-D4wB4wkJ.js} +1 -1
- package/web/dist/assets/{dist-BMZBs9W4.js → dist-DGfHUaeU.js} +1 -1
- package/web/dist/assets/{dist-BkZKygT6.js → dist-DOUkQhjz.js} +1 -1
- package/web/dist/assets/{dist-LUKHNpC7.js → dist-DPXNFIR0.js} +1 -1
- package/web/dist/assets/{editor-DQouCIUq.js → editor-oLm1tYCp.js} +3 -3
- package/web/dist/assets/index-CrD3grQE.css +2 -0
- package/web/dist/assets/{index-D57IrV_V.js → index-V2VT7ZQP.js} +6 -6
- package/web/dist/index.html +2 -2
- package/web/dist/assets/dist-DZLlNafq.js +0 -1
- package/web/dist/assets/index-BAYs-2WC.css +0 -2
package/README.md
CHANGED
|
@@ -95,13 +95,18 @@ you when it opens.
|
|
|
95
95
|
`kraftwerk ui` hands the workspace to the daemon whenever one is running;
|
|
96
96
|
`kraftwerk ui --standalone` starts a server of its own anyway.
|
|
97
97
|
|
|
98
|
-
**From your phone or another computer
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
**From your phone or another computer** on the same network:
|
|
99
|
+
|
|
100
|
+
1. Let the daemon listen on your network: `kraftwerk daemon install --lan`
|
|
101
|
+
(or run `kraftwerk daemon --lan`). It prints the address, for example
|
|
102
|
+
`http://192.168.1.20:1980`.
|
|
103
|
+
2. Open that address on the phone. It shows a pairing form, nothing else.
|
|
104
|
+
3. On this computer, run `kraftwerk devices pair` (or settings → devices →
|
|
105
|
+
pair a device) and type the code on the phone.
|
|
106
|
+
|
|
107
|
+
The phone now sees your workspaces, each at `/w/<slug>/` on that address,
|
|
108
|
+
and the workspace menu switches between them. One pairing covers all of them
|
|
109
|
+
(see [Inspector on your phone](#inspector-on-your-phone-paired-devices)).
|
|
105
110
|
|
|
106
111
|
**What to do first in the inspector.** Talk to **Ralv**, the workspace's
|
|
107
112
|
general chat, in plain words: it knows what is in the workspace and helps
|
|
@@ -231,7 +236,7 @@ kraftwerk runs # past runs from output/*/trace.jsonl; r
|
|
|
231
236
|
kraftwerk knowledge # Knowledge: OKF bundles (list/get/put/verify/search/...)
|
|
232
237
|
kraftwerk ui # start this workspace: http://localhost:1981 (or open it in the daemon); --port, --lan, --standalone
|
|
233
238
|
kraftwerk daemon # one kraftwerk for every open workspace: http://<slug>.localhost:1980; --port, --lan
|
|
234
|
-
kraftwerk daemon install # start the daemon at login (macOS LaunchAgent); `daemon uninstall` removes it
|
|
239
|
+
kraftwerk daemon install # start the daemon at login (macOS LaunchAgent); --lan, --port; `daemon uninstall` removes it
|
|
235
240
|
kraftwerk workspaces # every workspace on this machine; workspaces start|stop|forget <ref>
|
|
236
241
|
kraftwerk devices # paired devices (a phone, another computer); devices pair|revoke
|
|
237
242
|
kraftwerk projects # goal-scoped project folders; projects create|show|link|log|remove
|
|
@@ -384,13 +389,15 @@ follows to author the workflow folder and validate and smoke it with this CLI.
|
|
|
384
389
|
|
|
385
390
|
### Inspector on your phone (paired devices)
|
|
386
391
|
|
|
387
|
-
`kraftwerk ui --lan` (or `kraftwerk daemon --lan`
|
|
388
|
-
network and prints the
|
|
389
|
-
On the daemon, a device
|
|
392
|
+
`kraftwerk ui --lan` (or `kraftwerk daemon --lan`, `kraftwerk daemon install
|
|
393
|
+
--lan` for the login item) also listens on your network and prints the
|
|
394
|
+
addresses (for example `http://192.168.1.20:1981`). On the daemon, a device
|
|
395
|
+
opens a workspace at `/w/<slug>/` on that address
|
|
390
396
|
(`http://192.168.1.20:1980/w/team-blau/`), because `<slug>.localhost` only
|
|
391
|
-
works on this machine. The daemon's own address lists them
|
|
392
|
-
|
|
393
|
-
|
|
397
|
+
works on this machine. The daemon's own address lists them once the device
|
|
398
|
+
is paired, and pairs it when it is not, and the workspace menu links the
|
|
399
|
+
others the same way. Anyone who opens one gets a pairing screen, nothing
|
|
400
|
+
else. To let a device in, make a one-time code on
|
|
394
401
|
the machine kraftwerk runs on, with `kraftwerk devices pair` or settings →
|
|
395
402
|
devices → pair a device, and enter it on the device. The code works once,
|
|
396
403
|
for 10 minutes. The device stays paired until you unpair it in the same
|
|
@@ -12,6 +12,7 @@ export declare const LAUNCH_LABEL = "ch.netnode.kraftwerk.daemon";
|
|
|
12
12
|
export declare function renderPlist(program: string[], env: Record<string, string>, log: string): string;
|
|
13
13
|
export declare function installDaemon(opts: {
|
|
14
14
|
port?: string;
|
|
15
|
+
lan?: boolean;
|
|
15
16
|
}): Promise<void>;
|
|
16
17
|
export declare function uninstallDaemon(): Promise<void>;
|
|
17
18
|
/** For doctor: whether the login item is installed. */
|
package/dist/cli/daemon-login.js
CHANGED
|
@@ -62,7 +62,7 @@ function requireMac() {
|
|
|
62
62
|
}
|
|
63
63
|
export async function installDaemon(opts) {
|
|
64
64
|
requireMac();
|
|
65
|
-
const { cmd, args } = selfCommand(["daemon", ...(opts.port ? ["--port", opts.port] : [])]);
|
|
65
|
+
const { cmd, args } = selfCommand(["daemon", ...(opts.port ? ["--port", opts.port] : []), ...(opts.lan ? ["--lan"] : [])]);
|
|
66
66
|
const env = { PATH: process.env.PATH ?? "/usr/bin:/bin" };
|
|
67
67
|
if (process.env.KRAFTWERK_DAEMON_PORT && !opts.port)
|
|
68
68
|
env.KRAFTWERK_DAEMON_PORT = process.env.KRAFTWERK_DAEMON_PORT;
|
|
@@ -86,6 +86,8 @@ export async function installDaemon(opts) {
|
|
|
86
86
|
process.exit(1);
|
|
87
87
|
}
|
|
88
88
|
console.log(`${chalk.green("✔")} login item installed and started: ${chalk.dim(tildify(file))}`);
|
|
89
|
+
if (opts.lan)
|
|
90
|
+
console.log(chalk.dim(" on your network too: devices pair first (`kraftwerk devices pair`)"));
|
|
89
91
|
console.log(chalk.dim(` log: ${tildify(logPath())} · \`kraftwerk daemon uninstall\` removes it`));
|
|
90
92
|
}
|
|
91
93
|
export async function uninstallDaemon() {
|
package/dist/cli/daemon.js
CHANGED
|
@@ -62,9 +62,11 @@ export function registerDaemonCommand(program) {
|
|
|
62
62
|
.command("install")
|
|
63
63
|
.description("Start the daemon at login (macOS LaunchAgent), and now unless one runs")
|
|
64
64
|
.option("--port <port>", `Port (default: KRAFTWERK_DAEMON_PORT, else ${DAEMON_PORT})`)
|
|
65
|
+
.option("--lan", "Listen on the network too, for paired devices (a phone on the same Wi-Fi)")
|
|
65
66
|
.action(async (opts) => {
|
|
66
|
-
// `--port` after `install`
|
|
67
|
-
|
|
67
|
+
// `--port`/`--lan` after `install` are taken by `daemon` itself, which has the same options.
|
|
68
|
+
const parent = daemon.opts();
|
|
69
|
+
await installDaemon({ port: opts.port ?? parent.port, lan: opts.lan ?? parent.lan });
|
|
68
70
|
});
|
|
69
71
|
daemon
|
|
70
72
|
.command("uninstall")
|
package/dist/server/server.js
CHANGED
|
@@ -169,8 +169,8 @@ function installExitHandlers() {
|
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
171
|
/**
|
|
172
|
-
* Listen on `port`. On the default bind (127.0.0.1)
|
|
173
|
-
* feeds the same server: macOS resolves localhost and <slug>.localhost to
|
|
172
|
+
* Listen on `port`. On the default bind (127.0.0.1), and on 0.0.0.0
|
|
173
|
+
* (`--lan`, IPv4 only), the same port on ::1 feeds the same server: macOS resolves localhost and <slug>.localhost to
|
|
174
174
|
* ::1 first, and a client that does not fall back to IPv4 (a native app's
|
|
175
175
|
* HTTP library) would find nothing there. Best effort — without IPv6, or
|
|
176
176
|
* with ::1:port taken, it stays IPv4 only, as before.
|
|
@@ -184,7 +184,7 @@ async function listen(server, port) {
|
|
|
184
184
|
resolve(typeof addr === "object" && addr ? addr.port : port);
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
|
-
if (INSPECTOR_HOST !== "127.0.0.1")
|
|
187
|
+
if (INSPECTOR_HOST !== "127.0.0.1" && INSPECTOR_HOST !== "0.0.0.0")
|
|
188
188
|
return bound;
|
|
189
189
|
const twin = net.createServer((socket) => server.emit("connection", socket));
|
|
190
190
|
const ok = await new Promise((resolve) => {
|
|
@@ -205,18 +205,35 @@ async function listen(server, port) {
|
|
|
205
205
|
/**
|
|
206
206
|
* A small page at a daemon's own address: the open workspaces, each a link —
|
|
207
207
|
* <slug>.localhost on this machine, the path form on the address a paired
|
|
208
|
-
* device used (it cannot resolve <slug>.localhost).
|
|
208
|
+
* device used (it cannot resolve <slug>.localhost). A device that is not
|
|
209
|
+
* paired yet gets a pairing form instead, and no workspace names: the code
|
|
210
|
+
* comes from this machine (`kraftwerk devices pair`), and the cookie it earns
|
|
211
|
+
* covers every workspace on this address.
|
|
209
212
|
*/
|
|
210
|
-
function hubPage(req, res, open) {
|
|
213
|
+
function hubPage(req, res, open, trust) {
|
|
211
214
|
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
212
215
|
// Behind a proxy on this machine the browser's name is the forwarded one.
|
|
213
216
|
const host = hostnameOf(forwardedHost(req) ?? req.headers.host ?? "");
|
|
214
217
|
const local = LOOPBACK_NAMES.has(host) || host.endsWith(".localhost");
|
|
218
|
+
const head = `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>kraftwerk</title>` +
|
|
219
|
+
`<style>:root{color-scheme:light dark}body{font:16px/1.6 system-ui,sans-serif;max-width:560px;margin:40px auto;padding:0 16px}` +
|
|
220
|
+
`code{opacity:.7;font-size:13px}ul{list-style:none;padding:0}li{border-bottom:1px solid #8884}li a{display:block;padding:12px 0;font-weight:600;text-decoration:none;color:inherit}` +
|
|
221
|
+
`input,button{font:inherit;padding:10px 12px;border-radius:10px;border:1px solid #8888;width:100%;box-sizing:border-box;margin:6px 0}` +
|
|
222
|
+
`input[name=code]{letter-spacing:.2em;text-transform:uppercase}button{background:#5b4bd5;color:#fff;border:0;font-weight:600}#err{color:#c62828}</style><h1>kraftwerk</h1>`;
|
|
223
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
224
|
+
if (trust.kind === "none") {
|
|
225
|
+
return void res.end(head +
|
|
226
|
+
`<p>Pair this device to use kraftwerk on this computer. On the computer, run <code>kraftwerk devices pair</code> (or settings → devices → pair a device) and enter the code here.</p>` +
|
|
227
|
+
`<form id="pair"><input name="code" placeholder="code" autocomplete="one-time-code" autocapitalize="characters" required>` +
|
|
228
|
+
`<input name="name" placeholder="this device's name, e.g. Lukas' iPhone"><button>pair</button><p id="err"></p></form>` +
|
|
229
|
+
`<script>document.getElementById("pair").onsubmit=async(e)=>{e.preventDefault();const f=new FormData(e.target);` +
|
|
230
|
+
`const r=await fetch("/api/pair",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({code:String(f.get("code")).trim(),name:String(f.get("name")||navigator.platform||"device")})});` +
|
|
231
|
+
`if(r.ok)location.reload();else document.getElementById("err").textContent=(await r.json().catch(()=>({}))).error||"pairing failed"}</script>`);
|
|
232
|
+
}
|
|
215
233
|
const rows = open.length
|
|
216
|
-
? open.map((o) => `<li><a href="${esc(local ? `${o.url}/` : `/w/${o.slug}/`)}">${esc(o.name)}
|
|
234
|
+
? open.map((o) => `<li><a href="${esc(local ? `${o.url}/` : `/w/${o.slug}/`)}">${esc(o.name)} <code>${esc(o.slug)}</code></a></li>`).join("")
|
|
217
235
|
: "<li>No workspace is open. Run <code>kraftwerk ui</code> in a workspace folder.</li>";
|
|
218
|
-
res.
|
|
219
|
-
res.end(`<!doctype html><meta charset="utf-8"><title>kraftwerk</title><style>body{font:15px/1.6 system-ui,sans-serif;max-width:640px;margin:48px auto;padding:0 16px;color:#1b1b1f}code{color:#46464f}</style><h1>kraftwerk</h1><ul>${rows}</ul>`);
|
|
236
|
+
res.end(`${head}<ul>${rows}</ul>`);
|
|
220
237
|
}
|
|
221
238
|
export async function startHub(opts) {
|
|
222
239
|
const trustOpts = { trustLoopback: opts.trustLoopback !== false && !REQUIRE_PAIRING, trustNetwork: TRUST_NETWORK };
|
|
@@ -260,7 +277,7 @@ export async function startHub(opts) {
|
|
|
260
277
|
}
|
|
261
278
|
// A daemon's own address lists its workspaces; anywhere else, the web UI's shell (public: an unpaired browser needs it to pair).
|
|
262
279
|
if (!open && (url.pathname === "/" || url.pathname === "/index.html"))
|
|
263
|
-
return hubPage(req, res, [...bySlug.values()]);
|
|
280
|
+
return hubPage(req, res, [...bySlug.values()], trust);
|
|
264
281
|
await serveStatic(res, opts.staticDir, url.pathname);
|
|
265
282
|
}
|
|
266
283
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netnodeag/kraftwerk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "Open-source agentic workspace for teams — where people and AI agents get work done together: agents (Claude Code, Codex, Pi), skills, shared knowledge, repeatable workflows, and deterministic verification",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e}from"./editor-oLm1tYCp.js";export{e as markdown};
|