@netnodeag/kraftwerk 0.37.0 → 0.38.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/dist/cli/projects.js +2 -1
- package/dist/cli/ui.js +2 -2
- package/dist/config.d.ts +11 -0
- package/dist/config.js +20 -1
- package/dist/inspector/instances.d.ts +8 -0
- package/dist/inspector/instances.js +30 -10
- package/dist/inspector/server.js +2 -0
- package/dist/inspector/settings.d.ts +2 -0
- package/dist/inspector/settings.js +8 -0
- package/inspector/dist/assets/{editor-vM6JSj5h.js → editor-BnDJd3j4.js} +3 -3
- package/inspector/dist/assets/{index-MWv5H1oP.js → index-1K-YGe-R.js} +1 -1
- package/inspector/dist/assets/{index-C1UXlW32.js → index-5n-x53jb.js} +1 -1
- package/inspector/dist/assets/{index-BAAjszPI.js → index-B6zeZ-I9.js} +1 -1
- package/inspector/dist/assets/{index-CtMGH62C.js → index-BCwvYdEE.js} +1 -1
- package/inspector/dist/assets/{index-DfZT0IoM.js → index-BDHHRQvJ.js} +1 -1
- package/inspector/dist/assets/{index-D_11IZBV.js → index-BFmXikOX.js} +1 -1
- package/inspector/dist/assets/{index-Bb08wK-l.js → index-C2kxy-K_.js} +1 -1
- package/inspector/dist/assets/{index-DykDNrYm.js → index-CTECF5uX.js} +1 -1
- package/inspector/dist/assets/index-CtnGrZvu.js +217 -0
- package/inspector/dist/assets/{index-BrCwDkJZ.js → index-DBHgQG4M.js} +1 -1
- package/inspector/dist/assets/{index-CQ2XKqmD.js → index-DfqhDHp9.js} +1 -1
- package/inspector/dist/assets/index-DjMx3a5H.css +1 -0
- package/inspector/dist/assets/{index-yGZsomcy.js → index-DmY7gYaR.js} +1 -1
- package/inspector/dist/assets/{index-CaKmxcD6.js → index-Dq3QrDLb.js} +1 -1
- package/inspector/dist/assets/{index-oIQqfbwm.js → index-Fd3s8yys.js} +1 -1
- package/inspector/dist/assets/{index-Bs1U0rWW.js → index-Rmy9pam8.js} +1 -1
- package/inspector/dist/assets/{index-CUGfxoYt.js → index-VxlzgVIX.js} +1 -1
- package/inspector/dist/assets/{index-CReepirH.js → index-o4xPJnqZ.js} +1 -1
- package/inspector/dist/assets/{index-BKcOp35r.js → index-sSEE6ykA.js} +1 -1
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/inspector/dist/assets/index-Ba8VU6Wz.js +0 -217
- package/inspector/dist/assets/index-D4_mLTSH.css +0 -1
package/dist/cli/projects.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { absolutePath } from "../config.js";
|
|
3
4
|
import Table from "cli-table3";
|
|
4
5
|
import { discoverWorkspaces, forgetProject, startProject, stopProject, tildify, } from "../inspector/instances.js";
|
|
5
6
|
/**
|
|
@@ -43,7 +44,7 @@ const status = (e) => {
|
|
|
43
44
|
/** Match a ref against known workspaces; `needRoot` excludes rootless live instances (start/forget need a root). */
|
|
44
45
|
async function resolveRef(ref, needRoot = true) {
|
|
45
46
|
const all = (await discoverWorkspaces()).filter((e) => e.root || !needRoot);
|
|
46
|
-
const abs =
|
|
47
|
+
const abs = absolutePath(ref);
|
|
47
48
|
const port = /^\d+$/.test(ref) ? `http://localhost:${ref}` : `http://${ref.replace(/^https?:\/\//, "")}`;
|
|
48
49
|
const hits = all.filter((e) => e.root === abs ||
|
|
49
50
|
e.root === ref ||
|
package/dist/cli/ui.js
CHANGED
|
@@ -4,7 +4,7 @@ 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 { resolveProject } from "../config.js";
|
|
7
|
+
import { absolutePath, resolveProject } from "../config.js";
|
|
8
8
|
import { selfCommand } from "../inspector/self-command.js";
|
|
9
9
|
import { RESTART_EXIT_CODE, startInspector } from "../inspector/server.js";
|
|
10
10
|
/**
|
|
@@ -52,7 +52,7 @@ export async function runUi(cwd, opts) {
|
|
|
52
52
|
return superviseUi(opts);
|
|
53
53
|
const staticDir = ensureBuilt();
|
|
54
54
|
const project = await resolveProject(cwd);
|
|
55
|
-
const outputDir = opts.output ?
|
|
55
|
+
const outputDir = opts.output ? absolutePath(opts.output, cwd) : project.outputDir;
|
|
56
56
|
// Port precedence: --port flag > kraftwerk.yml `port` > 1981.
|
|
57
57
|
const port = opts.port ? Number(opts.port) : (project.config.port ?? 1981);
|
|
58
58
|
await startInspector({ outputDir, staticDir, port, projectRoot: project.root });
|
package/dist/config.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* name: my-project # display name (inspector header, "environment")
|
|
13
13
|
* icon: "⚡" # emoji shown as inspector favicon
|
|
14
|
+
* color: "#c2410c" # accent for this workspace in the switcher (default: derived from the root)
|
|
14
15
|
* port: 1981 # port `kraftwerk ui` listens on
|
|
15
16
|
* workflows: src/workflows # workflows root, relative to the file
|
|
16
17
|
* output: output # run-artifact directory, relative to the file
|
|
@@ -88,6 +89,8 @@ export interface ProjectConfig {
|
|
|
88
89
|
name?: string;
|
|
89
90
|
/** Emoji used as the inspector favicon (browser-tab icon). */
|
|
90
91
|
icon?: string;
|
|
92
|
+
/** Accent colour (#rgb / #rrggbb) for this workspace in the switcher. Default: derived from the root path. */
|
|
93
|
+
color?: string;
|
|
91
94
|
/** Port `kraftwerk ui` listens on. Default: 1981 (CLI --port wins). */
|
|
92
95
|
port?: number;
|
|
93
96
|
/** Workflows root relative to the project root. */
|
|
@@ -120,6 +123,14 @@ export interface Project {
|
|
|
120
123
|
outputDir: string;
|
|
121
124
|
}
|
|
122
125
|
/** True when the path exists and is a directory. */
|
|
126
|
+
/**
|
|
127
|
+
* A user-supplied path as an absolute one: a leading `~` is the home
|
|
128
|
+
* directory (shells expand it, JSON bodies and quoted args do not), then
|
|
129
|
+
* path.resolve. Every path that gets stored or compared — registry roots,
|
|
130
|
+
* `--output`, project refs — goes through here so nothing ever persists
|
|
131
|
+
* `~/…` or `<cwd>/~/…`.
|
|
132
|
+
*/
|
|
133
|
+
export declare function absolutePath(p: string, from?: string): string;
|
|
123
134
|
export declare const isDir: (p: string) => Promise<boolean>;
|
|
124
135
|
/**
|
|
125
136
|
* Resolve the project for a cwd: walk up until a kraftwerk.yml or a
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { parse } from "yaml";
|
|
4
5
|
/**
|
|
@@ -14,6 +15,7 @@ import { parse } from "yaml";
|
|
|
14
15
|
*
|
|
15
16
|
* name: my-project # display name (inspector header, "environment")
|
|
16
17
|
* icon: "⚡" # emoji shown as inspector favicon
|
|
18
|
+
* color: "#c2410c" # accent for this workspace in the switcher (default: derived from the root)
|
|
17
19
|
* port: 1981 # port `kraftwerk ui` listens on
|
|
18
20
|
* workflows: src/workflows # workflows root, relative to the file
|
|
19
21
|
* output: output # run-artifact directory, relative to the file
|
|
@@ -63,6 +65,18 @@ export function gitignoreHas(text, entry) {
|
|
|
63
65
|
}
|
|
64
66
|
const exists = async (p) => !!(await stat(p).catch(() => null));
|
|
65
67
|
/** True when the path exists and is a directory. */
|
|
68
|
+
/**
|
|
69
|
+
* A user-supplied path as an absolute one: a leading `~` is the home
|
|
70
|
+
* directory (shells expand it, JSON bodies and quoted args do not), then
|
|
71
|
+
* path.resolve. Every path that gets stored or compared — registry roots,
|
|
72
|
+
* `--output`, project refs — goes through here so nothing ever persists
|
|
73
|
+
* `~/…` or `<cwd>/~/…`.
|
|
74
|
+
*/
|
|
75
|
+
export function absolutePath(p, from = process.cwd()) {
|
|
76
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\"))
|
|
77
|
+
p = path.join(os.homedir(), p.slice(1));
|
|
78
|
+
return path.resolve(from, p);
|
|
79
|
+
}
|
|
66
80
|
export const isDir = async (p) => (await stat(p).catch(() => null))?.isDirectory() ?? false;
|
|
67
81
|
async function findConfigFile(dir) {
|
|
68
82
|
for (const name of CONFIG_FILENAMES) {
|
|
@@ -113,7 +127,7 @@ export async function resolveProject(cwd) {
|
|
|
113
127
|
const root = gitFallback ?? start;
|
|
114
128
|
return { root, config: {}, outputDir: path.join(root, "output") };
|
|
115
129
|
}
|
|
116
|
-
const KNOWN_KEYS = ["name", "icon", "port", "workflows", "output", "knowledge", "agents", "skills", "switcher", "git", "repos"];
|
|
130
|
+
const KNOWN_KEYS = ["name", "icon", "color", "port", "workflows", "output", "knowledge", "agents", "skills", "switcher", "git", "repos"];
|
|
117
131
|
async function loadConfig(configPath) {
|
|
118
132
|
let raw;
|
|
119
133
|
try {
|
|
@@ -138,6 +152,11 @@ async function loadConfig(configPath) {
|
|
|
138
152
|
throw new Error(`${path.basename(configPath)}: port must be an integer`);
|
|
139
153
|
}
|
|
140
154
|
}
|
|
155
|
+
else if (key === "color") {
|
|
156
|
+
if (typeof config[key] !== "string" || !/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(config[key])) {
|
|
157
|
+
throw new Error(`${path.basename(configPath)}: color must be a hex colour like "#c2410c"`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
141
160
|
else if (key === "switcher") {
|
|
142
161
|
validateSwitcher(configPath, config[key]);
|
|
143
162
|
}
|
|
@@ -15,6 +15,10 @@ export interface DiscoveredInstance {
|
|
|
15
15
|
name: string;
|
|
16
16
|
url: string;
|
|
17
17
|
icon?: string;
|
|
18
|
+
/** kraftwerk.yml `color`, if set. */
|
|
19
|
+
color?: string;
|
|
20
|
+
/** True when the name comes from kraftwerk.yml (false = folder name). */
|
|
21
|
+
named?: boolean;
|
|
18
22
|
live: true;
|
|
19
23
|
root?: string;
|
|
20
24
|
/** Server process id (what to signal to stop it). */
|
|
@@ -29,6 +33,10 @@ export interface WorkspaceEntry {
|
|
|
29
33
|
/** Where the inspector answers (live) or would answer once started. */
|
|
30
34
|
url: string;
|
|
31
35
|
icon?: string;
|
|
36
|
+
/** kraftwerk.yml `color`, if set; the UI derives one from the root otherwise. */
|
|
37
|
+
color?: string;
|
|
38
|
+
/** True when the name comes from kraftwerk.yml, false when it is just the folder name. */
|
|
39
|
+
named?: boolean;
|
|
32
40
|
live: boolean;
|
|
33
41
|
root?: string;
|
|
34
42
|
/** Root with ~ for the home dir — what the UI prints. */
|
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import fsSync, { promises as fs } from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { isDir, resolveProject } from "../config.js";
|
|
6
|
+
import { absolutePath, isDir, resolveProject } from "../config.js";
|
|
7
7
|
import { selfCommand } from "./self-command.js";
|
|
8
8
|
/**
|
|
9
9
|
* Two registries under ~/.kraftwerk, two lifecycles:
|
|
@@ -31,7 +31,7 @@ const LOGS_DIR = path.join(HOME, "logs");
|
|
|
31
31
|
const selfFile = () => path.join(INSTANCES_DIR, `${process.pid}.json`);
|
|
32
32
|
let selfPort = null;
|
|
33
33
|
let selfRoot = null;
|
|
34
|
-
const projectKey = (root) => createHash("sha1").update(
|
|
34
|
+
const projectKey = (root) => createHash("sha1").update(absolutePath(root)).digest("hex").slice(0, 16);
|
|
35
35
|
const projectFile = (root) => path.join(PROJECTS_DIR, `${projectKey(root)}.json`);
|
|
36
36
|
/** ~/… for display. */
|
|
37
37
|
export const tildify = (p) => {
|
|
@@ -42,7 +42,7 @@ export const tildify = (p) => {
|
|
|
42
42
|
/** Write this instance's registry file (call once the server listens). */
|
|
43
43
|
export async function registerInstance(port, root) {
|
|
44
44
|
selfPort = port;
|
|
45
|
-
selfRoot =
|
|
45
|
+
selfRoot = absolutePath(root);
|
|
46
46
|
try {
|
|
47
47
|
await fs.mkdir(INSTANCES_DIR, { recursive: true });
|
|
48
48
|
const rec = { pid: process.pid, port, startedAt: new Date().toISOString(), root: selfRoot };
|
|
@@ -86,6 +86,8 @@ async function probeAt(host, port) {
|
|
|
86
86
|
name: meta.projectName || `localhost:${port}`,
|
|
87
87
|
url: `http://localhost:${port}`,
|
|
88
88
|
icon: meta.projectIcon || undefined,
|
|
89
|
+
color: meta.projectColor || undefined,
|
|
90
|
+
named: typeof meta.projectNamed === "boolean" ? meta.projectNamed : undefined,
|
|
89
91
|
live: true,
|
|
90
92
|
};
|
|
91
93
|
}
|
|
@@ -151,7 +153,7 @@ async function readProject(file) {
|
|
|
151
153
|
}
|
|
152
154
|
/** Upsert the project record for a root (call on every inspector start). */
|
|
153
155
|
export async function registerProject(root) {
|
|
154
|
-
const abs =
|
|
156
|
+
const abs = absolutePath(root);
|
|
155
157
|
const now = new Date().toISOString();
|
|
156
158
|
try {
|
|
157
159
|
await fs.mkdir(PROJECTS_DIR, { recursive: true });
|
|
@@ -232,7 +234,7 @@ export async function forgetProject(root) {
|
|
|
232
234
|
* so without this check a stale record would wear its parent's identity.
|
|
233
235
|
*/
|
|
234
236
|
async function describeRoot(root) {
|
|
235
|
-
const fallback = { name: path.basename(root), port: 1981, exists: false };
|
|
237
|
+
const fallback = { name: path.basename(root), named: false, port: 1981, exists: false };
|
|
236
238
|
if (!(await isDir(root)))
|
|
237
239
|
return fallback;
|
|
238
240
|
try {
|
|
@@ -241,7 +243,9 @@ async function describeRoot(root) {
|
|
|
241
243
|
return fallback;
|
|
242
244
|
return {
|
|
243
245
|
name: project.config.name ?? path.basename(project.root),
|
|
246
|
+
named: !!project.config.name,
|
|
244
247
|
icon: project.config.icon || undefined,
|
|
248
|
+
color: project.config.color || undefined,
|
|
245
249
|
port: project.config.port ?? 1981,
|
|
246
250
|
exists: true,
|
|
247
251
|
};
|
|
@@ -278,15 +282,29 @@ export async function discoverWorkspaces() {
|
|
|
278
282
|
}
|
|
279
283
|
if (running) {
|
|
280
284
|
consumed.add(running);
|
|
281
|
-
|
|
285
|
+
// An instance from before 0.38 answers without color/named — read them from its root.
|
|
286
|
+
if (running.named === undefined)
|
|
287
|
+
d = await describeRoot(p.root);
|
|
288
|
+
return {
|
|
289
|
+
...base,
|
|
290
|
+
name: running.name,
|
|
291
|
+
url: running.url,
|
|
292
|
+
icon: running.icon,
|
|
293
|
+
color: running.color ?? d?.color,
|
|
294
|
+
named: running.named ?? d?.named,
|
|
295
|
+
live: true,
|
|
296
|
+
exists: true,
|
|
297
|
+
};
|
|
282
298
|
}
|
|
283
|
-
return { ...base, name: d.name, url: `http://localhost:${d.port}`, icon: d.icon, live: false, exists: d.exists };
|
|
299
|
+
return { ...base, name: d.name, url: `http://localhost:${d.port}`, icon: d.icon, color: d.color, named: d.named, live: false, exists: d.exists };
|
|
284
300
|
}))).concat(await Promise.all(live
|
|
285
301
|
.filter((i) => !consumed.has(i))
|
|
286
302
|
.map(async (i) => ({
|
|
287
303
|
name: i.name,
|
|
288
304
|
url: i.url,
|
|
289
305
|
icon: i.icon,
|
|
306
|
+
color: i.color,
|
|
307
|
+
named: i.named,
|
|
290
308
|
live: true,
|
|
291
309
|
root: i.root,
|
|
292
310
|
rootLabel: i.root ? tildify(i.root) : undefined,
|
|
@@ -335,6 +353,8 @@ export async function listWorkspacesDetailed() {
|
|
|
335
353
|
all.unshift({
|
|
336
354
|
name: d.name,
|
|
337
355
|
icon: d.icon,
|
|
356
|
+
color: d.color,
|
|
357
|
+
named: d.named,
|
|
338
358
|
url: `http://localhost:${selfPort}`,
|
|
339
359
|
live: true,
|
|
340
360
|
root: selfRoot,
|
|
@@ -395,7 +415,7 @@ export async function listWorkspacesDetailed() {
|
|
|
395
415
|
* straight to it.
|
|
396
416
|
*/
|
|
397
417
|
export async function startProject(root) {
|
|
398
|
-
const abs =
|
|
418
|
+
const abs = absolutePath(root);
|
|
399
419
|
const { name, port, exists } = await describeRoot(abs);
|
|
400
420
|
if (!exists)
|
|
401
421
|
return { ok: false, error: `not a project directory (missing or moved): ${abs}` };
|
|
@@ -464,12 +484,12 @@ export async function startProject(root) {
|
|
|
464
484
|
* that registered no root. Waits up to ~5s for the port to go quiet.
|
|
465
485
|
*/
|
|
466
486
|
export async function stopProject(target) {
|
|
467
|
-
if (target.root &&
|
|
487
|
+
if (target.root && absolutePath(target.root) === selfRoot) {
|
|
468
488
|
return { ok: false, error: "that is this workspace — stop it from its own terminal or pid" };
|
|
469
489
|
}
|
|
470
490
|
cache = wsCache = null;
|
|
471
491
|
const live = await discoverInstances();
|
|
472
|
-
const inst = live.find((i) => (target.root && i.root ===
|
|
492
|
+
const inst = live.find((i) => (target.root && i.root === absolutePath(target.root)) || (target.url && i.url === target.url));
|
|
473
493
|
if (!inst)
|
|
474
494
|
return { ok: false, error: "not running" };
|
|
475
495
|
try {
|
package/dist/inspector/server.js
CHANGED
|
@@ -213,6 +213,8 @@ async function handleApi(req, res, url) {
|
|
|
213
213
|
restartable: supervised(),
|
|
214
214
|
projectName,
|
|
215
215
|
projectIcon: project?.config.icon ?? "",
|
|
216
|
+
projectColor: project?.config.color ?? "",
|
|
217
|
+
projectNamed: !!project?.config.name,
|
|
216
218
|
projectRoot: project?.root ?? getProjectRoot(),
|
|
217
219
|
projectRootLabel: tildify(project?.root ?? getProjectRoot()),
|
|
218
220
|
git: (project?.config.git && project.config.git.enabled !== false) === true,
|
|
@@ -20,6 +20,8 @@ export interface SaveSettingsInput {
|
|
|
20
20
|
/** Omitted = untouched; empty string = remove the key. */
|
|
21
21
|
name?: string;
|
|
22
22
|
icon?: string;
|
|
23
|
+
/** Omitted = untouched; empty string = remove the key. Hex colour otherwise. */
|
|
24
|
+
color?: string;
|
|
23
25
|
/** Omitted = untouched; empty list = remove the key. */
|
|
24
26
|
switcher?: SwitcherEntry[];
|
|
25
27
|
/** Omitted = untouched. See cleanGit for how the block is written. */
|
|
@@ -94,6 +94,13 @@ export async function saveSettings(input) {
|
|
|
94
94
|
throw new Error("name must be a string");
|
|
95
95
|
if (input.icon !== undefined && typeof input.icon !== "string")
|
|
96
96
|
throw new Error("icon must be a string");
|
|
97
|
+
if (input.color !== undefined) {
|
|
98
|
+
if (typeof input.color !== "string")
|
|
99
|
+
throw new Error("color must be a string");
|
|
100
|
+
if (input.color.trim() && !/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(input.color.trim())) {
|
|
101
|
+
throw new Error('color must be a hex colour like "#c2410c"');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
97
104
|
const switcher = input.switcher === undefined ? undefined : cleanSwitcher(input.switcher);
|
|
98
105
|
const project = await resolveProject(getProjectRoot());
|
|
99
106
|
const configPath = project.configPath ?? path.join(project.root, "kraftwerk.yml");
|
|
@@ -111,6 +118,7 @@ export async function saveSettings(input) {
|
|
|
111
118
|
};
|
|
112
119
|
setOrDelete("name", input.name?.trim());
|
|
113
120
|
setOrDelete("icon", input.icon?.trim());
|
|
121
|
+
setOrDelete("color", input.color?.trim());
|
|
114
122
|
setOrDelete("switcher", switcher);
|
|
115
123
|
if (input.git !== undefined) {
|
|
116
124
|
const git = cleanGit(input.git);
|